Three-tier processing gate: Observe (Seshat) → Classify → Act with manual WI creation #3
Loading…
Reference in a new issue
No description provided.
Delete branch "claude/wi-019e535b-three-tier-processing-gate-observe-sesha"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Draft PR for Anima work item Three-tier processing gate: Observe (Seshat) → Classify → Act with manual WI creation (
019e535b-10a2-7b73-972f-07c984fee9aa).This PR is a scaffold opened by
anima-triage. The implementation worker picks it up from here..anima/wip/019e535b.mdtriagedClose or mark ready manually once the implementation lands.
WIP: Three-tier processing gate: Observe (Seshat) → Classify → Act with manual WI creationto Three-tier processing gate: Observe (Seshat) → Classify → Act with manual WI creationThree-tier processing gate: Observe (Seshat) → Classify → Act with manual WI creationto WIP: Three-tier processing gate: Observe (Seshat) → Classify → Act with manual WI creationWIP: Three-tier processing gate: Observe (Seshat) → Classify → Act with manual WI creationto Three-tier processing gate: Observe (Seshat) → Classify → Act with manual WI creationThree-tier processing gate: Observe (Seshat) → Classify → Act with manual WI creationto WIP: Three-tier processing gate: Observe (Seshat) → Classify → Act with manual WI creationWIP: Three-tier processing gate: Observe (Seshat) → Classify → Act with manual WI creationto Three-tier processing gate: Observe (Seshat) → Classify → Act with manual WI creationThree-tier processing gate: Observe (Seshat) → Classify → Act with manual WI creationto WIP: Three-tier processing gate: Observe (Seshat) → Classify → Act with manual WI creationWIP: Three-tier processing gate: Observe (Seshat) → Classify → Act with manual WI creationto Three-tier processing gate: Observe (Seshat) → Classify → Act with manual WI creationThree-tier processing gate: Observe (Seshat) → Classify → Act with manual WI creationto WIP: Three-tier processing gate: Observe (Seshat) → Classify → Act with manual WI creationWIP: Three-tier processing gate: Observe (Seshat) → Classify → Act with manual WI creationto Three-tier processing gate: Observe (Seshat) → Classify → Act with manual WI creationThree-tier processing gate: Observe (Seshat) → Classify → Act with manual WI creationto WIP: Three-tier processing gate: Observe (Seshat) → Classify → Act with manual WI creationWIP: Three-tier processing gate: Observe (Seshat) → Classify → Act with manual WI creationto Three-tier processing gate: Observe (Seshat) → Classify → Act with manual WI creationThree-tier processing gate: Observe (Seshat) → Classify → Act with manual WI creationto WIP: Three-tier processing gate: Observe (Seshat) → Classify → Act with manual WI creationWIP: Three-tier processing gate: Observe (Seshat) → Classify → Act with manual WI creationto Three-tier processing gate: Observe (Seshat) → Classify → Act with manual WI creationThree-tier processing gate: Observe (Seshat) → Classify → Act with manual WI creationto WIP: Three-tier processing gate: Observe (Seshat) → Classify → Act with manual WI creationWIP: Three-tier processing gate: Observe (Seshat) → Classify → Act with manual WI creationto Three-tier processing gate: Observe (Seshat) → Classify → Act with manual WI creationThree-tier processing gate: Observe (Seshat) → Classify → Act with manual WI creationto WIP: Three-tier processing gate: Observe (Seshat) → Classify → Act with manual WI creationWIP: Three-tier processing gate: Observe (Seshat) → Classify → Act with manual WI creationto Three-tier processing gate: Observe (Seshat) → Classify → Act with manual WI creationThree-tier processing gate: Observe (Seshat) → Classify → Act with manual WI creationto WIP: Three-tier processing gate: Observe (Seshat) → Classify → Act with manual WI creationWIP: Three-tier processing gate: Observe (Seshat) → Classify → Act with manual WI creationto Three-tier processing gate: Observe (Seshat) → Classify → Act with manual WI creationThree-tier processing gate: Observe (Seshat) → Classify → Act with manual WI creationto WIP: Three-tier processing gate: Observe (Seshat) → Classify → Act with manual WI creationWIP: Three-tier processing gate: Observe (Seshat) → Classify → Act with manual WI creationto Three-tier processing gate: Observe (Seshat) → Classify → Act with manual WI creationThree-tier processing gate: Observe (Seshat) → Classify → Act with manual WI creationto WIP: Three-tier processing gate: Observe (Seshat) → Classify → Act with manual WI creation3cffa728b314d10d331e14d10d331edb95e37be7db95e37be7ef2e157655ef2e1576557113c9ebfb7113c9ebfb48f120a97b48f120a97b3cdce92d823cdce92d8220c199553520c1995535f296c92d07f296c92d07621791f827621791f827f9a7da11f2f9a7da11f2f1be81ff08f1be81ff089efdd6540c9efdd6540c86d320b67a86d320b67ad28e8984a8d28e8984a82ad6fa4cdc2ad6fa4cdc7beabeb7db7beabeb7db32a6e2e0cb32a6e2e0cbaa651fb14faa651fb14fede1c11af0ede1c11af065cfe0329365cfe03293b41c58309db41c58309dc7bb801948`get_project_ci_status` reported `resolvable: false` for Sia — no `default_ci_steps` and no `.solstice/job.sh` — so every native dispatch failed at config resolution before running anything. That failure is not neutral. anima-server's solstice.rs routes it to `handle_config_failure`, which parks the work item with `ParkedReason::CiConfigUnresolvable`. The ABSENCE of this file reads as "this work item's CI is broken" and parks real work. akh-medu learned this the expensive way (8/8 native dispatches failed, the WI 1/2/3 series parked) and its job.sh states the rule: a missing file must not read as "the code is red". Sia had CI only on the Forgejo-Actions lane, so the native lane — the one Anima's exact-head gate reads — had never produced a verdict at all. One definition of the checks, two lanes --------------------------------------- scripts/ci-checks.sh is now the only place the cargo commands are written. Both .solstice/job.sh and .forgejo/workflows/ci.yml call it; neither re-lists them. akh-medu's rule is that a native verdict must never be red for work the Actions lane would call green, because the disagreement parks work items on phantom failures. Two inline copies drift the first time someone edits one; sharing the script makes that structurally impossible rather than a reviewer's responsibility. job.sh only provisions. It deliberately differs from the Anima/akh-medu scripts in two places: - It does not assume passwordless sudo. Those scripts call `sudo apt-get` unconditionally because their image grants it; a runner without sudo would fail with a bare "sudo: command not found" that classifies as a code failure and parks the item. Privilege is resolved once, only missing packages are installed, and the no-privilege case exits with a message naming the packages and the reason. - It does not `rm -rf ../anima`. The workflow did, and on a persistent or shared runner that deletes a checkout the job does not own. An existing sibling is reused; a non-git path there is refused rather than removed. The resolved revision is always logged so an upstream anima-proto change is attributable. ANIMA_SIBLING_REV pins a run without freezing CI to a revision the shipped image does not use. protoc keeps ci.yml's pinned 25.3 release, since Ubuntu's 3.12 rejects anima-proto's proto3 `optional` fields, and installs into the checkout so it needs no privileges. The build is debug: release codegen is what OOM-killed rustc on the Solstice VMs for akh-medu, and an OOM reads as red on healthy code. Sia's release build is still covered by the `docker` job on push to main. Validation ---------- scripts/test-ci-job.sh replaces every external command with a logging stub and asserts on the control flow — the branches that only execute on a runner in an unusual state, i.e. never until the day they matter. 10 scenarios, 36 assertions, all passing; it needs no toolchain and runs in seconds, so it is wired into ci-checks.sh and guards the CI definition on both lanes. It earned its keep immediately, catching three defects before any runner saw the script: `"${profile_args[@]}"` on an empty array is an unbound-variable error under `set -u` on bash < 4.4, so the script would have worked on the runner but not locally, defeating the point of sharing it; the openssl probe hardcoded /usr/include/openssl/ssl.h and reported a false "missing" on any other layout, triggering a pointless privileged install; and the harness's own no-sudo case was inheriting the host's cached sudo credentials, leaving the no-privilege branch untested exactly where it was most likely to be wrong. No application change: no Cargo.toml edit, no dependency version change. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>e5ead22All four were real. Reproduced each before fixing. 1. The install died as root. `$SUDO DEBIAN_FRONTEND=noninteractive apt-get` looks like an assignment prefix but is not one: bash decides that from the text BEFORE expansion, and the first word is `$SUDO`. Once that expands to nothing — i.e. running as root, the case that should always work — the assignment becomes the command name: $ SUDO=""; $SUDO DEMO_ONLY=1 true bash: DEMO_ONLY=1: command not found (rc=127) Routed through `env`, which is a real command in both shapes. The harness now exercises root explicitly via an `id` stub rather than only covering sudo/non-root, so the escalation-free path has real coverage. 2. A git worktree's `.git` is a FILE holding a gitdir pointer, so `[ -d .git ]` rejected a perfectly valid shared worktree — and the next branch then refused to run at all. This checkout is itself a worktree, so the script would have rejected the very tree it was written in. Now asks git (`rev-parse --is-inside-work-tree`) and confirms the path is that worktree's ROOT by comparing realpaths, so a subdirectory of an unrelated repo above it is still refused — non-destructively. 3. ANIMA_SIBLING_REV claimed a pin it never enforced: it was only applied on the clone path, so with an existing checkout the requested revision was silently ignored and the verdict was attributed to a revision that was never built. Now validated against the existing checkout and REFUSED on mismatch, without touching a checkout this job does not own. An unknown rev is likewise refused rather than fetched into it. ANIMA_SIBLING_DIR is gone. It was misleading: cargo reads the override path from the workspace [patch] table, so pointing the variable elsewhere provisioned a directory cargo would never look at while the build still failed on the real path. The sibling location is now derived from Cargo.toml, which is the only thing that actually decides it, and a missing [patch] path fails loudly instead of guessing. 4. `clippy --workspace --all-targets -- -D warnings` was stricter than the ci.yml it replaced (libs/bins only). Pulling tests, benches and examples into a -D warnings gate for the first time risks an unrelated baseline failure landing as a red verdict on unrelated work — the phantom-failure park this work item exists to stop. Restored to the previously-verified scope. Widening is a separate change that needs a clean baseline run and then moves both lanes together. Also fixes unbounded recursion introduced when the harness was wired into ci-checks.sh: the harness runs job.sh, which execs ci-checks.sh, which ran the harness. Guarded with CI_SKIP_JOB_HARNESS, which the harness sets for its children, and the guard itself is now covered. Harness: 18 scenarios, 62 assertions, all passing. New fixtures are real, not mocked around — an actual shared worktree created with `git worktree add`, an actual non-root checkout, actual commit SHAs for the pin cases, and a real Cargo.toml with a [patch] table so the derivation runs the same code path the runner takes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>Most of this work item already landed. main's decide_inbox consults ctx.route for List, Service and Personal and returns LeaveAlone{NotConfigured} when none matches, so "zero cost until a project route is explicitly configured" holds for those kinds; route-pinned work items already gate thread posts (file_into_route matches on route.work_item); and every routed path already sends a derived summary to Seshat rather than the body. Two rules were still broken. Travel bypassed the gate. decide_inbox dispatched HeaderKind::Travel without consulting ctx.route at all — the one inbox kind that did — and decide_travel then unconditionally emitted AnimaEnsureProject{"Travel"} plus SeshatIngest. On a deployment with zero routes configured a booking confirmation was therefore summarized, ingested, and had a project created for it. This is not hypothetical: the Anima project list carries a "Travel / Auto-curated travel itinerary inbox" project that nobody registered by hand. Travel is now gated like every other kind. The existing test asserted the bypass as intent ("Travel routes regardless of sender_routes config"); it is replaced by a pair covering both sides, so the feature still works once a route exists. The owner-sent path put the raw body in Seshat. decide_owner_sent computed content = readable_body(...) and passed it to BOTH the Anima description and SeshatIngest.body, making it the single leak of "raw mail bodies are never stored in Seshat. Only summaries." It now derives a summary the same way the other paths do and sends that, while content still reaches the Anima description — the acceptance rules restrict Seshat, not Anima, and the body stays useful where a human reads it. The new verdict_summary helper reuses the existing per-variant summarizers rather than adding a sixth near-duplicate, because an owner reply can carry any body shape. Deliberately NOT implemented, because main has moved past the brief: - "sia never creates work items" is superseded. main emits AnimaReconcileWorkItem for list mail under a documented rationale ("this makes discussions visible in Anima ... rather than hidden"), backed by newer infrastructure (work_item_links, migration m20260714_000001) and an open WI-12 about reconcile_work_item's draft-gate provenance guard. Removing it would revert a later product decision and break active work. - The structured escalation filters (header_kind / list_id / sender_pattern / subject_contains) were replaced by the per-route natural-language filter from WI-9, which is done and merged. Both are reported rather than "fixed". Tests: 12 in the engine suite (4 new), 25 in the lib, all passing. clippy -p sia-mail clean at the ci.yml scope, cargo fmt --all --check clean, cargo check --workspace clean. No production mail was touched; every assertion is over a constructed ParsedContext. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>befa312into the recovery branch bb9cf04f66WIP: Three-tier processing gate: Observe (Seshat) → Classify → Act with manual WI creationto Three-tier processing gate: Observe (Seshat) → Classify → Act with manual WI creationView command line instructions
Checkout
From your project repository, check out a new branch and test the changes.Merge
Merge the changes and update on Forgejo.Warning: The "Autodetect manual merge" setting is not enabled for this repository, you will have to mark this pull request as manually merged afterwards.