diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..5448eda1 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +/schemas/cultnet/epiphany.pipeline.*.schema.json text eol=lf diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 156588d7..00000000 --- a/.gitmodules +++ /dev/null @@ -1,6 +0,0 @@ -[submodule "vendor/cultcache-rs"] - path = vendor/cultcache-rs - url = https://github.com/GameCult/cultcache-rs.git -[submodule "vendor/cultnet-rs"] - path = vendor/cultnet-rs - url = https://github.com/GameCult/cultnet-rs.git diff --git a/AGENTS.md b/AGENTS.md index cd4da4bf..ac980edc 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,7 +2,10 @@ ## Project Purpose -This repo is the forge-temple of Epiphany: an opinionated Codex fork where +This repo is the forge-temple of Epiphany: an opinionated agent harness, born +as a Codex fork and now backend-agnostic. Codex, Claude, and OpenAI-compatible +APIs speak to it through connectors that share one standardized +Epiphany-facing CultNet API, the same one Ghostlight uses. It is the harness where external typed state, explicit mental maps, bounded scratch work, verifier evidence, and anti-churn discipline are wired into the blessed harness instead of taped onto the chat transcript like a purity seal over a cracked reactor. @@ -102,7 +105,6 @@ machine-spirit awake. ## Important Paths - Project root: `F:\Projects\Epiphany` -- Vendored Codex repo: `F:\Projects\Epiphany\vendor\codex` - Fork implementation plan: `F:\Projects\Epiphany\notes\epiphany-fork-implementation-plan.md` - Handoff summary: `F:\Projects\Epiphany\notes\fresh-workspace-handoff.md` - Epiphany algorithmic map: `F:\Projects\Epiphany\notes\epiphany-current-algorithmic-map.md` @@ -123,14 +125,6 @@ $env:CARGO_TARGET_DIR='C:\Users\Meta\.cargo-target-codex' cargo run --manifest-path .\Cargo.toml --bin epiphany-state -- add-evidence --type research --status ok --note '...' ``` -Useful Codex repo searches: - -```powershell -rg -n "pub enum ModeKind|TUI_VISIBLE_COLLABORATION_MODES" .\vendor\codex\codex-rs\protocol\src\config_types.rs -rg -n "builtin_collaboration_mode_presets|fn plan_preset|fn default_preset" .\vendor\codex\codex-rs\models-manager\src\collaboration_mode_presets.rs -rg -n "collaboration_mode_label|collaboration_mode_indicator|set_collaboration_mask" .\vendor\codex\codex-rs\tui\src\chatwidget.rs -``` - ## Session Bootstrap And Re-entry Rite On fresh awakening, perform this rite before wandering into implementation like diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..640c1a14 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,3 @@ +Repository doctrine for this project lives in AGENTS.md, shared with Codex. + +@AGENTS.md diff --git a/Cargo.lock b/Cargo.lock index 7d71f4c5..7d75c250 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,29 +2,13 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "Inflector" -version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" -dependencies = [ - "lazy_static", - "regex", -] - -[[package]] -name = "adler2" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" - [[package]] name = "aead" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" dependencies = [ - "crypto-common 0.1.7", + "crypto-common", "generic-array", ] @@ -53,19 +37,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "ahash" -version = "0.8.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" -dependencies = [ - "cfg-if", - "getrandom 0.3.4", - "once_cell", - "version_check", - "zerocopy", -] - [[package]] name = "aho-corasick" version = "1.1.5" @@ -75,36 +46,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "allocative" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fac2ce611db8b8cee9b2aa886ca03c924e9da5e5295d0dbd0526e5d0b0710f7" -dependencies = [ - "allocative_derive", - "bumpalo", - "ctor", - "hashbrown 0.14.5", - "num-bigint", -] - -[[package]] -name = "allocative_derive" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe233a377643e0fc1a56421d7c90acdec45c291b30345eb9f08e8d0ddce5a4ab" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "allocator-api2" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" - [[package]] name = "android_system_properties" version = "0.1.6" @@ -114,256 +55,12 @@ dependencies = [ "libc", ] -[[package]] -name = "annotate-snippets" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccaf7e9dfbb6ab22c82e473cd1a8a7bd313c19a5b7e40970f3d89ef5a5c9e81e" -dependencies = [ - "unicode-width", -] - -[[package]] -name = "anstream" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" -dependencies = [ - "anstyle", - "anstyle-parse", - "anstyle-query", - "anstyle-wincon", - "colorchoice", - "is_terminal_polyfill", - "utf8parse", -] - -[[package]] -name = "anstyle" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" - -[[package]] -name = "anstyle-parse" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" -dependencies = [ - "utf8parse", -] - -[[package]] -name = "anstyle-query" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "anstyle-wincon" -version = "3.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" -dependencies = [ - "anstyle", - "once_cell_polyfill", - "windows-sys 0.61.2", -] - [[package]] name = "anyhow" version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" -[[package]] -name = "ascii" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16" - -[[package]] -name = "ascii-canvas" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8824ecca2e851cec16968d54a01dd372ef8f95b244fb84b84e70128be347c3c6" -dependencies = [ - "term", -] - -[[package]] -name = "asn1-rs" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7f43a50ac4fdca5df8e885c21b835997f0a1cdee65494a6847694a98652d9d8" -dependencies = [ - "asn1-rs-derive", - "asn1-rs-impl", - "displaydoc", - "nom 7.1.3", - "num-traits", - "rusticata-macros", - "thiserror 2.0.20", - "time", -] - -[[package]] -name = "asn1-rs-derive" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", - "synstructure", -] - -[[package]] -name = "asn1-rs-impl" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "async-broadcast" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" -dependencies = [ - "event-listener", - "event-listener-strategy", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-channel" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" -dependencies = [ - "concurrent-queue", - "event-listener-strategy", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-executor" -version = "1.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a" -dependencies = [ - "async-task", - "concurrent-queue", - "fastrand", - "futures-lite", - "pin-project-lite", - "slab", -] - -[[package]] -name = "async-fs" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8034a681df4aed8b8edbd7fbe472401ecf009251c8b40556b304567052e294c5" -dependencies = [ - "async-lock", - "blocking", - "futures-lite", -] - -[[package]] -name = "async-io" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" -dependencies = [ - "autocfg", - "cfg-if", - "concurrent-queue", - "futures-io", - "futures-lite", - "parking", - "polling", - "rustix", - "slab", - "windows-sys 0.61.2", -] - -[[package]] -name = "async-lock" -version = "3.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" -dependencies = [ - "event-listener", - "event-listener-strategy", - "pin-project-lite", -] - -[[package]] -name = "async-process" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75" -dependencies = [ - "async-channel", - "async-io", - "async-lock", - "async-signal", - "async-task", - "blocking", - "cfg-if", - "event-listener", - "futures-lite", - "rustix", -] - -[[package]] -name = "async-recursion" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "async-signal" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52b5aaafa020cf5053a01f2a60e8ff5dccf550f0f77ec54a4e47285ac2bab485" -dependencies = [ - "async-io", - "async-lock", - "atomic-waker", - "cfg-if", - "futures-core", - "futures-io", - "rustix", - "signal-hook-registry", - "slab", - "windows-sys 0.61.2", -] - -[[package]] -name = "async-task" -version = "4.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" - [[package]] name = "async-trait" version = "0.1.92" @@ -375,16 +72,6 @@ dependencies = [ "syn 3.0.3", ] -[[package]] -name = "asynk-strim" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52697735bdaac441a29391a9e97102c74c6ef0f9b60a40cf109b1b404e29d2f6" -dependencies = [ - "futures-core", - "pin-project-lite", -] - [[package]] name = "atomic-waker" version = "1.1.2" @@ -404,7 +91,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce2b2dcc879c3bae0d371e77c99f2238400ef24ec001394befa67b6e543add9e" dependencies = [ "aws-lc-sys", - "untrusted 0.7.1", "zeroize", ] @@ -427,63 +113,24 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "base64" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5" + [[package]] name = "base64ct" version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" -[[package]] -name = "beef" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1" - -[[package]] -name = "bit-set" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" -dependencies = [ - "bit-vec 0.6.3", -] - -[[package]] -name = "bit-vec" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" - -[[package]] -name = "bit-vec" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b71798fca2c1fe1086445a7258a4bc81e6e49dcd24c8d0dd9a1e57395b603f51" -dependencies = [ - "serde", -] - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - [[package]] name = "bitflags" version = "2.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" -[[package]] -name = "blake2" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" -dependencies = [ - "digest 0.10.7", -] - [[package]] name = "block-buffer" version = "0.10.4" @@ -493,24 +140,6 @@ dependencies = [ "generic-array", ] -[[package]] -name = "block-buffer" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" -dependencies = [ - "hybrid-array", -] - -[[package]] -name = "block-padding" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" -dependencies = [ - "generic-array", -] - [[package]] name = "block2" version = "0.6.2" @@ -520,38 +149,6 @@ dependencies = [ "objc2", ] -[[package]] -name = "blocking" -version = "1.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" -dependencies = [ - "async-channel", - "async-task", - "futures-io", - "futures-lite", - "piper", -] - -[[package]] -name = "borsh" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a88b7ea17d208c4193f2c1e6de3c35fe71f98c96982d5ced308bdcc749ff6e1f" -dependencies = [ - "bytes", - "cfg_aliases 0.2.2", -] - -[[package]] -name = "bs58" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" -dependencies = [ - "tinyvec", -] - [[package]] name = "bstr" version = "1.13.0" @@ -568,39 +165,12 @@ version = "3.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" -[[package]] -name = "bytemuck" -version = "1.25.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797" - -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - -[[package]] -name = "byteorder-lite" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" - [[package]] name = "bytes" version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" -[[package]] -name = "cbc" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" -dependencies = [ - "cipher", -] - [[package]] name = "cc" version = "1.4.2" @@ -610,7 +180,7 @@ dependencies = [ "find-msvc-tools", "jobserver", "libc", - "shlex 2.0.1", + "shlex", ] [[package]] @@ -619,12 +189,6 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" -[[package]] -name = "cfg_aliases" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" - [[package]] name = "cfg_aliases" version = "0.2.2" @@ -642,17 +206,6 @@ dependencies = [ "rand_core 0.10.1", ] -[[package]] -name = "chardetng" -version = "0.1.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14b8f0b65b7b08ae3c8187e8d77174de20cb6777864c6b832d8ad365999cf1ea" -dependencies = [ - "cfg-if", - "encoding_rs", - "memchr", -] - [[package]] name = "chrono" version = "0.4.45" @@ -667,70 +220,14 @@ dependencies = [ "windows-link", ] -[[package]] -name = "chunked_transfer" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e4de3bc4ea267985becf712dc6d9eed8b04c953b3fcfb339ebc87acd9804901" - [[package]] name = "cipher" version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ - "crypto-common 0.1.7", + "crypto-common", "inout", - "zeroize", -] - -[[package]] -name = "clap" -version = "4.6.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "473c7e07f409a8d772161724aa8db6a765a2532a70f9667eeb7b49d3d02fbdca" -dependencies = [ - "clap_builder", - "clap_derive", -] - -[[package]] -name = "clap_builder" -version = "4.6.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b48fea5a88e9ae728a2dcbedbfc0e730f7d60da42e1cb049a83c9fb8b789889" -dependencies = [ - "anstream", - "anstyle", - "clap_lex", - "strsim 0.11.1", -] - -[[package]] -name = "clap_derive" -version = "4.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d012d2b9d65aca7f18f4d9878a045bc17899bba951561ba5ec3c2ba1eed9a061" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn 3.0.3", -] - -[[package]] -name = "clap_lex" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" - -[[package]] -name = "clipboard-win" -version = "5.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bde03770d3df201d4fb868f2c9c59e66a3e4e2bd06692a0fe701e7103c7e84d4" -dependencies = [ - "error-code", ] [[package]] @@ -743,441 +240,21 @@ dependencies = [ ] [[package]] -name = "cmov" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a" - -[[package]] -name = "cmp_any" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9b18233253483ce2f65329a24072ec414db782531bdbb7d0bbc4bd2ce6b7e21" - -[[package]] -name = "codex-agent-identity" -version = "0.0.0" +name = "codex-connector" +version = "0.1.0" +source = "git+https://github.com/GameCult/CodexConnector?rev=be1776ab455fd79bd74b393518ab07211dc9c1f0#be1776ab455fd79bd74b393518ab07211dc9c1f0" dependencies = [ - "anyhow", - "base64", - "chrono", - "codex-protocol", - "crypto_box", - "ed25519-dalek", - "rand 0.9.5", - "reqwest 0.12.28", + "aes-gcm", + "base64 0.23.1", + "rand_core 0.6.4", + "rmp-serde", "serde", "serde_json", - "sha2 0.10.9", + "sha2", + "thiserror", + "zeroize", ] -[[package]] -name = "codex-api" -version = "0.0.0" -dependencies = [ - "async-channel", - "async-trait", - "base64", - "bytes", - "chrono", - "codex-client", - "codex-protocol", - "codex-utils-rustls-provider", - "eventsource-stream", - "futures", - "http", - "regex-lite", - "reqwest 0.12.28", - "serde", - "serde_json", - "thiserror 2.0.20", - "tokio", - "tokio-tungstenite", - "tokio-util", - "tracing", - "tungstenite", - "url", -] - -[[package]] -name = "codex-app-server-protocol" -version = "0.0.0" -dependencies = [ - "anyhow", - "clap", - "codex-experimental-api-macros", - "codex-protocol", - "codex-shell-command", - "codex-utils-absolute-path", - "inventory", - "rmcp 0.15.0", - "schemars 0.8.22", - "serde", - "serde_json", - "serde_with", - "strum_macros", - "thiserror 2.0.20", - "tracing", - "ts-rs", - "uuid", -] - -[[package]] -name = "codex-async-utils" -version = "0.0.0" -dependencies = [ - "async-trait", - "tokio", - "tokio-util", -] - -[[package]] -name = "codex-client" -version = "0.0.0" -dependencies = [ - "async-trait", - "bytes", - "codex-utils-rustls-provider", - "eventsource-stream", - "futures", - "http", - "opentelemetry", - "rand 0.9.5", - "reqwest 0.12.28", - "rustls", - "rustls-native-certs", - "rustls-pki-types", - "serde", - "serde_json", - "thiserror 2.0.20", - "tokio", - "tracing", - "tracing-opentelemetry", - "zstd", -] - -[[package]] -name = "codex-config" -version = "0.0.0" -dependencies = [ - "anyhow", - "async-trait", - "codex-app-server-protocol", - "codex-execpolicy", - "codex-features", - "codex-model-provider-info", - "codex-network-proxy", - "codex-protocol", - "codex-utils-absolute-path", - "codex-utils-path", - "dns-lookup", - "futures", - "gethostname", - "libc", - "multimap", - "schemars 0.8.22", - "serde", - "serde_json", - "serde_path_to_error", - "sha2 0.10.9", - "thiserror 2.0.20", - "tokio", - "toml", - "toml_edit 0.24.1+spec-1.1.0", - "tracing", - "wildmatch", - "winapi-util", -] - -[[package]] -name = "codex-execpolicy" -version = "0.0.0" -dependencies = [ - "anyhow", - "clap", - "codex-utils-absolute-path", - "multimap", - "serde", - "serde_json", - "shlex 1.3.0", - "starlark", - "thiserror 2.0.20", -] - -[[package]] -name = "codex-experimental-api-macros" -version = "0.0.0" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "codex-features" -version = "0.0.0" -dependencies = [ - "codex-otel", - "codex-protocol", - "schemars 0.8.22", - "serde", - "toml", - "tracing", -] - -[[package]] -name = "codex-keyring-store" -version = "0.0.0" -dependencies = [ - "keyring", - "tracing", -] - -[[package]] -name = "codex-login" -version = "0.0.0" -dependencies = [ - "async-trait", - "base64", - "chrono", - "codex-agent-identity", - "codex-app-server-protocol", - "codex-client", - "codex-config", - "codex-keyring-store", - "codex-model-provider-info", - "codex-otel", - "codex-protocol", - "codex-terminal-detection", - "codex-utils-template", - "once_cell", - "os_info", - "rand 0.9.5", - "reqwest 0.12.28", - "serde", - "serde_json", - "sha2 0.10.9", - "thiserror 2.0.20", - "tiny_http", - "tokio", - "tracing", - "url", - "urlencoding", - "webbrowser", -] - -[[package]] -name = "codex-model-provider-info" -version = "0.0.0" -dependencies = [ - "codex-api", - "codex-app-server-protocol", - "codex-protocol", - "http", - "schemars 0.8.22", - "serde", -] - -[[package]] -name = "codex-network-proxy" -version = "0.0.0" -dependencies = [ - "anyhow", - "async-trait", - "chrono", - "clap", - "codex-utils-absolute-path", - "codex-utils-home-dir", - "codex-utils-rustls-provider", - "globset", - "rama-core", - "rama-http", - "rama-http-backend", - "rama-net", - "rama-socks5", - "rama-tcp", - "rama-tls-rustls", - "rama-unix", - "serde", - "serde_json", - "thiserror 2.0.20", - "time", - "tokio", - "tracing", - "url", -] - -[[package]] -name = "codex-otel" -version = "0.0.0" -dependencies = [ - "chrono", - "codex-api", - "codex-app-server-protocol", - "codex-protocol", - "codex-utils-absolute-path", - "codex-utils-string", - "eventsource-stream", - "gethostname", - "http", - "opentelemetry", - "opentelemetry-appender-tracing", - "opentelemetry-otlp", - "opentelemetry-semantic-conventions", - "opentelemetry_sdk", - "os_info", - "reqwest 0.12.28", - "serde", - "serde_json", - "strum_macros", - "thiserror 2.0.20", - "tokio", - "tokio-tungstenite", - "tracing", - "tracing-opentelemetry", - "tracing-subscriber", -] - -[[package]] -name = "codex-protocol" -version = "0.0.0" -dependencies = [ - "chardetng", - "chrono", - "codex-async-utils", - "codex-execpolicy", - "codex-network-proxy", - "codex-utils-absolute-path", - "codex-utils-image", - "codex-utils-string", - "codex-utils-template", - "encoding_rs", - "globset", - "icu_decimal", - "icu_locale_core", - "icu_provider", - "landlock", - "quick-xml", - "reqwest 0.12.28", - "schemars 0.8.22", - "seccompiler", - "serde", - "serde_json", - "serde_with", - "strum", - "strum_macros", - "sys-locale", - "thiserror 2.0.20", - "tokio", - "tracing", - "ts-rs", - "uuid", -] - -[[package]] -name = "codex-shell-command" -version = "0.0.0" -dependencies = [ - "base64", - "codex-protocol", - "codex-utils-absolute-path", - "once_cell", - "regex", - "serde", - "serde_json", - "shlex 1.3.0", - "tree-sitter", - "tree-sitter-bash", - "url", - "which", -] - -[[package]] -name = "codex-terminal-detection" -version = "0.0.0" -dependencies = [ - "tracing", -] - -[[package]] -name = "codex-utils-absolute-path" -version = "0.0.0" -dependencies = [ - "dirs", - "dunce", - "schemars 0.8.22", - "serde", - "ts-rs", -] - -[[package]] -name = "codex-utils-cache" -version = "0.0.0" -dependencies = [ - "lru", - "sha1", - "tokio", -] - -[[package]] -name = "codex-utils-home-dir" -version = "0.0.0" -dependencies = [ - "codex-utils-absolute-path", - "dirs", -] - -[[package]] -name = "codex-utils-image" -version = "0.0.0" -dependencies = [ - "base64", - "codex-utils-cache", - "image", - "mime_guess", - "thiserror 2.0.20", - "tokio", -] - -[[package]] -name = "codex-utils-path" -version = "0.0.0" -dependencies = [ - "codex-utils-absolute-path", - "dunce", - "tempfile", -] - -[[package]] -name = "codex-utils-rustls-provider" -version = "0.0.0" -dependencies = [ - "rustls", -] - -[[package]] -name = "codex-utils-string" -version = "0.0.0" -dependencies = [ - "regex-lite", -] - -[[package]] -name = "codex-utils-template" -version = "0.0.0" - -[[package]] -name = "color_quant" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" - -[[package]] -name = "colorchoice" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" - [[package]] name = "combine" version = "4.6.7" @@ -1188,108 +265,12 @@ dependencies = [ "memchr", ] -[[package]] -name = "concurrent-queue" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "const-hex" -version = "1.19.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33e2a781ebdf4467d1428dc4593067825fb646f6871475098d8577421af73558" -dependencies = [ - "cfg-if", - "cpufeatures 0.2.17", - "proptest", - "serde_core", -] - [[package]] name = "const-oid" version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" -[[package]] -name = "const-oid" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" - -[[package]] -name = "const_format" -version = "0.2.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4481a617ad9a412be3b97c5d403fef8ed023103368908b9c50af598ff467cc1e" -dependencies = [ - "const_format_proc_macros", - "konst", -] - -[[package]] -name = "const_format_proc_macros" -version = "0.2.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744" -dependencies = [ - "proc-macro2", - "quote", - "unicode-xid", -] - -[[package]] -name = "convert_case" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" -dependencies = [ - "unicode-segmentation", -] - -[[package]] -name = "cookie" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a373e3602691c3cdea496d2f0ee5935151e6168fe87739483c463db1b2f2f87" -dependencies = [ - "percent-encoding", - "time", - "version_check", -] - -[[package]] -name = "cookie_store" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15b2c103cf610ec6cae3da84a766285b42fd16aad564758459e6ecf128c75206" -dependencies = [ - "cookie", - "document-features", - "idna", - "log", - "publicsuffix", - "serde", - "serde_derive", - "serde_json", - "time", - "url", -] - -[[package]] -name = "core-foundation" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "core-foundation" version = "0.10.1" @@ -1324,30 +305,6 @@ dependencies = [ "libc", ] -[[package]] -name = "crc32fast" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "critical-section" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" - -[[package]] -name = "crossbeam-channel" -version = "0.5.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d85363c37faeca707aef026efa9f3b34d077bce547e48f770770625c6013679e" -dependencies = [ - "crossbeam-utils", -] - [[package]] name = "crossbeam-deque" version = "0.8.7" @@ -1373,12 +330,6 @@ version = "0.8.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17" -[[package]] -name = "crunchy" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" - [[package]] name = "crypto-common" version = "0.1.7" @@ -1390,76 +341,6 @@ dependencies = [ "typenum", ] -[[package]] -name = "crypto-common" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" -dependencies = [ - "hybrid-array", -] - -[[package]] -name = "crypto_box" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16182b4f39a82ec8a6851155cc4c0cda3065bb1db33651726a29e1951de0f009" -dependencies = [ - "aead", - "blake2", - "crypto_secretbox", - "curve25519-dalek", - "salsa20", - "subtle", - "zeroize", -] - -[[package]] -name = "crypto_secretbox" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d6cf87adf719ddf43a805e92c6870a531aedda35ff640442cbaf8674e141e1" -dependencies = [ - "aead", - "cipher", - "generic-array", - "poly1305", - "salsa20", - "subtle", - "zeroize", -] - -[[package]] -name = "csv" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52cd9d68cf7efc6ddfaaee42e7288d3a99d613d4b50f76ce9827ae0c6e14f938" -dependencies = [ - "csv-core", - "itoa", - "ryu", - "serde_core", -] - -[[package]] -name = "csv-core" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "704a3c26996a80471189265814dbc2c257598b96b8a7feae2d31ace646bb9782" -dependencies = [ - "memchr", -] - -[[package]] -name = "ctor" -version = "0.1.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" -dependencies = [ - "quote", - "syn 1.0.109", -] - [[package]] name = "ctr" version = "0.9.2" @@ -1476,26 +357,36 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0b1fab2ae45819af2d0731d60f2afe17227ebb1a1538a236da84c93e9a60162" dependencies = [ "dispatch2", - "nix 0.31.3", + "nix", "windows-sys 0.61.2", ] [[package]] -name = "ctutils" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e" +name = "cultcache-rs" +version = "0.2.0" +source = "git+https://github.com/GameCult/CultLib.git?rev=8fc74c709444b884a2bd339e0c306bf9484d99d2#8fc74c709444b884a2bd339e0c306bf9484d99d2" dependencies = [ - "cmov", + "anyhow", + "chrono", + "cultcache-rs-derive 0.1.0 (git+https://github.com/GameCult/CultLib.git?rev=8fc74c709444b884a2bd339e0c306bf9484d99d2)", + "fs2", + "redb", + "rmp-serde", + "serde", + "serde_bytes", + "serde_json", + "uuid", + "windows-sys 0.59.0", ] [[package]] name = "cultcache-rs" -version = "0.1.0" +version = "0.2.0" +source = "git+https://github.com/GameCult/CultLib.git?rev=a0813c6eed24d30bf88073ef615b633c77ebfcd6#a0813c6eed24d30bf88073ef615b633c77ebfcd6" dependencies = [ "anyhow", "chrono", - "cultcache-rs-derive", + "cultcache-rs-derive 0.1.0 (git+https://github.com/GameCult/CultLib.git?rev=a0813c6eed24d30bf88073ef615b633c77ebfcd6)", "fs2", "redb", "rmp-serde", @@ -1509,6 +400,17 @@ dependencies = [ [[package]] name = "cultcache-rs-derive" version = "0.1.0" +source = "git+https://github.com/GameCult/CultLib.git?rev=8fc74c709444b884a2bd339e0c306bf9484d99d2#8fc74c709444b884a2bd339e0c306bf9484d99d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "cultcache-rs-derive" +version = "0.1.0" +source = "git+https://github.com/GameCult/CultLib.git?rev=a0813c6eed24d30bf88073ef615b633c77ebfcd6#a0813c6eed24d30bf88073ef615b633c77ebfcd6" dependencies = [ "proc-macro2", "quote", @@ -1518,38 +420,37 @@ dependencies = [ [[package]] name = "cultmesh-rs" version = "0.1.0" +source = "git+https://github.com/GameCult/CultLib.git?rev=a0813c6eed24d30bf88073ef615b633c77ebfcd6#a0813c6eed24d30bf88073ef615b633c77ebfcd6" dependencies = [ "anyhow", - "cultcache-rs", + "cultcache-rs 0.2.0 (git+https://github.com/GameCult/CultLib.git?rev=a0813c6eed24d30bf88073ef615b633c77ebfcd6)", "cultnet-rs", - "pretty_assertions", "serde", - "tempfile", ] [[package]] name = "cultnet-rs" version = "0.1.0" +source = "git+https://github.com/GameCult/CultLib.git?rev=a0813c6eed24d30bf88073ef615b633c77ebfcd6#a0813c6eed24d30bf88073ef615b633c77ebfcd6" dependencies = [ "aes-gcm", "anyhow", - "base64", + "base64 0.22.1", "chrono", - "cultcache-rs", + "cultcache-rs 0.2.0 (git+https://github.com/GameCult/CultLib.git?rev=a0813c6eed24d30bf88073ef615b633c77ebfcd6)", "ed25519-dalek", - "hmac 0.12.1", - "pretty_assertions", + "hmac", "rand 0.9.5", "rmp-serde", "rmpv", "serde", "serde_bytes", "serde_json", - "sha2 0.10.9", + "sha2", "socket2 0.5.10", - "tempfile", "uuid", "windows-sys 0.61.2", + "zeroize", ] [[package]] @@ -1561,7 +462,7 @@ dependencies = [ "cfg-if", "cpufeatures 0.2.17", "curve25519-dalek-derive", - "digest 0.10.7", + "digest", "fiat-crypto", "rustc_version", "subtle", @@ -1579,245 +480,39 @@ dependencies = [ "syn 2.0.119", ] -[[package]] -name = "darling" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" -dependencies = [ - "darling_core", - "darling_macro", -] - -[[package]] -name = "darling_core" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" -dependencies = [ - "ident_case", - "proc-macro2", - "quote", - "strsim 0.11.1", - "syn 2.0.119", -] - -[[package]] -name = "darling_macro" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" -dependencies = [ - "darling_core", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "data-encoding" -version = "2.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4583a4551df46e2792f82ceeac45e850d2e2d5debba0b91f102385cda5b11f06" - -[[package]] -name = "dbus" -version = "0.9.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ab69f03cc8c4340c9c8e315114e1658e6775a9b16a04357973aa21cec22b32e" -dependencies = [ - "libc", - "libdbus-sys", - "windows-sys 0.61.2", -] - -[[package]] -name = "dbus-secret-service" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "708b509edf7889e53d7efb0ffadd994cc6c2345ccb62f55cfd6b0682165e4fa6" -dependencies = [ - "aes", - "block-padding", - "cbc", - "dbus", - "fastrand", - "hkdf", - "num", - "once_cell", - "sha2 0.10.9", - "zeroize", -] - -[[package]] -name = "debugserver-types" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bf6834a70ed14e8e4e41882df27190bea150f1f6ecf461f1033f8739cd8af4a" -dependencies = [ - "schemafy", - "serde", - "serde_json", -] - [[package]] name = "der" version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" dependencies = [ - "const-oid 0.9.6", + "const-oid", "zeroize", ] -[[package]] -name = "der-parser" -version = "10.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07da5016415d5a3c4dd39b11ed26f915f52fc4e0dc197d87908bc916e51bc1a6" -dependencies = [ - "asn1-rs", - "displaydoc", - "nom 7.1.3", - "num-bigint", - "num-traits", - "rusticata-macros", -] - -[[package]] -name = "deranged" -version = "0.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" -dependencies = [ - "serde_core", -] - -[[package]] -name = "derivative" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "derive_more" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" -dependencies = [ - "derive_more-impl", -] - -[[package]] -name = "derive_more-impl" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" -dependencies = [ - "convert_case", - "proc-macro2", - "quote", - "syn 2.0.119", - "unicode-xid", -] - -[[package]] -name = "diff" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" - [[package]] name = "digest" version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer 0.10.4", - "crypto-common 0.1.7", + "block-buffer", + "crypto-common", "subtle", ] -[[package]] -name = "digest" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" -dependencies = [ - "block-buffer 0.12.1", - "const-oid 0.10.2", - "crypto-common 0.2.2", - "ctutils", -] - -[[package]] -name = "dirs" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" -dependencies = [ - "dirs-sys", -] - -[[package]] -name = "dirs-next" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" -dependencies = [ - "cfg-if", - "dirs-sys-next", -] - -[[package]] -name = "dirs-sys" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" -dependencies = [ - "libc", - "option-ext", - "redox_users 0.5.2", - "windows-sys 0.61.2", -] - -[[package]] -name = "dirs-sys-next" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" -dependencies = [ - "libc", - "redox_users 0.4.6", - "winapi", -] - [[package]] name = "dispatch2" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" dependencies = [ - "bitflags 2.13.1", + "bitflags", "block2", "libc", "objc2", ] -[[package]] -name = "display_container" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a110a75c96bedec8e65823dea00a1d710288b7a369d95fd8a0f5127639466fa" -dependencies = [ - "either", - "indenter", -] - [[package]] name = "displaydoc" version = "0.2.7" @@ -1829,53 +524,12 @@ dependencies = [ "syn 3.0.3", ] -[[package]] -name = "dns-lookup" -version = "3.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e39034cee21a2f5bbb66ba0e3689819c4bb5d00382a282006e802a7ffa6c41d" -dependencies = [ - "cfg-if", - "libc", - "socket2 0.6.5", - "windows-sys 0.60.2", -] - -[[package]] -name = "document-features" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" -dependencies = [ - "litrs", -] - [[package]] name = "dunce" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" -[[package]] -name = "dupe" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ed2bc011db9c93fbc2b6cdb341a53737a55bafb46dbb74cf6764fc33a2fbf9c" -dependencies = [ - "dupe_derive", -] - -[[package]] -name = "dupe_derive" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83e195b4945e88836d826124af44fdcb262ec01ef94d44f14f4fb5103f19892a" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - [[package]] name = "dyn-clone" version = "1.0.20" @@ -1901,86 +555,11 @@ dependencies = [ "curve25519-dalek", "ed25519", "serde", - "sha2 0.10.9", + "sha2", "subtle", "zeroize", ] -[[package]] -name = "either" -version = "1.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d" - -[[package]] -name = "ena" -version = "0.14.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eabffdaee24bd1bf95c5ef7cec31260444317e72ea56c4c91750e8b7ee58d5f1" -dependencies = [ - "log", -] - -[[package]] -name = "encoding_rs" -version = "0.8.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "endi" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66b7e2430c6dff6a955451e2cfc438f09cea1965a9d6f87f7e3b90decc014099" - -[[package]] -name = "endian-type" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" - -[[package]] -name = "endian-type" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "869b0adbda23651a9c5c0c3d270aac9fcb52e8622a8f2b17e57802d7791962f2" - -[[package]] -name = "enum-as-inner" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "enumflags2" -version = "0.7.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef" -dependencies = [ - "enumflags2_derive", - "serde", -] - -[[package]] -name = "enumflags2_derive" -version = "0.7.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - [[package]] name = "epiphany-core" version = "0.1.0" @@ -1988,24 +567,21 @@ dependencies = [ "anyhow", "chrono", "ctrlc", - "cultcache-rs", + "cultcache-rs 0.2.0 (git+https://github.com/GameCult/CultLib.git?rev=a0813c6eed24d30bf88073ef615b633c77ebfcd6)", "cultmesh-rs", "cultnet-rs", "ed25519-dalek", "epiphany-model-adapter", - "epiphany-openai-adapter", - "epiphany-release-construction", - "epiphany-state-model", "epiphany-tool-adapter", + "fs2", "ghostlight-persona-projection", "ignore", "libc", - "pretty_assertions", "rmp-serde", "semver", "serde", "serde_json", - "sha2 0.10.9", + "sha2", "tempfile", "uuid", "windows-sys 0.59.0", @@ -2014,34 +590,13 @@ dependencies = [ [[package]] name = "epiphany-model-adapter" version = "0.1.0" -dependencies = [ - "cultcache-rs", - "serde", -] - -[[package]] -name = "epiphany-openai-adapter" -version = "0.1.0" -dependencies = [ - "cultcache-rs", - "epiphany-model-adapter", - "serde", -] - -[[package]] -name = "epiphany-openai-codex-spine" -version = "0.1.0" dependencies = [ "anyhow", - "codex-client", - "codex-login", - "epiphany-openai-adapter", - "futures", - "http", + "codex-connector", + "cultcache-rs 0.2.0 (git+https://github.com/GameCult/CultLib.git?rev=a0813c6eed24d30bf88073ef615b633c77ebfcd6)", "serde", "serde_json", - "sha2 0.10.9", - "tokio", + "sha2", ] [[package]] @@ -2050,89 +605,64 @@ version = "0.1.0" dependencies = [ "anyhow", "chrono", - "codex-login", - "cultcache-rs", - "cultmesh-rs", - "cultnet-rs", + "cultcache-rs 0.2.0 (git+https://github.com/GameCult/CultLib.git?rev=a0813c6eed24d30bf88073ef615b633c77ebfcd6)", "epiphany-core", "epiphany-model-adapter", - "epiphany-openai-adapter", - "epiphany-openai-codex-spine", - "epiphany-state-model", "epiphany-tool-adapter", "rmp-serde", "serde", "serde_json", - "sha2 0.10.9", + "sha2", "tempfile", "tokio", "uuid", ] +[[package]] +name = "epiphany-pipeline" +version = "0.1.0" +dependencies = [ + "anyhow", + "chrono", + "cultcache-rs 0.2.0 (git+https://github.com/GameCult/CultLib.git?rev=8fc74c709444b884a2bd339e0c306bf9484d99d2)", + "rmp-serde", + "schemars", + "serde", + "serde_json", +] + [[package]] name = "epiphany-release-bundle" version = "0.1.0" dependencies = [ "anyhow", "chrono", + "codex-connector", "ctrlc", - "cultcache-rs", - "cultmesh-rs", + "cultcache-rs 0.2.0 (git+https://github.com/GameCult/CultLib.git?rev=a0813c6eed24d30bf88073ef615b633c77ebfcd6)", "cultnet-rs", "epiphany-core", "epiphany-model-adapter", - "epiphany-openai-adapter", "epiphany-openai-runtime", - "epiphany-state-model", "epiphany-tool-adapter", "epiphany-tool-mcp-runtime", - "http", - "ignore", - "postgres", - "reqwest 0.13.4", - "rmcp 2.2.0", "rmp-serde", - "semver", "serde", "serde_json", - "sha2 0.10.9", + "sha2", "tempfile", "tokio", "toml", - "tracing", + "ureq", "uuid", -] - -[[package]] -name = "epiphany-release-construction" -version = "0.1.0" -dependencies = [ - "anyhow", - "chrono", - "cultcache-rs", - "fs2", - "rmp-serde", - "serde", - "serde_json", - "sha2 0.10.9", - "tempfile", - "uuid", -] - -[[package]] -name = "epiphany-state-model" -version = "0.1.0" -dependencies = [ - "schemars 0.8.22", - "serde", - "ts-rs", + "zeroize", ] [[package]] name = "epiphany-tool-adapter" version = "0.1.0" dependencies = [ - "cultcache-rs", + "cultcache-rs 0.2.0 (git+https://github.com/GameCult/CultLib.git?rev=a0813c6eed24d30bf88073ef615b633c77ebfcd6)", "serde", "serde_json", ] @@ -2143,15 +673,15 @@ version = "0.1.0" dependencies = [ "anyhow", "chrono", - "cultcache-rs", + "cultcache-rs 0.2.0 (git+https://github.com/GameCult/CultLib.git?rev=a0813c6eed24d30bf88073ef615b633c77ebfcd6)", "epiphany-core", "epiphany-tool-adapter", "http", - "reqwest 0.13.4", - "rmcp 2.2.0", + "reqwest", + "rmcp", "serde", "serde_json", - "sha2 0.10.9", + "sha2", "tempfile", "tokio", "toml", @@ -2163,15 +693,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" -[[package]] -name = "erased-serde" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c138974f9d5e7fe373eb04df7cae98833802ae4b11c24ac7039a21d5af4b26c" -dependencies = [ - "serde", -] - [[package]] name = "errno" version = "0.3.14" @@ -2182,77 +703,11 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "error-code" -version = "3.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59" - -[[package]] -name = "event-listener" -version = "5.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a23add41df1562121a9393cb065eab5146a1242410f23a644851e90cfd669d2" -dependencies = [ - "parking", - "pin-project-lite", -] - -[[package]] -name = "event-listener-strategy" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" -dependencies = [ - "event-listener", - "pin-project-lite", -] - -[[package]] -name = "eventsource-stream" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74fef4569247a5f429d9156b9d0a2599914385dd189c539334c625d8099d90ab" -dependencies = [ - "futures-core", - "nom 7.1.3", - "pin-project-lite", -] - -[[package]] -name = "fallible-iterator" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" - [[package]] name = "fastrand" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" -dependencies = [ - "getrandom 0.4.3", -] - -[[package]] -name = "fd-lock" -version = "4.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ce92ff622d6dadf7349484f42c93271a0d49b7cc4d466a936405bacbe10aa78" -dependencies = [ - "cfg-if", - "rustix", - "windows-sys 0.59.0", -] - -[[package]] -name = "fdeflate" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" -dependencies = [ - "simd-adler32", -] [[package]] name = "fiat-crypto" @@ -2266,73 +721,6 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26b73573e6edcd2af0cdf47bd6cb58f0b3839491263c314eaad1ccf24430e1de" -[[package]] -name = "fixed_decimal" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79c3c892f121fff406e5dd6b28c1b30096b95111c30701a899d4f2b18da6d1bd" -dependencies = [ - "displaydoc", - "smallvec", - "writeable", -] - -[[package]] -name = "fixedbitset" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" - -[[package]] -name = "flate2" -version = "1.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" -dependencies = [ - "crc32fast", - "libz-sys", - "miniz_oxide", -] - -[[package]] -name = "flume" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e139bc46ca777eb5efaf62df0ab8cc5fd400866427e56c68b22e414e53bd3be" -dependencies = [ - "fastrand", - "futures-core", - "futures-sink", - "spin", -] - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "foldhash" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" - -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - [[package]] name = "form_urlencoded" version = "1.2.2" @@ -2406,19 +794,6 @@ version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4577ecaa3c4f96589d473f679a71b596316f6641bc350038b962a5daf0085d7a" -[[package]] -name = "futures-lite" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" -dependencies = [ - "fastrand", - "futures-core", - "futures-io", - "parking", - "pin-project-lite", -] - [[package]] name = "futures-macro" version = "0.3.33" @@ -2459,30 +834,6 @@ dependencies = [ "slab", ] -[[package]] -name = "fxhash" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" -dependencies = [ - "byteorder", -] - -[[package]] -name = "generator" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3b854b0e584ead1a33f18b2fcad7cf7be18b3875c78816b753639aa501513ae" -dependencies = [ - "cc", - "cfg-if", - "libc", - "log", - "rustversion", - "windows-link", - "windows-result", -] - [[package]] name = "generic-array" version = "0.14.7" @@ -2491,17 +842,6 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", - "zeroize", -] - -[[package]] -name = "gethostname" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8" -dependencies = [ - "rustix", - "windows-link", ] [[package]] @@ -2513,7 +853,7 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "wasi 0.11.1+wasi-snapshot-preview1", + "wasi", "wasm-bindgen", ] @@ -2556,20 +896,10 @@ dependencies = [ [[package]] name = "ghostlight-persona-projection" version = "0.1.0" -source = "git+https://github.com/GameCult/Ghostlight.git?rev=22281891cbf9258db80c15a9fd465e6fa251e7dd#22281891cbf9258db80c15a9fd465e6fa251e7dd" +source = "git+https://github.com/GameCult/Ghostlight.git?rev=22937b71f056d2000dd7ea1f6af0c45825de495f#22937b71f056d2000dd7ea1f6af0c45825de495f" dependencies = [ "serde", - "sha2 0.10.9", -] - -[[package]] -name = "gif" -version = "0.14.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee8cfcc411d9adbbaba82fb72661cc1bcca13e8bba98b364e62b2dba8f960159" -dependencies = [ - "color_quant", - "weezl", + "sha2", ] [[package]] @@ -2582,53 +912,7 @@ dependencies = [ "bstr", "log", "regex-automata", - "regex-syntax 0.8.11", -] - -[[package]] -name = "h2" -version = "0.4.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cb093c84e8bd9b188d4c4a8cb6579fc016968d14c99882163cd3ff402a4f155" -dependencies = [ - "atomic-waker", - "bytes", - "fnv", - "futures-core", - "futures-sink", - "http", - "indexmap 2.14.0", - "slab", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - -[[package]] -name = "hashbrown" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" -dependencies = [ - "ahash", - "allocator-api2", -] - -[[package]] -name = "hashbrown" -version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" -dependencies = [ - "allocator-api2", - "equivalent", - "foldhash", + "regex-syntax", ] [[package]] @@ -2637,128 +921,13 @@ version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" -[[package]] -name = "headers" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3314d5adb5d94bcdf56771f2e50dbbc80bb4bdf88967526706205ac9eff24eb" -dependencies = [ - "base64", - "bytes", - "headers-core", - "http", - "httpdate", - "mime", - "sha1", -] - -[[package]] -name = "headers-core" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4" -dependencies = [ - "http", -] - -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - -[[package]] -name = "hermit-abi" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" - -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - -[[package]] -name = "hickory-proto" -version = "0.25.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8a6fe56c0038198998a6f217ca4e7ef3a5e51f46163bd6dd60b5c71ca6c6502" -dependencies = [ - "async-trait", - "cfg-if", - "data-encoding", - "enum-as-inner", - "futures-channel", - "futures-io", - "futures-util", - "idna", - "ipnet", - "once_cell", - "rand 0.9.5", - "ring", - "thiserror 2.0.20", - "tinyvec", - "tokio", - "tracing", - "url", -] - -[[package]] -name = "hickory-resolver" -version = "0.25.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc62a9a99b0bfb44d2ab95a7208ac952d31060efc16241c87eaf36406fecf87a" -dependencies = [ - "cfg-if", - "futures-util", - "hickory-proto", - "ipconfig", - "moka", - "once_cell", - "parking_lot", - "rand 0.9.5", - "resolv-conf", - "smallvec", - "thiserror 2.0.20", - "tokio", - "tracing", -] - -[[package]] -name = "hkdf" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" -dependencies = [ - "hmac 0.12.1", -] - [[package]] name = "hmac" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest 0.10.7", -] - -[[package]] -name = "hmac" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f" -dependencies = [ - "digest 0.11.3", -] - -[[package]] -name = "home" -version = "0.5.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" -dependencies = [ - "windows-sys 0.61.2", + "digest", ] [[package]] @@ -2794,33 +963,12 @@ dependencies = [ "pin-project-lite", ] -[[package]] -name = "http-range-header" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9171a2ea8a68358193d15dd5d70c1c10a2afc3e7e4c5bc92bc9f025cebd7359c" - [[package]] name = "httparse" version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" -[[package]] -name = "httpdate" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" - -[[package]] -name = "hybrid-array" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707114b52a152fa7bdb290cd7cd5912d9467273b6d74e21b8d81aca1f8533f6b" -dependencies = [ - "typenum", -] - [[package]] name = "hyper" version = "1.11.0" @@ -2831,7 +979,6 @@ dependencies = [ "bytes", "futures-channel", "futures-core", - "h2", "http", "http-body", "httparse", @@ -2852,40 +999,9 @@ dependencies = [ "hyper", "hyper-util", "rustls", - "rustls-native-certs", "tokio", "tokio-rustls", "tower-service", - "webpki-roots", -] - -[[package]] -name = "hyper-timeout" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" -dependencies = [ - "hyper", - "hyper-util", - "pin-project-lite", - "tokio", - "tower-service", -] - -[[package]] -name = "hyper-tls" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" -dependencies = [ - "bytes", - "http-body-util", - "hyper", - "hyper-util", - "native-tls", - "tokio", - "tokio-native-tls", - "tower-service", ] [[package]] @@ -2894,7 +1010,7 @@ version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" dependencies = [ - "base64", + "base64 0.22.1", "bytes", "futures-channel", "futures-util", @@ -2906,11 +1022,9 @@ dependencies = [ "percent-encoding", "pin-project-lite", "socket2 0.6.5", - "system-configuration", "tokio", "tower-service", "tracing", - "windows-registry", ] [[package]] @@ -2951,44 +1065,6 @@ dependencies = [ "zerovec", ] -[[package]] -name = "icu_decimal" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "288247df2e32aa776ac54fdd64de552149ac43cb840f2761811f0e8d09719dd4" -dependencies = [ - "displaydoc", - "fixed_decimal", - "icu_decimal_data", - "icu_locale", - "icu_locale_core", - "icu_plurals", - "icu_provider", - "writeable", - "zerovec", -] - -[[package]] -name = "icu_decimal_data" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f14a5ca9e8af29eef62064f269078424283d90dbaffeac5225addf62aaabc22" - -[[package]] -name = "icu_locale" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5a396343c7208121dc86e35623d3dfe19814a7613cfd14964994cdc9c9a2e26" -dependencies = [ - "icu_collections", - "icu_locale_core", - "icu_locale_data", - "icu_provider", - "potential_utf", - "tinystr", - "zerovec", -] - [[package]] name = "icu_locale_core" version = "2.2.0" @@ -2997,18 +1073,11 @@ checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" dependencies = [ "displaydoc", "litemap", - "serde", "tinystr", "writeable", "zerovec", ] -[[package]] -name = "icu_locale_data" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5fdcc9ac77c6d74ff5cf6e65ef3181d6af32003b16fce3a77fb451d2f695993" - [[package]] name = "icu_normalizer" version = "2.2.0" @@ -3029,25 +1098,6 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" -[[package]] -name = "icu_plurals" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a50023f1d49ad5c4333380328a0d4a19e4b9d6d842ec06639affd5ba47c8103" -dependencies = [ - "fixed_decimal", - "icu_locale", - "icu_plurals_data", - "icu_provider", - "zerovec", -] - -[[package]] -name = "icu_plurals_data" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8485497155dc865f901decb93ecc20d3e467df67bfeceb91e3ba34e2b11e8e1d" - [[package]] name = "icu_properties" version = "2.2.0" @@ -3076,8 +1126,6 @@ checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" dependencies = [ "displaydoc", "icu_locale_core", - "serde", - "stable_deref_trait", "writeable", "yoke", "zerofrom", @@ -3085,12 +1133,6 @@ dependencies = [ "zerovec", ] -[[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - [[package]] name = "idna" version = "1.1.0" @@ -3128,51 +1170,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "image" -version = "0.25.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104" -dependencies = [ - "bytemuck", - "byteorder-lite", - "color_quant", - "gif", - "image-webp", - "moxcms", - "num-traits", - "png", - "zune-core", - "zune-jpeg", -] - -[[package]] -name = "image-webp" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "525e9ff3e1a4be2fbea1fdf0e98686a6d98b4d8f937e1bf7402245af1909e8c3" -dependencies = [ - "byteorder-lite", - "quick-error", -] - -[[package]] -name = "indenter" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5" - -[[package]] -name = "indexmap" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" -dependencies = [ - "autocfg", - "hashbrown 0.12.3", - "serde", -] - [[package]] name = "indexmap" version = "2.14.0" @@ -3180,9 +1177,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", - "hashbrown 0.17.1", - "serde", - "serde_core", + "hashbrown", ] [[package]] @@ -3191,92 +1186,15 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" dependencies = [ - "block-padding", "generic-array", ] -[[package]] -name = "inventory" -version = "0.3.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4f0c30c76f2f4ccee3fe55a2435f691ca00c0e4bd87abe4f4a851b1d4dac39b" -dependencies = [ - "rustversion", -] - -[[package]] -name = "ipconfig" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d40460c0ce33d6ce4b0630ad68ff63d6661961c48b6dba35e5a4d81cfb48222" -dependencies = [ - "socket2 0.6.5", - "widestring", - "windows-registry", - "windows-result", - "windows-sys 0.61.2", -] - [[package]] name = "ipnet" version = "2.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a756c3fac73139e83f14c2d742155dd2b78d3ee56597b419a0579b7bdd6dd78" -[[package]] -name = "iri-string" -version = "0.7.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1663ee7d8cf2900cc1414b1e1eec9f348d6eaa3bcab07579f4726a4b8499f447" -dependencies = [ - "memchr", - "serde", -] - -[[package]] -name = "is-terminal" -version = "0.4.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" -dependencies = [ - "hermit-abi", - "libc", - "windows-sys 0.61.2", -] - -[[package]] -name = "is_terminal_polyfill" -version = "1.70.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" - -[[package]] -name = "itertools" -version = "0.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" -dependencies = [ - "either", -] - -[[package]] -name = "itertools" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" -dependencies = [ - "either", -] - -[[package]] -name = "itertools" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" -dependencies = [ - "either", -] - [[package]] name = "itoa" version = "1.0.18" @@ -3295,7 +1213,7 @@ dependencies = [ "jni-sys", "log", "simd_cesu8", - "thiserror 2.0.20", + "thiserror", "walkdir", "windows-link", ] @@ -3353,132 +1271,12 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "keyring" -version = "3.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eebcc3aff044e5944a8fbaf69eb277d11986064cba30c468730e8b9909fb551c" -dependencies = [ - "byteorder", - "dbus-secret-service", - "linux-keyutils", - "log", - "secret-service", - "security-framework 2.11.1", - "security-framework 3.7.0", - "windows-sys 0.60.2", - "zbus", - "zeroize", -] - -[[package]] -name = "konst" -version = "0.2.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "128133ed7824fcd73d6e7b17957c5eb7bacb885649bd8c69708b2331a10bcefb" -dependencies = [ - "konst_macro_rules", -] - -[[package]] -name = "konst_macro_rules" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4933f3f57a8e9d9da04db23fb153356ecaf00cbd14aee46279c33dc80925c37" - -[[package]] -name = "lalrpop" -version = "0.19.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a1cbf952127589f2851ab2046af368fd20645491bb4b376f04b7f94d7a9837b" -dependencies = [ - "ascii-canvas", - "bit-set", - "diff", - "ena", - "is-terminal", - "itertools 0.10.5", - "lalrpop-util", - "petgraph", - "regex", - "regex-syntax 0.6.29", - "string_cache", - "term", - "tiny-keccak", - "unicode-xid", -] - -[[package]] -name = "lalrpop-util" -version = "0.19.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3c48237b9604c5a4702de6b824e02006c3214327564636aef27c1028a8fa0ed" -dependencies = [ - "regex", -] - -[[package]] -name = "landlock" -version = "0.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cca98e95f35b29d469dade6724c6f96cec9236640f745a0e99b0334ec320ab1" -dependencies = [ - "enumflags2", - "libc", - "thiserror 2.0.20", -] - -[[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" - [[package]] name = "libc" version = "0.2.189" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" -[[package]] -name = "libdbus-sys" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "328c4789d42200f1eeec05bd86c9c13c7f091d2ba9a6ea35acdf51f31bc0f043" -dependencies = [ - "pkg-config", -] - -[[package]] -name = "libredox" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2026a5056764a10b2bf5d56488cba40da507f5493a6a429340e2004d9ed085fa" -dependencies = [ - "libc", -] - -[[package]] -name = "libz-sys" -version = "1.1.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85bc9657773828b90eeb625adff10eeac83cc21bbfd8e23a03eaa8a33c9e28d9" -dependencies = [ - "cc", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "linux-keyutils" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83270a18e9f90d0707c41e9f35efada77b64c0e6f3f1810e71c8368a864d5590" -dependencies = [ - "bitflags 2.13.1", - "libc", -] - [[package]] name = "linux-raw-sys" version = "0.12.1" @@ -3491,187 +1289,24 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" -[[package]] -name = "litrs" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" - -[[package]] -name = "lock_api" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" -dependencies = [ - "scopeguard", -] - [[package]] name = "log" version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" -[[package]] -name = "logos" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf8b031682c67a8e3d5446840f9573eb7fe26efe7ec8d195c9ac4c0647c502f1" -dependencies = [ - "logos-derive", -] - -[[package]] -name = "logos-derive" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d849148dbaf9661a6151d1ca82b13bb4c4c128146a88d05253b38d4e2f496c" -dependencies = [ - "beef", - "fnv", - "proc-macro2", - "quote", - "regex-syntax 0.6.29", - "syn 1.0.109", -] - -[[package]] -name = "loom" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" -dependencies = [ - "cfg-if", - "generator", - "pin-utils", - "scoped-tls", - "serde", - "serde_json", - "tracing", - "tracing-subscriber", -] - -[[package]] -name = "lru" -version = "0.16.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f66e8d5d03f609abc3a39e6f08e4164ebf1447a732906d39eb9b99b7919ef39" -dependencies = [ - "hashbrown 0.16.1", -] - [[package]] name = "lru-slab" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" -[[package]] -name = "lsp-types" -version = "0.94.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c66bfd44a06ae10647fe3f8214762e9369fd4248df1350924b4ef9e770a85ea1" -dependencies = [ - "bitflags 1.3.2", - "serde", - "serde_json", - "serde_repr", - "url", -] - -[[package]] -name = "maplit" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" - -[[package]] -name = "matchers" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" -dependencies = [ - "regex-automata", -] - -[[package]] -name = "matchit" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8863b587001c1b9a8a4e36008cebc6b3612cb1226fe2de94858e06092687b608" - -[[package]] -name = "md-5" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69b6441f590336821bb897fb28fc622898ccceb1d6cea3fde5ea86b090c4de98" -dependencies = [ - "cfg-if", - "digest 0.11.3", -] - -[[package]] -name = "md5" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ebb8d8732c6a6df3d8f032a82911cfc747e00efb95cc46e8d0acd5b5b88570c" - [[package]] name = "memchr" version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" -[[package]] -name = "memoffset" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" -dependencies = [ - "autocfg", -] - -[[package]] -name = "memoffset" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" -dependencies = [ - "autocfg", -] - -[[package]] -name = "mime" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" - -[[package]] -name = "mime_guess" -version = "2.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" -dependencies = [ - "mime", - "unicase", -] - -[[package]] -name = "minimal-lexical" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" - -[[package]] -name = "miniz_oxide" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" -dependencies = [ - "adler2", - "simd-adler32", -] - [[package]] name = "mio" version = "1.2.2" @@ -3679,218 +1314,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" dependencies = [ "libc", - "wasi 0.11.1+wasi-snapshot-preview1", + "wasi", "windows-sys 0.61.2", ] -[[package]] -name = "moka" -version = "0.12.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "957228ad12042ee839f93c8f257b62b4c0ab5eaae1d4fa60de53b27c9d7c5046" -dependencies = [ - "crossbeam-channel", - "crossbeam-epoch", - "crossbeam-utils", - "equivalent", - "parking_lot", - "portable-atomic", - "smallvec", - "tagptr", - "uuid", -] - -[[package]] -name = "moxcms" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b" -dependencies = [ - "num-traits", - "pxfm", -] - -[[package]] -name = "multimap" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" -dependencies = [ - "serde", -] - -[[package]] -name = "native-tls" -version = "0.2.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2" -dependencies = [ - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework 3.7.0", - "security-framework-sys", - "tempfile", -] - -[[package]] -name = "ndk-context" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" - -[[package]] -name = "new_debug_unreachable" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" - -[[package]] -name = "nibble_vec" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43" -dependencies = [ - "smallvec", -] - -[[package]] -name = "nix" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" -dependencies = [ - "bitflags 2.13.1", - "cfg-if", - "cfg_aliases 0.1.1", - "libc", -] - -[[package]] -name = "nix" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" -dependencies = [ - "bitflags 2.13.1", - "cfg-if", - "cfg_aliases 0.2.2", - "libc", - "memoffset 0.9.1", -] - [[package]] name = "nix" version = "0.31.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d" dependencies = [ - "bitflags 2.13.1", + "bitflags", "cfg-if", - "cfg_aliases 0.2.2", + "cfg_aliases", "libc", ] -[[package]] -name = "nom" -version = "7.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" -dependencies = [ - "memchr", - "minimal-lexical", -] - -[[package]] -name = "nom" -version = "8.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405" -dependencies = [ - "memchr", -] - -[[package]] -name = "nu-ansi-term" -version = "0.50.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "num" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" -dependencies = [ - "num-bigint", - "num-complex", - "num-integer", - "num-iter", - "num-rational", - "num-traits", -] - -[[package]] -name = "num-bigint" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367" -dependencies = [ - "num-integer", - "num-traits", -] - -[[package]] -name = "num-complex" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-conv" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" - -[[package]] -name = "num-integer" -version = "0.1.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-iter" -version = "0.1.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c92800bd69a1eac91786bcfe9da64a897eb72911b8dc3095decbd07429e8048b" -dependencies = [ - "num-integer", - "num-traits", -] - -[[package]] -name = "num-rational" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" -dependencies = [ - "num-bigint", - "num-integer", - "num-traits", -] - [[package]] name = "num-traits" version = "0.2.19" @@ -3909,200 +1348,17 @@ dependencies = [ "objc2-encode", ] -[[package]] -name = "objc2-app-kit" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" -dependencies = [ - "bitflags 2.13.1", - "objc2", - "objc2-foundation", -] - -[[package]] -name = "objc2-cloud-kit" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c" -dependencies = [ - "bitflags 2.13.1", - "objc2", - "objc2-foundation", -] - -[[package]] -name = "objc2-core-data" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa" -dependencies = [ - "objc2", - "objc2-foundation", -] - -[[package]] -name = "objc2-core-foundation" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" -dependencies = [ - "bitflags 2.13.1", - "dispatch2", - "objc2", -] - -[[package]] -name = "objc2-core-graphics" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" -dependencies = [ - "bitflags 2.13.1", - "dispatch2", - "objc2", - "objc2-core-foundation", - "objc2-io-surface", -] - -[[package]] -name = "objc2-core-image" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5d563b38d2b97209f8e861173de434bd0214cf020e3423a52624cd1d989f006" -dependencies = [ - "objc2", - "objc2-foundation", -] - -[[package]] -name = "objc2-core-location" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca347214e24bc973fc025fd0d36ebb179ff30536ed1f80252706db19ee452009" -dependencies = [ - "objc2", - "objc2-foundation", -] - -[[package]] -name = "objc2-core-text" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d" -dependencies = [ - "bitflags 2.13.1", - "objc2", - "objc2-core-foundation", - "objc2-core-graphics", -] - [[package]] name = "objc2-encode" version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" -[[package]] -name = "objc2-foundation" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" -dependencies = [ - "bitflags 2.13.1", - "block2", - "libc", - "objc2", - "objc2-core-foundation", -] - -[[package]] -name = "objc2-io-surface" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" -dependencies = [ - "bitflags 2.13.1", - "objc2", - "objc2-core-foundation", -] - -[[package]] -name = "objc2-quartz-core" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" -dependencies = [ - "bitflags 2.13.1", - "objc2", - "objc2-core-foundation", - "objc2-foundation", -] - -[[package]] -name = "objc2-system-configuration" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7216bd11cbda54ccabcab84d523dc93b858ec75ecfb3a7d89513fa22464da396" -dependencies = [ - "objc2-core-foundation", -] - -[[package]] -name = "objc2-ui-kit" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" -dependencies = [ - "bitflags 2.13.1", - "block2", - "objc2", - "objc2-cloud-kit", - "objc2-core-data", - "objc2-core-foundation", - "objc2-core-graphics", - "objc2-core-image", - "objc2-core-location", - "objc2-core-text", - "objc2-foundation", - "objc2-quartz-core", - "objc2-user-notifications", -] - -[[package]] -name = "objc2-user-notifications" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9df9128cbbfef73cda168416ccf7f837b62737d748333bfe9ab71c245d76613e" -dependencies = [ - "objc2", - "objc2-foundation", -] - -[[package]] -name = "oid-registry" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12f40cff3dde1b6087cc5d5f5d4d65712f34016a03ed60e9c08dcc392736b5b7" -dependencies = [ - "asn1-rs", -] - [[package]] name = "once_cell" version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" -dependencies = [ - "critical-section", - "portable-atomic", -] - -[[package]] -name = "once_cell_polyfill" -version = "1.70.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "opaque-debug" @@ -4110,318 +1366,24 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" -[[package]] -name = "openssl" -version = "0.10.81" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77823a27f0babb03091cb9ed9ef80af3b39dbc82f97e8fa530374b7dafd87a45" -dependencies = [ - "bitflags 2.13.1", - "cfg-if", - "foreign-types", - "libc", - "openssl-macros", - "openssl-sys", -] - -[[package]] -name = "openssl-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - [[package]] name = "openssl-probe" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" -[[package]] -name = "openssl-sys" -version = "0.9.117" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b47e7e6bb2c38cd930d25a23b40fa52e068c10e85f3e03a7f5ba5aaca5713695" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "opentelemetry" -version = "0.31.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b84bcd6ae87133e903af7ef497404dda70c60d0ea14895fc8a5e6722754fc2a0" -dependencies = [ - "futures-core", - "futures-sink", - "js-sys", - "pin-project-lite", - "thiserror 2.0.20", - "tracing", -] - -[[package]] -name = "opentelemetry-appender-tracing" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef6a1ac5ca3accf562b8c306fa8483c85f4390f768185ab775f242f7fe8fdcc2" -dependencies = [ - "opentelemetry", - "tracing", - "tracing-core", - "tracing-subscriber", -] - -[[package]] -name = "opentelemetry-http" -version = "0.31.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7a6d09a73194e6b66df7c8f1b680f156d916a1a942abf2de06823dd02b7855d" -dependencies = [ - "async-trait", - "bytes", - "http", - "opentelemetry", - "reqwest 0.12.28", -] - -[[package]] -name = "opentelemetry-otlp" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f69cd6acbb9af919df949cd1ec9e5e7fdc2ef15d234b6b795aaa525cc02f71f" -dependencies = [ - "http", - "opentelemetry", - "opentelemetry-http", - "opentelemetry-proto", - "opentelemetry_sdk", - "prost", - "reqwest 0.12.28", - "serde_json", - "thiserror 2.0.20", - "tokio", - "tonic", - "tracing", -] - -[[package]] -name = "opentelemetry-proto" -version = "0.31.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7175df06de5eaee9909d4805a3d07e28bb752c34cab57fa9cff549da596b30f" -dependencies = [ - "base64", - "const-hex", - "opentelemetry", - "opentelemetry_sdk", - "prost", - "serde", - "serde_json", - "tonic", - "tonic-prost", -] - -[[package]] -name = "opentelemetry-semantic-conventions" -version = "0.31.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e62e29dfe041afb8ed2a6c9737ab57db4907285d999ef8ad3a59092a36bdc846" - -[[package]] -name = "opentelemetry_sdk" -version = "0.31.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14ae4f5991976fd48df6d843de219ca6d31b01daaab2dad5af2badeded372bd" -dependencies = [ - "futures-channel", - "futures-executor", - "futures-util", - "opentelemetry", - "percent-encoding", - "rand 0.9.5", - "thiserror 2.0.20", - "tokio", - "tokio-stream", -] - -[[package]] -name = "option-ext" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" - -[[package]] -name = "ordered-stream" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" -dependencies = [ - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "os_info" -version = "3.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf20a545b305cf1da722b236b5155c9bb35f1d5ceb28c048bd96ca842f41b5b" -dependencies = [ - "android_system_properties", - "log", - "nix 0.31.3", - "objc2", - "objc2-foundation", - "objc2-ui-kit", - "serde", - "windows-sys 0.61.2", -] - -[[package]] -name = "parking" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" - -[[package]] -name = "parking_lot" -version = "0.12.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-link", -] - -[[package]] -name = "paste" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" - -[[package]] -name = "pastey" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ee67f1008b1ba2321834326597b8e186293b049a023cdef258527550b9935b4" - -[[package]] -name = "pem" -version = "3.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" -dependencies = [ - "base64", - "serde_core", -] - [[package]] name = "percent-encoding" version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" -[[package]] -name = "petgraph" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" -dependencies = [ - "fixedbitset", - "indexmap 2.14.0", -] - -[[package]] -name = "phf" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" -dependencies = [ - "phf_shared 0.13.1", - "serde", -] - -[[package]] -name = "phf_shared" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" -dependencies = [ - "siphasher", -] - -[[package]] -name = "phf_shared" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266" -dependencies = [ - "siphasher", -] - -[[package]] -name = "pin-project" -version = "1.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924" -dependencies = [ - "pin-project-internal", -] - -[[package]] -name = "pin-project-internal" -version = "1.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - [[package]] name = "pin-project-lite" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - -[[package]] -name = "piper" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1" -dependencies = [ - "atomic-waker", - "fastrand", - "futures-io", -] - [[package]] name = "pkcs8" version = "0.10.2" @@ -4438,44 +1400,6 @@ version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" -[[package]] -name = "png" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" -dependencies = [ - "bitflags 2.13.1", - "crc32fast", - "fdeflate", - "flate2", - "miniz_oxide", -] - -[[package]] -name = "polling" -version = "3.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" -dependencies = [ - "cfg-if", - "concurrent-queue", - "hermit-abi", - "pin-project-lite", - "rustix", - "windows-sys 0.61.2", -] - -[[package]] -name = "poly1305" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" -dependencies = [ - "cpufeatures 0.2.17", - "opaque-debug", - "universal-hash", -] - [[package]] name = "polyval" version = "0.6.2" @@ -4488,72 +1412,15 @@ dependencies = [ "universal-hash", ] -[[package]] -name = "portable-atomic" -version = "1.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d20d5497ef88037a52ff98267d066e7f11fcc5e99bbfbd58a42336193aacec3" - -[[package]] -name = "postgres" -version = "0.19.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ad20e0aa0b24f5a394eab4f78c781d248982b22b25cecc7e3aa46a681605bd" -dependencies = [ - "bytes", - "fallible-iterator", - "futures-util", - "log", - "tokio", - "tokio-postgres", -] - -[[package]] -name = "postgres-protocol" -version = "0.6.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08808e3c483c46e999108051c78334f473d5adb59d78bb80a1268c7e6aa6c514" -dependencies = [ - "base64", - "byteorder", - "bytes", - "fallible-iterator", - "hmac 0.13.0", - "md-5", - "memchr", - "rand 0.10.2", - "sha2 0.11.0", - "stringprep", -] - -[[package]] -name = "postgres-types" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "851ca9db4932932d69f3ea811b1abe63087a0f740a47692619dd40d4899b68be" -dependencies = [ - "bytes", - "fallible-iterator", - "postgres-protocol", -] - [[package]] name = "potential_utf" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" dependencies = [ - "serde_core", - "writeable", "zerovec", ] -[[package]] -name = "powerfmt" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" - [[package]] name = "ppv-lite86" version = "0.2.21" @@ -4563,31 +1430,6 @@ dependencies = [ "zerocopy", ] -[[package]] -name = "precomputed-hash" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" - -[[package]] -name = "pretty_assertions" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" -dependencies = [ - "diff", - "yansi", -] - -[[package]] -name = "proc-macro-crate" -version = "3.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" -dependencies = [ - "toml_edit 0.25.13+spec-1.1.0", -] - [[package]] name = "proc-macro2" version = "1.0.107" @@ -4604,98 +1446,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e842efad9119158434d193c6682e2ebee4b44d6ad801d7b349623b3f57cdf55" dependencies = [ "futures", - "indexmap 2.14.0", - "nix 0.31.3", + "indexmap", + "nix", "tokio", "tracing", "windows", ] -[[package]] -name = "proptest" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b45fcc2344c680f5025fe57779faef368840d0bd1f42f216291f0dc4ace4744" -dependencies = [ - "bitflags 2.13.1", - "num-traits", - "rand 0.9.5", - "rand_chacha 0.9.0", - "rand_xorshift", - "regex-syntax 0.8.11", - "unarray", -] - -[[package]] -name = "prost" -version = "0.14.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "528ac67416ff8646872a3c02cad9cc4ee5dc9f9540c9b10771855c95cb2e5ae1" -dependencies = [ - "bytes", - "prost-derive", -] - -[[package]] -name = "prost-derive" -version = "0.14.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b570b25f7617e43d59005d0990ccb79e950a423952cea19671b7a876da390adf" -dependencies = [ - "anyhow", - "itertools 0.14.0", - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "psl" -version = "2.1.223" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0dedad316e05de220cbf3fd8bfb69f3df8755dde2d7d479211827b595168a93" -dependencies = [ - "psl-types", -] - -[[package]] -name = "psl-types" -version = "2.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac" - -[[package]] -name = "publicsuffix" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f42ea446cab60335f76979ec15e12619a2165b5ae2c12166bef27d283a9fadf" -dependencies = [ - "idna", - "psl-types", -] - -[[package]] -name = "pxfm" -version = "0.1.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d55d956fa96f5ec02be2e13af0e20391a5aa83d6a074e3ad368959d0fab299ea" - -[[package]] -name = "quick-error" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" - -[[package]] -name = "quick-xml" -version = "0.38.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b66c2058c55a409d601666cffe35f04333cf1013010882cec174a7467cd4e21c" -dependencies = [ - "memchr", - "serde", -] - [[package]] name = "quinn" version = "0.11.11" @@ -4703,14 +1460,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c1a41e437b6bbd489372cd4971de128e85c855f56c57f283d20ff016cf7c0a8" dependencies = [ "bytes", - "cfg_aliases 0.2.2", + "cfg_aliases", "pin-project-lite", "quinn-proto", "quinn-udp", "rustc-hash", "rustls", "socket2 0.6.5", - "thiserror 2.0.20", + "thiserror", "tokio", "tracing", "web-time", @@ -4733,7 +1490,7 @@ dependencies = [ "rustls", "rustls-pki-types", "slab", - "thiserror 2.0.20", + "thiserror", "tinyvec", "tracing", "web-time", @@ -4745,7 +1502,7 @@ version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35a133f956daabe89a61a685c2649f13d82d5aa4bd5d12d1277e1072a21c0694" dependencies = [ - "cfg_aliases 0.2.2", + "cfg_aliases", "libc", "once_cell", "socket2 0.6.5", @@ -4774,354 +1531,13 @@ version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" -[[package]] -name = "radix_trie" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" -dependencies = [ - "endian-type 0.1.2", - "nibble_vec", -] - -[[package]] -name = "radix_trie" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b4431027dcd37fc2a73ef740b5f233aa805897935b8bce0195e41bbf9a3289a" -dependencies = [ - "endian-type 0.2.0", - "nibble_vec", -] - -[[package]] -name = "rama-core" -version = "0.3.0-alpha.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b93751ab27c9d151e84c1100057eab3f2a6a1378bc31b62abd416ecb1847658" -dependencies = [ - "ahash", - "asynk-strim", - "bytes", - "futures", - "parking_lot", - "pin-project-lite", - "rama-error", - "rama-macros", - "rama-utils", - "serde", - "serde_json", - "tokio", - "tokio-graceful", - "tokio-util", - "tracing", -] - -[[package]] -name = "rama-dns" -version = "0.3.0-alpha.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e340fef2799277e204260b17af01bc23604712092eacd6defe40167f304baed8" -dependencies = [ - "ahash", - "hickory-resolver", - "rama-core", - "rama-net", - "rama-utils", - "serde", - "tokio", -] - -[[package]] -name = "rama-error" -version = "0.3.0-alpha.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c452aba1beb7e29b873ff32f304536164cffcc596e786921aea64e858ff8f40" - -[[package]] -name = "rama-http" -version = "0.3.0-alpha.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "453d60af031e23af2d48995e41b17023f6150044738680508b63671f8d7417dd" -dependencies = [ - "ahash", - "base64", - "bitflags 2.13.1", - "chrono", - "const_format", - "csv", - "http", - "http-range-header", - "httpdate", - "iri-string", - "matchit", - "parking_lot", - "percent-encoding", - "pin-project-lite", - "radix_trie 0.3.0", - "rama-core", - "rama-error", - "rama-http-headers", - "rama-http-types", - "rama-net", - "rama-utils", - "rand 0.9.5", - "serde", - "serde_html_form", - "serde_json", - "tokio", - "uuid", -] - -[[package]] -name = "rama-http-backend" -version = "0.3.0-alpha.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3ff6a3c8ae690be8167e43777ba0bf6b0c8c2f6de165c538666affe2a32fd81" -dependencies = [ - "h2", - "pin-project-lite", - "rama-core", - "rama-http", - "rama-http-core", - "rama-http-headers", - "rama-http-types", - "rama-net", - "rama-tcp", - "rama-unix", - "rama-utils", - "tokio", -] - -[[package]] -name = "rama-http-core" -version = "0.3.0-alpha.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3822be6703e010afec0bcfeb5dbb6e5a3b23ca5689d9b1215b66ce6446653b77" -dependencies = [ - "ahash", - "atomic-waker", - "futures-channel", - "httparse", - "httpdate", - "indexmap 2.14.0", - "itoa", - "parking_lot", - "pin-project-lite", - "rama-core", - "rama-http", - "rama-http-types", - "rama-utils", - "slab", - "tokio", - "tokio-test", - "want", -] - -[[package]] -name = "rama-http-headers" -version = "0.3.0-alpha.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d74fe0cd9bd4440827dc6dc0f504cf66065396532e798891dee2c1b740b2285" -dependencies = [ - "ahash", - "base64", - "chrono", - "const_format", - "httpdate", - "rama-core", - "rama-error", - "rama-http-types", - "rama-macros", - "rama-net", - "rama-utils", - "rand 0.9.5", - "serde", - "sha1", -] - -[[package]] -name = "rama-http-types" -version = "0.3.0-alpha.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6dae655a72da5f2b97cfacb67960d8b28c5025e62707b4c8c5f0c5c9843a444" -dependencies = [ - "ahash", - "bytes", - "const_format", - "fnv", - "http", - "http-body", - "http-body-util", - "itoa", - "memchr", - "mime", - "mime_guess", - "nom 8.0.0", - "pin-project-lite", - "rama-core", - "rama-error", - "rama-macros", - "rama-utils", - "rand 0.9.5", - "serde", - "serde_json", - "sync_wrapper", - "tokio", -] - -[[package]] -name = "rama-macros" -version = "0.3.0-alpha.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea18a110bcf21e35c5f194168e6914ccea45ffdd0fea51bc4b169fbeafef6428" -dependencies = [ - "proc-macro-crate", - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "rama-net" -version = "0.3.0-alpha.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b28ee9e1e5d39264414b71f5c33e7fbb66b382c3fac456fe0daad39cf5509933" -dependencies = [ - "ahash", - "const_format", - "flume", - "hex", - "ipnet", - "itertools 0.14.0", - "md5", - "nom 8.0.0", - "parking_lot", - "pin-project-lite", - "psl", - "radix_trie 0.3.0", - "rama-core", - "rama-http-types", - "rama-macros", - "rama-utils", - "serde", - "sha2 0.10.9", - "socket2 0.6.5", - "tokio", -] - -[[package]] -name = "rama-socks5" -version = "0.3.0-alpha.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5468b263516daaf258de32542c1974b7cbe962363ad913dcb669f5d46db0ef3e" -dependencies = [ - "byteorder", - "rama-core", - "rama-net", - "rama-tcp", - "rama-udp", - "rama-utils", - "tokio", -] - -[[package]] -name = "rama-tcp" -version = "0.3.0-alpha.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe60cd604f91196b3659a1b28945add2e8b10bd0b4e6373c93d024fb3197704b" -dependencies = [ - "pin-project-lite", - "rama-core", - "rama-dns", - "rama-http-types", - "rama-net", - "rama-utils", - "rand 0.9.5", - "tokio", -] - -[[package]] -name = "rama-tls-rustls" -version = "0.3.0-alpha.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "536d47f6b269fb20dffd45e4c04aa8b340698b3509326e3c36e444b4f33ce0d6" -dependencies = [ - "pin-project-lite", - "rama-core", - "rama-http-types", - "rama-net", - "rama-utils", - "rcgen", - "rustls", - "rustls-native-certs", - "rustls-pki-types", - "tokio", - "tokio-rustls", - "webpki-roots", - "x509-parser", -] - -[[package]] -name = "rama-udp" -version = "0.3.0-alpha.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36ed05e0ecac73e084e92a3a8b1fbf16fdae8958c506f0f0eada180a2d99eef4" -dependencies = [ - "rama-core", - "rama-net", - "tokio", - "tokio-util", -] - -[[package]] -name = "rama-unix" -version = "0.3.0-alpha.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91acb16d571428ba4cece072dfab90d2667cdfa910a7b3cb4530c3f31542d708" -dependencies = [ - "pin-project-lite", - "rama-core", - "rama-net", - "tokio", -] - -[[package]] -name = "rama-utils" -version = "0.3.0-alpha.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf28b18ba4a57f8334d7992d3f8020194ea359b246ae6f8f98b8df524c7a14ef" -dependencies = [ - "const_format", - "parking_lot", - "pin-project-lite", - "rama-macros", - "regex", - "serde", - "smallvec", - "smol_str", - "tokio", - "wildcard", -] - -[[package]] -name = "rand" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a" -dependencies = [ - "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.4", -] - [[package]] name = "rand" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41" dependencies = [ - "rand_chacha 0.9.0", + "rand_chacha", "rand_core 0.9.5", ] @@ -5136,16 +1552,6 @@ dependencies = [ "rand_core 0.10.1", ] -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core 0.6.4", -] - [[package]] name = "rand_chacha" version = "0.9.0" @@ -5189,29 +1595,6 @@ dependencies = [ "rand_core 0.10.1", ] -[[package]] -name = "rand_xorshift" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" -dependencies = [ - "rand_core 0.9.5", -] - -[[package]] -name = "rcgen" -version = "0.14.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57f6d249aad744e274e682777a50283a225a32705394ee6d5fcc01efa25e4055" -dependencies = [ - "aws-lc-rs", - "pem", - "rustls-pki-types", - "time", - "x509-parser", - "yasna", -] - [[package]] name = "redb" version = "4.1.0" @@ -5221,69 +1604,26 @@ dependencies = [ "libc", ] -[[package]] -name = "redox_syscall" -version = "0.5.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" -dependencies = [ - "bitflags 2.13.1", -] - -[[package]] -name = "redox_users" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" -dependencies = [ - "getrandom 0.2.17", - "libredox", - "thiserror 1.0.69", -] - -[[package]] -name = "redox_users" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" -dependencies = [ - "getrandom 0.2.17", - "libredox", - "thiserror 2.0.20", -] - [[package]] name = "ref-cast" -version = "1.0.26" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "216e8f773d7923bcba9ceb86a86c93cabb3903a11872fc3f138c49630e50b96d" +checksum = "7e440fb4e4b4147295338efb76001ab9e4efc0e5839df2c47fc5ac2381d365c3" dependencies = [ "ref-cast-impl", ] [[package]] name = "ref-cast-impl" -version = "1.0.26" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c9283685feec7d69af75fb0e858d5e7378f33fe4fc699383b2916ab9273e03c" +checksum = "92ecd8964f8453721699a1ed72037b0db49ce2f5a5138486ee89bed6f67cdf3a" dependencies = [ "proc-macro2", "quote", "syn 3.0.3", ] -[[package]] -name = "regex" -version = "1.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax 0.8.11", -] - [[package]] name = "regex-automata" version = "0.4.18" @@ -5292,87 +1632,23 @@ checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.11", + "regex-syntax", ] -[[package]] -name = "regex-lite" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cab834c73d247e67f4fae452806d17d3c7501756d98c8808d7c9c7aa7d18f973" - -[[package]] -name = "regex-syntax" -version = "0.6.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" - [[package]] name = "regex-syntax" version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" -[[package]] -name = "reqwest" -version = "0.12.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" -dependencies = [ - "base64", - "bytes", - "cookie", - "cookie_store", - "encoding_rs", - "futures-channel", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-rustls", - "hyper-tls", - "hyper-util", - "js-sys", - "log", - "mime", - "native-tls", - "percent-encoding", - "pin-project-lite", - "quinn", - "rustls", - "rustls-native-certs", - "rustls-pki-types", - "serde", - "serde_json", - "serde_urlencoded", - "sync_wrapper", - "tokio", - "tokio-native-tls", - "tokio-rustls", - "tokio-util", - "tower", - "tower-http", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "wasm-streams 0.4.2", - "web-sys", - "webpki-roots", -] - [[package]] name = "reqwest" version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3" dependencies = [ - "base64", + "base64 0.22.1", "bytes", - "futures-channel", "futures-core", "futures-util", "http", @@ -5401,16 +1677,10 @@ dependencies = [ "url", "wasm-bindgen", "wasm-bindgen-futures", - "wasm-streams 0.5.0", + "wasm-streams", "web-sys", ] -[[package]] -name = "resolv-conf" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e061d1b48cb8d38042de4ae0a7a6401009d6143dc80d2e2d6f31f0bdd6470c7" - [[package]] name = "ring" version = "0.17.14" @@ -5421,32 +1691,10 @@ dependencies = [ "cfg-if", "getrandom 0.2.17", "libc", - "untrusted 0.9.0", + "untrusted", "windows-sys 0.52.0", ] -[[package]] -name = "rmcp" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bef41ebc9ebed2c1b1d90203e9d1756091e8a00bbc3107676151f39868ca0ee" -dependencies = [ - "async-trait", - "base64", - "chrono", - "futures", - "pastey", - "pin-project-lite", - "rmcp-macros", - "schemars 1.2.2", - "serde", - "serde_json", - "thiserror 2.0.20", - "tokio", - "tokio-util", - "tracing", -] - [[package]] name = "rmcp" version = "2.2.0" @@ -5459,30 +1707,17 @@ dependencies = [ "http", "pin-project-lite", "process-wrap", - "reqwest 0.13.4", + "reqwest", "serde", "serde_json", "sse-stream", - "thiserror 2.0.20", + "thiserror", "tokio", "tokio-stream", "tokio-util", "tracing", ] -[[package]] -name = "rmcp-macros" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e88ad84b8b6237a934534a62b379a5be6388915663c0cc598ceb9b3292bbbfe" -dependencies = [ - "darling", - "proc-macro2", - "quote", - "serde_json", - "syn 2.0.119", -] - [[package]] name = "rmp" version = "0.8.15" @@ -5528,22 +1763,13 @@ dependencies = [ "semver", ] -[[package]] -name = "rusticata-macros" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" -dependencies = [ - "nom 7.1.3", -] - [[package]] name = "rustix" version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags 2.13.1", + "bitflags", "errno", "libc", "linux-raw-sys", @@ -5575,7 +1801,7 @@ dependencies = [ "openssl-probe", "rustls-pki-types", "schannel", - "security-framework 3.7.0", + "security-framework", ] [[package]] @@ -5594,7 +1820,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0" dependencies = [ - "core-foundation 0.10.1", + "core-foundation", "core-foundation-sys", "jni", "log", @@ -5603,7 +1829,7 @@ dependencies = [ "rustls-native-certs", "rustls-platform-verifier-android", "rustls-webpki", - "security-framework 3.7.0", + "security-framework", "security-framework-sys", "webpki-root-certs", "windows-sys 0.61.2", @@ -5624,7 +1850,7 @@ dependencies = [ "aws-lc-rs", "ring", "rustls-pki-types", - "untrusted 0.9.0", + "untrusted", ] [[package]] @@ -5633,43 +1859,6 @@ version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" -[[package]] -name = "rustyline" -version = "14.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7803e8936da37efd9b6d4478277f4b2b9bb5cdb37a113e8d63222e58da647e63" -dependencies = [ - "bitflags 2.13.1", - "cfg-if", - "clipboard-win", - "fd-lock", - "home", - "libc", - "log", - "memchr", - "nix 0.28.0", - "radix_trie 0.2.1", - "unicode-segmentation", - "unicode-width", - "utf8parse", - "windows-sys 0.52.0", -] - -[[package]] -name = "ryu" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" - -[[package]] -name = "salsa20" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213" -dependencies = [ - "cipher", -] - [[package]] name = "same-file" version = "1.0.6" @@ -5688,98 +1877,19 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "schemafy" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8aea5ba40287dae331f2c48b64dbc8138541f5e97ee8793caa7948c1f31d86d5" -dependencies = [ - "Inflector", - "schemafy_core", - "schemafy_lib", - "serde", - "serde_derive", - "serde_json", - "serde_repr", - "syn 1.0.109", -] - -[[package]] -name = "schemafy_core" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41781ae092f4fd52c9287efb74456aea0d3b90032d2ecad272bd14dbbcb0511b" -dependencies = [ - "serde", - "serde_json", -] - -[[package]] -name = "schemafy_lib" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e953db32579999ca98c451d80801b6f6a7ecba6127196c5387ec0774c528befa" -dependencies = [ - "Inflector", - "proc-macro2", - "quote", - "schemafy_core", - "serde", - "serde_derive", - "serde_json", - "syn 1.0.109", -] - -[[package]] -name = "schemars" -version = "0.8.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615" -dependencies = [ - "dyn-clone", - "schemars_derive 0.8.22", - "serde", - "serde_json", -] - -[[package]] -name = "schemars" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" -dependencies = [ - "dyn-clone", - "ref-cast", - "serde", - "serde_json", -] - [[package]] name = "schemars" version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "687274d293b6cdc6e73e0fee520bf2049650090d7164f87672d212a3c530cf4a" dependencies = [ - "chrono", "dyn-clone", "ref-cast", - "schemars_derive 1.2.2", + "schemars_derive", "serde", "serde_json", ] -[[package]] -name = "schemars_derive" -version = "0.8.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d" -dependencies = [ - "proc-macro2", - "quote", - "serde_derive_internals 0.29.1", - "syn 2.0.119", -] - [[package]] name = "schemars_derive" version = "1.2.2" @@ -5788,71 +1898,18 @@ checksum = "d98c67716b46af2f0b8cf752abc930f6f9aecfbf671ecfb531db8a31dbe4e2ba" dependencies = [ "proc-macro2", "quote", - "serde_derive_internals 0.30.0", + "serde_derive_internals", "syn 3.0.3", ] -[[package]] -name = "scoped-tls" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "seccompiler" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4ae55de56877481d112a559bbc12667635fdaf5e005712fd4e2b2fa50ffc884" -dependencies = [ - "libc", -] - -[[package]] -name = "secret-service" -version = "4.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4d35ad99a181be0a60ffcbe85d680d98f87bdc4d7644ade319b87076b9dbfd4" -dependencies = [ - "aes", - "cbc", - "futures-util", - "generic-array", - "hkdf", - "num", - "once_cell", - "rand 0.8.7", - "serde", - "sha2 0.10.9", - "zbus", -] - -[[package]] -name = "security-framework" -version = "2.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" -dependencies = [ - "bitflags 2.13.1", - "core-foundation 0.9.4", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - [[package]] name = "security-framework" version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ - "bitflags 2.13.1", - "core-foundation 0.10.1", + "bitflags", + "core-foundation", "core-foundation-sys", "libc", "security-framework-sys", @@ -5918,17 +1975,6 @@ dependencies = [ "syn 3.0.3", ] -[[package]] -name = "serde_derive_internals" -version = "0.29.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - [[package]] name = "serde_derive_internals" version = "0.30.0" @@ -5940,26 +1986,12 @@ dependencies = [ "syn 3.0.3", ] -[[package]] -name = "serde_html_form" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2acf96b1d9364968fce46ebb548f1c0e1d7eceae27bdff73865d42e6c7369d94" -dependencies = [ - "form_urlencoded", - "indexmap 2.14.0", - "itoa", - "ryu", - "serde_core", -] - [[package]] name = "serde_json" version = "1.0.151" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" dependencies = [ - "indexmap 2.14.0", "itoa", "memchr", "serde", @@ -5967,28 +1999,6 @@ dependencies = [ "zmij", ] -[[package]] -name = "serde_path_to_error" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" -dependencies = [ - "itoa", - "serde", - "serde_core", -] - -[[package]] -name = "serde_repr" -version = "0.1.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d3b1629de253c70a0508c3899572da79ca359fdab27c7920ff00406df418906" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.3", -] - [[package]] name = "serde_spanned" version = "1.1.1" @@ -5998,61 +2008,6 @@ dependencies = [ "serde_core", ] -[[package]] -name = "serde_urlencoded" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "serde_with" -version = "3.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76a5c54c7310e7b8b9577c286d7e399ddd876c3e12b3ed917a8aabc4b96e9e8c" -dependencies = [ - "base64", - "bs58", - "chrono", - "hex", - "indexmap 1.9.3", - "indexmap 2.14.0", - "schemars 0.9.0", - "schemars 1.2.2", - "serde_core", - "serde_json", - "serde_with_macros", - "time", -] - -[[package]] -name = "serde_with_macros" -version = "3.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84d57bc0c8b9a17920c178daa6bb924850d54a9c97ab45194bb8c17ad66bb660" -dependencies = [ - "darling", - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "sha1" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a978451301f4db1d02937a4ab3ccce137717b81826e79b7d49ffe3244a13c3b8" -dependencies = [ - "cfg-if", - "cpufeatures 0.2.17", - "digest 0.10.7", -] - [[package]] name = "sha1_smol" version = "1.0.1" @@ -6067,35 +2022,9 @@ checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", "cpufeatures 0.2.17", - "digest 0.10.7", + "digest", ] -[[package]] -name = "sha2" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" -dependencies = [ - "cfg-if", - "cpufeatures 0.3.0", - "digest 0.11.3", -] - -[[package]] -name = "sharded-slab" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" -dependencies = [ - "lazy_static", -] - -[[package]] -name = "shlex" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - [[package]] name = "shlex" version = "2.0.1" @@ -6121,12 +2050,6 @@ dependencies = [ "rand_core 0.6.4", ] -[[package]] -name = "simd-adler32" -version = "0.3.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" - [[package]] name = "simd_cesu8" version = "1.2.0" @@ -6143,12 +2066,6 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" -[[package]] -name = "siphasher" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" - [[package]] name = "slab" version = "0.4.12" @@ -6160,19 +2077,6 @@ name = "smallvec" version = "1.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" -dependencies = [ - "serde", -] - -[[package]] -name = "smol_str" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4aaa7368fcf4852a4c2dd92df0cace6a71f2091ca0a23391ce7f3a31833f1523" -dependencies = [ - "borsh", - "serde_core", -] [[package]] name = "socket2" @@ -6194,15 +2098,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "spin" -version = "0.9.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e" -dependencies = [ - "lock_api", -] - [[package]] name = "spki" version = "0.7.3" @@ -6232,178 +2127,12 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" -[[package]] -name = "starlark" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f53849859f05d9db705b221bd92eede93877fd426c1b4a3c3061403a5912a8f" -dependencies = [ - "allocative", - "anyhow", - "bumpalo", - "cmp_any", - "debugserver-types", - "derivative", - "derive_more", - "display_container", - "dupe", - "either", - "erased-serde", - "hashbrown 0.14.5", - "inventory", - "itertools 0.13.0", - "maplit", - "memoffset 0.6.5", - "num-bigint", - "num-traits", - "once_cell", - "paste", - "ref-cast", - "regex", - "rustyline", - "serde", - "serde_json", - "starlark_derive", - "starlark_map", - "starlark_syntax", - "static_assertions", - "strsim 0.10.0", - "textwrap", - "thiserror 1.0.69", -] - -[[package]] -name = "starlark_derive" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe58bc6c8b7980a1fe4c9f8f48200c3212db42ebfe21ae6a0336385ab53f082a" -dependencies = [ - "dupe", - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "starlark_map" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92659970f120df0cc1c0bb220b33587b7a9a90e80d4eecc5c5af5debb950173d" -dependencies = [ - "allocative", - "dupe", - "equivalent", - "fxhash", - "hashbrown 0.14.5", - "serde", -] - -[[package]] -name = "starlark_syntax" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe53b3690d776aafd7cb6b9fed62d94f83280e3b87d88e3719cc0024638461b3" -dependencies = [ - "allocative", - "annotate-snippets", - "anyhow", - "derivative", - "derive_more", - "dupe", - "lalrpop", - "lalrpop-util", - "logos", - "lsp-types", - "memchr", - "num-bigint", - "num-traits", - "once_cell", - "starlark_map", - "thiserror 1.0.69", -] - -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - -[[package]] -name = "streaming-iterator" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b2231b7c3057d5e4ad0156fb3dc807d900806020c5ffa3ee6ff2c8c76fb8520" - -[[package]] -name = "string_cache" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" -dependencies = [ - "new_debug_unreachable", - "parking_lot", - "phf_shared 0.11.3", - "precomputed-hash", -] - -[[package]] -name = "stringprep" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" -dependencies = [ - "unicode-bidi", - "unicode-normalization", - "unicode-properties", -] - -[[package]] -name = "strsim" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" - -[[package]] -name = "strsim" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" - -[[package]] -name = "strum" -version = "0.27.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" - -[[package]] -name = "strum_macros" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn 2.0.119", -] - [[package]] name = "subtle" version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - [[package]] name = "syn" version = "2.0.119" @@ -6446,42 +2175,6 @@ dependencies = [ "syn 2.0.119", ] -[[package]] -name = "sys-locale" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eab9a99a024a169fe8a903cf9d4a3b3601109bcc13bd9e3c6fff259138626c4" -dependencies = [ - "libc", -] - -[[package]] -name = "system-configuration" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" -dependencies = [ - "bitflags 2.13.1", - "core-foundation 0.9.4", - "system-configuration-sys", -] - -[[package]] -name = "system-configuration-sys" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "tagptr" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" - [[package]] name = "tempfile" version = "3.27.0" @@ -6495,62 +2188,13 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "term" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" -dependencies = [ - "dirs-next", - "rustversion", - "winapi", -] - -[[package]] -name = "termcolor" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "textwrap" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" -dependencies = [ - "unicode-width", -] - -[[package]] -name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl 1.0.69", -] - [[package]] name = "thiserror" version = "2.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" dependencies = [ - "thiserror-impl 2.0.20", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", + "thiserror-impl", ] [[package]] @@ -6564,66 +2208,6 @@ dependencies = [ "syn 3.0.3", ] -[[package]] -name = "thread_local" -version = "1.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ad99c4c6d32803332c548b1af0540b357b3f5fc0be8f6c6bfe8b2e6ae784070" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "time" -version = "0.3.55" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134" -dependencies = [ - "deranged", - "num-conv", - "powerfmt", - "serde_core", - "time-core", - "time-macros", -] - -[[package]] -name = "time-core" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" - -[[package]] -name = "time-macros" -version = "0.2.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85" -dependencies = [ - "num-conv", - "time-core", -] - -[[package]] -name = "tiny-keccak" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" -dependencies = [ - "crunchy", -] - -[[package]] -name = "tiny_http" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389915df6413a2e74fb181895f933386023c71110878cd0825588928e64cdc82" -dependencies = [ - "ascii", - "chunked_transfer", - "httpdate", - "log", -] - [[package]] name = "tinystr" version = "0.8.3" @@ -6631,7 +2215,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" dependencies = [ "displaydoc", - "serde_core", "zerovec", ] @@ -6659,7 +2242,6 @@ dependencies = [ "bytes", "libc", "mio", - "parking_lot", "pin-project-lite", "signal-hook-registry", "socket2 0.6.5", @@ -6667,19 +2249,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "tokio-graceful" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45740b38b48641855471cd402922e89156bdfbd97b69b45eeff170369cc18c7d" -dependencies = [ - "loom", - "pin-project-lite", - "slab", - "tokio", - "tracing", -] - [[package]] name = "tokio-macros" version = "2.7.2" @@ -6691,42 +2260,6 @@ dependencies = [ "syn 3.0.3", ] -[[package]] -name = "tokio-native-tls" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" -dependencies = [ - "native-tls", - "tokio", -] - -[[package]] -name = "tokio-postgres" -version = "0.7.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a528f7d280f6d5b9cd149635c8705b0dd049754bc67d81d31fa25169a93809d3" -dependencies = [ - "async-trait", - "byteorder", - "bytes", - "fallible-iterator", - "futures-channel", - "futures-util", - "log", - "parking_lot", - "percent-encoding", - "phf", - "pin-project-lite", - "postgres-protocol", - "postgres-types", - "rand 0.10.2", - "socket2 0.6.5", - "tokio", - "tokio-util", - "whoami", -] - [[package]] name = "tokio-rustls" version = "0.26.4" @@ -6748,32 +2281,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "tokio-test" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f6d24790a10a7af737693a3e8f1d03faef7e6ca0cc99aae5066f533766de545" -dependencies = [ - "futures-core", - "tokio", - "tokio-stream", -] - -[[package]] -name = "tokio-tungstenite" -version = "0.28.0" -source = "git+https://github.com/openai-oss-forks/tokio-tungstenite?rev=132f5b39c862e3a970f731d709608b3e6276d5f6#132f5b39c862e3a970f731d709608b3e6276d5f6" -dependencies = [ - "futures-util", - "log", - "rustls", - "rustls-native-certs", - "rustls-pki-types", - "tokio", - "tokio-rustls", - "tungstenite", -] - [[package]] name = "tokio-util" version = "0.7.19" @@ -6794,10 +2301,10 @@ version = "0.9.12+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" dependencies = [ - "indexmap 2.14.0", + "indexmap", "serde_core", "serde_spanned", - "toml_datetime 0.7.5+spec-1.1.0", + "toml_datetime", "toml_parser", "toml_writer", "winnow 0.7.15", @@ -6812,40 +2319,6 @@ dependencies = [ "serde_core", ] -[[package]] -name = "toml_datetime" -version = "1.1.1+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" -dependencies = [ - "serde_core", -] - -[[package]] -name = "toml_edit" -version = "0.24.1+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01f2eadbbc6b377a847be05f60791ef1058d9f696ecb51d2c07fe911d8569d8e" -dependencies = [ - "indexmap 2.14.0", - "toml_datetime 0.7.5+spec-1.1.0", - "toml_parser", - "toml_writer", - "winnow 0.7.15", -] - -[[package]] -name = "toml_edit" -version = "0.25.13+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6975367e4d2ef766d86af01ffad14b622fecc8d4357a998fbc4deb6e9bacaf9b" -dependencies = [ - "indexmap 2.14.0", - "toml_datetime 1.1.1+spec-1.1.0", - "toml_parser", - "winnow 1.0.4", -] - [[package]] name = "toml_parser" version = "1.1.3+spec-1.1.0" @@ -6861,45 +2334,6 @@ version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2" -[[package]] -name = "tonic" -version = "0.14.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac2a5518c70fa84342385732db33fb3f44bc4cc748936eb5833d2df34d6445ef" -dependencies = [ - "async-trait", - "base64", - "bytes", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-timeout", - "hyper-util", - "percent-encoding", - "pin-project", - "rustls-native-certs", - "sync_wrapper", - "tokio", - "tokio-rustls", - "tokio-stream", - "tower", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "tonic-prost" -version = "0.14.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50849f68853be452acf590cde0b146665b8d507b3b8af17261df47e02c209ea0" -dependencies = [ - "bytes", - "prost", - "tonic", -] - [[package]] name = "tower" version = "0.5.3" @@ -6908,15 +2342,11 @@ checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" dependencies = [ "futures-core", "futures-util", - "indexmap 2.14.0", "pin-project-lite", - "slab", "sync_wrapper", "tokio", - "tokio-util", "tower-layer", "tower-service", - "tracing", ] [[package]] @@ -6925,7 +2355,7 @@ version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" dependencies = [ - "bitflags 2.13.1", + "bitflags", "bytes", "futures-util", "http", @@ -6955,7 +2385,6 @@ version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ - "log", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -6979,230 +2408,70 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", - "valuable", ] -[[package]] -name = "tracing-log" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" -dependencies = [ - "log", - "once_cell", - "tracing-core", -] - -[[package]] -name = "tracing-opentelemetry" -version = "0.32.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ac28f2d093c6c477eaa76b23525478f38de514fa9aeb1285738d4b97a9552fc" -dependencies = [ - "js-sys", - "opentelemetry", - "smallvec", - "tracing", - "tracing-core", - "tracing-log", - "tracing-subscriber", - "web-time", -] - -[[package]] -name = "tracing-subscriber" -version = "0.3.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" -dependencies = [ - "matchers", - "nu-ansi-term", - "once_cell", - "regex-automata", - "sharded-slab", - "smallvec", - "thread_local", - "tracing", - "tracing-core", - "tracing-log", -] - -[[package]] -name = "tree-sitter" -version = "0.25.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78f873475d258561b06f1c595d93308a7ed124d9977cb26b148c2084a4a3cc87" -dependencies = [ - "cc", - "regex", - "regex-syntax 0.8.11", - "serde_json", - "streaming-iterator", - "tree-sitter-language", -] - -[[package]] -name = "tree-sitter-bash" -version = "0.25.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5ec769279cc91b561d3df0d8a5deb26b0ad40d183127f409494d6d8fc53062" -dependencies = [ - "cc", - "tree-sitter-language", -] - -[[package]] -name = "tree-sitter-language" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "009994f150cc0cd50ff54917d5bc8bffe8cad10ca10d81c34da2ec421ae61782" - [[package]] name = "try-lock" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" -[[package]] -name = "ts-rs" -version = "11.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4994acea2522cd2b3b85c1d9529a55991e3ad5e25cdcd3de9d505972c4379424" -dependencies = [ - "serde_json", - "thiserror 2.0.20", - "ts-rs-macros", - "uuid", -] - -[[package]] -name = "ts-rs-macros" -version = "11.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee6ff59666c9cbaec3533964505d39154dc4e0a56151fdea30a09ed0301f62e2" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", - "termcolor", -] - -[[package]] -name = "tungstenite" -version = "0.27.0" -source = "git+https://github.com/openai-oss-forks/tungstenite-rs?rev=9200079d3b54a1ff51072e24d81fd354f085156f#9200079d3b54a1ff51072e24d81fd354f085156f" -dependencies = [ - "bytes", - "data-encoding", - "flate2", - "headers", - "http", - "httparse", - "log", - "rand 0.9.5", - "rustls", - "rustls-pki-types", - "sha1", - "thiserror 2.0.20", - "utf-8", -] - [[package]] name = "typenum" version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" -[[package]] -name = "uds_windows" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e" -dependencies = [ - "memoffset 0.9.1", - "tempfile", - "windows-sys 0.61.2", -] - -[[package]] -name = "unarray" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" - -[[package]] -name = "unicase" -version = "2.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" - -[[package]] -name = "unicode-bidi" -version = "0.3.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" - [[package]] name = "unicode-ident" version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" -[[package]] -name = "unicode-normalization" -version = "0.1.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" -dependencies = [ - "tinyvec", -] - -[[package]] -name = "unicode-properties" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" - -[[package]] -name = "unicode-segmentation" -version = "1.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" - -[[package]] -name = "unicode-width" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" - -[[package]] -name = "unicode-xid" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" - [[package]] name = "universal-hash" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" dependencies = [ - "crypto-common 0.1.7", + "crypto-common", "subtle", ] -[[package]] -name = "untrusted" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" - [[package]] name = "untrusted" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +[[package]] +name = "ureq" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "972d7902c8735f2695410b8aed7df6ed12a47394aa1c8d7af49f0497b731a94d" +dependencies = [ + "base64 0.23.1", + "log", + "percent-encoding", + "rustls", + "rustls-pki-types", + "ureq-proto", + "utf8-zero", + "webpki-roots", +] + +[[package]] +name = "ureq-proto" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da5f78b09e6941e1a0f2e30e695e4b120377b54d5e0aec11b594bb57b3971613" +dependencies = [ + "base64 0.23.1", + "http", + "httparse", + "log", +] + [[package]] name = "url" version = "2.5.8" @@ -7213,20 +2482,13 @@ dependencies = [ "idna", "percent-encoding", "serde", - "serde_derive", ] [[package]] -name = "urlencoding" -version = "2.1.3" +name = "utf8-zero" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" - -[[package]] -name = "utf-8" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +checksum = "b8c0a043c9540bae7c578c88f91dda8bd82e59ae27c21baca69c8b191aaf5a6e" [[package]] name = "utf8_iter" @@ -7234,12 +2496,6 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" -[[package]] -name = "utf8parse" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" - [[package]] name = "uuid" version = "1.24.0" @@ -7253,18 +2509,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "valuable" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" - -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - [[package]] name = "version_check" version = "0.9.5" @@ -7296,15 +2540,6 @@ version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" -[[package]] -name = "wasi" -version = "0.14.7+wasi-0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c" -dependencies = [ - "wasip2", -] - [[package]] name = "wasip2" version = "1.0.4+wasi-0.2.12" @@ -7314,15 +2549,6 @@ dependencies = [ "wit-bindgen", ] -[[package]] -name = "wasite" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66fe902b4a6b8028a753d5424909b764ccf79b7a209eac9bf97e59cda9f71a42" -dependencies = [ - "wasi 0.14.7+wasi-0.2.4", -] - [[package]] name = "wasm-bindgen" version = "0.2.127" @@ -7378,19 +2604,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "wasm-streams" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" -dependencies = [ - "futures-util", - "js-sys", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - [[package]] name = "wasm-streams" version = "0.5.0" @@ -7424,22 +2637,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "webbrowser" -version = "1.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62c35be770821a214dbc362fc26908c853e776c0004294d0b10b8a6bad582f94" -dependencies = [ - "jni", - "log", - "ndk-context", - "objc2", - "objc2-app-kit", - "objc2-foundation", - "url", - "web-sys", -] - [[package]] name = "webpki-root-certs" version = "1.0.9" @@ -7458,55 +2655,6 @@ dependencies = [ "rustls-pki-types", ] -[[package]] -name = "weezl" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" - -[[package]] -name = "which" -version = "8.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f3ef584124b911bcc3875c2f1472e80f24361ceb789bd1c62b3e9a3df9ff43c" -dependencies = [ - "libc", -] - -[[package]] -name = "whoami" -version = "2.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "998767ef88740d1f5b0682a9c53c24431453923962269c2db68ee43788c5a40d" -dependencies = [ - "libc", - "libredox", - "objc2-system-configuration", - "wasite", - "web-sys", -] - -[[package]] -name = "widestring" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" - -[[package]] -name = "wildcard" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9b0540e91e49de3817c314da0dd3bc518093ceacc6ea5327cb0e1eb073e5189" -dependencies = [ - "thiserror 2.0.20", -] - -[[package]] -name = "wildmatch" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29333c3ea1ba8b17211763463ff24ee84e41c78224c16b001cd907e663a38c68" - [[package]] name = "winapi" version = "0.3.9" @@ -7621,17 +2769,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-registry" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" -dependencies = [ - "windows-link", - "windows-result", - "windows-strings", -] - [[package]] name = "windows-result" version = "0.4.1" @@ -7656,7 +2793,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.6", + "windows-targets", ] [[package]] @@ -7665,16 +2802,7 @@ version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" -dependencies = [ - "windows-targets 0.53.5", + "windows-targets", ] [[package]] @@ -7692,31 +2820,14 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.53.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" -dependencies = [ - "windows-link", - "windows_aarch64_gnullvm 0.53.1", - "windows_aarch64_msvc 0.53.1", - "windows_i686_gnu 0.53.1", - "windows_i686_gnullvm 0.53.1", - "windows_i686_msvc 0.53.1", - "windows_x86_64_gnu 0.53.1", - "windows_x86_64_gnullvm 0.53.1", - "windows_x86_64_msvc 0.53.1", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] [[package]] @@ -7734,113 +2845,59 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" - [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" -[[package]] -name = "windows_i686_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" - [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" - [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" -[[package]] -name = "windows_i686_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" - [[package]] name = "winnow" version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" -dependencies = [ - "memchr", -] [[package]] name = "winnow" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" -dependencies = [ - "memchr", -] [[package]] name = "wit-bindgen" @@ -7854,51 +2911,6 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" -[[package]] -name = "x509-parser" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d43b0f71ce057da06bc0851b23ee24f3f86190b07203dd8f567d0b706a185202" -dependencies = [ - "asn1-rs", - "aws-lc-rs", - "data-encoding", - "der-parser", - "lazy_static", - "nom 7.1.3", - "oid-registry", - "ring", - "rusticata-macros", - "thiserror 2.0.20", - "time", -] - -[[package]] -name = "xdg-home" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec1cdab258fb55c0da61328dc52c8764709b249011b2cad0454c72f0bf10a1f6" -dependencies = [ - "libc", - "windows-sys 0.59.0", -] - -[[package]] -name = "yansi" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" - -[[package]] -name = "yasna" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5f6765e852b9b4dc8e2a76843e4d64d1cea8e79bcde0b6901aea8e7c7f08282" -dependencies = [ - "bit-vec 0.9.1", - "time", -] - [[package]] name = "yoke" version = "0.8.3" @@ -7922,68 +2934,6 @@ dependencies = [ "synstructure", ] -[[package]] -name = "zbus" -version = "4.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb97012beadd29e654708a0fdb4c84bc046f537aecfde2c3ee0a9e4b4d48c725" -dependencies = [ - "async-broadcast", - "async-executor", - "async-fs", - "async-io", - "async-lock", - "async-process", - "async-recursion", - "async-task", - "async-trait", - "blocking", - "enumflags2", - "event-listener", - "futures-core", - "futures-sink", - "futures-util", - "hex", - "nix 0.29.0", - "ordered-stream", - "rand 0.8.7", - "serde", - "serde_repr", - "sha1", - "static_assertions", - "tracing", - "uds_windows", - "windows-sys 0.52.0", - "xdg-home", - "zbus_macros", - "zbus_names", - "zvariant", -] - -[[package]] -name = "zbus_macros" -version = "4.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "267db9407081e90bbfa46d841d3cbc60f59c0351838c4bc65199ecd79ab1983e" -dependencies = [ - "proc-macro-crate", - "proc-macro2", - "quote", - "syn 2.0.119", - "zvariant_utils", -] - -[[package]] -name = "zbus_names" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b9b1fef7d021261cc16cba64c351d291b715febe0fa10dc3a443ac5a5022e6c" -dependencies = [ - "serde", - "static_assertions", - "zvariant", -] - [[package]] name = "zerocopy" version = "0.8.56" @@ -8030,20 +2980,6 @@ name = "zeroize" version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" -dependencies = [ - "zeroize_derive", -] - -[[package]] -name = "zeroize_derive" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] [[package]] name = "zerotrie" @@ -8054,7 +2990,6 @@ dependencies = [ "displaydoc", "yoke", "zerofrom", - "zerovec", ] [[package]] @@ -8063,7 +2998,6 @@ version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" dependencies = [ - "serde", "yoke", "zerofrom", "zerovec-derive", @@ -8085,83 +3019,3 @@ name = "zmij" version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" - -[[package]] -name = "zstd" -version = "0.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" -dependencies = [ - "zstd-safe", -] - -[[package]] -name = "zstd-safe" -version = "7.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" -dependencies = [ - "zstd-sys", -] - -[[package]] -name = "zstd-sys" -version = "2.0.16+zstd.1.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" -dependencies = [ - "cc", - "pkg-config", -] - -[[package]] -name = "zune-core" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d56377fd46368984a170bc5aac5567e52ca5da874caa60bea39fcbca78fb658b" - -[[package]] -name = "zune-jpeg" -version = "0.5.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27bc9d5b815bc103f142aa054f561d9187d191692ec7c2d1e2b4737f8dbd7296" -dependencies = [ - "zune-core", -] - -[[package]] -name = "zvariant" -version = "4.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2084290ab9a1c471c38fc524945837734fbf124487e105daec2bb57fd48c81fe" -dependencies = [ - "endi", - "enumflags2", - "serde", - "static_assertions", - "zvariant_derive", -] - -[[package]] -name = "zvariant_derive" -version = "4.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73e2ba546bda683a90652bac4a279bc146adad1386f25379cf73200d2002c449" -dependencies = [ - "proc-macro-crate", - "proc-macro2", - "quote", - "syn 2.0.119", - "zvariant_utils", -] - -[[package]] -name = "zvariant_utils" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c51bcff7cc3dbb5055396bcf774748c3dab426b4b8659046963523cee4808340" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] diff --git a/Cargo.toml b/Cargo.toml index 038914fa..2d59e523 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,15 +2,11 @@ members = [ "epiphany-core", "epiphany-model-adapter", - "epiphany-openai-adapter", - "epiphany-openai-codex-spine", "epiphany-openai-runtime", - "epiphany-release-construction", - "epiphany-state-model", + "epiphany-pipeline", "epiphany-tool-adapter", "epiphany-tool-mcp-runtime", ] -exclude = ["vendor/codex", "vendor/cultcache-rs"] resolver = "3" [package] @@ -25,33 +21,26 @@ license-file = "LICENSE" anyhow = "1" chrono = "0.4.44" ctrlc = { version = "3.5.1", features = ["termination"] } -cultcache-rs = { path = "vendor/cultcache-rs" } -cultmesh-rs = { path = "vendor/cultmesh-rs" } -cultnet-rs = { path = "vendor/cultnet-rs" } +cultcache-rs = { git = "https://github.com/GameCult/CultLib.git", rev = "a0813c6eed24d30bf88073ef615b633c77ebfcd6", package = "cultcache-rs" } +cultnet-rs = { git = "https://github.com/GameCult/CultLib.git", rev = "a0813c6eed24d30bf88073ef615b633c77ebfcd6", package = "cultnet-rs" } epiphany-core = { path = "epiphany-core" } epiphany-model-adapter = { path = "epiphany-model-adapter" } -epiphany-openai-adapter = { path = "epiphany-openai-adapter" } epiphany-openai-runtime = { path = "epiphany-openai-runtime", optional = true } -epiphany-state-model = { path = "epiphany-state-model" } +codex-connector = { git = "https://github.com/GameCult/CodexConnector", rev = "be1776ab455fd79bd74b393518ab07211dc9c1f0", default-features = false, optional = true } epiphany-tool-adapter = { path = "epiphany-tool-adapter" } epiphany-tool-mcp-runtime = { path = "epiphany-tool-mcp-runtime", optional = true } -http = "1" -ignore = "0.4.23" -postgres = "0.19" -reqwest = { version = "0.13.2", default-features = false, features = ["blocking", "json", "rustls"] } rmp-serde = "1" -rmcp = { version = "2.2.0", default-features = false, features = ["client", "transport-child-process", "transport-streamable-http-client-reqwest"] } serde = { version = "1", features = ["derive"] } serde_json = "1" -semver = { version = "1", features = ["serde"] } sha2 = "0.10" tokio = { version = "1", features = ["io-util", "macros", "process", "rt", "rt-multi-thread", "time"] } toml = "0.9" -tracing = "0.1.44" +ureq = { version = "3.4", default-features = false, features = ["rustls"], optional = true } uuid = { version = "1", features = ["serde", "v4"] } +zeroize = { version = "1", optional = true } [features] -openai-runtime = ["dep:epiphany-openai-runtime"] +openai-runtime = ["dep:codex-connector", "dep:epiphany-openai-runtime", "dep:ureq", "dep:zeroize"] tool-mcp-runtime = ["dep:epiphany-tool-mcp-runtime"] release-runtime = ["openai-runtime", "tool-mcp-runtime"] @@ -61,13 +50,6 @@ incremental = true [dev-dependencies] tempfile = "3" -[patch.crates-io] -tokio-tungstenite = { git = "https://github.com/openai-oss-forks/tokio-tungstenite", rev = "132f5b39c862e3a970f731d709608b3e6276d5f6" } -tungstenite = { git = "https://github.com/openai-oss-forks/tungstenite-rs", rev = "9200079d3b54a1ff51072e24d81fd354f085156f" } - -[patch."ssh://git@github.com/openai-oss-forks/tungstenite-rs.git"] -tungstenite = { git = "https://github.com/openai-oss-forks/tungstenite-rs", rev = "9200079d3b54a1ff51072e24d81fd354f085156f" } - [[bin]] name = "epiphany-release" path = "epiphany-core/src/bin/epiphany-release.rs" @@ -99,12 +81,7 @@ path = "epiphany-core/src/bin/epiphany-mvp-coordinator.rs" [[bin]] name = "epiphany-model-runtime" -path = "epiphany-openai-runtime/src/bin/epiphany-model-runtime.rs" -required-features = ["openai-runtime"] - -[[bin]] -name = "epiphany-model-connector" -path = "epiphany-openai-runtime/src/bin/epiphany-model-connector.rs" +path = "epiphany-openai-runtime/src/bin/epiphany-openai-runtime.rs" required-features = ["openai-runtime"] [[bin]] diff --git a/LICENSE b/LICENSE index b0d7b796..d815141e 100644 --- a/LICENSE +++ b/LICENSE @@ -19,15 +19,10 @@ For this file: directory states different terms. - `Upstream Material` means material copied or adapted from third parties and distributed under their own licenses. -- `Mixed Apache Files` means files under `vendor/codex/**` that contain - project-authored changes on top of upstream Apache-licensed material. 2. Upstream And Third-Party Material ------------------------------------ -- All material under `vendor/codex/**` remains subject to its upstream licenses - and notices, including Apache License 2.0 at `vendor/codex/LICENSE` and any - applicable `NOTICE` or file-level notices. - Other third-party material distributed with the repository keeps its own license terms where provided. - You must continue to comply with those upstream terms when using, @@ -36,8 +31,8 @@ For this file: 3. Public License For Project-Authored Material ----------------------------------------------- -Except where a file or directory says otherwise, Project-Authored Material -outside `vendor/codex/**` is licensed to the public under: +Except where a file or directory says otherwise, Project-Authored Material is +licensed to the public under: - PolyForm Noncommercial License 1.0.0 @@ -66,27 +61,7 @@ entity is formed and designated in writing, any commercial license for Project-Authored Material may be offered directly by the individual copyright holder or holders. -5. Mixed Apache Files Under `vendor/codex/**` ---------------------------------------------- - -Some files under `vendor/codex/**` contain project-authored changes layered on -top of upstream Apache-licensed code. - -For those files: - -- Apache License 2.0 and the applicable upstream notices still apply. -- You must not read this repository notice as converting those files into a - PolyForm-only codebase. -- To the extent the project owns copyright in new additions to those files, the - project may also offer separate commercial terms for its own contributions. -- This repository notice does not claim to erase or retract Apache-2.0 - permissions already granted for Apache-covered material. - -The strongest commercial boundary in this repository therefore applies to -clearly separable Project-Authored Material outside `vendor/codex/**`, -including `epiphany-core/**` and other non-vendored project files. - -6. Open Source Status +5. Open Source Status --------------------- Because the public license path for Project-Authored Material is PolyForm @@ -94,7 +69,7 @@ Noncommercial License 1.0.0 rather than an OSI-approved license, the repository as a whole is source-available and dual-licensed. It is not an OSI Open Source distribution as a whole. -7. Reference Structures +6. Reference Structures ----------------------- This licensing structure intentionally borrows the general dual-license shape @@ -107,7 +82,7 @@ This repository does not claim that Qt or MySQL use the same exact public license terms used here. Those projects are referenced only as structural examples of dual licensing, not as the operative terms of this repository. -8. Contributions +7. Contributions ---------------- External contributions are accepted only under the EpiphanyAgent Contributor diff --git a/README.md b/README.md index c640dc83..aa29c3ff 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,19 @@ enough to act honestly. This is not a faster autocomplete costume. It is the beginning of governed human/agent labor. +## Front Door + +- **Value:** the shared typed Mind for agent work: governed project state, role lanes for research, modeling, implementation, verification, planning and Persona, and receipts for commands, edits and commits, so work stays coherent past one prompt, one agent or one session. +- **Problem:** global coherence collapse. Agents keep producing plausible local progress after the project has lost a valid global model; Epiphany turns understanding into inspectable state instead of a longer prompt. +- **Entry points:** + - `AGENTS.md`: operating law and re-entry rites. Read it before touching the machine. + - `epiphany-core/`: the typed domain organs (`src/runtime_spine.rs` job, worker, model and tool documents; `src/substrate_gate.rs`; `src/state_model.rs`; `src/mind_documents.rs`). + - `cargo run --bin epiphany-state -- status`: the steward CLI for direct Mind inspection (other native binaries are declared in `Cargo.toml`). + - `schemas/cultnet/`, `state/map.yaml` (canonical project map), `notes/epiphany-current-algorithmic-map.md` (live control flow). + - `docs/positioning.md`: what is claimed and what is not yet earned. +- **Live:** a supervised engineering alpha: durable typed state and prompt projection, update/proposal/promotion/review paths, runtime-spine documents, operator status and coordinator commands, heartbeat and Persona-state machinery, and the Hands, Eyes, Soul, Mind, Continuity and Substrate Gate receipt families. +- **Planned:** sustained autonomous GameCult production, complete Bifrost attribution, and a longitudinal fleet of one Epiphany organism per project Body. See What Exists Now. + ## The GameCult Bet GameCult is building infrastructure for coordinated work between humans, @@ -120,19 +133,15 @@ Epiphany is a native GameCult runtime that began as an opinionated Codex fork. Her body is made of: -- typed Rust domain organs in `epiphany-core` and `epiphany-state-model` +- typed Rust domain organs owned by `epiphany-core` - CultCache `.cc` stores for runtime, heartbeat, agent state, local Verse, memory graph, and thread state - CultMesh and CultNet contracts for local and distributed state - provider-neutral model and tool request/receipt documents - Mind, Substrate Gate, Eyes, Hands, Soul, Continuity, Persona, and heartbeat authority surfaces -- Codex retained only for honest OpenAI subscription auth and model transport; - Epiphany does not publish a Codex-owned project-state surface -- an independently supervised, loopback-only - [model provider connector](docs/model-provider-connector.md) can lend that - transport to an admitted GameCult service without lending Epiphany's Mind or - credentials +- a narrow standalone CodexConnector process for OpenAI subscription auth and + model transport; Epiphany compiles no Codex crate and keeps cognition local That last clause matters. Codex and other frontier agents can be excellent workers. They do not decide which organizational state is authoritative, who diff --git a/docs/README.md b/docs/README.md index 954cd487..02786fdd 100644 --- a/docs/README.md +++ b/docs/README.md @@ -25,9 +25,6 @@ outside the working notes. - [Fork implementation plan](../notes/epiphany-fork-implementation-plan.md) records the current architecture and cut lines. - [Project map](../state/map.yaml) is the canonical slow machine map. -- [Model provider connector](./model-provider-connector.md) records the typed - loopback boundary that lends Epiphany's Codex transport without lending her - Mind, credentials, or swarm runtime. For historical GameCult-level investor context, the sibling site dossier lives at `F:\Projects\gamecult-site\docs\gamecult_integrated_dossier.tex`. It is a diff --git a/docs/epiphany_body_whitepaper.tex b/docs/epiphany_body_whitepaper.tex index 973b94bb..05046be2 100644 --- a/docs/epiphany_body_whitepaper.tex +++ b/docs/epiphany_body_whitepaper.tex @@ -48,7 +48,7 @@ The practical definition in this repository is: \begin{itemize} \item the root checkout at \code{F:\Projects\Epiphany}; - \item Epiphany Rust crates such as \code{epiphany-core}, \code{epiphany-state-model}, model/tool adapters, and OpenAI transport crates; + \item Epiphany Rust crates such as \code{epiphany-core}, model/tool adapters, and provider runtimes; \item vendored substrate in \code{vendor/codex}, \code{vendor/cultcache-rs}, \code{vendor/cultnet-rs}, and \code{vendor/cultmesh-rs}; \item typed state stores under \code{state/}; \item schemas under \code{schemas/cultnet/}; @@ -59,20 +59,19 @@ The practical definition in this repository is: \section{Executive Map} -Epiphany's current Body is a hybrid machine in transition. Its center of gravity -has moved into typed Rust/CultCache organs, but compatibility arteries still -run through vendored Codex. The live target is not a cleaner pile of Codex -routes. The target is an Epiphany-native CultCache/CultMesh/CultNet body with a -relatively vanilla Codex-derived reliquary retained for OpenAI subscription -auth and model transport. +Epiphany's current Body is a typed Rust/CultCache organism with provider +transport outside its cognition boundary. Codex requests cross a lean client +ABI into the independent CodexConnector daemon, which owns subscription +credential refresh, transport, and replay. Epiphany owns prompts, tools, +reasoning context, decisions, and Mind admission; it compiles no Codex crate. \begin{longtable}{>{\raggedright\arraybackslash}p{0.23\linewidth}>{\raggedright\arraybackslash}p{0.32\linewidth}>{\raggedright\arraybackslash}p{0.35\linewidth}} \textbf{Body Region} & \textbf{Current Owner} & \textbf{Invariant} \\ \hline Durable Mind state & keyed CultCache documents assembled by \code{epiphany-core} & Prompt rendering consumes sealed typed projections; no persisted thread aggregate or global authority revision exists. \\ Domain policy & \code{epiphany-core} & Verdicts, launch policy, acceptance rules, runtime lifecycle, and mutation law live here, not in bridge DTOs. \\ -Vendored Codex & \code{vendor/codex} & Retain host/app-server/auth/model compatibility where useful; do not let Codex own Epiphany prompts, scheduler, or state. \\ -Runtime spine & \code{epiphany-core/src/runtime_spine.rs} and \code{state/runtime-spine.cc} & Runtime identity, jobs, events, receipts, tool/model documents are typed CultCache records. \\ +Codex transport & independent \code{GameCult/CodexConnector} daemon & Own subscription credentials, provider transport, and exact replay only; never own Epiphany prompts, scheduler, decisions, or state. \\ +Runtime spine & \code{epiphany-core/src/runtime_spine.rs} and \code{state/runtime-spine.cc} & Runtime identity, jobs, receipts, and tool/model documents are typed CultCache records. \\ Persona memory & keyed Mind Persona-memory documents & Persona decisions admit bounded memories by semantic identity; no parallel role-memory aggregate or generation head exists. \\ Resident Self physiology & Resident Self CultCache documents via \code{epiphany-swarm} & Typed pressure, exact grant creation, launch exclusion, leases, terminal receipts, retry, and bounded retention have one owner. No scheduler singleton or acknowledgement loop exists. \\ Local Verse & CultMesh over \code{state/local-verse.cc} & Local Verse context is compact prompt/operator context; private state does not become public by flipping a flag. \\ @@ -82,27 +81,21 @@ Operator artifacts & \code{.epiphany-*}, \code{tools/epiphany_local_run.ps1} & A \section{Repository Body} -The checkout has nine primary Epiphany Rust crates: +The checkout has eight primary Epiphany Rust packages: \begin{itemize} - \item \code{epiphany-core}: the main domain organ. It owns state-update - validation, launch documents, runtime spine, Resident Self, Persona - projection, Substrate Gate, Mind, Eyes, Hands, Soul, Continuity, CultMesh - integration, surface derivation, smokes, and local operator binaries. - \item \code{epiphany-state-model}: the durable \code{EpiphanyThreadState} - type plus \code{render_epiphany_state}. This is the typed Mind surface that - prompt fragments and views read. + \item \code{epiphany-core}: the main domain organ. It owns semantic state + contracts, keyed Mind admission, launch documents, runtime spine, Resident + Self, Persona projection, Substrate Gate, Eyes, Hands, Soul, Continuity, + CultMesh integration, and surface derivation. \item \code{epiphany-model-adapter}: provider-neutral model request, stream event, receipt, and status documents. \item \code{epiphany-openai-adapter}: OpenAI-shaped request/event/receipt and status documents. - \item \code{epiphany-openai-auth-spine}: Codex-derived auth boundary over - \code{codex-login}. It preserves honest Codex-compatible credential and token - behavior rather than impersonating it from scratch. - \item \code{epiphany-openai-codex-spine}: model transport over vendored Codex - client machinery. \item \code{epiphany-openai-runtime}: executable model runtime, including \code{epiphany-model-runtime}. + \item \code{epiphany-release-construction}: exact package construction and + release-witness verification. \item \code{epiphany-tool-adapter}: typed tool capability, invocation intent, and invocation receipt documents. \item \code{epiphany-tool-mcp-runtime}: first-party tool executor. Native @@ -114,8 +107,8 @@ The checkout has nine primary Epiphany Rust crates: The vendored region contains: \begin{itemize} - \item \code{vendor/codex}: the Codex-derived host and compatibility substrate. - It is tracked directly, not as a submodule. + \item \code{vendor/codex}: retained Codex-fork source history. It is not in + Epiphany's compiled runtime graph. \item \code{vendor/cultcache-rs}, \code{vendor/cultnet-rs}, and \code{vendor/cultmesh-rs}: repo-contained Rust Cult substrate. \end{itemize} @@ -170,7 +163,7 @@ publication, sealed forensic artifacts, or named quarantine experiments. the Body. It imports and registers: \begin{itemize} - \item runtime identity, session, job, job-result, and event records; + \item runtime identity, session, job, and job-result records; \item worker launch requests and role/reorient worker result documents; \item model adapter, OpenAI adapter, and tool adapter documents; \item sealed reasoning bases, decision contexts, typed pass failures, and diff --git a/docs/epiphany_investor_brief.md b/docs/epiphany_investor_brief.md index b5ab23f2..cbd5c556 100644 --- a/docs/epiphany_investor_brief.md +++ b/docs/epiphany_investor_brief.md @@ -77,7 +77,7 @@ conflicts, or a consequential judgment cannot be derived honestly. The current native Epiphany body includes: -- typed Rust domain organs in `epiphany-core` and `epiphany-state-model` +- typed Rust domain organs owned by `epiphany-core` - keyed Mind state with typed projection and promotion paths - RepoModel and Model Atlas surfaces for source-grounded architectural belief - CultCache `.cc` state and CultMesh/CultNet contracts @@ -86,7 +86,7 @@ The current native Epiphany body includes: - runtime-spine job and worker-result documents - Mind, Modeling, Substrate Gate, Eyes, Hands, Soul, Continuity, Persona, and heartbeat authority surfaces -- local operator status, coordinator, smoke, and Verse-context commands +- local operator status, coordinator, and Verse-context commands Codex is retained only for OpenAI authentication and model transport. Epiphany does not publish Codex app-server state as its project Mind. diff --git a/docs/model-provider-connector.md b/docs/model-provider-connector.md deleted file mode 100644 index 82a1b383..00000000 --- a/docs/model-provider-connector.md +++ /dev/null @@ -1,117 +0,0 @@ -# Epiphany Model Provider Connector - -## Objective - -Let another GameCult service use Epiphany's admitted Codex subscription -transport without acquiring Epiphany's Mind, credentials, scheduler, swarm -runtime, or authority. The first consumer is Ghostlight Dungeon. - -The connector is an independently supervised loopback service. Its daemon is -earned by credential isolation, request-concurrency ownership, and failure -isolation from both the consumer and the Epiphany swarm. - -## Authority map - -- **Owner:** `epiphany-model-connector` owns Codex authentication custody, - provider transport, physical request concurrency, transport replay handling, - and a redacted capability/status projection. -- **Inputs:** one encrypted, expiring - `epiphany.model_connector_invocation.v1` carried in a bounded CultNet - `DocumentPutRaw` frame. The admitted caller and model are configured by the - operator. -- **Outputs:** one encrypted `epiphany.model_connector_result.v1` containing - ordered provider events and the terminal model receipt, or a standard - CultNet refusal. Odin receives only - `epiphany.model_connector_status.v1`. -- **Derived state:** connector readiness and capacity are discovery state. They - are not provider credentials, inference permission, campaign state, or - Epiphany Mind state. -- **Forbidden writers:** the caller cannot select an unadmitted model, attach - tools, request reasoning summaries, chain provider response state, inspect - credentials, or mutate Epiphany. The connector cannot interpret model output - as application state or commit application mutations. -- **Shared path:** every accepted consumer request uses the same bounded frame, - authenticated envelope, caller/model checks, semaphore, Codex transport, and - terminal receipt validation. -- **Cut line:** the connector calls the existing Codex transport directly. It - does not start Epiphany's runtime, open her CultCache Mind, emit heartbeat - state, or participate in swarm scheduling. - -## Wire flow - -```text -consumer-owned typed stage - -> provider-neutral model request - -> MessagePack connector invocation - -> AES-GCM authenticated envelope - -> bounded TCP-framed CultNet on loopback - -> Epiphany Codex transport - -> ordered text/provider receipt events - -> encrypted typed connector result - -> consumer validation and local interpretation - -> consumer-owned commit authority -``` - -The outer CultNet message ID, document key, encrypted envelope request ID, -invocation request ID, model request ID, every stream-event request ID, and -terminal receipt request ID must agree. Requests carry a short expiry and an -exact payload digest. A repeated request ID with identical bytes receives the -cached terminal response for that connector lifetime; a conflicting or -concurrently active duplicate is rejected. Application idempotency and commit -authority remain with the consumer. - -The frame decoder rejects a declared payload larger than its advertised limit -before allocating the payload. This is a CultNet transport invariant rather -than a connector-specific workaround. - -## Security and privacy - -- The listener must bind to loopback. It is never routed through nginx or a - public firewall. -- A service-private shared key authenticates and encrypts every invocation and - result. It is stored outside source and release directories. -- The Codex credential stays in the connector's service-readable credential - directory. It is never copied into Ghostlight, arguments, logs, CultMesh, or - Odin. -- Tools, provider-side state chaining, reasoning summaries, reasoning deltas, - and tool-call events are refused. -- Prompts and results are not persisted by the connector. The consumer owns - its own permitted stage receipts. - -## Cache and token efficiency - -The request contract carries `prompt_cache_key` and `max_output_tokens` through -the existing model spine. Consumers construct the cache key from stable stage -and output-contract identity, not volatile world state. Provider receipts -report prompt, completion, reasoning, and cached-input token counts when the -provider supplies them. Epiphany neither fabricates cache hits nor interprets -their application value. - -## Deployment boundary - -Idunn manages this connector as a separate immutable artifact and service. An -Epiphany deployment brake may prevent changing Epiphany's swarm release; it -does not make the connector part of that release and must not wake the swarm. -The connector's own deployment and continuity remain explicit. - -The default endpoint is `127.0.0.1:4103`, maximum payload is one MiB, and -physical concurrency is eight. On Yggdrasil, the Odin publication endpoint is -`10.77.0.1:17871`; local `127.0.0.1:17871` belongs to Idunn. - -## Verification - -Focused tests prove: - -- encrypted round trips and exact request correlation; -- wrong caller, model, expiry, replay, tool, and reasoning refusals; -- no reasoning or tool material crosses the accepted result boundary; -- connector status contains no credential or application state; -- oversized TCP-framed payloads are refused before allocation; -- provider cache-token telemetry survives the Codex spine; -- a consumer transport can use the real framed/encrypted boundary without - learning connector credentials or Epiphany state. - -The build surface is one new production binary in the existing OpenAI runtime -crate. It reuses the existing CultNet, CultMesh, Codex login, and model-spine -dependencies. It does not add a second model client or a second orchestration -runtime. diff --git a/epiphany-core/Cargo.toml b/epiphany-core/Cargo.toml index 43ed6b10..e0986e4e 100644 --- a/epiphany-core/Cargo.toml +++ b/epiphany-core/Cargo.toml @@ -14,26 +14,23 @@ path = "src/lib.rs" anyhow = "1" chrono = "0.4.44" ctrlc = { version = "3.5.1", features = ["termination"] } -cultcache-rs = { path = "../vendor/cultcache-rs" } -cultmesh-rs = { path = "../vendor/cultmesh-rs" } -cultnet-rs = { path = "../vendor/cultnet-rs" } -epiphany-openai-adapter = { path = "../epiphany-openai-adapter" } -epiphany-release-construction = { path = "../epiphany-release-construction" } +cultcache-rs = { git = "https://github.com/GameCult/CultLib.git", rev = "a0813c6eed24d30bf88073ef615b633c77ebfcd6", package = "cultcache-rs" } +cultmesh-rs = { git = "https://github.com/GameCult/CultLib.git", rev = "a0813c6eed24d30bf88073ef615b633c77ebfcd6", package = "cultmesh-rs" } +cultnet-rs = { git = "https://github.com/GameCult/CultLib.git", rev = "a0813c6eed24d30bf88073ef615b633c77ebfcd6", package = "cultnet-rs" } epiphany-model-adapter = { path = "../epiphany-model-adapter" } epiphany-tool-adapter = { path = "../epiphany-tool-adapter" } -epiphany-state-model = { path = "../epiphany-state-model" } +fs2 = "0.4" ghostlight-persona-projection = { git = "https://github.com/GameCult/Ghostlight.git", rev = "22937b71f056d2000dd7ea1f6af0c45825de495f", package = "ghostlight-persona-projection" } ed25519-dalek = "2" ignore = "0.4.23" rmp-serde = "1" -serde = { version = "1", features = ["derive"] } +serde ={ version = "1", features = ["derive"] } serde_json = "1" semver = { version = "1", features = ["serde"] } sha2 = "0.10" uuid = { version = "1", features = ["serde", "v4", "v5"] } [dev-dependencies] -pretty_assertions = "1.4.1" tempfile = "3" [target.'cfg(windows)'.dependencies] diff --git a/epiphany-core/src/admitted_model_direction_consideration.rs b/epiphany-core/src/admitted_model_direction_consideration.rs index e5c2d824..4dfbc278 100644 --- a/epiphany-core/src/admitted_model_direction_consideration.rs +++ b/epiphany-core/src/admitted_model_direction_consideration.rs @@ -186,7 +186,7 @@ pub fn validate_request(request: &AdmittedModelDirectionConsiderationRequest) -> Ok(()) } -pub fn request_is_superseded( +pub(crate) fn request_is_superseded( cache: &cultcache_rs::CultCache, request: &AdmittedModelDirectionConsiderationRequest, ) -> Result { @@ -240,7 +240,7 @@ pub fn result_id_for_launch(request_id: &str, job_id: &str) -> String { ) } -pub fn render_prompt(request: &AdmittedModelDirectionConsiderationRequest) -> String { +pub(crate) fn render_prompt(request: &AdmittedModelDirectionConsiderationRequest) -> String { format!( "Act as Epiphany Imagination for one proposal-only direction consideration. Inspect the exact current keyed Modeling map bound by request {} at projection digest {}. Suggest options or hold. Do not adopt, edit, execute, release, or deploy.", request.request_id, request.model_projection_digest diff --git a/epiphany-core/src/agent_launch.rs b/epiphany-core/src/agent_launch.rs index b9031d72..20932392 100644 --- a/epiphany-core/src/agent_launch.rs +++ b/epiphany-core/src/agent_launch.rs @@ -13,35 +13,6 @@ pub const EPIPHANY_VERIFICATION_OWNER_ROLE: &str = "epiphany-verifier"; pub const EPIPHANY_REORIENT_LAUNCH_BINDING_ID: &str = "reorient-worker"; pub const EPIPHANY_REORIENT_OWNER_ROLE: &str = "epiphany-reorient"; -pub fn epiphany_role_binding_id(role_id: EpiphanyRoleResultRoleId) -> Result<&'static str, String> { - match role_id { - EpiphanyRoleResultRoleId::Imagination => Ok(EPIPHANY_IMAGINATION_ROLE_BINDING_ID), - EpiphanyRoleResultRoleId::Research => Ok(EPIPHANY_RESEARCH_ROLE_BINDING_ID), - EpiphanyRoleResultRoleId::Modeling => Ok(EPIPHANY_MODELING_ROLE_BINDING_ID), - EpiphanyRoleResultRoleId::Verification => Ok(EPIPHANY_VERIFICATION_ROLE_BINDING_ID), - EpiphanyRoleResultRoleId::Implementation => Err( - "implementation is owned by the main coding agent; no role specialist launch template exists" - .to_string(), - ), - EpiphanyRoleResultRoleId::Reorientation => Err( - "reorientation uses epiphany.coordinator.reorient.launch and epiphany.coordinator.reorient.result.read" - .to_string(), - ), - } -} - -pub fn epiphany_role_owner(role_id: EpiphanyRoleResultRoleId) -> Result<&'static str, String> { - match role_id { - EpiphanyRoleResultRoleId::Imagination => Ok(EPIPHANY_IMAGINATION_OWNER_ROLE), - EpiphanyRoleResultRoleId::Research => Ok(EPIPHANY_RESEARCH_OWNER_ROLE), - EpiphanyRoleResultRoleId::Modeling => Ok(EPIPHANY_MODELING_OWNER_ROLE), - EpiphanyRoleResultRoleId::Verification => Ok(EPIPHANY_VERIFICATION_OWNER_ROLE), - EpiphanyRoleResultRoleId::Implementation | EpiphanyRoleResultRoleId::Reorientation => { - Err(epiphany_role_binding_id(role_id).unwrap_err()) - } - } -} - pub fn epiphany_role_label(role_id: EpiphanyRoleResultRoleId) -> &'static str { match role_id { EpiphanyRoleResultRoleId::Implementation => "implementation", @@ -760,17 +731,3 @@ pub fn epiphany_reorient_launch_output_schema() -> serde_json::Value { "additionalProperties": true }) } - -pub fn unique_strings(values: impl IntoIterator) -> Vec { - let mut unique = Vec::new(); - extend_unique_strings(&mut unique, values); - unique -} - -fn extend_unique_strings(target: &mut Vec, values: impl IntoIterator) { - for value in values { - if !target.iter().any(|existing| existing == &value) { - target.push(value); - } - } -} diff --git a/epiphany-core/src/atlas/store.rs b/epiphany-core/src/atlas/store.rs index e82ccf75..3cee306c 100644 --- a/epiphany-core/src/atlas/store.rs +++ b/epiphany-core/src/atlas/store.rs @@ -355,7 +355,7 @@ impl AtlasCultCacheStore { cache.register_entry_type::()?; cache.register_entry_type::()?; cache.register_entry_type::()?; - cache.add_generic_backing_store(SingleFileMessagePackBackingStore::new(&self.path)); + cache.add_generic_backing_store(SingleFileMessagePackBackingStore::new(&self.path))?; cache.pull_all_backing_stores()?; Ok(cache) } diff --git a/epiphany-core/src/bin/epiphany-mvp-coordinator.rs b/epiphany-core/src/bin/epiphany-mvp-coordinator.rs index 33f5f3e6..9e8d4cd3 100644 --- a/epiphany-core/src/bin/epiphany-mvp-coordinator.rs +++ b/epiphany-core/src/bin/epiphany-mvp-coordinator.rs @@ -2,25 +2,20 @@ use anyhow::Context; use anyhow::Result; use anyhow::anyhow; use chrono::SecondsFormat; -use epiphany_core::COORDINATOR_RUN_RECEIPT_SCHEMA_VERSION; use epiphany_core::COORDINATOR_RUN_RECEIPT_TYPE; use epiphany_core::EpiphanyCoordinatorRunReceipt; -use epiphany_core::HANDS_ACTION_INTENT_SCHEMA_VERSION; use epiphany_core::HANDS_COMMAND_RECEIPT_TYPE; use epiphany_core::HANDS_COMMIT_RECEIPT_TYPE; use epiphany_core::HANDS_PATCH_RECEIPT_TYPE; use epiphany_core::HandsActionIntent; -use epiphany_core::REPO_FRONTIER_HANDS_AUTHORITY_CONTRACT; -use epiphany_core::REPO_FRONTIER_HANDS_AUTHORITY_SCHEMA_VERSION; use epiphany_core::RepoFrontierHandsAuthority; use epiphany_core::RuntimeSpineInitOptions; +use epiphany_core::coordinator_status as status_projection; use epiphany_core::finalize_coordinator_run; -use epiphany_core::hands_action_review_for_intent; use epiphany_core::initialize_runtime_spine; use epiphany_core::load_epiphany_cultmesh_swarm_brake; use epiphany_core::open_coordinator_run; use epiphany_core::put_hands_action_intent; -use epiphany_core::put_hands_action_review; use epiphany_core::put_repo_frontier_hands_authority; use epiphany_core::put_substrate_gate_repo_access_grant_receipt; use epiphany_core::select_and_commit_repo_frontier_route; @@ -28,13 +23,13 @@ use epiphany_core::substrate_gate_coordinator_implementation_grant; use epiphany_core::{ LaunchedCoordinator, capture_process_instance, claim_resident_self_preparation_as_child, }; -use epiphany_core::coordinator_status as status_projection; use serde_json::Value; use serde_json::json; use sha2::{Digest, Sha256}; use std::collections::BTreeMap; use std::env; use std::fs; +use std::net::SocketAddr; use std::path::{Path, PathBuf}; use std::process::{Command, Stdio}; use std::thread; @@ -72,7 +67,7 @@ struct Args { thread_id: Option, objective: Option, cwd: PathBuf, - codex_home: PathBuf, + model_connector_endpoint: SocketAddr, mcp_config: PathBuf, artifact_dir: PathBuf, runtime_store: PathBuf, @@ -86,7 +81,6 @@ struct Args { auto_review: bool, supersede_failed_results: bool, approve_manual_regather: bool, - auto_tools: bool, proposal_modeling_request_id: Option, imagination_consideration_request_id: Option, admitted_model_direction_consideration_request_id: Option, @@ -110,9 +104,7 @@ impl Args { thread_id: None, objective: None, cwd: root.clone(), - codex_home: env::var_os("CODEX_HOME") - .map(PathBuf::from) - .unwrap_or_else(|| home_dir().join(".codex")), + model_connector_endpoint: "127.0.0.1:17891".parse().unwrap(), mcp_config: root.join(".epiphany").join("mcp.toml"), artifact_dir: root.join(".epiphany-dogfood").join("coordinator"), runtime_store: root.join("state").join("runtime-spine.msgpack"), @@ -129,7 +121,6 @@ impl Args { auto_review: false, supersede_failed_results: false, approve_manual_regather: false, - auto_tools: true, proposal_modeling_request_id: None, imagination_consideration_request_id: None, admitted_model_direction_consideration_request_id: None, @@ -202,7 +193,10 @@ impl Args { parsed.required_action = Some(take_string(&mut args, "--required-action")?) } "--cwd" => parsed.cwd = take_path(&mut args, "--cwd")?, - "--codex-home" => parsed.codex_home = take_path(&mut args, "--codex-home")?, + "--connector-endpoint" => { + parsed.model_connector_endpoint = + take_string(&mut args, "--connector-endpoint")?.parse()? + } "--mcp-config" => parsed.mcp_config = take_path(&mut args, "--mcp-config")?, "--artifact-dir" => parsed.artifact_dir = take_path(&mut args, "--artifact-dir")?, "--runtime-store" => { @@ -231,8 +225,6 @@ impl Args { "--auto-review" => parsed.auto_review = true, "--supersede-failed-results" => parsed.supersede_failed_results = true, "--approve-manual-regather" => parsed.approve_manual_regather = true, - "--auto-tools" => parsed.auto_tools = true, - "--no-auto-tools" => parsed.auto_tools = false, "--test-complete-backend" => { return Err(anyhow!( "--test-complete-backend was removed: native coordinator refuses direct private state-store job mutation; use live workers or a future CultNet job-result API" @@ -246,6 +238,9 @@ impl Args { "coordinator requires exact --runtime-id for local Verse authority" )); } + if !matches!(parsed.mode.as_str(), "plan" | "execute") { + return Err(anyhow!("--mode must be plan or execute")); + } if parsed.required_action.is_some() && (parsed.mode != "execute" || parsed.max_steps != 1) { return Err(anyhow!( "--required-action requires execute mode with exactly one step" @@ -287,12 +282,10 @@ fn run_coordinator(args: &Args) -> Result { let cwd = status_projection::absolute_path(&args.cwd)?; let model_runtime_bin = resolve_model_runtime_bin(&root, &args.model_runtime_bin)?; let tool_adapter_bin = resolve_model_runtime_bin(&root, &args.tool_adapter_bin)?; - let codex_home = status_projection::absolute_path(&args.codex_home)?; let mcp_config = status_projection::absolute_path(&args.mcp_config)?; let artifact_dir = status_projection::absolute_path(&args.artifact_dir)?; let runtime_store = status_projection::absolute_path(&args.runtime_store)?; reset_artifact_dir(&artifact_dir)?; - fs::create_dir_all(&codex_home)?; let telemetry_path = artifact_dir.join("agent-function-telemetry.json"); let steps_path = artifact_dir.join("coordinator-steps.jsonl"); @@ -370,7 +363,7 @@ fn run_coordinator(args: &Args) -> Result { "threadId": thread_id, "grantId": claim.grant_id, "preparationId": preparation_id, - "mindProjectionDigest": intake.mind.projection_digest, + "mindProjectionDigest": intake.mind_projection_digest, "commitReceiptId": intake.commit_receipt.receipt_id, "canonicalObjectiveChanged": intake.changed, })); @@ -384,7 +377,7 @@ fn run_coordinator(args: &Args) -> Result { startup_events.push(json!({ "type": "operatorObjectiveIntake", "threadId": thread_id, - "mindProjectionDigest": intake.mind.projection_digest, + "mindProjectionDigest": intake.mind_projection_digest, "commitReceiptId": intake.commit_receipt.receipt_id, "changed": intake.changed, })); @@ -412,7 +405,7 @@ fn run_coordinator(args: &Args) -> Result { &args.model, args.provider_credential_path.as_deref(), &runtime_store, - &codex_home, + args.model_connector_endpoint, &mcp_config, &cwd, args.resident_state_store.as_deref(), @@ -421,7 +414,6 @@ fn run_coordinator(args: &Args) -> Result { 0, &artifact_dir, args.max_runtime_seconds, - args.auto_tools, )?; startup_events.push(json!({ "type": "imaginationConsiderationLaunch", @@ -455,7 +447,7 @@ fn run_coordinator(args: &Args) -> Result { &args.model, args.provider_credential_path.as_deref(), &runtime_store, - &codex_home, + args.model_connector_endpoint, &mcp_config, &cwd, args.resident_state_store.as_deref(), @@ -464,7 +456,6 @@ fn run_coordinator(args: &Args) -> Result { 0, &artifact_dir, args.max_runtime_seconds, - args.auto_tools, )?; startup_events.push(json!({ "type": "admittedModelDirectionConsiderationLaunch", @@ -498,11 +489,10 @@ fn run_coordinator(args: &Args) -> Result { "explicit proposal Modeling request does not match current keyed work" )); } - let binding = epiphany_core::launch_current_proposal_modeling_work( + let worker_job_id = epiphany_core::launch_current_proposal_modeling_work( &runtime_store, epiphany_core::EpiphanyProposalModelingLaunchOptions { created_at: now() }, )?; - let worker_job_id = binding.job_id.clone(); startup_proposal_modeling_job_id = Some(worker_job_id.clone()); let worker_run = launch_worker_runtime_detached( &model_runtime_bin, @@ -511,7 +501,7 @@ fn run_coordinator(args: &Args) -> Result { &args.model, args.provider_credential_path.as_deref(), &runtime_store, - &codex_home, + args.model_connector_endpoint, &mcp_config, &cwd, args.resident_state_store.as_deref(), @@ -520,13 +510,11 @@ fn run_coordinator(args: &Args) -> Result { 0, &artifact_dir, args.max_runtime_seconds, - args.auto_tools, )?; startup_events.push(json!({ "type": "proposalModelingLaunch", "requestId": request_id, "runtimeJobId": worker_job_id, - "launch": binding, "worker": worker_run, "authority": "proposal-modeling-only" })); @@ -558,7 +546,9 @@ fn run_coordinator(args: &Args) -> Result { let snapshot_name = format!("step-{index:02}-{action}.txt"); fs::write( artifact_dir.join(&snapshot_name), - status_projection::render_status(&status_projection::sanitize_for_operator(status.clone())), + status_projection::render_status(&status_projection::sanitize_for_operator( + status.clone(), + )), )?; snapshots.push(snapshot_name); let mut step = json!({ @@ -665,7 +655,7 @@ fn run_coordinator(args: &Args) -> Result { &args.model, args.provider_credential_path.as_deref(), &runtime_store, - &codex_home, + args.model_connector_endpoint, &mcp_config, &cwd, args.resident_state_store.as_deref(), @@ -674,7 +664,6 @@ fn run_coordinator(args: &Args) -> Result { index, &artifact_dir, args.max_runtime_seconds, - args.auto_tools, )?; push_event( &mut step, @@ -716,7 +705,7 @@ fn run_coordinator(args: &Args) -> Result { &args.model, args.provider_credential_path.as_deref(), &runtime_store, - &codex_home, + args.model_connector_endpoint, &mcp_config, &cwd, args.resident_state_store.as_deref(), @@ -725,7 +714,6 @@ fn run_coordinator(args: &Args) -> Result { index, &artifact_dir, args.max_runtime_seconds, - args.auto_tools, )?; push_event( &mut step, @@ -752,200 +740,101 @@ fn run_coordinator(args: &Args) -> Result { "reviewResearchResult" | "reviewModelingResult" | "reviewVerificationResult" => { let role_id = role_id_for_coordinator_action(&action) .ok_or_else(|| anyhow!("unsupported review action {action}"))?; - if role_id == "research" - && let Some(job_id) = - epiphany_core::current_frontier_research_review_job_id(&runtime_store)? - { - let result = - epiphany_core::runtime_role_worker_result(&runtime_store, &job_id)? - .ok_or_else(|| { - anyhow!("frontier Research review lost its typed result") - })?; - push_event( - &mut step, - json!({"type": "frontierResearchResult", "roleId": role_id, "result": result}), - ); - if !args.auto_review { - final_action = json!({ - "action": "reviewResearchResult", - "reason": "The exact frontier Research result awaits keyed Mind admission.", - "runtimeJobId": job_id, - }); - append_operator_step_jsonl(&steps_path, &step)?; - steps.push(step); - break; + let current_work = epiphany_core::project_current_work(&runtime_store)?; + let target = family_review_target(role_id, ¤t_work).ok_or_else(|| { + anyhow!( + "{role_id} review has no keyed current-work owner; generic role-lane review is retired" + ) + })?; + let (family_label, result_event_type, admission_owner) = match target.kind { + FamilyReviewKind::FrontierResearch => { + ("frontier Research", "frontierResearchResult", "keyed Mind") } - let accepted = epiphany_core::accept_frontier_research_result( - &runtime_store, - &job_id, - &now(), - )?; - push_event( - &mut step, - json!({"type": "frontierResearchAccept", "roleId": role_id, "commit": accepted}), - ); - final_status = collect_coordinator_status(&runtime_store, &thread_id)?; + FamilyReviewKind::FrontierVerification => ( + "frontier Verification", + "frontierVerificationResult", + "Soul", + ), + FamilyReviewKind::ProposalModeling => { + ("proposal Modeling", "proposalModelingResult", "keyed Mind") + } + FamilyReviewKind::BodyModeling => { + ("Body Modeling", "bodyModelingResult", "keyed Mind") + } + FamilyReviewKind::FrontierVerdictModeling => ( + "frontier verdict Modeling", + "frontierVerdictModelingResult", + "keyed Mind", + ), + }; + let result = + epiphany_core::runtime_role_worker_result(&runtime_store, target.job_id)? + .ok_or_else(|| anyhow!("{family_label} review lost its typed result"))?; + push_event( + &mut step, + json!({ + "type": result_event_type, + "roleId": role_id, + "result": result, + }), + ); + if !args.auto_review { + final_action = json!({ + "action": action, + "reason": format!("The exact {family_label} result awaits {admission_owner} admission."), + "runtimeJobId": target.job_id, + }); append_operator_step_jsonl(&steps_path, &step)?; steps.push(step); - continue; + break; } - if role_id == "verification" - && let Some(job_id) = - epiphany_core::current_frontier_verification_review_job_id( + let admission_event = match target.kind { + FamilyReviewKind::FrontierResearch => { + let commit = epiphany_core::accept_frontier_research_result( &runtime_store, - )? - { - let result = - epiphany_core::runtime_role_worker_result(&runtime_store, &job_id)? - .ok_or_else(|| { - anyhow!("frontier Verification review lost its typed result") - })?; - push_event( - &mut step, - json!({"type": "frontierVerificationResult", "roleId": role_id, "result": result}), - ); - if !args.auto_review { - final_action = json!({ - "action": "reviewVerificationResult", - "reason": "The exact frontier Verification result awaits Soul admission.", - "runtimeJobId": job_id, - }); - append_operator_step_jsonl(&steps_path, &step)?; - steps.push(step); - break; + target.job_id, + &now(), + )?; + json!({"type": "frontierResearchAccept", "roleId": role_id, "commit": commit}) } - let admission = epiphany_core::accept_frontier_verification_result( - &runtime_store, - &job_id, - &now(), - )?; - push_event( - &mut step, - json!({"type": "frontierVerificationAdmission", "roleId": role_id, "outcome": admission}), - ); - final_status = collect_coordinator_status(&runtime_store, &thread_id)?; - append_operator_step_jsonl(&steps_path, &step)?; - steps.push(step); - continue; - } - if role_id == "modeling" - && let Some(job_id) = - epiphany_core::current_proposal_modeling_review_job_id(&runtime_store)? - { - let result = - epiphany_core::runtime_role_worker_result(&runtime_store, &job_id)? - .ok_or_else(|| { - anyhow!("proposal Modeling review lost its typed result") - })?; - push_event( - &mut step, - json!({"type": "proposalModelingResult", "roleId": role_id, "result": result}), - ); - if !args.auto_review { - final_action = json!({ - "action": "reviewModelingResult", - "reason": "The exact proposal Modeling result awaits keyed Mind admission.", - "runtimeJobId": job_id, - }); - append_operator_step_jsonl(&steps_path, &step)?; - steps.push(step); - break; - } - let admission = epiphany_core::accept_proposal_modeling_result( - &runtime_store, - &job_id, - &now(), - )?; - push_event( - &mut step, - json!({"type": "proposalModelingAdmission", "roleId": role_id, "outcome": admission}), - ); - final_status = collect_coordinator_status(&runtime_store, &thread_id)?; - append_operator_step_jsonl(&steps_path, &step)?; - steps.push(step); - continue; - } - if role_id == "modeling" - && let Some(job_id) = - epiphany_core::current_body_modeling_review_job_id(&runtime_store)? - { - let result = - epiphany_core::runtime_role_worker_result(&runtime_store, &job_id)? - .ok_or_else(|| { - anyhow!("Body Modeling review lost its typed result") - })?; - push_event( - &mut step, - json!({"type": "bodyModelingResult", "roleId": role_id, "result": result}), - ); - if !args.auto_review { - final_action = json!({ - "action": "reviewModelingResult", - "reason": "The exact Body Modeling result awaits keyed Mind admission.", - "runtimeJobId": job_id, - }); - append_operator_step_jsonl(&steps_path, &step)?; - steps.push(step); - break; - } - let admission = epiphany_core::accept_body_modeling_result( - &runtime_store, - &job_id, - &now(), - )?; - push_event( - &mut step, - json!({"type": "bodyModelingAdmission", "roleId": role_id, "outcome": admission}), - ); - final_status = collect_coordinator_status(&runtime_store, &thread_id)?; - append_operator_step_jsonl(&steps_path, &step)?; - steps.push(step); - continue; - } - if role_id == "modeling" - && let Some(job_id) = - epiphany_core::current_frontier_verdict_modeling_review_job_id( + FamilyReviewKind::FrontierVerification => { + let outcome = epiphany_core::accept_frontier_verification_result( &runtime_store, - )? - { - let result = - epiphany_core::runtime_role_worker_result(&runtime_store, &job_id)? - .ok_or_else(|| { - anyhow!( - "frontier verdict Modeling review lost its typed result" - ) - })?; - push_event( - &mut step, - json!({"type": "frontierVerdictModelingResult", "roleId": role_id, "result": result}), - ); - if !args.auto_review { - final_action = json!({ - "action": "reviewModelingResult", - "reason": "The exact frontier verdict Modeling result awaits keyed Mind admission.", - "runtimeJobId": job_id, - }); - append_operator_step_jsonl(&steps_path, &step)?; - steps.push(step); - break; + target.job_id, + &now(), + )?; + json!({"type": "frontierVerificationAdmission", "roleId": role_id, "outcome": outcome}) } - let admission = epiphany_core::accept_frontier_verdict_modeling_result( - &runtime_store, - &job_id, - &now(), - )?; - push_event( - &mut step, - json!({"type": "frontierVerdictModelingAdmission", "roleId": role_id, "outcome": admission}), - ); - final_status = collect_coordinator_status(&runtime_store, &thread_id)?; - append_operator_step_jsonl(&steps_path, &step)?; - steps.push(step); - continue; - } - return Err(anyhow!( - "{role_id} review has no keyed current-work owner; generic role-lane review is retired" - )); + FamilyReviewKind::ProposalModeling => { + let outcome = epiphany_core::accept_proposal_modeling_result( + &runtime_store, + target.job_id, + &now(), + )?; + json!({"type": "proposalModelingAdmission", "roleId": role_id, "outcome": outcome}) + } + FamilyReviewKind::BodyModeling => { + let outcome = epiphany_core::accept_body_modeling_result( + &runtime_store, + target.job_id, + &now(), + )?; + json!({"type": "bodyModelingAdmission", "roleId": role_id, "outcome": outcome}) + } + FamilyReviewKind::FrontierVerdictModeling => { + let outcome = epiphany_core::accept_frontier_verdict_modeling_result( + &runtime_store, + target.job_id, + &now(), + )?; + json!({"type": "frontierVerdictModelingAdmission", "roleId": role_id, "outcome": outcome}) + } + }; + push_event(&mut step, admission_event); + final_status = collect_coordinator_status(&runtime_store, &thread_id)?; + append_operator_step_jsonl(&steps_path, &step)?; + steps.push(step); + continue; } "reviewReorientResult" => { let result = read_reorient_result(&runtime_store, &thread_id)?; @@ -1038,7 +927,7 @@ fn run_coordinator(args: &Args) -> Result { &args.model, args.provider_credential_path.as_deref(), &runtime_store, - &codex_home, + args.model_connector_endpoint, &mcp_config, &cwd, args.resident_state_store.as_deref(), @@ -1047,7 +936,6 @@ fn run_coordinator(args: &Args) -> Result { index, &artifact_dir, args.max_runtime_seconds, - args.auto_tools, )?; push_event( &mut step, @@ -1082,7 +970,7 @@ fn run_coordinator(args: &Args) -> Result { &args.model, args.provider_credential_path.as_deref(), &runtime_store, - &codex_home, + args.model_connector_endpoint, &mcp_config, &cwd, args.resident_state_store.as_deref(), @@ -1091,7 +979,6 @@ fn run_coordinator(args: &Args) -> Result { index, &artifact_dir, args.max_runtime_seconds, - args.auto_tools, )?; push_event( &mut step, @@ -1150,7 +1037,7 @@ fn run_coordinator(args: &Args) -> Result { "Self-derived proposal Modeling request changed before launch" )); } - let binding = epiphany_core::launch_current_proposal_modeling_work( + let job_id = epiphany_core::launch_current_proposal_modeling_work( &runtime_store, epiphany_core::EpiphanyProposalModelingLaunchOptions { created_at: now(), @@ -1158,23 +1045,18 @@ fn run_coordinator(args: &Args) -> Result { )?; json!({ "bindingId": epiphany_core::EPIPHANY_MODELING_ROLE_BINDING_ID, - "backendJobId": binding.job_id, - "proposalModelingLaunchBinding": binding, + "backendJobId": job_id, }) } else if role_id == "modeling" && status["currentWork"]["bodyModeling"].is_object() { - let binding = epiphany_core::launch_current_body_modeling_work( + let job_id = epiphany_core::launch_current_body_modeling_work( &runtime_store, - epiphany_core::EpiphanyBodyModelingLaunchOptions { - job_id: Uuid::new_v4().to_string(), - created_at: now(), - }, + &now(), )?; json!({ "bindingId": epiphany_core::EPIPHANY_MODELING_ROLE_BINDING_ID, - "backendJobId": binding.job_id, - "bodyModelingLaunchBinding": binding, + "backendJobId": job_id, }) } else if role_id == "modeling" && status["currentWork"]["frontierVerdictModeling"].is_object() @@ -1204,7 +1086,7 @@ fn run_coordinator(args: &Args) -> Result { &args.model, args.provider_credential_path.as_deref(), &runtime_store, - &codex_home, + args.model_connector_endpoint, &mcp_config, &cwd, args.resident_state_store.as_deref(), @@ -1213,7 +1095,6 @@ fn run_coordinator(args: &Args) -> Result { index, &artifact_dir, args.max_runtime_seconds, - args.auto_tools, )?; push_event( &mut step, @@ -1275,7 +1156,7 @@ fn run_coordinator(args: &Args) -> Result { &args.model, args.provider_credential_path.as_deref(), &runtime_store, - &codex_home, + args.model_connector_endpoint, &mcp_config, &cwd, args.resident_state_store.as_deref(), @@ -1284,7 +1165,6 @@ fn run_coordinator(args: &Args) -> Result { index, &artifact_dir, args.max_runtime_seconds, - args.auto_tools, )?; push_event( &mut step, @@ -1331,45 +1211,16 @@ fn run_coordinator(args: &Args) -> Result { "coordinator-final-action.txt".to_string(), "agent-function-telemetry.json".to_string(), ]; - let sealed_artifact_manifest = Vec::new(); let receipt_created_at = now(); let coordinator_run_receipt = EpiphanyCoordinatorRunReceipt { - schema_version: COORDINATOR_RUN_RECEIPT_SCHEMA_VERSION.to_string(), - receipt_id: format!( - "coordinator-run-{}-{}", - sanitize_id(&thread_id), - chrono::Utc::now().timestamp_millis() - ), + receipt_id: format!("coordinator-run-terminal-{runtime_session_id}"), session_id: runtime_session_id.clone(), thread_id: thread_id.clone(), mode: args.mode.clone(), status: coordinator_receipt_status(&args.mode, &final_action), final_action: final_action_name(&final_action), final_reason: final_action_reason(&final_action), - step_count: operator_steps - .as_array() - .map_or(0, |items| items.len() as u64), created_at: receipt_created_at, - model_provider: Some(args.model_provider.clone()), - runtime_store: runtime_store.display().to_string(), - artifact_refs: artifact_manifest.clone(), - sealed_artifact_refs: sealed_artifact_manifest.clone(), - metadata: BTreeMap::from([ - ( - "artifactDir".to_string(), - artifact_dir.display().to_string(), - ), - ( - "modelRuntimeBin".to_string(), - model_runtime_bin.display().to_string(), - ), - ( - "toolAdapterBin".to_string(), - tool_adapter_bin.display().to_string(), - ), - ("mcpConfig".to_string(), mcp_config.display().to_string()), - ("autoTools".to_string(), args.auto_tools.to_string()), - ]), resident_grant_id: args.resident_binding.get("grant-id").cloned(), resident_launch_digest: args.resident_binding.get("launch-digest").cloned(), resident_policy_digest: args.resident_binding.get("policy-digest").cloned(), @@ -1381,7 +1232,6 @@ fn run_coordinator(args: &Args) -> Result { .get("release-manifest-digest") .cloned(), resident_executable_digest: args.resident_binding.get("executable-digest").cloned(), - final_runtime_job_id: final_action["runtimeJobId"].as_str().map(str::to_string), }; finalize_coordinator_run(&runtime_store, &coordinator_run_receipt)?; let summary = json!({ @@ -1389,9 +1239,8 @@ fn run_coordinator(args: &Args) -> Result { "artifactDir": artifact_dir, "modelRuntimeBin": model_runtime_bin, "toolAdapterBin": tool_adapter_bin, - "autoTools": args.auto_tools, "modelProvider": args.model_provider, - "codexHome": codex_home, + "modelConnectorEndpoint": args.model_connector_endpoint, "mcpConfig": mcp_config, "runtimeStore": runtime_store, "workspace": cwd, @@ -1408,7 +1257,6 @@ fn run_coordinator(args: &Args) -> Result { "store": runtime_store, }, "artifactManifest": artifact_manifest, - "sealedArtifactManifest": [] }); write_json(&artifact_dir.join("coordinator-summary.json"), &summary)?; write_json( @@ -1440,13 +1288,8 @@ fn run_coordinator(args: &Args) -> Result { match run_result { Ok(summary) => Ok(summary), Err(run_error) => { - let failure_receipt = failed_coordinator_run_receipt( - args, - &runtime_session_id, - &thread_id, - &runtime_store, - &run_error, - ); + let failure_receipt = + failed_coordinator_run_receipt(args, &runtime_session_id, &thread_id, &run_error); match finalize_coordinator_run(&runtime_store, &failure_receipt) { Ok(_) => Err(anyhow!( "coordinator run failed after opening and was terminalized by receipt {:?}: {run_error:#}", @@ -1464,33 +1307,18 @@ fn failed_coordinator_run_receipt( args: &Args, session_id: &str, thread_id: &str, - runtime_store: &Path, error: &anyhow::Error, ) -> EpiphanyCoordinatorRunReceipt { let created_at = now(); EpiphanyCoordinatorRunReceipt { - schema_version: COORDINATOR_RUN_RECEIPT_SCHEMA_VERSION.to_string(), - receipt_id: format!( - "coordinator-run-{}-failed-{}", - sanitize_id(thread_id), - chrono::Utc::now().timestamp_millis() - ), + receipt_id: format!("coordinator-run-terminal-{session_id}"), session_id: session_id.to_string(), thread_id: thread_id.to_string(), mode: args.mode.clone(), status: "failed".to_string(), final_action: "failed".to_string(), final_reason: Some(format!("{error:#}")), - step_count: 0, created_at, - model_provider: Some(args.model_provider.clone()), - runtime_store: runtime_store.display().to_string(), - artifact_refs: Vec::new(), - sealed_artifact_refs: Vec::new(), - metadata: BTreeMap::from([( - "terminalizationOwner".to_string(), - "epiphany-mvp-coordinator-error-boundary".to_string(), - )]), resident_grant_id: args.resident_binding.get("grant-id").cloned(), resident_launch_digest: args.resident_binding.get("launch-digest").cloned(), resident_policy_digest: args.resident_binding.get("policy-digest").cloned(), @@ -1502,7 +1330,6 @@ fn failed_coordinator_run_receipt( .get("release-manifest-digest") .cloned(), resident_executable_digest: args.resident_binding.get("executable-digest").cloned(), - final_runtime_job_id: None, } } @@ -1604,73 +1431,17 @@ fn record_hands_implementation_gate( put_substrate_gate_repo_access_grant_receipt(runtime_store, &substrate_grant)?; let intent = HandsActionIntent { - schema_version: HANDS_ACTION_INTENT_SCHEMA_VERSION.to_string(), intent_id: format!("hands-intent-{suffix}"), runtime_job_id: runtime_job_id.clone(), - binding_id: "implementation-worker".to_string(), - role: "epiphany-hands".to_string(), - authority_scope: "epiphany.role.implementation".to_string(), - requested_action: "continueImplementation".to_string(), requested_paths: requested_paths.clone(), substrate_gate_grant_receipt_id: grant_id.clone(), - requested_at: requested_at.clone(), - contract: "Coordinator continuation becomes a typed Hands action intent before any file edit, command, or commit may count as implementation evidence." - .to_string(), - frontier_route_id: route - .adopted_plan - .as_ref() - .map(|_| route.route_id.clone()) - .unwrap_or_default(), - plan_candidate_sha256: route - .adopted_plan - .as_ref() - .map(|plan| plan.candidate_sha256.clone()) - .unwrap_or_default(), - plan_action: route - .adopted_plan - .as_ref() - .map(|plan| plan.effective_action().to_string()) - .unwrap_or_default(), }; put_hands_action_intent(runtime_store, &intent)?; - - let mut review = hands_action_review_for_intent( - format!("hands-review-{suffix}"), - &intent, - "approved".to_string(), - vec![ - "patch".to_string(), - "command".to_string(), - "commit".to_string(), - ], - vec![ - "Coordinator selected continueImplementation for the current thread state." - .to_string(), - "Hands remains the action owner; Mind and Soul still own state admission and verification." - .to_string(), - ], - requested_at, - ); - review.required_receipts = vec![ - HANDS_PATCH_RECEIPT_TYPE.to_string(), - HANDS_COMMAND_RECEIPT_TYPE.to_string(), - HANDS_COMMIT_RECEIPT_TYPE.to_string(), - ]; - put_hands_action_review(runtime_store, &review)?; let authority = RepoFrontierHandsAuthority { - schema_version: REPO_FRONTIER_HANDS_AUTHORITY_SCHEMA_VERSION.to_string(), authority_id: format!("repo-frontier-hands-authority-{}", intent.intent_id), route_id: route.route_id.clone(), - model_projection_digest: route.model_projection_digest.clone(), - model_source_documents: route.model_source_documents.clone(), - frontier_item_id: route.frontier_item_id.clone(), - frontier_item_hash: route.frontier_item_hash.clone(), hands_intent_id: intent.intent_id.clone(), - hands_review_id: review.review_id.clone(), substrate_grant_receipt_id: grant_id.clone(), - requested_paths: requested_paths.clone(), - granted_at: review.reviewed_at.clone(), - contract: REPO_FRONTIER_HANDS_AUTHORITY_CONTRACT.to_string(), }; put_repo_frontier_hands_authority(runtime_store, &authority)?; @@ -1679,15 +1450,14 @@ fn record_hands_implementation_gate( "runtimeJobId": runtime_job_id, "substrateGateGrantReceiptId": grant_id, "intentId": intent.intent_id, - "reviewId": review.review_id, "routeId": route.route_id, "modelProjectionDigest": route.model_projection_digest, "modelSourceDocuments": route.model_source_documents, "frontierItemId": route.frontier_item_id, "requestedPaths": requested_paths, - "requiredReceipts": review.required_receipts, - "plannedAction": route.adopted_plan.as_ref().map(|plan| plan.effective_action()), - "plannedCommand": route.adopted_plan.as_ref().map(|plan| plan.effective_command()), + "requiredReceipts": [HANDS_PATCH_RECEIPT_TYPE, HANDS_COMMAND_RECEIPT_TYPE, HANDS_COMMIT_RECEIPT_TYPE], + "plannedAction": route.adopted_plan.as_ref().map(|plan| plan.action.as_str()), + "plannedCommand": route.adopted_plan.as_ref().map(|plan| plan.command.as_str()), "originalPlannedAction": route.adopted_plan.as_ref().map(|plan| plan.action.as_str()), "originalPlannedCommand": route.adopted_plan.as_ref().map(|plan| plan.command.as_str()), "plannedChecks": route.adopted_plan.as_ref().map(|plan| plan.checks.as_slice()), @@ -1709,16 +1479,10 @@ fn accept_reorient(runtime_store: &Path) -> Result { .attempt .job_id .ok_or_else(|| anyhow!("reorientation review lost its runtime job"))?; - let snapshot = epiphany_core::runtime_job_snapshot(runtime_store, &job_id)? - .ok_or_else(|| anyhow!("reorientation review lost its runtime job"))?; - let accepted = match snapshot.job.status { - epiphany_core::EpiphanyRuntimeJobStatus::Completed => { - epiphany_core::accept_reorientation_result(runtime_store, &job_id, &now())? - } - epiphany_core::EpiphanyRuntimeJobStatus::Failed => { - epiphany_core::record_reorientation_pass_failure(runtime_store, &job_id)? - } - _ => return Err(anyhow!("reorientation work is not terminally reviewable")), + let accepted = if work.terminal_failure { + epiphany_core::record_reorientation_pass_failure(runtime_store, &job_id)? + } else { + epiphany_core::accept_reorientation_result(runtime_store, &job_id, &now())? }; Ok(json!({ "mindCommitReceipt": accepted, @@ -1726,6 +1490,72 @@ fn accept_reorient(runtime_store: &Path) -> Result { })) } +#[derive(Clone, Copy)] +enum FamilyReviewKind { + FrontierResearch, + FrontierVerification, + ProposalModeling, + BodyModeling, + FrontierVerdictModeling, +} + +struct FamilyReviewTarget<'a> { + kind: FamilyReviewKind, + job_id: &'a str, +} + +fn family_review_target<'a>( + role_id: &str, + work: &'a epiphany_core::EpiphanyCurrentWorkProjection, +) -> Option> { + match role_id { + "research" + if work.research.stage + == epiphany_core::RepoFrontierResearchLifecycleStage::ResultReady => + { + Some(FamilyReviewTarget { + kind: FamilyReviewKind::FrontierResearch, + job_id: work.research.worker_job_id.as_deref()?, + }) + } + "verification" => Some(FamilyReviewTarget { + kind: FamilyReviewKind::FrontierVerification, + job_id: work.verification.as_ref()?.attempt.review_job_id()?, + }), + "modeling" => { + if let Some(job_id) = work + .proposal_modeling + .as_ref() + .and_then(|candidate| candidate.attempt.review_job_id()) + { + return Some(FamilyReviewTarget { + kind: FamilyReviewKind::ProposalModeling, + job_id, + }); + } + if let Some(job_id) = work + .body_modeling + .as_ref() + .and_then(|candidate| candidate.attempt.review_job_id()) + { + return Some(FamilyReviewTarget { + kind: FamilyReviewKind::BodyModeling, + job_id, + }); + } + Some(FamilyReviewTarget { + kind: FamilyReviewKind::FrontierVerdictModeling, + job_id: work + .frontier_verdict_modeling + .as_ref()? + .attempt + .review_job_id()?, + }) + } + _ => None, + } +} + fn role_id_for_coordinator_action(action: &str) -> Option<&'static str> { match action { "launchResearch" | "reviewResearchResult" => Some("research"), @@ -1805,7 +1635,7 @@ fn launch_worker_runtime_detached( model: &str, provider_credential_path: Option<&Path>, runtime_store: &Path, - codex_home: &Path, + model_connector_endpoint: SocketAddr, mcp_config: &Path, cwd: &Path, resident_store: Option<&Path>, @@ -1814,7 +1644,6 @@ fn launch_worker_runtime_detached( step_index: usize, artifact_dir: &Path, max_runtime_seconds: u64, - auto_tools: bool, ) -> Result { let activation_token = Uuid::new_v4().to_string(); let activation_token_sha256 = format!("{:x}", Sha256::digest(activation_token.as_bytes())); @@ -1835,8 +1664,8 @@ fn launch_worker_runtime_detached( .arg(model) .arg("--store") .arg(runtime_store) - .arg("--codex-home") - .arg(codex_home) + .arg("--connector-endpoint") + .arg(model_connector_endpoint.to_string()) .arg("--mcp-config") .arg(mcp_config) .arg("--job-id") @@ -1848,18 +1677,15 @@ fn launch_worker_runtime_detached( if let Some(path) = provider_credential_path { command.arg("--provider-credential").arg(path); } - if auto_tools { - command - .arg("--auto-tools") - .arg("--tool-adapter-bin") - .arg(tool_adapter_bin) - .arg("--cwd") - .arg(cwd) - .arg("--max-tool-rounds") - .arg(WORKER_AUTO_TOOL_MAX_ROUNDS.to_string()); - if let Some(resident_store) = resident_store { - command.arg("--resident-store").arg(resident_store); - } + command + .arg("--tool-adapter-bin") + .arg(tool_adapter_bin) + .arg("--cwd") + .arg(cwd) + .arg("--max-tool-rounds") + .arg(WORKER_AUTO_TOOL_MAX_ROUNDS.to_string()); + if let Some(resident_store) = resident_store { + command.arg("--resident-store").arg(resident_store); } let stdout_file = fs::File::create(&stdout_path) .with_context(|| format!("failed to create {}", stdout_path.display()))?; @@ -1935,7 +1761,7 @@ fn read_reorient_result(runtime_store: &Path, _thread_id: &str) -> Result "note": "The keyed reorientation request is ready to launch." })); }; - let snapshot = epiphany_core::read_runtime_reorient_result(Some(runtime_store), &job_id); + let snapshot = epiphany_core::read_runtime_reorient_result(runtime_store, &job_id)?; let mut value = json!({ "source": "native", "status": snapshot.status, @@ -2124,7 +1950,10 @@ fn append_jsonl(path: &Path, value: &Value) -> Result<()> { } fn append_operator_step_jsonl(path: &Path, step: &Value) -> Result<()> { - append_jsonl(path, &status_projection::sanitize_for_operator(step.clone())) + append_jsonl( + path, + &status_projection::sanitize_for_operator(step.clone()), + ) } fn take_string(args: &mut impl Iterator, name: &str) -> Result { @@ -2136,13 +1965,6 @@ fn take_path(args: &mut impl Iterator, name: &str) -> Result PathBuf { - env::var_os("USERPROFILE") - .or_else(|| env::var_os("HOME")) - .map(PathBuf::from) - .unwrap_or_else(|| PathBuf::from(".")) -} - #[cfg(test)] mod tests { use super::*; @@ -2165,7 +1987,7 @@ mod tests { } #[test] - fn coordinator_error_after_opening_terminalizes_the_exact_session() -> Result<()> { + fn coordinator_error_preserves_basis_and_writes_terminal_receipt() -> Result<()> { let smoke_root = env::current_dir()?.join(".epiphany-smoke"); fs::create_dir_all(&smoke_root)?; let temp = tempfile::tempdir_in(smoke_root)?; @@ -2182,7 +2004,7 @@ mod tests { thread_id: Some("error-terminalization".to_string()), objective: Some("Exercise the coordinator error boundary.".to_string()), cwd: cwd.clone(), - codex_home: temp.path().join("codex-home"), + model_connector_endpoint: "127.0.0.1:17891".parse().unwrap(), mcp_config: cwd.join(".epiphany/mcp.toml"), artifact_dir: cwd.join(".epiphany-smoke/error-terminalization"), runtime_store: runtime_store.clone(), @@ -2196,7 +2018,6 @@ mod tests { auto_review: false, supersede_failed_results: false, approve_manual_regather: false, - auto_tools: false, proposal_modeling_request_id: None, imagination_consideration_request_id: Some("conflicting-request".to_string()), admitted_model_direction_consideration_request_id: None, @@ -2215,63 +2036,25 @@ mod tests { ); let mut cache = epiphany_core::runtime_spine_cache(&runtime_store)?; cache.pull_all_backing_stores()?; - let session = cache.get_required::( - "coordinator-error-terminalization", - )?; - assert_eq!( - session.status, - epiphany_core::EpiphanyRuntimeSessionStatus::Completed + assert!( + cache + .get::( + "coordinator-error-terminalization", + )? + .is_some(), + "terminal receipt must retain the exact immutable coordinator basis" ); let receipts = cache.get_all::()?; assert_eq!(receipts.len(), 1); assert_eq!(receipts[0].status, "failed"); - assert_eq!(receipts[0].session_id, session.session_id); - Ok(()) - } - - #[test] - fn operator_objective_intake_creates_once_and_refuses_replacement() -> Result<()> { - let temp = tempfile::tempdir()?; - let store = temp.path().join("runtime.cc"); - epiphany_core::initialize_runtime_spine( - &store, - epiphany_core::RuntimeSpineInitOptions { - runtime_id: "objective-intake-bin".into(), - display_name: "Objective intake bin".into(), - created_at: "2026-08-14T00:00:00Z".into(), - }, - )?; - - let first = intake_operator_objective( - &store, - "thread-1", - "Map the machine", - "cli://epiphany-mvp-coordinator", - )?; - assert!(first.changed); - assert_eq!(first.mind.objective.as_deref(), Some("Map the machine")); - - let repeated = intake_operator_objective( - &store, - "thread-1", - " Map the machine ", - "cli://epiphany-mvp-coordinator", - )?; - assert!(!repeated.changed); - assert_eq!( - repeated.mind.projection_digest, - first.mind.projection_digest + assert_eq!(receipts[0].session_id, "coordinator-error-terminalization"); + assert!( + cache + .get::( + "coordinator-error-terminalization", + )? + .is_some() ); - assert_eq!(repeated.commit_receipt, first.commit_receipt); - - let error = intake_operator_objective( - &store, - "thread-1", - "Replace the machine", - "cli://epiphany-mvp-coordinator", - ) - .expect_err("objective replacement must require a typed adoption flow"); - assert!(error.to_string().contains("refusing to replace")); Ok(()) } @@ -2295,9 +2078,15 @@ mod tests { "resident-self-grant://grant-1", )?; assert!(applied.changed); - assert_eq!(applied.intake.source_ref, "resident-self-grant://grant-1"); + let mut cache = epiphany_core::runtime_spine_cache(&store)?; + cache.pull_all_backing_stores()?; + let intakes = cache.get_all::()?; + assert_eq!(intakes.len(), 1); + assert_eq!(intakes[0].source_ref, "resident-self-grant://grant-1"); assert_eq!( - applied.mind.objective.as_deref(), + epiphany_core::assemble_mind_view(&store)? + .objective + .as_deref(), Some("Keep the objective in Mind") ); assert!( diff --git a/epiphany-core/src/bin/epiphany-repository-body.rs b/epiphany-core/src/bin/epiphany-repository-body.rs index 9b6bd8a3..e940ab75 100644 --- a/epiphany-core/src/bin/epiphany-repository-body.rs +++ b/epiphany-core/src/bin/epiphany-repository-body.rs @@ -2,10 +2,9 @@ use anyhow::{Result, bail}; use chrono::Utc; use epiphany_core::{ EpiphanyMemoryAnchor, EpiphanyMemoryDomain, EpiphanyMemoryLifecycle, EpiphanyMemoryNode, - EpiphanyMemoryNodeKind, EpiphanyRepoModelSeed, - EpiphanyRepoModelSeedDocuments, ObserveOutcome, RuntimeSpineInitOptions, - admit_repository_body_observation, bind_repository_body, bind_runtime_to_swarm, - initialize_keyed_repo_model, initialize_runtime_spine, + EpiphanyMemoryNodeKind, EpiphanyRepoModelSeed, EpiphanyRepoModelSeedDocuments, ObserveOutcome, + RuntimeSpineInitOptions, admit_repository_body_observation, bind_repository_body, + bind_runtime_to_swarm, initialize_keyed_repo_model, initialize_runtime_spine, load_current_runtime_repository_body_basis, load_repository_body_status, observe_repository_body, }; diff --git a/epiphany-core/src/bin/epiphany-swarm.rs b/epiphany-core/src/bin/epiphany-swarm.rs index a1e1fa2c..31831b41 100644 --- a/epiphany-core/src/bin/epiphany-swarm.rs +++ b/epiphany-core/src/bin/epiphany-swarm.rs @@ -10,19 +10,17 @@ use epiphany_core::{ derive_resident_cognition_readiness, enqueue_resident_self_pressure_idempotent, import_bifrost_persona_feedback_deliveries, ingest_resident_self_current_work_pressure, issue_resident_self_grant, live_resident_self_typed_request_ids, - load_epiphany_cultmesh_swarm_brake, - load_resident_self_state, materialize_resident_self_domain_obligations, - observe_process_instance, prepare_resident_self_launch, - publish_resident_provider_readiness, reap_exited_child_process, + load_epiphany_cultmesh_swarm_brake, load_resident_self_state, + materialize_resident_self_domain_obligations, observe_process_instance, + prepare_resident_self_launch, publish_resident_provider_readiness, reap_exited_child_process, recover_dead_runtime_worker_attempts, resident_cognitive_runtime_id, resident_prepared_launch_thread_id, resident_self_child_claim, resident_self_local_provider_status, retain_completed_runtime_sessions, - retain_failed_runtime_worker_attempts, - retain_fulfilled_runtime_worker_attempts, retain_resident_self_lifecycles, - runtime_worker_process_claims, settle_resident_self_exited_coordinator, - settle_resident_self_receipt_free_dead_coordinator, terminate_process_instance, - validate_persona_feedback_store_separation, validate_resident_self_coordinator_receipt_binding, - validate_resident_self_store_separation, + retain_failed_runtime_worker_attempts, retain_fulfilled_runtime_worker_attempts, + retain_resident_self_lifecycles, runtime_worker_process_claims, + settle_resident_self_exited_coordinator, settle_resident_self_receipt_free_dead_coordinator, + terminate_process_instance, validate_persona_feedback_store_separation, + validate_resident_self_coordinator_receipt_binding, validate_resident_self_store_separation, }; use serde_json::json; use std::collections::BTreeMap; @@ -39,6 +37,9 @@ use std::time::Duration; const RESIDENT_MAINTENANCE_INTERVAL: Duration = Duration::from_secs(60); fn main() -> Result<()> { + if env::args().nth(1).as_deref() == Some("initialize-and-release-brake") { + return initialize_and_release_brake(); + } let mut args = Args::parse()?; authenticate_resident_self_policy(&mut args.policy)?; args.policy.validate()?; @@ -119,6 +120,46 @@ fn main() -> Result<()> { Ok(()) } +fn initialize_and_release_brake() -> Result<()> { + let mut values = BTreeMap::new(); + let mut args = env::args().skip(2); + while let Some(flag) = args.next() { + let value = args + .next() + .ok_or_else(|| anyhow!("missing value for {flag}"))?; + values.insert(flag, value); + } + let required = |name: &str| { + values + .get(name) + .filter(|value| !value.trim().is_empty()) + .cloned() + .ok_or_else(|| anyhow!("missing {name}")) + }; + let store = PathBuf::from(required("--local-verse-store")?); + let runtime_id = required("--runtime-id")?; + let actor_id = required("--actor-id")?; + let reason = required("--reason")?; + let now = Utc::now().to_rfc3339(); + epiphany_core::engage_epiphany_cultmesh_swarm_brake( + &store, + &runtime_id, + "Fresh runtime begins braked before explicit release.", + &actor_id, + &now, + false, + )?; + let brake = epiphany_core::release_epiphany_cultmesh_swarm_brake( + &store, + &runtime_id, + reason, + actor_id, + now, + )?; + println!("{}", serde_json::to_string_pretty(&brake)?); + Ok(()) +} + fn require_resident_git(executable: &str) -> Result<()> { let output = Command::new(executable) .arg("--version") @@ -135,11 +176,7 @@ fn require_resident_git(executable: &str) -> Result<()> { fn maintain_resident_runtime(args: &Args, state: &ResidentSelfState) -> Result<()> { retain_runtime_receipts(args, state)?; - retain_resident_self_lifecycles( - &args.state_store, - args.retained_closed_lifecycles, - Utc::now().timestamp_millis().max(0) as u64, - )?; + retain_resident_self_lifecycles(&args.state_store, args.retained_closed_lifecycles)?; retain_runtime_worker_attempts(args)?; Ok(()) } @@ -268,7 +305,6 @@ fn cycle( bind_runtime_repository_domain( &args.policy.runtime_store, &args.feedback_target_repository, - &Utc::now().to_rfc3339(), )?; materialize_resident_self_domain_obligations( &args.policy.runtime_store, @@ -502,7 +538,11 @@ impl Args { runtime_store: path("--runtime-store")?, local_verse_store: path("--local-verse-store")?, artifact_root: path("--artifact-root")?, - codex_home: path("--codex-home")?, + model_connector_endpoint: value + .get("--connector-endpoint") + .ok_or_else(|| anyhow!("missing --connector-endpoint"))? + .parse() + .context("invalid --connector-endpoint")?, mcp_config: path("--mcp-config")?, model_provider: value .get("--model-provider") @@ -783,7 +823,7 @@ mod brake_tests { runtime_store, local_verse_store: verse_store, artifact_root: shared.join("artifacts"), - codex_home: shared.join("codex-home"), + model_connector_endpoint: "127.0.0.1:17891".parse().unwrap(), mcp_config: shared.join("mcp.toml"), model_provider: "test".into(), model: "test-model".into(), @@ -810,5 +850,4 @@ mod brake_tests { )?); Ok(()) } - } diff --git a/epiphany-core/src/causal_work_identity.rs b/epiphany-core/src/causal_work_identity.rs index bc3b2f7c..a66843f0 100644 --- a/epiphany-core/src/causal_work_identity.rs +++ b/epiphany-core/src/causal_work_identity.rs @@ -9,7 +9,7 @@ fn digest_id(prefix: &str, causal_parts: &[&str]) -> String { format!("{prefix}-{:x}", digest.finalize()) } -pub fn proposal_modeling_request_id( +pub(crate) fn proposal_modeling_request_id( runtime_id: &str, proposal_id: &str, payload_sha256: &str, @@ -20,7 +20,7 @@ pub fn proposal_modeling_request_id( ) } -pub fn frontier_research_request_id( +pub(crate) fn frontier_research_request_id( runtime_id: &str, frontier_item_id: &str, frontier_item_hash: &str, @@ -31,7 +31,7 @@ pub fn frontier_research_request_id( ) } -pub fn frontier_planning_request_id( +pub(crate) fn frontier_planning_request_id( runtime_id: &str, frontier_item_id: &str, frontier_item_hash: &str, @@ -42,7 +42,7 @@ pub fn frontier_planning_request_id( ) } -pub fn frontier_plan_mind_request_id( +pub(crate) fn frontier_plan_mind_request_id( runtime_id: &str, planning_request_id: &str, imagination_result_id: &str, @@ -59,7 +59,7 @@ pub fn frontier_plan_mind_request_id( ) } -pub fn frontier_verdict_modeling_request_id( +pub(crate) fn frontier_verdict_modeling_request_id( runtime_id: &str, soul_verdict_receipt_id: &str, verification_result_id: &str, @@ -76,7 +76,7 @@ pub fn frontier_verdict_modeling_request_id( ) } -pub fn admitted_model_direction_request_id( +pub(crate) fn admitted_model_direction_request_id( runtime_id: &str, model_projection_digest: &str, previous_terminal_result_id: Option<&str>, diff --git a/epiphany-core/src/continuity_gateway.rs b/epiphany-core/src/continuity_gateway.rs deleted file mode 100644 index 871bc55e..00000000 --- a/epiphany-core/src/continuity_gateway.rs +++ /dev/null @@ -1,63 +0,0 @@ -use crate::EpiphanyReorientFindingInterpretation; -use cultcache_rs::DatabaseEntry; - -pub const CONTINUITY_RECOVERY_RECEIPT_TYPE: &str = "epiphany.continuity.recovery_receipt"; -pub const CONTINUITY_RECOVERY_RECEIPT_SCHEMA_VERSION: &str = - "epiphany.continuity.recovery_receipt.v0"; - -#[derive(Debug, Clone, PartialEq, Eq, DatabaseEntry)] -#[cultcache( - type = "epiphany.continuity.recovery_receipt", - schema = "ContinuityRecoveryReceipt" -)] -pub struct ContinuityRecoveryReceipt { - #[cultcache(key = 0)] - pub schema_version: String, - #[cultcache(key = 1)] - pub receipt_id: String, - #[cultcache(key = 2)] - pub source_result_id: String, - #[cultcache(key = 3)] - pub source_job_id: String, - #[cultcache(key = 4)] - pub binding_id: String, - #[cultcache(key = 5)] - pub mode: String, - #[cultcache(key = 6)] - pub checkpoint_still_valid: String, - #[cultcache(key = 7)] - pub summary: String, - #[cultcache(key = 8)] - pub next_safe_move: String, - #[cultcache(key = 9)] - pub files_inspected: Vec, - #[cultcache(key = 10)] - pub emitted_at: String, - #[cultcache(key = 11)] - pub contract: String, -} - -pub fn continuity_recovery_receipt_from_reorient_finding( - receipt_id: String, - binding_id: String, - finding: &EpiphanyReorientFindingInterpretation, - emitted_at: String, -) -> ContinuityRecoveryReceipt { - ContinuityRecoveryReceipt { - schema_version: CONTINUITY_RECOVERY_RECEIPT_SCHEMA_VERSION.to_string(), - receipt_id, - source_result_id: finding.runtime_result_id.clone().unwrap_or_default(), - source_job_id: finding.runtime_job_id.clone().unwrap_or_default(), - binding_id, - mode: finding.mode.clone().unwrap_or_else(|| "unknown".to_string()), - checkpoint_still_valid: finding - .checkpoint_still_valid - .map(|value| value.to_string()) - .unwrap_or_else(|| "unknown".to_string()), - summary: finding.summary.clone().unwrap_or_default(), - next_safe_move: finding.next_safe_move.clone().unwrap_or_default(), - files_inspected: finding.files_inspected.clone(), - emitted_at, - contract: "Continuity recovery emitted from a reviewed reorientation finding; it proves what survived rupture before Mind admits recovery state.".to_string(), - } -} diff --git a/epiphany-core/src/coordinator_objective_intake.rs b/epiphany-core/src/coordinator_objective_intake.rs index 7464ff52..4d8bab2c 100644 --- a/epiphany-core/src/coordinator_objective_intake.rs +++ b/epiphany-core/src/coordinator_objective_intake.rs @@ -1,17 +1,13 @@ use crate::{ EpiphanyMindCommitAuthority, EpiphanyMindCommitOutcome, EpiphanyMindCommitReceipt, - EpiphanyMindDocumentVersion, EpiphanyMindObjectiveDocument, EpiphanyMindView, - MIND_OBJECTIVE_KEY, assemble_mind_view, commit_operator_mind_mutation, runtime_spine_cache, + EpiphanyMindDocumentVersion, EpiphanyMindObjectiveDocument, MIND_OBJECTIVE_KEY, + assemble_mind_view, commit_operator_mind_mutation, runtime_spine_cache, }; use anyhow::{Result, anyhow}; use cultcache_rs::DatabaseEntry; use sha2::{Digest, Sha256}; use std::path::Path; -pub const USER_OBJECTIVE_INTAKE_SCHEMA_VERSION: &str = "gamecult.epiphany.user_objective_intake.v0"; -pub const USER_OBJECTIVE_INTAKE_TYPE: &str = "epiphany.coordinator.user_objective_intake"; -pub const USER_OBJECTIVE_INTAKE_CONTRACT: &str = "The human supplies the initial objective. Self records that assertion and the canonical keyed Mind objective in one CAS. Thread identity is provenance only. Repeated identical intake is read-idempotent; replacement requires a separate reviewed adoption flow."; - #[derive(Debug, Clone)] pub struct UserObjectiveIntakeInput { pub thread_id: String, @@ -28,29 +24,20 @@ pub struct UserObjectiveIntakeInput { )] pub struct UserObjectiveIntake { #[cultcache(key = 0)] - pub schema_version: String, - #[cultcache(key = 1)] - pub intake_id: String, - #[cultcache(key = 2)] pub thread_id: String, - #[cultcache(key = 3)] + #[cultcache(key = 1)] pub objective: String, - #[cultcache(key = 4)] - pub objective_sha256: String, - #[cultcache(key = 5)] + #[cultcache(key = 2)] pub source_actor: String, - #[cultcache(key = 6)] + #[cultcache(key = 3)] pub source_ref: String, - #[cultcache(key = 7)] + #[cultcache(key = 4)] pub submitted_at: String, - #[cultcache(key = 8)] - pub contract: String, } #[derive(Debug, Clone)] pub struct UserObjectiveIntakeApplied { - pub intake: UserObjectiveIntake, - pub mind: EpiphanyMindView, + pub mind_projection_digest: String, pub commit_receipt: EpiphanyMindCommitReceipt, pub changed: bool, } @@ -71,7 +58,6 @@ pub fn intake_user_objective( { return Err(anyhow!("invalid typed user-objective intake")); } - let objective_sha256 = format!("{:x}", Sha256::digest(objective.as_bytes())); let intake_id = format!("user-objective-{:x}", Sha256::digest(thread_id.as_bytes())); let mut cache = runtime_spine_cache(store)?; @@ -82,7 +68,6 @@ pub fn intake_user_objective( .ok_or_else(|| anyhow!("typed user-objective intake lost its Mind objective"))?; if existing.thread_id != thread_id || existing.objective != objective - || existing.objective_sha256 != objective_sha256 || existing.source_actor != source_actor || existing.source_ref != source_ref || objective_document.objective.trim() != objective @@ -113,8 +98,7 @@ pub fn intake_user_objective( }) .ok_or_else(|| anyhow!("typed user-objective intake lost its Mind commit receipt"))?; return Ok(UserObjectiveIntakeApplied { - intake: existing, - mind: assemble_mind_view(store)?, + mind_projection_digest: assemble_mind_view(store)?.projection_digest, commit_receipt: receipt, changed: false, }); @@ -128,15 +112,11 @@ pub fn intake_user_objective( )); } let intake = UserObjectiveIntake { - schema_version: USER_OBJECTIVE_INTAKE_SCHEMA_VERSION.into(), - intake_id: intake_id.clone(), thread_id: thread_id.to_string(), objective: objective.to_string(), - objective_sha256, source_actor: source_actor.to_string(), source_ref: source_ref.to_string(), submitted_at: input.submitted_at, - contract: USER_OBJECTIVE_INTAKE_CONTRACT.into(), }; let provenance = cache.prepare_entry(&intake_id, &intake)?.0; let objective_write = crate::mind_documents::prepare_mind_document( @@ -160,8 +140,7 @@ pub fn intake_user_objective( } }; Ok(UserObjectiveIntakeApplied { - intake, - mind: assemble_mind_view(store)?, + mind_projection_digest: assemble_mind_view(store)?.projection_digest, commit_receipt, changed: true, }) @@ -196,7 +175,10 @@ mod tests { )?; let first = intake_user_objective(&store, input("Map the machine"))?; assert!(first.changed); - assert_eq!(first.mind.objective.as_deref(), Some("Map the machine")); + assert_eq!( + assemble_mind_view(&store)?.objective.as_deref(), + Some("Map the machine") + ); assert_eq!( first.commit_receipt.invariant_owner, "Self.user_objective_intake" @@ -209,11 +191,16 @@ mod tests { .iter() .all(|envelope| envelope.r#type != "epiphany.thread_state") ); + let after_first = std::fs::read(&store)?; let repeated = intake_user_objective(&store, input(" Map the machine "))?; assert!(!repeated.changed); - assert_eq!(repeated.intake, first.intake); + assert_eq!( + repeated.mind_projection_digest, + first.mind_projection_digest + ); assert_eq!(repeated.commit_receipt, first.commit_receipt); + assert_eq!(std::fs::read(&store)?, after_first); let before = std::fs::read(&store)?; let error = intake_user_objective(&store, input("Replace the machine")) diff --git a/epiphany-core/src/coordinator_results.rs b/epiphany-core/src/coordinator_results.rs index 1d35fadc..2b047782 100644 --- a/epiphany-core/src/coordinator_results.rs +++ b/epiphany-core/src/coordinator_results.rs @@ -1,95 +1,68 @@ use crate::*; +use anyhow::{Result, anyhow}; +use serde::{Deserialize, Serialize}; use std::path::Path; +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub enum EpiphanyReorientResultStatus { + Pending, + Completed, + Failed, +} + #[derive(Debug, Clone)] pub struct EpiphanyCoordinatorReorientResultSnapshot { - pub status: EpiphanyCrrcResultStatus, + pub status: EpiphanyReorientResultStatus, pub finding: Option, pub note: String, } - pub fn read_runtime_reorient_result( - runtime_store_path: Option<&Path>, + runtime_store_path: &Path, job_id: &str, -) -> EpiphanyCoordinatorReorientResultSnapshot { - let Some(runtime_store_path) = runtime_store_path else { - return reorient_snapshot( - EpiphanyCrrcResultStatus::Pending, - None, - Some( - "Resident Self owns this reorientation worker; no loaded runtime-spine store is available yet.", - ), - ); - }; - let snapshot = match runtime_job_snapshot(runtime_store_path, job_id) { - Ok(Some(snapshot)) => snapshot, - Ok(None) => { - return reorient_snapshot( - EpiphanyCrrcResultStatus::Pending, +) -> Result { + let mut cache = runtime_spine_cache(runtime_store_path)?; + cache.pull_all_backing_stores()?; + let job = match cache.get::(job_id)? { + Some(job) => job, + None => { + return Ok(reorient_snapshot( + EpiphanyReorientResultStatus::Pending, None, Some(&format!( "Resident runtime job {job_id:?} has not reported typed state yet." )), - ); - } - Err(error) => { - return reorient_snapshot( - EpiphanyCrrcResultStatus::BackendUnavailable, - None, - Some(&format!( - "Failed to read resident runtime-spine job {job_id:?}: {error}" - )), - ); + )); } }; - let status = reorient_result_status(&snapshot); + let status = reorient_result_status(&job.status); let finding = match status { - EpiphanyCrrcResultStatus::Completed => { - match runtime_reorient_worker_result(runtime_store_path, job_id) { - Ok(Some(result)) => Some(interpret_runtime_reorient_worker_result(&result)), - Ok(None) => { - return reorient_snapshot( - EpiphanyCrrcResultStatus::BackendUnavailable, - None, - Some(&format!( - "Resident runtime job {job_id:?} completed without an EpiphanyRuntimeReorientWorkerResult typed document; generic lifecycle receipts are not reviewable findings." - )), - ); - } - Err(error) => { - return reorient_snapshot( - EpiphanyCrrcResultStatus::BackendUnavailable, - None, - Some(&format!( - "Failed to read typed reorientation worker result for resident runtime job {job_id:?}: {error}" - )), - ); - } - } - } - EpiphanyCrrcResultStatus::Failed | EpiphanyCrrcResultStatus::Cancelled => snapshot - .result + EpiphanyReorientResultStatus::Completed => Some(interpret_runtime_reorient_worker_result( + &cache + .get::(job_id)? + .ok_or_else(|| { + anyhow!( + "resident runtime job {job_id:?} completed without its typed reorientation result" + ) + })?, + )), + EpiphanyReorientResultStatus::Failed => cache + .get::(job_id)? .as_ref() - .map(interpret_reorient_lifecycle_failure), - _ => None, + .map(interpret_runtime_reorient_worker_result), + EpiphanyReorientResultStatus::Pending => None, }; - reorient_snapshot(status, finding, None) + Ok(reorient_snapshot(status, finding, None)) } fn reorient_snapshot( - status: EpiphanyCrrcResultStatus, + status: EpiphanyReorientResultStatus, finding: Option, note_override: Option<&str>, ) -> EpiphanyCoordinatorReorientResultSnapshot { - let note = note_override.map(str::to_string).unwrap_or_else(|| { - render_reorient_result_note( - status, - finding.as_ref(), - finding - .as_ref() - .and_then(|finding| finding.job_error.as_deref()), - ) - }); + let note = note_override + .map(str::to_string) + .unwrap_or_else(|| render_reorient_result_note(status, finding.as_ref())); EpiphanyCoordinatorReorientResultSnapshot { status, finding, @@ -97,80 +70,31 @@ fn reorient_snapshot( } } -fn interpret_reorient_lifecycle_failure( - result: &EpiphanyRuntimeJobResult, -) -> EpiphanyReorientFindingInterpretation { - EpiphanyReorientFindingInterpretation { - mode: None, - summary: Some(result.summary.clone()), - next_safe_move: nonempty(&result.next_safe_move), - checkpoint_still_valid: None, - files_inspected: Vec::new(), - frontier_node_ids: Vec::new(), - evidence_ids: result.evidence_refs.clone(), - artifact_refs: result.artifact_refs.clone(), - runtime_result_id: Some(result.result_id.clone()), - runtime_job_id: Some(result.job_id.clone()), - job_error: Some(result.summary.clone()), - item_error: None, - } -} - -fn reorient_result_status(snapshot: &EpiphanyRuntimeJobSnapshot) -> EpiphanyCrrcResultStatus { - match snapshot.job.status { - EpiphanyRuntimeJobStatus::Queued => EpiphanyCrrcResultStatus::Pending, - EpiphanyRuntimeJobStatus::Running | EpiphanyRuntimeJobStatus::WaitingForReview => { - EpiphanyCrrcResultStatus::Running - } - EpiphanyRuntimeJobStatus::Completed if snapshot.result.is_some() => { - EpiphanyCrrcResultStatus::Completed - } - EpiphanyRuntimeJobStatus::Completed => EpiphanyCrrcResultStatus::Pending, - EpiphanyRuntimeJobStatus::Failed => EpiphanyCrrcResultStatus::Failed, - EpiphanyRuntimeJobStatus::Cancelled => EpiphanyCrrcResultStatus::Cancelled, +fn reorient_result_status(status: &EpiphanyRuntimeJobStatus) -> EpiphanyReorientResultStatus { + match status { + EpiphanyRuntimeJobStatus::Queued => EpiphanyReorientResultStatus::Pending, + EpiphanyRuntimeJobStatus::Completed => EpiphanyReorientResultStatus::Completed, + EpiphanyRuntimeJobStatus::Failed => EpiphanyReorientResultStatus::Failed, } } fn render_reorient_result_note( - status: EpiphanyCrrcResultStatus, + status: EpiphanyReorientResultStatus, finding: Option<&EpiphanyReorientFindingInterpretation>, - item_error: Option<&str>, ) -> String { match status { - EpiphanyCrrcResultStatus::Completed => finding.map_or_else( - || "Reorientation worker completed, but no structured result was recorded.".to_string(), - |finding| { - format!( - "Reorientation worker completed. Next safe move: {}", - finding.next_safe_move.as_deref().unwrap_or("not supplied") - ) - }, + EpiphanyReorientResultStatus::Completed => format!( + "Reorientation worker completed. Next safe move: {}", + finding + .and_then(|finding| finding.next_safe_move.as_deref()) + .unwrap_or("not supplied") ), - EpiphanyCrrcResultStatus::Failed => item_error + EpiphanyReorientResultStatus::Failed => finding + .and_then(|finding| finding.job_error.as_deref()) .map(|error| format!("Reorientation worker failed: {error}")) .unwrap_or_else(|| "Reorientation worker failed.".to_string()), - EpiphanyCrrcResultStatus::Cancelled => { - "Reorientation worker was cancelled before producing a result.".to_string() - } - EpiphanyCrrcResultStatus::Running => "Reorientation worker is still running.".to_string(), - EpiphanyCrrcResultStatus::Pending => { + EpiphanyReorientResultStatus::Pending => { "Reorientation worker has not produced a result yet.".to_string() } - EpiphanyCrrcResultStatus::MissingState => { - "No authoritative Epiphany state exists for this thread.".to_string() - } - EpiphanyCrrcResultStatus::MissingBinding => { - "No matching Epiphany reorientation worker binding exists.".to_string() - } - EpiphanyCrrcResultStatus::BackendUnavailable => { - "The bound runtime backend is unavailable.".to_string() - } - EpiphanyCrrcResultStatus::BackendMissing => { - "The bound runtime backend job or item is missing.".to_string() - } } } - -fn nonempty(value: &str) -> Option { - (!value.trim().is_empty()).then(|| value.to_string()) -} diff --git a/epiphany-core/src/coordinator_status.rs b/epiphany-core/src/coordinator_status.rs index 22473be3..a4faf243 100644 --- a/epiphany-core/src/coordinator_status.rs +++ b/epiphany-core/src/coordinator_status.rs @@ -1,9 +1,9 @@ -use anyhow::{Context, Result}; use crate::{ - EpiphanyCoordinatorInput, EpiphanyCrrcAction, EpiphanyCurrentWorkProjection, - EpiphanyRoleBoardInput, RepoFrontierPlanningLifecycle, RepoFrontierPlanningLifecycleStage, + EpiphanyCoordinatorInput, EpiphanyCurrentWorkProjection, EpiphanyRoleBoardInput, + RepoFrontierPlanningLifecycle, RepoFrontierPlanningLifecycleStage, RepoFrontierResearchLifecycle, RepoFrontierResearchLifecycleStage, derive_role_board, }; +use anyhow::{Context, Result}; use serde_json::{Value, json}; use std::{ env, @@ -23,38 +23,25 @@ const MAX_OPERATOR_TEXT_CHARS: usize = 1200; pub fn native_coordinator_json(runtime_store: &Path, thread_id: &str) -> Result { let store_path = absolute_path(runtime_store)?; - let mind = if store_path.exists() { - Some( - crate::assemble_mind_view(&store_path).with_context(|| { - format!("failed to assemble keyed Mind {}", store_path.display()) - })?, - ) - } else { - None - }; - let current_work = if mind.is_some() { - crate::project_current_work(&store_path) - .context("failed to derive current work from keyed Mind and runtime receipts")? - } else { - empty_current_work() - }; - let latest_reorientation_decision = mind + let projected_work = store_path + .exists() + .then(|| { + crate::project_current_work(&store_path) + .context("failed to derive current work from keyed Mind and runtime receipts") + }) + .transpose()?; + let mind_present = projected_work.is_some(); + let projection_digest = projected_work .as_ref() - .and_then(|mind| mind.reorientation_decisions.last()); - let crrc_action = - if latest_reorientation_decision.is_some_and(|decision| decision.mode == "regather") { - EpiphanyCrrcAction::RegatherManually - } else { - EpiphanyCrrcAction::Continue - }; + .map(|work| work.mind_projection_digest.clone()); + let current_work = projected_work.unwrap_or_else(empty_current_work); let roles = derive_role_board(EpiphanyRoleBoardInput { - mind_present: mind.is_some(), + mind_present, current_work: current_work.clone(), }); let coordinator = crate::recommend_coordinator_action(EpiphanyCoordinatorInput { - mind_present: mind.is_some(), - crrc_action, + mind_present, current_work: current_work.clone(), }); Ok(sanitize_for_operator(json!({ @@ -62,8 +49,8 @@ pub fn native_coordinator_json(runtime_store: &Path, thread_id: &str) -> Result< "read": { "source": "native", "mindStore": store_path, - "mindPresent": mind.is_some(), - "projectionDigest": mind.as_ref().map(|mind| mind.projection_digest.as_str()), + "mindPresent": mind_present, + "projectionDigest": projection_digest, }, "roles": {"threadId": thread_id, "source": "native", "roles": roles}, "currentWork": current_work, @@ -74,6 +61,7 @@ pub fn native_coordinator_json(runtime_store: &Path, thread_id: &str) -> Result< fn empty_current_work() -> EpiphanyCurrentWorkProjection { EpiphanyCurrentWorkProjection { mind_projection_digest: String::new(), + operator_regather_required: false, body_modeling: None, research: RepoFrontierResearchLifecycle { stage: RepoFrontierResearchLifecycleStage::Terminal, diff --git a/epiphany-core/src/cultmesh_integration.rs b/epiphany-core/src/cultmesh_integration.rs index a91efbd6..18254033 100644 --- a/epiphany-core/src/cultmesh_integration.rs +++ b/epiphany-core/src/cultmesh_integration.rs @@ -5,8 +5,7 @@ use cultmesh_rs::{CultMesh, CultMeshNode, CultMeshNodeOptions, cultmesh_document use std::path::Path; pub const EPIPHANY_CULTMESH_SWARM_BRAKE_TYPE: &str = "epiphany.cultmesh.swarm_brake"; -pub const EPIPHANY_CULTMESH_SWARM_BRAKE_SCHEMA_VERSION: &str = - "epiphany.cultmesh.swarm_brake.v0"; +pub const EPIPHANY_CULTMESH_SWARM_BRAKE_SCHEMA_VERSION: &str = "epiphany.cultmesh.swarm_brake.v0"; pub const EPIPHANY_CULTMESH_SWARM_BRAKE_KEY: &str = "epiphany-local/swarm-brake"; pub const EPIPHANY_CANONICAL_SWARM_BRAKE_ID: &str = "epiphany/swarm-brake"; pub const EPIPHANY_CANONICAL_SWARM_BRAKE_OWNER: &str = "epiphany.swarm-brake"; @@ -66,18 +65,6 @@ pub fn open_epiphany_cultmesh_node( ) } -pub fn canonical_epiphany_swarm_brake_protected_surfaces() -> Vec { - vec![ - "resident.self".to_string(), - "coordinator.run".to_string(), - "persona.public_speech".to_string(), - "hands.consequence".to_string(), - "atlas.publish".to_string(), - "atlas.project".to_string(), - "atlas.impact_ingress".to_string(), - ] -} - pub fn default_epiphany_cultmesh_swarm_brake( generated_at_utc: impl Into, ) -> EpiphanyCultMeshSwarmBrakeEntry { @@ -90,7 +77,15 @@ pub fn default_epiphany_cultmesh_swarm_brake( .to_string(), operator_agent_id: EPIPHANY_CANONICAL_SWARM_BRAKE_OWNER.to_string(), affected_clusters: Vec::new(), - protected_surfaces: canonical_epiphany_swarm_brake_protected_surfaces(), + protected_surfaces: vec![ + "resident.self".into(), + "coordinator.run".into(), + "persona.public_speech".into(), + "hands.consequence".into(), + "atlas.publish".into(), + "atlas.project".into(), + "atlas.impact_ingress".into(), + ], created_at_utc: generated_at_utc.into(), expires_at_utc: None, private_state_exposed: false, @@ -113,7 +108,9 @@ pub fn engage_epiphany_cultmesh_swarm_brake( return Err(anyhow!("swarm brake engagement requires an actor identity")); } if allow_engaged_adoption && actor_id != "Idunn" { - return Err(anyhow!("only Idunn may adopt an already-engaged legacy brake")); + return Err(anyhow!( + "only Idunn may adopt an already-engaged legacy brake" + )); } if let Some(current) = load_epiphany_cultmesh_swarm_brake(&store_path, runtime_id.clone())? { let foreign = current.brake_id != EPIPHANY_CANONICAL_SWARM_BRAKE_ID @@ -200,7 +197,9 @@ fn validate_swarm_brake(brake: &EpiphanyCultMeshSwarmBrakeEntry) -> Result<()> { || brake.runtime_id.trim().is_empty() || brake.created_at_utc.trim().is_empty() { - return Err(anyhow!("swarm brake identity, scope, runtime, and timestamp are required")); + return Err(anyhow!( + "swarm brake identity, scope, runtime, and timestamp are required" + )); } if !matches!(brake.status.as_str(), "released" | "engaged") { return Err(anyhow!("swarm brake status must be released or engaged")); @@ -210,7 +209,9 @@ fn validate_swarm_brake(brake: &EpiphanyCultMeshSwarmBrakeEntry) -> Result<()> { || brake.operator_agent_id.trim().is_empty() || (brake.affected_clusters.is_empty() && brake.protected_surfaces.is_empty())) { - return Err(anyhow!("engaged swarm brake requires operator id, reason, and scope")); + return Err(anyhow!( + "engaged swarm brake requires operator id, reason, and scope" + )); } Ok(()) } @@ -233,7 +234,12 @@ mod tests { )?; assert_eq!(engaged.status, "engaged"); assert_eq!(engaged.brake_id, EPIPHANY_CANONICAL_SWARM_BRAKE_ID); - assert!(engaged.protected_surfaces.iter().any(|value| value == "hands.consequence")); + assert!( + engaged + .protected_surfaces + .iter() + .any(|value| value == "hands.consequence") + ); assert_eq!( load_epiphany_cultmesh_swarm_brake(&store, "runtime")?, Some(engaged) diff --git a/epiphany-core/src/current_work.rs b/epiphany-core/src/current_work.rs index 156ef266..53be6c1a 100644 --- a/epiphany-core/src/current_work.rs +++ b/epiphany-core/src/current_work.rs @@ -11,11 +11,11 @@ use crate::{ #[cfg(test)] use crate::RepoFrontierResearchContinuationAction; +#[cfg(test)] +use cultcache_rs::{CacheBackingStore, SingleFileMessagePackBackingStore}; pub const BODY_MODELING_DECISION_RECEIPT_SCHEMA_VERSION: &str = "epiphany.mind.body_modeling_decision.v1"; -pub const BODY_MODELING_LAUNCH_BINDING_SCHEMA_VERSION: &str = - "epiphany.runtime.body_modeling_launch.v1"; pub const AGENT_PASS_ADMISSION_REFUSAL_SCHEMA_VERSION: &str = "epiphany.mind.agent_pass_admission_refusal.v1"; @@ -50,6 +50,7 @@ pub enum EpiphanyAgentPassAdmissionRefusalKind { #[serde(rename_all = "camelCase")] pub struct EpiphanyCurrentWorkProjection { pub mind_projection_digest: String, + pub operator_regather_required: bool, pub body_modeling: Option, pub research: crate::RepoFrontierResearchLifecycle, pub frontier_planning: crate::RepoFrontierPlanningLifecycle, @@ -87,6 +88,14 @@ pub struct EpiphanyAgentPassAttemptProjection { pub job_id: Option, } +impl EpiphanyAgentPassAttemptProjection { + pub fn review_job_id(&self) -> Option<&str> { + (self.action == EpiphanyAgentPassContinuationAction::Review) + .then_some(self.job_id.as_deref()) + .flatten() + } +} + #[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)] #[cultcache( type = "epiphany.mind.agent_pass_admission_refusal.v1", @@ -163,10 +172,7 @@ impl EpiphanyAgentPassAttemptProjection { fn from_job(job: &crate::EpiphanyRuntimeJob, completed_is_reviewable: bool) -> Self { let action = match job.status { - crate::EpiphanyRuntimeJobStatus::Failed - | crate::EpiphanyRuntimeJobStatus::Cancelled => { - EpiphanyAgentPassContinuationAction::Launch - } + crate::EpiphanyRuntimeJobStatus::Failed => EpiphanyAgentPassContinuationAction::Launch, crate::EpiphanyRuntimeJobStatus::Completed if completed_is_reviewable => { EpiphanyAgentPassContinuationAction::Review } @@ -233,36 +239,6 @@ pub struct EpiphanyBodyModelingCurrentWorkProjection { pub attempt: EpiphanyAgentPassAttemptProjection, } -#[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)] -#[cultcache( - type = "epiphany.runtime.body_modeling_launch.v1", - schema = "EpiphanyBodyModelingLaunchBinding" -)] -pub struct EpiphanyBodyModelingLaunchBinding { - #[cultcache(key = 0)] - pub schema_version: String, - #[cultcache(key = 1)] - pub binding_record_id: String, - #[cultcache(key = 2)] - pub work_id: String, - #[cultcache(key = 3)] - pub job_id: String, - #[cultcache(key = 4)] - pub attempt_ordinal: u64, - #[cultcache(key = 5)] - pub body_document_sha256: String, - #[cultcache(key = 6)] - pub observed_repo_model_projection_digest: String, - #[cultcache(key = 7)] - pub launched_at: String, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct EpiphanyBodyModelingLaunchOptions { - pub job_id: String, - pub created_at: String, -} - #[derive(Clone, Debug, PartialEq, Eq)] pub struct EpiphanyProposalModelingLaunchOptions { pub created_at: String, @@ -373,23 +349,97 @@ impl EpiphanyBodyModelingDecisionReceipt { pub(crate) fn register_current_work_types(cache: &mut CultCache) -> Result<()> { cache.register_entry_type::()?; - cache.register_entry_type::()?; cache.register_entry_type::()?; Ok(()) } +fn role_launch_document( + launch: &crate::EpiphanyRuntimeWorkerLaunchRequest, +) -> Result { + match launch.launch_document()? { + crate::EpiphanyWorkerLaunchDocument::Role(document) => Ok(document), + crate::EpiphanyWorkerLaunchDocument::Reorient(_) => { + Err(anyhow!("typed role work carried reorientation authority")) + } + } +} + +fn launch_matches_typed_request( + launch: &crate::EpiphanyRuntimeWorkerLaunchRequest, + request: crate::RuntimeTypedRequestRef<'_>, +) -> Result { + request.matches_launch(launch) +} + +fn typed_request_launches<'a>( + launches: &'a [crate::EpiphanyRuntimeWorkerLaunchRequest], + request: crate::RuntimeTypedRequestRef<'_>, +) -> Result> { + launches + .iter() + .map(|launch| Ok(launch_matches_typed_request(launch, request)?.then_some(launch))) + .filter_map(Result::transpose) + .collect() +} + +fn launch_matches_frontier_planning_request( + launch: &crate::EpiphanyRuntimeWorkerLaunchRequest, + request_id: &str, +) -> Result { + Ok(role_launch_document(launch)? + .frontier_planning_context + .is_some_and(|context| context.request_id == request_id)) +} + +fn launch_matches_frontier_plan_mind_request( + launch: &crate::EpiphanyRuntimeWorkerLaunchRequest, + request_id: &str, +) -> Result { + Ok(role_launch_document(launch)? + .frontier_plan_mind_context + .is_some_and(|context| context.request.request_id == request_id)) +} + +fn frontier_planning_launches<'a>( + launches: &'a [crate::EpiphanyRuntimeWorkerLaunchRequest], + request_id: &str, +) -> Result> { + launches + .iter() + .map(|launch| { + Ok(launch_matches_frontier_planning_request(launch, request_id)?.then_some(launch)) + }) + .filter_map(Result::transpose) + .collect() +} + +fn frontier_plan_mind_launches<'a>( + launches: &'a [crate::EpiphanyRuntimeWorkerLaunchRequest], + request_id: &str, +) -> Result> { + launches + .iter() + .map(|launch| { + Ok(launch_matches_frontier_plan_mind_request(launch, request_id)?.then_some(launch)) + }) + .filter_map(Result::transpose) + .collect() +} + pub(crate) fn body_modeling_decision_envelope( store_path: &Path, result: &crate::EpiphanyRuntimeRoleWorkerResult, disposition: &str, decided_at: &str, ) -> Result { - let body_basis = result - .repository_body_observation_basis - .clone() - .ok_or_else(|| anyhow!("Body Modeling result has no Body observation basis"))?; let mut cache = crate::runtime_spine_cache(store_path)?; cache.pull_all_backing_stores()?; + let launch = cache + .get::(&result.job_id)? + .ok_or_else(|| anyhow!("Body Modeling result lost its immutable launch"))?; + let body_basis = launch + .repository_body_observation_basis()? + .ok_or_else(|| anyhow!("Body Modeling result has no Body observation basis"))?; let context = cache .get::(&result.decision_context_id)? .ok_or_else(|| anyhow!("Body Modeling result has no decision context"))?; @@ -437,35 +487,11 @@ pub(crate) fn body_modeling_decision_envelope( Ok(cache.prepare_entry(&receipt.work_id, &receipt)?.0) } -pub fn current_body_modeling_work( - store_path: impl AsRef, -) -> Result { - let body_basis = crate::current_mind_repository_body_observation(store_path.as_ref())? - .ok_or_else(|| anyhow!("Mind has no admitted repository Body observation"))?; - let repo_model_basis = crate::assemble_repo_model_view(store_path.as_ref())?.reasoning_basis(); - EpiphanyBodyModelingWorkProjection::derive( - body_basis.runtime_id.clone(), - body_basis, - repo_model_basis, - ) -} - -pub fn unresolved_body_modeling_work( - store_path: impl AsRef, -) -> Result> { - let store_path = store_path.as_ref(); - let work = current_body_modeling_work(store_path)?; - let mut cache = crate::runtime_spine_cache(store_path)?; - cache.pull_all_backing_stores()?; - let decision = cache.get::(&work.work_id)?; - resolve_body_modeling_work(work, decision) -} - pub fn project_current_work(store_path: impl AsRef) -> Result { let store_path = store_path.as_ref(); - let mind = crate::assemble_mind_view(store_path)?; let mut cache = crate::runtime_spine_cache(store_path)?; cache.pull_all_backing_stores()?; + let mind = crate::mind_documents::assemble_mind_view_from_cache(&cache)?; let body_modeling = match ( mind.repository_body_observation.clone(), mind.repo_model.as_ref(), @@ -482,7 +508,7 @@ pub fn project_current_work(store_path: impl AsRef) -> Result Some(EpiphanyBodyModelingCurrentWorkProjection { - attempt: body_modeling_attempt(&cache, &work.work_id)?, + attempt: body_modeling_attempt(&cache, &work)?, work, }), None => None, @@ -504,18 +530,23 @@ pub fn project_current_work(store_path: impl AsRef) -> Result Result { +pub(crate) fn consideration_attempt_ordinal(request_id: &str, job_id: &str) -> Result { let prefix = format!("consideration-{request_id}-attempt-"); job_id .strip_prefix(&prefix) @@ -547,7 +578,7 @@ fn current_imagination_consideration_work( let model_basis = crate::repo_model_documents::assemble_repo_model_view_from_cache(cache)?.reasoning_basis(); let candidates = cache.get_all::()?; - let bindings = cache.get_all::()?; + let launches = cache.get_all::()?; let mut requests = cache.get_all::()?; requests.sort_by(|left, right| left.request_id.cmp(&right.request_id)); let mut work = Vec::new(); @@ -561,22 +592,31 @@ fn current_imagination_consideration_work( continue; } crate::validate_current_imagination_consideration_request(cache, &request)?; - let mut launches = bindings - .iter() - .filter(|binding| binding.request_id == request.request_id) - .map(|binding| { - Ok(( - consideration_attempt_ordinal(&request.request_id, &binding.job_id)?, - binding.job_id.clone(), - )) - }) - .collect::>>()?; - launches.sort_by_key(|(ordinal, _)| *ordinal); - let launches = launches + let mut job_ids = typed_request_launches( + &launches, + crate::RuntimeTypedRequestRef::ImaginationConsideration(&request.request_id), + )? + .into_iter() + .map(|launch| { + Ok(( + consideration_attempt_ordinal(&request.request_id, &launch.job_id)?, + launch.job_id.clone(), + )) + }) + .collect::>>()?; + job_ids.sort_by_key(|(ordinal, _)| *ordinal); + for (expected, (ordinal, _)) in job_ids.iter().enumerate() { + if *ordinal != expected { + return Err(anyhow!( + "Persona feedback consideration has noncontiguous attempt identity" + )); + } + } + let job_ids = job_ids .into_iter() .map(|(_, job_id)| job_id) .collect::>(); - let attempt = consideration_attempt(cache, &launches)?; + let attempt = consideration_attempt(cache, &job_ids)?; work.push(EpiphanyImaginationConsiderationWorkProjection { request, attempt }); } Ok(work) @@ -601,21 +641,18 @@ fn current_admitted_model_direction_consideration_work( continue; } crate::validate_current_admitted_model_direction_consideration_request(cache, &request)?; - let mut job_ids = launches - .iter() - .filter(|launch| { - launch - .admitted_model_direction_consideration_request_id - .as_deref() - == Some(request.request_id.as_str()) - }) - .map(|launch| { - Ok(( - consideration_attempt_ordinal(&request.request_id, &launch.job_id)?, - launch.job_id.clone(), - )) - }) - .collect::>>()?; + let mut job_ids = typed_request_launches( + &launches, + crate::RuntimeTypedRequestRef::AdmittedModelDirection(&request.request_id), + )? + .into_iter() + .map(|launch| { + Ok(( + consideration_attempt_ordinal(&request.request_id, &launch.job_id)?, + launch.job_id.clone(), + )) + }) + .collect::>>()?; job_ids.sort_by_key(|(ordinal, _)| *ordinal); let job_ids = job_ids .into_iter() @@ -648,19 +685,18 @@ fn current_verification_work( let admission_refusals = cache.get_all::()?; for request in requests { crate::runtime_spine::validate_repo_frontier_verification_request_intrinsic(&request)?; - let mut request_launches = launches - .iter() - .filter(|launch| { - launch.repo_frontier_verification_request_id.as_deref() - == Some(request.request_id.as_str()) - }) - .map(|launch| { - Ok(( - verification_attempt_ordinal(&request.request_id, &launch.job_id)?, - launch, - )) - }) - .collect::>>()?; + let mut request_launches = typed_request_launches( + &launches, + crate::RuntimeTypedRequestRef::FrontierVerification(&request.request_id), + )? + .into_iter() + .map(|launch| { + Ok(( + verification_attempt_ordinal(&request.request_id, &launch.job_id)?, + launch, + )) + }) + .collect::>>()?; request_launches.sort_by_key(|(ordinal, _)| *ordinal); if let Some((_, launch)) = request_launches.last() && let Some(result) = @@ -687,14 +723,6 @@ fn current_verification_work( continue; }; let result = cache.get::(&launch.job_id)?; - if let Some(result) = result.as_ref() - && (result.verification_request_id.as_deref() != Some(request.request_id.as_str()) - || result.frontier_route_id.as_deref() != Some(request.route_id.as_str())) - { - return Err(anyhow!( - "frontier Verification result crossed request authority" - )); - } let job = cache .get::(&launch.job_id)? .ok_or_else(|| anyhow!("frontier Verification launch lost its runtime job"))?; @@ -746,19 +774,18 @@ fn current_frontier_verdict_modeling_work( let receipts = cache.get_all::()?; let admission_refusals = cache.get_all::()?; for request in requests { - let mut request_launches = launches - .iter() - .filter(|launch| { - launch.repo_frontier_modeling_request_id.as_deref() - == Some(request.request_id.as_str()) - }) - .map(|launch| { - Ok(( - frontier_verdict_attempt_ordinal(&request.request_id, &launch.job_id)?, - launch, - )) - }) - .collect::>>()?; + let mut request_launches = typed_request_launches( + &launches, + crate::RuntimeTypedRequestRef::FrontierVerdictModeling(&request.request_id), + )? + .into_iter() + .map(|launch| { + Ok(( + frontier_verdict_attempt_ordinal(&request.request_id, &launch.job_id)?, + launch, + )) + }) + .collect::>>()?; request_launches.sort_by_key(|(ordinal, _)| *ordinal); if let Some((_, launch)) = request_launches.last() && let Some(result) = @@ -783,14 +810,6 @@ fn current_frontier_verdict_modeling_work( })); }; let result = cache.get::(&launch.job_id)?; - if let Some(result) = result.as_ref() - && result.repo_frontier_modeling_request_id.as_deref() - != Some(request.request_id.as_str()) - { - return Err(anyhow!( - "frontier verdict Modeling result crossed request authority" - )); - } let job = cache .get::(&launch.job_id)? .ok_or_else(|| anyhow!("frontier verdict Modeling launch lost its runtime job"))?; @@ -819,30 +838,77 @@ fn current_frontier_verdict_modeling_work( Ok(None) } +fn body_modeling_attempt_ordinal(work_id: &str, job_id: &str) -> Result { + let prefix = format!("{work_id}-attempt-"); + job_id + .strip_prefix(&prefix) + .ok_or_else(|| anyhow!("Body Modeling job identity is not canonical"))? + .parse::() + .map_err(|_| anyhow!("Body Modeling attempt ordinal is invalid")) +} + +fn body_modeling_launches( + cache: &CultCache, + work: &EpiphanyBodyModelingWorkProjection, +) -> Result> { + let prefix = format!("{}-attempt-", work.work_id); + let mut launches = cache + .get_all::()? + .into_iter() + .filter(|launch| launch.job_id.starts_with(&prefix)) + .map(|launch| { + let ordinal = body_modeling_attempt_ordinal(&work.work_id, &launch.job_id)?; + let document = launch.launch_document()?; + let role = match &document { + crate::EpiphanyWorkerLaunchDocument::Role(role) => role, + crate::EpiphanyWorkerLaunchDocument::Reorient(_) => { + return Err(anyhow!("Body Modeling launch used reorientation authority")); + } + }; + if launch.role != crate::EPIPHANY_MODELING_OWNER_ROLE + || launch.binding_id != crate::EPIPHANY_MODELING_ROLE_BINDING_ID + || document.thread_id() != launch.job_id + || role.role_id != "modeling" + || role.repository_body_observation_basis.as_ref() != Some(&work.body_basis) + || role.family_context_count() != 0 + { + return Err(anyhow!( + "Body Modeling immutable launch crossed family authority" + )); + } + Ok((ordinal, launch)) + }) + .collect::>>()?; + launches.sort_by_key(|(ordinal, _)| *ordinal); + for (expected, (ordinal, _)) in launches.iter().enumerate() { + if *ordinal != expected { + return Err(anyhow!( + "Body Modeling request has noncontiguous attempt identity" + )); + } + } + Ok(launches) +} + fn body_modeling_attempt( cache: &CultCache, - work_id: &str, + work: &EpiphanyBodyModelingWorkProjection, ) -> Result { - let mut bindings = cache - .get_all::()? - .into_iter() - .filter(|binding| binding.work_id == work_id) - .collect::>(); - bindings.sort_by_key(|binding| binding.attempt_ordinal); - let Some(binding) = bindings.last() else { + let launches = body_modeling_launches(cache, work)?; + let Some((_, launch)) = launches.last() else { return Ok(EpiphanyAgentPassAttemptProjection::unattempted()); }; let job = cache - .get::(&binding.job_id)? - .ok_or_else(|| anyhow!("Body Modeling launch binding lost its runtime job"))?; - let result = cache.get::(&binding.job_id)?; + .get::(&launch.job_id)? + .ok_or_else(|| anyhow!("Body Modeling launch lost its runtime job"))?; + let result = cache.get::(&launch.job_id)?; let reviewable = result.is_some(); let admission_refusals = cache.get_all::()?; let admission_refused = result.as_ref().is_some_and(|result| { admission_refusals.iter().any(|refusal| { refusal.pass_family == EpiphanyAgentPassFamily::BodyModeling - && refusal.request_id == work_id - && refusal.job_id == binding.job_id + && refusal.request_id == work.work_id + && refusal.job_id == launch.job_id && refusal.result_id == result.result_id && refusal.decision_context_id == result.decision_context_id }) @@ -850,7 +916,7 @@ fn body_modeling_attempt( if admission_refused { return Ok(EpiphanyAgentPassAttemptProjection::with( EpiphanyAgentPassContinuationAction::Launch, - Some(binding.job_id.clone()), + Some(launch.job_id.clone()), )); } Ok(EpiphanyAgentPassAttemptProjection::from_job( @@ -872,50 +938,45 @@ fn current_proposal_modeling_work( ) -> Result> { let mut requests = cache.get_all::()?; requests.sort_by(|left, right| left.request_id.cmp(&right.request_id)); - let bindings = cache.get_all::()?; + let launches = cache.get_all::()?; let receipts = cache.get_all::()?; let admission_refusals = cache.get_all::()?; 'requests: for request in requests { crate::runtime_spine::validate_repo_frontier_proposal_modeling_request(&request)?; - let mut request_bindings = bindings - .iter() - .filter(|binding| binding.proposal_modeling_request_id == request.request_id) - .map(|binding| { - Ok(( - proposal_modeling_attempt_ordinal(&request.request_id, &binding.job_id)?, - binding, - )) - }) - .collect::>>()?; - request_bindings.sort_by_key(|(ordinal, _)| *ordinal); - for (expected, (ordinal, _)) in request_bindings.iter().enumerate() { + let mut request_launches = typed_request_launches( + &launches, + crate::RuntimeTypedRequestRef::ProposalModeling(&request.request_id), + )? + .into_iter() + .map(|launch| { + Ok(( + proposal_modeling_attempt_ordinal(&request.request_id, &launch.job_id)?, + launch, + )) + }) + .collect::>>()?; + request_launches.sort_by_key(|(ordinal, _)| *ordinal); + for (expected, (ordinal, _)) in request_launches.iter().enumerate() { if *ordinal != expected { return Err(anyhow!( "proposal Modeling request has noncontiguous attempt identity" )); } } - if request_bindings.is_empty() { + if request_launches.is_empty() { return Ok(Some(EpiphanyProposalModelingWorkProjection { request, attempt: EpiphanyAgentPassAttemptProjection::unattempted(), })); } - let latest_ordinal = request_bindings + let latest_ordinal = request_launches .last() - .expect("checked nonempty bindings") + .expect("checked nonempty launches") .0; let mut admitted_ordinal = None; - for (ordinal, binding) in &request_bindings { - let result = cache.get::(&binding.job_id)?; + for (ordinal, launch) in &request_launches { + let result = cache.get::(&launch.job_id)?; if let Some(result) = result.as_ref() { - if result.proposal_modeling_request_id.as_deref() - != Some(request.request_id.as_str()) - { - return Err(anyhow!( - "proposal Modeling launch result crossed request authority" - )); - } if receipts.iter().any(|receipt| { receipt.invariant_owner == "Modeling.proposal_frontier" && matches!( @@ -934,12 +995,12 @@ fn current_proposal_modeling_work( } } let job = cache - .get::(&binding.job_id)? - .ok_or_else(|| anyhow!("proposal Modeling launch binding lost its runtime job"))?; + .get::(&launch.job_id)? + .ok_or_else(|| anyhow!("proposal Modeling launch lost its runtime job"))?; let admission_refused = admission_refusals.iter().any(|refusal| { refusal.pass_family == EpiphanyAgentPassFamily::ProposalModeling && refusal.request_id == request.request_id - && refusal.job_id == binding.job_id + && refusal.job_id == launch.job_id && result.as_ref().is_some_and(|result| { refusal.result_id == result.result_id && refusal.decision_context_id == result.decision_context_id @@ -952,11 +1013,7 @@ fn current_proposal_modeling_work( )?; } if *ordinal != latest_ordinal - && !matches!( - job.status, - crate::EpiphanyRuntimeJobStatus::Failed - | crate::EpiphanyRuntimeJobStatus::Cancelled - ) + && !matches!(job.status, crate::EpiphanyRuntimeJobStatus::Failed) && !admission_refused { return Err(anyhow!( @@ -972,11 +1029,11 @@ fn current_proposal_modeling_work( } continue 'requests; } - let (_, latest) = request_bindings.last().expect("checked nonempty bindings"); + let (_, latest) = request_launches.last().expect("checked nonempty launches"); let result = cache.get::(&latest.job_id)?; let job = cache .get::(&latest.job_id)? - .ok_or_else(|| anyhow!("proposal Modeling launch binding lost its runtime job"))?; + .ok_or_else(|| anyhow!("proposal Modeling launch lost its runtime job"))?; let admission_refused = result.as_ref().is_some_and(|result| { admission_refusals.iter().any(|refusal| { refusal.pass_family == EpiphanyAgentPassFamily::ProposalModeling @@ -1002,99 +1059,22 @@ fn current_proposal_modeling_work( Ok(None) } -pub fn body_modeling_continuation_action_for_job( - store_path: impl AsRef, - job_id: &str, -) -> Result> { - let mut cache = crate::runtime_spine_cache(store_path)?; - cache.pull_all_backing_stores()?; - let bindings = cache - .get_all::()? - .into_iter() - .filter(|binding| binding.job_id == job_id) - .collect::>(); - let [binding] = bindings.as_slice() else { - if bindings.is_empty() { - return Ok(None); - } - return Err(anyhow!( - "runtime job has multiple Body Modeling launch bindings" - )); - }; - if cache - .get::(&binding.work_id)? - .is_some() - { - return Ok(None); - } - Ok(Some( - body_modeling_attempt(&cache, &binding.work_id)?.action, - )) +pub(crate) fn frontier_planning_attempt_ordinal(request_id: &str, job_id: &str) -> Result { + let prefix = format!("frontier-planning-{request_id}-attempt-"); + job_id + .strip_prefix(&prefix) + .ok_or_else(|| anyhow!("frontier Planning job identity is not canonical"))? + .parse::() + .map_err(|_| anyhow!("frontier Planning attempt ordinal is invalid")) } -pub fn current_body_modeling_review_job_id(store_path: impl AsRef) -> Result> { - Ok(crate::project_current_work(store_path)? - .body_modeling - .filter(|work| work.attempt.action == EpiphanyAgentPassContinuationAction::Review) - .and_then(|work| work.attempt.job_id)) -} - -pub fn proposal_modeling_continuation_action_for_job( - store_path: impl AsRef, - job_id: &str, -) -> Result> { - Ok(project_current_work(store_path)? - .proposal_modeling - .filter(|work| work.attempt.job_id.as_deref() == Some(job_id)) - .map(|work| work.attempt.action)) -} - -pub fn current_proposal_modeling_review_job_id( - store_path: impl AsRef, -) -> Result> { - Ok(project_current_work(store_path)? - .proposal_modeling - .filter(|work| work.attempt.action == EpiphanyAgentPassContinuationAction::Review) - .and_then(|work| work.attempt.job_id)) -} - -pub fn frontier_verdict_modeling_continuation_action_for_job( - store_path: impl AsRef, - job_id: &str, -) -> Result> { - Ok(project_current_work(store_path)? - .frontier_verdict_modeling - .filter(|work| work.attempt.job_id.as_deref() == Some(job_id)) - .map(|work| work.attempt.action)) -} - -pub fn current_frontier_verdict_modeling_review_job_id( - store_path: impl AsRef, -) -> Result> { - Ok(project_current_work(store_path)? - .frontier_verdict_modeling - .filter(|work| work.attempt.action == EpiphanyAgentPassContinuationAction::Review) - .and_then(|work| work.attempt.job_id)) -} - -pub fn current_frontier_research_review_job_id( - store_path: impl AsRef, -) -> Result> { - let lifecycle = crate::runtime_repo_frontier_research_lifecycle(store_path)?; - Ok( - (lifecycle.stage == crate::RepoFrontierResearchLifecycleStage::ResultReady) - .then_some(lifecycle.worker_job_id) - .flatten(), - ) -} - -pub fn current_frontier_verification_review_job_id( - store_path: impl AsRef, -) -> Result> { - Ok(project_current_work(store_path)? - .verification - .filter(|work| work.attempt.action == EpiphanyAgentPassContinuationAction::Review) - .and_then(|work| work.attempt.job_id)) +pub(crate) fn frontier_plan_mind_attempt_ordinal(request_id: &str, job_id: &str) -> Result { + let prefix = format!("frontier-plan-mind-{request_id}-attempt-"); + job_id + .strip_prefix(&prefix) + .ok_or_else(|| anyhow!("frontier plan Mind job identity is not canonical"))? + .parse::() + .map_err(|_| anyhow!("frontier plan Mind attempt ordinal is invalid")) } pub fn launch_current_frontier_planning_work( @@ -1104,15 +1084,15 @@ pub fn launch_current_frontier_planning_work( let store_path = store_path.as_ref(); chrono::DateTime::parse_from_rfc3339(created_at) .map_err(|_| anyhow!("frontier Planning launch time is invalid"))?; - let lifecycle = crate::runtime_repo_frontier_planning_lifecycle(store_path)?; + let mut cache = crate::runtime_spine_cache(store_path)?; + cache.pull_all_backing_stores()?; + let lifecycle = crate::runtime_spine::repo_frontier_planning_lifecycle(&cache)?; if lifecycle.stage != RepoFrontierPlanningLifecycleStage::ImaginationLaunchReady { return Err(anyhow!("Mind has no launchable frontier Planning work")); } let request_id = lifecycle .planning_request_id .ok_or_else(|| anyhow!("frontier Planning work lost its request"))?; - let mut cache = crate::runtime_spine_cache(store_path)?; - cache.pull_all_backing_stores()?; let request = cache .get::(&request_id)? .ok_or_else(|| anyhow!("frontier Planning request disappeared"))?; @@ -1120,26 +1100,38 @@ pub fn launch_current_frontier_planning_work( let identity = cache .get::(crate::RUNTIME_IDENTITY_KEY)? .ok_or_else(|| anyhow!("frontier Planning launch requires runtime identity"))?; - let mut prior = cache - .get_all::()? + let launches = cache.get_all::()?; + let mut prior = frontier_planning_launches(&launches, &request.request_id)? .into_iter() - .filter(|binding| binding.planning_request_id == request.request_id) - .collect::>(); - prior.sort_by_key(|binding| binding.attempt_ordinal); - let attempt_ordinal = prior.len() as u64; - let superseded_failure_result_id = if let Some(latest) = prior.last() { + .cloned() + .map(|launch| { + Ok(( + frontier_planning_attempt_ordinal(&request.request_id, &launch.job_id)?, + launch, + )) + }) + .collect::>>()?; + prior.sort_by_key(|(ordinal, _)| *ordinal); + for (expected, (ordinal, _)) in prior.iter().enumerate() { + if *ordinal != expected { + return Err(anyhow!( + "frontier Planning request has noncontiguous attempt identity" + )); + } + } + let attempt_ordinal = prior.len(); + let retry_authority = if let Some((_, latest)) = prior.last() { let result = cache .get::(&latest.job_id)? .ok_or_else(|| anyhow!("frontier Planning retry lost its failure result"))?; - crate::runtime_spine::repo_frontier_planning_failure_review( + let review = crate::runtime_spine::repo_frontier_planning_failure_review( &cache, &request.request_id, "imagination", &result, )? - .map(|review| review.result_id) - .ok_or_else(|| anyhow!("frontier Planning retry is not reviewed"))? - .into() + .ok_or_else(|| anyhow!("frontier Planning retry is not reviewed"))?; + Some((result, review)) } else { None }; @@ -1171,10 +1163,6 @@ pub fn launch_current_frontier_planning_work( &cache, crate::RuntimeSpineHeartbeatJobOptions { runtime_id: identity.runtime_id.clone(), - session_id: crate::EPIPHANY_RUNTIME_ROOT_SESSION_ID.into(), - objective: "Propose one bounded plan for the exact frontier authority".into(), - coordinator_note: "Planning current-work launch transaction opened this session." - .into(), job_id: job_id.clone(), role: crate::EPIPHANY_IMAGINATION_OWNER_ROLE.into(), binding_id: crate::EPIPHANY_IMAGINATION_ROLE_BINDING_ID.into(), @@ -1182,51 +1170,9 @@ pub fn launch_current_frontier_planning_work( instruction: "Act as Epiphany Imagination. Propose one bounded candidate for only the exact typed frontier and return the dedicated planning candidate contract.".into(), launch_document, output_contract_id: output_contract_id.clone(), - proposal_modeling_request_id: None, - frontier_planning_request_id: Some(request.request_id.clone()), - frontier_plan_mind_request_id: None, - imagination_consideration_request_id: None, - admitted_model_direction_consideration_request_id: None, - repo_frontier_modeling_request_id: None, - repo_frontier_research_request_id: None, - repo_frontier_verification_request_id: None, created_at: created_at.into(), }, )?; - let launch = prepared - .iter() - .find(|envelope| { - envelope.r#type == crate::EpiphanyRuntimeWorkerLaunchRequest::TYPE - && envelope.key == job_id - }) - .ok_or_else(|| anyhow!("frontier Planning launch preparation lost its worker"))?; - let worker_launch: crate::EpiphanyRuntimeWorkerLaunchRequest = - rmp_serde::from_slice(&launch.payload)?; - let binding_record_id = if attempt_ordinal == 0 { - format!("repo-frontier-planning-launch-{}", request.request_id) - } else { - format!( - "repo-frontier-planning-launch-{}-attempt-{attempt_ordinal}", - request.request_id - ) - }; - let binding = crate::RepoFrontierPlanningLaunchBinding { - schema_version: crate::REPO_FRONTIER_PLANNING_LAUNCH_BINDING_SCHEMA_VERSION.into(), - binding_record_id, - planning_request_id: request.request_id.clone(), - job_id: job_id.clone(), - binding_id: crate::EPIPHANY_IMAGINATION_ROLE_BINDING_ID.into(), - runtime_id: identity.runtime_id, - thread_id: job_id.clone(), - launched_at: created_at.into(), - worker_launch_document_sha256: format!( - "{:x}", - Sha256::digest(&worker_launch.launch_document_msgpack) - ), - contract: crate::REPO_FRONTIER_PLANNING_LAUNCH_BINDING_CONTRACT.into(), - attempt_ordinal, - superseded_failure_result_id, - }; let snapshot = cache.snapshot_envelopes(); let mut expected = vec![ cache @@ -1249,12 +1195,31 @@ pub fn launch_current_frontier_planning_work( } expected.push(envelope.clone()); } + for (_, launch) in &prior { + expected.push( + cache + .get_envelope::(&launch.job_id)? + .ok_or_else(|| anyhow!("frontier Planning retry lost its prior launch envelope"))?, + ); + } + if let Some((result, review)) = retry_authority { + expected.push( + cache + .get_envelope::(&result.job_id)? + .ok_or_else(|| anyhow!("frontier Planning retry lost its result envelope"))?, + ); + expected.push( + cache + .get_envelope::(&review.review_id)? + .ok_or_else(|| anyhow!("frontier Planning retry lost its review envelope"))?, + ); + } commit_current_work_launch( store_path, &cache, expected, prepared, - vec![cache.prepare_entry(&binding.binding_record_id, &binding)?.0], + Vec::new(), "frontier Planning", )?; Ok(job_id) @@ -1267,7 +1232,9 @@ pub fn launch_current_frontier_plan_mind_work( let store_path = store_path.as_ref(); chrono::DateTime::parse_from_rfc3339(created_at) .map_err(|_| anyhow!("frontier plan Mind launch time is invalid"))?; - let lifecycle = crate::runtime_repo_frontier_planning_lifecycle(store_path)?; + let mut cache = crate::runtime_spine_cache(store_path)?; + cache.pull_all_backing_stores()?; + let lifecycle = crate::runtime_spine::repo_frontier_planning_lifecycle(&cache)?; if lifecycle.stage != RepoFrontierPlanningLifecycleStage::MindLaunchReady { return Err(anyhow!("Mind has no launchable frontier plan review")); } @@ -1277,8 +1244,6 @@ pub fn launch_current_frontier_plan_mind_work( let request_id = lifecycle .mind_request_id .ok_or_else(|| anyhow!("frontier plan Mind launch lost its request"))?; - let mut cache = crate::runtime_spine_cache(store_path)?; - cache.pull_all_backing_stores()?; let request = cache .get::(&request_id)? .ok_or_else(|| anyhow!("frontier plan Mind request disappeared"))?; @@ -1292,26 +1257,38 @@ pub fn launch_current_frontier_plan_mind_work( let identity = cache .get::(crate::RUNTIME_IDENTITY_KEY)? .ok_or_else(|| anyhow!("frontier plan Mind launch requires runtime identity"))?; - let mut prior = cache - .get_all::()? + let launches = cache.get_all::()?; + let mut prior = frontier_plan_mind_launches(&launches, &request.request_id)? .into_iter() - .filter(|binding| binding.mind_request_id == request.request_id) - .collect::>(); - prior.sort_by_key(|binding| binding.attempt_ordinal); - let attempt_ordinal = prior.len() as u64; - let superseded_failure_result_id = if let Some(latest) = prior.last() { + .cloned() + .map(|launch| { + Ok(( + frontier_plan_mind_attempt_ordinal(&request.request_id, &launch.job_id)?, + launch, + )) + }) + .collect::>>()?; + prior.sort_by_key(|(ordinal, _)| *ordinal); + for (expected, (ordinal, _)) in prior.iter().enumerate() { + if *ordinal != expected { + return Err(anyhow!( + "frontier plan Mind request has noncontiguous attempt identity" + )); + } + } + let attempt_ordinal = prior.len(); + let retry_authority = if let Some((_, latest)) = prior.last() { let result = cache .get::(&latest.job_id)? .ok_or_else(|| anyhow!("frontier plan Mind retry lost its failure result"))?; - crate::runtime_spine::repo_frontier_planning_failure_review( + let review = crate::runtime_spine::repo_frontier_planning_failure_review( &cache, &planning.request_id, "mind", &result, )? - .map(|review| review.result_id) - .ok_or_else(|| anyhow!("frontier plan Mind retry is not reviewed"))? - .into() + .ok_or_else(|| anyhow!("frontier plan Mind retry is not reviewed"))?; + Some((result, review)) } else { None }; @@ -1343,10 +1320,6 @@ pub fn launch_current_frontier_plan_mind_work( &cache, crate::RuntimeSpineHeartbeatJobOptions { runtime_id: identity.runtime_id.clone(), - session_id: crate::EPIPHANY_RUNTIME_ROOT_SESSION_ID.into(), - objective: "Adjudicate one exact Imagination plan candidate".into(), - coordinator_note: "Plan Mind current-work launch transaction opened this session." - .into(), job_id: job_id.clone(), role: crate::EPIPHANY_MIND_OWNER_ROLE.into(), binding_id: crate::EPIPHANY_MIND_ROLE_BINDING_ID.into(), @@ -1354,51 +1327,9 @@ pub fn launch_current_frontier_plan_mind_work( instruction: "Act as Epiphany Mind. Adopt, refuse, or hold only the exact typed candidate and return the dedicated plan decision contract.".into(), launch_document, output_contract_id: output_contract_id.clone(), - proposal_modeling_request_id: None, - frontier_planning_request_id: None, - frontier_plan_mind_request_id: Some(request.request_id.clone()), - imagination_consideration_request_id: None, - admitted_model_direction_consideration_request_id: None, - repo_frontier_modeling_request_id: None, - repo_frontier_research_request_id: None, - repo_frontier_verification_request_id: None, created_at: created_at.into(), }, )?; - let worker_envelope = prepared - .iter() - .find(|envelope| { - envelope.r#type == crate::EpiphanyRuntimeWorkerLaunchRequest::TYPE - && envelope.key == job_id - }) - .ok_or_else(|| anyhow!("frontier plan Mind preparation lost its worker"))?; - let worker_launch: crate::EpiphanyRuntimeWorkerLaunchRequest = - rmp_serde::from_slice(&worker_envelope.payload)?; - let binding_record_id = if attempt_ordinal == 0 { - format!("repo-frontier-plan-mind-launch-{}", request.request_id) - } else { - format!( - "repo-frontier-plan-mind-launch-{}-attempt-{attempt_ordinal}", - request.request_id - ) - }; - let binding = crate::RepoFrontierPlanMindLaunchBinding { - schema_version: crate::REPO_FRONTIER_PLAN_MIND_LAUNCH_BINDING_SCHEMA_VERSION.into(), - binding_record_id, - mind_request_id: request.request_id.clone(), - job_id: job_id.clone(), - binding_id: crate::EPIPHANY_MIND_ROLE_BINDING_ID.into(), - runtime_id: identity.runtime_id, - thread_id: job_id.clone(), - launched_at: created_at.into(), - worker_launch_document_sha256: format!( - "{:x}", - Sha256::digest(&worker_launch.launch_document_msgpack) - ), - contract: crate::REPO_FRONTIER_PLAN_MIND_LAUNCH_BINDING_CONTRACT.into(), - attempt_ordinal, - superseded_failure_result_id, - }; let snapshot = cache.snapshot_envelopes(); let mut expected = Vec::new(); for (document_type, document_key) in [ @@ -1439,12 +1370,33 @@ pub fn launch_current_frontier_plan_mind_work( } expected.push(envelope.clone()); } + for (_, launch) in &prior { + expected.push( + cache + .get_envelope::(&launch.job_id)? + .ok_or_else(|| { + anyhow!("frontier plan Mind retry lost its prior launch envelope") + })?, + ); + } + if let Some((result, review)) = retry_authority { + expected.push( + cache + .get_envelope::(&result.job_id)? + .ok_or_else(|| anyhow!("frontier plan Mind retry lost its result envelope"))?, + ); + expected.push( + cache + .get_envelope::(&review.review_id)? + .ok_or_else(|| anyhow!("frontier plan Mind retry lost its review envelope"))?, + ); + } commit_current_work_launch( store_path, &cache, expected, prepared, - vec![cache.prepare_entry(&binding.binding_record_id, &binding)?.0], + Vec::new(), "frontier plan Mind", )?; Ok(job_id) @@ -1467,14 +1419,12 @@ pub fn launch_current_frontier_verification_work( let identity = cache .get::(crate::RUNTIME_IDENTITY_KEY)? .ok_or_else(|| anyhow!("frontier Verification launch requires runtime identity"))?; - let attempt_ordinal = cache - .get_all::()? - .into_iter() - .filter(|launch| { - launch.repo_frontier_verification_request_id.as_deref() - == Some(request.request_id.as_str()) - }) - .count() + let launches = cache.get_all::()?; + let attempt_ordinal = typed_request_launches( + &launches, + crate::RuntimeTypedRequestRef::FrontierVerification(&request.request_id), + )? + .len() + cache .get_all::()? .into_iter() @@ -1509,10 +1459,6 @@ pub fn launch_current_frontier_verification_work( &cache, crate::RuntimeSpineHeartbeatJobOptions { runtime_id: identity.runtime_id, - session_id: crate::EPIPHANY_RUNTIME_ROOT_SESSION_ID.into(), - objective: "Audit one exact Hands consequence against its routed invariant".into(), - coordinator_note: - "Verification current-work launch transaction opened this session.".into(), job_id: job_id.clone(), role: crate::EPIPHANY_VERIFICATION_OWNER_ROLE.into(), binding_id: crate::EPIPHANY_VERIFICATION_ROLE_BINDING_ID.into(), @@ -1520,14 +1466,6 @@ pub fn launch_current_frontier_verification_work( instruction: "Act as Epiphany Verification. Audit only the exact typed Hands consequence and route carried by this request; return a structured verdict, evidence ids, and risks.".into(), launch_document, output_contract_id: output_contract_id.clone(), - proposal_modeling_request_id: None, - frontier_planning_request_id: None, - frontier_plan_mind_request_id: None, - imagination_consideration_request_id: None, - admitted_model_direction_consideration_request_id: None, - repo_frontier_modeling_request_id: None, - repo_frontier_research_request_id: None, - repo_frontier_verification_request_id: Some(request.request_id.clone()), created_at: created_at.to_string(), }, )?; @@ -1556,10 +1494,6 @@ pub fn launch_current_frontier_verification_work( crate::HandsActionIntent::TYPE, request.hands_intent_id.as_str(), ), - ( - crate::HandsActionReview::TYPE, - request.hands_review_id.as_str(), - ), ( crate::HandsPatchReceipt::TYPE, request.hands_patch_receipt_id.as_str(), @@ -1613,7 +1547,9 @@ pub fn launch_current_frontier_research_work( let store_path = store_path.as_ref(); chrono::DateTime::parse_from_rfc3339(created_at) .map_err(|_| anyhow!("frontier Research launch time is invalid"))?; - if crate::runtime_repo_frontier_research_lifecycle(store_path)?.stage + let mut current = crate::runtime_spine_cache(store_path)?; + current.pull_all_backing_stores()?; + if crate::runtime_spine::repo_frontier_research_lifecycle(¤t)?.stage != crate::RepoFrontierResearchLifecycleStage::LaunchReady { return Err(anyhow!("Mind has no launchable frontier Research work")); @@ -1627,13 +1563,12 @@ pub fn launch_current_frontier_research_work( let identity = cache .get::(crate::RUNTIME_IDENTITY_KEY)? .ok_or_else(|| anyhow!("frontier Research launch requires runtime identity"))?; - let attempt_ordinal = cache - .get_all::()? - .into_iter() - .filter(|launch| { - launch.repo_frontier_research_request_id.as_deref() == Some(request.request_id.as_str()) - }) - .count() + let launches = cache.get_all::()?; + let attempt_ordinal = typed_request_launches( + &launches, + crate::RuntimeTypedRequestRef::FrontierResearch(&request.request_id), + )? + .len() + cache .get_all::()? .into_iter() @@ -1668,10 +1603,6 @@ pub fn launch_current_frontier_research_work( &cache, crate::RuntimeSpineHeartbeatJobOptions { runtime_id: identity.runtime_id, - session_id: crate::EPIPHANY_RUNTIME_ROOT_SESSION_ID.into(), - objective: "Gather evidence for one exact external-evidence obligation".into(), - coordinator_note: "Research current-work launch transaction opened this session." - .into(), job_id: job_id.clone(), role: crate::EPIPHANY_RESEARCH_OWNER_ROLE.into(), binding_id: crate::EPIPHANY_RESEARCH_ROLE_BINDING_ID.into(), @@ -1679,14 +1610,6 @@ pub fn launch_current_frontier_research_work( instruction: "Act as Epiphany Research. Gather only the evidence required by the exact frontier request and return keyed evidence and observations for Mind review.".into(), launch_document, output_contract_id: output_contract_id.clone(), - proposal_modeling_request_id: None, - frontier_planning_request_id: None, - frontier_plan_mind_request_id: None, - imagination_consideration_request_id: None, - admitted_model_direction_consideration_request_id: None, - repo_frontier_modeling_request_id: None, - repo_frontier_research_request_id: Some(request.request_id.clone()), - repo_frontier_verification_request_id: None, created_at: created_at.to_string(), }, )?; @@ -1752,20 +1675,24 @@ pub fn accept_frontier_research_result( )); } if let Some(receipt) = receipts.pop() { - let request_id = result - .repo_frontier_research_request_id - .as_deref() - .ok_or_else(|| anyhow!("frontier Research replay lost its request identity"))?; - let packet_id = format!("eyes-packet-{request_id}"); - let packet = cache - .get::(&packet_id)? - .ok_or_else(|| anyhow!("frontier Research replay lost its evidence packet"))?; - if packet.research_request_id != request_id - || packet.decision_context_id != result.decision_context_id - || !receipt - .writes - .iter() - .any(|write| write.document_type == crate::EpiphanyMindEvidenceDocument::TYPE) + receipt.validate()?; + if receipt + .strong_reads + .iter() + .filter(|read| read.document_type == crate::RepoFrontierResearchRequest::TYPE) + .count() + != 1 + || !receipt.strong_reads.iter().any(|read| { + read.document_type == crate::EpiphanyRuntimeRoleWorkerResult::TYPE + && read.document_key == job_id + }) + || !receipt.writes.iter().any(|write| { + matches!( + write.document_type.as_str(), + crate::EpiphanyMindEvidenceDocument::TYPE + | crate::EpiphanyMindObservationDocument::TYPE + ) + }) { return Err(anyhow!( "frontier Research replay does not preserve its exact accepted decision" @@ -1774,7 +1701,10 @@ pub fn accept_frontier_research_result( return Ok(receipt); } } - if current_frontier_research_review_job_id(store_path)?.as_deref() != Some(job_id) { + let research_lifecycle = crate::runtime_spine::repo_frontier_research_lifecycle(&cache)?; + if research_lifecycle.stage != crate::RepoFrontierResearchLifecycleStage::ResultReady + || research_lifecycle.worker_job_id.as_deref() != Some(job_id) + { return Err(anyhow!( "frontier Research result is not current review work" )); @@ -1790,8 +1720,9 @@ pub fn accept_frontier_research_result( .ok_or_else(|| anyhow!("frontier Research acceptance lost its launch"))?; let request = crate::runtime_spine::frontier_research_request_for_launch(&cache, &launch)? .ok_or_else(|| anyhow!("frontier Research acceptance lost its exact request"))?; - let fulfillment = crate::runtime_typed_request_fulfillment( + let fulfillment = crate::runtime_spine::runtime_typed_request_fulfillment_from_cache( store_path, + &cache, crate::RuntimeTypedRequestRef::FrontierResearch(&request.request_id), )? .ok_or_else(|| anyhow!("frontier Research result is not exact typed fulfillment"))?; @@ -1803,18 +1734,16 @@ pub fn accept_frontier_research_result( let result = cache .get::(job_id)? .ok_or_else(|| anyhow!("frontier Research acceptance lost its typed result"))?; - if !result.role_id.eq_ignore_ascii_case("research") - || result.repo_frontier_research_request_id.as_deref() != Some(request.request_id.as_str()) - || result.item_error.is_some() - { + if !result.role_id.eq_ignore_ascii_case("research") || result.item_error.is_some() { return Err(anyhow!("frontier Research result crossed family authority")); } let decision = result .research_decision()? .ok_or_else(|| anyhow!("frontier Research result has no typed decision"))?; decision.validate()?; - let lookups = - crate::runtime_authenticated_public_source_lookups_for_worker(store_path, job_id)?; + let lookups = crate::runtime_spine::authenticated_requested_public_source_lookups_for_worker( + &cache, job_id, + )?; for lookup in &lookups { if !result.evidence_ids.contains(&lookup.receipt_id) || !result.files_inspected.contains(&lookup.source_ref) @@ -1833,20 +1762,6 @@ pub fn accept_frontier_research_result( "frontier Research result did not expose every proposed evidence identity" )); } - let finding = crate::interpret_runtime_role_worker_result( - crate::EpiphanyRoleResultRoleId::Research, - &result, - ); - let packet_id = format!("eyes-packet-{}", request.request_id); - let packet = crate::eyes_evidence_packet_from_research_finding( - packet_id.clone(), - request.request_id.clone(), - result.decision_context_id.clone(), - &finding, - &decision, - &lookups, - accepted_at.to_string(), - ); let mut writes = Vec::new(); for evidence in &decision.evidence { writes.push(crate::mind_documents::prepare_mind_document( @@ -1910,14 +1825,12 @@ pub fn accept_frontier_research_result( } strong_reads.push(envelope.clone()); } - let packet_envelope = cache.prepare_entry(&packet_id, &packet)?.0; - match crate::reasoning_context::commit_mind_mutation_with_derived_companions( + match crate::reasoning_context::commit_mind_mutation( store_path, &result.decision_context_id, "Eyes.frontier_research", strong_reads, writes, - vec![packet_envelope], accepted_at, )? { crate::EpiphanyMindCommitOutcome::Committed(receipt) => Ok(receipt), @@ -1988,7 +1901,13 @@ pub fn accept_frontier_verification_result( return Ok(EpiphanyAgentPassAdmissionOutcome::Committed(receipt)); } } - if current_frontier_verification_review_job_id(store_path)?.as_deref() != Some(job_id) { + let verification = current_verification_work(&cache)?; + if verification + .filter(|work| work.attempt.action == EpiphanyAgentPassContinuationAction::Review) + .and_then(|work| work.attempt.job_id) + .as_deref() + != Some(job_id) + { return Err(anyhow!( "frontier Verification result is not current review work" )); @@ -2006,8 +1925,9 @@ pub fn accept_frontier_verification_result( .ok_or_else(|| anyhow!("frontier Verification acceptance lost its launch"))?; let request = crate::runtime_spine::frontier_verification_request_for_launch(&cache, &launch)? .ok_or_else(|| anyhow!("frontier Verification acceptance lost its exact request"))?; - let fulfillment = crate::runtime_typed_request_fulfillment( + let fulfillment = crate::runtime_spine::runtime_typed_request_fulfillment_from_cache( store_path, + &cache, crate::RuntimeTypedRequestRef::FrontierVerification(&request.request_id), )? .ok_or_else(|| anyhow!("frontier Verification result is not exact typed fulfillment"))?; @@ -2019,11 +1939,7 @@ pub fn accept_frontier_verification_result( let result = cache .get::(job_id)? .ok_or_else(|| anyhow!("frontier Verification acceptance lost its typed result"))?; - if !result.role_id.eq_ignore_ascii_case("verification") - || result.verification_request_id.as_deref() != Some(request.request_id.as_str()) - || result.frontier_route_id.as_deref() != Some(request.route_id.as_str()) - || result.item_error.is_some() - { + if !result.role_id.eq_ignore_ascii_case("verification") || result.item_error.is_some() { return Err(anyhow!( "frontier Verification result crossed request authority" )); @@ -2059,7 +1975,6 @@ pub fn accept_frontier_verification_result( }; audit.validate()?; let verdict = crate::SoulVerdictReceipt { - schema_version: crate::SOUL_VERDICT_RECEIPT_SCHEMA_VERSION.into(), receipt_id: format!("soul-verdict-{}", result.result_id), source_result_id: result.result_id.clone(), source_job_id: result.job_id.clone(), @@ -2068,7 +1983,6 @@ pub fn accept_frontier_verification_result( evidence_ids, risks: result.risks.clone(), emitted_at: accepted_at.to_string(), - contract: "Soul verdict emitted by the exact Verification Mind admission owner.".into(), verification_request_id: request.request_id.clone(), frontier_route_id: request.route_id.clone(), }; @@ -2093,10 +2007,6 @@ pub fn accept_frontier_verification_result( crate::HandsActionIntent::TYPE, request.hands_intent_id.as_str(), ), - ( - crate::HandsActionReview::TYPE, - request.hands_review_id.as_str(), - ), ( crate::HandsPatchReceipt::TYPE, request.hands_patch_receipt_id.as_str(), @@ -2197,13 +2107,14 @@ pub fn accept_body_modeling_result( let result = cache .get::(job_id)? .ok_or_else(|| anyhow!("Body Modeling acceptance lost its typed result"))?; - if result.role_id != "modeling" - || result.proposal_modeling_request_id.is_some() - || result.repo_frontier_modeling_request_id.is_some() - { + let launch = cache + .get::(job_id)? + .ok_or_else(|| anyhow!("Body Modeling acceptance lost its immutable launch"))?; + let role_launch = role_launch_document(&launch)?; + if result.role_id != "modeling" || role_launch.family_context_count() != 0 { return Err(anyhow!("result is not baseline Body Modeling authority")); } - let body_basis = result + let body_basis = role_launch .repository_body_observation_basis .clone() .ok_or_else(|| anyhow!("Body Modeling result has no Body observation basis"))?; @@ -2309,17 +2220,21 @@ pub fn accept_proposal_modeling_result( let result = cache .get::(job_id)? .ok_or_else(|| anyhow!("proposal Modeling acceptance lost its typed result"))?; - let request_id = result - .proposal_modeling_request_id - .as_deref() + let launch = cache + .get::(job_id)? + .ok_or_else(|| anyhow!("proposal Modeling acceptance lost its immutable launch"))?; + let role_launch = role_launch_document(&launch)?; + let request_id = role_launch + .proposal_modeling_context + .as_ref() + .map(|context| context.request_id.as_str()) .ok_or_else(|| anyhow!("result is not proposal Modeling authority"))?; - if !result.role_id.eq_ignore_ascii_case("modeling") - || result.repo_frontier_modeling_request_id.is_some() - { + if !result.role_id.eq_ignore_ascii_case("modeling") || role_launch.family_context_count() != 1 { return Err(anyhow!("proposal Modeling result crossed family authority")); } - let evidence = crate::runtime_typed_request_fulfillment( + let evidence = crate::runtime_spine::runtime_typed_request_fulfillment_from_cache( store_path, + &cache, crate::RuntimeTypedRequestRef::ProposalModeling(request_id), )? .ok_or_else(|| anyhow!("proposal Modeling result is not exact typed fulfillment"))?; @@ -2510,9 +2425,14 @@ pub fn accept_frontier_verdict_modeling_result( let result = cache .get::(job_id)? .ok_or_else(|| anyhow!("frontier verdict Modeling acceptance lost its typed result"))?; - let request_id = result - .repo_frontier_modeling_request_id - .as_deref() + let launch = cache + .get::(job_id)? + .ok_or_else(|| anyhow!("frontier verdict Modeling acceptance lost its immutable launch"))?; + let role_launch = role_launch_document(&launch)?; + let request_id = role_launch + .frontier_verdict_modeling_context + .as_ref() + .map(|context| context.request.request_id.as_str()) .ok_or_else(|| anyhow!("result is not frontier verdict Modeling authority"))?; let current = match current_frontier_verdict_modeling_work(&cache) { Ok(Some(work)) @@ -2541,7 +2461,7 @@ pub fn accept_frontier_verdict_modeling_result( }; if !result.role_id.eq_ignore_ascii_case("modeling") || request_id != current.request.request_id - || result.proposal_modeling_request_id.is_some() + || role_launch.family_context_count() != 1 { return Err(anyhow!( "frontier verdict Modeling result crossed family authority" @@ -2717,68 +2637,72 @@ fn launch_consideration_work( .get::(crate::RUNTIME_IDENTITY_KEY)? .ok_or_else(|| anyhow!("consideration launch requires runtime identity"))?; let model = crate::repo_model_documents::assemble_repo_model_view_from_cache(&cache)?; - let (attempt_ordinal, dynamic_prompt_context, imagination_context, direction_context) = - match launch { - ConsiderationLaunch::PersonaFeedback(request) => { - crate::validate_current_imagination_consideration_request(&cache, request)?; - let current = current_imagination_consideration_work(&cache)? - .into_iter() - .find(|work| work.request.request_id == request.request_id) - .ok_or_else(|| anyhow!("Persona feedback consideration is not current"))?; - if current.attempt.action != EpiphanyAgentPassContinuationAction::Launch { - return Err(anyhow!("Persona feedback consideration is not launchable")); - } - let ordinal = cache - .get_all::()? - .into_iter() - .filter(|binding| binding.request_id == request.request_id) - .count(); - ( - ordinal, - crate::render_imagination_consideration_prompt(request)?, - Some(crate::ImaginationConsiderationContextProjection::new( - request, &model, - )), - None, - ) - } - ConsiderationLaunch::AdmittedModelDirection(request) => { - crate::validate_current_admitted_model_direction_consideration_request( - &cache, request, - )?; - let current = current_admitted_model_direction_consideration_work(&cache)? - .filter(|work| work.request.request_id == request.request_id) - .ok_or_else(|| { - anyhow!("admitted model direction consideration is not current") - })?; - if current.attempt.action != EpiphanyAgentPassContinuationAction::Launch { - return Err(anyhow!( - "admitted model direction consideration is not launchable" - )); - } - let ordinal = cache - .get_all::()? - .into_iter() - .filter(|worker| { - worker - .admitted_model_direction_consideration_request_id - .as_deref() - == Some(request.request_id.as_str()) - }) - .count(); - ( - ordinal, - crate::render_admitted_model_direction_consideration_prompt(request), - None, - Some( - crate::AdmittedModelDirectionConsiderationContextProjection::new( - request, &model, - ), - ), - ) - } - }; let request_id = launch.request_id().to_string(); + let is_feedback = matches!(launch, ConsiderationLaunch::PersonaFeedback(_)); + let launches = cache.get_all::()?; + let request_ref = if is_feedback { + crate::RuntimeTypedRequestRef::ImaginationConsideration(&request_id) + } else { + crate::RuntimeTypedRequestRef::AdmittedModelDirection(&request_id) + }; + let mut prior_launches = typed_request_launches(&launches, request_ref)? + .into_iter() + .cloned() + .map(|worker| { + Ok(( + consideration_attempt_ordinal(&request_id, &worker.job_id)?, + worker, + )) + }) + .collect::>>()?; + prior_launches.sort_by_key(|(ordinal, _)| *ordinal); + for (expected, (ordinal, _)) in prior_launches.iter().enumerate() { + if *ordinal != expected { + return Err(anyhow!("consideration has noncontiguous attempt identity")); + } + } + let attempt_ordinal = prior_launches.len(); + let (dynamic_prompt_context, imagination_context, direction_context) = match launch { + ConsiderationLaunch::PersonaFeedback(request) => { + crate::validate_current_imagination_consideration_request(&cache, request)?; + let current = current_imagination_consideration_work(&cache)? + .into_iter() + .find(|work| work.request.request_id == request.request_id) + .ok_or_else(|| anyhow!("Persona feedback consideration is not current"))?; + if current.attempt.action != EpiphanyAgentPassContinuationAction::Launch { + return Err(anyhow!("Persona feedback consideration is not launchable")); + } + ( + crate::render_imagination_consideration_prompt(request)?, + Some(crate::ImaginationConsiderationContextProjection::new( + request, &model, + )), + None, + ) + } + ConsiderationLaunch::AdmittedModelDirection(request) => { + crate::validate_current_admitted_model_direction_consideration_request( + &cache, request, + )?; + let current = current_admitted_model_direction_consideration_work(&cache)? + .filter(|work| work.request.request_id == request.request_id) + .ok_or_else(|| anyhow!("admitted model direction consideration is not current"))?; + if current.attempt.action != EpiphanyAgentPassContinuationAction::Launch { + return Err(anyhow!( + "admitted model direction consideration is not launchable" + )); + } + ( + crate::admitted_model_direction_consideration::render_prompt(request), + None, + Some( + crate::AdmittedModelDirectionConsiderationContextProjection::new( + request, &model, + ), + ), + ) + } + }; let job_id = format!("consideration-{request_id}-attempt-{attempt_ordinal}"); let launch_document = crate::EpiphanyWorkerLaunchDocument::Role(crate::EpiphanyRoleWorkerLaunchDocument { @@ -2803,14 +2727,10 @@ fn launch_consideration_work( } .to_string(); let output_contract_id = launch_document.output_contract_id().to_string(); - let is_feedback = matches!(launch, ConsiderationLaunch::PersonaFeedback(_)); let prepared = crate::prepare_runtime_spine_heartbeat_job( &cache, crate::RuntimeSpineHeartbeatJobOptions { runtime_id: identity.runtime_id, - session_id: crate::EPIPHANY_RUNTIME_ROOT_SESSION_ID.into(), - objective: "Consider one exact typed proposal-only obligation".into(), - coordinator_note: "Consideration current-work launch opened this attempt.".into(), job_id: job_id.clone(), role: crate::EPIPHANY_IMAGINATION_OWNER_ROLE.into(), binding_id: crate::EPIPHANY_IMAGINATION_ROLE_BINDING_ID.into(), @@ -2818,15 +2738,6 @@ fn launch_consideration_work( instruction: "Act as Epiphany Imagination for one exact proposal-only typed consideration pass and return only its dedicated terminal contract.".into(), launch_document, output_contract_id: output_contract_id.clone(), - proposal_modeling_request_id: None, - frontier_planning_request_id: None, - frontier_plan_mind_request_id: None, - imagination_consideration_request_id: is_feedback.then_some(request_id.clone()), - admitted_model_direction_consideration_request_id: (!is_feedback) - .then_some(request_id.clone()), - repo_frontier_modeling_request_id: None, - repo_frontier_research_request_id: None, - repo_frontier_verification_request_id: None, created_at: created_at.into(), }, )?; @@ -2854,42 +2765,11 @@ fn launch_consideration_work( &cache, launch.model_source_documents(), )?); - let mut companions = Vec::new(); - if let ConsiderationLaunch::PersonaFeedback(request) = launch { - let worker = prepared - .iter() - .find(|envelope| { - envelope.r#type == crate::EpiphanyRuntimeWorkerLaunchRequest::TYPE - && envelope.key == job_id - }) - .ok_or_else(|| anyhow!("consideration launch preparation lost its worker"))?; - let worker: crate::EpiphanyRuntimeWorkerLaunchRequest = - rmp_serde::from_slice(&worker.payload)?; - let binding_record_id = format!( - "imagination-consideration-launch-{}-attempt-{attempt_ordinal}", - request.request_id - ); - companions.push( + for (_, worker) in &prior_launches { + expected.push( cache - .prepare_entry( - &binding_record_id, - &crate::ImaginationConsiderationLaunchBinding { - schema_version: - crate::IMAGINATION_CONSIDERATION_LAUNCH_BINDING_SCHEMA_VERSION.into(), - binding_record_id: binding_record_id.clone(), - request_id: request.request_id.clone(), - job_id: job_id.clone(), - binding_id: crate::EPIPHANY_IMAGINATION_ROLE_BINDING_ID.into(), - runtime_id: request.runtime_id.clone(), - thread_id: request.thread_id.clone(), - launched_at: created_at.into(), - worker_launch_document_sha256: format!( - "{:x}", - Sha256::digest(&worker.launch_document_msgpack) - ), - }, - )? - .0, + .get_envelope::(&worker.job_id)? + .ok_or_else(|| anyhow!("consideration retry lost its prior launch envelope"))?, ); } commit_current_work_launch( @@ -2897,7 +2777,7 @@ fn launch_consideration_work( &cache, expected, prepared, - companions, + Vec::new(), "consideration", )?; Ok(job_id) @@ -2941,15 +2821,14 @@ pub fn launch_current_admitted_model_direction_consideration_work( pub fn launch_current_body_modeling_work( store_path: impl AsRef, - options: EpiphanyBodyModelingLaunchOptions, -) -> Result { + created_at: &str, +) -> Result { let store_path = store_path.as_ref(); - if options.job_id.trim().is_empty() - || chrono::DateTime::parse_from_rfc3339(&options.created_at).is_err() - { - return Err(anyhow!("Body Modeling launch options are invalid")); - } - let mind = crate::assemble_mind_view(store_path)?; + chrono::DateTime::parse_from_rfc3339(created_at) + .map_err(|_| anyhow!("Body Modeling launch time is invalid"))?; + let mut cache = crate::runtime_spine_cache(store_path)?; + cache.pull_all_backing_stores()?; + let mind = crate::mind_documents::assemble_mind_view_from_cache(&cache)?; let body_basis = mind .repository_body_observation .clone() @@ -2963,14 +2842,12 @@ pub fn launch_current_body_modeling_work( body_basis, repo_model.reasoning_basis(), )?; - let mut cache = crate::runtime_spine_cache(store_path)?; - cache.pull_all_backing_stores()?; if resolve_body_modeling_work( work.clone(), cache.get::(&work.work_id)?, )? .is_none() - || body_modeling_attempt(&cache, &work.work_id)?.action + || body_modeling_attempt(&cache, &work)?.action != EpiphanyAgentPassContinuationAction::Launch { return Err(anyhow!("Mind has no launchable Body Modeling work")); @@ -2984,20 +2861,12 @@ pub fn launch_current_body_modeling_work( }) .cloned() .ok_or_else(|| anyhow!("Body Modeling launch lost its admitted Body source"))?; - let attempt_ordinal = cache - .get_all::()? - .into_iter() - .filter(|binding| binding.work_id == work.work_id) - .map(|binding| binding.attempt_ordinal) - .max() - .map_or(0, |ordinal| ordinal + 1); - let binding_record_id = format!( - "body-modeling-launch-{}-attempt-{attempt_ordinal}", - work.work_id - ); + let prior_launches = body_modeling_launches(&cache, &work)?; + let attempt_ordinal = prior_launches.len(); + let job_id = format!("{}-attempt-{attempt_ordinal}", work.work_id); let launch_document = crate::EpiphanyWorkerLaunchDocument::Role(crate::EpiphanyRoleWorkerLaunchDocument { - thread_id: binding_record_id.clone(), + thread_id: job_id.clone(), role_id: "modeling".into(), objective: None, dynamic_prompt_context: None, @@ -3018,46 +2887,24 @@ pub fn launch_current_body_modeling_work( &cache, crate::RuntimeSpineHeartbeatJobOptions { runtime_id: work.runtime_id.clone(), - session_id: crate::EPIPHANY_RUNTIME_ROOT_SESSION_ID.into(), - objective: "Model the admitted repository Body".into(), - coordinator_note: "Keyed Mind current-work launch transaction opened this session." - .into(), - job_id: options.job_id.clone(), + job_id: job_id.clone(), role: crate::EPIPHANY_MODELING_OWNER_ROLE.into(), binding_id: crate::EPIPHANY_MODELING_ROLE_BINDING_ID.into(), authority_scope: authority_scope.clone(), instruction: instruction.clone(), launch_document: launch_document.clone(), output_contract_id: output_contract_id.clone(), - proposal_modeling_request_id: None, - frontier_planning_request_id: None, - frontier_plan_mind_request_id: None, - imagination_consideration_request_id: None, - admitted_model_direction_consideration_request_id: None, - repo_frontier_modeling_request_id: None, - repo_frontier_research_request_id: None, - repo_frontier_verification_request_id: None, - created_at: options.created_at.clone(), + created_at: created_at.into(), }, )?; - let binding = EpiphanyBodyModelingLaunchBinding { - schema_version: BODY_MODELING_LAUNCH_BINDING_SCHEMA_VERSION.into(), - binding_record_id, - work_id: work.work_id, - job_id: options.job_id, - attempt_ordinal, - body_document_sha256: body_source.payload_sha256.clone(), - observed_repo_model_projection_digest: work.repo_model_basis.projection_digest.clone(), - launched_at: options.created_at.clone(), - }; let grant = crate::substrate_gate::substrate_gate_repo_access_grant_for_worker( - format!("substrate-grant-{}", binding.job_id), - binding.job_id.clone(), + format!("substrate-grant-{job_id}"), + job_id.clone(), crate::EPIPHANY_MODELING_ROLE_BINDING_ID.into(), crate::EPIPHANY_MODELING_OWNER_ROLE.into(), authority_scope, false, - options.created_at, + created_at.into(), ); let snapshot = cache.snapshot_envelopes(); let mut expected = Vec::new(); @@ -3073,24 +2920,61 @@ pub fn launch_current_body_modeling_work( expected.push(envelope.clone()); } } + for (_, launch) in &prior_launches { + expected.push( + cache + .get_envelope::(&launch.job_id)? + .ok_or_else(|| anyhow!("Body Modeling retry lost its prior launch envelope"))?, + ); + } + if let Some((_, latest)) = prior_launches.last() { + expected.push( + cache + .get_envelope::(&latest.job_id)? + .ok_or_else(|| anyhow!("Body Modeling retry lost its prior job envelope"))?, + ); + if let Some(result) = cache.get::(&latest.job_id)? { + expected.push( + cache + .get_envelope::(&latest.job_id)? + .ok_or_else(|| anyhow!("Body Modeling retry lost its result envelope"))?, + ); + if let Some(refusal) = cache + .get_all::()? + .into_iter() + .find(|refusal| { + refusal.pass_family == EpiphanyAgentPassFamily::BodyModeling + && refusal.request_id == work.work_id + && refusal.job_id == latest.job_id + && refusal.result_id == result.result_id + && refusal.decision_context_id == result.decision_context_id + }) + { + expected.push( + cache + .get_envelope::(&refusal.refusal_id)? + .ok_or_else(|| { + anyhow!("Body Modeling retry lost its admission refusal envelope") + })?, + ); + } + } + } commit_current_work_launch( store_path, &cache, expected, prepared, - vec![ - cache.prepare_entry(&binding.binding_record_id, &binding)?.0, - cache.prepare_entry(&grant.receipt_id, &grant)?.0, - ], + vec![cache.prepare_entry(&grant.receipt_id, &grant)?.0], "Body Modeling", )?; - Ok(binding) + Ok(job_id) } pub fn launch_current_proposal_modeling_work( store_path: impl AsRef, options: EpiphanyProposalModelingLaunchOptions, -) -> Result { +) -> Result { let store_path = store_path.as_ref(); if chrono::DateTime::parse_from_rfc3339(&options.created_at).is_err() { return Err(anyhow!("proposal Modeling launch options are invalid")); @@ -3105,7 +2989,7 @@ pub fn launch_current_proposal_modeling_work( .get::(&request.proposal_id)? .ok_or_else(|| anyhow!("proposal Modeling launch lost its proposal"))?; crate::runtime_spine::validate_repo_frontier_work_proposal(&proposal)?; - crate::runtime_spine::validate_autonomous_proposal_binding(&cache, &proposal)?; + crate::runtime_spine::validate_autonomous_proposal_origin(&cache, &proposal)?; let identity = cache .get::(crate::RUNTIME_IDENTITY_KEY)? .ok_or_else(|| anyhow!("proposal Modeling launch requires runtime identity"))?; @@ -3115,7 +2999,7 @@ pub fn launch_current_proposal_modeling_work( { return Err(anyhow!("proposal Modeling launch provenance mismatch")); } - let mind = crate::assemble_mind_view(store_path)?; + let mind = crate::mind_documents::assemble_mind_view_from_cache(&cache)?; let body_basis = mind .repository_body_observation .clone() @@ -3124,11 +3008,12 @@ pub fn launch_current_proposal_modeling_work( .repo_model .as_ref() .ok_or_else(|| anyhow!("proposal Modeling launch requires keyed RepoModel state"))?; - let attempt_ordinal = cache - .get_all::()? - .into_iter() - .filter(|binding| binding.proposal_modeling_request_id == request.request_id) - .count() + let launches = cache.get_all::()?; + let attempt_ordinal = typed_request_launches( + &launches, + crate::RuntimeTypedRequestRef::ProposalModeling(&request.request_id), + )? + .len() + cache .get_all::()? .into_iter() @@ -3149,7 +3034,6 @@ pub fn launch_current_proposal_modeling_work( "proposal-modeling-{}-attempt-{attempt_ordinal}", request.request_id ); - let binding_record_id = format!("repo-frontier-proposal-modeling-launch-{job_id}"); let launch_document = crate::EpiphanyWorkerLaunchDocument::Role(crate::EpiphanyRoleWorkerLaunchDocument { thread_id: request.thread_id.clone(), @@ -3166,20 +3050,12 @@ pub fn launch_current_proposal_modeling_work( imagination_consideration_context: None, admitted_model_direction_consideration_context: None, }); - let worker_launch_document_sha256 = format!( - "{:x}", - Sha256::digest(rmp_serde::to_vec_named(&launch_document)?) - ); let authority_scope = "epiphany.role.modeling".to_string(); let output_contract_id = launch_document.output_contract_id().to_string(); let prepared = crate::prepare_runtime_spine_heartbeat_job( &cache, crate::RuntimeSpineHeartbeatJobOptions { runtime_id: identity.runtime_id.clone(), - session_id: crate::EPIPHANY_RUNTIME_ROOT_SESSION_ID.into(), - objective: "Model one admitted proposal against keyed Mind".into(), - coordinator_note: "Proposal current-work launch transaction opened this session." - .into(), job_id: job_id.clone(), role: crate::EPIPHANY_MODELING_OWNER_ROLE.into(), binding_id: crate::EPIPHANY_MODELING_ROLE_BINDING_ID.into(), @@ -3187,26 +3063,12 @@ pub fn launch_current_proposal_modeling_work( instruction: "Act as Epiphany Modeling. Evaluate the exact admitted proposal against the sealed keyed Mind projection and return only the proposal Modeling result contract.".into(), launch_document, output_contract_id: output_contract_id.clone(), - proposal_modeling_request_id: Some(request.request_id.clone()), - frontier_planning_request_id: None, - frontier_plan_mind_request_id: None, - imagination_consideration_request_id: None, - admitted_model_direction_consideration_request_id: None, - repo_frontier_modeling_request_id: None, - repo_frontier_research_request_id: None, - repo_frontier_verification_request_id: None, created_at: options.created_at.clone(), }, )?; - let binding = crate::RepoFrontierProposalModelingLaunchBinding { - schema_version: crate::REPO_FRONTIER_PROPOSAL_MODELING_LAUNCH_BINDING_SCHEMA_VERSION.into(), - proposal_modeling_request_id: request.request_id.clone(), - job_id: job_id.clone(), - worker_launch_document_sha256, - }; let grant = crate::substrate_gate::substrate_gate_repo_access_grant_for_worker( - format!("substrate-grant-{}", binding.job_id), - binding.job_id.clone(), + format!("substrate-grant-{job_id}"), + job_id.clone(), crate::EPIPHANY_MODELING_ROLE_BINDING_ID.into(), crate::EPIPHANY_MODELING_OWNER_ROLE.into(), authority_scope, @@ -3243,13 +3105,10 @@ pub fn launch_current_proposal_modeling_work( &cache, expected, prepared, - vec![ - cache.prepare_entry(&binding_record_id, &binding)?.0, - cache.prepare_entry(&grant.receipt_id, &grant)?.0, - ], + vec![cache.prepare_entry(&grant.receipt_id, &grant)?.0], "proposal Modeling", )?; - Ok(binding) + Ok(job_id) } pub fn launch_current_frontier_verdict_modeling_work( @@ -3273,7 +3132,7 @@ pub fn launch_current_frontier_verdict_modeling_work( .get::(&request.frontier_item_id)? .ok_or_else(|| anyhow!("frontier verdict Modeling launch lost its frontier document"))?; let frontier_item = frontier_document.value()?; - let mind = crate::assemble_mind_view(store_path)?; + let mind = crate::mind_documents::assemble_mind_view_from_cache(&cache)?; let body_basis = mind .repository_body_observation .clone() @@ -3281,17 +3140,17 @@ pub fn launch_current_frontier_verdict_modeling_work( let identity = cache .get::(crate::RUNTIME_IDENTITY_KEY)? .ok_or_else(|| anyhow!("frontier verdict Modeling launch requires runtime identity"))?; - let attempt_ordinal = cache - .get_all::()? - .into_iter() - .filter(|launch| { - launch.repo_frontier_modeling_request_id.as_deref() == Some(request.request_id.as_str()) - }) - .map(|launch| frontier_verdict_attempt_ordinal(&request.request_id, &launch.job_id)) - .collect::>>()? - .into_iter() - .max() - .map_or(0, |ordinal| ordinal + 1); + let launches = cache.get_all::()?; + let attempt_ordinal = typed_request_launches( + &launches, + crate::RuntimeTypedRequestRef::FrontierVerdictModeling(&request.request_id), + )? + .into_iter() + .map(|launch| frontier_verdict_attempt_ordinal(&request.request_id, &launch.job_id)) + .collect::>>()? + .into_iter() + .max() + .map_or(0, |ordinal| ordinal + 1); let job_id = format!( "frontier-verdict-modeling-{}-attempt-{attempt_ordinal}", request.request_id @@ -3323,10 +3182,6 @@ pub fn launch_current_frontier_verdict_modeling_work( &cache, crate::RuntimeSpineHeartbeatJobOptions { runtime_id: identity.runtime_id, - session_id: crate::EPIPHANY_RUNTIME_ROOT_SESSION_ID.into(), - objective: "Incorporate one exact Soul verdict into its routed frontier".into(), - coordinator_note: - "Frontier verdict current-work launch transaction opened this session.".into(), job_id: job_id.clone(), role: crate::EPIPHANY_MODELING_OWNER_ROLE.into(), binding_id: crate::EPIPHANY_MODELING_ROLE_BINDING_ID.into(), @@ -3334,14 +3189,6 @@ pub fn launch_current_frontier_verdict_modeling_work( instruction: "Act as Epiphany Modeling. Incorporate the exact typed Soul verdict into only its routed frontier item and return the frontier-verdict result contract.".into(), launch_document, output_contract_id: output_contract_id.clone(), - proposal_modeling_request_id: None, - frontier_planning_request_id: None, - frontier_plan_mind_request_id: None, - imagination_consideration_request_id: None, - admitted_model_direction_consideration_request_id: None, - repo_frontier_modeling_request_id: Some(request.request_id.clone()), - repo_frontier_research_request_id: None, - repo_frontier_verification_request_id: None, created_at: created_at.to_string(), }, )?; @@ -3462,7 +3309,7 @@ fn resolve_body_modeling_work( #[cfg(test)] mod tests { use super::*; - use epiphany_state_model::{ + use crate::state_model::{ EpiphanyMemoryDomain, EpiphanyMemoryLifecycle, EpiphanyMemoryNode, EpiphanyMemoryNodeKind, RepoFrontierItem, RepoFrontierStatus, }; @@ -3633,10 +3480,15 @@ mod tests { "2026-08-17T00:00:01Z", )?; crate::reset_repository_body_read_counters(); - let projected_work = current_body_modeling_work(&store)?; + let current_work = project_current_work(&store)?; + let projected_work = current_work + .body_modeling + .as_ref() + .expect("current Mind projects Body Modeling work") + .work + .clone(); assert_eq!(projected_work.body_basis, body); assert_eq!(crate::repository_body_read_counters(), (0, 0)); - let current_work = project_current_work(&store)?; assert_eq!( current_work.body_modeling, Some(EpiphanyBodyModelingCurrentWorkProjection { @@ -3694,15 +3546,8 @@ mod tests { ); assert_eq!(crate::repository_body_read_counters(), (0, 0)); assert_current_work_reentry_is_read_only(&store, ¤t_work)?; - let scheduled = launch_current_body_modeling_work( - &store, - EpiphanyBodyModelingLaunchOptions { - job_id: "body-scheduled-job".into(), - created_at: "2026-08-17T00:00:02Z".into(), - }, - )?; - assert_eq!(scheduled.work_id, projected_work.work_id); - assert_eq!(scheduled.attempt_ordinal, 0); + let scheduled = launch_current_body_modeling_work(&store, "2026-08-17T00:00:02Z")?; + assert_eq!(scheduled, format!("{}-attempt-0", projected_work.work_id)); let scheduled_work = project_current_work(&store)?; assert_eq!( scheduled_work @@ -3711,7 +3556,7 @@ mod tests { .map(|work| (work.attempt.action, work.attempt.job_id.as_deref())), Some(( EpiphanyAgentPassContinuationAction::Wait, - Some("body-scheduled-job") + Some(scheduled.as_str()) )) ); let resident_policy = crate::ResidentSelfPolicy { @@ -3722,7 +3567,7 @@ mod tests { runtime_store: store.clone(), local_verse_store: temp.path().join("local-verse.cc"), artifact_root: temp.path().join("artifacts"), - codex_home: temp.path().join("codex-home"), + model_connector_endpoint: "127.0.0.1:17891".parse().unwrap(), mcp_config: temp.path().join("mcp.toml"), model_provider: "openrouter".into(), model: "stealth/ox-alpha".into(), @@ -3744,9 +3589,14 @@ mod tests { "a grant derived from the prior Mind projection must not launch" ); assert!(crate::pending_resident_self_grant(&resident_store)?.is_none()); - let terminal_receipts = crate::resident_self_terminal_receipts(&resident_store)?; - assert_eq!(terminal_receipts.len(), 1); - assert_eq!(terminal_receipts[0].terminal_status, "superseded"); + let terminal = SingleFileMessagePackBackingStore::new(&resident_store) + .pull_all()? + .into_iter() + .find(|entry| entry.r#type == crate::ResidentSelfTerminalReceipt::TYPE) + .expect("superseded grant must leave one terminal receipt"); + let terminal: crate::ResidentSelfTerminalReceipt = + rmp_serde::from_slice(&terminal.payload)?; + assert_eq!(terminal.terminal_status, "superseded"); assert_current_work_reentry_is_read_only(&store, &scheduled_work)?; let mut scheduled_cache = crate::runtime_spine_cache(&store)?; scheduled_cache.pull_all_backing_stores()?; @@ -3758,31 +3608,22 @@ mod tests { ); assert!( scheduled_cache - .get::("body-scheduled-job")? + .get::(&scheduled)? .is_some() ); assert!( scheduled_cache - .get::( - "substrate-grant-body-scheduled-job" - )? + .get::(&format!( + "substrate-grant-{scheduled}" + ))? .is_some() ); - assert!( - launch_current_body_modeling_work( - &store, - EpiphanyBodyModelingLaunchOptions { - job_id: "body-scheduled-job-duplicate".into(), - created_at: "2026-08-17T00:00:03Z".into(), - }, - ) - .is_err() - ); + assert!(launch_current_body_modeling_work(&store, "2026-08-17T00:00:03Z").is_err()); assert_eq!(crate::repository_body_read_counters(), (0, 0)); let mut cache = crate::runtime_spine_cache(&store)?; cache.pull_all_backing_stores()?; let failed_launch = cache - .get::("body-scheduled-job")? + .get::(&scheduled)? .expect("scheduled Body launch exists"); drop(cache); let failed_basis = crate::worker_reasoning_basis(&store, &failed_launch)?; @@ -3798,22 +3639,11 @@ mod tests { failed_native.source_worker_job_id = Some(failed_launch.job_id.clone()); crate::open_runtime_model_execution( &store, - crate::RuntimeSpineSessionOptions { - session_id: "body-failed-model-session".into(), - objective: "Model the exact Body projection.".into(), - created_at: "2026-08-17T00:00:02.100Z".into(), - coordinator_note: "Test dead worker audit context.".into(), - }, crate::RuntimeSpineJobOptions { job_id: "body-failed-model-job".into(), session_id: "body-failed-model-session".into(), - role: "openai-model".into(), - created_at: "2026-08-17T00:00:02.100Z".into(), - summary: "Bound Body inference.".into(), - artifact_refs: Vec::new(), }, &failed_native, - "2026-08-17T00:00:02.100Z", )?; let failed_process = crate::ProcessInstanceIdentity { process_id: u32::MAX, @@ -3842,14 +3672,15 @@ mod tests { ); let mut death_cache = crate::runtime_spine_cache(&store)?; death_cache.pull_all_backing_stores()?; - let death = death_cache - .get::("result-worker-death-body-scheduled-job")? - .expect("worker death emits its terminal runtime result"); - assert_eq!(death.verdict, "failed"); - let death_context = death - .decision_context_id - .as_deref() - .expect("model-backed death preserves its decision context"); + let death_failures = death_cache + .get_all::()? + .into_iter() + .filter(|failure| failure.pass_id == scheduled) + .collect::>(); + let [death_failure] = death_failures.as_slice() else { + panic!("worker death emits one typed model-pass failure") + }; + let death_context = death_failure.decision_context_id.as_str(); assert!(crate::audit_decision_context(&store, death_context).is_ok()); assert_eq!( crate::runtime_worker_process_claim(&store, &failed_launch.job_id)? @@ -3865,7 +3696,7 @@ mod tests { .map(|work| (work.attempt.action, work.attempt.job_id.as_deref())), Some(( EpiphanyAgentPassContinuationAction::Launch, - Some("body-scheduled-job") + Some(scheduled.as_str()) )) ); assert_ne!( @@ -3893,18 +3724,12 @@ mod tests { "/launchModeling" ) ); - let retried = launch_current_body_modeling_work( - &store, - EpiphanyBodyModelingLaunchOptions { - job_id: "body-retry-job".into(), - created_at: "2026-08-17T00:00:04Z".into(), - }, - )?; - assert_eq!(retried.attempt_ordinal, 1); + let retried = launch_current_body_modeling_work(&store, "2026-08-17T00:00:04Z")?; + assert_eq!(retried, format!("{}-attempt-1", projected_work.work_id)); let mut cache = crate::runtime_spine_cache(&store)?; cache.pull_all_backing_stores()?; let launch = cache - .get::("body-retry-job")? + .get::(&retried)? .unwrap(); crate::reset_repository_body_read_counters(); let reasoning_basis = crate::worker_reasoning_basis(&store, &launch)?; @@ -3932,7 +3757,6 @@ mod tests { let context = crate::EpiphanyDecisionContext::new(&reasoning_basis, native, Vec::new())?; cache.put(&context.context_id, &context)?; let result = crate::EpiphanyRuntimeRoleWorkerResult { - schema_version: crate::RUNTIME_ROLE_WORKER_RESULT_SCHEMA_VERSION.into(), result_id: "body-result".into(), job_id: launch.job_id.clone(), role_id: "modeling".into(), @@ -3952,19 +3776,9 @@ mod tests { item_error: None, metadata: Default::default(), repo_model_mutation_proposal_msgpack: None, - verification_request_id: None, - frontier_route_id: None, - repo_frontier_modeling_request_id: None, - proposal_modeling_request_id: None, - repo_frontier_research_request_id: None, - frontier_planning_request_id: None, frontier_plan_candidate_msgpack: None, - frontier_plan_mind_request_id: None, frontier_plan_mind_decision_msgpack: None, - repository_body_observation_basis: Some(body.clone()), - imagination_consideration_request_id: None, imagination_consideration_candidate_msgpack: None, - admitted_model_direction_consideration_request_id: None, admitted_model_direction_consideration_result_msgpack: None, decision_context_id: context.context_id, }; @@ -3973,7 +3787,6 @@ mod tests { .get::(&result.job_id)? .unwrap(); job.status = crate::EpiphanyRuntimeJobStatus::Completed; - job.updated_at = "2026-08-17T00:00:04Z".into(); cache.put(&job.job_id, &job)?; assert_eq!( project_current_work(&store)? @@ -4009,11 +3822,7 @@ mod tests { let (body_binding, _) = crate::load_repository_body_status(std::path::Path::new(&route.body_store_path))? .expect("runtime Body store remains authenticated"); - crate::bind_runtime_repository_domain( - &store, - "GameCult/Epiphany", - "2026-08-17T00:00:05.100Z", - )?; + crate::bind_runtime_repository_domain(&store, "GameCult/Epiphany")?; let direction_request = crate::commit_admitted_model_direction_consideration_request( &store, "2026-08-17T00:00:05.200Z", @@ -4071,7 +3880,6 @@ mod tests { terminal: true, }; let direction_worker_result = crate::EpiphanyRuntimeRoleWorkerResult { - schema_version: crate::RUNTIME_ROLE_WORKER_RESULT_SCHEMA_VERSION.into(), result_id: "direction-worker-result".into(), job_id: direction_job_id.clone(), role_id: "imagination".into(), @@ -4091,41 +3899,15 @@ mod tests { item_error: None, metadata: Default::default(), repo_model_mutation_proposal_msgpack: None, - verification_request_id: None, - frontier_route_id: None, - repo_frontier_modeling_request_id: None, - proposal_modeling_request_id: None, - repo_frontier_research_request_id: None, - frontier_planning_request_id: None, frontier_plan_candidate_msgpack: None, - frontier_plan_mind_request_id: None, frontier_plan_mind_decision_msgpack: None, - repository_body_observation_basis: None, - imagination_consideration_request_id: None, imagination_consideration_candidate_msgpack: None, - admitted_model_direction_consideration_request_id: Some( - direction_request.request_id.clone(), - ), admitted_model_direction_consideration_result_msgpack: Some(rmp_serde::to_vec_named( &direction_result, )?), decision_context_id: direction_context.context_id.clone(), }; crate::put_runtime_role_worker_result(&store, &direction_worker_result)?; - crate::complete_runtime_job( - &store, - crate::RuntimeSpineJobResultOptions { - result_id: "direction-runtime-result".into(), - job_id: direction_job_id, - completed_at: "2026-08-17T00:00:05.500Z".into(), - verdict: "suggest".into(), - summary: direction_result.summary, - next_safe_move: "Promote through the exact proposal owner.".into(), - evidence_refs: Vec::new(), - artifact_refs: Vec::new(), - decision_context_id: Some(direction_context.context_id), - }, - )?; let mut promoted = crate::promote_autonomous_direction_options_for_modeling( &store, "GameCult/Epiphany", @@ -4172,7 +3954,7 @@ mod tests { .into_iter() .map(|racer| racer.join().expect("proposal launch racer panicked")) .collect::>(); - let proposal_launch = race_results + let proposal_job_id = race_results .iter() .find_map(|result| result.as_ref().ok()) .cloned() @@ -4180,15 +3962,11 @@ mod tests { assert!(race_results.iter().all(|result| { result .as_ref() - .map(|binding| binding == &proposal_launch) + .map(|job_id| job_id == &proposal_job_id) .unwrap_or(true) })); assert_eq!( - proposal_launch.proposal_modeling_request_id, - request.request_id - ); - assert_eq!( - proposal_launch.job_id, + proposal_job_id, format!("proposal-modeling-{}-attempt-0", request.request_id) ); assert_eq!( @@ -4208,19 +3986,12 @@ mod tests { ) .is_err() ); - crate::complete_runtime_job( + crate::terminalize_runtime_job( &store, - crate::RuntimeSpineJobResultOptions { - result_id: format!("runtime-result-failed-{}", proposal_launch.job_id), - job_id: proposal_launch.job_id.clone(), - completed_at: "2026-08-17T00:00:09.100Z".into(), - verdict: "failed".into(), - summary: "provider returned no terminal decision".into(), - next_safe_move: "retry from the same exact obligation".into(), - evidence_refs: Vec::new(), - artifact_refs: Vec::new(), - decision_context_id: None, - }, + &proposal_job_id, + crate::EpiphanyRuntimeJobStatus::Failed, + "2026-08-17T00:00:09.100Z", + &format!("worker-runtime-failure-{proposal_job_id}"), )?; let proposal_retry_projection = project_current_work(&store)?; assert_eq!( @@ -4230,7 +4001,7 @@ mod tests { .map(|work| (work.attempt.action, work.attempt.job_id.as_deref())), Some(( EpiphanyAgentPassContinuationAction::Launch, - Some(proposal_launch.job_id.as_str()), + Some(proposal_job_id.as_str()), )) ); assert_ne!( @@ -4251,23 +4022,29 @@ mod tests { )?, "unchanged proposal failure must remain idempotent" ); - let proposal_launch = launch_current_proposal_modeling_work( + let proposal_job_id = launch_current_proposal_modeling_work( &store, EpiphanyProposalModelingLaunchOptions { created_at: "2026-08-17T00:00:09.200Z".into(), }, )?; assert_eq!( - proposal_launch.job_id, + proposal_job_id, format!("proposal-modeling-{}-attempt-1", request.request_id) ); let mut final_cache = crate::runtime_spine_cache(&store)?; final_cache.pull_all_backing_stores()?; assert_eq!( final_cache - .get_all::()? + .get_all::()? .into_iter() - .filter(|binding| binding.proposal_modeling_request_id == request.request_id) + .map( + |launch| crate::RuntimeTypedRequestRef::ProposalModeling(&request.request_id) + .matches_launch(&launch) + ) + .collect::>>()? + .into_iter() + .filter(|matches| *matches) .count(), 2 ); @@ -4278,7 +4055,7 @@ mod tests { .all(|envelope| envelope.r#type != "epiphany.thread_state") ); let proposal_runtime_launch = final_cache - .get::(&proposal_launch.job_id)? + .get::(&proposal_job_id)? .expect("proposal runtime launch"); let proposal_document = proposal_runtime_launch.launch_document()?; let crate::EpiphanyWorkerLaunchDocument::Role(proposal_document) = proposal_document else { @@ -4302,7 +4079,7 @@ mod tests { "model", ); proposal_native.reasoning_basis_id = Some(proposal_reasoning_basis.basis_id.clone()); - proposal_native.source_worker_job_id = Some(proposal_launch.job_id.clone()); + proposal_native.source_worker_job_id = Some(proposal_job_id.clone()); let proposal_context = crate::EpiphanyDecisionContext::new( &proposal_reasoning_basis, proposal_native, @@ -4311,7 +4088,7 @@ mod tests { final_cache.put(&proposal_context.context_id, &proposal_context)?; let result_id = "proposal-result"; let mutation = crate::EpiphanyRepoModelMutationProposal::new( - format!("repo-model-mutation-proposal-{}", proposal_launch.job_id), + format!("repo-model-mutation-proposal-{proposal_job_id}"), request.request_id.clone(), result_id, vec![proposal.proposal_id.clone()], @@ -4333,9 +4110,8 @@ mod tests { }], )?; let proposal_result = crate::EpiphanyRuntimeRoleWorkerResult { - schema_version: crate::RUNTIME_ROLE_WORKER_RESULT_SCHEMA_VERSION.into(), result_id: result_id.into(), - job_id: proposal_launch.job_id.clone(), + job_id: proposal_job_id.clone(), role_id: "modeling".into(), verdict: "checkpoint-update-needed".into(), summary: "proposal modeled".into(), @@ -4353,19 +4129,9 @@ mod tests { item_error: None, metadata: Default::default(), repo_model_mutation_proposal_msgpack: Some(rmp_serde::to_vec_named(&mutation)?), - verification_request_id: None, - frontier_route_id: None, - repo_frontier_modeling_request_id: None, - proposal_modeling_request_id: Some(request.request_id.clone()), - repo_frontier_research_request_id: None, - frontier_planning_request_id: None, frontier_plan_candidate_msgpack: None, - frontier_plan_mind_request_id: None, frontier_plan_mind_decision_msgpack: None, - repository_body_observation_basis: Some(body.clone()), - imagination_consideration_request_id: None, imagination_consideration_candidate_msgpack: None, - admitted_model_direction_consideration_request_id: None, admitted_model_direction_consideration_result_msgpack: None, decision_context_id: proposal_context.context_id.clone(), }; @@ -4378,35 +4144,21 @@ mod tests { let activation_token = "proposal-activation"; crate::claim_runtime_worker_process( &store, - &proposal_launch.job_id, + &proposal_job_id, &process, &format!("{:x}", Sha256::digest(activation_token.as_bytes())), "2026-08-17T00:00:10Z", )?; crate::activate_runtime_worker_process( &store, - &proposal_launch.job_id, + &proposal_job_id, &process, activation_token, "2026-08-17T00:00:11Z", )?; crate::put_runtime_role_worker_result(&store, &proposal_result)?; - crate::complete_runtime_job( - &store, - crate::RuntimeSpineJobResultOptions { - result_id: format!("runtime-result-{}", proposal_launch.job_id), - job_id: proposal_launch.job_id.clone(), - completed_at: "2026-08-17T00:00:12Z".into(), - verdict: proposal_result.verdict.clone(), - summary: proposal_result.summary.clone(), - next_safe_move: proposal_result.next_safe_move.clone(), - evidence_refs: proposal_result.evidence_ids.clone(), - artifact_refs: Vec::new(), - decision_context_id: Some(proposal_context.context_id.clone()), - }, - )?; assert_eq!( - crate::runtime_worker_process_claim(&store, &proposal_launch.job_id)? + crate::runtime_worker_process_claim(&store, &proposal_job_id)? .expect("structured Modeling result terminalizes its process claim") .status, crate::WorkerProcessStatus::TerminalResult.as_str() @@ -4419,11 +4171,8 @@ mod tests { .action, EpiphanyAgentPassContinuationAction::Review ); - let proposal_outcome = accept_proposal_modeling_result( - &store, - &proposal_launch.job_id, - "2026-08-17T00:00:13Z", - )?; + let proposal_outcome = + accept_proposal_modeling_result(&store, &proposal_job_id, "2026-08-17T00:00:13Z")?; let EpiphanyAgentPassAdmissionOutcome::Refused { refusal, commit: refusal_commit, @@ -4457,23 +4206,23 @@ mod tests { ); assert_eq!( refused_work.attempt.job_id.as_deref(), - Some(proposal_launch.job_id.as_str()) + Some(proposal_job_id.as_str()) ); - let proposal_launch = launch_current_proposal_modeling_work( + let proposal_job_id = launch_current_proposal_modeling_work( &store, EpiphanyProposalModelingLaunchOptions { created_at: "2026-08-17T00:00:13.100Z".into(), }, )?; assert_eq!( - proposal_launch.job_id, + proposal_job_id, format!("proposal-modeling-{}-attempt-2", request.request_id) ); let mut retry_cache = crate::runtime_spine_cache(&store)?; retry_cache.pull_all_backing_stores()?; let retry_launch = retry_cache - .get::(&proposal_launch.job_id)? + .get::(&proposal_job_id)? .expect("refused proposal retry launch"); let crate::EpiphanyWorkerLaunchDocument::Role(retry_document) = retry_launch.launch_document()? @@ -4494,12 +4243,12 @@ mod tests { "model", ); retry_native.reasoning_basis_id = Some(retry_basis.basis_id.clone()); - retry_native.source_worker_job_id = Some(proposal_launch.job_id.clone()); + retry_native.source_worker_job_id = Some(proposal_job_id.clone()); let retry_context = crate::EpiphanyDecisionContext::new(&retry_basis, retry_native, Vec::new())?; retry_cache.put(&retry_context.context_id, &retry_context)?; let valid_mutation = crate::EpiphanyRepoModelMutationProposal::new( - format!("repo-model-mutation-proposal-{}", proposal_launch.job_id), + format!("repo-model-mutation-proposal-{proposal_job_id}"), request.request_id.clone(), "proposal-result-retry", vec![proposal.proposal_id.clone()], @@ -4521,7 +4270,7 @@ mod tests { )?; let mut proposal_result = proposal_result; proposal_result.result_id = "proposal-result-retry".into(); - proposal_result.job_id = proposal_launch.job_id.clone(); + proposal_result.job_id = proposal_job_id.clone(); proposal_result.repo_model_mutation_proposal_msgpack = Some(rmp_serde::to_vec_named(&valid_mutation)?); proposal_result.decision_context_id = retry_context.context_id.clone(); @@ -4534,33 +4283,19 @@ mod tests { let retry_activation = "proposal-retry-activation"; crate::claim_runtime_worker_process( &store, - &proposal_launch.job_id, + &proposal_job_id, &retry_process, &format!("{:x}", Sha256::digest(retry_activation.as_bytes())), "2026-08-17T00:00:13.200Z", )?; crate::activate_runtime_worker_process( &store, - &proposal_launch.job_id, + &proposal_job_id, &retry_process, retry_activation, "2026-08-17T00:00:13.300Z", )?; crate::put_runtime_role_worker_result(&store, &proposal_result)?; - crate::complete_runtime_job( - &store, - crate::RuntimeSpineJobResultOptions { - result_id: format!("runtime-result-{}", proposal_launch.job_id), - job_id: proposal_launch.job_id.clone(), - completed_at: "2026-08-17T00:00:13.400Z".into(), - verdict: proposal_result.verdict.clone(), - summary: proposal_result.summary.clone(), - next_safe_move: proposal_result.next_safe_move.clone(), - evidence_refs: proposal_result.evidence_ids.clone(), - artifact_refs: Vec::new(), - decision_context_id: Some(retry_context.context_id), - }, - )?; assert_eq!( project_current_work(&store)? .proposal_modeling @@ -4569,11 +4304,8 @@ mod tests { .action, EpiphanyAgentPassContinuationAction::Review ); - let proposal_outcome = accept_proposal_modeling_result( - &store, - &proposal_launch.job_id, - "2026-08-17T00:00:14Z", - )?; + let proposal_outcome = + accept_proposal_modeling_result(&store, &proposal_job_id, "2026-08-17T00:00:14Z")?; let EpiphanyAgentPassAdmissionOutcome::Committed(proposal_commit) = proposal_outcome else { panic!("corrected proposal Modeling result must commit") }; @@ -4603,7 +4335,6 @@ mod tests { Sha256::digest(rmp_serde::to_vec_named(&frontier_item)?) ); let route = crate::RepoFrontierRoute { - schema_version: crate::REPO_FRONTIER_ROUTE_SCHEMA_VERSION.into(), route_id: "route-proposal-frontier".into(), next_organ: crate::RepoFrontierNextOrgan::Hands, model_projection_digest: model.projection_digest.clone(), @@ -4617,7 +4348,6 @@ mod tests { authorized_paths: frontier_item.repository_scope.clone(), adopted_plan: frontier_item.adopted_plan.clone(), selected_at: "2026-08-17T00:00:14Z".into(), - contract: crate::REPO_FRONTIER_ROUTE_CONTRACT.into(), }; accepted_cache.put(&route.route_id, &route)?; @@ -4630,82 +4360,57 @@ mod tests { ); crate::put_substrate_gate_repo_access_grant_receipt(&store, &grant)?; let intent = crate::HandsActionIntent { - schema_version: crate::HANDS_ACTION_INTENT_SCHEMA_VERSION.into(), intent_id: "hands-intent-verification-fixture".into(), runtime_job_id: hands_job_id.into(), - binding_id: "implementation-worker".into(), - role: "epiphany-hands".into(), - authority_scope: "epiphany.role.implementation".into(), - requested_action: "continueImplementation".into(), requested_paths: route.authorized_paths.clone(), substrate_gate_grant_receipt_id: grant.receipt_id.clone(), - requested_at: "2026-08-17T00:00:14.200Z".into(), - contract: "The exact Hands consequence under Verification.".into(), - frontier_route_id: String::new(), - plan_candidate_sha256: String::new(), - plan_action: String::new(), }; crate::put_hands_action_intent(&store, &intent)?; - let mut hands_review = crate::hands_action_review_for_intent( - "hands-review-verification-fixture".into(), - &intent, - "approved".into(), - vec!["patch".into(), "command".into(), "commit".into()], - vec!["Bounded fixture consequence is authorized.".into()], - "2026-08-17T00:00:14.300Z".into(), - ); - hands_review.required_receipts = vec![ - crate::HANDS_PATCH_RECEIPT_TYPE.into(), - crate::HANDS_COMMAND_RECEIPT_TYPE.into(), - crate::HANDS_COMMIT_RECEIPT_TYPE.into(), - ]; - crate::put_hands_action_review(&store, &hands_review)?; let hands_authority = crate::RepoFrontierHandsAuthority { - schema_version: crate::REPO_FRONTIER_HANDS_AUTHORITY_SCHEMA_VERSION.into(), authority_id: "repo-frontier-hands-authority-verification-fixture".into(), route_id: route.route_id.clone(), - model_projection_digest: route.model_projection_digest.clone(), - model_source_documents: route.model_source_documents.clone(), - frontier_item_id: route.frontier_item_id.clone(), - frontier_item_hash: route.frontier_item_hash.clone(), hands_intent_id: intent.intent_id.clone(), - hands_review_id: hands_review.review_id.clone(), substrate_grant_receipt_id: grant.receipt_id.clone(), - requested_paths: route.authorized_paths.clone(), - granted_at: hands_review.reviewed_at.clone(), - contract: crate::REPO_FRONTIER_HANDS_AUTHORITY_CONTRACT.into(), }; crate::put_repo_frontier_hands_authority(&store, &hands_authority)?; - let hands_patch = crate::hands_patch_receipt_for_review( - "hands-patch-verification-fixture".into(), - &intent, - &hands_review, - route.authorized_paths.clone(), - "Applied the exact bounded change.".into(), - "2026-08-17T00:00:14.400Z".into(), - ); + let hands_patch = crate::HandsPatchReceipt { + receipt_id: "hands-patch-verification-fixture".into(), + intent_id: intent.intent_id.clone(), + changed_paths: route.authorized_paths.clone(), + summary: "Applied the exact bounded change.".into(), + emitted_at: "2026-08-17T00:00:14.400Z".into(), + }; crate::put_hands_patch_receipt(&store, &hands_patch)?; - let hands_command = crate::hands_command_receipt_for_review( - "hands-command-verification-fixture".into(), - &intent, - &hands_review, - "cargo test exact-verification-fixture".into(), - "0".into(), - "verification.stdout".into(), - "verification.stderr".into(), - "The bounded command passed.".into(), - "2026-08-17T00:00:14.500Z".into(), - ); + let hands_command = crate::HandsCommandReceipt { + receipt_id: "hands-command-verification-fixture".into(), + intent_id: intent.intent_id.clone(), + command: "cargo test exact-verification-fixture".into(), + exit_code: "0".into(), + stdout_artifact: "verification.stdout".into(), + stderr_artifact: "verification.stderr".into(), + summary: "The bounded command passed.".into(), + emitted_at: "2026-08-17T00:00:14.500Z".into(), + }; crate::put_hands_command_receipt(&store, &hands_command)?; - let hands_commit = crate::hands_commit_receipt_for_review( - "hands-commit-verification-fixture".into(), - &intent, - &hands_review, - "0123456789abcdef0123456789abcdef01234567".into(), - "codex/verification-fixture".into(), - route.authorized_paths.clone(), - "Committed the exact bounded consequence.".into(), - "2026-08-17T00:00:14.600Z".into(), + let hands_commit = crate::HandsCommitReceipt { + receipt_id: "hands-commit-verification-fixture".into(), + intent_id: intent.intent_id.clone(), + commit_sha: "0123456789abcdef0123456789abcdef01234567".into(), + branch: "codex/verification-fixture".into(), + changed_paths: route.authorized_paths.clone(), + summary: "Committed the exact bounded consequence.".into(), + emitted_at: "2026-08-17T00:00:14.600Z".into(), + }; + let ambiguous_hands_store = temp.path().join("ambiguous-hands.cc"); + std::fs::copy(&store, &ambiguous_hands_store)?; + let mut duplicate_patch = hands_patch.clone(); + duplicate_patch.receipt_id = "hands-patch-verification-fixture-duplicate".into(); + crate::put_hands_patch_receipt(&ambiguous_hands_store, &duplicate_patch)?; + assert!( + crate::put_hands_commit_receipt(&ambiguous_hands_store, &hands_commit) + .unwrap_err() + .to_string() + .contains("one exact patch receipt") ); let persona_cultmesh = temp.path().join("persona-concurrency-cultmesh.cc"); crate::write_epiphany_cultmesh_swarm_brake( @@ -4808,12 +4513,6 @@ mod tests { let verification_launch = accepted_cache .get::(&verification_job_id)? .expect("exact Verification launch"); - assert_eq!( - verification_launch - .repo_frontier_verification_request_id - .as_deref(), - Some(verification_request.request_id.as_str()) - ); let crate::EpiphanyWorkerLaunchDocument::Role(verification_document) = verification_launch.launch_document()? else { @@ -4841,22 +4540,11 @@ mod tests { verification_native.source_worker_job_id = Some(verification_job_id.clone()); crate::open_runtime_model_execution( &store, - crate::RuntimeSpineSessionOptions { - session_id: "verification-model-session".into(), - objective: "Verify the exact Hands consequence.".into(), - created_at: "2026-08-17T00:00:14.750Z".into(), - coordinator_note: "Bound Verification model pass.".into(), - }, crate::RuntimeSpineJobOptions { job_id: "verification-model-job".into(), session_id: "verification-model-session".into(), - role: "openai-model".into(), - created_at: "2026-08-17T00:00:14.750Z".into(), - summary: "Bound Verification inference.".into(), - artifact_refs: Vec::new(), }, &verification_native, - "2026-08-17T00:00:14.750Z", )?; let verification_context = crate::EpiphanyDecisionContext::new( &verification_basis, @@ -4886,7 +4574,6 @@ mod tests { "2026-08-17T00:00:14.900Z", )?; let verification_result = crate::EpiphanyRuntimeRoleWorkerResult { - schema_version: crate::RUNTIME_ROLE_WORKER_RESULT_SCHEMA_VERSION.into(), result_id: "verification-result".into(), job_id: verification_job_id.clone(), role_id: "verification".into(), @@ -4906,37 +4593,13 @@ mod tests { item_error: None, metadata: Default::default(), repo_model_mutation_proposal_msgpack: None, - verification_request_id: Some(verification_request.request_id.clone()), - frontier_route_id: Some(route.route_id.clone()), - repo_frontier_modeling_request_id: None, - proposal_modeling_request_id: None, - repo_frontier_research_request_id: None, - frontier_planning_request_id: None, frontier_plan_candidate_msgpack: None, - frontier_plan_mind_request_id: None, frontier_plan_mind_decision_msgpack: None, - repository_body_observation_basis: None, - imagination_consideration_request_id: None, imagination_consideration_candidate_msgpack: None, - admitted_model_direction_consideration_request_id: None, admitted_model_direction_consideration_result_msgpack: None, decision_context_id: verification_context.context_id.clone(), }; crate::put_runtime_role_worker_result(&store, &verification_result)?; - crate::complete_runtime_job( - &store, - crate::RuntimeSpineJobResultOptions { - result_id: format!("runtime-result-{verification_job_id}"), - job_id: verification_job_id.clone(), - completed_at: "2026-08-17T00:00:15Z".into(), - verdict: verification_result.verdict.clone(), - summary: verification_result.summary.clone(), - next_safe_move: verification_result.next_safe_move.clone(), - evidence_refs: verification_result.evidence_ids.clone(), - artifact_refs: Vec::new(), - decision_context_id: Some(verification_context.context_id.clone()), - }, - )?; assert_eq!( project_current_work(&store)? .verification @@ -5232,7 +4895,6 @@ mod tests { }], )?; let verdict_result = crate::EpiphanyRuntimeRoleWorkerResult { - schema_version: crate::RUNTIME_ROLE_WORKER_RESULT_SCHEMA_VERSION.into(), result_id: verdict_result_id.into(), job_id: verdict_job_id.clone(), role_id: "modeling".into(), @@ -5252,19 +4914,9 @@ mod tests { item_error: None, metadata: Default::default(), repo_model_mutation_proposal_msgpack: Some(rmp_serde::to_vec_named(&verdict_mutation)?), - verification_request_id: None, - frontier_route_id: None, - repo_frontier_modeling_request_id: Some(verdict_request.request_id.clone()), - proposal_modeling_request_id: None, - repo_frontier_research_request_id: None, - frontier_planning_request_id: None, frontier_plan_candidate_msgpack: None, - frontier_plan_mind_request_id: None, frontier_plan_mind_decision_msgpack: None, - repository_body_observation_basis: Some(body.clone()), - imagination_consideration_request_id: None, imagination_consideration_candidate_msgpack: None, - admitted_model_direction_consideration_request_id: None, admitted_model_direction_consideration_result_msgpack: None, decision_context_id: verdict_context.context_id.clone(), }; @@ -5290,20 +4942,6 @@ mod tests { "2026-08-17T00:00:19Z", )?; crate::put_runtime_role_worker_result(&store, &verdict_result)?; - crate::complete_runtime_job( - &store, - crate::RuntimeSpineJobResultOptions { - result_id: format!("runtime-result-{verdict_job_id}"), - job_id: verdict_job_id.clone(), - completed_at: "2026-08-17T00:00:20Z".into(), - verdict: verdict_result.verdict.clone(), - summary: verdict_result.summary.clone(), - next_safe_move: verdict_result.next_safe_move.clone(), - evidence_refs: verdict_result.evidence_ids.clone(), - artifact_refs: Vec::new(), - decision_context_id: Some(verdict_context.context_id.clone()), - }, - )?; assert_eq!( project_current_work(&store)? .frontier_verdict_modeling @@ -5506,8 +5144,16 @@ mod tests { let planning_launch = planning_cache .get::(&planning_job)? .expect("exact Planning launch"); + let crate::EpiphanyWorkerLaunchDocument::Role(planning_document) = + planning_launch.launch_document()? + else { + panic!("Planning must launch as a role pass") + }; assert_eq!( - planning_launch.frontier_planning_request_id.as_deref(), + planning_document + .frontier_planning_context + .as_ref() + .map(|context| context.request_id.as_str()), Some(planning_request.request_id.as_str()) ); let planning_basis = crate::worker_reasoning_basis(&store, &planning_launch)?; @@ -5527,28 +5173,16 @@ mod tests { )?); crate::open_runtime_model_execution( &store, - crate::RuntimeSpineSessionOptions { - session_id: "planning-model-session".into(), - objective: "Propose the exact frontier plan.".into(), - created_at: "2026-08-17T00:00:21.750Z".into(), - coordinator_note: "Bound Planning model pass.".into(), - }, crate::RuntimeSpineJobOptions { job_id: "planning-model-job".into(), session_id: "planning-model-session".into(), - role: "openai-model".into(), - created_at: "2026-08-17T00:00:21.750Z".into(), - summary: "Bound Planning inference.".into(), - artifact_refs: Vec::new(), }, &planning_native, - "2026-08-17T00:00:21.750Z", )?; let planning_context = crate::EpiphanyDecisionContext::new(&planning_basis, planning_native, Vec::new())?; crate::put_decision_context(&store, &planning_context)?; let mut candidate = crate::RepoFrontierPlanCandidate { - schema_version: crate::REPO_FRONTIER_PLAN_CANDIDATE_SCHEMA_VERSION.into(), candidate_id: "pending".into(), planning_request_id: planning_request.request_id.clone(), model_projection_digest: planning_request.model_projection_digest.clone(), @@ -5563,11 +5197,9 @@ mod tests { rollback_steps: vec!["revert the keyed Planning commit".into()], commit_message: "Migrate Planning to keyed Mind authority".into(), proposed_at: "2026-08-17T00:00:21.800Z".into(), - contract: crate::REPO_FRONTIER_PLANNING_CONTRACT.into(), }; candidate.candidate_id = crate::canonical_repo_frontier_plan_candidate_id(&candidate)?; let planning_result = crate::EpiphanyRuntimeRoleWorkerResult { - schema_version: crate::RUNTIME_ROLE_WORKER_RESULT_SCHEMA_VERSION.into(), result_id: "planning-result".into(), job_id: planning_job.clone(), role_id: "imagination".into(), @@ -5587,19 +5219,9 @@ mod tests { item_error: None, metadata: Default::default(), repo_model_mutation_proposal_msgpack: None, - verification_request_id: None, - frontier_route_id: None, - repo_frontier_modeling_request_id: None, - proposal_modeling_request_id: None, - repo_frontier_research_request_id: None, - frontier_planning_request_id: Some(planning_request.request_id.clone()), frontier_plan_candidate_msgpack: Some(rmp_serde::to_vec_named(&candidate)?), - frontier_plan_mind_request_id: None, frontier_plan_mind_decision_msgpack: None, - repository_body_observation_basis: None, - imagination_consideration_request_id: None, imagination_consideration_candidate_msgpack: None, - admitted_model_direction_consideration_request_id: None, admitted_model_direction_consideration_result_msgpack: None, decision_context_id: planning_context.context_id.clone(), }; @@ -5625,20 +5247,6 @@ mod tests { "2026-08-17T00:00:21.950Z", )?; crate::put_runtime_role_worker_result(&store, &planning_result)?; - crate::complete_runtime_job( - &store, - crate::RuntimeSpineJobResultOptions { - result_id: format!("runtime-result-{planning_job}"), - job_id: planning_job.clone(), - completed_at: "2026-08-17T00:00:22Z".into(), - verdict: planning_result.verdict.clone(), - summary: planning_result.summary.clone(), - next_safe_move: planning_result.next_safe_move.clone(), - evidence_refs: planning_result.evidence_ids.clone(), - artifact_refs: Vec::new(), - decision_context_id: Some(planning_context.context_id.clone()), - }, - )?; let mind_request = crate::commit_repo_frontier_plan_mind_request( &store, &planning_result.result_id, @@ -5649,8 +5257,16 @@ mod tests { let mind_launch = planning_cache .get::(&mind_job)? .expect("exact plan Mind launch"); + let crate::EpiphanyWorkerLaunchDocument::Role(mind_document) = + mind_launch.launch_document()? + else { + panic!("plan Mind must launch as a role pass") + }; assert_eq!( - mind_launch.frontier_plan_mind_request_id.as_deref(), + mind_document + .frontier_plan_mind_context + .as_ref() + .map(|context| context.request.request_id.as_str()), Some(mind_request.request_id.as_str()) ); let mind_basis = crate::worker_reasoning_basis(&store, &mind_launch)?; @@ -5670,22 +5286,11 @@ mod tests { )?); crate::open_runtime_model_execution( &store, - crate::RuntimeSpineSessionOptions { - session_id: "plan-mind-model-session".into(), - objective: "Adjudicate the exact frontier plan.".into(), - created_at: "2026-08-17T00:00:22.250Z".into(), - coordinator_note: "Bound plan Mind model pass.".into(), - }, crate::RuntimeSpineJobOptions { job_id: "plan-mind-model-job".into(), session_id: "plan-mind-model-session".into(), - role: "openai-model".into(), - created_at: "2026-08-17T00:00:22.250Z".into(), - summary: "Bound plan Mind inference.".into(), - artifact_refs: Vec::new(), }, &mind_native, - "2026-08-17T00:00:22.250Z", )?; let mind_context = crate::EpiphanyDecisionContext::new(&mind_basis, mind_native, Vec::new())?; @@ -5701,7 +5306,6 @@ mod tests { decided_at: "2026-08-17T00:00:22.300Z".into(), }; let mind_result = crate::EpiphanyRuntimeRoleWorkerResult { - schema_version: crate::RUNTIME_ROLE_WORKER_RESULT_SCHEMA_VERSION.into(), result_id: "plan-mind-result".into(), job_id: mind_job.clone(), role_id: "mindAdmissionReview".into(), @@ -5721,19 +5325,9 @@ mod tests { item_error: None, metadata: Default::default(), repo_model_mutation_proposal_msgpack: None, - verification_request_id: None, - frontier_route_id: None, - repo_frontier_modeling_request_id: None, - proposal_modeling_request_id: None, - repo_frontier_research_request_id: None, - frontier_planning_request_id: None, frontier_plan_candidate_msgpack: None, - frontier_plan_mind_request_id: Some(mind_request.request_id.clone()), frontier_plan_mind_decision_msgpack: Some(rmp_serde::to_vec_named(&mind_decision)?), - repository_body_observation_basis: None, - imagination_consideration_request_id: None, imagination_consideration_candidate_msgpack: None, - admitted_model_direction_consideration_request_id: None, admitted_model_direction_consideration_result_msgpack: None, decision_context_id: mind_context.context_id.clone(), }; @@ -5759,20 +5353,6 @@ mod tests { "2026-08-17T00:00:22.450Z", )?; crate::put_runtime_role_worker_result(&store, &mind_result)?; - crate::complete_runtime_job( - &store, - crate::RuntimeSpineJobResultOptions { - result_id: format!("runtime-result-{mind_job}"), - job_id: mind_job.clone(), - completed_at: "2026-08-17T00:00:22.500Z".into(), - verdict: mind_result.verdict.clone(), - summary: mind_result.summary.clone(), - next_safe_move: mind_result.next_safe_move.clone(), - evidence_refs: mind_result.evidence_ids.clone(), - artifact_refs: Vec::new(), - decision_context_id: Some(mind_context.context_id.clone()), - }, - )?; let plan_decision = crate::commit_repo_frontier_plan_decision(&store, &mind_result.result_id)?; assert_eq!( @@ -5869,13 +5449,11 @@ mod tests { let research_launch = research_cache .get::(&research_job)? .expect("exact Research launch"); - let research_request_id = research_launch - .repo_frontier_research_request_id - .clone() - .expect("exact Research request identity"); - let research_request = research_cache - .get::(&research_request_id)? - .expect("exact Research request"); + let research_request = crate::runtime_spine::frontier_research_request_for_launch( + &research_cache, + &research_launch, + )? + .expect("exact Research request"); assert_eq!(research_request.frontier_item_id, eyes_frontier.id); assert!( research_request.frontier_authority_documents.len() @@ -5901,12 +5479,12 @@ mod tests { research_cache.put(&research_context.context_id, &research_context)?; let stale_research_store = temp.path().join("stale-research-output.cc"); std::fs::copy(&store, &stale_research_store)?; - let evidence = epiphany_state_model::EpiphanyEvidenceRecord { + let evidence = crate::EpiphanyEvidenceRecord { id: "research-evidence-1".into(), kind: "external-source".into(), status: "ok".into(), summary: "The bounded external claim is supported.".into(), - code_refs: vec![epiphany_state_model::EpiphanyCodeRef { + code_refs: vec![crate::EpiphanyCodeRef { path: "epiphany-core/src/current_work.rs".into(), start_line: Some(1), end_line: None, @@ -5914,7 +5492,7 @@ mod tests { note: None, }], }; - let observation = epiphany_state_model::EpiphanyObservation { + let observation = crate::EpiphanyObservation { id: "research-observation-1".into(), summary: "External evidence answers the exact frontier question.".into(), source_kind: "research".into(), @@ -5928,7 +5506,6 @@ mod tests { investigation_checkpoint: None, }; let research_result = crate::EpiphanyRuntimeRoleWorkerResult { - schema_version: crate::RUNTIME_ROLE_WORKER_RESULT_SCHEMA_VERSION.into(), result_id: "research-result".into(), job_id: research_job.clone(), role_id: "research".into(), @@ -5948,19 +5525,9 @@ mod tests { item_error: None, metadata: Default::default(), repo_model_mutation_proposal_msgpack: None, - verification_request_id: None, - frontier_route_id: None, - repo_frontier_modeling_request_id: None, - proposal_modeling_request_id: None, - repo_frontier_research_request_id: Some(research_request_id.clone()), - frontier_planning_request_id: None, frontier_plan_candidate_msgpack: None, - frontier_plan_mind_request_id: None, frontier_plan_mind_decision_msgpack: None, - repository_body_observation_basis: None, - imagination_consideration_request_id: None, imagination_consideration_candidate_msgpack: None, - admitted_model_direction_consideration_request_id: None, admitted_model_direction_consideration_result_msgpack: None, decision_context_id: research_context.context_id.clone(), }; @@ -5986,20 +5553,6 @@ mod tests { "2026-08-17T00:00:25Z", )?; crate::put_runtime_role_worker_result(&store, &research_result)?; - crate::complete_runtime_job( - &store, - crate::RuntimeSpineJobResultOptions { - result_id: format!("runtime-result-{research_job}"), - job_id: research_job.clone(), - completed_at: "2026-08-17T00:00:26Z".into(), - verdict: research_result.verdict.clone(), - summary: research_result.summary.clone(), - next_safe_move: research_result.next_safe_move.clone(), - evidence_refs: research_result.evidence_ids.clone(), - artifact_refs: Vec::new(), - decision_context_id: Some(research_context.context_id.clone()), - }, - )?; assert_eq!( project_current_work(&store)?.research.continuation_action(), Some(RepoFrontierResearchContinuationAction::ReviewResearchResult) @@ -6098,20 +5651,6 @@ mod tests { "2026-08-17T00:00:27.100Z", )?; crate::put_runtime_role_worker_result(&stale_research_store, &research_result)?; - crate::complete_runtime_job( - &stale_research_store, - crate::RuntimeSpineJobResultOptions { - result_id: format!("runtime-result-{research_job}"), - job_id: research_job.clone(), - completed_at: "2026-08-17T00:00:27.200Z".into(), - verdict: research_result.verdict.clone(), - summary: research_result.summary.clone(), - next_safe_move: research_result.next_safe_move.clone(), - evidence_refs: research_result.evidence_ids.clone(), - artifact_refs: Vec::new(), - decision_context_id: Some(research_context.context_id.clone()), - }, - )?; assert_eq!( crate::runtime_role_worker_result(&stale_research_store, &research_job)? .expect("stale output remains a durable decision") @@ -6155,11 +5694,6 @@ mod tests { .get::(&observation.id)? .is_some() ); - let packet = admitted_research_cache - .get::(&format!("eyes-packet-{research_request_id}"))? - .expect("request-bound Eyes packet"); - assert_eq!(packet.research_request_id, research_request_id); - assert_eq!(packet.decision_context_id, research_context.context_id); let after_research = project_current_work(&store)?; assert!(after_research.research.continuation_action().is_none()); assert!(after_research.body_modeling.is_none()); diff --git a/epiphany-core/src/eyes_gateway.rs b/epiphany-core/src/eyes_gateway.rs deleted file mode 100644 index 221af786..00000000 --- a/epiphany-core/src/eyes_gateway.rs +++ /dev/null @@ -1,168 +0,0 @@ -use crate::EpiphanyResearchDecision; -use crate::EpiphanyRoleFindingInterpretation; -use cultcache_rs::DatabaseEntry; - -pub const EYES_SOURCE_LOOKUP_RECEIPT_TYPE: &str = "epiphany.eyes.source_lookup_receipt"; -pub const EYES_EVIDENCE_PACKET_TYPE: &str = "epiphany.eyes.evidence_packet"; -pub const EYES_SOURCE_LOOKUP_RECEIPT_SCHEMA_VERSION: &str = - "epiphany.eyes.source_lookup_receipt.v0"; -pub const EYES_EVIDENCE_PACKET_SCHEMA_VERSION: &str = "epiphany.eyes.evidence_packet.v1"; - -#[derive(Debug, Clone, PartialEq, Eq, DatabaseEntry)] -#[cultcache( - type = "epiphany.eyes.source_lookup_receipt", - schema = "EyesSourceLookupReceipt" -)] -pub struct EyesSourceLookupReceipt { - #[cultcache(key = 0)] - pub schema_version: String, - #[cultcache(key = 1)] - pub receipt_id: String, - #[cultcache(key = 2)] - pub source_job_id: String, - #[cultcache(key = 3)] - pub substrate_grant_receipt_id: String, - #[cultcache(key = 4)] - pub tool_intent_id: String, - #[cultcache(key = 5)] - pub tool_receipt_id: String, - #[cultcache(key = 6)] - pub provider: String, - #[cultcache(key = 7)] - pub repository: String, - #[cultcache(key = 8)] - pub revision: String, - #[cultcache(key = 9)] - pub path: String, - #[cultcache(key = 10)] - pub source_ref: String, - #[cultcache(key = 11)] - pub content_sha256: String, - #[cultcache(key = 12)] - pub byte_count: u64, - #[cultcache(key = 13)] - pub observed_at: String, - #[cultcache(key = 14)] - pub contract: String, -} - -#[derive(Debug, Clone, PartialEq, Eq, DatabaseEntry)] -#[cultcache(type = "epiphany.eyes.evidence_packet", schema = "EyesEvidencePacket")] -pub struct EyesEvidencePacket { - #[cultcache(key = 0)] - pub schema_version: String, - #[cultcache(key = 1)] - pub packet_id: String, - #[cultcache(key = 2)] - pub source_result_id: String, - #[cultcache(key = 3)] - pub source_job_id: String, - #[cultcache(key = 4)] - pub source_role_id: String, - #[cultcache(key = 5)] - pub evidence_ids: Vec, - #[cultcache(key = 6)] - pub observation_ids: Vec, - #[cultcache(key = 7)] - pub source_refs: Vec, - #[cultcache(key = 8)] - pub summary: String, - #[cultcache(key = 9)] - pub uncertainty: String, - #[cultcache(key = 10)] - pub emitted_at: String, - #[cultcache(key = 11)] - pub contract: String, - #[cultcache(key = 12, default)] - pub source_lookup_receipt_ids: Vec, - #[cultcache(key = 13)] - pub research_request_id: String, - #[cultcache(key = 14)] - pub decision_context_id: String, -} - -pub fn eyes_evidence_packet_from_research_finding( - packet_id: String, - research_request_id: String, - decision_context_id: String, - finding: &EpiphanyRoleFindingInterpretation, - decision: &EpiphanyResearchDecision, - source_lookups: &[EyesSourceLookupReceipt], - emitted_at: String, -) -> EyesEvidencePacket { - let evidence_ids = decision - .evidence - .iter() - .filter_map(|evidence| non_empty_string(&evidence.id)) - .collect::>(); - let observation_ids = decision - .observations - .iter() - .filter_map(|observation| non_empty_string(&observation.id)) - .collect::>(); - let mut source_refs = Vec::new(); - for evidence in &decision.evidence { - for code_ref in &evidence.code_refs { - let mut rendered = code_ref.path.display().to_string(); - if let Some(start_line) = code_ref.start_line { - rendered.push(':'); - rendered.push_str(&start_line.to_string()); - } - if let Some(end_line) = code_ref.end_line - && Some(end_line) != code_ref.start_line - { - rendered.push('-'); - rendered.push_str(&end_line.to_string()); - } - if let Some(symbol) = code_ref.symbol.as_deref().filter(|value| !value.is_empty()) { - rendered.push('#'); - rendered.push_str(symbol); - } - push_unique(&mut source_refs, rendered); - } - } - for lookup in source_lookups { - push_unique(&mut source_refs, lookup.source_ref.clone()); - } - EyesEvidencePacket { - schema_version: EYES_EVIDENCE_PACKET_SCHEMA_VERSION.to_string(), - packet_id, - source_result_id: finding.runtime_result_id.clone().unwrap_or_default(), - source_job_id: finding.runtime_job_id.clone().unwrap_or_default(), - source_role_id: "research".to_string(), - evidence_ids, - observation_ids, - source_refs, - summary: finding.summary.clone().unwrap_or_default(), - uncertainty: if finding.evidence_gaps.is_empty() && finding.risks.is_empty() { - "no declared research gaps or risks".to_string() - } else { - finding - .evidence_gaps - .iter() - .chain(finding.risks.iter()) - .cloned() - .collect::>() - .join("; ") - }, - emitted_at, - contract: "Eyes packet emitted from a reviewed Research lane finding; it makes the source-gathering evidence claim citable before Mind admission.".to_string(), - source_lookup_receipt_ids: source_lookups - .iter() - .map(|lookup| lookup.receipt_id.clone()) - .collect(), - research_request_id, - decision_context_id, - } -} - -fn non_empty_string(value: &str) -> Option { - let value = value.trim(); - (!value.is_empty()).then(|| value.to_string()) -} - -fn push_unique(out: &mut Vec, value: String) { - if !out.contains(&value) { - out.push(value); - } -} diff --git a/epiphany-core/src/hands_gateway.rs b/epiphany-core/src/hands_gateway.rs index 0e0a6820..798e701d 100644 --- a/epiphany-core/src/hands_gateway.rs +++ b/epiphany-core/src/hands_gateway.rs @@ -1,95 +1,34 @@ use cultcache_rs::DatabaseEntry; -pub const HANDS_ACTION_INTENT_TYPE: &str = "epiphany.hands.action_intent"; -pub const HANDS_ACTION_REVIEW_TYPE: &str = "epiphany.hands.action_review"; pub const HANDS_COMMAND_RECEIPT_TYPE: &str = "epiphany.hands.command_receipt"; pub const HANDS_PATCH_RECEIPT_TYPE: &str = "epiphany.hands.patch_receipt"; pub const HANDS_COMMIT_RECEIPT_TYPE: &str = "epiphany.hands.commit_receipt"; -pub const HANDS_ACTION_INTENT_SCHEMA_VERSION: &str = "epiphany.hands.action_intent.v1"; -pub const HANDS_ACTION_REVIEW_SCHEMA_VERSION: &str = "epiphany.hands.action_review.v0"; -pub const HANDS_COMMAND_RECEIPT_SCHEMA_VERSION: &str = "epiphany.hands.command_receipt.v0"; -pub const HANDS_PATCH_RECEIPT_SCHEMA_VERSION: &str = "epiphany.hands.patch_receipt.v0"; -pub const HANDS_COMMIT_RECEIPT_SCHEMA_VERSION: &str = "epiphany.hands.commit_receipt.v0"; - #[derive(Debug, Clone, PartialEq, Eq, DatabaseEntry)] #[cultcache(type = "epiphany.hands.action_intent", schema = "HandsActionIntent")] pub struct HandsActionIntent { #[cultcache(key = 0)] - pub schema_version: String, - #[cultcache(key = 1)] pub intent_id: String, - #[cultcache(key = 2)] + #[cultcache(key = 1)] pub runtime_job_id: String, - #[cultcache(key = 3)] - pub binding_id: String, - #[cultcache(key = 4)] - pub role: String, - #[cultcache(key = 5)] - pub authority_scope: String, - #[cultcache(key = 6)] - pub requested_action: String, - #[cultcache(key = 7)] - pub requested_paths: Vec, - #[cultcache(key = 8)] - pub substrate_gate_grant_receipt_id: String, - #[cultcache(key = 9)] - pub requested_at: String, - #[cultcache(key = 10)] - pub contract: String, - #[cultcache(key = 11, default)] - pub frontier_route_id: String, - #[cultcache(key = 12, default)] - pub plan_candidate_sha256: String, - #[cultcache(key = 13, default)] - pub plan_action: String, -} - -#[derive(Debug, Clone, PartialEq, Eq, DatabaseEntry)] -#[cultcache(type = "epiphany.hands.action_review", schema = "HandsActionReview")] -pub struct HandsActionReview { - #[cultcache(key = 0)] - pub schema_version: String, - #[cultcache(key = 1)] - pub review_id: String, #[cultcache(key = 2)] - pub intent_id: String, + pub requested_paths: Vec, #[cultcache(key = 3)] - pub decision: String, - #[cultcache(key = 4)] - pub allowed_operations: Vec, - #[cultcache(key = 5)] - pub required_receipts: Vec, - #[cultcache(key = 6)] - pub reasons: Vec, - #[cultcache(key = 7)] - pub reviewed_at: String, - #[cultcache(key = 8)] - pub contract: String, + pub substrate_gate_grant_receipt_id: String, } #[derive(Debug, Clone, PartialEq, Eq, DatabaseEntry)] #[cultcache(type = "epiphany.hands.patch_receipt", schema = "HandsPatchReceipt")] pub struct HandsPatchReceipt { #[cultcache(key = 0)] - pub schema_version: String, - #[cultcache(key = 1)] pub receipt_id: String, - #[cultcache(key = 2)] + #[cultcache(key = 1)] pub intent_id: String, - #[cultcache(key = 3)] - pub review_id: String, - #[cultcache(key = 4)] - pub substrate_gate_grant_receipt_id: String, - #[cultcache(key = 5)] - pub runtime_job_id: String, - #[cultcache(key = 6)] + #[cultcache(key = 2)] pub changed_paths: Vec, - #[cultcache(key = 7)] + #[cultcache(key = 3)] pub summary: String, - #[cultcache(key = 8)] + #[cultcache(key = 4)] pub emitted_at: String, - #[cultcache(key = 9)] - pub contract: String, } #[derive(Debug, Clone, PartialEq, Eq, DatabaseEntry)] @@ -99,153 +38,38 @@ pub struct HandsPatchReceipt { )] pub struct HandsCommandReceipt { #[cultcache(key = 0)] - pub schema_version: String, - #[cultcache(key = 1)] pub receipt_id: String, - #[cultcache(key = 2)] + #[cultcache(key = 1)] pub intent_id: String, - #[cultcache(key = 3)] - pub review_id: String, - #[cultcache(key = 4)] - pub substrate_gate_grant_receipt_id: String, - #[cultcache(key = 5)] - pub runtime_job_id: String, - #[cultcache(key = 6)] + #[cultcache(key = 2)] pub command: String, - #[cultcache(key = 7)] + #[cultcache(key = 3)] pub exit_code: String, - #[cultcache(key = 8)] + #[cultcache(key = 4)] pub stdout_artifact: String, - #[cultcache(key = 9)] + #[cultcache(key = 5)] pub stderr_artifact: String, - #[cultcache(key = 10)] + #[cultcache(key = 6)] pub summary: String, - #[cultcache(key = 11)] + #[cultcache(key = 7)] pub emitted_at: String, - #[cultcache(key = 12)] - pub contract: String, } #[derive(Debug, Clone, PartialEq, Eq, DatabaseEntry)] #[cultcache(type = "epiphany.hands.commit_receipt", schema = "HandsCommitReceipt")] pub struct HandsCommitReceipt { #[cultcache(key = 0)] - pub schema_version: String, - #[cultcache(key = 1)] pub receipt_id: String, - #[cultcache(key = 2)] + #[cultcache(key = 1)] pub intent_id: String, - #[cultcache(key = 3)] - pub review_id: String, - #[cultcache(key = 4)] - pub runtime_job_id: String, - #[cultcache(key = 5)] + #[cultcache(key = 2)] pub commit_sha: String, - #[cultcache(key = 6)] + #[cultcache(key = 3)] pub branch: String, - #[cultcache(key = 7)] + #[cultcache(key = 4)] pub changed_paths: Vec, - #[cultcache(key = 8)] + #[cultcache(key = 5)] pub summary: String, - #[cultcache(key = 9)] + #[cultcache(key = 6)] pub emitted_at: String, - #[cultcache(key = 10)] - pub contract: String, -} - -pub fn hands_action_review_for_intent( - review_id: String, - intent: &HandsActionIntent, - decision: String, - allowed_operations: Vec, - reasons: Vec, - reviewed_at: String, -) -> HandsActionReview { - HandsActionReview { - schema_version: HANDS_ACTION_REVIEW_SCHEMA_VERSION.to_string(), - review_id, - intent_id: intent.intent_id.clone(), - decision, - allowed_operations, - required_receipts: vec![HANDS_PATCH_RECEIPT_TYPE.to_string()], - reasons, - reviewed_at, - contract: "Hands review is the execution decision for a bounded action intent; it depends on Substrate Gate access and does not admit durable Mind state.".to_string(), - } -} - -pub fn hands_patch_receipt_for_review( - receipt_id: String, - intent: &HandsActionIntent, - review: &HandsActionReview, - changed_paths: Vec, - summary: String, - emitted_at: String, -) -> HandsPatchReceipt { - HandsPatchReceipt { - schema_version: HANDS_PATCH_RECEIPT_SCHEMA_VERSION.to_string(), - receipt_id, - intent_id: intent.intent_id.clone(), - review_id: review.review_id.clone(), - substrate_gate_grant_receipt_id: intent.substrate_gate_grant_receipt_id.clone(), - runtime_job_id: intent.runtime_job_id.clone(), - changed_paths, - summary, - emitted_at, - contract: "Hands patch receipt proves which files changed under the reviewed action and named Substrate Gate grant; Soul and Mind still decide verification and durable admission.".to_string(), - } -} - -#[allow(clippy::too_many_arguments)] -pub fn hands_command_receipt_for_review( - receipt_id: String, - intent: &HandsActionIntent, - review: &HandsActionReview, - command: String, - exit_code: String, - stdout_artifact: String, - stderr_artifact: String, - summary: String, - emitted_at: String, -) -> HandsCommandReceipt { - HandsCommandReceipt { - schema_version: HANDS_COMMAND_RECEIPT_SCHEMA_VERSION.to_string(), - receipt_id, - intent_id: intent.intent_id.clone(), - review_id: review.review_id.clone(), - substrate_gate_grant_receipt_id: intent.substrate_gate_grant_receipt_id.clone(), - runtime_job_id: intent.runtime_job_id.clone(), - command, - exit_code, - stdout_artifact, - stderr_artifact, - summary, - emitted_at, - contract: "Hands command receipt proves which command ran, where output evidence lives, and which reviewed action plus Substrate Gate grant authorized it.".to_string(), - } -} - -pub fn hands_commit_receipt_for_review( - receipt_id: String, - intent: &HandsActionIntent, - review: &HandsActionReview, - commit_sha: String, - branch: String, - changed_paths: Vec, - summary: String, - emitted_at: String, -) -> HandsCommitReceipt { - HandsCommitReceipt { - schema_version: HANDS_COMMIT_RECEIPT_SCHEMA_VERSION.to_string(), - receipt_id, - intent_id: intent.intent_id.clone(), - review_id: review.review_id.clone(), - runtime_job_id: intent.runtime_job_id.clone(), - commit_sha, - branch, - changed_paths, - summary, - emitted_at, - contract: "Hands commit receipt proves a repository commit consequence after a reviewed action; it is still subject to Soul verification and Mind admission.".to_string(), - } } diff --git a/epiphany-core/src/idunn_provider_health.rs b/epiphany-core/src/idunn_provider_health.rs deleted file mode 100644 index fa5e01af..00000000 --- a/epiphany-core/src/idunn_provider_health.rs +++ /dev/null @@ -1,1046 +0,0 @@ -use std::collections::{BTreeMap, BTreeSet}; -use std::path::Path; - -use anyhow::{Context, Result, anyhow, bail}; -use cultcache_rs::{ - CultCache, CultCacheEnvelope, DatabaseEntry, SingleFileMessagePackBackingStore, -}; -use cultnet_rs::{ - CultNetRawDocumentRecord, CultNetRawPayloadEncoding, CultNetRawSnapshotQuery, - CultNetSnapshotRecordExpectation, CultNetSnapshotSourceExpectation, - GameCultServiceTrustAnchorRecord, IDUNN_AUTHENTICATED_PROVIDER_HEALTH_PROJECTION_SCHEMA, - IDUNN_AUTHENTICATED_PROVIDER_HEALTH_PROJECTION_SIGNING_PURPOSE, - IdunnAuthenticatedProviderHealthProjectionPurpose, - IdunnAuthenticatedProviderHealthProjectionRecord, IdunnServiceIdentity, - ServiceIdentitySignature, verify_service_identity_signature_with_public_key, -}; -use sha2::{Digest, Sha256}; - -pub const EPIPHANY_IDUNN_PROVIDER_HEALTH_ADMISSION_SCHEMA: &str = - "epiphany.idunn_provider_health_admission.v1"; -pub const EPIPHANY_IDUNN_PROVIDER_HEALTH_ADMISSION_TYPE: &str = - "epiphany.idunn_provider_health_admission"; - -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum ProviderReleaseBinding { - Forbidden, - Exact { - release_id: String, - release_witness_sha256: String, - source_commit: String, - deployment_id: String, - }, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct RequiredProviderHealth { - pub daemon_id: String, - pub health_contract: String, - pub release_binding: ProviderReleaseBinding, -} - -impl RequiredProviderHealth { - pub fn record_key(&self) -> String { - provider_health_record_key(&self.daemon_id, &self.health_contract) - } -} - -#[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)] -#[cultcache( - type = "epiphany.idunn_provider_health_admission", - schema = "epiphany.idunn_provider_health_admission.v1" -)] -pub struct IdunnProviderHealthAdmission { - #[cultcache(key = 0)] - pub schema_version: String, - #[cultcache(key = 1)] - pub admission_id: String, - #[cultcache(key = 2)] - pub daemon_id: String, - #[cultcache(key = 3)] - pub health_contract: String, - #[cultcache(key = 4)] - pub provider_state: String, - #[cultcache(key = 5)] - pub reason_code: String, - #[cultcache(key = 6)] - pub projection_id: String, - #[cultcache(key = 7)] - pub projection_sha256: String, - #[cultcache(key = 8)] - pub projection_incarnation_id: String, - #[cultcache(key = 9)] - pub projection_sequence: u64, - #[cultcache(key = 10)] - pub evaluated_at_unix_millis: u64, - #[cultcache(key = 11)] - pub expires_at_unix_millis: u64, - #[cultcache(key = 12)] - pub provider_incarnation_id: String, - #[cultcache(key = 13)] - pub provider_sequence: u64, - #[cultcache(key = 14)] - pub provider_observed_at_unix_millis: u64, - #[cultcache(key = 15)] - pub signed_health_sha256: String, - #[cultcache(key = 16)] - pub idunn_runtime_id: String, - #[cultcache(key = 17)] - pub idunn_signer_identity_id: String, - #[cultcache(key = 18)] - pub trust_anchor_id: String, - #[cultcache(key = 19)] - pub trust_anchor_sha256: String, - #[cultcache(key = 20)] - pub release_id: Option, - #[cultcache(key = 21)] - pub release_witness_sha256: Option, - #[cultcache(key = 22)] - pub source_commit: Option, - #[cultcache(key = 23)] - pub deployment_id: Option, - #[cultcache(key = 24)] - pub admitted_at_unix_millis: u64, - #[cultcache(key = 25)] - pub private_state_exposed: bool, - #[cultcache(key = 26)] - pub trust_anchor_bound_at_unix_millis: u64, -} - -pub fn read_idunn_provider_health_trust_anchor( - path: &Path, -) -> Result { - let rows = SingleFileMessagePackBackingStore::new(path).pull_all_read_only_snapshot()?; - if rows.len() != 1 { - bail!("Idunn provider-health trust anchor store must contain exactly one row"); - } - let row = &rows[0]; - if row.r#type != GameCultServiceTrustAnchorRecord::TYPE { - bail!("Idunn provider-health trust anchor store contains foreign authority"); - } - let anchor: GameCultServiceTrustAnchorRecord = rmp_serde::from_slice(&row.payload)?; - anchor.validate()?; - Ok(anchor) -} - -pub fn provider_health_record_key(daemon_id: &str, health_contract: &str) -> String { - format!( - "provider-health:{:x}", - Sha256::digest([daemon_id.as_bytes(), b"\0", health_contract.as_bytes()].concat()) - ) -} - -/// Build the exact read-only request expected from Idunn's public store. The -/// returned query rejects extra identities and source-runtime substitution; -/// admission below additionally refuses any missing required identity. -pub fn required_idunn_provider_health_query( - message_id: impl Into, - required: &[RequiredProviderHealth], - expected_idunn_runtime_id: &str, -) -> Result { - validate_required_set(required)?; - if expected_idunn_runtime_id.trim().is_empty() { - bail!("expected Idunn runtime id is empty"); - } - CultNetRawSnapshotQuery::new( - message_id, - required - .iter() - .map(|requirement| CultNetSnapshotRecordExpectation { - schema_id: IDUNN_AUTHENTICATED_PROVIDER_HEALTH_PROJECTION_SCHEMA.into(), - record_key: requirement.record_key(), - source: CultNetSnapshotSourceExpectation { - runtime_id: Some(expected_idunn_runtime_id.into()), - agent_id: None, - role: None, - tags: None, - }, - }) - .collect(), - ) -} - -/// Verify a complete required snapshot, then atomically advance its durable -/// local admissions. Invalid, missing, replayed, or substituted input performs -/// no write and therefore cannot erase the last admitted truth. -pub fn admit_required_idunn_provider_health( - admission_store: &Path, - required: &[RequiredProviderHealth], - records: &[CultNetRawDocumentRecord], - trust_anchor: &GameCultServiceTrustAnchorRecord, - expected_idunn_runtime_id: &str, - now_unix_millis: u64, - max_local_age_millis: u64, -) -> Result> { - validate_required_set(required)?; - validate_anchor(trust_anchor, expected_idunn_runtime_id, now_unix_millis)?; - if max_local_age_millis == 0 { - bail!("local provider-health TTL is zero"); - } - - let requirements = required - .iter() - .map(|item| (item.record_key(), item)) - .collect::>(); - let mut incoming = BTreeMap::new(); - for record in records { - let requirement = requirements - .get(&record.record_key) - .ok_or_else(|| anyhow!("Idunn snapshot contains an unknown provider-health record"))?; - if incoming.contains_key(&record.record_key) { - bail!("Idunn snapshot contains a duplicate provider-health record"); - } - let admission = verify_projection( - record, - requirement, - trust_anchor, - expected_idunn_runtime_id, - now_unix_millis, - max_local_age_millis, - )?; - incoming.insert(record.record_key.clone(), admission); - } - if incoming.len() != requirements.len() { - let missing = requirements - .keys() - .filter(|key| !incoming.contains_key(*key)) - .cloned() - .collect::>(); - bail!("Idunn snapshot is missing required provider-health records: {missing:?}"); - } - - let backing = SingleFileMessagePackBackingStore::new(admission_store); - for _ in 0..8 { - let snapshot = backing.pull_all_read_only_snapshot()?; - let existing = load_existing_admissions(&snapshot)?; - let mut expected = Vec::new(); - let mut replacements = Vec::new(); - let mut final_values = BTreeMap::new(); - for requirement in required { - let key = requirement.record_key(); - let next = incoming - .get(&key) - .ok_or_else(|| anyhow!("verified provider-health set lost a required record"))?; - match existing.get(&key) { - None => replacements.push(admission_envelope(next)?), - Some((envelope, current)) - if current.projection_sha256 == next.projection_sha256 => - { - expected.push(envelope.clone()); - replacements.push(envelope.clone()); - final_values.insert(key, current.clone()); - continue; - } - Some((envelope, current)) => { - validate_monotonic_successor(current, next)?; - expected.push(envelope.clone()); - replacements.push(admission_envelope(next)?); - } - } - final_values.insert(key, next.clone()); - } - if backing.compare_and_swap_batch(&expected, replacements)? { - return order_admission_map(required, &final_values); - } - } - bail!("Idunn provider-health admission lost repeated cross-process contention") -} - -/// Verify one expected provider projection without granting durable complete-set -/// admission authority. Status may use this to report partial current sight. -pub fn verify_idunn_provider_health_candidate( - requirement: &RequiredProviderHealth, - record: &CultNetRawDocumentRecord, - trust_anchor: &GameCultServiceTrustAnchorRecord, - expected_idunn_runtime_id: &str, - now_unix_millis: u64, - max_local_age_millis: u64, -) -> Result { - validate_required_set(std::slice::from_ref(requirement))?; - validate_anchor(trust_anchor, expected_idunn_runtime_id, now_unix_millis)?; - if max_local_age_millis == 0 { - bail!("local provider-health TTL is zero"); - } - verify_projection( - record, - requirement, - trust_anchor, - expected_idunn_runtime_id, - now_unix_millis, - max_local_age_millis, - ) -} - -fn verify_projection( - record: &CultNetRawDocumentRecord, - requirement: &RequiredProviderHealth, - anchor: &GameCultServiceTrustAnchorRecord, - expected_idunn_runtime_id: &str, - now: u64, - max_local_age: u64, -) -> Result { - if record.schema_id != IDUNN_AUTHENTICATED_PROVIDER_HEALTH_PROJECTION_SCHEMA - || record.payload_encoding != CultNetRawPayloadEncoding::Messagepack - || record.record_key != requirement.record_key() - || record.source_runtime_id.as_deref() != Some(expected_idunn_runtime_id) - { - bail!("Idunn provider-health record schema, key, encoding, or source runtime is invalid"); - } - let projection: IdunnAuthenticatedProviderHealthProjectionRecord = - rmp_serde::from_slice(&record.payload) - .context("Idunn provider-health payload is malformed MessagePack")?; - if rmp_serde::to_vec(&projection)? != record.payload { - bail!("Idunn provider-health payload is not canonical MessagePack"); - } - projection.validate()?; - if projection.projection_id != record.record_key - || projection.daemon_id != requirement.daemon_id - || projection.health_contract != requirement.health_contract - || projection.idunn_runtime_id != expected_idunn_runtime_id - || projection.idunn_signer_identity_id != anchor.signer_identity_id - || projection.private_state_exposed - { - bail!( - "Idunn provider-health projection identity, pair, runtime, signer, or privacy is invalid" - ); - } - if projection.evaluated_at_unix_millis > now - || projection.expires_at_unix_millis <= now - || now.saturating_sub(projection.evaluated_at_unix_millis) > max_local_age - || anchor.bound_at_unix_millis > projection.evaluated_at_unix_millis - || anchor - .expires_at_unix_millis - .is_some_and(|expires| expires <= projection.evaluated_at_unix_millis) - || anchor - .expires_at_unix_millis - .is_some_and(|expires| projection.expires_at_unix_millis > expires) - { - bail!( - "Idunn provider-health projection is future, expired, outside local TTL, or outside anchor lifetime" - ); - } - validate_release_binding(&projection, &requirement.release_binding)?; - - let proof = ServiceIdentitySignature { - identity_id: projection.idunn_signer_identity_id.clone(), - signature: projection.signature.clone(), - }; - let mut unsigned = projection.clone(); - unsigned.signature.clear(); - verify_service_identity_signature_with_public_key::< - IdunnServiceIdentity, - IdunnAuthenticatedProviderHealthProjectionPurpose, - >( - &anchor.signer_public_key, - &rmp_serde::to_vec(&unsigned)?, - &proof, - ) - .context("Idunn provider-health signature is invalid")?; - - Ok(IdunnProviderHealthAdmission { - schema_version: EPIPHANY_IDUNN_PROVIDER_HEALTH_ADMISSION_SCHEMA.into(), - admission_id: requirement.record_key(), - daemon_id: projection.daemon_id, - health_contract: projection.health_contract, - provider_state: projection.provider_state, - reason_code: projection.reason_code, - projection_id: projection.projection_id, - projection_sha256: sha256(&record.payload), - projection_incarnation_id: projection.projection_incarnation_id, - projection_sequence: projection.projection_sequence, - evaluated_at_unix_millis: projection.evaluated_at_unix_millis, - expires_at_unix_millis: projection.expires_at_unix_millis, - provider_incarnation_id: projection.provider_incarnation_id, - provider_sequence: projection.provider_sequence, - provider_observed_at_unix_millis: projection.provider_observed_at_unix_millis, - signed_health_sha256: projection.signed_health_sha256, - idunn_runtime_id: projection.idunn_runtime_id, - idunn_signer_identity_id: projection.idunn_signer_identity_id, - trust_anchor_id: anchor.trust_anchor_id.clone(), - trust_anchor_sha256: sha256(&rmp_serde::to_vec(anchor)?), - release_id: projection.release_id, - release_witness_sha256: projection.release_witness_sha256, - source_commit: projection.source_commit, - deployment_id: projection.deployment_id, - admitted_at_unix_millis: now, - private_state_exposed: false, - trust_anchor_bound_at_unix_millis: anchor.bound_at_unix_millis, - }) -} - -fn validate_anchor( - anchor: &GameCultServiceTrustAnchorRecord, - expected_runtime: &str, - now: u64, -) -> Result<()> { - anchor.validate()?; - if expected_runtime.trim().is_empty() - || anchor.runtime_id != expected_runtime - || anchor.service_id != "idunn" - || anchor.signed_schema != IDUNN_AUTHENTICATED_PROVIDER_HEALTH_PROJECTION_SCHEMA - || anchor.signing_purpose != IDUNN_AUTHENTICATED_PROVIDER_HEALTH_PROJECTION_SIGNING_PURPOSE - || anchor.bound_at_unix_millis > now - || anchor - .expires_at_unix_millis - .is_some_and(|expires| expires <= now) - || anchor.private_state_exposed - { - bail!("root-pinned Idunn trust anchor profile, runtime, lifetime, or privacy is invalid"); - } - Ok(()) -} - -fn validate_release_binding( - projection: &IdunnAuthenticatedProviderHealthProjectionRecord, - policy: &ProviderReleaseBinding, -) -> Result<()> { - match policy { - ProviderReleaseBinding::Forbidden => { - if projection.release_id.is_some() - || projection.release_witness_sha256.is_some() - || projection.source_commit.is_some() - || projection.deployment_id.is_some() - { - bail!("provider-health projection carries forbidden release authority"); - } - } - ProviderReleaseBinding::Exact { - release_id, - release_witness_sha256, - source_commit, - deployment_id, - } => { - if projection.release_id.as_ref() != Some(release_id) - || projection.release_witness_sha256.as_ref() != Some(release_witness_sha256) - || projection.source_commit.as_ref() != Some(source_commit) - || projection.deployment_id.as_ref() != Some(deployment_id) - { - bail!( - "provider-health projection release lineage is not the exact required release" - ); - } - } - } - Ok(()) -} - -fn validate_monotonic_successor( - current: &IdunnProviderHealthAdmission, - next: &IdunnProviderHealthAdmission, -) -> Result<()> { - if current.daemon_id != next.daemon_id - || current.health_contract != next.health_contract - || current.idunn_runtime_id != next.idunn_runtime_id - { - bail!("provider-health successor changes admitted pair or Idunn runtime"); - } - let same_anchor = current.idunn_signer_identity_id == next.idunn_signer_identity_id - && current.trust_anchor_id == next.trust_anchor_id - && current.trust_anchor_sha256 == next.trust_anchor_sha256 - && current.trust_anchor_bound_at_unix_millis == next.trust_anchor_bound_at_unix_millis; - if !same_anchor { - if next.trust_anchor_bound_at_unix_millis <= current.trust_anchor_bound_at_unix_millis - || next.trust_anchor_bound_at_unix_millis <= current.admitted_at_unix_millis - || next.trust_anchor_bound_at_unix_millis <= current.evaluated_at_unix_millis - || next.evaluated_at_unix_millis < next.trust_anchor_bound_at_unix_millis - { - bail!("root Idunn trust-anchor rotation is stale or precedes its signed projection"); - } - return Ok(()); - } - if current.projection_incarnation_id == next.projection_incarnation_id { - if next.projection_sequence <= current.projection_sequence - || next.evaluated_at_unix_millis < current.evaluated_at_unix_millis - { - bail!("provider-health projection sequence or evaluation time replayed"); - } - } else if next.evaluated_at_unix_millis <= current.evaluated_at_unix_millis { - bail!("new Idunn projection incarnation does not advance evaluation time"); - } - if current.provider_incarnation_id == next.provider_incarnation_id { - if next.provider_sequence < current.provider_sequence - || next.provider_observed_at_unix_millis < current.provider_observed_at_unix_millis - || (next.provider_sequence == current.provider_sequence - && (next.provider_observed_at_unix_millis - != current.provider_observed_at_unix_millis - || next.signed_health_sha256 != current.signed_health_sha256)) - { - bail!("provider statement sequence, observation, or digest replayed"); - } - } else if next.provider_observed_at_unix_millis <= current.provider_observed_at_unix_millis { - bail!("new provider incarnation does not advance observation time"); - } - Ok(()) -} - -fn validate_required_set(required: &[RequiredProviderHealth]) -> Result<()> { - if required.is_empty() { - bail!("required provider-health set is empty"); - } - let mut pairs = BTreeSet::new(); - let mut keys = BTreeSet::new(); - for item in required { - if item.daemon_id.trim().is_empty() || item.health_contract.trim().is_empty() { - bail!("required provider-health pair contains an empty identity"); - } - if !pairs.insert((item.daemon_id.clone(), item.health_contract.clone())) - || !keys.insert(item.record_key()) - { - bail!("required provider-health set contains a duplicate pair"); - } - } - Ok(()) -} - -fn load_existing_admissions( - entries: &[CultCacheEnvelope], -) -> Result> { - let mut output = BTreeMap::new(); - for entry in entries { - if entry.r#type != EPIPHANY_IDUNN_PROVIDER_HEALTH_ADMISSION_TYPE - || entry.schema_id.as_deref() != Some(EPIPHANY_IDUNN_PROVIDER_HEALTH_ADMISSION_TYPE) - { - bail!( - "Idunn provider-health admission store contains foreign state type={:?} schema={:?}", - entry.r#type, - entry.schema_id - ); - } - let admission: IdunnProviderHealthAdmission = rmp_serde::from_slice(&entry.payload)?; - if rmp_serde::to_vec(&admission)? != entry.payload - || entry.key != admission.admission_id - || admission.schema_version != EPIPHANY_IDUNN_PROVIDER_HEALTH_ADMISSION_SCHEMA - || admission.private_state_exposed - || admission.trust_anchor_bound_at_unix_millis == 0 - { - bail!("stored Idunn provider-health admission is malformed or noncanonical"); - } - if output - .insert(entry.key.clone(), (entry.clone(), admission)) - .is_some() - { - bail!("Idunn provider-health admission store contains duplicate identity"); - } - } - Ok(output) -} - -fn admission_envelope(admission: &IdunnProviderHealthAdmission) -> Result { - let mut cache = CultCache::new(); - cache.register_entry_type::()?; - Ok(cache.prepare_entry(&admission.admission_id, admission)?.0) -} - -fn order_admission_map( - required: &[RequiredProviderHealth], - values: &BTreeMap, -) -> Result> { - required - .iter() - .map(|item| { - values - .get(&item.record_key()) - .cloned() - .ok_or_else(|| anyhow!("admitted set lost required provider-health record")) - }) - .collect() -} - -fn sha256(bytes: &[u8]) -> String { - format!("sha256-{:x}", Sha256::digest(bytes)) -} - -#[cfg(test)] -mod tests { - use super::*; - use cultnet_rs::{ - IdunnAuthenticatedProviderHealthProjectionPurpose, ServiceIdentitySigner, - enroll_service_identity_at, - }; - - const NOW: u64 = 1_800_000_000_000; - const RUNTIME: &str = "idunn-yggdrasil"; - - fn required(daemon: &str) -> RequiredProviderHealth { - RequiredProviderHealth { - daemon_id: daemon.into(), - health_contract: "idunn.signed_daemon_health.v1".into(), - release_binding: ProviderReleaseBinding::Forbidden, - } - } - - fn anchor( - signer: &ServiceIdentitySigner, - ) -> GameCultServiceTrustAnchorRecord { - GameCultServiceTrustAnchorRecord { - schema_version: cultnet_rs::GAMECULT_SERVICE_TRUST_ANCHOR_SCHEMA.into(), - trust_anchor_id: "root/idunn/provider-health".into(), - service_id: "idunn".into(), - runtime_id: RUNTIME.into(), - signer_identity_id: signer.entry().identity_id.clone(), - signer_public_key: signer.entry().public_key.clone(), - signature_algorithm: "ed25519".into(), - signing_purpose: IDUNN_AUTHENTICATED_PROVIDER_HEALTH_PROJECTION_SIGNING_PURPOSE.into(), - signed_schema: IDUNN_AUTHENTICATED_PROVIDER_HEALTH_PROJECTION_SCHEMA.into(), - binding_authority: "root".into(), - bound_at_unix_millis: NOW - 10_000, - expires_at_unix_millis: Some(NOW + 100_000), - private_state_exposed: false, - } - } - - fn projection( - signer: &ServiceIdentitySigner, - requirement: &RequiredProviderHealth, - projection_sequence: u64, - provider_sequence: u64, - ) -> IdunnAuthenticatedProviderHealthProjectionRecord { - let mut value = IdunnAuthenticatedProviderHealthProjectionRecord { - schema_version: IDUNN_AUTHENTICATED_PROVIDER_HEALTH_PROJECTION_SCHEMA.into(), - projection_id: requirement.record_key(), - daemon_id: requirement.daemon_id.clone(), - health_contract: requirement.health_contract.clone(), - provider_state: "active".into(), - reason_code: cultnet_rs::IDUNN_PROVIDER_ACTIVE_REASON.into(), - provider_observed_at_unix_millis: NOW - 3_000 + provider_sequence, - admitted_at_unix_millis: NOW - 2_000 + provider_sequence, - evaluated_at_unix_millis: NOW - 1_000 + projection_sequence, - trust_binding_id: "root/provider".into(), - trust_binding_sha256: format!("sha256-{}", "a".repeat(64)), - signed_health_sha256: format!("sha256-{:064x}", provider_sequence), - authenticated_admission_sha256: format!("sha256-{}", "c".repeat(64)), - provider_signer_identity_id: "provider-key".into(), - provider_incarnation_id: "provider/boot/1".into(), - provider_sequence, - release_id: None, - release_witness_sha256: None, - source_commit: None, - deployment_id: None, - idunn_runtime_id: RUNTIME.into(), - idunn_signer_identity_id: signer.entry().identity_id.clone(), - projection_incarnation_id: "idunn/boot/1".into(), - projection_sequence, - signature_algorithm: "ed25519".into(), - signature: Vec::new(), - private_state_exposed: false, - expires_at_unix_millis: NOW + 60_000, - }; - resign(signer, &mut value); - value - } - - fn resign( - signer: &ServiceIdentitySigner, - value: &mut IdunnAuthenticatedProviderHealthProjectionRecord, - ) { - value.signature.clear(); - value.signature = signer - .sign::( - &rmp_serde::to_vec(value).unwrap(), - ) - .signature; - } - - fn record( - projection: &IdunnAuthenticatedProviderHealthProjectionRecord, - ) -> CultNetRawDocumentRecord { - CultNetRawDocumentRecord { - schema_id: IDUNN_AUTHENTICATED_PROVIDER_HEALTH_PROJECTION_SCHEMA.into(), - record_key: projection.projection_id.clone(), - stored_at: "2027-01-15T08:00:00Z".into(), - payload_encoding: CultNetRawPayloadEncoding::Messagepack, - payload: rmp_serde::to_vec(projection).unwrap(), - source_runtime_id: Some(RUNTIME.into()), - source_agent_id: Some("idunn".into()), - source_role: Some("provider-health-projection".into()), - tags: Some(vec!["public-health".into()]), - } - } - - #[test] - fn required_query_and_admission_accept_exact_complete_set() -> Result<()> { - let temp = tempfile::tempdir()?; - let signer = - enroll_service_identity_at::(&temp.path().join("id.cc"))?; - let requirements = vec![required("epiphany"), required("bifrost")]; - let query = required_idunn_provider_health_query("status-1", &requirements, RUNTIME)?; - let records = requirements - .iter() - .map(|item| record(&projection(&signer, item, 1, 1))) - .collect::>(); - let accepted = query.accept_response(cultnet_rs::CultNetMessage::SnapshotResponseRaw { - message_id: "status-1".into(), - documents: records, - })?; - let admitted = admit_required_idunn_provider_health( - &temp.path().join("admission.cc"), - &requirements, - &accepted, - &anchor(&signer), - RUNTIME, - NOW, - 10_000, - )?; - assert_eq!(admitted.len(), 2); - assert!(admitted.iter().all(|item| item.provider_state == "active")); - Ok(()) - } - - #[test] - fn one_current_candidate_remains_reportable_without_partial_admission() -> Result<()> { - let temp = tempfile::tempdir()?; - let signer = - enroll_service_identity_at::(&temp.path().join("id.cc"))?; - let epiphany = required("epiphany"); - let bifrost = required("bifrost"); - let epiphany_record = record(&projection(&signer, &epiphany, 1, 1)); - let candidate = verify_idunn_provider_health_candidate( - &epiphany, - &epiphany_record, - &anchor(&signer), - RUNTIME, - NOW, - 30_000, - )?; - assert_eq!(candidate.daemon_id, "epiphany"); - let store = temp.path().join("partial.cc"); - assert!( - admit_required_idunn_provider_health( - &store, - &[epiphany, bifrost], - &[epiphany_record], - &anchor(&signer), - RUNTIME, - NOW, - 30_000, - ) - .is_err() - ); - assert!(!store.exists()); - Ok(()) - } - - #[test] - fn hostile_projection_substitutions_cannot_erase_prior_admission() -> Result<()> { - let temp = tempfile::tempdir()?; - let signer = - enroll_service_identity_at::(&temp.path().join("id.cc"))?; - let requirement = required("epiphany"); - let store = temp.path().join("admission.cc"); - let valid = record(&projection(&signer, &requirement, 1, 1)); - admit_required_idunn_provider_health( - &store, - std::slice::from_ref(&requirement), - std::slice::from_ref(&valid), - &anchor(&signer), - RUNTIME, - NOW, - 10_000, - )?; - let baseline = std::fs::read(&store)?; - - let mut attacks = Vec::new(); - let mut forged = valid.clone(); - forged.source_runtime_id = Some("mallory".into()); - attacks.push(forged); - let mut forged = valid.clone(); - forged.payload.push(0); - attacks.push(forged); - let mut unsigned: IdunnAuthenticatedProviderHealthProjectionRecord = - rmp_serde::from_slice(&valid.payload)?; - unsigned.signature[0] ^= 1; - attacks.push(record(&unsigned)); - let mut private: IdunnAuthenticatedProviderHealthProjectionRecord = - rmp_serde::from_slice(&valid.payload)?; - private.private_state_exposed = true; - attacks.push(record(&private)); - let mut wrong_reason: IdunnAuthenticatedProviderHealthProjectionRecord = - rmp_serde::from_slice(&valid.payload)?; - wrong_reason.reason_code = "release_drift".into(); - resign(&signer, &mut wrong_reason); - attacks.push(record(&wrong_reason)); - let mut expired: IdunnAuthenticatedProviderHealthProjectionRecord = - rmp_serde::from_slice(&valid.payload)?; - expired.expires_at_unix_millis = NOW; - resign(&signer, &mut expired); - attacks.push(record(&expired)); - let mut future: IdunnAuthenticatedProviderHealthProjectionRecord = - rmp_serde::from_slice(&valid.payload)?; - future.evaluated_at_unix_millis = NOW + 1; - future.expires_at_unix_millis = NOW + 60_000; - resign(&signer, &mut future); - attacks.push(record(&future)); - let mut forbidden_release: IdunnAuthenticatedProviderHealthProjectionRecord = - rmp_serde::from_slice(&valid.payload)?; - forbidden_release.release_id = Some("release-1".into()); - forbidden_release.release_witness_sha256 = Some(format!("sha256-{}", "d".repeat(64))); - forbidden_release.source_commit = Some("e".repeat(40)); - forbidden_release.deployment_id = Some("deployment-1".into()); - resign(&signer, &mut forbidden_release); - attacks.push(record(&forbidden_release)); - for attack in attacks { - assert!( - admit_required_idunn_provider_health( - &store, - std::slice::from_ref(&requirement), - &[attack], - &anchor(&signer), - RUNTIME, - NOW, - 10_000, - ) - .is_err() - ); - assert_eq!(std::fs::read(&store)?, baseline); - } - - let mut expired_anchor = anchor(&signer); - expired_anchor.expires_at_unix_millis = Some(NOW); - assert!( - admit_required_idunn_provider_health( - &store, - std::slice::from_ref(&requirement), - std::slice::from_ref(&valid), - &expired_anchor, - RUNTIME, - NOW, - 10_000, - ) - .is_err() - ); - let mut wrong_purpose = anchor(&signer); - wrong_purpose.signing_purpose = "caller-selected-purpose".into(); - assert!( - admit_required_idunn_provider_health( - &store, - std::slice::from_ref(&requirement), - std::slice::from_ref(&valid), - &wrong_purpose, - RUNTIME, - NOW, - 10_000, - ) - .is_err() - ); - let mut late_anchor = anchor(&signer); - late_anchor.bound_at_unix_millis = NOW - 100; - assert!( - admit_required_idunn_provider_health( - &store, - std::slice::from_ref(&requirement), - std::slice::from_ref(&valid), - &late_anchor, - RUNTIME, - NOW, - 10_000, - ) - .is_err() - ); - assert!( - admit_required_idunn_provider_health( - &store, - std::slice::from_ref(&requirement), - std::slice::from_ref(&valid), - &anchor(&signer), - RUNTIME, - NOW + 20_000, - 10_000, - ) - .is_err() - ); - assert_eq!(std::fs::read(&store)?, baseline); - Ok(()) - } - - #[test] - fn required_set_refuses_duplicate_missing_unknown_and_release_substitution() -> Result<()> { - let temp = tempfile::tempdir()?; - let signer = - enroll_service_identity_at::(&temp.path().join("id.cc"))?; - let requirement = required("epiphany"); - assert!( - required_idunn_provider_health_query( - "duplicate", - &[requirement.clone(), requirement.clone()], - RUNTIME, - ) - .is_err() - ); - assert!( - admit_required_idunn_provider_health( - &temp.path().join("missing.cc"), - std::slice::from_ref(&requirement), - &[], - &anchor(&signer), - RUNTIME, - NOW, - 10_000, - ) - .is_err() - ); - let unknown_requirement = required("odin"); - let unknown = record(&projection(&signer, &unknown_requirement, 1, 1)); - assert!( - admit_required_idunn_provider_health( - &temp.path().join("unknown.cc"), - std::slice::from_ref(&requirement), - &[unknown], - &anchor(&signer), - RUNTIME, - NOW, - 10_000, - ) - .is_err() - ); - let mut exact = requirement.clone(); - exact.release_binding = ProviderReleaseBinding::Exact { - release_id: "release-1".into(), - release_witness_sha256: format!("sha256-{}", "d".repeat(64)), - source_commit: "e".repeat(40), - deployment_id: "deployment-1".into(), - }; - assert!( - admit_required_idunn_provider_health( - &temp.path().join("release.cc"), - std::slice::from_ref(&exact), - &[record(&projection(&signer, &exact, 1, 1))], - &anchor(&signer), - RUNTIME, - NOW, - 10_000, - ) - .is_err() - ); - Ok(()) - } - - #[test] - fn monotonic_cas_accepts_successor_and_rejects_replays_without_write() -> Result<()> { - let temp = tempfile::tempdir()?; - let signer = - enroll_service_identity_at::(&temp.path().join("id.cc"))?; - let requirement = required("epiphany"); - let store = temp.path().join("admission.cc"); - let first = record(&projection(&signer, &requirement, 1, 1)); - admit_required_idunn_provider_health( - &store, - std::slice::from_ref(&requirement), - &[first], - &anchor(&signer), - RUNTIME, - NOW, - 10_000, - )?; - let second = record(&projection(&signer, &requirement, 2, 2)); - let accepted = admit_required_idunn_provider_health( - &store, - std::slice::from_ref(&requirement), - &[second], - &anchor(&signer), - RUNTIME, - NOW + 10, - 10_000, - )?; - assert_eq!(accepted[0].projection_sequence, 2); - let baseline = std::fs::read(&store)?; - let replay = record(&projection(&signer, &requirement, 1, 1)); - assert!( - admit_required_idunn_provider_health( - &store, - std::slice::from_ref(&requirement), - &[replay], - &anchor(&signer), - RUNTIME, - NOW + 20, - 10_000 - ) - .is_err() - ); - assert_eq!(std::fs::read(&store)?, baseline); - Ok(()) - } - - #[test] - fn root_anchor_rotation_requires_newer_root_sight_and_resets_continuity_once() -> Result<()> { - let temp = tempfile::tempdir()?; - let old_signer = - enroll_service_identity_at::(&temp.path().join("old-id.cc"))?; - let new_signer = - enroll_service_identity_at::(&temp.path().join("new-id.cc"))?; - let attacker = - enroll_service_identity_at::(&temp.path().join("attacker.cc"))?; - let requirement = required("epiphany"); - let store = temp.path().join("admission.cc"); - let first = record(&projection(&old_signer, &requirement, 5, 5)); - admit_required_idunn_provider_health( - &store, - std::slice::from_ref(&requirement), - &[first], - &anchor(&old_signer), - RUNTIME, - NOW, - 10_000, - )?; - - let mut new_anchor = anchor(&new_signer); - new_anchor.bound_at_unix_millis = NOW + 100; - new_anchor.expires_at_unix_millis = Some(NOW + 200_000); - let mut rotated = projection(&new_signer, &requirement, 1, 1); - rotated.provider_observed_at_unix_millis = NOW; - rotated.admitted_at_unix_millis = NOW + 50; - rotated.evaluated_at_unix_millis = NOW + 101; - rotated.expires_at_unix_millis = NOW + 80_000; - rotated.projection_incarnation_id = "idunn/boot/rotated".into(); - rotated.provider_incarnation_id = "provider/boot/rotated".into(); - resign(&new_signer, &mut rotated); - let accepted = admit_required_idunn_provider_health( - &store, - std::slice::from_ref(&requirement), - &[record(&rotated)], - &new_anchor, - RUNTIME, - NOW + 200, - 10_000, - )?; - assert_eq!( - accepted[0].idunn_signer_identity_id, - new_anchor.signer_identity_id - ); - let baseline = std::fs::read(&store)?; - - let mut old_after_rotation = projection(&old_signer, &requirement, 99, 99); - old_after_rotation.evaluated_at_unix_millis = NOW + 150; - old_after_rotation.expires_at_unix_millis = NOW + 60_000; - resign(&old_signer, &mut old_after_rotation); - assert!( - admit_required_idunn_provider_health( - &store, - std::slice::from_ref(&requirement), - &[record(&old_after_rotation)], - &anchor(&old_signer), - RUNTIME, - NOW + 200, - 10_000, - ) - .is_err() - ); - - let mut self_key = rotated.clone(); - self_key.idunn_signer_identity_id = attacker.entry().identity_id.clone(); - resign(&attacker, &mut self_key); - assert!( - admit_required_idunn_provider_health( - &store, - std::slice::from_ref(&requirement), - &[record(&self_key)], - &new_anchor, - RUNTIME, - NOW + 200, - 10_000, - ) - .is_err() - ); - assert_eq!(std::fs::read(&store)?, baseline); - Ok(()) - } -} diff --git a/epiphany-core/src/idunn_runtime_health.rs b/epiphany-core/src/idunn_runtime_health.rs deleted file mode 100644 index e29ccf33..00000000 --- a/epiphany-core/src/idunn_runtime_health.rs +++ /dev/null @@ -1,518 +0,0 @@ -use anyhow::{Context, Result, anyhow, bail}; -use cultnet_rs::{ - CultNetMessage, CultNetRawDocumentRecord, CultNetRawPayloadEncoding, - CultNetRudpSocketTransportConnection, CultNetRudpSocketTransportOptions, CultNetWireContract, - GameCultProviderHealthIdentity, IDUNN_SIGNED_DAEMON_HEALTH_SCHEMA, - IdunnSignedDaemonHealthPurpose, IdunnSignedDaemonHealthRecord, ServiceIdentitySigner, - encode_cultnet_message_to_vec, -}; -use serde::{Deserialize, Serialize}; -use std::net::{SocketAddr, UdpSocket}; -use std::time::{Duration, Instant}; - -pub const EPIPHANY_IDUNN_RUNTIME_HEALTH_CONTRACT: &str = "epiphany.cultnet-rudp-runtime-health"; -pub const CULTNET_RUDP_PROTOCOL_ID: &str = "cultnet.transport.rudp.v0"; -const IDUNN_HEALTH_RUDP_CONNECTION_ID: u32 = 0x1d0d_0001; - -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -pub struct IdunnDaemonHealthDocument { - pub daemon_id: String, - pub state: String, - pub detail: String, - pub observed_at: String, - pub health_contract: String, - pub publication_source: String, - pub transport: String, -} - -pub fn sign_epiphany_runtime_health( - health: IdunnDaemonHealthDocument, - source_runtime_id: &str, - release_id: &str, - release_witness_sha256: &str, - source_commit: &str, - deployment_request_id: &str, - publisher_incarnation_id: &str, - publisher_sequence: u64, - signer: &ServiceIdentitySigner, -) -> Result { - validate_health_document(&health)?; - let observed_at_unix_millis = chrono::DateTime::parse_from_rfc3339(&health.observed_at)? - .timestamp_millis() - .try_into() - .context("runtime health observation precedes Unix epoch")?; - let mut record = IdunnSignedDaemonHealthRecord { - schema_version: "idunn.signed_daemon_health.v1".into(), - daemon_id: health.daemon_id, - health_contract: health.health_contract, - source_runtime_id: source_runtime_id.into(), - state: health.state, - detail: health.detail, - signer_identity_id: signer.entry().identity_id.clone(), - publisher_incarnation_id: publisher_incarnation_id.into(), - publisher_sequence, - observed_at_unix_millis, - release_id: Some(release_id.into()), - release_witness_sha256: Some(release_witness_sha256.into()), - source_commit: Some(source_commit.into()), - deployment_id: Some(deployment_request_id.into()), - signature_algorithm: "ed25519".into(), - signature: Vec::new(), - private_state_exposed: false, - }; - validate_unsigned_record(&record)?; - let canonical = canonical_unsigned_record(&record)?; - let proof = signer.sign::(&canonical); - if proof.identity_id != record.signer_identity_id { - bail!("provider-health signer identity disagrees with record") - } - record.signature = proof.signature; - record.validate()?; - Ok(record) -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct EpiphanyAggregateRuntimeHealthInput { - pub daemon_id: String, - pub health_contract: String, - pub observed_at: String, - pub release_authenticated: bool, - pub expected_service_count: usize, - pub terminal_current_service_count: usize, - pub warming_service_count: usize, - pub contradictions: Vec, -} - -pub fn derive_epiphany_aggregate_runtime_health( - input: EpiphanyAggregateRuntimeHealthInput, -) -> Result { - require_id(&input.daemon_id, "Idunn daemon id")?; - require_id(&input.health_contract, "Idunn health contract")?; - require_id(&input.observed_at, "Idunn health observation time")?; - if input.expected_service_count == 0 { - bail!("aggregate runtime health requires at least one managed service"); - } - if input.terminal_current_service_count > input.expected_service_count - || input.warming_service_count > input.expected_service_count - || input - .terminal_current_service_count - .saturating_add(input.warming_service_count) - > input.expected_service_count - { - bail!("managed service health counts exceed the expected service count"); - } - let (state, detail) = if !input.contradictions.is_empty() { - ("failed", "runtime-contradiction") - } else if !input.release_authenticated { - ("failed", "release-authentication-failed") - } else if input.terminal_current_service_count == input.expected_service_count { - ("active", "managed-services-current") - } else if input.warming_service_count > 0 - && input - .terminal_current_service_count - .saturating_add(input.warming_service_count) - == input.expected_service_count - { - ("warming", "managed-services-warming") - } else { - ("degraded", "managed-services-reconciling") - }; - Ok(IdunnDaemonHealthDocument { - daemon_id: input.daemon_id, - state: state.into(), - detail: detail.into(), - observed_at: input.observed_at, - health_contract: input.health_contract, - publication_source: "daemon-published".into(), - transport: CULTNET_RUDP_PROTOCOL_ID.into(), - }) -} - -pub fn publish_idunn_daemon_health_rudp( - endpoint: SocketAddr, - source_runtime_id: &str, - signed: &IdunnSignedDaemonHealthRecord, -) -> Result<()> { - signed.validate()?; - require_id(source_runtime_id, "health publisher runtime id")?; - if source_runtime_id != signed.source_runtime_id { - bail!("health transport source disagrees with signed source runtime"); - } - let payload = rmp_serde::to_vec(signed).context("encoding canonical signed Idunn health")?; - let decoded: IdunnSignedDaemonHealthRecord = rmp_serde::from_slice(&payload)?; - if decoded != *signed || rmp_serde::to_vec(&decoded)? != payload { - bail!("signed Idunn health encoding is noncanonical"); - } - let message = CultNetMessage::DocumentPutRaw { - message_id: format!( - "epiphany-signed-health:{}:{}:{}", - signed.daemon_id, signed.publisher_incarnation_id, signed.publisher_sequence - ), - document: CultNetRawDocumentRecord { - schema_id: IDUNN_SIGNED_DAEMON_HEALTH_SCHEMA.into(), - record_key: signed.daemon_id.clone(), - stored_at: chrono::DateTime::from_timestamp_millis( - signed.observed_at_unix_millis.try_into()?, - ) - .context("signed health observation time is invalid")? - .to_rfc3339(), - payload_encoding: CultNetRawPayloadEncoding::Messagepack, - payload, - source_runtime_id: Some(source_runtime_id.into()), - source_agent_id: Some(signed.signer_identity_id.clone()), - source_role: Some("daemon-health-publisher".into()), - tags: Some(vec![CULTNET_RUDP_PROTOCOL_ID.into()]), - }, - }; - let bind = if endpoint.is_ipv4() { - "0.0.0.0:0" - } else { - "[::]:0" - }; - let socket = UdpSocket::bind(bind) - .with_context(|| format!("binding Epiphany Idunn RUDP sender at {bind}"))?; - socket.set_read_timeout(Some(Duration::from_millis(100)))?; - let mut transport = - CultNetRudpSocketTransportConnection::new(CultNetRudpSocketTransportOptions::client( - source_runtime_id, - socket, - endpoint, - IDUNN_HEALTH_RUDP_CONNECTION_ID, - ))?; - transport.connect(Vec::new())?; - let deadline = Instant::now() + Duration::from_millis(500); - while !transport.connected() { - let _ = transport.receive_once()?; - transport.poll_resends()?; - if Instant::now() >= deadline { - return Err(anyhow!( - "timed out connecting Epiphany health publisher to {endpoint}" - )); - } - } - transport.send( - "schema", - encode_cultnet_message_to_vec(&message, CultNetWireContract::CultNetSchemaV0)?, - )?; - Ok(()) -} - -fn canonical_unsigned_record(record: &IdunnSignedDaemonHealthRecord) -> Result> { - let mut unsigned = record.clone(); - unsigned.signature.clear(); - validate_unsigned_record(&unsigned)?; - rmp_serde::to_vec(&unsigned).context("encoding canonical unsigned Idunn health") -} - -fn validate_unsigned_record(record: &IdunnSignedDaemonHealthRecord) -> Result<()> { - if !record.signature.is_empty() { - bail!("unsigned signing record already contains a signature"); - } - let mut signed_shape = record.clone(); - signed_shape.signature = vec![0; 64]; - signed_shape.validate()?; - uuid::Uuid::parse_str(&record.publisher_incarnation_id) - .context("publisher incarnation id must be UUID")?; - Ok(()) -} - -fn validate_health_document(health: &IdunnDaemonHealthDocument) -> Result<()> { - require_id(&health.daemon_id, "Idunn daemon id")?; - require_id(&health.health_contract, "Idunn health contract")?; - require_id(&health.observed_at, "Idunn health observation time")?; - if !matches!( - health.state.as_str(), - "active" | "warming" | "degraded" | "failed" - ) { - bail!("unsupported Idunn daemon health state"); - } - if !matches!( - health.detail.as_str(), - "runtime-contradiction" - | "release-authentication-failed" - | "managed-services-current" - | "managed-services-warming" - | "managed-services-reconciling" - ) { - bail!("Idunn daemon health detail is not a bounded generic reason"); - } - if health.publication_source != "daemon-published" - || health.transport != CULTNET_RUDP_PROTOCOL_ID - { - bail!("Idunn daemon health publication authority is invalid"); - } - chrono::DateTime::parse_from_rfc3339(&health.observed_at)?; - Ok(()) -} - -fn require_id(value: &str, label: &str) -> Result<()> { - if value.trim().is_empty() || value.len() > 256 || value.chars().any(char::is_control) { - bail!("{label} is empty, oversized, or contains control characters"); - } - Ok(()) -} - -#[cfg(test)] -mod tests { - use super::*; - use cultnet_rs::{ - ServiceIdentityProfile, ServiceIdentitySignature, ServiceSignaturePurpose, - decode_cultnet_message_from_slice, enroll_service_identity_at, - verify_service_identity_signature, - }; - use std::thread; - - struct WrongPurpose; - impl ServiceSignaturePurpose for WrongPurpose { - const PURPOSE: &'static [u8] = b"wrong-purpose"; - } - - fn input(current: usize) -> EpiphanyAggregateRuntimeHealthInput { - EpiphanyAggregateRuntimeHealthInput { - daemon_id: "yggdrasil-epiphany".into(), - health_contract: EPIPHANY_IDUNN_RUNTIME_HEALTH_CONTRACT.into(), - observed_at: "2026-07-16T00:00:00Z".into(), - release_authenticated: true, - expected_service_count: 2, - terminal_current_service_count: current, - warming_service_count: 0, - contradictions: Vec::new(), - } - } - - fn signed_health() -> ( - tempfile::TempDir, - ServiceIdentitySigner, - IdunnSignedDaemonHealthRecord, - ) { - let root = tempfile::tempdir().unwrap(); - let signer = enroll_service_identity_at::( - &root.path().join("provider.cc"), - ) - .unwrap(); - let record = sign_epiphany_runtime_health( - derive_epiphany_aggregate_runtime_health(input(2)).unwrap(), - "epiphany-yggdrasil", - "release-test", - &format!("sha256-{}", "a".repeat(64)), - &"b".repeat(40), - "deploy-request-test", - "00000000-0000-4000-8000-000000000001", - 1, - &signer, - ) - .unwrap(); - (root, signer, record) - } - - #[test] - fn aggregate_health_uses_closed_nonprivate_reasons() { - assert_eq!( - derive_epiphany_aggregate_runtime_health(input(2)) - .unwrap() - .detail, - "managed-services-current" - ); - let mut warming = input(1); - warming.warming_service_count = 1; - assert_eq!( - derive_epiphany_aggregate_runtime_health(warming) - .unwrap() - .detail, - "managed-services-warming" - ); - let mut failed = input(2); - failed - .contradictions - .push("secret path and process detail".into()); - assert_eq!( - derive_epiphany_aggregate_runtime_health(failed) - .unwrap() - .detail, - "runtime-contradiction" - ); - } - - #[test] - fn canonical_record_binds_exact_release_and_process_continuity() { - let (_root, signer, record) = signed_health(); - assert_eq!(record.source_runtime_id, "epiphany-yggdrasil"); - assert_eq!(record.release_id.as_deref(), Some("release-test")); - assert_eq!(record.deployment_id.as_deref(), Some("deploy-request-test")); - assert_eq!(record.publisher_sequence, 1); - assert!(!record.private_state_exposed); - let statement = canonical_unsigned_record(&record).unwrap(); - let proof = ServiceIdentitySignature { - identity_id: record.signer_identity_id.clone(), - signature: record.signature.clone(), - }; - verify_service_identity_signature::< - GameCultProviderHealthIdentity, - IdunnSignedDaemonHealthPurpose, - >(&signer.trust_anchor().unwrap(), &statement, &proof) - .unwrap(); - assert_eq!(&statement[..3], &[0xdc, 0, 17]); - } - - #[test] - fn wrong_purpose_key_mutation_and_noncanonical_encoding_fail() { - let (_root, signer, record) = signed_health(); - let statement = canonical_unsigned_record(&record).unwrap(); - let wrong = signer.sign::(&statement); - assert!( - verify_service_identity_signature::< - GameCultProviderHealthIdentity, - IdunnSignedDaemonHealthPurpose, - >(&signer.trust_anchor().unwrap(), &statement, &wrong) - .is_err() - ); - let other_root = tempfile::tempdir().unwrap(); - let other = enroll_service_identity_at::( - &other_root.path().join("other.cc"), - ) - .unwrap(); - let proof = ServiceIdentitySignature { - identity_id: record.signer_identity_id.clone(), - signature: record.signature.clone(), - }; - assert!( - verify_service_identity_signature::< - GameCultProviderHealthIdentity, - IdunnSignedDaemonHealthPurpose, - >(&other.trust_anchor().unwrap(), &statement, &proof) - .is_err() - ); - let mut mutated = record.clone(); - mutated.state = "failed".into(); - assert!( - verify_service_identity_signature::< - GameCultProviderHealthIdentity, - IdunnSignedDaemonHealthPurpose, - >( - &signer.trust_anchor().unwrap(), - &canonical_unsigned_record(&mutated).unwrap(), - &proof - ) - .is_err() - ); - let named = rmp_serde::to_vec(&serde_json::json!({ - "schema_version": record.schema_version, - "daemon_id": record.daemon_id, - "health_contract": record.health_contract, - "source_runtime_id": record.source_runtime_id, - "state": record.state, - "detail": record.detail, - "signer_identity_id": record.signer_identity_id, - "publisher_incarnation_id": record.publisher_incarnation_id, - "publisher_sequence": record.publisher_sequence, - "observed_at_unix_millis": record.observed_at_unix_millis, - "release_id": record.release_id, - "release_witness_sha256": record.release_witness_sha256, - "source_commit": record.source_commit, - "deployment_id": record.deployment_id, - "signature_algorithm": record.signature_algorithm, - "signature": [], - "private_state_exposed": record.private_state_exposed, - })) - .unwrap(); - assert!( - verify_service_identity_signature::< - GameCultProviderHealthIdentity, - IdunnSignedDaemonHealthPurpose, - >(&signer.trust_anchor().unwrap(), &named, &proof) - .is_err() - ); - } - - #[test] - fn sequence_and_incarnation_are_strict() { - let (_root, signer, record) = signed_health(); - let health = derive_epiphany_aggregate_runtime_health(input(2)).unwrap(); - assert!( - sign_epiphany_runtime_health( - health.clone(), - "epiphany-yggdrasil", - "r", - &format!("sha256-{}", "a".repeat(64)), - &"b".repeat(40), - "d", - "bad", - 2, - &signer - ) - .is_err() - ); - assert!( - sign_epiphany_runtime_health( - health, - "epiphany-yggdrasil", - "r", - &format!("sha256-{}", "a".repeat(64)), - &"b".repeat(40), - "d", - &record.publisher_incarnation_id, - 0, - &signer - ) - .is_err() - ); - } - - #[test] - fn rudp_publisher_emits_exact_canonical_tuple() { - let socket = UdpSocket::bind("127.0.0.1:0").unwrap(); - socket - .set_read_timeout(Some(Duration::from_millis(100))) - .unwrap(); - let endpoint = socket.local_addr().unwrap(); - let server = thread::spawn(move || { - let mut transport = CultNetRudpSocketTransportConnection::new( - CultNetRudpSocketTransportOptions::server( - "idunn-test", - socket, - IDUNN_HEALTH_RUDP_CONNECTION_ID, - ), - ) - .unwrap(); - let deadline = Instant::now() + Duration::from_secs(2); - loop { - if let Some(frame) = transport.receive_once().unwrap() { - return decode_cultnet_message_from_slice( - &frame.payload, - CultNetWireContract::CultNetSchemaV0, - ) - .unwrap(); - } - transport.poll_resends().unwrap(); - assert!(Instant::now() < deadline); - } - }); - let (_root, _signer, record) = signed_health(); - publish_idunn_daemon_health_rudp(endpoint, "epiphany-yggdrasil", &record).unwrap(); - let CultNetMessage::DocumentPutRaw { document, .. } = server.join().unwrap() else { - panic!("wrong message") - }; - assert_eq!(document.schema_id, IDUNN_SIGNED_DAEMON_HEALTH_SCHEMA); - assert_eq!(document.schema_id, record.schema_version); - assert_eq!( - document.source_role.as_deref(), - Some("daemon-health-publisher") - ); - let decoded: IdunnSignedDaemonHealthRecord = - rmp_serde::from_slice(&document.payload).unwrap(); - assert_eq!(decoded, record); - assert_eq!(rmp_serde::to_vec(&decoded).unwrap(), document.payload); - } - - #[test] - fn identity_profile_is_not_host_identity() { - assert_eq!( - GameCultProviderHealthIdentity::TRUST_ANCHOR_SCHEMA, - "gamecult.provider_health_identity.trust_anchor.v1" - ); - assert_ne!( - GameCultProviderHealthIdentity::ID_DOMAIN, - b"epiphany.host-incarnation.identity.v0\0" - ); - } -} diff --git a/epiphany-core/src/imagination_consideration.rs b/epiphany-core/src/imagination_consideration.rs index 2f303540..d6c2bcc4 100644 --- a/epiphany-core/src/imagination_consideration.rs +++ b/epiphany-core/src/imagination_consideration.rs @@ -8,9 +8,6 @@ pub const REQUEST_SCHEMA: &str = "epiphany.self.imagination_consideration_reques pub const REQUEST_CONTRACT: &str = "epiphany.imagination_consideration_request.v1"; pub const CANDIDATE_SCHEMA: &str = "epiphany.imagination.consideration_candidate.v1"; pub const CANDIDATE_CONTRACT: &str = "epiphany.imagination_consideration_candidate.v1"; -pub const LAUNCH_BINDING_SCHEMA: &str = - "epiphany.coordinator.imagination_consideration_launch_binding.v0"; -pub const REVIEW_REQUEST_SCHEMA: &str = "epiphany.self.imagination_consideration_review_request.v0"; #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] @@ -149,52 +146,6 @@ pub struct ImaginationConsiderationCandidate { pub data_classification: String, } -#[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)] -#[cultcache( - type = "epiphany.coordinator.imagination_consideration_launch_binding", - schema = "ImaginationConsiderationLaunchBinding" -)] -pub struct ImaginationConsiderationLaunchBinding { - #[cultcache(key = 0)] - pub schema_version: String, - #[cultcache(key = 1)] - pub binding_record_id: String, - #[cultcache(key = 2)] - pub request_id: String, - #[cultcache(key = 3)] - pub job_id: String, - #[cultcache(key = 4)] - pub binding_id: String, - #[cultcache(key = 5)] - pub runtime_id: String, - #[cultcache(key = 6)] - pub thread_id: String, - #[cultcache(key = 7)] - pub launched_at: String, - #[cultcache(key = 8)] - pub worker_launch_document_sha256: String, -} - -#[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)] -#[cultcache( - type = "epiphany.self.imagination_consideration_review_request", - schema = "ImaginationConsiderationReviewRequest" -)] -pub struct ImaginationConsiderationReviewRequest { - #[cultcache(key = 0)] - pub schema_version: String, - #[cultcache(key = 1)] - pub review_request_id: String, - #[cultcache(key = 2)] - pub candidate_id: String, - #[cultcache(key = 3)] - pub candidate_sha256: String, - #[cultcache(key = 4)] - pub requested_review_route: String, - #[cultcache(key = 5)] - pub selected_at: String, -} - pub fn commit_request( runtime_store: &Path, persona_feedback_store: &Path, @@ -407,52 +358,6 @@ pub fn render_consideration_prompt(request: &ImaginationConsiderationRequest) -> )) } -pub fn request_candidate_modeling_review( - runtime_store: &Path, - request: &ImaginationConsiderationRequest, - candidate: &ImaginationConsiderationCandidate, - selected_at: &str, -) -> Result { - chrono::DateTime::parse_from_rfc3339(selected_at) - .map_err(|_| anyhow!("consideration selection timestamp must be RFC3339"))?; - let mut cache = crate::runtime_spine_cache(runtime_store)?; - cache.pull_all_backing_stores()?; - validate_current_request(&cache, request)?; - validate_candidate(request, candidate)?; - if candidate.disposition != ImaginationConsiderationDisposition::Suggest - || candidate.recommended_review_route != ImaginationConsiderationReviewRoute::ModelingReview - { - bail!("only an explicit suggest/modeling_review candidate may be selected"); - } - let candidate_sha256 = format!("sha256-{:x}", Sha256::digest(rmp_serde::to_vec(candidate)?)); - let review_request_id = format!( - "imagination-consideration-review-{:x}", - Sha256::digest(format!("{}:{candidate_sha256}", request.request_id).as_bytes()) - ); - let review = ImaginationConsiderationReviewRequest { - schema_version: REVIEW_REQUEST_SCHEMA.into(), - review_request_id: review_request_id.clone(), - candidate_id: candidate.candidate_id.clone(), - candidate_sha256, - requested_review_route: "modeling_review_proposal_only".into(), - selected_at: selected_at.into(), - }; - if let Some(existing) = - cache.get::(&review_request_id)? - { - let mut replay = review; - replay.selected_at = existing.selected_at.clone(); - return if replay == existing { - Ok(existing) - } else { - bail!("consideration review request collision") - }; - } - let (entry, _) = cache.prepare_entry(&review_request_id, &review)?; - SingleFileMessagePackBackingStore::new(runtime_store).push(&entry)?; - Ok(review) -} - #[cfg(test)] mod tests { use super::*; diff --git a/epiphany-core/src/lib.rs b/epiphany-core/src/lib.rs index 8f380c45..30dac8b8 100644 --- a/epiphany-core/src/lib.rs +++ b/epiphany-core/src/lib.rs @@ -1,16 +1,12 @@ mod agent_launch; pub mod atlas; mod causal_work_identity; -mod continuity_gateway; mod coordinator_objective_intake; mod coordinator_results; pub mod coordinator_status; mod cultmesh_integration; mod current_work; -mod eyes_gateway; mod hands_gateway; -mod idunn_provider_health; -mod idunn_runtime_health; mod imagination_consideration; mod memory_graph; mod mind_documents; @@ -35,6 +31,7 @@ mod runtime_store_backend; mod runtime_worker_attempt; mod soul_gateway; mod state_ledger; +mod state_model; mod substrate_gate; mod surfaces; @@ -46,7 +43,6 @@ pub use admitted_model_direction_consideration::{ RESULT_CONTRACT as ADMITTED_MODEL_DIRECTION_CONSIDERATION_RESULT_CONTRACT, RESULT_SCHEMA as ADMITTED_MODEL_DIRECTION_CONSIDERATION_RESULT_SCHEMA_VERSION, commit_request as commit_admitted_model_direction_consideration_request, - render_prompt as render_admitted_model_direction_consideration_prompt, result_id_for_launch as admitted_model_direction_consideration_result_id_for_launch, validate_current_request as validate_current_admitted_model_direction_consideration_request, validate_request as validate_admitted_model_direction_consideration_request, @@ -71,22 +67,16 @@ pub use agent_launch::epiphany_frontier_verdict_modeling_output_schema; pub use agent_launch::epiphany_imagination_consideration_output_schema; pub use agent_launch::epiphany_proposal_modeling_output_schema; pub use agent_launch::epiphany_reorient_launch_output_schema; -pub use agent_launch::epiphany_role_binding_id; pub use agent_launch::epiphany_role_label; pub use agent_launch::epiphany_role_launch_output_schema; -pub use agent_launch::epiphany_role_owner; -pub use agent_launch::unique_strings; pub use atlas::*; -pub use causal_work_identity::*; -pub use continuity_gateway::*; -pub use coordinator_objective_intake::USER_OBJECTIVE_INTAKE_CONTRACT; -pub use coordinator_objective_intake::USER_OBJECTIVE_INTAKE_SCHEMA_VERSION; -pub use coordinator_objective_intake::USER_OBJECTIVE_INTAKE_TYPE; +pub(crate) use causal_work_identity::*; pub use coordinator_objective_intake::UserObjectiveIntake; pub use coordinator_objective_intake::UserObjectiveIntakeApplied; pub use coordinator_objective_intake::UserObjectiveIntakeInput; pub use coordinator_objective_intake::intake_user_objective; pub use coordinator_results::EpiphanyCoordinatorReorientResultSnapshot; +pub use coordinator_results::EpiphanyReorientResultStatus; pub use coordinator_results::read_runtime_reorient_result; pub use cultcache_rs::CacheBackingStore; pub use cultcache_rs::CultCache; @@ -100,64 +90,29 @@ pub use cultmesh_integration::EPIPHANY_CANONICAL_SWARM_BRAKE_ID; pub use cultmesh_integration::EPIPHANY_CANONICAL_SWARM_BRAKE_OWNER; pub use cultmesh_integration::{ EPIPHANY_CULTMESH_SWARM_BRAKE_KEY, EPIPHANY_CULTMESH_SWARM_BRAKE_SCHEMA_VERSION, - EPIPHANY_CULTMESH_SWARM_BRAKE_TYPE, EpiphanyCultMeshDocuments, - EpiphanyCultMeshSwarmBrakeEntry, canonical_epiphany_swarm_brake_protected_surfaces, + EPIPHANY_CULTMESH_SWARM_BRAKE_TYPE, EpiphanyCultMeshDocuments, EpiphanyCultMeshSwarmBrakeEntry, default_epiphany_cultmesh_swarm_brake, engage_epiphany_cultmesh_swarm_brake, load_epiphany_cultmesh_swarm_brake, open_epiphany_cultmesh_node, release_epiphany_cultmesh_swarm_brake, write_epiphany_cultmesh_swarm_brake, }; pub use current_work::*; -pub use epiphany_state_model::EpiphanyMemoryAnchor; -pub use epiphany_state_model::EpiphanyMemoryDomain; -pub use epiphany_state_model::EpiphanyMemoryEdge; -pub use epiphany_state_model::EpiphanyMemoryLifecycle; -pub use epiphany_state_model::EpiphanyMemoryNode; -pub use eyes_gateway::EYES_EVIDENCE_PACKET_SCHEMA_VERSION; -pub use eyes_gateway::EYES_EVIDENCE_PACKET_TYPE; -pub use eyes_gateway::EYES_SOURCE_LOOKUP_RECEIPT_SCHEMA_VERSION; -pub use eyes_gateway::EYES_SOURCE_LOOKUP_RECEIPT_TYPE; -pub use eyes_gateway::EyesEvidencePacket; -pub use eyes_gateway::EyesSourceLookupReceipt; -pub use eyes_gateway::eyes_evidence_packet_from_research_finding; pub use hands_gateway::*; -pub use idunn_provider_health::{ - EPIPHANY_IDUNN_PROVIDER_HEALTH_ADMISSION_SCHEMA, EPIPHANY_IDUNN_PROVIDER_HEALTH_ADMISSION_TYPE, - IdunnProviderHealthAdmission, ProviderReleaseBinding, RequiredProviderHealth, - admit_required_idunn_provider_health, provider_health_record_key, - read_idunn_provider_health_trust_anchor, required_idunn_provider_health_query, - verify_idunn_provider_health_candidate, -}; -pub use idunn_runtime_health::{ - CULTNET_RUDP_PROTOCOL_ID, EPIPHANY_IDUNN_RUNTIME_HEALTH_CONTRACT, - EpiphanyAggregateRuntimeHealthInput, IdunnDaemonHealthDocument, - derive_epiphany_aggregate_runtime_health, publish_idunn_daemon_health_rudp, - sign_epiphany_runtime_health, -}; pub use imagination_consideration::{ CANDIDATE_CONTRACT as IMAGINATION_CONSIDERATION_CANDIDATE_CONTRACT, CANDIDATE_SCHEMA as IMAGINATION_CONSIDERATION_CANDIDATE_SCHEMA_VERSION, ImaginationConsiderationCandidate, ImaginationConsiderationDisposition, - ImaginationConsiderationLaunchBinding, ImaginationConsiderationQuestion, - ImaginationConsiderationRequest, ImaginationConsiderationReviewRequest, - ImaginationConsiderationReviewRoute, ImaginationOptionDraft, - LAUNCH_BINDING_SCHEMA as IMAGINATION_CONSIDERATION_LAUNCH_BINDING_SCHEMA_VERSION, - QuotedPersonaFeedbackEvidence, REQUEST_CONTRACT as IMAGINATION_CONSIDERATION_REQUEST_CONTRACT, + ImaginationConsiderationQuestion, ImaginationConsiderationRequest, + ImaginationConsiderationReviewRoute, ImaginationOptionDraft, QuotedPersonaFeedbackEvidence, + REQUEST_CONTRACT as IMAGINATION_CONSIDERATION_REQUEST_CONTRACT, REQUEST_SCHEMA as IMAGINATION_CONSIDERATION_REQUEST_SCHEMA_VERSION, candidate_id_for_launch as imagination_consideration_candidate_id_for_launch, commit_request as commit_imagination_consideration_request, render_consideration_prompt as render_imagination_consideration_prompt, - request_candidate_modeling_review as request_imagination_consideration_modeling_review, validate_candidate as validate_imagination_consideration_candidate, validate_current_request as validate_current_imagination_consideration_request, }; -pub use memory_graph::EpiphanyMemoryEdgeKind; -pub use memory_graph::EpiphanyMemoryGraphSnapshot; pub use memory_graph::EpiphanyMemoryGraphValidationError; -pub use memory_graph::EpiphanyMemoryNodeKind; -pub use memory_graph::RepoFrontierAdoptedPlan; -pub use memory_graph::RepoFrontierItem; -pub use memory_graph::RepoFrontierStatus; pub use memory_graph::validate_memory_graph_snapshot; pub use mind_documents::*; pub use packaged_release::{ @@ -166,23 +121,18 @@ pub use packaged_release::{ EpiphanyPackagedReleaseEntry, EpiphanyPackagedReleaseHead, PackageReleaseRequest, authenticate_epiphany_packaged_release, epiphany_packaged_release_binary_path, epiphany_packaged_release_witness_sha256, inspect_epiphany_packaged_release_witness, - load_epiphany_packaged_release, load_epiphany_packaged_release_head, package_epiphany_release, - publish_epiphany_packaged_release, read_epiphany_packaged_release_witness, - required_packaged_release_binaries, validate_epiphany_packaged_release, - verify_epiphany_packaged_release_files, write_epiphany_packaged_release_witness, + package_epiphany_release, publish_epiphany_packaged_release, }; pub use persona_conversation::*; pub use persona_discord_crossing::*; pub use persona_discord_permit::*; pub use persona_feedback_admission::{ BIFROST_PERSONA_FEEDBACK_ADMISSION_SCHEMA_VERSION, BIFROST_PERSONA_FEEDBACK_DELIVERY_TYPE, - BifrostPersonaFeedbackAdmission, - LOCAL_PERSONA_FEEDBACK_SCHEMA_VERSION, LocalAdmittedPersonaFeedback, - PERSONA_FEEDBACK_SOCIAL_ADMISSION_SCHEMA_VERSION, PersonaFeedbackPacket, - PersonaFeedbackSocialAdmissionReceipt, admit_persona_feedback_to_social, + BifrostPersonaFeedbackAdmission, LOCAL_PERSONA_FEEDBACK_SCHEMA_VERSION, + LocalAdmittedPersonaFeedback, PERSONA_FEEDBACK_SOCIAL_ADMISSION_SCHEMA_VERSION, + PersonaFeedbackPacket, PersonaFeedbackSocialAdmissionReceipt, admit_persona_feedback_to_social, admitted_persona_feedback, import_bifrost_persona_feedback_deliveries, - persona_feedback_ready_for_cognition, - validate_persona_feedback_store_separation, + persona_feedback_ready_for_cognition, validate_persona_feedback_store_separation, }; pub use persona_social_state::*; pub use persona_turn::PERSONA_INTERPRETER_EFFECT_DOCUMENT_SCHEMA_VERSION; @@ -225,20 +175,16 @@ pub use repo_model_gateway::*; pub use repository_body_observer::*; pub use resident_readiness::*; pub use resident_self::*; -pub use runtime_spine::ARCHIVED_RUNTIME_WORKER_ATTEMPT_SCHEMA_VERSION; -pub use runtime_spine::ARCHIVED_RUNTIME_WORKER_ATTEMPT_TYPE; -pub use runtime_spine::COORDINATOR_DEATH_RECOVERY_SCHEMA_VERSION; -pub use runtime_spine::COORDINATOR_RUN_RECEIPT_SCHEMA_VERSION; pub use runtime_spine::COORDINATOR_RUN_RECEIPT_TYPE; pub use runtime_spine::EPIPHANY_RUNTIME_ROOT_SESSION_ID; pub use runtime_spine::EpiphanyArchivedRuntimeWorkerAttempt; pub use runtime_spine::EpiphanyArchivedRuntimeWorkerDecision; pub use runtime_spine::EpiphanyCoordinatorDeathRecovery; +pub use runtime_spine::EpiphanyCoordinatorRunBasis; pub use runtime_spine::EpiphanyCoordinatorRunReceipt; +pub use runtime_spine::EpiphanyCoordinatorRunTerminality; pub use runtime_spine::EpiphanyRuntimeIdentity; pub use runtime_spine::EpiphanyRuntimeJob; -pub use runtime_spine::EpiphanyRuntimeJobResult; -pub use runtime_spine::EpiphanyRuntimeJobSnapshot; pub use runtime_spine::EpiphanyRuntimeJobStatus; pub use runtime_spine::EpiphanyRuntimeModelExecutionBinding; pub use runtime_spine::EpiphanyRuntimeReorientWorkerResult; @@ -252,24 +198,9 @@ pub use runtime_spine::EpiphanyRuntimeWorkerProcessClaim; pub use runtime_spine::ModelPassFailureTerminalOptions; pub use runtime_spine::RUNTIME_IDENTITY_KEY; pub use runtime_spine::RUNTIME_IDENTITY_TYPE; -pub use runtime_spine::RUNTIME_JOB_RESULT_TYPE; -pub use runtime_spine::RUNTIME_JOB_TYPE; -pub use runtime_spine::RUNTIME_MODEL_EXECUTION_BINDING_SCHEMA_VERSION; -pub use runtime_spine::RUNTIME_MODEL_EXECUTION_BINDING_TYPE; -pub use runtime_spine::RUNTIME_REORIENT_WORKER_RESULT_SCHEMA_VERSION; -pub use runtime_spine::RUNTIME_REORIENT_WORKER_RESULT_TYPE; -pub use runtime_spine::RUNTIME_ROLE_WORKER_RESULT_SCHEMA_VERSION; -pub use runtime_spine::RUNTIME_ROLE_WORKER_RESULT_TYPE; -pub use runtime_spine::RUNTIME_SESSION_TYPE; pub use runtime_spine::RUNTIME_SPINE_SCHEMA_VERSION; pub use runtime_spine::RUNTIME_SWARM_BINDING_KEY; pub use runtime_spine::RUNTIME_SWARM_BINDING_SCHEMA_VERSION; -pub use runtime_spine::RUNTIME_TOOL_EXECUTION_BINDING_SCHEMA_VERSION; -pub use runtime_spine::RUNTIME_TOOL_EXECUTION_BINDING_TYPE; -pub use runtime_spine::RUNTIME_WORKER_LAUNCH_REQUEST_SCHEMA_VERSION; -pub use runtime_spine::RUNTIME_WORKER_LAUNCH_REQUEST_TYPE; -pub use runtime_spine::RUNTIME_WORKER_PROCESS_CLAIM_SCHEMA_VERSION; -pub use runtime_spine::RUNTIME_WORKER_PROCESS_CLAIM_TYPE; pub use runtime_spine::RepoFrontierResearchContinuationAction; pub use runtime_spine::RepoFrontierResearchLifecycle; pub use runtime_spine::RepoFrontierResearchLifecycleStage; @@ -277,9 +208,6 @@ pub use runtime_spine::RepoFrontierVerdictModelingLaunchAuthority; pub use runtime_spine::RuntimeSpineHeartbeatJobOptions; pub use runtime_spine::RuntimeSpineInitOptions; pub use runtime_spine::RuntimeSpineJobOptions; -pub use runtime_spine::RuntimeSpineJobResultOptions; -pub use runtime_spine::RuntimeSpineSessionClosureOptions; -pub use runtime_spine::RuntimeSpineSessionOptions; pub use runtime_spine::abandon_unactivated_runtime_worker_process; pub use runtime_spine::activate_runtime_worker_process; pub use runtime_spine::bind_runtime_repository_domain; @@ -287,15 +215,10 @@ pub use runtime_spine::bind_runtime_to_swarm; pub use runtime_spine::canonical_repo_frontier_plan_candidate_id; pub use runtime_spine::claim_runtime_worker_process; pub use runtime_spine::close_runtime_session; -pub use runtime_spine::commit_repo_frontier_modeling_request; pub use runtime_spine::commit_repo_frontier_plan_decision; pub use runtime_spine::commit_repo_frontier_plan_mind_request; -pub use runtime_spine::complete_runtime_job; pub use runtime_spine::coordinator_run_receipts; pub use runtime_spine::coordinator_run_session_id; -pub use runtime_spine::create_runtime_job; -pub use runtime_spine::create_runtime_session; -pub use runtime_spine::ensure_runtime_session; pub use runtime_spine::finalize_coordinator_run; pub use runtime_spine::initialize_runtime_spine; pub use runtime_spine::model_pass_failure_for_request; @@ -304,7 +227,6 @@ pub use runtime_spine::open_runtime_model_execution; pub use runtime_spine::prepare_runtime_spine_heartbeat_job; pub(crate) use runtime_spine::promote_autonomous_direction_options_for_modeling; pub use runtime_spine::put_hands_action_intent; -pub use runtime_spine::put_hands_action_review; pub use runtime_spine::put_hands_command_receipt; pub use runtime_spine::put_hands_commit_receipt; pub use runtime_spine::put_hands_patch_receipt; @@ -320,22 +242,14 @@ pub use runtime_spine::retain_completed_runtime_sessions; pub use runtime_spine::retain_failed_runtime_worker_attempts; pub use runtime_spine::retain_fulfilled_runtime_worker_attempts; pub use runtime_spine::review_repo_frontier_planning_failure; -pub(crate) use runtime_spine::runtime_authenticated_public_source_lookups_for_worker; -pub use runtime_spine::runtime_hands_command_receipt; -pub use runtime_spine::runtime_hands_commit_receipt; -pub use runtime_spine::runtime_hands_patch_receipt; -pub(crate) use runtime_spine::runtime_has_actionable_hands_frontier; -pub use runtime_spine::runtime_job_snapshot; pub use runtime_spine::runtime_identity; -pub use runtime_spine::runtime_registered_document_types; -pub(crate) use runtime_spine::runtime_reorient_worker_result; +pub use runtime_spine::runtime_job; +pub use runtime_spine::runtime_reorient_worker_result; pub use runtime_spine::runtime_repo_frontier_planning_lifecycle; -pub(crate) use runtime_spine::runtime_repo_frontier_research_lifecycle; pub use runtime_spine::runtime_requested_public_source_refs_for_worker; pub use runtime_spine::runtime_role_worker_result; pub use runtime_spine::runtime_spine_cache; pub(crate) use runtime_spine::runtime_typed_request_attempt_exists; -pub(crate) use runtime_spine::runtime_typed_request_fulfillment; pub use runtime_spine::runtime_worker_launch_request; pub use runtime_spine::runtime_worker_process_claim; pub use runtime_spine::runtime_worker_process_claims; @@ -343,6 +257,7 @@ pub use runtime_spine::select_and_commit_repo_frontier_planning_request; pub(crate) use runtime_spine::select_and_commit_repo_frontier_research_request; pub use runtime_spine::select_and_commit_repo_frontier_route; pub use runtime_spine::terminalize_model_pass_failure_session; +pub use runtime_spine::terminalize_runtime_job; pub(crate) use runtime_worker_attempt::{RuntimeTypedRequestRef, WorkerProcessStatus}; pub use soul_gateway::*; pub use state_ledger::EpiphanyBranchRecord; @@ -352,23 +267,17 @@ pub use state_ledger::add_state_branch; pub use state_ledger::append_state_evidence; pub use state_ledger::close_state_branch; pub use state_ledger::load_state_ledger; -pub use state_ledger::state_ledger_status; +pub use state_model::*; pub use substrate_gate::SUBSTRATE_GATE_REPO_ACCESS_GRANT_RECEIPT_SCHEMA_VERSION; -pub use substrate_gate::SUBSTRATE_GATE_REPO_ACCESS_GRANT_RECEIPT_TYPE; pub use substrate_gate::SubstrateGateRepoAccessGrantReceipt; pub use substrate_gate::substrate_gate_coordinator_implementation_grant; pub use substrate_gate::substrate_gate_operation_for_governed_tool; pub use substrate_gate::substrate_gate_repo_access_grant_for_worker; -pub use substrate_gate::substrate_gate_repo_work_planning_grant; pub use surfaces::AdmittedModelDirectionConsiderationContextProjection; pub use surfaces::EpiphanyCoordinatorAction; pub use surfaces::EpiphanyCoordinatorDecision; pub use surfaces::EpiphanyCoordinatorInput; -pub use surfaces::EpiphanyCoordinatorRoleResultStatus; pub use surfaces::EpiphanyCoordinatorRoleStatus; -pub use surfaces::EpiphanyCrrcAction; -pub use surfaces::EpiphanyCrrcResultStatus; -pub use surfaces::EpiphanyReorientAction; pub use surfaces::EpiphanyReorientFindingInterpretation; pub use surfaces::EpiphanyReorientWorkerLaunchDocument; pub use surfaces::EpiphanyResearchDecision; diff --git a/epiphany-core/src/memory_graph/mod.rs b/epiphany-core/src/memory_graph/mod.rs index 3a4d5073..d1620351 100644 --- a/epiphany-core/src/memory_graph/mod.rs +++ b/epiphany-core/src/memory_graph/mod.rs @@ -1,16 +1,5 @@ pub mod validation; -pub use epiphany_state_model::EpiphanyMemoryDomain; -pub use epiphany_state_model::EpiphanyMemoryEdge; -pub use epiphany_state_model::EpiphanyMemoryEdgeKind; -pub use epiphany_state_model::EpiphanyMemoryGraphSnapshot; -pub use epiphany_state_model::EpiphanyMemoryLifecycle; -pub use epiphany_state_model::EpiphanyMemoryNode; -pub use epiphany_state_model::EpiphanyMemoryNodeKind; -pub use epiphany_state_model::RepoFrontierAdoptedPlan; -pub use epiphany_state_model::RepoFrontierItem; -pub use epiphany_state_model::RepoFrontierStatus; - pub use validation::EpiphanyMemoryGraphValidationError; pub(crate) use validation::frontier_item_has_routeable_repository_scope; pub(crate) use validation::repo_paths_are_canonical_and_safe; diff --git a/epiphany-core/src/memory_graph/validation.rs b/epiphany-core/src/memory_graph/validation.rs index 10e4ad8b..40950c7e 100644 --- a/epiphany-core/src/memory_graph/validation.rs +++ b/epiphany-core/src/memory_graph/validation.rs @@ -1,9 +1,9 @@ -use super::EpiphanyMemoryDomain; -use super::EpiphanyMemoryEdge; -use super::EpiphanyMemoryGraphSnapshot; -use super::EpiphanyMemoryLifecycle; -use super::EpiphanyMemoryNode; -use super::RepoFrontierStatus; +use crate::EpiphanyMemoryDomain; +use crate::EpiphanyMemoryEdge; +use crate::EpiphanyMemoryGraphSnapshot; +use crate::EpiphanyMemoryLifecycle; +use crate::EpiphanyMemoryNode; +use crate::RepoFrontierStatus; use std::collections::{HashMap, HashSet}; pub(crate) fn repo_paths_are_canonical_and_safe(paths: &[String]) -> bool { @@ -22,9 +22,7 @@ pub(crate) fn repo_paths_are_canonical_and_safe(paths: &[String]) -> bool { }) } -pub(crate) fn frontier_item_has_routeable_repository_scope( - item: &epiphany_state_model::RepoFrontierItem, -) -> bool { +pub(crate) fn frontier_item_has_routeable_repository_scope(item: &crate::RepoFrontierItem) -> bool { !item.repository_scope.is_empty() && repo_paths_are_canonical_and_safe(&item.repository_scope) } diff --git a/epiphany-core/src/mind_documents.rs b/epiphany-core/src/mind_documents.rs index c3d35de7..6c5ccc2c 100644 --- a/epiphany-core/src/mind_documents.rs +++ b/epiphany-core/src/mind_documents.rs @@ -1,19 +1,17 @@ use std::path::Path; +use crate::state_model::{ + EpiphanyEvidenceRecord, EpiphanyInvariant, EpiphanyInvestigationCheckpoint, EpiphanyModeState, + EpiphanyObservation, EpiphanySubgoal, +}; use anyhow::{Result, anyhow}; use cultcache_rs::{CultCache, CultCacheEnvelope, DatabaseEntry}; -use epiphany_state_model::{ - EpiphanyBacklogItem, EpiphanyEvidenceRecord, EpiphanyInvariant, - EpiphanyInvestigationCheckpoint, EpiphanyModeState, EpiphanyObjectiveDraft, - EpiphanyObservation, EpiphanyPlanningCapture, EpiphanyPlanningState, EpiphanyRoadmapStream, - EpiphanySubgoal, -}; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; use crate::{EpiphanyMindDocumentVersion, runtime_spine_cache}; -pub const MIND_SCHEMA_EPOCH: &str = "epiphany.mind.epoch.v8"; +pub const MIND_SCHEMA_EPOCH: &str = "epiphany.mind.epoch.v11"; pub const MIND_OBJECTIVE_KEY: &str = "objective"; pub const MIND_FOCUS_KEY: &str = "focus"; pub const MIND_MODE_KEY: &str = "mode"; @@ -140,7 +138,7 @@ pub struct EpiphanyMindEvidenceDocument { #[derive(Clone, Debug, PartialEq, DatabaseEntry)] #[cultcache( - type = "epiphany.mind.persona_memory.v1", + type = "epiphany.mind.persona_memory.v2", schema = "EpiphanyMindPersonaMemoryDocument" )] pub struct EpiphanyMindPersonaMemoryDocument { @@ -157,12 +155,8 @@ pub struct EpiphanyMindPersonaMemoryDocument { #[cultcache(key = 5)] pub confidence: f64, #[cultcache(key = 6)] - pub linked_event_ids: Vec, - #[cultcache(key = 7)] - pub linked_relationship_id: Option, - #[cultcache(key = 8)] pub effect_document_id: String, - #[cultcache(key = 9)] + #[cultcache(key = 7)] pub decision_context_id: String, } @@ -297,46 +291,6 @@ pub struct EpiphanyMindInvestigationCheckpointDocument { pub value: EpiphanyInvestigationCheckpoint, } -#[derive(Clone, Debug, PartialEq, DatabaseEntry)] -#[cultcache( - type = "epiphany.mind.planning_capture.v1", - schema = "EpiphanyMindPlanningCaptureDocument" -)] -pub struct EpiphanyMindPlanningCaptureDocument { - #[cultcache(key = 0)] - pub value: EpiphanyPlanningCapture, -} - -#[derive(Clone, Debug, PartialEq, DatabaseEntry)] -#[cultcache( - type = "epiphany.mind.backlog_item.v1", - schema = "EpiphanyMindBacklogItemDocument" -)] -pub struct EpiphanyMindBacklogItemDocument { - #[cultcache(key = 0)] - pub value: EpiphanyBacklogItem, -} - -#[derive(Clone, Debug, PartialEq, DatabaseEntry)] -#[cultcache( - type = "epiphany.mind.roadmap_stream.v1", - schema = "EpiphanyMindRoadmapStreamDocument" -)] -pub struct EpiphanyMindRoadmapStreamDocument { - #[cultcache(key = 0)] - pub value: EpiphanyRoadmapStream, -} - -#[derive(Clone, Debug, PartialEq, DatabaseEntry)] -#[cultcache( - type = "epiphany.mind.objective_draft.v1", - schema = "EpiphanyMindObjectiveDraftDocument" -)] -pub struct EpiphanyMindObjectiveDraftDocument { - #[cultcache(key = 0)] - pub value: EpiphanyObjectiveDraft, -} - #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct EpiphanyMindView { pub schema_epoch: String, @@ -356,7 +310,6 @@ pub struct EpiphanyMindView { pub agent_pass_admission_refusals: Vec, pub investigation_checkpoint: Option, pub mode: Option, - pub planning: EpiphanyPlanningState, pub repository_body_observation: Option, pub repo_model: Option, } @@ -376,10 +329,6 @@ pub(crate) fn register_mind_document_types(cache: &mut CultCache) -> Result<()> cache.register_entry_type::()?; cache.register_entry_type::()?; cache.register_entry_type::()?; - cache.register_entry_type::()?; - cache.register_entry_type::()?; - cache.register_entry_type::()?; - cache.register_entry_type::()?; cache.register_entry_type::()?; crate::repo_model_documents::register_repo_model_document_types(cache)?; cache.register_entry_type::()?; @@ -524,22 +473,6 @@ pub(crate) fn validate_mind_write_envelope(envelope: &CultCacheEnvelope) -> Resu rmp_serde::from_slice::(&envelope.payload)? .value .checkpoint_id - } else if envelope.r#type == EpiphanyMindPlanningCaptureDocument::TYPE { - rmp_serde::from_slice::(&envelope.payload)? - .value - .id - } else if envelope.r#type == EpiphanyMindBacklogItemDocument::TYPE { - rmp_serde::from_slice::(&envelope.payload)? - .value - .id - } else if envelope.r#type == EpiphanyMindRoadmapStreamDocument::TYPE { - rmp_serde::from_slice::(&envelope.payload)? - .value - .id - } else if envelope.r#type == EpiphanyMindObjectiveDraftDocument::TYPE { - rmp_serde::from_slice::(&envelope.payload)? - .value - .id } else if envelope.r#type == EpiphanyMindRepositoryBodyObservationDocument::TYPE { let value: EpiphanyMindRepositoryBodyObservationDocument = rmp_serde::from_slice(&envelope.payload)?; @@ -562,6 +495,10 @@ pub(crate) fn validate_mind_write_envelope(envelope: &CultCacheEnvelope) -> Resu pub fn assemble_mind_view(store_path: impl AsRef) -> Result { let mut cache = runtime_spine_cache(store_path.as_ref())?; cache.pull_all_backing_stores()?; + assemble_mind_view_from_cache(&cache) +} + +pub(crate) fn assemble_mind_view_from_cache(cache: &CultCache) -> Result { let identity = cache .get::(MIND_SCHEMA_EPOCH)? .ok_or_else(|| anyhow!("writable Mind store has no v1 identity"))?; @@ -599,14 +536,6 @@ pub fn assemble_mind_view(store_path: impl AsRef) -> Result(&cache, |value| value.value)?; - let mut captures = - values::(&cache, |value| value.value)?; - let mut backlog_items = - values::(&cache, |value| value.value)?; - let mut roadmap_streams = - values::(&cache, |value| value.value)?; - let mut objective_drafts = - values::(&cache, |value| value.value)?; let mut body_observations = cache.get_all::()?; for observation in &body_observations { observation.validate()?; @@ -629,7 +558,7 @@ pub fn assemble_mind_view(store_path: impl AsRef) -> Result(crate::REPO_MODEL_IDENTITY_KEY)? .is_some() { - Some(crate::assemble_repo_model_view(store_path.as_ref())?) + Some(crate::repo_model_documents::assemble_repo_model_view_from_cache(cache)?) } else { None }; @@ -642,10 +571,6 @@ pub fn assemble_mind_view(store_path: impl AsRef) -> Result) -> Result Result> { - open_epiphany_cultmesh_node(store, runtime_id.to_string())? - .get(&format!("{RELEASE_KEY_PREFIX}{release_id}")) -} - -pub fn load_epiphany_packaged_release_head( - store: &Path, - runtime_id: &str, -) -> Result> { - open_epiphany_cultmesh_node(store, runtime_id.to_string())?.get(RELEASE_HEAD_KEY) -} - pub fn authenticate_epiphany_packaged_release( store: &Path, runtime_id: &str, @@ -75,7 +60,8 @@ pub fn authenticate_epiphany_packaged_release( expected_witness_sha256: &str, ) -> Result { validate_epiphany_packaged_release_sha256(expected_witness_sha256)?; - let entry = load_epiphany_packaged_release(store, runtime_id, release_id)? + let entry = open_epiphany_cultmesh_node(store, runtime_id.to_string())? + .get::(&format!("{RELEASE_KEY_PREFIX}{release_id}"))? .context("packaged release witness is absent")?; validate_epiphany_packaged_release(&entry)?; verify_epiphany_packaged_release_files(&entry)?; diff --git a/epiphany-release-construction/src/lib.rs b/epiphany-core/src/packaged_release/construction.rs similarity index 98% rename from epiphany-release-construction/src/lib.rs rename to epiphany-core/src/packaged_release/construction.rs index d2de779d..858a392d 100644 --- a/epiphany-release-construction/src/lib.rs +++ b/epiphany-core/src/packaged_release/construction.rs @@ -79,7 +79,7 @@ pub struct PackageReleaseRequest<'a> { pub target_triple: &'a str, } -pub fn required_packaged_release_binaries(target_triple: &str) -> Vec<(&'static str, String)> { +fn required_packaged_release_binaries(target_triple: &str) -> Vec<(&'static str, String)> { let file_name = |name: &str| target_binary_file_name(target_triple, name); vec![ ("release-publisher", file_name("epiphany-release")), @@ -572,7 +572,9 @@ fn release_bundle_target_dir( target_root.join(format!("graph-{:x}", digest.finalize())) } -pub fn validate_epiphany_packaged_release(entry: &EpiphanyPackagedReleaseEntry) -> Result<()> { +pub(super) fn validate_epiphany_packaged_release( + entry: &EpiphanyPackagedReleaseEntry, +) -> Result<()> { if entry.schema_version != EPIPHANY_PACKAGED_RELEASE_SCHEMA_VERSION { bail!("unsupported packaged release schema"); } @@ -630,7 +632,9 @@ pub fn validate_epiphany_packaged_release(entry: &EpiphanyPackagedReleaseEntry) Ok(()) } -pub fn verify_epiphany_packaged_release_files(entry: &EpiphanyPackagedReleaseEntry) -> Result<()> { +pub(super) fn verify_epiphany_packaged_release_files( + entry: &EpiphanyPackagedReleaseEntry, +) -> Result<()> { validate_epiphany_packaged_release(entry)?; let root = canonical_path(&entry.package_root).context("packaged release root is absent")?; if root != PathBuf::from(&entry.package_root) { @@ -672,7 +676,7 @@ pub fn verify_epiphany_packaged_release_files(entry: &EpiphanyPackagedReleaseEnt Ok(()) } -pub fn write_epiphany_packaged_release_witness( +fn write_epiphany_packaged_release_witness( path: &Path, entry: &EpiphanyPackagedReleaseEntry, ) -> Result<()> { @@ -686,7 +690,7 @@ pub fn write_epiphany_packaged_release_witness( }) } -pub fn read_epiphany_packaged_release_witness(path: &Path) -> Result { +fn read_epiphany_packaged_release_witness(path: &Path) -> Result { let bytes = fs::read(path) .with_context(|| format!("failed to read packaged release witness {}", path.display()))?; let entry = rmp_serde::from_slice(&bytes).with_context(|| { @@ -727,7 +731,7 @@ pub fn epiphany_packaged_release_witness_sha256( witness_sha256(entry) } -pub fn validate_epiphany_packaged_release_sha256(value: &str) -> Result<()> { +pub(super) fn validate_epiphany_packaged_release_sha256(value: &str) -> Result<()> { validate_sha256(value) } diff --git a/epiphany-core/src/persona_conversation.rs b/epiphany-core/src/persona_conversation.rs index dc317ace..d27f9e59 100644 --- a/epiphany-core/src/persona_conversation.rs +++ b/epiphany-core/src/persona_conversation.rs @@ -21,10 +21,8 @@ use crate::{ verify_persona_discord_delivery_receipt, }; -pub const PERSONA_DISCORD_DELIVERY_EVIDENCE_SCHEMA_VERSION: &str = - "epiphany.persona_discord_delivery_evidence.v0"; pub const PERSONA_CONVERSATION_EXECUTION_RECEIPT_SCHEMA_VERSION: &str = - "epiphany.persona_conversation_execution_receipt.v1"; + "epiphany.persona_conversation_execution_receipt.v2"; pub const PERSONA_EFFECT_EXECUTION_INTENT_SCHEMA_VERSION: &str = "epiphany.persona_effect_execution_intent.v0"; pub const PERSONA_CONVERSATION_STORE_RETIREMENT_RECEIPT_SCHEMA_VERSION: &str = @@ -150,37 +148,7 @@ pub struct PersonaEffectExecutionIntent { #[derive(Clone, Debug, PartialEq, DatabaseEntry)] #[cultcache( - type = "epiphany.persona_discord_delivery_evidence.v0", - schema = "PersonaDiscordDeliveryEvidence" -)] -pub struct PersonaDiscordDeliveryEvidence { - #[cultcache(key = 0)] - pub schema_version: String, - #[cultcache(key = 1)] - pub evidence_id: String, - #[cultcache(key = 2)] - pub effect_document_id: String, - #[cultcache(key = 3)] - pub channel_id: String, - #[cultcache(key = 4, default)] - pub reply_to_message_id: Option, - #[cultcache(key = 5)] - pub message_id: String, - #[cultcache(key = 6)] - pub transport: String, - #[cultcache(key = 7)] - pub crossing_receipt_id: String, - #[cultcache(key = 8)] - pub receipt_url: String, - #[cultcache(key = 9)] - pub bridge_receipt_sha256: String, - #[cultcache(key = 10)] - pub private_state_exposed: bool, -} - -#[derive(Clone, Debug, PartialEq, DatabaseEntry)] -#[cultcache( - type = "epiphany.persona_conversation_execution_receipt.v1", + type = "epiphany.persona_conversation_execution_receipt.v2", schema = "PersonaConversationExecutionReceipt" )] pub struct PersonaConversationExecutionReceipt { @@ -198,8 +166,6 @@ pub struct PersonaConversationExecutionReceipt { pub state_effect_status: String, #[cultcache(key = 6, default)] pub state_effect_reasons: Vec, - #[cultcache(key = 7, default)] - pub delivery_evidence_ids: Vec, #[cultcache(key = 8)] pub social_terminal_receipt_id: Option, #[cultcache(key = 9)] @@ -289,7 +255,7 @@ pub fn poll_persona_discord_crossing( PersonaTurnTerminalOptions { request_id: request_id.into(), outcome: outcome.into(), - delivery_evidence: None, + delivery_receipt: None, blocked_evidence: None, }, )?; @@ -301,7 +267,6 @@ pub fn poll_persona_discord_crossing( outcome: outcome.into(), state_effect_status: state_status, state_effect_reasons: reasons, - delivery_evidence_ids: vec![], social_terminal_receipt_id: Some(terminal.receipt_id), private_state_exposed: false, model_terminal_receipt_id: Some(model_terminal.receipt_id.clone()), @@ -379,32 +344,12 @@ pub fn poll_persona_discord_crossing( ), _ => unreachable!(), }; - let evidence = (outcome == "delivered").then(|| PersonaDiscordDeliveryEvidence { - schema_version: PERSONA_DISCORD_DELIVERY_EVIDENCE_SCHEMA_VERSION.into(), - evidence_id: format!( - "persona-delivery:{}:{}", - effects.document_id, delivery.message_id - ), - effect_document_id: effects.document_id.clone(), - channel_id: delivery.channel_id.clone(), - reply_to_message_id: (!delivery.reply_to_message_id.is_empty()) - .then(|| delivery.reply_to_message_id.clone()), - message_id: delivery.message_id.clone(), - transport: delivery.transport.clone(), - crossing_receipt_id: delivery.crossing_receipt_id.clone(), - receipt_url: delivery.receipt_url.clone(), - bridge_receipt_sha256: signed_receipt_sha256, - private_state_exposed: false, - }); - if let Some(value) = &evidence { - put_runtime_document(runtime_store, &value.evidence_id, value)?; - } let terminal = complete_persona_social_turn( social_store, PersonaTurnTerminalOptions { request_id: request_id.into(), outcome: outcome.into(), - delivery_evidence: evidence.clone(), + delivery_receipt: (outcome == "delivered").then(|| delivery.clone()), blocked_evidence, }, )?; @@ -422,10 +367,6 @@ pub fn poll_persona_discord_crossing( outcome: outcome.into(), state_effect_status: "admitted_before_delivery_request".into(), state_effect_reasons: vec![], - delivery_evidence_ids: evidence - .iter() - .map(|value| value.evidence_id.clone()) - .collect(), social_terminal_receipt_id: Some(terminal.receipt_id), private_state_exposed: false, model_terminal_receipt_id: Some(model_terminal.receipt_id.clone()), @@ -448,7 +389,7 @@ fn terminalize_local_effect_quarantine( PersonaTurnTerminalOptions { request_id: request.request_id.clone(), outcome: "blocked".into(), - delivery_evidence: None, + delivery_receipt: None, blocked_evidence: Some(crate::PersonaTurnBlockedEvidence { evidence_source: "local_effect".into(), crossing_status: "unknown".into(), @@ -466,7 +407,6 @@ fn terminalize_local_effect_quarantine( outcome: "blocked".into(), state_effect_status: "quarantined_ambiguous_local_effect".into(), state_effect_reasons: vec![error.to_string()], - delivery_evidence_ids: vec![], social_terminal_receipt_id: Some(terminal.receipt_id), private_state_exposed: false, model_terminal_receipt_id: Some(model_terminal.receipt_id), @@ -519,7 +459,6 @@ pub fn reconcile_terminal_persona_conversation( outcome: terminal.outcome.clone(), state_effect_status: "reconciled_from_social_terminal".into(), state_effect_reasons: vec![], - delivery_evidence_ids: terminal.delivery_evidence_id.clone().into_iter().collect(), social_terminal_receipt_id: Some(terminal.receipt_id.clone()), private_state_exposed: false, model_terminal_receipt_id: Some(model_terminal.receipt_id), @@ -756,22 +695,6 @@ fn build_persona_retention_member( } runtime_ids.push((::TYPE, id)); } - if let Some(evidence_id) = &terminal.delivery_evidence_id { - let Some(evidence) = - runtime_document::(runtime_store, evidence_id)? - else { - return Ok(None); - }; - if conversation.delivery_evidence_ids != [evidence_id.clone()] - || evidence.private_state_exposed - { - return Err(anyhow!("Persona retained delivery evidence is invalid")); - } - } else if terminal.outcome == "delivered" || !conversation.delivery_evidence_ids.is_empty() { - return Err(anyhow!( - "delivered Persona retention candidate lacks evidence" - )); - } let mut runtime_cache = runtime_spine_cache(runtime_store)?; runtime_cache.pull_all_backing_stores()?; let runtime_snapshot = runtime_cache.snapshot_envelopes(); @@ -801,7 +724,16 @@ fn build_persona_retention_member( &crossing_request, receipt_anchor, )?; - if crossing_receipt.status != "completed" { + let receipt_sha256 = format!( + "sha256:{:x}", + Sha256::digest(rmp_serde::to_vec(&crossing_receipt)?) + ); + if crossing_receipt.status != "completed" + || terminal.delivery_message_id.as_deref() != Some(crossing_receipt.message_id.as_str()) + || terminal.crossing_receipt_id.as_deref() + != Some(crossing_receipt.crossing_receipt_id.as_str()) + || terminal.bridge_receipt_sha256.as_deref() != Some(receipt_sha256.as_str()) + { return Ok(None); } // The signed request and consequence receipt remain durable audit @@ -1026,7 +958,7 @@ fn require_store_retirement_receipt( })?; let mut cache = CultCache::new(); cache.register_entry_type::()?; - cache.load_envelope::(envelope.clone())?; + cache.put_envelope::(envelope.clone())?; if cache .get::(&expected.receipt_id)? .as_ref() @@ -1110,8 +1042,6 @@ pub(crate) fn admit_persona_state_notes( summary: summary.clone(), salience: 0.7, confidence: confidence.unwrap_or(0.7), - linked_event_ids: vec![document.document_id.clone()], - linked_relationship_id: None, effect_document_id: document.document_id.clone(), decision_context_id: document.decision_context_id.clone(), }; @@ -1530,7 +1460,6 @@ mod tests { outcome: "dropped".into(), state_effect_status: "none".into(), state_effect_reasons: vec![], - delivery_evidence_ids: vec![], social_terminal_receipt_id: Some(terminal.receipt_id), private_state_exposed: false, model_terminal_receipt_id: Some(format!("persona-terminal:{request_id}")), @@ -1898,7 +1827,6 @@ mod tests { outcome: "dropped".into(), state_effect_status: "none".into(), state_effect_reasons: vec![], - delivery_evidence_ids: vec![], social_terminal_receipt_id: Some("retry:terminal".into()), private_state_exposed: false, model_terminal_receipt_id: Some("persona-terminal:retry".into()), diff --git a/epiphany-core/src/persona_feedback_admission.rs b/epiphany-core/src/persona_feedback_admission.rs index dd477ed0..8a5ae614 100644 --- a/epiphany-core/src/persona_feedback_admission.rs +++ b/epiphany-core/src/persona_feedback_admission.rs @@ -1042,7 +1042,7 @@ mod tests { let delivery = signed_delivery(&signer, "epiphany")?; let mut source = CultCache::new(); source.register_entry_type::()?; - source.add_generic_backing_store(SingleFileMessagePackBackingStore::new(&source_store)); + source.add_generic_backing_store(SingleFileMessagePackBackingStore::new(&source_store))?; source.put(&delivery.admission_id, &delivery)?; let source_lock = source_store.with_file_name(format!( "{}.lock", diff --git a/epiphany-core/src/persona_social_state.rs b/epiphany-core/src/persona_social_state.rs index a2d955a4..a755d7ff 100644 --- a/epiphany-core/src/persona_social_state.rs +++ b/epiphany-core/src/persona_social_state.rs @@ -10,7 +10,7 @@ use std::path::Path; pub const PERSONA_TURN_REQUEST_SCHEMA_VERSION: &str = "epiphany.persona_turn_request.v1"; pub const PERSONA_TURN_TERMINAL_RECEIPT_SCHEMA_VERSION: &str = - "epiphany.persona_turn_terminal_receipt.v1"; + "epiphany.persona_turn_terminal_receipt.v2"; pub const PERSONA_CONVERSATION_RETENTION_HEAD_SCHEMA_VERSION: &str = "epiphany.persona_conversation_retention_head.v1"; pub const PERSONA_CONVERSATION_RETENTION_PLAN_SCHEMA_VERSION: &str = @@ -21,7 +21,6 @@ pub const PERSONA_CONVERSATION_RETENTION_PLAN_SCHEMA_VERSION: &str = pub struct PersonaSocialMention { pub id: String, pub target_role_id: String, - pub target_agent_id: String, pub source_surface: String, pub channel_id: String, pub message_id: String, @@ -73,7 +72,7 @@ pub struct PersonaTurnTerminalReceipt { pub mention_ids: Vec, pub mention_cargo_sha256: String, #[serde(default)] - pub delivery_evidence_id: Option, + pub delivery_message_id: Option, #[serde(default)] pub crossing_receipt_id: Option, #[serde(default)] @@ -90,7 +89,7 @@ pub struct PersonaTurnTerminalReceipt { pub struct PersonaTurnTerminalOptions { pub request_id: String, pub outcome: String, - pub delivery_evidence: Option, + pub delivery_receipt: Option, pub blocked_evidence: Option, } @@ -146,30 +145,9 @@ pub struct PersonaTurnBlockedEvidence { pub bridge_receipt_sha256: Option, } -#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct PersonaBlockedConversationPressure { - pub schema_version: String, - pub quarantine_id: String, - pub request_id: String, - pub terminal_receipt_id: String, - pub crossing_status: String, - pub evidence_source: String, - pub reason: String, - pub mentions: Vec, - pub mention_cargo_sha256: String, - #[serde(default)] - pub crossing_receipt_id: Option, - #[serde(default)] - pub bridge_receipt_sha256: Option, - pub quarantined_at: String, - pub private_state_exposed: bool, -} - -pub const PERSONA_SOCIAL_MENTION_TYPE: &str = "epiphany.persona.social_mention.v1"; +pub const PERSONA_SOCIAL_MENTION_TYPE: &str = "epiphany.persona.social_mention.v2"; pub const PERSONA_SOCIAL_TURN_REQUEST_TYPE: &str = "epiphany.persona.turn_request.v1"; -pub const PERSONA_SOCIAL_TURN_TERMINAL_TYPE: &str = "epiphany.persona.turn_terminal.v1"; -pub const PERSONA_SOCIAL_QUARANTINE_TYPE: &str = "epiphany.persona.quarantine.v1"; +pub const PERSONA_SOCIAL_TURN_TERMINAL_TYPE: &str = "epiphany.persona.turn_terminal.v2"; pub const PERSONA_SOCIAL_RETENTION_HEAD_TYPE: &str = "epiphany.persona.retention_head.v1"; pub const PERSONA_SOCIAL_RETENTION_PLAN_TYPE: &str = "epiphany.persona.retention_plan.v1"; pub const PERSONA_SOCIAL_RETENTION_HEAD_KEY: &str = "persona"; @@ -196,7 +174,7 @@ pub struct PersonaSocialQueueMentionOptions { #[derive(Clone, Debug, PartialEq, DatabaseEntry)] #[cultcache( - type = "epiphany.persona.social_mention.v1", + type = "epiphany.persona.social_mention.v2", schema = "PersonaSocialMentionDocument" )] pub struct PersonaSocialMentionDocument { @@ -220,7 +198,7 @@ pub struct PersonaSocialTurnRequestDocument { #[derive(Clone, Debug, PartialEq, DatabaseEntry)] #[cultcache( - type = "epiphany.persona.turn_terminal.v1", + type = "epiphany.persona.turn_terminal.v2", schema = "PersonaSocialTurnTerminalDocument" )] pub struct PersonaSocialTurnTerminalDocument { @@ -228,16 +206,6 @@ pub struct PersonaSocialTurnTerminalDocument { pub receipt: PersonaTurnTerminalReceipt, } -#[derive(Clone, Debug, PartialEq, DatabaseEntry)] -#[cultcache( - type = "epiphany.persona.quarantine.v1", - schema = "PersonaSocialQuarantineDocument" -)] -pub struct PersonaSocialQuarantineDocument { - #[cultcache(key = 0)] - pub pressure: PersonaBlockedConversationPressure, -} - #[derive(Clone, Debug, PartialEq, DatabaseEntry)] #[cultcache( type = "epiphany.persona.retention_head.v1", @@ -265,6 +233,11 @@ pub fn persona_social_cache(store_path: impl AsRef) -> Result { register_persona_social_types(&mut cache)?; let mut identities = HashSet::new(); for envelope in SingleFileMessagePackBackingStore::new(store_path.as_ref()).pull_all()? { + if envelope.r#type == "epiphany.persona.social_mention.v1" { + return Err(anyhow!( + "Persona social store contains an obsolete writable mention epoch" + )); + } if !is_persona_social_type(&envelope.r#type) { continue; } @@ -320,7 +293,6 @@ pub fn queue_persona_social_mention( mention: PersonaSocialMention { id: mention_id.clone(), target_role_id: options.target_role_id.clone(), - target_agent_id: "epiphany.Persona".into(), source_surface: options.source_surface, channel_id: options.channel_id, message_id: options.message_id, @@ -522,30 +494,33 @@ pub fn complete_persona_social_turn( existing.outcome )); } - let delivery_evidence = options.delivery_evidence.as_ref(); + let delivery_receipt = options.delivery_receipt.as_ref(); let blocked_evidence = options.blocked_evidence.as_ref(); if options.outcome == "delivered" { - let evidence = delivery_evidence.ok_or_else(|| { - anyhow!("delivered Persona turn requires typed Discord delivery evidence") + let receipt = delivery_receipt.ok_or_else(|| { + anyhow!("delivered Persona turn requires a typed Discord delivery receipt") })?; - if evidence.schema_version != crate::PERSONA_DISCORD_DELIVERY_EVIDENCE_SCHEMA_VERSION - || evidence.private_state_exposed - || evidence.evidence_id.trim().is_empty() - || evidence.message_id.trim().is_empty() - || evidence.crossing_receipt_id.trim().is_empty() - || evidence.bridge_receipt_sha256.trim().is_empty() + if receipt.schema_version != crate::PERSONA_DISCORD_DELIVERY_RECEIPT_SCHEMA_VERSION + || receipt.status != "completed" + || receipt.private_state_exposed + || receipt.receipt_id != receipt.request_id + || !receipt + .request_id + .starts_with(&format!("persona-discord:{}:", request.request_id)) + || receipt.message_id.trim().is_empty() + || receipt.crossing_receipt_id.trim().is_empty() || !request .mentions .iter() - .any(|mention| mention.channel_id == evidence.channel_id) + .any(|mention| mention.channel_id == receipt.channel_id) { return Err(anyhow!( - "Discord delivery evidence is not bound to the Persona turn" + "Discord delivery receipt is not bound to the Persona turn" )); } - } else if delivery_evidence.is_some() { + } else if delivery_receipt.is_some() { return Err(anyhow!( - "non-delivered Persona terminal outcome must not carry delivery evidence" + "non-delivered Persona terminal outcome must not carry a delivery receipt" )); } if options.outcome == "blocked" { @@ -596,10 +571,12 @@ pub fn complete_persona_social_turn( "sha256-{:x}", sha2::Sha256::digest(rmp_serde::to_vec(&request.mentions)?) ), - delivery_evidence_id: delivery_evidence.map(|evidence| evidence.evidence_id.clone()), - crossing_receipt_id: delivery_evidence.map(|evidence| evidence.crossing_receipt_id.clone()), - bridge_receipt_sha256: delivery_evidence - .map(|evidence| evidence.bridge_receipt_sha256.clone()), + delivery_message_id: delivery_receipt.map(|receipt| receipt.message_id.clone()), + crossing_receipt_id: delivery_receipt.map(|receipt| receipt.crossing_receipt_id.clone()), + bridge_receipt_sha256: delivery_receipt + .map(rmp_serde::to_vec) + .transpose()? + .map(|bytes| format!("sha256:{:x}", sha2::Sha256::digest(bytes))), blocked_crossing_status: blocked_evidence.map(|evidence| evidence.crossing_status.clone()), blocked_reason: blocked_evidence.map(|evidence| evidence.reason.clone()), completed_at: now_iso(), @@ -644,33 +621,6 @@ pub fn complete_persona_social_turn( document.terminal_receipt_id = Some(receipt.receipt_id.clone()); writes.push(cache.prepare_entry(&mention.id, &document)?.0); } - if mention_disposition == "quarantined" { - let evidence = blocked_evidence.expect("validated blocked evidence"); - let pressure = PersonaBlockedConversationPressure { - schema_version: "epiphany.persona_blocked_conversation_pressure.v1".into(), - quarantine_id: format!("{}:quarantine", request.request_id), - request_id: request.request_id.clone(), - terminal_receipt_id: receipt.receipt_id.clone(), - crossing_status: evidence.crossing_status.clone(), - evidence_source: evidence.evidence_source.clone(), - reason: evidence.reason.clone(), - mentions: request.mentions.clone(), - mention_cargo_sha256: receipt.mention_cargo_sha256.clone(), - crossing_receipt_id: evidence.crossing_receipt_id.clone(), - bridge_receipt_sha256: evidence.bridge_receipt_sha256.clone(), - quarantined_at: receipt.completed_at.clone(), - private_state_exposed: false, - }; - let quarantine_id = pressure.quarantine_id.clone(); - writes.push( - cache - .prepare_entry( - &quarantine_id, - &PersonaSocialQuarantineDocument { pressure }, - )? - .0, - ); - } if !persona_social_backing(store_path).compare_and_swap_batch(&expected, writes)? { return Err(anyhow!( "Persona terminal decision lost exact document compare-and-swap" @@ -742,19 +692,6 @@ pub fn persona_turn_request_source( crate::EpiphanyMindDocumentVersion::from_envelope("epiphany-persona-social", &envelope) } -pub fn blocked_persona_pressures( - store_path: impl AsRef, -) -> Result> { - let cache = persona_social_cache(store_path)?; - let mut pressures = cache - .get_all::()? - .into_iter() - .map(|document| document.pressure) - .collect::>(); - pressures.sort_by(|left, right| left.quarantine_id.cmp(&right.quarantine_id)); - Ok(pressures) -} - pub fn persona_retention_head( store_path: impl AsRef, ) -> Result> { @@ -890,7 +827,6 @@ fn register_persona_social_types(cache: &mut CultCache) -> Result<()> { cache.register_entry_type::()?; cache.register_entry_type::()?; cache.register_entry_type::()?; - cache.register_entry_type::()?; cache.register_entry_type::()?; cache.register_entry_type::()?; Ok(()) @@ -901,7 +837,6 @@ fn is_persona_social_type(document_type: &str) -> bool { PERSONA_SOCIAL_MENTION_TYPE, PERSONA_SOCIAL_TURN_REQUEST_TYPE, PERSONA_SOCIAL_TURN_TERMINAL_TYPE, - PERSONA_SOCIAL_QUARANTINE_TYPE, PERSONA_SOCIAL_RETENTION_HEAD_TYPE, PERSONA_SOCIAL_RETENTION_PLAN_TYPE, ] @@ -911,22 +846,19 @@ fn is_persona_social_type(document_type: &str) -> bool { fn load_persona_social_envelope(cache: &mut CultCache, envelope: CultCacheEnvelope) -> Result<()> { match envelope.r#type.as_str() { PERSONA_SOCIAL_MENTION_TYPE => { - cache.load_envelope::(envelope)?; + cache.put_envelope::(envelope)?; } PERSONA_SOCIAL_TURN_REQUEST_TYPE => { - cache.load_envelope::(envelope)?; + cache.put_envelope::(envelope)?; } PERSONA_SOCIAL_TURN_TERMINAL_TYPE => { - cache.load_envelope::(envelope)?; - } - PERSONA_SOCIAL_QUARANTINE_TYPE => { - cache.load_envelope::(envelope)?; + cache.put_envelope::(envelope)?; } PERSONA_SOCIAL_RETENTION_HEAD_TYPE => { - cache.load_envelope::(envelope)?; + cache.put_envelope::(envelope)?; } PERSONA_SOCIAL_RETENTION_PLAN_TYPE => { - cache.load_envelope::(envelope)?; + cache.put_envelope::(envelope)?; } _ => unreachable!("caller filtered Persona social document types"), } @@ -959,3 +891,50 @@ fn stable_pending_mention_id( fn now_iso() -> String { chrono::Utc::now().to_rfc3339() } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn persona_social_cache_reads_without_writing_the_store() -> Result<()> { + let temp = tempfile::tempdir()?; + let store = temp.path().join("social.cc"); + let cache = persona_social_cache(&store)?; + let head = PersonaSocialRetentionHeadDocument { + head: PersonaConversationRetentionHead::default(), + }; + SingleFileMessagePackBackingStore::new(&store).push( + &cache + .prepare_entry(PERSONA_SOCIAL_RETENTION_HEAD_KEY, &head)? + .0, + )?; + let past = std::time::UNIX_EPOCH + std::time::Duration::from_secs(1_000_000_000); + std::fs::File::options() + .write(true) + .open(&store)? + .set_modified(past)?; + let listing = || -> Result> { + let mut entries = std::fs::read_dir(temp.path())? + .map(|entry| { + let entry = entry?; + let metadata = entry.metadata()?; + Ok((entry.file_name(), metadata.len(), metadata.modified()?)) + }) + .collect::>>()?; + entries.sort(); + Ok(entries) + }; + let before = listing()?; + let names = before.iter().map(|entry| entry.0.clone()).collect::>(); + assert_eq!(names, ["social.cc", "social.cc.lock"]); + let reopened = persona_social_cache(&store)?; + assert_eq!( + reopened + .get::(PERSONA_SOCIAL_RETENTION_HEAD_KEY)?, + Some(head) + ); + assert_eq!(listing()?, before, "a read attached or wrote a store"); + Ok(()) + } +} diff --git a/epiphany-core/src/persona_turn.rs b/epiphany-core/src/persona_turn.rs index 71d19a02..205f9797 100644 --- a/epiphany-core/src/persona_turn.rs +++ b/epiphany-core/src/persona_turn.rs @@ -574,7 +574,6 @@ fn fallback<'a>(value: &'a str, fallback: &'a str) -> &'a str { mod tests { use super::*; - #[test] fn typed_interpreter_effects_reject_channel_escape_and_mixed_drop() { let valid = r#"{"schemaVersion":"epiphany.persona_interpreter_effect_set.v0","effects":[{"kind":"state_note","memory_kind":"memory","summary":"The operator expects a native conversational nerve."},{"kind":"say","channel_id":"aquarium","content":"The nerve is live.","speech_act":"status","register":"concise","safety_notes":[]}]}"#; diff --git a/epiphany-core/src/process_observation.rs b/epiphany-core/src/process_observation.rs index 61717a32..728d40fc 100644 --- a/epiphany-core/src/process_observation.rs +++ b/epiphany-core/src/process_observation.rs @@ -109,8 +109,8 @@ mod platform { use chrono::{DateTime, SecondsFormat, Utc}; use std::os::windows::ffi::OsStringExt; use windows_sys::Win32::Foundation::{ - CloseHandle, ERROR_ACCESS_DENIED, ERROR_NO_MORE_FILES, FILETIME, - GetLastError, INVALID_HANDLE_VALUE, WAIT_FAILED, WAIT_OBJECT_0, WAIT_TIMEOUT, + CloseHandle, ERROR_ACCESS_DENIED, ERROR_NO_MORE_FILES, FILETIME, GetLastError, + INVALID_HANDLE_VALUE, WAIT_FAILED, WAIT_OBJECT_0, WAIT_TIMEOUT, }; use windows_sys::Win32::System::Diagnostics::ToolHelp::{ CreateToolhelp32Snapshot, PROCESSENTRY32W, Process32FirstW, Process32NextW, @@ -359,17 +359,6 @@ mod platform { use super::*; use chrono::{DateTime, SecondsFormat, Utc}; - fn is_missing_error(error: &anyhow::Error) -> bool { - error.chain().any(|cause| { - cause - .downcast_ref::() - .is_some_and(|error| { - error.kind() == std::io::ErrorKind::NotFound - || error.raw_os_error() == Some(libc::ESRCH) - }) - }) - } - fn stat(process_id: u32) -> Result<(u64, char)> { let raw = std::fs::read_to_string(format!("/proc/{process_id}/stat")) .with_context(|| format!("read /proc/{process_id}/stat"))?; @@ -386,6 +375,14 @@ mod platform { Ok((starttime, state)) } + fn is_missing_error(error: &anyhow::Error) -> bool { + error.chain().any(|source| { + source + .downcast_ref::() + .is_some_and(|error| error.kind() == std::io::ErrorKind::NotFound) + }) + } + fn created_at_rfc3339(starttime: u64) -> Result { let ticks_per_second = unsafe { libc::sysconf(libc::_SC_CLK_TCK) }; if ticks_per_second <= 0 { @@ -557,21 +554,6 @@ mod tests { )); } - #[cfg(target_os = "linux")] - #[test] - fn absent_linux_pid_is_missing_not_indeterminate() { - let identity = ProcessInstanceIdentity { - process_id: u32::MAX, - creation_token: 1, - created_at_rfc3339: None, - executable_path: PathBuf::from("/definitely/absent"), - }; - assert_eq!( - observe_process_instance(&identity), - ProcessInstanceObservation::Missing - ); - } - #[test] fn child_exit_is_observed_for_the_exact_instance() { let mut child = if cfg!(windows) { diff --git a/epiphany-core/src/reasoning_context.rs b/epiphany-core/src/reasoning_context.rs index d656cd35..11f5a8a4 100644 --- a/epiphany-core/src/reasoning_context.rs +++ b/epiphany-core/src/reasoning_context.rs @@ -6,8 +6,9 @@ use crate::{ }; use anyhow::{Result, anyhow}; use cultcache_rs::{CacheBackingStore, CultCache, CultCacheEnvelope, DatabaseEntry}; -use epiphany_model_adapter::EpiphanyModelRequest; -use epiphany_openai_adapter::EpiphanyOpenAiModelRequest; +#[cfg(test)] +use epiphany_model_adapter::EpiphanyProviderRequestPayload; +use epiphany_model_adapter::{EpiphanyModelRequest, EpiphanyProviderRequest}; use epiphany_tool_adapter::{ EpiphanyToolInvocationIntent, EpiphanyToolInvocationReceipt, receipt_output_for_model, tool_invocation_intent_key, tool_invocation_receipt_key, @@ -18,13 +19,9 @@ use std::collections::BTreeSet; use std::path::Path; pub const REASONING_BASIS_SCHEMA_VERSION: &str = "epiphany.reasoning_basis.v1"; -pub const DECISION_CONTEXT_SCHEMA_VERSION: &str = "epiphany.decision_context.v2"; +pub const DECISION_CONTEXT_SCHEMA_VERSION: &str = "epiphany.decision_context.v3"; pub const MODEL_PASS_FAILURE_SCHEMA_VERSION: &str = "epiphany.model_pass_failure.v1"; pub const MIND_COMMIT_RECEIPT_SCHEMA_VERSION: &str = "epiphany.mind_commit_receipt.v1"; -pub const REASONING_BASIS_TYPE: &str = "epiphany.reasoning_basis.v1"; -pub const DECISION_CONTEXT_TYPE: &str = "epiphany.decision_context.v2"; -pub const MODEL_PASS_FAILURE_TYPE: &str = "epiphany.model_pass_failure.v1"; -pub const MIND_COMMIT_RECEIPT_TYPE: &str = "epiphany.mind_commit_receipt.v1"; pub const DECISION_AUDIT_PROJECTION_SCHEMA_VERSION: &str = "epiphany.decision_audit_projection.v1"; pub const DECISION_AUDIT_INDEX_SCHEMA_VERSION: &str = "epiphany.decision_audit_index.v1"; pub const WORKER_REASONING_PROJECTION_POLICY: &str = @@ -72,10 +69,10 @@ impl From for EpiphanyRolePassAuthorityProject pub struct EpiphanyRepoModelPromptProjection { pub identity: crate::EpiphanyRepoModelIdentityDocument, pub projection_digest: String, - pub domains: Vec, - pub nodes: Vec, - pub edges: Vec, - pub frontier: Vec, + pub domains: Vec, + pub nodes: Vec, + pub edges: Vec, + pub frontier: Vec, pub claim_obligations: Vec, pub surface_offers: Vec, pub dependency_claims: Vec, @@ -108,17 +105,16 @@ pub struct EpiphanyMindPromptProjection { pub projection_digest: String, pub objective: Option, pub active_subgoal_id: Option, - pub subgoals: Vec, - pub invariants: Vec, - pub observations: Vec, - pub evidence: Vec, + pub subgoals: Vec, + pub invariants: Vec, + pub observations: Vec, + pub evidence: Vec, pub verification_audits: Vec, pub reorientation_decisions: Vec, pub reorientation_failures: Vec, pub agent_pass_admission_refusals: Vec, - pub investigation_checkpoint: Option, - pub mode: Option, - pub planning: epiphany_state_model::EpiphanyPlanningState, + pub investigation_checkpoint: Option, + pub mode: Option, pub repository_body_observation: Option, pub repo_model: Option, } @@ -141,7 +137,6 @@ impl From for EpiphanyMindPromptProjection { agent_pass_admission_refusals: value.agent_pass_admission_refusals, investigation_checkpoint: value.investigation_checkpoint, mode: value.mode, - planning: value.planning, repository_body_observation: value.repository_body_observation, repo_model: value.repo_model.map(Into::into), } @@ -348,7 +343,7 @@ pub struct EpiphanyDecisionToolObservation { #[derive(Clone, Debug, PartialEq, DatabaseEntry)] #[cultcache( - type = "epiphany.decision_context.v2", + type = "epiphany.decision_context.v3", schema = "EpiphanyDecisionContext" )] pub struct EpiphanyDecisionContext { @@ -375,7 +370,7 @@ impl EpiphanyDecisionContext { tool_observations: Vec, ) -> Result { basis.validate()?; - let provider_request = epiphany_openai_adapter::request_from_native(&native_request); + let provider_request = epiphany_model_adapter::request_from_native(&native_request)?; validate_request_pair(basis, &native_request, &provider_request)?; validate_tool_observations(&native_request, &tool_observations)?; let mut context = Self { @@ -417,7 +412,7 @@ impl EpiphanyDecisionContext { .map_err(|error| anyhow!("decision native request is invalid: {error}")) } - pub fn provider_request(&self) -> Result { + pub fn provider_request(&self) -> Result { rmp_serde::from_slice(&self.provider_request_msgpack) .map_err(|error| anyhow!("decision provider request is invalid: {error}")) } @@ -639,7 +634,7 @@ pub struct EpiphanyDecisionAuditProjection { pub decision_context: EpiphanyDecisionContext, pub reasoning_projection: EpiphanyReasoningProjection, pub terminal_native_request: EpiphanyModelRequest, - pub terminal_provider_request: EpiphanyOpenAiModelRequest, + pub terminal_provider_request: EpiphanyProviderRequest, pub tool_observations: Vec, pub terminal_records: EpiphanyDecisionTerminalRecords, pub mind_commit_receipts: Vec, @@ -674,7 +669,6 @@ pub struct EpiphanyDecisionAuditIndexEntry { pub struct EpiphanyDecisionTerminalRecords { pub role_worker_results: Vec, pub reorient_worker_results: Vec, - pub runtime_job_results: Vec, pub model_pass_failures: Vec, pub archived_worker_attempts: Vec, pub persona_stage_receipts: Vec, @@ -689,7 +683,6 @@ impl EpiphanyDecisionTerminalRecords { fn len(&self) -> usize { self.role_worker_results.len() + self.reorient_worker_results.len() - + self.runtime_job_results.len() + self.model_pass_failures.len() + self.archived_worker_attempts.len() + self.persona_stage_receipts.len() @@ -705,8 +698,6 @@ impl EpiphanyDecisionTerminalRecords { .sort_by(|left, right| left.result_id.cmp(&right.result_id)); self.reorient_worker_results .sort_by(|left, right| left.result_id.cmp(&right.result_id)); - self.runtime_job_results - .sort_by(|left, right| left.result_id.cmp(&right.result_id)); self.model_pass_failures .sort_by(|left, right| left.failure_id.cmp(&right.failure_id)); self.archived_worker_attempts @@ -798,11 +789,6 @@ fn audit_decision_context_from_cache( .into_iter() .filter(|record| record.decision_context_id == context_id) .collect(), - runtime_job_results: cache - .get_all::()? - .into_iter() - .filter(|record| record.decision_context_id.as_deref() == Some(context_id)) - .collect(), model_pass_failures: cache .get_all::()? .into_iter() @@ -901,10 +887,6 @@ fn validate_decision_terminal_records( .reorient_worker_results .iter() .any(|record| record.job_id != basis.pass_id) - || records - .runtime_job_results - .iter() - .any(|record| record.job_id != basis.pass_id) || records .archived_worker_attempts .iter() @@ -1011,12 +993,6 @@ fn validate_model_failure_companions(records: &EpiphanyDecisionTerminalRecords) .admitted_model_direction_consideration_result_msgpack .is_none() }; - let runtime_result_is_failure_projection = |record: &crate::EpiphanyRuntimeJobResult| { - matches!( - record.verdict.as_str(), - "failed" | "fail" | "runtime-error" | "error" | "blocked" | "cancelled" | "canceled" - ) - }; let archived_attempt_is_failure = |record: &crate::EpiphanyArchivedRuntimeWorkerAttempt| { crate::WorkerProcessStatus::parse(&record.terminal_process_status) .map(|status| !status.is_fulfilled_terminal()) @@ -1027,10 +1003,6 @@ fn validate_model_failure_companions(records: &EpiphanyDecisionTerminalRecords) .role_worker_results .iter() .any(|record| !role_result_is_failure_projection(record)) - || records - .runtime_job_results - .iter() - .any(|record| !runtime_result_is_failure_projection(record)) || records .archived_worker_attempts .iter() @@ -1068,7 +1040,7 @@ pub fn worker_reasoning_basis( let launch_document = launch.launch_document()?; let (source_documents, projection) = match launch_document { EpiphanyWorkerLaunchDocument::Role(document) => { - let mind = crate::assemble_mind_view(store_path)?; + let mind = crate::mind_documents::assemble_mind_view_from_cache(&cache)?; let source_documents = mind.source_documents.clone(); let authority: EpiphanyRolePassAuthorityProjection = document.into(); let modeling_body = if authority.role_id.eq_ignore_ascii_case("modeling") { @@ -1176,7 +1148,7 @@ pub fn put_reasoning_basis( let EpiphanyWorkerLaunchDocument::Role(document) = launch.launch_document()? else { return Err(anyhow!("role reasoning basis cites a non-role launch")); }; - let current_mind = crate::assemble_mind_view(store_path)?; + let current_mind = crate::mind_documents::assemble_mind_view_from_cache(&cache)?; let current_modeling_body = if projection .authority .role_id @@ -1369,7 +1341,7 @@ fn validate_context_store_ownership( .as_ref() != Some(&native) || cache - .get::(&provider.request_id)? + .get::(provider.request_id())? .as_ref() != Some(&provider) { @@ -1379,9 +1351,7 @@ fn validate_context_store_ownership( } let model_binding = crate::runtime_spine::validate_runtime_model_execution_binding(cache, &native.request_id)?; - if model_binding.reasoning_basis_id.as_deref() != Some(context.basis_id.as_str()) - || model_binding.source_worker_job_id != native.source_worker_job_id - { + if native.reasoning_basis_id.as_deref() != Some(context.basis_id.as_str()) { return Err(anyhow!( "decision context terminal request has foreign runtime ownership" )); @@ -1415,12 +1385,14 @@ fn validate_context_store_ownership( )); } if let Some(request_id) = intent.model_request_id.as_deref() { - let continuation = cache - .get::(request_id)? - .ok_or_else(|| anyhow!("decision tool continuation request is absent"))?; + let continuation = + crate::runtime_spine::validate_runtime_model_execution_binding(cache, request_id)?; + let continuation_request = cache + .get::(request_id)? + .ok_or_else(|| anyhow!("decision tool continuation native request is absent"))?; if continuation.session_id != model_binding.session_id - || continuation.source_worker_job_id != model_binding.source_worker_job_id - || continuation.reasoning_basis_id != model_binding.reasoning_basis_id + || continuation_request.source_worker_job_id != native.source_worker_job_id + || continuation_request.reasoning_basis_id != native.reasoning_basis_id || tool_binding.session_id != continuation.session_id || tool_binding.job_id != continuation.job_id { @@ -1600,7 +1572,16 @@ pub(crate) fn commit_external_typed_observation_mind_mutation( ) } -fn commit_authorized_mind_mutation( +/// The one owner of a Mind receipt commit. It resolves the Mind backing store +/// once per commit from `store_path` and uses that one store for the cache +/// load, the batch CAS, and the conflict re-read, so no path in this function +/// can target a second store. +/// +/// Order is load-bearing and fail-closed: identities must be unique, the store +/// must open (which refuses a foreign epoch), and every write must validate, +/// all before a stored receipt may answer a replay. A batch the current +/// validator refuses is refused, not answered from the store. +pub(crate) fn commit_authorized_mind_mutation( store_path: &Path, authority: EpiphanyMindCommitAuthority, invariant_owner: &str, @@ -1616,9 +1597,6 @@ fn commit_authorized_mind_mutation( if writes.is_empty() { return Err(anyhow!("Mind mutation requires at least one write")); } - for write in &writes { - crate::mind_documents::validate_mind_write_envelope(write)?; - } validate_unique_envelope_identities(&strong_reads, "strong read")?; validate_unique_envelope_identities(&writes, "write")?; validate_unique_envelope_identities(&companions, "companion")?; @@ -1630,8 +1608,12 @@ fn commit_authorized_mind_mutation( .iter() .map(|entry| (entry.r#type.clone(), entry.key.clone())) .collect::>(); - let mut cache = runtime_spine_cache(store_path)?; + let backing_store = runtime_spine_backing_store(store_path)?; + let mut cache = crate::runtime_spine::open_runtime_spine_cache(backing_store.clone())?; cache.pull_all_backing_stores()?; + for write in &writes { + crate::mind_documents::validate_mind_write_envelope(write)?; + } let mut companion_expected = Vec::new(); let mut companion_replacements = Vec::new(); for companion in companions { @@ -1694,10 +1676,10 @@ fn commit_authorized_mind_mutation( replacements.push(cache.prepare_entry(&receipt_id, &receipt)?.0); let mut expected = strong_reads.clone(); expected.extend(companion_expected); - if runtime_spine_backing_store(store_path)?.compare_and_swap_batch(&expected, replacements)? { + if backing_store.compare_and_swap_batch(&expected, replacements)? { return Ok(EpiphanyMindCommitOutcome::Committed(receipt)); } - let current = runtime_spine_backing_store(store_path)?.pull_all()?; + let current = backing_store.pull_all()?; let mut conflicts = strong_reads .iter() .filter(|expected| { @@ -1721,12 +1703,12 @@ fn commit_authorized_mind_mutation( fn validate_request_pair( basis: &EpiphanyReasoningBasis, native: &EpiphanyModelRequest, - provider: &EpiphanyOpenAiModelRequest, + provider: &EpiphanyProviderRequest, ) -> Result<()> { if native.reasoning_basis_id.as_deref() != Some(basis.basis_id.as_str()) { return Err(anyhow!("model request does not bind its reasoning basis")); } - if provider != &epiphany_openai_adapter::request_from_native(native) { + if provider != &epiphany_model_adapter::request_from_native(native)? { return Err(anyhow!("native and provider terminal requests diverge")); } Ok(()) @@ -1949,7 +1931,6 @@ mod tests { fn role_launch() -> Result { let document = EpiphanyWorkerLaunchDocument::Role(role_document()); Ok(EpiphanyRuntimeWorkerLaunchRequest { - schema_version: crate::RUNTIME_WORKER_LAUNCH_REQUEST_SCHEMA_VERSION.into(), job_id: "pass-1".into(), binding_id: crate::EPIPHANY_IMAGINATION_ROLE_BINDING_ID.into(), role: "Imagination".into(), @@ -1959,14 +1940,6 @@ mod tests { document_kind: "role".into(), launch_document_msgpack: rmp_serde::to_vec_named(&document)?, metadata: Default::default(), - proposal_modeling_request_id: None, - frontier_planning_request_id: None, - frontier_plan_mind_request_id: None, - imagination_consideration_request_id: None, - admitted_model_direction_consideration_request_id: None, - repo_frontier_modeling_request_id: None, - repo_frontier_research_request_id: None, - repo_frontier_verification_request_id: None, }) } @@ -2005,7 +1978,6 @@ mod tests { agent_pass_admission_refusals: Vec::new(), investigation_checkpoint: None, mode: None, - planning: Default::default(), repository_body_observation: None, repo_model: None, }, @@ -2020,9 +1992,7 @@ mod tests { ) } - fn requests( - basis: &EpiphanyReasoningBasis, - ) -> (EpiphanyModelRequest, EpiphanyOpenAiModelRequest) { + fn requests(basis: &EpiphanyReasoningBasis) -> (EpiphanyModelRequest, EpiphanyProviderRequest) { let mut native = EpiphanyModelRequest::new( "request-1", "conversation-1", @@ -2034,27 +2004,7 @@ mod tests { native.input.push(EpiphanyModelInputItem::UserText { text: "projection".into(), }); - let provider = EpiphanyOpenAiModelRequest { - schema_id: epiphany_openai_adapter::OPENAI_ADAPTER_REQUEST_SCHEMA_ID.into(), - request_id: native.request_id.clone(), - conversation_id: native.conversation_id.clone(), - model: native.model.clone(), - instructions: native.instructions.clone(), - input: vec![epiphany_openai_adapter::EpiphanyOpenAiInputItem::UserText { - text: "projection".into(), - }], - reasoning_effort: None, - reasoning_summary: None, - service_tier: None, - output_contract_id: None, - previous_response_id: None, - tools: Vec::new(), - output_schema_json: None, - provider_id: native.provider.clone(), - wire_dialect: epiphany_openai_adapter::EpiphanyOpenAiWireDialect::Responses, - max_output_tokens: native.max_output_tokens, - prompt_cache_key: native.prompt_cache_key.clone(), - }; + let provider = epiphany_model_adapter::request_from_native(&native).unwrap(); (native, provider) } @@ -2101,7 +2051,12 @@ mod tests { let (native, _) = requests(&reasoning_basis); let context = EpiphanyDecisionContext::new(&reasoning_basis, native.clone(), Vec::new())?; let mut substituted_provider_input = context.provider_request()?; - substituted_provider_input.input.clear(); + match &mut substituted_provider_input.payload { + EpiphanyProviderRequestPayload::Codex(request) => request.input.clear(), + EpiphanyProviderRequestPayload::OpenRouter(_) => { + panic!("test fixture must lower to the Codex provider contract") + } + } let mut substituted_provider_context = context; substituted_provider_context.provider_request_msgpack = rmp_serde::to_vec_named(&substituted_provider_input)?; @@ -2126,7 +2081,7 @@ mod tests { )?; assert_eq!( tool_context.provider_request()?, - epiphany_openai_adapter::request_from_native(&native_with_substituted_tools), + epiphany_model_adapter::request_from_native(&native_with_substituted_tools)?, "provider request must be derived from the complete native request" ); @@ -2220,14 +2175,13 @@ mod tests { let context = EpiphanyDecisionContext::new(&reasoning_basis, native.clone(), Vec::new())?; let provider = context.provider_request()?; - assert_eq!(provider.provider_id, "openrouter"); - assert_eq!( - provider.wire_dialect, - epiphany_openai_adapter::EpiphanyOpenAiWireDialect::ChatCompletionsTerminalTool - ); + assert!(matches!( + provider.payload, + EpiphanyProviderRequestPayload::OpenRouter(_) + )); assert_eq!( provider, - epiphany_openai_adapter::request_from_native(&native) + epiphany_model_adapter::request_from_native(&native)? ); Ok(()) } @@ -2253,22 +2207,11 @@ mod tests { let (initial_native, _) = requests(&reasoning_basis); crate::open_runtime_model_execution( &store, - crate::RuntimeSpineSessionOptions { - session_id: "session-1".into(), - objective: "Prove governed decision context".into(), - created_at: "2026-08-17T00:00:01Z".into(), - coordinator_note: "decision context binding test".into(), - }, crate::RuntimeSpineJobOptions { job_id: "model-job-1".into(), session_id: "session-1".into(), - role: "model-adapter".into(), - created_at: "2026-08-17T00:00:01Z".into(), - summary: "Initial model request".into(), - artifact_refs: Vec::new(), }, &initial_native, - "2026-08-17T00:00:01Z", )?; let intent = EpiphanyToolInvocationIntent::new( "intent-1", @@ -2281,13 +2224,7 @@ mod tests { "2026-08-17T00:00:02Z", ) .with_model_call("call-1", &initial_native.request_id); - crate::put_runtime_tool_execution_intent( - &store, - "session-1", - "model-job-1", - &intent, - "2026-08-17T00:00:02Z", - )?; + crate::put_runtime_tool_execution_intent(&store, "session-1", "model-job-1", &intent)?; let mut receipt = EpiphanyToolInvocationReceipt::new( "receipt-1", &intent.intent_id, @@ -2315,22 +2252,11 @@ mod tests { ]); crate::open_runtime_model_execution( &store, - crate::RuntimeSpineSessionOptions { - session_id: "session-1".into(), - objective: "Prove governed decision context".into(), - created_at: "2026-08-17T00:00:04Z".into(), - coordinator_note: "decision context binding test".into(), - }, crate::RuntimeSpineJobOptions { job_id: "model-job-2".into(), session_id: "session-1".into(), - role: "model-adapter".into(), - created_at: "2026-08-17T00:00:04Z".into(), - summary: "Terminal model request".into(), - artifact_refs: Vec::new(), }, &terminal_native, - "2026-08-17T00:00:04Z", )?; let context = seal_model_decision_context(&store, &terminal_native.request_id)?; @@ -2343,26 +2269,12 @@ mod tests { text: "optional transcript".into(), }, }; - let provider_delta = epiphany_openai_adapter::EpiphanyOpenAiStreamEvent { - schema_id: epiphany_openai_adapter::OPENAI_ADAPTER_EVENT_SCHEMA_ID.into(), - request_id: terminal_native.request_id.clone(), - sequence: 0, - payload: epiphany_openai_adapter::EpiphanyOpenAiStreamPayload::TextDelta { - text: "optional transcript".into(), - }, - }; cache = runtime_spine_cache(&store)?; cache.put("request-2:00000000", &native_delta)?; - cache.put("request-2:00000000", &provider_delta)?; assert!( cache .delete::("request-2:00000000")? ); - assert!( - cache.delete::( - "request-2:00000000" - )? - ); cache.pull_all_backing_stores()?; let retained = cache .get::(&context.context_id)? @@ -2375,7 +2287,7 @@ mod tests { let mut hostile_binding = cache .get::(&intent.intent_id)? .expect("tool binding"); - hostile_binding.bound_at = "not-a-time".into(); + hostile_binding.session_id = "foreign-session".into(); cache.put(&intent.intent_id, &hostile_binding)?; let before = SingleFileMessagePackBackingStore::new(&store).pull_all()?; assert!(seal_model_decision_context(&store, &terminal_native.request_id).is_err()); @@ -2406,22 +2318,11 @@ mod tests { let (native, _provider) = requests(&reasoning_basis); crate::open_runtime_model_execution( &store, - crate::RuntimeSpineSessionOptions { - session_id: "failed-pass-session".into(), - objective: "Prove terminal failure ownership".into(), - created_at: "2026-08-18T00:00:01Z".into(), - coordinator_note: "model pass failure test".into(), - }, crate::RuntimeSpineJobOptions { job_id: "failed-model-job".into(), session_id: "failed-pass-session".into(), - role: "model-adapter".into(), - created_at: "2026-08-18T00:00:01Z".into(), - summary: "Run the model pass".into(), - artifact_refs: Vec::new(), }, &native, - "2026-08-18T00:00:01Z", )?; let context = seal_model_decision_context(&store, &native.request_id)?; let options = crate::ModelPassFailureTerminalOptions { @@ -2445,19 +2346,8 @@ mod tests { Some(failure.clone()) ); let model_job = - crate::runtime_job_snapshot(&store, "failed-model-job")?.expect("model job snapshot"); - assert_eq!( - model_job.job.status, - crate::EpiphanyRuntimeJobStatus::Failed - ); - assert!( - model_job - .result - .expect("model transport failure result") - .decision_context_id - .is_none(), - "generic model transport must not own decision authority" - ); + crate::runtime_job(&store, "failed-model-job")?.expect("failed model runtime job"); + assert_eq!(model_job.status, crate::EpiphanyRuntimeJobStatus::Failed); let audit = audit_decision_context(&store, &context.context_id)?; assert_eq!( audit.terminal_records.model_pass_failures, @@ -2475,7 +2365,6 @@ mod tests { let failure_summary = "Provider refused before assistant output.".to_string(); let failure_role_result = crate::EpiphanyRuntimeRoleWorkerResult { - schema_version: crate::RUNTIME_ROLE_WORKER_RESULT_SCHEMA_VERSION.to_string(), result_id: format!("result-worker-{}", reasoning_basis.pass_id), job_id: reasoning_basis.pass_id.clone(), role_id: role_terminal_semantic_role(&reasoning_basis)?.expect("role pass"), @@ -2495,47 +2384,20 @@ mod tests { item_error: Some(failure_summary.clone()), metadata: std::collections::BTreeMap::new(), repo_model_mutation_proposal_msgpack: None, - verification_request_id: None, - frontier_route_id: None, - repo_frontier_modeling_request_id: None, - proposal_modeling_request_id: None, - repo_frontier_research_request_id: None, - frontier_planning_request_id: None, frontier_plan_candidate_msgpack: None, - frontier_plan_mind_request_id: None, frontier_plan_mind_decision_msgpack: None, - repository_body_observation_basis: None, - imagination_consideration_request_id: None, imagination_consideration_candidate_msgpack: None, - admitted_model_direction_consideration_request_id: None, admitted_model_direction_consideration_result_msgpack: None, decision_context_id: context.context_id.clone(), }; - let failure_runtime_result = crate::EpiphanyRuntimeJobResult { - schema_version: crate::RUNTIME_SPINE_SCHEMA_VERSION.to_string(), - result_id: format!("result-runtime-{}", reasoning_basis.pass_id), - job_id: reasoning_basis.pass_id.clone(), - session_id: "failed-pass-session".into(), - role: reasoning_basis.organ_id.clone(), - verdict: "failed".into(), - summary: failure_summary, - completed_at: "2026-08-18T00:00:03Z".into(), - next_safe_move: "Create a fresh work obligation and reasoning pass.".into(), - evidence_refs: Vec::new(), - artifact_refs: Vec::new(), - metadata: std::collections::BTreeMap::new(), - decision_context_id: Some(context.context_id.clone()), - }; let mut cache = runtime_spine_cache(&store)?; cache.put(&failure_role_result.job_id, &failure_role_result)?; - cache.put(&failure_runtime_result.result_id, &failure_runtime_result)?; let audit = audit_decision_context(&store, &context.context_id)?; assert_eq!(audit.terminal_records.role_worker_results.len(), 1); - assert_eq!(audit.terminal_records.runtime_job_results.len(), 1); assert_eq!(audit.terminal_records.model_pass_failures.len(), 1); assert_eq!( list_auditable_decision_contexts(&store)?.decisions[0].terminal_record_count, - 3 + 2 ); let mut false_success = failure_role_result.clone(); false_success.verdict = "completed".into(); @@ -2626,6 +2488,386 @@ mod tests { Ok(()) } + /// An envelope the Mind validator refuses: its key is not the value's + /// semantic identity. + fn refused_mind_write(store: &Path) -> Result { + let value = crate::EpiphanyObservation { + id: "right-id".into(), + summary: "one".into(), + source_kind: "test".into(), + status: "accepted".into(), + code_refs: Vec::new(), + evidence_ids: Vec::new(), + }; + Ok(runtime_spine_cache(store)? + .prepare_entry("wrong-key", &crate::EpiphanyMindObservationDocument { value })? + .0) + } + + /// Ruling 12, and Soul's finding S6: every write is validated before a + /// stored receipt may answer a replay, so a replay of a batch the current + /// validator refuses is refused rather than answered from the store. + #[test] + fn mind_commit_validates_before_answering_a_replay() -> Result<()> { + let temp = tempdir()?; + let store = temp.path().join("mind.cc"); + initialize_runtime_spine( + &store, + RuntimeSpineInitOptions { + runtime_id: "mind-replay-test".into(), + display_name: "Mind replay test".into(), + created_at: "2026-09-15T00:00:00Z".into(), + }, + )?; + let refused = refused_mind_write(&store)?; + let provenance = EpiphanyMindDocumentVersion::from_envelope("epiphany-organ", &refused)?; + let authority = EpiphanyMindCommitAuthority::TypedOrganProvenance { + organ: "test-organ".into(), + provenance: provenance.clone(), + }; + let writes = vec![EpiphanyMindDocumentVersion::from_envelope( + "epiphany-mind", + &refused, + )?]; + // Plant exactly the receipt this batch would produce, so a replay check + // that ran first would have a stored answer to hand back. + let receipt_id = mind_commit_receipt_id(&authority, "test-owner", &[], &writes)?; + let planted = EpiphanyMindCommitReceipt { + schema_version: MIND_COMMIT_RECEIPT_SCHEMA_VERSION.to_string(), + receipt_id: receipt_id.clone(), + authority, + invariant_owner: "test-owner".into(), + strong_reads: Vec::new(), + writes, + committed_at: "2026-09-15T00:00:01Z".into(), + }; + planted.validate()?; + let mut planting = runtime_spine_cache(&store)?; + planting.pull_all_backing_stores()?; + planting.put(&receipt_id, &planted)?; + let before = std::fs::read(&store)?; + let error = commit_external_typed_observation_mind_mutation( + &store, + "test-organ", + provenance, + "test-owner", + Vec::new(), + vec![refused], + "2026-09-15T00:00:02Z", + ) + .unwrap_err(); + assert!( + error.to_string().contains("semantic identity"), + "a refused batch must be refused, not answered with the stored receipt: {error}" + ); + assert_eq!(std::fs::read(&store)?, before); + Ok(()) + } + + /// Every write in the batch is validated, not just the first. A batch of + /// two distinct writes whose second is refused is refused whole, with + /// nothing written. + #[test] + fn mind_commit_validates_every_write_in_the_batch() -> Result<()> { + let temp = tempdir()?; + let store = temp.path().join("mind.cc"); + initialize_runtime_spine( + &store, + RuntimeSpineInitOptions { + runtime_id: "mind-batch-validation-test".into(), + display_name: "Mind batch validation test".into(), + created_at: "2026-09-15T00:00:00Z".into(), + }, + )?; + let value = crate::EpiphanyObservation { + id: "first".into(), + summary: "one".into(), + source_kind: "test".into(), + status: "accepted".into(), + code_refs: Vec::new(), + evidence_ids: Vec::new(), + }; + let accepted = runtime_spine_cache(&store)? + .prepare_entry("first", &crate::EpiphanyMindObservationDocument { value })? + .0; + let refused = refused_mind_write(&store)?; + assert_ne!( + (&accepted.r#type, &accepted.key), + (&refused.r#type, &refused.key), + "the two writes carry distinct identities, so uniqueness cannot answer first" + ); + let provenance = EpiphanyMindDocumentVersion::from_envelope("epiphany-organ", &accepted)?; + let before = std::fs::read(&store)?; + let error = commit_external_typed_observation_mind_mutation( + &store, + "test-organ", + provenance, + "test-owner", + Vec::new(), + vec![accepted, refused], + "2026-09-15T00:00:01Z", + ) + .unwrap_err(); + assert!( + error.to_string().contains("semantic identity"), + "the second write is validated too: {error}" + ); + assert_eq!( + std::fs::read(&store)?, + before, + "a refused batch writes nothing, including its valid writes" + ); + Ok(()) + } + + /// Ruling 12 again: identity uniqueness is checked before write validation, + /// so a batch that repeats an identity reports the repeat, not the + /// validator's refusal. The order is fail-closed either way; this pins + /// which refusal the caller is told about. + #[test] + fn mind_commit_refuses_repeated_write_identities_before_validating() -> Result<()> { + let temp = tempdir()?; + let store = temp.path().join("mind.cc"); + initialize_runtime_spine( + &store, + RuntimeSpineInitOptions { + runtime_id: "mind-uniqueness-test".into(), + display_name: "Mind uniqueness test".into(), + created_at: "2026-09-15T00:00:00Z".into(), + }, + )?; + let refused = refused_mind_write(&store)?; + let provenance = EpiphanyMindDocumentVersion::from_envelope("epiphany-organ", &refused)?; + let before = std::fs::read(&store)?; + let error = commit_external_typed_observation_mind_mutation( + &store, + "test-organ", + provenance, + "test-owner", + Vec::new(), + vec![refused.clone(), refused], + "2026-09-15T00:00:01Z", + ) + .unwrap_err(); + assert!( + error.to_string().contains("repeats write identity"), + "uniqueness is checked before the writes are validated: {error}" + ); + assert_eq!(std::fs::read(&store)?, before); + Ok(()) + } + + /// The owner resolves one backing store from the path and uses that one for + /// the cache load, the batch CAS and the conflict re-read. A commit that + /// wrote anywhere else would leave a second store beside this one, and a + /// replay read from anywhere else would not find the receipt. + /// + /// The file assertions below cannot see a handle re-derived from the *same* + /// path, because a handle carries nothing but its path. The resolution + /// count can, and that is the shape the map's redb plan makes load-bearing: + /// redb permits one writable handle per path, so a second derivation is a + /// second handle there. + #[test] + fn mind_commit_reads_and_writes_one_store() -> Result<()> { + let temp = tempdir()?; + let store = temp.path().join("mind.cc"); + initialize_runtime_spine( + &store, + RuntimeSpineInitOptions { + runtime_id: "mind-one-store-test".into(), + display_name: "Mind one store test".into(), + created_at: "2026-09-15T00:00:00Z".into(), + }, + )?; + let value = crate::EpiphanyObservation { + id: "first".into(), + summary: "one".into(), + source_kind: "test".into(), + status: "accepted".into(), + code_refs: Vec::new(), + evidence_ids: Vec::new(), + }; + let write = runtime_spine_cache(&store)? + .prepare_entry("first", &crate::EpiphanyMindObservationDocument { value })? + .0; + let provenance = EpiphanyMindDocumentVersion::from_envelope("epiphany-organ", &write)?; + let commit = |at: &str| { + commit_external_typed_observation_mind_mutation( + &store, + "test-organ", + provenance.clone(), + "test-owner", + Vec::new(), + vec![write.clone()], + at, + ) + }; + let resolutions_before = + crate::runtime_store_backend::backing_store_resolutions(&store); + let EpiphanyMindCommitOutcome::Committed(receipt) = commit("2026-09-15T00:00:01Z")? else { + panic!("the first Mind write must commit"); + }; + assert_eq!( + crate::runtime_store_backend::backing_store_resolutions(&store) - resolutions_before, + 1, + "the commit resolves the backing store once and reuses that handle" + ); + let mut files = std::fs::read_dir(temp.path())? + .map(|entry| Ok(entry?.file_name().to_string_lossy().into_owned())) + .collect::>>()?; + files.sort(); + assert_eq!( + files, + vec!["mind.cc".to_string(), "mind.cc.lock".to_string()], + "the commit writes exactly the store it was given, and locks that one" + ); + let stored = SingleFileMessagePackBackingStore::new(&store).pull_all()?; + assert!( + stored.iter().any(|entry| entry.key == receipt.receipt_id), + "the receipt lands in the store the owner read" + ); + assert_eq!( + commit("2026-09-15T00:00:09Z")?, + EpiphanyMindCommitOutcome::Committed(receipt), + "the replay is answered from that same store" + ); + Ok(()) + } + + #[test] + fn mind_commit_refuses_a_foreign_epoch_store() -> Result<()> { + let temp = tempdir()?; + let store = temp.path().join("mind.cc"); + initialize_runtime_spine( + &store, + RuntimeSpineInitOptions { + runtime_id: "mind-epoch-test".into(), + display_name: "Mind epoch test".into(), + created_at: "2026-09-15T00:00:00Z".into(), + }, + )?; + let cache = runtime_spine_cache(&store)?; + let backing = runtime_spine_backing_store(&store)?; + let current = backing + .pull_all()? + .into_iter() + .find(|entry| entry.r#type == crate::EpiphanyMindIdentity::TYPE) + .expect("an initialized store carries its Mind identity"); + let foreign = crate::EpiphanyMindIdentity { + schema_epoch: "epiphany.mind.epoch.foreign".into(), + runtime_id: "mind-epoch-test".into(), + }; + let foreign = cache.prepare_entry(¤t.key, &foreign)?.0; + assert!(backing.compare_and_swap_batch(&[current], vec![foreign])?); + let value = crate::EpiphanyObservation { + id: "foreign".into(), + summary: "foreign".into(), + source_kind: "test".into(), + status: "accepted".into(), + code_refs: Vec::new(), + evidence_ids: Vec::new(), + }; + let write = cache + .prepare_entry("foreign", &crate::EpiphanyMindObservationDocument { value })? + .0; + let provenance = EpiphanyMindDocumentVersion::from_envelope("epiphany-organ", &write)?; + let before = std::fs::read(&store)?; + let error = commit_external_typed_observation_mind_mutation( + &store, + "test-organ", + provenance, + "test-owner", + Vec::new(), + vec![write], + "2026-09-15T00:00:01Z", + ) + .unwrap_err(); + assert!(error.to_string().contains("unsupported writable schema epoch")); + assert_eq!(std::fs::read(&store)?, before); + Ok(()) + } + + #[test] + fn mind_commit_keeps_validation_receipts_replay_and_conflicts() -> Result<()> { + let temp = tempdir()?; + let store = temp.path().join("mind.cc"); + initialize_runtime_spine( + &store, + RuntimeSpineInitOptions { + runtime_id: "mind-commit-keeps-test".into(), + display_name: "Mind commit keeps test".into(), + created_at: "2026-09-15T00:00:00Z".into(), + }, + )?; + let cache = runtime_spine_cache(&store)?; + let observation = |key: &str, id: &str, summary: &str| -> Result { + let value = crate::EpiphanyObservation { + id: id.into(), + summary: summary.into(), + source_kind: "test".into(), + status: "accepted".into(), + code_refs: Vec::new(), + evidence_ids: Vec::new(), + }; + Ok(cache + .prepare_entry(key, &crate::EpiphanyMindObservationDocument { value })? + .0) + }; + let provenance = EpiphanyMindDocumentVersion::from_envelope( + "epiphany-organ", + &observation("provenance", "provenance", "organ")?, + )?; + let commit = |reads: Vec, + writes: Vec, + at: &str| { + commit_external_typed_observation_mind_mutation( + &store, + "test-organ", + provenance.clone(), + "test-owner", + reads, + writes, + at, + ) + }; + let before = std::fs::read(&store)?; + let error = commit( + Vec::new(), + vec![observation("wrong-key", "right-id", "x")?], + "2026-09-15T00:00:01Z", + ) + .unwrap_err(); + assert!(error.to_string().contains("semantic identity")); + assert_eq!(std::fs::read(&store)?, before); + let first = observation("first", "first", "one")?; + let EpiphanyMindCommitOutcome::Committed(receipt) = + commit(Vec::new(), vec![first.clone()], "2026-09-15T00:00:02Z")? + else { + panic!("the first Mind write must commit"); + }; + assert!(receipt.writes.iter().all(|version| version.store_id == "epiphany-mind")); + assert_eq!( + receipt.receipt_id, + "mind-commit-sha256:415283192ba888e99ce011b5e7a2d2ff82e93d9cbf9b5c8af96eebc57acc281e" + ); + assert_eq!( + commit(Vec::new(), vec![first.clone()], "2026-09-15T00:00:30Z")?, + EpiphanyMindCommitOutcome::Committed(receipt), + "exact replay returns the stored receipt" + ); + assert_eq!( + commit( + vec![observation("first", "first", "stale")?], + vec![observation("second", "second", "two")?], + "2026-09-15T00:00:03Z", + )?, + EpiphanyMindCommitOutcome::Conflict { + document_identities: vec![(first.r#type.clone(), "first".into())], + } + ); + Ok(()) + } + #[test] fn disjoint_mind_mutations_merge_and_same_identity_conflicts() -> Result<()> { let temp = tempdir()?; @@ -2646,22 +2888,11 @@ mod tests { let (native, _) = requests(&basis); crate::open_runtime_model_execution( &store, - crate::RuntimeSpineSessionOptions { - session_id: "session-1".into(), - objective: "Test sealed decision context".into(), - created_at: "2026-08-14T00:00:01Z".into(), - coordinator_note: "reasoning context test".into(), - }, crate::RuntimeSpineJobOptions { job_id: "model-job-1".into(), session_id: "session-1".into(), - role: "model-adapter".into(), - created_at: "2026-08-14T00:00:01Z".into(), - summary: "Test terminal request publication".into(), - artifact_refs: Vec::new(), }, &native, - "2026-08-14T00:00:01Z", )?; let context = put_decision_context( &store, @@ -2671,7 +2902,7 @@ mod tests { let make = |key: &str, summary: &str| -> Result { let cache = runtime_spine_cache(&store)?; let document = crate::EpiphanyMindObservationDocument { - value: epiphany_state_model::EpiphanyObservation { + value: crate::EpiphanyObservation { id: key.into(), summary: summary.into(), source_kind: "test".into(), @@ -2720,30 +2951,41 @@ mod tests { let before_unterminal_audit = backing.pull_all()?; assert!(audit_decision_context(&store, &context.context_id).is_err()); assert_eq!(backing.pull_all()?, before_unterminal_audit); - let terminal_result = crate::EpiphanyRuntimeJobResult { - schema_version: crate::RUNTIME_SPINE_SCHEMA_VERSION.into(), + let terminal_result = crate::EpiphanyRuntimeRoleWorkerResult { result_id: "worker-result-1".into(), job_id: "pass-1".into(), - session_id: "session-1".into(), - role: "Imagination".into(), + role_id: role_terminal_semantic_role(&basis)?.expect("role pass"), verdict: "completed".into(), summary: "Structured Imagination decision".into(), - completed_at: "2026-08-14T00:00:03Z".into(), next_safe_move: "Inspect the durable decision context".into(), - evidence_refs: Vec::new(), + checkpoint_summary: None, + scratch_summary: None, + files_inspected: Vec::new(), + frontier_node_ids: Vec::new(), + evidence_ids: Vec::new(), artifact_refs: Vec::new(), + open_questions: Vec::new(), + evidence_gaps: Vec::new(), + risks: Vec::new(), + research_decision_msgpack: None, + item_error: None, metadata: Default::default(), - decision_context_id: Some(context.context_id.clone()), + repo_model_mutation_proposal_msgpack: None, + frontier_plan_candidate_msgpack: None, + frontier_plan_mind_decision_msgpack: None, + imagination_consideration_candidate_msgpack: None, + admitted_model_direction_consideration_result_msgpack: None, + decision_context_id: context.context_id.clone(), }; - runtime_spine_cache(&store)?.put(&terminal_result.result_id, &terminal_result)?; + runtime_spine_cache(&store)?.put(&terminal_result.job_id, &terminal_result)?; let before_audit = backing.pull_all()?; let audit = audit_decision_context(&store, &context.context_id)?; assert_eq!(audit.context_id, context.context_id); assert_eq!(audit.reasoning_basis, basis); assert_eq!(audit.terminal_native_request.request_id, "request-1"); - assert_eq!(audit.terminal_provider_request.request_id, "request-1"); + assert_eq!(audit.terminal_provider_request.request_id(), "request-1"); assert_eq!( - audit.terminal_records.runtime_job_results, + audit.terminal_records.role_worker_results, vec![terminal_result.clone()] ); assert_eq!(audit.mind_commit_receipts.len(), 2); @@ -2752,10 +2994,10 @@ mod tests { let mut archive_cache = runtime_spine_cache(&store)?; archive_cache.pull_all_backing_stores()?; assert!( - archive_cache.delete::(&terminal_result.result_id)? + archive_cache + .delete::(&terminal_result.job_id)? ); let archived = crate::EpiphanyArchivedRuntimeWorkerAttempt { - schema_version: crate::ARCHIVED_RUNTIME_WORKER_ATTEMPT_SCHEMA_VERSION.into(), job_id: "pass-1".into(), request_kind: "proposal-modeling".into(), request_id: "proposal-request-1".into(), @@ -2764,7 +3006,6 @@ mod tests { decision: Some(crate::EpiphanyArchivedRuntimeWorkerDecision { decision_context_id: context.context_id.clone(), role_result: None, - job_results: vec![terminal_result], }), }; archive_cache.put(&archived.job_id, &archived)?; @@ -2773,7 +3014,7 @@ mod tests { assert!( archived_audit .terminal_records - .runtime_job_results + .role_worker_results .is_empty() ); assert_eq!( diff --git a/epiphany-core/src/reorientation_work.rs b/epiphany-core/src/reorientation_work.rs index 588f5441..44c0ac33 100644 --- a/epiphany-core/src/reorientation_work.rs +++ b/epiphany-core/src/reorientation_work.rs @@ -9,22 +9,20 @@ use crate::{ }; pub const REORIENTATION_REQUEST_SCHEMA_VERSION: &str = "epiphany.self.reorientation_request.v1"; -pub const REORIENTATION_LAUNCH_BINDING_SCHEMA_VERSION: &str = - "epiphany.runtime.reorientation_launch.v1"; pub const MIND_REORIENTATION_DECISION_SCHEMA_VERSION: &str = "epiphany.mind.reorientation_decision.v1"; pub const MIND_REORIENTATION_PASS_FAILURE_SCHEMA_VERSION: &str = - "epiphany.mind.reorientation_pass_failure.v1"; + "epiphany.mind.reorientation_pass_failure.v2"; #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct EpiphanyReorientationStateProjection { pub runtime_id: String, pub objective: Option, pub active_subgoal_id: Option, - pub investigation_checkpoint: Option, - pub mode: Option, + pub investigation_checkpoint: Option, + pub mode: Option, pub repository_body_observation: Option, - pub frontier: Vec, + pub frontier: Vec, pub projection_digest: String, } @@ -48,26 +46,6 @@ pub struct EpiphanyReorientationRequest { pub requested_at: String, } -#[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)] -#[cultcache( - type = "epiphany.runtime.reorientation_launch.v1", - schema = "EpiphanyReorientationLaunchBinding" -)] -pub struct EpiphanyReorientationLaunchBinding { - #[cultcache(key = 0)] - pub schema_version: String, - #[cultcache(key = 1)] - pub binding_id: String, - #[cultcache(key = 2)] - pub request_id: String, - #[cultcache(key = 3)] - pub job_id: String, - #[cultcache(key = 4)] - pub attempt_ordinal: u64, - #[cultcache(key = 5)] - pub launched_at: String, -} - #[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)] #[cultcache( type = "epiphany.mind.reorientation_decision.v1", @@ -100,7 +78,7 @@ pub struct EpiphanyMindReorientationDecisionDocument { #[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)] #[cultcache( - type = "epiphany.mind.reorientation_pass_failure.v1", + type = "epiphany.mind.reorientation_pass_failure.v2", schema = "EpiphanyMindReorientationPassFailureDocument" )] pub struct EpiphanyMindReorientationPassFailureDocument { @@ -113,14 +91,12 @@ pub struct EpiphanyMindReorientationPassFailureDocument { #[cultcache(key = 3)] pub job_id: String, #[cultcache(key = 4)] - pub runtime_result_id: String, + pub model_pass_failure_id: String, #[cultcache(key = 5)] pub decision_context_id: String, #[cultcache(key = 6)] - pub verdict: String, - #[cultcache(key = 7)] pub summary: String, - #[cultcache(key = 8)] + #[cultcache(key = 7)] pub failed_at: String, } @@ -129,6 +105,7 @@ pub struct EpiphanyReorientationWorkProjection { pub request: EpiphanyReorientationRequest, #[serde(flatten)] pub attempt: crate::EpiphanyAgentPassAttemptProjection, + pub terminal_failure: bool, } pub fn request_current_reorientation( @@ -138,7 +115,9 @@ pub fn request_current_reorientation( chrono::DateTime::parse_from_rfc3339(requested_at) .map_err(|_| anyhow!("reorientation request time is invalid"))?; let store_path = store_path.as_ref(); - let mind = crate::assemble_mind_view(store_path)?; + let mut cache = crate::runtime_spine_cache(store_path)?; + cache.pull_all_backing_stores()?; + let mind = crate::mind_documents::assemble_mind_view_from_cache(&cache)?; let mut source_documents = mind .source_documents .iter() @@ -192,8 +171,6 @@ pub fn request_current_reorientation( requested_at: requested_at.into(), }; validate_reorientation_request_intrinsic(&request)?; - let mut cache = crate::runtime_spine_cache(store_path)?; - cache.pull_all_backing_stores()?; if let Some(existing) = cache.get::(&request_id)? { validate_reorientation_request_current(&cache, &existing)?; return Ok(existing); @@ -210,6 +187,70 @@ pub fn request_current_reorientation( Ok(request) } +fn reorientation_attempt_ordinal(request_id: &str, job_id: &str) -> Result { + let prefix = format!("reorientation-{request_id}-attempt-"); + job_id + .strip_prefix(&prefix) + .ok_or_else(|| anyhow!("reorientation job identity is not canonical"))? + .parse::() + .map_err(|_| anyhow!("reorientation attempt ordinal is invalid")) +} + +fn reorientation_launch_document( + launch: &crate::EpiphanyRuntimeWorkerLaunchRequest, +) -> Result { + let document = match launch.launch_document()? { + crate::EpiphanyWorkerLaunchDocument::Reorient(document) => document, + crate::EpiphanyWorkerLaunchDocument::Role(_) => { + return Err(anyhow!("reorientation owner carried a role launch")); + } + }; + if launch.role != crate::EPIPHANY_REORIENT_OWNER_ROLE + || launch.binding_id != crate::EPIPHANY_REORIENT_LAUNCH_BINDING_ID + || document.schema_version != "epiphany.reorientation_launch_projection.v1" + || document.request_id.trim().is_empty() + || document.creation_thread_id != launch.job_id + { + return Err(anyhow!( + "reorientation immutable launch crossed family authority" + )); + } + reorientation_attempt_ordinal(&document.request_id, &launch.job_id)?; + Ok(document) +} + +fn reorientation_launches( + cache: &CultCache, + request_id: &str, +) -> Result> { + let mut launches = Vec::new(); + for launch in cache + .get_all::()? + .into_iter() + .filter(|launch| { + launch.role == crate::EPIPHANY_REORIENT_OWNER_ROLE + || launch.binding_id == crate::EPIPHANY_REORIENT_LAUNCH_BINDING_ID + }) + { + let document = reorientation_launch_document(&launch)?; + if document.request_id == request_id { + launches.push(( + reorientation_attempt_ordinal(request_id, &launch.job_id)?, + launch, + )); + } + } + launches.sort_by_key(|(ordinal, _)| *ordinal); + for (expected, (ordinal, _)) in launches.iter().enumerate() { + if *ordinal != expected { + return Err(anyhow!( + "reorientation request has noncontiguous attempt identity" + )); + } + } + Ok(launches) +} + pub(crate) fn current_reorientation_work( cache: &CultCache, ) -> Result> { @@ -225,52 +266,58 @@ pub(crate) fn current_reorientation_work( { continue; } - let mut bindings = cache - .get_all::()? - .into_iter() - .filter(|binding| binding.request_id == request.request_id) - .collect::>(); - bindings.sort_by_key(|binding| binding.attempt_ordinal); - let Some(binding) = bindings.last() else { + let launches = reorientation_launches(cache, &request.request_id)?; + let Some((_, launch)) = launches.last() else { return Ok(Some(EpiphanyReorientationWorkProjection { request, attempt: crate::EpiphanyAgentPassAttemptProjection::unattempted(), + terminal_failure: false, })); }; let job = cache - .get::(&binding.job_id)? + .get::(&launch.job_id)? .ok_or_else(|| anyhow!("reorientation launch lost its runtime job"))?; - let result = cache.get::(&binding.job_id)?; + let result = cache.get::(&launch.job_id)?; + let terminal_failure = job.status == EpiphanyRuntimeJobStatus::Failed; let action = match job.status { EpiphanyRuntimeJobStatus::Completed if result.is_some() => { EpiphanyAgentPassContinuationAction::Review } EpiphanyRuntimeJobStatus::Failed => { - let terminal = exact_runtime_job_result(cache, &binding.job_id)?; - match terminal.decision_context_id.as_deref() { - Some(context_id) + let model_failures = cache + .get_all::()? + .into_iter() + .filter(|failure| failure.pass_id == launch.job_id) + .collect::>(); + match model_failures.as_slice() { + [] => EpiphanyAgentPassContinuationAction::Launch, + [model_failure] if failures.iter().any(|failure| { failure.request_id == request.request_id - && failure.job_id == binding.job_id - && failure.runtime_result_id == terminal.result_id - && failure.decision_context_id == context_id + && failure.job_id == launch.job_id + && failure.model_pass_failure_id == model_failure.failure_id + && failure.decision_context_id == model_failure.decision_context_id }) => { EpiphanyAgentPassContinuationAction::Launch } - Some(_) => EpiphanyAgentPassContinuationAction::Review, - None => EpiphanyAgentPassContinuationAction::Launch, + [_] => EpiphanyAgentPassContinuationAction::Review, + _ => { + return Err(anyhow!( + "reorientation pass has multiple model-failure authorities" + )); + } } } - EpiphanyRuntimeJobStatus::Cancelled => EpiphanyAgentPassContinuationAction::Launch, _ => EpiphanyAgentPassContinuationAction::Wait, }; return Ok(Some(EpiphanyReorientationWorkProjection { request, attempt: crate::EpiphanyAgentPassAttemptProjection::with( action, - Some(binding.job_id.clone()), + Some(launch.job_id.clone()), ), + terminal_failure, })); } Ok(None) @@ -283,13 +330,12 @@ pub fn record_reorientation_pass_failure( let store_path = store_path.as_ref(); let mut cache = crate::runtime_spine_cache(store_path)?; cache.pull_all_backing_stores()?; - let binding = cache - .get_all::()? - .into_iter() - .find(|binding| binding.job_id == job_id) - .ok_or_else(|| anyhow!("reorientation failure has no exact launch binding"))?; + let launch = cache + .get::(job_id)? + .ok_or_else(|| anyhow!("reorientation failure has no immutable launch"))?; + let launch_document = reorientation_launch_document(&launch)?; let request = cache - .get::(&binding.request_id)? + .get::(&launch_document.request_id)? .ok_or_else(|| anyhow!("reorientation failure lost its request"))?; validate_reorientation_request_intrinsic(&request)?; let job = cache @@ -298,21 +344,24 @@ pub fn record_reorientation_pass_failure( if job.status != EpiphanyRuntimeJobStatus::Failed { return Err(anyhow!("reorientation pass is not a failed model pass")); } - let terminal = exact_runtime_job_result(&cache, job_id)?; - let decision_context_id = terminal - .decision_context_id - .as_deref() - .ok_or_else(|| anyhow!("model-backed reorientation failure has no decision context"))?; + let model_failures = cache + .get_all::()? + .into_iter() + .filter(|failure| failure.pass_id == job_id) + .collect::>(); + let [model_failure] = model_failures.as_slice() else { + return Err(anyhow!( + "reorientation failure requires one exact model-pass failure" + )); + }; let context = cache - .get::(decision_context_id)? + .get::(&model_failure.decision_context_id)? .ok_or_else(|| anyhow!("reorientation failure lost its decision context"))?; let basis = cache .get::(&context.basis_id)? .ok_or_else(|| anyhow!("reorientation failure lost its reasoning basis"))?; context.validate(&basis)?; - let model_failure = - crate::model_pass_failure_for_request(store_path, &context.terminal_request_id)? - .ok_or_else(|| anyhow!("reorientation failure has no exact model-pass failure"))?; + model_failure.validate(&basis, &context)?; if model_failure.decision_context_id != context.context_id { return Err(anyhow!( "reorientation failure crossed its model-pass failure context" @@ -325,14 +374,13 @@ pub fn record_reorientation_pass_failure( } let failure = EpiphanyMindReorientationPassFailureDocument { schema_version: MIND_REORIENTATION_PASS_FAILURE_SCHEMA_VERSION.into(), - failure_id: format!("reorientation-pass-failure-{}", terminal.result_id), + failure_id: format!("reorientation-pass-failure-{}", model_failure.failure_id), request_id: request.request_id.clone(), job_id: job_id.into(), - runtime_result_id: terminal.result_id.clone(), - decision_context_id: decision_context_id.into(), - verdict: terminal.verdict.clone(), - summary: terminal.summary.clone(), - failed_at: terminal.completed_at.clone(), + model_pass_failure_id: model_failure.failure_id.clone(), + decision_context_id: context.context_id.clone(), + summary: model_failure.summary.clone(), + failed_at: model_failure.failed_at.clone(), }; if let Some(existing) = cache.get::(&failure.failure_id)? @@ -340,7 +388,7 @@ pub fn record_reorientation_pass_failure( if existing != failure { return Err(anyhow!("reorientation failure replay was substituted")); } - return mind_receipt_for_failure_context(&cache, decision_context_id); + return mind_receipt_for_failure_context(&cache, &context.context_id); } let snapshot = cache.snapshot_envelopes(); let mut strong_reads = Vec::new(); @@ -349,17 +397,12 @@ pub fn record_reorientation_pass_failure( EpiphanyReorientationRequest::TYPE, request.request_id.as_str(), ), - ( - EpiphanyReorientationLaunchBinding::TYPE, - binding.binding_id.as_str(), - ), (crate::EpiphanyRuntimeWorkerLaunchRequest::TYPE, job_id), (crate::EpiphanyRuntimeJob::TYPE, job_id), ( - crate::EpiphanyRuntimeJobResult::TYPE, - terminal.result_id.as_str(), + crate::EpiphanyDecisionContext::TYPE, + context.context_id.as_str(), ), - (crate::EpiphanyDecisionContext::TYPE, decision_context_id), ( crate::EpiphanyModelPassFailure::TYPE, model_failure.failure_id.as_str(), @@ -375,14 +418,13 @@ pub fn record_reorientation_pass_failure( } let write = crate::mind_documents::prepare_mind_document(&cache, &failure.failure_id, &failure)?; - match crate::reasoning_context::commit_mind_mutation_with_derived_companions( + match crate::reasoning_context::commit_mind_mutation( store_path, - decision_context_id, + &context.context_id, "Continuity.reorientation_failure", strong_reads, vec![write], - Vec::new(), - &terminal.completed_at, + &model_failure.failed_at, )? { crate::EpiphanyMindCommitOutcome::Committed(receipt) => Ok(receipt), crate::EpiphanyMindCommitOutcome::Conflict { @@ -408,11 +450,8 @@ pub fn launch_current_reorientation_work( return Err(anyhow!("reorientation request is not launchable")); } validate_reorientation_request_current(&cache, &work.request)?; - let attempt_ordinal = cache - .get_all::()? - .into_iter() - .filter(|binding| binding.request_id == work.request.request_id) - .count() as u64; + let prior_launches = reorientation_launches(&cache, &work.request.request_id)?; + let attempt_ordinal = prior_launches.len(); let job_id = format!( "reorientation-{}-attempt-{attempt_ordinal}", work.request.request_id @@ -429,9 +468,6 @@ pub fn launch_current_reorientation_work( &cache, crate::RuntimeSpineHeartbeatJobOptions { runtime_id: work.request.runtime_id.clone(), - session_id: crate::EPIPHANY_RUNTIME_ROOT_SESSION_ID.into(), - objective: "Reorient from one exact keyed Mind projection".into(), - coordinator_note: "Reorientation current-work owner opened this pass.".into(), job_id: job_id.clone(), role: crate::EPIPHANY_REORIENT_OWNER_ROLE.into(), binding_id: crate::EPIPHANY_REORIENT_LAUNCH_BINDING_ID.into(), @@ -439,39 +475,61 @@ pub fn launch_current_reorientation_work( instruction: "Decide resume or regather from only the sealed typed continuity projection. Return the reorientation result contract; do not invent live state.".into(), launch_document, output_contract_id: output_contract_id.clone(), - proposal_modeling_request_id: None, - frontier_planning_request_id: None, - frontier_plan_mind_request_id: None, - imagination_consideration_request_id: None, - admitted_model_direction_consideration_request_id: None, - repo_frontier_modeling_request_id: None, - repo_frontier_research_request_id: None, - repo_frontier_verification_request_id: None, created_at: created_at.into(), }, )?; - let binding = EpiphanyReorientationLaunchBinding { - schema_version: REORIENTATION_LAUNCH_BINDING_SCHEMA_VERSION.into(), - binding_id: format!("reorientation-launch-{job_id}"), - request_id: work.request.request_id.clone(), - job_id: job_id.clone(), - attempt_ordinal, - launched_at: created_at.into(), - }; - let expected = exact_source_envelopes(&cache, &work.request.source_documents)? + let mut expected = exact_source_envelopes(&cache, &work.request.source_documents)? .into_iter() .chain( cache .get_envelope::(&work.request.request_id)? .into_iter(), ) - .collect(); + .collect::>(); + for (_, launch) in &prior_launches { + expected.push( + cache + .get_envelope::(&launch.job_id)? + .ok_or_else(|| anyhow!("reorientation retry lost its prior launch envelope"))?, + ); + } + if let Some((_, latest)) = prior_launches.last() { + expected.push( + cache + .get_envelope::(&latest.job_id)? + .ok_or_else(|| anyhow!("reorientation retry lost its prior job envelope"))?, + ); + for failure in cache + .get_all::()? + .into_iter() + .filter(|failure| { + failure.request_id == work.request.request_id && failure.job_id == latest.job_id + }) + { + expected.push( + cache + .get_envelope::( + &failure.failure_id, + )? + .ok_or_else(|| anyhow!("reorientation retry lost its failure envelope"))?, + ); + expected.push( + cache + .get_envelope::( + &failure.model_pass_failure_id, + )? + .ok_or_else(|| { + anyhow!("reorientation retry lost its model-failure envelope") + })?, + ); + } + } crate::current_work::commit_current_work_launch( store_path, &cache, expected, prepared, - vec![cache.prepare_entry(&binding.binding_id, &binding)?.0], + Vec::new(), "reorientation", )?; Ok(job_id) @@ -487,13 +545,12 @@ pub fn accept_reorientation_result( let store_path = store_path.as_ref(); let mut cache = crate::runtime_spine_cache(store_path)?; cache.pull_all_backing_stores()?; - let binding = cache - .get_all::()? - .into_iter() - .find(|binding| binding.job_id == job_id) - .ok_or_else(|| anyhow!("reorientation result has no exact launch binding"))?; + let launch = cache + .get::(job_id)? + .ok_or_else(|| anyhow!("reorientation result has no immutable launch"))?; + let launch_document = reorientation_launch_document(&launch)?; let request = cache - .get::(&binding.request_id)? + .get::(&launch_document.request_id)? .ok_or_else(|| anyhow!("reorientation result lost its request"))?; validate_reorientation_request_intrinsic(&request)?; let job = cache @@ -554,23 +611,12 @@ pub fn accept_reorientation_result( next_safe_move: result.next_safe_move.clone(), decided_at: accepted_at.into(), }; - let finding = crate::interpret_runtime_reorient_worker_result(&result); - let recovery = crate::continuity_recovery_receipt_from_reorient_finding( - format!("continuity-recovery-{}", request.request_id), - crate::EPIPHANY_REORIENT_LAUNCH_BINDING_ID.into(), - &finding, - accepted_at.into(), - ); let mut strong_reads = exact_source_envelopes(&cache, &request.source_documents)?; for (document_type, document_key) in [ ( EpiphanyReorientationRequest::TYPE, request.request_id.as_str(), ), - ( - EpiphanyReorientationLaunchBinding::TYPE, - binding.binding_id.as_str(), - ), (crate::EpiphanyRuntimeWorkerLaunchRequest::TYPE, job_id), (crate::EpiphanyRuntimeJob::TYPE, job_id), (crate::EpiphanyRuntimeReorientWorkerResult::TYPE, job_id), @@ -589,14 +635,12 @@ pub fn accept_reorientation_result( } let write = crate::mind_documents::prepare_mind_document(&cache, &decision.decision_id, &decision)?; - let companion = cache.prepare_entry(&recovery.receipt_id, &recovery)?.0; - match crate::reasoning_context::commit_mind_mutation_with_derived_companions( + match crate::reasoning_context::commit_mind_mutation( store_path, &result.decision_context_id, "Continuity.reorientation", strong_reads, vec![write], - vec![companion], accepted_at, )? { crate::EpiphanyMindCommitOutcome::Committed(receipt) => Ok(receipt), @@ -826,26 +870,6 @@ fn mind_receipt_for_failure_context( } } -fn exact_runtime_job_result( - cache: &CultCache, - job_id: &str, -) -> Result { - let mut results = cache - .get_all::()? - .into_iter() - .filter(|result| result.job_id == job_id) - .collect::>(); - match results.len() { - 1 => Ok(results.remove(0)), - 0 => Err(anyhow!( - "reorientation attempt lost its terminal runtime result" - )), - _ => Err(anyhow!( - "reorientation attempt has multiple terminal runtime results" - )), - } -} - #[cfg(test)] mod tests { use super::*; @@ -934,22 +958,11 @@ mod tests { )?); crate::open_runtime_model_execution( &store, - crate::RuntimeSpineSessionOptions { - session_id: "reorientation-model-session".into(), - objective: "Reorient from exact Mind state".into(), - created_at: "2026-08-18T10:00:03Z".into(), - coordinator_note: "test".into(), - }, crate::RuntimeSpineJobOptions { job_id: "reorientation-model-job".into(), session_id: "reorientation-model-session".into(), - role: "openai-model".into(), - created_at: "2026-08-18T10:00:03Z".into(), - summary: "Bound reorientation inference".into(), - artifact_refs: Vec::new(), }, &native, - "2026-08-18T10:00:03Z", )?; let context = crate::EpiphanyDecisionContext::new(&basis, native, Vec::new())?; crate::put_decision_context(&store, &context)?; @@ -975,13 +988,12 @@ mod tests { "2026-08-18T10:00:05Z", )?; let result = crate::EpiphanyRuntimeReorientWorkerResult { - schema_version: crate::RUNTIME_REORIENT_WORKER_RESULT_SCHEMA_VERSION.into(), result_id: "reorientation-result".into(), job_id: job_id.clone(), - mode: "resume".into(), - summary: "The sealed checkpoint remains coherent.".into(), - next_safe_move: "Continue the keyed migration.".into(), - checkpoint_still_valid: Some(true), + mode: "regather".into(), + summary: "The sealed checkpoint requires fresh external evidence.".into(), + next_safe_move: "Regather before resuming the keyed migration.".into(), + checkpoint_still_valid: Some(false), files_inspected: Vec::new(), frontier_node_ids: Vec::new(), evidence_ids: Vec::new(), @@ -993,26 +1005,29 @@ mod tests { decision_context_id: context.context_id.clone(), }; crate::put_runtime_reorient_worker_result(&store, &result)?; - crate::complete_runtime_job( - &store, - crate::RuntimeSpineJobResultOptions { - result_id: format!("runtime-result-{job_id}"), - job_id: job_id.clone(), - completed_at: "2026-08-18T10:00:06Z".into(), - verdict: "resume".into(), - summary: result.summary.clone(), - next_safe_move: result.next_safe_move.clone(), - evidence_refs: Vec::new(), - artifact_refs: Vec::new(), - decision_context_id: Some(context.context_id.clone()), - }, - )?; + let projected_result = crate::read_runtime_reorient_result(&store, &job_id)?; + assert_eq!( + projected_result.status, + crate::EpiphanyReorientResultStatus::Completed + ); + assert_eq!( + projected_result.finding.and_then(|finding| finding.mode), + Some("regather".into()) + ); + let review_work = crate::project_current_work(&store)? + .reorientation + .expect("completed Reorientation work"); + assert_eq!( + review_work.attempt.action, + EpiphanyAgentPassContinuationAction::Review + ); + assert!(!review_work.terminal_failure); cache.pull_all_backing_stores()?; cache.put( "unrelated-observation", &crate::EpiphanyMindObservationDocument { - value: epiphany_state_model::EpiphanyObservation { + value: crate::EpiphanyObservation { id: "unrelated-observation".into(), summary: "Persona observed an unrelated social fact.".into(), ..Default::default() @@ -1028,7 +1043,17 @@ mod tests { accept_reorientation_result(&store, &job_id, "2026-08-18T10:00:07Z")?, receipt ); - assert!(crate::project_current_work(&store)?.reorientation.is_none()); + let current_work = crate::project_current_work(&store)?; + assert!(current_work.reorientation.is_none()); + assert!(current_work.operator_regather_required); + assert_eq!( + crate::recommend_coordinator_action(crate::EpiphanyCoordinatorInput { + mind_present: true, + current_work, + }) + .action, + crate::EpiphanyCoordinatorAction::RegatherManually + ); let mut accepted = crate::runtime_spine_cache(&store)?; accepted.pull_all_backing_stores()?; assert!( @@ -1068,22 +1093,11 @@ mod tests { )?); crate::open_runtime_model_execution( &store, - crate::RuntimeSpineSessionOptions { - session_id: "failed-reorientation-model-session".into(), - objective: "Fail from exact Mind state".into(), - created_at: "2026-08-18T10:00:10Z".into(), - coordinator_note: "test".into(), - }, crate::RuntimeSpineJobOptions { job_id: "failed-reorientation-model-job".into(), session_id: "failed-reorientation-model-session".into(), - role: "openai-model".into(), - created_at: "2026-08-18T10:00:10Z".into(), - summary: "Bound failing inference".into(), - artifact_refs: Vec::new(), }, &failed_native, - "2026-08-18T10:00:10Z", )?; let failed_context = crate::EpiphanyDecisionContext::new(&failed_basis, failed_native, Vec::new())?; @@ -1119,27 +1133,21 @@ mod tests { failed_activation, "2026-08-18T10:00:12Z", )?; - crate::complete_runtime_job( + crate::terminalize_runtime_job( &store, - crate::RuntimeSpineJobResultOptions { - result_id: format!("runtime-result-{failed_job}"), - job_id: failed_job.clone(), - completed_at: "2026-08-18T10:00:14Z".into(), - verdict: "failed".into(), - summary: "Provider failed after the terminal request was sealed.".into(), - next_safe_move: "Review the typed failure, then retry.".into(), - evidence_refs: Vec::new(), - artifact_refs: Vec::new(), - decision_context_id: Some(failed_context.context_id.clone()), - }, + &failed_job, + EpiphanyRuntimeJobStatus::Failed, + "2026-08-18T10:00:14Z", + &model_failure.failure_id, )?; + let failed_review = crate::project_current_work(&store)? + .reorientation + .expect("failed Reorientation work"); assert_eq!( - crate::project_current_work(&store)? - .reorientation - .as_ref() - .map(|work| work.attempt.action), - Some(EpiphanyAgentPassContinuationAction::Review) + failed_review.attempt.action, + EpiphanyAgentPassContinuationAction::Review ); + assert!(failed_review.terminal_failure); let failure_receipt = record_reorientation_pass_failure(&store, &failed_job)?; assert_eq!( record_reorientation_pass_failure(&store, &failed_job)?, diff --git a/epiphany-core/src/repo_model_documents.rs b/epiphany-core/src/repo_model_documents.rs index 32703de1..ab8f7ad8 100644 --- a/epiphany-core/src/repo_model_documents.rs +++ b/epiphany-core/src/repo_model_documents.rs @@ -1,12 +1,12 @@ use std::collections::{BTreeMap, BTreeSet}; use std::path::Path; -use anyhow::{Result, anyhow}; -use cultcache_rs::{CultCache, CultCacheEnvelope, DatabaseEntry}; -use epiphany_state_model::{ +use crate::state_model::{ EpiphanyMemoryDomain, EpiphanyMemoryEdge, EpiphanyMemoryGraphSnapshot, EpiphanyMemoryNode, RepoFrontierItem, }; +use anyhow::{Result, anyhow}; +use cultcache_rs::{CultCache, CultCacheEnvelope, DatabaseEntry}; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; @@ -179,15 +179,6 @@ impl EpiphanyRepoModelBasis { Ok(()) } - pub fn validate_current(&self, store_path: impl AsRef) -> Result<()> { - self.validate()?; - let current = assemble_repo_model_view(store_path)?.reasoning_basis(); - if current != *self { - return Err(anyhow!("RepoModel basis is stale")); - } - Ok(()) - } - pub fn validate_against_cache(&self, cache: &CultCache) -> Result<()> { self.validate()?; let live = cache.snapshot_envelopes(); @@ -835,7 +826,7 @@ pub fn plan_repo_model_mutation( { insert_strong_envelope(&mut strong, existing)?; } - node.lifecycle = epiphany_state_model::EpiphanyMemoryLifecycle::Retired; + node.lifecycle = crate::EpiphanyMemoryLifecycle::Retired; nodes.insert(node_id.clone(), node.clone()); insert_envelope( &mut writes, @@ -879,7 +870,7 @@ pub fn plan_repo_model_mutation( .get_envelope::(edge_id)? .ok_or_else(|| anyhow!("RepoModel edge lost its envelope"))?; insert_strong_envelope(&mut strong, existing)?; - edge.lifecycle = epiphany_state_model::EpiphanyMemoryLifecycle::Retired; + edge.lifecycle = crate::EpiphanyMemoryLifecycle::Retired; edges.insert(edge_id.clone(), edge.clone()); insert_envelope( &mut writes, @@ -1319,9 +1310,9 @@ fn require_semantic_id(value: &str, label: &str) -> Result<()> { fn frontier_is_unresolved(item: &RepoFrontierItem) -> bool { matches!( item.status, - epiphany_state_model::RepoFrontierStatus::Proposed - | epiphany_state_model::RepoFrontierStatus::Active - | epiphany_state_model::RepoFrontierStatus::Blocked + crate::RepoFrontierStatus::Proposed + | crate::RepoFrontierStatus::Active + | crate::RepoFrontierStatus::Blocked ) } @@ -1509,9 +1500,9 @@ fn validate_claim_obligations( if !item.target_claim_ids.contains(&obligation.node_id) || !matches!( item.status, - epiphany_state_model::RepoFrontierStatus::Proposed - | epiphany_state_model::RepoFrontierStatus::Active - | epiphany_state_model::RepoFrontierStatus::Blocked + crate::RepoFrontierStatus::Proposed + | crate::RepoFrontierStatus::Active + | crate::RepoFrontierStatus::Blocked ) { return Err(anyhow!("RepoModel claim obligation is not live")); @@ -1521,9 +1512,9 @@ fn validate_claim_obligations( for item in frontier.iter().filter(|item| { matches!( item.status, - epiphany_state_model::RepoFrontierStatus::Proposed - | epiphany_state_model::RepoFrontierStatus::Active - | epiphany_state_model::RepoFrontierStatus::Blocked + crate::RepoFrontierStatus::Proposed + | crate::RepoFrontierStatus::Active + | crate::RepoFrontierStatus::Blocked ) }) { for node_id in &item.target_claim_ids { @@ -1541,10 +1532,10 @@ fn validate_claim_obligations( #[cfg(test)] mod tests { use super::*; - use crate::{RuntimeSpineInitOptions, initialize_runtime_spine}; - use epiphany_state_model::{ + use crate::state_model::{ EpiphanyMemoryEdgeKind, EpiphanyMemoryLifecycle, EpiphanyMemoryNodeKind, RepoFrontierStatus, }; + use crate::{RuntimeSpineInitOptions, initialize_runtime_spine}; fn bind_test_body( store: &Path, @@ -2251,7 +2242,10 @@ mod tests { source.document_type == crate::AtlasSurfaceOffer::TYPE && source.document_key == offer_id.to_string() })); - offer_view.reasoning_basis().validate_current(&store)?; + cache.pull_all_backing_stores()?; + offer_view + .reasoning_basis() + .validate_against_cache(&cache)?; let eve = crate::AtlasRepositoryIdentity::new("gamecult-local", "eve")?; let eve_surface = uuid::Uuid::new_v4(); diff --git a/epiphany-core/src/repo_model_gateway.rs b/epiphany-core/src/repo_model_gateway.rs index fb511642..35f77608 100644 --- a/epiphany-core/src/repo_model_gateway.rs +++ b/epiphany-core/src/repo_model_gateway.rs @@ -33,58 +33,7 @@ use cultcache_rs::DatabaseEntry; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; -pub const REPO_FRONTIER_ROUTE_SCHEMA_VERSION: &str = "epiphany.self.repo_frontier_route.v2"; -pub const REPO_FRONTIER_HANDS_AUTHORITY_SCHEMA_VERSION: &str = - "epiphany.hands.repo_frontier_authority.v0"; -pub const REPO_FRONTIER_ROUTE_CONTRACT: &str = "epiphany.repo_frontier_route.v2"; -pub const REPO_FRONTIER_HANDS_AUTHORITY_CONTRACT: &str = - "epiphany.repo_frontier_hands_authority.v0"; -pub const REPO_FRONTIER_MODELING_REQUEST_SCHEMA_VERSION: &str = - "epiphany.modeling.repo_frontier_verdict_request.v1"; -pub const REPO_FRONTIER_MODELING_REQUEST_CONTRACT: &str = - "epiphany.repo_frontier_verdict_modeling_request.v1"; -pub const REPO_FRONTIER_WORK_PROPOSAL_SCHEMA_VERSION: &str = - "epiphany.repo_frontier_work_proposal.v3"; -pub const REPO_FRONTIER_PLANNING_REQUEST_SCHEMA_VERSION: &str = - "epiphany.self.repo_frontier_planning_request.v4"; -pub const REPO_FRONTIER_PLAN_CANDIDATE_SCHEMA_VERSION: &str = - "epiphany.imagination.repo_frontier_plan_candidate.v0"; -pub const REPO_FRONTIER_PLAN_DECISION_RECEIPT_SCHEMA_VERSION: &str = - "epiphany.mind.repo_frontier_plan_decision_receipt.v2"; -pub const REPO_FRONTIER_PLAN_DECISION_CONTRACT: &str = "epiphany.repo_frontier_plan_decision.v0"; -pub const REPO_FRONTIER_PLANNING_CONTRACT: &str = "epiphany.repo_frontier_planning.v4"; -pub const REPO_FRONTIER_PLANNING_FAILURE_REVIEW_SCHEMA_VERSION: &str = - "epiphany.self.repo_frontier_planning_failure_review.v0"; -pub const REPO_FRONTIER_RESEARCH_REQUEST_SCHEMA_VERSION: &str = - "epiphany.self.repo_frontier_research_request.v3"; -pub const REPO_FRONTIER_RESEARCH_REQUEST_CONTRACT: &str = - "epiphany.repo_frontier_research_request.v3"; -pub const REPO_FRONTIER_WORK_PROPOSAL_CONTRACT: &str = - "epiphany.repo_frontier_work_proposal.inert.v3"; -pub const REPO_FRONTIER_AUTONOMOUS_PROPOSAL_BINDING_SCHEMA_VERSION: &str = - "epiphany.self.repo_frontier_autonomous_proposal_binding.v2"; pub const RUNTIME_REPOSITORY_DOMAIN_BINDING_KEY: &str = "runtime-repository-domain-binding"; -pub const RUNTIME_REPOSITORY_DOMAIN_BINDING_SCHEMA_VERSION: &str = - "epiphany.runtime.repository_domain_binding.v0"; -pub const RUNTIME_REPOSITORY_DOMAIN_BINDING_CONTRACT: &str = "deployment configuration binds one organizational repository name to one exact authenticated repository Body; Self may consume but not relabel it"; -pub const REPO_FRONTIER_PROPOSAL_MODELING_REQUEST_SCHEMA_VERSION: &str = - "epiphany.coordinator.repo_frontier_proposal_modeling_request.v0"; -pub const REPO_FRONTIER_PROPOSAL_MODELING_REQUEST_CONTRACT: &str = - "epiphany.repo_frontier_proposal_modeling_request.v0"; -pub const REPO_FRONTIER_PROPOSAL_MODELING_LAUNCH_BINDING_SCHEMA_VERSION: &str = - "epiphany.coordinator.repo_frontier_proposal_modeling_launch_binding.v2"; -pub const REPO_FRONTIER_PLANNING_LAUNCH_BINDING_SCHEMA_VERSION: &str = - "epiphany.coordinator.repo_frontier_planning_launch_binding.v0"; -pub const REPO_FRONTIER_PLANNING_LAUNCH_BINDING_CONTRACT: &str = - "epiphany.repo_frontier_planning_launch_binding.v0"; -pub const REPO_FRONTIER_PLAN_MIND_REQUEST_SCHEMA_VERSION: &str = - "epiphany.self.repo_frontier_plan_mind_request.v1"; -pub const REPO_FRONTIER_PLAN_MIND_REQUEST_CONTRACT: &str = - "epiphany.repo_frontier_plan_mind_request.v1"; -pub const REPO_FRONTIER_PLAN_MIND_LAUNCH_BINDING_SCHEMA_VERSION: &str = - "epiphany.coordinator.repo_frontier_plan_mind_launch_binding.v0"; -pub const REPO_FRONTIER_PLAN_MIND_LAUNCH_BINDING_CONTRACT: &str = - "epiphany.repo_frontier_plan_mind_launch_binding.v0"; #[derive(Debug, Clone, PartialEq, Eq, DatabaseEntry)] #[cultcache( @@ -92,8 +41,6 @@ pub const REPO_FRONTIER_PLAN_MIND_LAUNCH_BINDING_CONTRACT: &str = schema = "RepoFrontierWorkProposal" )] pub struct RepoFrontierWorkProposal { - #[cultcache(key = 0)] - pub schema_version: String, #[cultcache(key = 1)] pub proposal_id: String, #[cultcache(key = 2)] @@ -106,8 +53,6 @@ pub struct RepoFrontierWorkProposal { pub constraints: Vec, #[cultcache(key = 6)] pub evidence_refs: Vec, - #[cultcache(key = 7)] - pub contract: String, } pub fn repo_frontier_proposal_payload_sha256( @@ -120,26 +65,6 @@ pub fn repo_frontier_proposal_payload_sha256( Ok(format!("{:x}", Sha256::digest(content))) } -#[derive(Debug, Clone, PartialEq, Eq, DatabaseEntry)] -#[cultcache( - type = "epiphany.self.repo_frontier_autonomous_proposal_binding", - schema = "RepoFrontierAutonomousProposalBinding" -)] -pub struct RepoFrontierAutonomousProposalBinding { - #[cultcache(key = 0)] - pub schema_version: String, - #[cultcache(key = 1)] - pub direction_result_id: String, - #[cultcache(key = 2)] - pub option_ordinal: u32, - #[cultcache(key = 3)] - pub direction_worker_job_id: String, - #[cultcache(key = 4)] - pub direction_worker_result_sha256: String, - #[cultcache(key = 5)] - pub direction_worker_launch_sha256: String, -} - #[derive(Debug, Clone, PartialEq, Eq, DatabaseEntry)] #[cultcache( type = "epiphany.runtime.repository_domain_binding", @@ -147,23 +72,9 @@ pub struct RepoFrontierAutonomousProposalBinding { )] pub struct RuntimeRepositoryDomainBinding { #[cultcache(key = 0)] - pub schema_version: String, - #[cultcache(key = 1)] - pub binding_id: String, - #[cultcache(key = 2)] pub repository_full_name: String, - #[cultcache(key = 3)] - pub runtime_id: String, - #[cultcache(key = 4)] - pub swarm_id: String, - #[cultcache(key = 5)] - pub workspace_id: String, - #[cultcache(key = 6)] + #[cultcache(key = 1)] pub body_binding_sha256: String, - #[cultcache(key = 7)] - pub bound_at: String, - #[cultcache(key = 8)] - pub contract: String, } #[derive(Debug, Clone, PartialEq, Eq, DatabaseEntry)] @@ -172,8 +83,6 @@ pub struct RuntimeRepositoryDomainBinding { schema = "RepoFrontierProposalModelingRequest" )] pub struct RepoFrontierProposalModelingRequest { - #[cultcache(key = 0)] - pub schema_version: String, #[cultcache(key = 1)] pub request_id: String, #[cultcache(key = 2)] @@ -190,24 +99,12 @@ pub struct RepoFrontierProposalModelingRequest { pub workspace: String, #[cultcache(key = 8)] pub selected_at: String, - #[cultcache(key = 9)] - pub contract: String, -} - -#[derive(Debug, Clone, PartialEq, Eq, DatabaseEntry)] -#[cultcache( - type = "epiphany.coordinator.repo_frontier_proposal_modeling_launch_binding", - schema = "RepoFrontierProposalModelingLaunchBinding" -)] -pub struct RepoFrontierProposalModelingLaunchBinding { - #[cultcache(key = 0)] - pub schema_version: String, - #[cultcache(key = 1)] - pub proposal_modeling_request_id: String, - #[cultcache(key = 2)] - pub job_id: String, - #[cultcache(key = 3)] - pub worker_launch_document_sha256: String, + #[cultcache(key = 10)] + pub direction_result_id: String, + #[cultcache(key = 11)] + pub direction_option_ordinal: u32, + #[cultcache(key = 12)] + pub direction_worker_job_id: String, } #[derive(Debug, Clone, PartialEq, Eq, DatabaseEntry)] @@ -216,8 +113,6 @@ pub struct RepoFrontierProposalModelingLaunchBinding { schema = "RepoFrontierPlanningRequest" )] pub struct RepoFrontierPlanningRequest { - #[cultcache(key = 0)] - pub schema_version: String, #[cultcache(key = 1)] pub request_id: String, #[cultcache(key = 2)] @@ -234,8 +129,6 @@ pub struct RepoFrontierPlanningRequest { pub repository_scope: Vec, #[cultcache(key = 8)] pub requested_at: String, - #[cultcache(key = 9)] - pub contract: String, #[cultcache(key = 10)] pub runtime_id: String, /// Exact current documents that own whether this planning work remains @@ -248,46 +141,12 @@ pub struct RepoFrontierPlanningRequest { pub claim_obligation_documents: Vec, } -#[derive(Debug, Clone, PartialEq, Eq, DatabaseEntry)] -#[cultcache( - type = "epiphany.coordinator.repo_frontier_planning_launch_binding", - schema = "RepoFrontierPlanningLaunchBinding" -)] -pub struct RepoFrontierPlanningLaunchBinding { - #[cultcache(key = 0)] - pub schema_version: String, - #[cultcache(key = 1)] - pub binding_record_id: String, - #[cultcache(key = 2)] - pub planning_request_id: String, - #[cultcache(key = 3)] - pub job_id: String, - #[cultcache(key = 4)] - pub binding_id: String, - #[cultcache(key = 5)] - pub runtime_id: String, - #[cultcache(key = 6)] - pub thread_id: String, - #[cultcache(key = 7)] - pub launched_at: String, - #[cultcache(key = 8)] - pub worker_launch_document_sha256: String, - #[cultcache(key = 9)] - pub contract: String, - #[cultcache(key = 10, default)] - pub attempt_ordinal: u64, - #[cultcache(key = 11, default)] - pub superseded_failure_result_id: Option, -} - #[derive(Debug, Clone, PartialEq, Eq, DatabaseEntry)] #[cultcache( type = "epiphany.self.repo_frontier_research_request", schema = "RepoFrontierResearchRequest" )] pub struct RepoFrontierResearchRequest { - #[cultcache(key = 0)] - pub schema_version: String, #[cultcache(key = 1)] pub request_id: String, #[cultcache(key = 2)] @@ -304,8 +163,6 @@ pub struct RepoFrontierResearchRequest { pub requested_at: String, #[cultcache(key = 8)] pub runtime_id: String, - #[cultcache(key = 10)] - pub contract: String, /// Immutable public source identities already owned by the admitted /// frontier. This is dedicated retrieval authority, not worker-authored /// evidence or a search hint. @@ -324,8 +181,6 @@ pub struct RepoFrontierResearchRequest { schema = "RepoFrontierPlanCandidate" )] pub struct RepoFrontierPlanCandidate { - #[cultcache(key = 0)] - pub schema_version: String, #[cultcache(key = 1)] pub candidate_id: String, #[cultcache(key = 2)] @@ -354,8 +209,6 @@ pub struct RepoFrontierPlanCandidate { pub commit_message: String, #[cultcache(key = 14)] pub proposed_at: String, - #[cultcache(key = 15)] - pub contract: String, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] @@ -401,8 +254,6 @@ pub struct RepoFrontierPlanningLifecycle { schema = "RepoFrontierPlanMindRequest" )] pub struct RepoFrontierPlanMindRequest { - #[cultcache(key = 0)] - pub schema_version: String, #[cultcache(key = 1)] pub request_id: String, #[cultcache(key = 2)] @@ -419,40 +270,6 @@ pub struct RepoFrontierPlanMindRequest { pub runtime_id: String, #[cultcache(key = 9)] pub requested_at: String, - #[cultcache(key = 10)] - pub contract: String, -} - -#[derive(Debug, Clone, PartialEq, Eq, DatabaseEntry)] -#[cultcache( - type = "epiphany.coordinator.repo_frontier_plan_mind_launch_binding", - schema = "RepoFrontierPlanMindLaunchBinding" -)] -pub struct RepoFrontierPlanMindLaunchBinding { - #[cultcache(key = 0)] - pub schema_version: String, - #[cultcache(key = 1)] - pub binding_record_id: String, - #[cultcache(key = 2)] - pub mind_request_id: String, - #[cultcache(key = 3)] - pub job_id: String, - #[cultcache(key = 4)] - pub binding_id: String, - #[cultcache(key = 5)] - pub runtime_id: String, - #[cultcache(key = 6)] - pub thread_id: String, - #[cultcache(key = 7)] - pub launched_at: String, - #[cultcache(key = 8)] - pub worker_launch_document_sha256: String, - #[cultcache(key = 9)] - pub contract: String, - #[cultcache(key = 10, default)] - pub attempt_ordinal: u64, - #[cultcache(key = 11, default)] - pub superseded_failure_result_id: Option, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] @@ -473,8 +290,6 @@ pub struct RepoFrontierPlanMindDecision { schema = "RepoFrontierPlanDecisionReceipt" )] pub struct RepoFrontierPlanDecisionReceipt { - #[cultcache(key = 0)] - pub schema_version: String, #[cultcache(key = 1)] pub decision_id: String, #[cultcache(key = 2)] @@ -497,8 +312,6 @@ pub struct RepoFrontierPlanDecisionReceipt { pub rationale: String, #[cultcache(key = 11)] pub decided_at: String, - #[cultcache(key = 12)] - pub contract: String, #[cultcache(key = 13)] pub decision_source: RepoFrontierPlanDecisionSource, } @@ -522,8 +335,6 @@ pub enum RepoFrontierVerdictDisposition { schema = "RepoFrontierModelingRequest" )] pub struct RepoFrontierModelingRequest { - #[cultcache(key = 0)] - pub schema_version: String, #[cultcache(key = 1)] pub request_id: String, #[cultcache(key = 2)] @@ -548,8 +359,6 @@ pub struct RepoFrontierModelingRequest { pub allowed_disposition: RepoFrontierVerdictDisposition, #[cultcache(key = 12)] pub requested_at: String, - #[cultcache(key = 13)] - pub contract: String, } #[derive(Debug, Clone, PartialEq, Eq, DatabaseEntry)] @@ -558,8 +367,6 @@ pub struct RepoFrontierModelingRequest { schema = "RepoFrontierPlanningFailureReview" )] pub struct RepoFrontierPlanningFailureReview { - #[cultcache(key = 0)] - pub schema_version: String, #[cultcache(key = 1)] pub review_id: String, #[cultcache(key = 2)] @@ -588,8 +395,6 @@ pub enum RepoFrontierNextOrgan { schema = "RepoFrontierRoute" )] pub struct RepoFrontierRoute { - #[cultcache(key = 0)] - pub schema_version: String, #[cultcache(key = 1)] pub route_id: String, #[cultcache(key = 2)] @@ -613,11 +418,9 @@ pub struct RepoFrontierRoute { #[cultcache(key = 11)] pub authorized_paths: Vec, #[cultcache(key = 12, default)] - pub adopted_plan: Option, + pub adopted_plan: Option, #[cultcache(key = 13)] pub selected_at: String, - #[cultcache(key = 14)] - pub contract: String, } #[derive(Debug, Clone, PartialEq, Eq, DatabaseEntry)] @@ -626,30 +429,12 @@ pub struct RepoFrontierRoute { schema = "RepoFrontierHandsAuthority" )] pub struct RepoFrontierHandsAuthority { - #[cultcache(key = 0)] - pub schema_version: String, #[cultcache(key = 1)] pub authority_id: String, #[cultcache(key = 2)] pub route_id: String, #[cultcache(key = 3)] - pub model_projection_digest: String, - #[cultcache(key = 4)] - pub model_source_documents: Vec, - #[cultcache(key = 5)] - pub frontier_item_id: String, - #[cultcache(key = 6)] - pub frontier_item_hash: String, - #[cultcache(key = 7)] pub hands_intent_id: String, - #[cultcache(key = 8)] - pub hands_review_id: String, - #[cultcache(key = 9)] + #[cultcache(key = 4)] pub substrate_grant_receipt_id: String, - #[cultcache(key = 10)] - pub requested_paths: Vec, - #[cultcache(key = 11)] - pub granted_at: String, - #[cultcache(key = 12)] - pub contract: String, } diff --git a/epiphany-core/src/repository_body_observer.rs b/epiphany-core/src/repository_body_observer.rs index 82e4270d..d7625251 100644 --- a/epiphany-core/src/repository_body_observer.rs +++ b/epiphany-core/src/repository_body_observer.rs @@ -214,7 +214,6 @@ pub struct RepositoryBodyManifest { pub entries: Vec, } - #[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)] #[cultcache(type = "epiphany.repository_body.head", schema = "RepositoryBodyHead")] pub struct RepositoryBodyHead { @@ -331,7 +330,7 @@ pub fn admit_repository_body_observation( BODY_OBSERVATION_TYPE, &basis.observation_id, ) - .ok_or_else(|| anyhow!("repository Body observation source is absent"))?; + .ok_or_else(|| anyhow!("repository Body observation source is absent"))?; let source_observation = crate::EpiphanyMindDocumentVersion::from_envelope("epiphany-repository-body", source)?; let document = crate::EpiphanyMindRepositoryBodyObservationDocument { @@ -367,12 +366,6 @@ pub fn admit_repository_body_observation( } } -pub fn current_mind_repository_body_observation( - runtime_store: &Path, -) -> Result> { - Ok(crate::assemble_mind_view(runtime_store)?.repository_body_observation) -} - /// Loads the authenticated current Repository Body basis already persisted for /// a runtime without observing the worktree or advancing the Body generation. pub fn load_current_runtime_repository_body_basis( @@ -986,7 +979,7 @@ fn sanitized_git_command() -> Command { sanitize_git_command(&mut command); command } -fn repository_git_command(repo: &Path) -> Result { +pub(crate) fn repository_git_command(repo: &Path) -> Result { let canonical = std::fs::canonicalize(repo) .with_context(|| format!("failed to canonicalize Git repository {}", repo.display()))?; let mut command = sanitized_git_command(); @@ -1362,7 +1355,7 @@ fn validate_runtime_binding_entries( EpiphanyRuntimeSwarmBinding::TYPE, RUNTIME_SWARM_BINDING_KEY, ) - .ok_or_else(|| anyhow!("runtime store has no immutable swarm binding"))?, + .ok_or_else(|| anyhow!("runtime store has no immutable swarm binding"))?, )?; if binding.schema_version != RUNTIME_SWARM_BINDING_SCHEMA_VERSION || binding.binding_id != RUNTIME_SWARM_BINDING_KEY @@ -1586,7 +1579,7 @@ mod tests { run(d.path(), &["add", "."])?; let observed = observe_runtime_repository_body_basis(&runtime)?; assert_eq!( - current_mind_repository_body_observation(&runtime)?, + crate::assemble_mind_view(&runtime)?.repository_body_observation, Some(observed.clone()) ); let mut mind = crate::runtime_spine_cache(&runtime)?; diff --git a/epiphany-core/src/resident_readiness.rs b/epiphany-core/src/resident_readiness.rs index 564745b5..a8a947c9 100644 --- a/epiphany-core/src/resident_readiness.rs +++ b/epiphany-core/src/resident_readiness.rs @@ -214,7 +214,7 @@ pub struct ResidentProviderReadiness { } impl ResidentProviderReadiness { - pub fn validate(&self) -> Result<()> { + fn validate(&self) -> Result<()> { if self.schema_version != RESIDENT_PROVIDER_READINESS_SCHEMA_VERSION { bail!("resident provider readiness schema is unsupported"); } @@ -295,7 +295,7 @@ pub fn publish_resident_provider_readiness( Ok(readiness) } -pub fn load_resident_provider_readiness(store: &Path) -> Result> { +fn load_resident_provider_readiness(store: &Path) -> Result> { Ok(readiness_snapshot(store)?.1) } @@ -520,72 +520,6 @@ fn classify_active_lease_observation( } } -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct ResidentProviderHealth { - pub terminal_current: usize, - pub warming: usize, - pub contradictions: Vec, -} - -pub fn authenticate_resident_provider( - release: &crate::EpiphanyPackagedReleaseEntry, - witness: &str, - resident_store: &Path, - freshness_millis: u64, -) -> ResidentProviderHealth { - let mut result = ResidentProviderHealth { - terminal_current: 0, - warming: 0, - contradictions: Vec::new(), - }; - for (owner, store, role) in [("resident-self", resident_store, "swarm")] { - let executable = epiphany_packaged_release_binary_path(release, role).ok(); - let value = match load_resident_provider_readiness(store) { - Ok(Some(value)) => value, - Ok(None) => { - result.warming += 1; - continue; - } - Err(error) => { - result - .contradictions - .push(format!("{owner} provider readiness is invalid: {error:#}")); - continue; - } - }; - let role_owner_present = match resident_process_singleton_is_owned(store) { - Ok(value) => value, - Err(error) => { - result.contradictions.push(format!( - "{owner} provider singleton observation failed: {error:#}" - )); - continue; - } - }; - if provider_matches_authority( - &value, - owner, - &release.runtime_id, - &release.release_id, - witness, - Some(&release.source_commit_sha), - executable.as_deref(), - current_time_millis(), - freshness_millis, - role_owner_present, - ) { - result.terminal_current += 1; - } else if value.runtime_id == release.runtime_id && value.release_id == release.release_id { - result.warming += 1; - } else { - result - .contradictions - .push(format!("{owner} provider readiness names another release")); - } - } - result -} - fn provider_is_fresh( store: &Path, owner: &str, @@ -684,34 +618,6 @@ fn provider_authority_failures( failures } -#[allow(clippy::too_many_arguments)] -fn provider_matches_authority( - value: &ResidentProviderReadiness, - owner: &str, - runtime_id: &str, - release_id: &str, - witness: &str, - source_commit: Option<&str>, - executable: Option<&Path>, - now_millis: u64, - freshness_millis: u64, - role_owner_present: bool, -) -> bool { - provider_authority_failures( - value, - owner, - runtime_id, - release_id, - witness, - source_commit, - executable, - now_millis, - freshness_millis, - role_owner_present, - ) - .is_empty() -} - fn canonical_or_absolute(path: &Path) -> PathBuf { fs::canonicalize(path).unwrap_or_else(|_| path.to_path_buf()) } @@ -728,20 +634,12 @@ fn directory_ready(path: &Path) -> bool { fs::metadata(path).is_ok_and(|value| value.is_dir() && !value.permissions().readonly()) } -fn codex_credentials_ready(codex_home: &Path) -> bool { - directory_ready(codex_home) && credential_file_ready(&codex_home.join("auth.json")) -} - fn model_provider_credential_ready(policy: &ResidentSelfPolicy) -> bool { match policy.model_provider.as_str() { - "openai-codex" | "openai" => codex_credentials_ready(&policy.codex_home), - "openrouter" => policy + "openai-codex" | "openai" | "openrouter" => policy .provider_credential_path .as_deref() - .is_some_and(|path| { - fs::metadata(path).is_ok_and(|metadata| metadata.is_file()) - && fs::File::open(path).is_ok() - }), + .is_some_and(credential_file_ready), _ => false, } } @@ -845,60 +743,6 @@ mod tests { } } - #[test] - fn stale_wrong_release_and_absent_role_owner_cannot_be_ready() { - let value = provider(); - let executable = std::env::current_exe().unwrap(); - assert!(provider_matches_authority( - &value, - "resident-self", - "ygg", - "release-a", - &value.release_witness_sha256, - Some("commit-a"), - Some(&executable), - 1_050, - 100, - true - )); - assert!(!provider_matches_authority( - &value, - "resident-self", - "ygg", - "release-b", - &value.release_witness_sha256, - Some("commit-a"), - Some(&executable), - 1_050, - 100, - true - )); - assert!(!provider_matches_authority( - &value, - "resident-self", - "ygg", - "release-a", - &value.release_witness_sha256, - Some("commit-a"), - Some(&executable), - 1_101, - 100, - true - )); - assert!(!provider_matches_authority( - &value, - "resident-self", - "ygg", - "release-a", - &value.release_witness_sha256, - Some("commit-a"), - Some(&executable), - 1_050, - 100, - false - )); - } - #[test] fn role_singleton_probe_observes_only_a_live_owner() -> Result<()> { let temp = tempfile::tempdir()?; @@ -944,7 +788,7 @@ mod tests { let store = temp.path().join("mixed.cc"); let mut mixed = CultCache::new(); mixed.register_entry_type::()?; - mixed.add_generic_backing_store(SingleFileMessagePackBackingStore::new(&store)); + mixed.add_generic_backing_store(SingleFileMessagePackBackingStore::new(&store))?; let foreign = ForeignOwnerEntry { value: "must survive readiness CAS".into(), }; @@ -972,30 +816,6 @@ mod tests { Ok(()) } - #[cfg(unix)] - #[test] - fn unix_credentials_reject_group_or_other_access() -> Result<()> { - use std::os::unix::fs::PermissionsExt; - let temp = tempfile::tempdir()?; - let auth = temp.path().join("auth.json"); - fs::write(&auth, b"{}")?; - fs::set_permissions(&auth, fs::Permissions::from_mode(0o400))?; - assert!(!codex_credentials_ready(temp.path())); - fs::set_permissions(&auth, fs::Permissions::from_mode(0o644))?; - assert!(!codex_credentials_ready(temp.path())); - fs::set_permissions(&auth, fs::Permissions::from_mode(0o600))?; - assert!(codex_credentials_ready(temp.path())); - Ok(()) - } - - #[test] - fn legacy_credentials_file_cannot_impersonate_canonical_auth() -> Result<()> { - let temp = tempfile::tempdir()?; - fs::write(temp.path().join("credentials.json"), b"{}")?; - assert!(!codex_credentials_ready(temp.path())); - Ok(()) - } - #[test] fn readiness_uses_mounted_cognitive_identity_for_projection_and_brake() -> Result<()> { let temp = tempfile::tempdir()?; @@ -1026,7 +846,7 @@ mod tests { runtime_store, local_verse_store, artifact_root: shared.join("artifacts"), - codex_home: shared.join("codex-home"), + model_connector_endpoint: "127.0.0.1:17891".parse().unwrap(), mcp_config: shared.join("mcp.toml"), model_provider: "test".into(), model: "test-model".into(), @@ -1116,79 +936,22 @@ mod tests { let mut value = provider(); value.observed_at_millis = 1_500; let executable = PathBuf::from(&value.process_executable_path); - assert!(provider_matches_authority( - &value, - "resident-self", - "ygg", - "release-a", - &value.release_witness_sha256, - Some("commit-a"), - Some(&executable), - 1_000, - 100, - true, - )); + let failures = |value: &ResidentProviderReadiness| { + provider_authority_failures( + value, + "resident-self", + "ygg", + "release-a", + &value.release_witness_sha256, + Some("commit-a"), + Some(&executable), + 1_000, + 100, + true, + ) + }; + assert!(failures(&value).is_empty()); value.observed_at_millis = 2_001; - assert!(!provider_matches_authority( - &value, - "resident-self", - "ygg", - "release-a", - &value.release_witness_sha256, - Some("commit-a"), - Some(&executable), - 1_000, - 100, - true, - )); - } - - #[test] - fn aggregate_health_warms_on_missing_and_rejects_wrong_release() -> Result<()> { - let temp = tempfile::tempdir()?; - let resident = temp.path().join("resident.cc"); - let executable = std::env::current_exe()?; - let binary = |role: &str| crate::EpiphanyPackagedReleaseBinary { - role: role.into(), - file_name: executable.file_name().unwrap().to_string_lossy().into(), - canonical_path: executable.display().to_string(), - sha256: format!("sha256-{}", "b".repeat(64)), - byte_len: 1, - }; - let release = crate::EpiphanyPackagedReleaseEntry { - schema_version: crate::EPIPHANY_PACKAGED_RELEASE_SCHEMA_VERSION.into(), - release_id: "release-a".into(), - runtime_id: "ygg".into(), - source_commit_sha: "commit-a".into(), - target_triple: "test".into(), - cargo_profile: "release".into(), - toolchain_fingerprint: "toolchain".into(), - created_at_utc: "now".into(), - package_root: temp.path().display().to_string(), - binaries: vec![binary("swarm")], - private_state_exposed: false, - }; - let witness = format!("sha256-{}", "a".repeat(64)); - let missing = authenticate_resident_provider(&release, &witness, &resident, 10); - assert_eq!(missing.warming, 1); - let process = crate::capture_process_instance(std::process::id())?; - let mut wrong = provider(); - wrong.runtime_id = "ygg".into(); - wrong.release_id = "other-release".into(); - wrong.release_witness_sha256 = witness.clone(); - wrong.source_commit = "commit-a".into(); - wrong.observed_at_millis = 10; - wrong.process_id = process.process_id; - wrong.process_creation_token = process.creation_token; - wrong.process_executable_path = process.executable_path.display().to_string(); - publish_resident_provider_readiness(&resident, wrong)?; - let rejected = authenticate_resident_provider(&release, &witness, &resident, 10); - assert!( - rejected - .contradictions - .iter() - .any(|reason| reason.contains("another release")) - ); - Ok(()) + assert_eq!(failures(&value), vec!["future-observation"]); } } diff --git a/epiphany-core/src/resident_self.rs b/epiphany-core/src/resident_self.rs index a516b73e..9871ca45 100644 --- a/epiphany-core/src/resident_self.rs +++ b/epiphany-core/src/resident_self.rs @@ -5,12 +5,11 @@ use cultcache_rs::{ use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; use std::collections::{BTreeSet, HashSet}; +use std::net::SocketAddr; use std::path::{Path, PathBuf}; pub const RESIDENT_SELF_STATE_KEY: &str = "resident-self"; -pub const RESIDENT_SELF_STATE_SCHEMA_VERSION: &str = "epiphany.resident_self.state.v1"; -pub const RESIDENT_SELF_RUNTIME_RECEIPT_SCHEMA_VERSION: &str = - "epiphany.resident_self.runtime_receipt.v0"; +pub const RESIDENT_SELF_STATE_SCHEMA_VERSION: &str = "epiphany.resident_self.state.v3"; pub const RESIDENT_SELF_PRESSURE_SCHEMA_VERSION: &str = "epiphany.resident_self.pressure.v0"; pub const RESIDENT_SELF_COORDINATOR_CONTINUATION_PRESSURE_KIND: &str = "coordinator-internal-continuation"; @@ -26,7 +25,7 @@ pub const RESIDENT_SELF_TERMINAL_RECEIPT_SCHEMA_VERSION: &str = "epiphany.resident_self.terminal_receipt.v1"; pub const RESIDENT_SELF_CHILD_CLAIM_SCHEMA_VERSION: &str = "epiphany.resident_self.child_claim.v0"; pub const RESIDENT_SELF_RETENTION_HEAD_SCHEMA_VERSION: &str = - "epiphany.resident_self.retention_head.v0"; + "epiphany.resident_self.retention_head.v1"; pub const RESIDENT_SELF_RETENTION_HEAD_KEY: &str = "resident-self-retention"; #[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)] @@ -241,7 +240,7 @@ pub struct ResidentSelfChildClaim { #[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)] #[cultcache( - type = "epiphany.resident_self.retention_head.v0", + type = "epiphany.resident_self.retention_head.v1", schema = "ResidentSelfRetentionHead" )] pub struct ResidentSelfRetentionHead { @@ -255,9 +254,7 @@ pub struct ResidentSelfRetentionHead { pub retired_envelope_count: u64, #[cultcache(key = 4)] pub retired_chain_digest: String, - #[cultcache(key = 5)] - pub retained_at_millis: u64, - #[cultcache(key = 6, default)] + #[cultcache(key = 5, default)] pub private_state_exposed: bool, } @@ -277,7 +274,7 @@ pub struct ResidentSelfPolicy { pub runtime_store: PathBuf, pub local_verse_store: PathBuf, pub artifact_root: PathBuf, - pub codex_home: PathBuf, + pub model_connector_endpoint: SocketAddr, pub mcp_config: PathBuf, pub model_provider: String, pub model: String, @@ -305,7 +302,6 @@ impl ResidentSelfPolicy { ("runtime store", &self.runtime_store), ("local Verse store", &self.local_verse_store), ("artifact root", &self.artifact_root), - ("Codex home", &self.codex_home), ("MCP config", &self.mcp_config), ("release store", &self.release_store), ] { @@ -324,6 +320,13 @@ impl ResidentSelfPolicy { path.display() )); } + if !self.model_connector_endpoint.ip().is_loopback() + || self.model_connector_endpoint.port() == 0 + { + return Err(anyhow!( + "resident Self model connector must use a nonzero loopback endpoint" + )); + } if self.model_provider.trim().is_empty() || self.model.trim().is_empty() || self.max_steps == 0 @@ -530,32 +533,6 @@ impl Default for ResidentSelfState { } } -#[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)] -#[cultcache( - type = "epiphany.resident_self.runtime_receipt.v0", - schema = "ResidentSelfRuntimeReceipt" -)] -pub struct ResidentSelfRuntimeReceipt { - #[cultcache(key = 0)] - pub schema_version: String, - #[cultcache(key = 1)] - pub receipt_id: String, - #[cultcache(key = 2)] - pub occurred_at_millis: u64, - #[cultcache(key = 3)] - pub status: String, - #[cultcache(key = 4)] - pub reason: String, - #[cultcache(key = 5, default)] - pub turn_id: Option, - #[cultcache(key = 6, default)] - pub coordinator_receipt_id: Option, - #[cultcache(key = 7, default)] - pub process_id: Option, - #[cultcache(key = 8, default)] - pub private_state_exposed: bool, -} - #[derive(Clone, Debug, PartialEq, Eq)] pub enum ChildObservation { Running, @@ -647,8 +624,8 @@ pub fn coordinator_argv( turn_id.into(), "--cwd".into(), policy.workspace.display().to_string(), - "--codex-home".into(), - policy.codex_home.display().to_string(), + "--connector-endpoint".into(), + policy.model_connector_endpoint.to_string(), "--mcp-config".into(), policy.mcp_config.display().to_string(), "--artifact-dir".into(), @@ -785,7 +762,6 @@ pub fn resident_prepared_launch_thread_id(prepared: &ResidentSelfPreparedLaunch) fn state_cache(path: &Path) -> Result { let mut cache = CultCache::new(); cache.register_entry_type::()?; - cache.register_entry_type::()?; cache.register_entry_type::()?; cache.register_entry_type::()?; cache.register_entry_type::()?; @@ -795,14 +771,15 @@ fn state_cache(path: &Path) -> Result { for envelope in SingleFileMessagePackBackingStore::new(path).pull_all()? { if matches!( envelope.r#type.as_str(), - "epiphany.resident_self.heartbeat_grant.v0" | "epiphany.resident_self.terminal_ack.v0" + "epiphany.resident_self.heartbeat_grant.v0" + | "epiphany.resident_self.terminal_ack.v0" + | "epiphany.resident_self.retention_head.v0" ) { return Err(anyhow!( "resident Self store contains a pre-cut heartbeat lifecycle document" )); } let owned = envelope.r#type == ResidentSelfState::TYPE - || envelope.r#type == ResidentSelfRuntimeReceipt::TYPE || envelope.r#type == ResidentSelfPressure::TYPE || envelope.r#type == ResidentSelfGrant::TYPE || envelope.r#type == ResidentSelfTerminalReceipt::TYPE @@ -820,25 +797,22 @@ fn state_cache(path: &Path) -> Result { } match envelope.r#type.as_str() { ResidentSelfState::TYPE => { - cache.load_envelope::(envelope)?; - } - ResidentSelfRuntimeReceipt::TYPE => { - cache.load_envelope::(envelope)?; + cache.put_envelope::(envelope)?; } ResidentSelfPressure::TYPE => { - cache.load_envelope::(envelope)?; + cache.put_envelope::(envelope)?; } ResidentSelfGrant::TYPE => { - cache.load_envelope::(envelope)?; + cache.put_envelope::(envelope)?; } ResidentSelfTerminalReceipt::TYPE => { - cache.load_envelope::(envelope)?; + cache.put_envelope::(envelope)?; } ResidentSelfChildClaim::TYPE => { - cache.load_envelope::(envelope)?; + cache.put_envelope::(envelope)?; } ResidentSelfRetentionHead::TYPE => { - cache.load_envelope::(envelope)?; + cache.put_envelope::(envelope)?; } _ => unreachable!("owned resident Self type was matched above"), }; @@ -995,7 +969,6 @@ pub(crate) fn resident_self_current_work_action( let projection_digest = current_work.projection_digest()?; let decision = crate::recommend_coordinator_action(crate::EpiphanyCoordinatorInput { mind_present: true, - crrc_action: crate::EpiphanyCrrcAction::Continue, current_work, }); if !matches!( @@ -1241,8 +1214,16 @@ pub fn verify_resident_self_grant_fulfillment( let Some(request) = request else { return Ok(ResidentSelfGrantFulfillment::Fulfilled); }; + let mut runtime = crate::runtime_spine_cache(runtime_store)?; + runtime.pull_all_backing_stores()?; Ok( - if crate::runtime_typed_request_fulfillment(runtime_store, request)?.is_some() { + if crate::runtime_spine::runtime_typed_request_fulfillment_from_cache( + runtime_store, + &runtime, + request, + )? + .is_some() + { ResidentSelfGrantFulfillment::Fulfilled } else { ResidentSelfGrantFulfillment::Pending @@ -1349,11 +1330,12 @@ pub fn recover_dead_resident_typed_worker( }; let mut cache = crate::runtime_spine_cache(runtime_store)?; cache.pull_all_backing_stores()?; - let mut launches = cache - .get_all::()? - .into_iter() - .filter(|launch| request.matches_launch(launch)) - .collect::>(); + let mut launches = Vec::new(); + for launch in cache.get_all::()? { + if request.matches_launch(&launch)? { + launches.push(launch); + } + } launches.sort_by(|a, b| a.job_id.cmp(&b.job_id)); let mut retryable = Vec::new(); for launch in &launches { @@ -1838,7 +1820,6 @@ pub fn recover_receipt_free_dead_coordinator_session( return Ok(None); } let recovery = crate::EpiphanyCoordinatorDeathRecovery { - schema_version: crate::runtime_spine::COORDINATOR_DEATH_RECOVERY_SCHEMA_VERSION.into(), recovery_id: format!("coordinator-death-recovery-{session_id}"), session_id, thread_id: lease.turn_id.clone(), @@ -1888,7 +1869,7 @@ fn digest_parts(parts: impl IntoIterator>) -> String { format!("sha256:{:x}", hash.finalize()) } -pub fn resident_self_policy_digest(policy: &ResidentSelfPolicy) -> String { +fn resident_self_policy_digest(policy: &ResidentSelfPolicy) -> String { digest_parts([ policy.workspace.display().to_string(), policy.coordinator_bin.display().to_string(), @@ -2333,20 +2314,6 @@ pub fn validate_resident_self_prepared_objective( )) } -pub fn validate_resident_self_coordinator_receipt( - lease: &ResidentSelfTurnLease, - coordinator: &crate::EpiphanyCoordinatorRunReceipt, -) -> Result<()> { - validate_resident_self_coordinator_receipt_binding(lease, coordinator)?; - if !matches!( - coordinator.status.as_str(), - "planned" | "needsReview" | "completed" - ) { - return Err(anyhow!("coordinator terminal receipt is not successful")); - } - Ok(()) -} - pub fn validate_resident_self_coordinator_receipt_binding( lease: &ResidentSelfTurnLease, coordinator: &crate::EpiphanyCoordinatorRunReceipt, @@ -2372,7 +2339,7 @@ pub fn validate_resident_self_coordinator_receipt_binding( Ok(()) } -pub fn complete_resident_self_turn_after_death( +fn complete_resident_self_turn_after_death( path: &Path, lease: &ResidentSelfTurnLease, recovery: &crate::EpiphanyCoordinatorDeathRecovery, @@ -2566,17 +2533,9 @@ pub fn cancel_resident_self_turn( Ok(terminal) } -pub fn resident_self_terminal_receipts(path: &Path) -> Result> { - Ok(state_cache(path)? - .get_all::()? - .into_iter() - .collect()) -} - pub fn retain_resident_self_lifecycles( path: &Path, retain_closed: usize, - now_millis: u64, ) -> Result> { let cache = state_cache(path)?; let state = cache @@ -2718,7 +2677,6 @@ pub fn retain_resident_self_lifecycles( head.retired_envelope_count + deletions.len() as u64 }), retired_chain_digest: digest_parts(digest_inputs), - retained_at_millis: now_millis, private_state_exposed: false, }; let (replacement, _) = cache.prepare_entry(RESIDENT_SELF_RETENTION_HEAD_KEY, &head)?; @@ -2756,6 +2714,61 @@ mod pressure_replay_tests { } } + #[test] + fn obsolete_state_epoch_refuses_without_mutation() -> Result<()> { + let temp = tempfile::tempdir()?; + let store = temp.path().join("resident.cc"); + let cache = state_cache(&store)?; + let mut state = ResidentSelfState::default(); + state.schema_version = "epiphany.resident_self.state.v2".into(); + SingleFileMessagePackBackingStore::new(&store) + .push(&cache.prepare_entry(RESIDENT_SELF_STATE_KEY, &state)?.0)?; + let before = std::fs::read(&store)?; + let error = load_resident_self_state(&store).unwrap_err(); + assert!(error.to_string().contains("obsolete writable state epoch")); + assert_eq!(std::fs::read(&store)?, before); + Ok(()) + } + + #[test] + fn state_cache_reads_without_writing_the_store() -> Result<()> { + let temp = tempfile::tempdir()?; + let store = temp.path().join("resident.cc"); + let cache = state_cache(&store)?; + SingleFileMessagePackBackingStore::new(&store).push( + &cache + .prepare_entry(RESIDENT_SELF_STATE_KEY, &ResidentSelfState::default())? + .0, + )?; + let past = std::time::UNIX_EPOCH + std::time::Duration::from_secs(1_000_000_000); + std::fs::File::options() + .write(true) + .open(&store)? + .set_modified(past)?; + let listing = || -> Result> { + let mut entries = std::fs::read_dir(temp.path())? + .map(|entry| { + let entry = entry?; + let metadata = entry.metadata()?; + Ok((entry.file_name(), metadata.len(), metadata.modified()?)) + }) + .collect::>>()?; + entries.sort(); + Ok(entries) + }; + let before = listing()?; + let names = before.iter().map(|entry| entry.0.clone()).collect::>(); + assert_eq!(names, ["resident.cc", "resident.cc.lock"]); + let reopened = state_cache(&store)?; + assert!( + reopened + .get::(RESIDENT_SELF_STATE_KEY)? + .is_some() + ); + assert_eq!(listing()?, before, "a read attached or wrote a store"); + Ok(()) + } + #[test] fn configured_operator_pressure_replay_preserves_the_first_document() -> Result<()> { let temp = tempfile::tempdir()?; @@ -2796,7 +2809,7 @@ mod coordinator_launch_contract_tests { runtime_store, local_verse_store: root.join("local-verse.cc"), artifact_root: root.join("artifacts"), - codex_home: root.join("codex-home"), + model_connector_endpoint: "127.0.0.1:17891".parse().unwrap(), mcp_config: root.join("mcp.toml"), model_provider: "openrouter".into(), model: "stealth/ox-alpha".into(), @@ -2825,7 +2838,7 @@ mod coordinator_launch_contract_tests { runtime_store: PathBuf::from("/epiphany/state/runtime.cc"), local_verse_store: PathBuf::from("/epiphany/state/local-verse.cc"), artifact_root: PathBuf::from("/epiphany/artifacts"), - codex_home: PathBuf::from("/epiphany/codex-home"), + model_connector_endpoint: "127.0.0.1:17891".parse().unwrap(), mcp_config: PathBuf::from("/epiphany/mcp.toml"), model_provider: "openrouter".into(), model: "stealth/ox-alpha".into(), diff --git a/epiphany-core/src/runtime_spine.rs b/epiphany-core/src/runtime_spine.rs index 3b01587f..af9e626a 100644 --- a/epiphany-core/src/runtime_spine.rs +++ b/epiphany-core/src/runtime_spine.rs @@ -5,39 +5,15 @@ use crate::agent_launch::{ EPIPHANY_MIND_OWNER_ROLE, EPIPHANY_MIND_ROLE_BINDING_ID, EPIPHANY_MODELING_OWNER_ROLE, EPIPHANY_MODELING_ROLE_BINDING_ID, }; -use crate::continuity_gateway::ContinuityRecoveryReceipt; -use crate::eyes_gateway::EyesEvidencePacket; -use crate::eyes_gateway::EyesSourceLookupReceipt; use crate::hands_gateway::*; use crate::repo_model_gateway::{ - REPO_FRONTIER_AUTONOMOUS_PROPOSAL_BINDING_SCHEMA_VERSION, - REPO_FRONTIER_HANDS_AUTHORITY_CONTRACT, REPO_FRONTIER_HANDS_AUTHORITY_SCHEMA_VERSION, - REPO_FRONTIER_MODELING_REQUEST_CONTRACT, REPO_FRONTIER_MODELING_REQUEST_SCHEMA_VERSION, - REPO_FRONTIER_PLAN_CANDIDATE_SCHEMA_VERSION, REPO_FRONTIER_PLAN_DECISION_CONTRACT, - REPO_FRONTIER_PLAN_DECISION_RECEIPT_SCHEMA_VERSION, - REPO_FRONTIER_PLAN_MIND_LAUNCH_BINDING_CONTRACT, - REPO_FRONTIER_PLAN_MIND_LAUNCH_BINDING_SCHEMA_VERSION, - REPO_FRONTIER_PLAN_MIND_REQUEST_CONTRACT, REPO_FRONTIER_PLAN_MIND_REQUEST_SCHEMA_VERSION, - REPO_FRONTIER_PLANNING_CONTRACT, REPO_FRONTIER_PLANNING_FAILURE_REVIEW_SCHEMA_VERSION, - REPO_FRONTIER_PLANNING_LAUNCH_BINDING_CONTRACT, - REPO_FRONTIER_PLANNING_LAUNCH_BINDING_SCHEMA_VERSION, - REPO_FRONTIER_PLANNING_REQUEST_SCHEMA_VERSION, - REPO_FRONTIER_PROPOSAL_MODELING_LAUNCH_BINDING_SCHEMA_VERSION, - REPO_FRONTIER_PROPOSAL_MODELING_REQUEST_CONTRACT, - REPO_FRONTIER_PROPOSAL_MODELING_REQUEST_SCHEMA_VERSION, - REPO_FRONTIER_RESEARCH_REQUEST_CONTRACT, REPO_FRONTIER_RESEARCH_REQUEST_SCHEMA_VERSION, - REPO_FRONTIER_ROUTE_CONTRACT, REPO_FRONTIER_ROUTE_SCHEMA_VERSION, - REPO_FRONTIER_WORK_PROPOSAL_CONTRACT, REPO_FRONTIER_WORK_PROPOSAL_SCHEMA_VERSION, - RUNTIME_REPOSITORY_DOMAIN_BINDING_CONTRACT, RUNTIME_REPOSITORY_DOMAIN_BINDING_KEY, - RUNTIME_REPOSITORY_DOMAIN_BINDING_SCHEMA_VERSION, RepoFrontierAutonomousProposalBinding, - RepoFrontierHandsAuthority, RepoFrontierModelingRequest, RepoFrontierNextOrgan, - RepoFrontierPlanCandidate, RepoFrontierPlanDecision, RepoFrontierPlanDecisionReceipt, - RepoFrontierPlanMindDecision, RepoFrontierPlanMindLaunchBinding, RepoFrontierPlanMindRequest, - RepoFrontierPlanningFailureReview, RepoFrontierPlanningLaunchBinding, - RepoFrontierPlanningLifecycle, RepoFrontierPlanningLifecycleStage, RepoFrontierPlanningRequest, - RepoFrontierProposalModelingLaunchBinding, RepoFrontierProposalModelingRequest, - RepoFrontierResearchRequest, RepoFrontierRoute, RepoFrontierVerdictDisposition, - RepoFrontierWorkProposal, RuntimeRepositoryDomainBinding, + RUNTIME_REPOSITORY_DOMAIN_BINDING_KEY, RepoFrontierHandsAuthority, RepoFrontierModelingRequest, + RepoFrontierNextOrgan, RepoFrontierPlanCandidate, RepoFrontierPlanDecision, + RepoFrontierPlanDecisionReceipt, RepoFrontierPlanMindDecision, RepoFrontierPlanMindRequest, + RepoFrontierPlanningFailureReview, RepoFrontierPlanningLifecycle, + RepoFrontierPlanningLifecycleStage, RepoFrontierPlanningRequest, + RepoFrontierProposalModelingRequest, RepoFrontierResearchRequest, RepoFrontierRoute, + RepoFrontierVerdictDisposition, RepoFrontierWorkProposal, RuntimeRepositoryDomainBinding, }; use crate::runtime_store_backend::{ RuntimeSpineBackingStore as SingleFileMessagePackBackingStore, runtime_spine_backing_store, @@ -54,16 +30,11 @@ use cultcache_rs::CacheBackingStore; use cultcache_rs::CultCache; use cultcache_rs::CultCacheEnvelope; use cultcache_rs::DatabaseEntry; -use epiphany_model_adapter::EpiphanyModelAdapterStatus; use epiphany_model_adapter::EpiphanyModelReceipt; use epiphany_model_adapter::EpiphanyModelRequest; use epiphany_model_adapter::EpiphanyModelStreamEvent; use epiphany_model_adapter::EpiphanyModelStreamPayload; -use epiphany_openai_adapter::EpiphanyOpenAiAdapterStatus; -use epiphany_openai_adapter::EpiphanyOpenAiModelReceipt; -use epiphany_openai_adapter::EpiphanyOpenAiModelRequest; -use epiphany_openai_adapter::EpiphanyOpenAiStreamEvent; -use epiphany_openai_adapter::EpiphanyOpenAiStreamPayload; +use epiphany_model_adapter::EpiphanyProviderRequest; use epiphany_tool_adapter::EpiphanyToolInvocationIntent; use epiphany_tool_adapter::EpiphanyToolInvocationReceipt; use epiphany_tool_adapter::TOOL_ADAPTER_INVOCATION_INTENT_SCHEMA_ID; @@ -80,39 +51,12 @@ use std::path::Path; use std::path::PathBuf; pub const RUNTIME_IDENTITY_TYPE: &str = "epiphany.runtime.identity"; -pub const RUNTIME_SESSION_TYPE: &str = "epiphany.runtime.session"; -pub const RUNTIME_JOB_TYPE: &str = "epiphany.runtime.job"; -pub const RUNTIME_MODEL_EXECUTION_BINDING_TYPE: &str = "epiphany.runtime.model_execution_binding"; -pub const RUNTIME_TOOL_EXECUTION_BINDING_TYPE: &str = "epiphany.runtime.tool_execution_binding"; -pub const RUNTIME_WORKER_LAUNCH_REQUEST_TYPE: &str = "epiphany.runtime.worker_launch_request"; -pub const RUNTIME_WORKER_PROCESS_CLAIM_TYPE: &str = "epiphany.runtime.worker_process_claim.v0"; -pub const ARCHIVED_RUNTIME_WORKER_ATTEMPT_TYPE: &str = - "epiphany.runtime.archived_worker_attempt.v2"; -pub const RUNTIME_ROLE_WORKER_RESULT_TYPE: &str = "epiphany.runtime.role_worker_result"; -pub const RUNTIME_REORIENT_WORKER_RESULT_TYPE: &str = "epiphany.runtime.reorient_worker_result"; -pub const RUNTIME_JOB_RESULT_TYPE: &str = "epiphany.runtime.job_result"; -pub const COORDINATOR_RUN_RECEIPT_TYPE: &str = "epiphany.coordinator_run_receipt.v0"; +pub const COORDINATOR_RUN_RECEIPT_TYPE: &str = "epiphany.coordinator_run_receipt.v1"; pub const RUNTIME_IDENTITY_KEY: &str = "self"; pub const RUNTIME_SWARM_BINDING_KEY: &str = "runtime-swarm-binding"; pub const RUNTIME_SWARM_BINDING_SCHEMA_VERSION: &str = "epiphany.runtime.swarm_binding.v1"; -pub const RUNTIME_SPINE_SCHEMA_VERSION: &str = "epiphany.runtime_spine.v16"; +pub const RUNTIME_SPINE_SCHEMA_VERSION: &str = "epiphany.runtime_spine.v47"; pub const EPIPHANY_RUNTIME_ROOT_SESSION_ID: &str = "epiphany-main"; -pub const RUNTIME_MODEL_EXECUTION_BINDING_SCHEMA_VERSION: &str = - "epiphany.runtime.model_execution_binding.v0"; -pub const RUNTIME_TOOL_EXECUTION_BINDING_SCHEMA_VERSION: &str = - "epiphany.runtime.tool_execution_binding.v0"; -const ARCHIVED_RUNTIME_SESSION_SCHEMA_VERSION: &str = "epiphany.runtime.archived_session.v1"; -pub const RUNTIME_WORKER_LAUNCH_REQUEST_SCHEMA_VERSION: &str = - "epiphany.runtime.worker_launch_request.v2"; -pub const RUNTIME_WORKER_PROCESS_CLAIM_SCHEMA_VERSION: &str = - "epiphany.runtime.worker_process_claim.v0"; -pub const ARCHIVED_RUNTIME_WORKER_ATTEMPT_SCHEMA_VERSION: &str = - "epiphany.runtime.archived_worker_attempt.v2"; -pub const RUNTIME_ROLE_WORKER_RESULT_SCHEMA_VERSION: &str = - "epiphany.runtime.role_worker_result.v4"; -pub const RUNTIME_REORIENT_WORKER_RESULT_SCHEMA_VERSION: &str = - "epiphany.runtime.reorient_worker_result.v0"; -pub const COORDINATOR_RUN_RECEIPT_SCHEMA_VERSION: &str = "epiphany.coordinator_run_receipt.v0"; #[derive(Clone, Debug, PartialEq, DatabaseEntry)] #[cultcache(type = "epiphany.runtime.identity", schema = "EpiphanyRuntimeIdentity")] pub struct EpiphanyRuntimeIdentity { @@ -153,47 +97,37 @@ pub struct EpiphanyRuntimeSwarmBinding { #[derive(Clone, Debug, PartialEq, DatabaseEntry)] #[cultcache(type = "epiphany.runtime.session", schema = "EpiphanyRuntimeSession")] pub struct EpiphanyRuntimeSession { - #[cultcache(key = 0)] - pub schema_version: String, #[cultcache(key = 1)] pub session_id: String, #[cultcache(key = 2)] - pub objective: String, - #[cultcache(key = 3)] pub status: EpiphanyRuntimeSessionStatus, +} + +#[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)] +#[cultcache( + type = "epiphany.coordinator_run_basis.v0", + schema = "EpiphanyCoordinatorRunBasis" +)] +pub struct EpiphanyCoordinatorRunBasis { + #[cultcache(key = 1)] + pub session_id: String, + #[cultcache(key = 2)] + pub thread_id: String, + #[cultcache(key = 3, default)] + pub resident_launch_digest: Option, #[cultcache(key = 4)] - pub created_at: String, + pub objective: String, #[cultcache(key = 5)] - pub updated_at: String, - #[cultcache(key = 6, default)] - pub coordinator_note: String, - #[cultcache(key = 7, default)] - pub metadata: BTreeMap, + pub started_at: String, } #[derive(Clone, Debug, PartialEq, DatabaseEntry)] #[cultcache(type = "epiphany.runtime.job", schema = "EpiphanyRuntimeJob")] pub struct EpiphanyRuntimeJob { - #[cultcache(key = 0)] - pub schema_version: String, #[cultcache(key = 1)] pub job_id: String, #[cultcache(key = 2)] - pub session_id: String, - #[cultcache(key = 3)] - pub role: String, - #[cultcache(key = 4)] pub status: EpiphanyRuntimeJobStatus, - #[cultcache(key = 5)] - pub created_at: String, - #[cultcache(key = 6)] - pub updated_at: String, - #[cultcache(key = 7, default)] - pub summary: String, - #[cultcache(key = 8, default)] - pub artifact_refs: Vec, - #[cultcache(key = 9, default)] - pub metadata: BTreeMap, } #[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)] @@ -202,24 +136,12 @@ pub struct EpiphanyRuntimeJob { schema = "EpiphanyRuntimeModelExecutionBinding" )] pub struct EpiphanyRuntimeModelExecutionBinding { - #[cultcache(key = 0)] - pub schema_version: String, #[cultcache(key = 1)] - pub binding_id: String, - #[cultcache(key = 2)] pub request_id: String, - #[cultcache(key = 3)] + #[cultcache(key = 2)] pub session_id: String, - #[cultcache(key = 4)] + #[cultcache(key = 3)] pub job_id: String, - #[cultcache(key = 5)] - pub provider: String, - #[cultcache(key = 6)] - pub bound_at: String, - #[cultcache(key = 7, default)] - pub source_worker_job_id: Option, - #[cultcache(key = 8, default)] - pub reasoning_basis_id: Option, } #[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)] @@ -228,20 +150,12 @@ pub struct EpiphanyRuntimeModelExecutionBinding { schema = "EpiphanyRuntimeToolExecutionBinding" )] pub struct EpiphanyRuntimeToolExecutionBinding { - #[cultcache(key = 0)] - pub schema_version: String, #[cultcache(key = 1)] - pub binding_id: String, - #[cultcache(key = 2)] pub intent_id: String, - #[cultcache(key = 3)] + #[cultcache(key = 2)] pub session_id: String, - #[cultcache(key = 4)] + #[cultcache(key = 3)] pub job_id: String, - #[cultcache(key = 5, default)] - pub model_request_id: Option, - #[cultcache(key = 6)] - pub bound_at: String, } #[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)] @@ -250,8 +164,6 @@ pub struct EpiphanyRuntimeToolExecutionBinding { schema = "EpiphanyArchivedRuntimeSession" )] struct EpiphanyArchivedRuntimeSession { - #[cultcache(key = 0)] - schema_version: String, #[cultcache(key = 1)] session_id: String, #[cultcache(key = 2)] @@ -270,8 +182,6 @@ struct EpiphanyArchivedRuntimeSession { schema = "EpiphanyRuntimeWorkerLaunchRequest" )] pub struct EpiphanyRuntimeWorkerLaunchRequest { - #[cultcache(key = 0)] - pub schema_version: String, #[cultcache(key = 1)] pub job_id: String, #[cultcache(key = 2)] @@ -290,22 +200,6 @@ pub struct EpiphanyRuntimeWorkerLaunchRequest { pub launch_document_msgpack: Vec, #[cultcache(key = 9, default)] pub metadata: BTreeMap, - #[cultcache(key = 11, default)] - pub proposal_modeling_request_id: Option, - #[cultcache(key = 12, default)] - pub repo_frontier_verification_request_id: Option, - #[cultcache(key = 13, default)] - pub frontier_planning_request_id: Option, - #[cultcache(key = 14, default)] - pub frontier_plan_mind_request_id: Option, - #[cultcache(key = 15, default)] - pub imagination_consideration_request_id: Option, - #[cultcache(key = 16, default)] - pub admitted_model_direction_consideration_request_id: Option, - #[cultcache(key = 17, default)] - pub repo_frontier_modeling_request_id: Option, - #[cultcache(key = 19, default)] - pub repo_frontier_research_request_id: Option, } #[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)] @@ -314,8 +208,6 @@ pub struct EpiphanyRuntimeWorkerLaunchRequest { schema = "EpiphanyRuntimeWorkerProcessClaim" )] pub struct EpiphanyRuntimeWorkerProcessClaim { - #[cultcache(key = 0)] - pub schema_version: String, #[cultcache(key = 1)] pub claim_id: String, #[cultcache(key = 2)] @@ -342,12 +234,10 @@ pub struct EpiphanyRuntimeWorkerProcessClaim { #[derive(Clone, Debug, PartialEq, DatabaseEntry)] #[cultcache( - type = "epiphany.runtime.archived_worker_attempt.v2", + type = "epiphany.runtime.archived_worker_attempt.v3", schema = "EpiphanyArchivedRuntimeWorkerAttempt" )] pub struct EpiphanyArchivedRuntimeWorkerAttempt { - #[cultcache(key = 0)] - pub schema_version: String, #[cultcache(key = 1)] pub job_id: String, #[cultcache(key = 2)] @@ -366,7 +256,6 @@ pub struct EpiphanyArchivedRuntimeWorkerAttempt { pub struct EpiphanyArchivedRuntimeWorkerDecision { pub decision_context_id: String, pub role_result: Option, - pub job_results: Vec, } impl EpiphanyArchivedRuntimeWorkerAttempt { @@ -396,22 +285,11 @@ impl EpiphanyArchivedRuntimeWorkerAttempt { &decision.decision_context_id, "archived worker decision context id", )?; - if decision.job_results.is_empty() - || decision.job_results.iter().any(|result| { - result.job_id != self.job_id - || result.decision_context_id.as_deref() - != Some(decision.decision_context_id.as_str()) - }) - { - return Err(anyhow!( - "archived worker decision lost its exact generic result family" - )); - } match &decision.role_result { Some(result) - if fulfilled - && result.job_id == self.job_id - && result.decision_context_id == decision.decision_context_id => + if result.job_id == self.job_id + && result.decision_context_id == decision.decision_context_id + && (fulfilled == result.item_error.is_none()) => { Ok(()) } @@ -427,6 +305,36 @@ fn worker_process_claim_id(job_id: &str) -> String { format!("runtime-worker-process-{job_id}") } +fn worker_outcome_status( + item_error: Option<&str>, +) -> Result<(EpiphanyRuntimeJobStatus, WorkerProcessStatus)> { + match item_error { + Some(error) if error.trim().is_empty() => { + Err(anyhow!("typed worker failure requires a nonempty error")) + } + Some(_) => Ok(( + EpiphanyRuntimeJobStatus::Failed, + WorkerProcessStatus::TerminalFailure, + )), + None => Ok(( + EpiphanyRuntimeJobStatus::Completed, + WorkerProcessStatus::TerminalResult, + )), + } +} + +fn process_status_for_terminal_job( + status: &EpiphanyRuntimeJobStatus, +) -> Result { + match status { + EpiphanyRuntimeJobStatus::Completed => Ok(WorkerProcessStatus::TerminalResult), + EpiphanyRuntimeJobStatus::Failed => Ok(WorkerProcessStatus::TerminalFailure), + EpiphanyRuntimeJobStatus::Queued => { + Err(anyhow!("runtime job terminal status cannot be queued")) + } + } +} + #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct RepoFrontierVerdictModelingLaunchAuthority { pub request: RepoFrontierModelingRequest, @@ -462,14 +370,27 @@ impl EpiphanyRuntimeWorkerLaunchRequest { } } +fn role_launch_document_for_job( + cache: &CultCache, + job_id: &str, +) -> Result { + let launch = cache + .get::(job_id)? + .ok_or_else(|| anyhow!("typed role result lost its immutable launch"))?; + match launch.launch_document()? { + EpiphanyWorkerLaunchDocument::Role(document) => Ok(document), + EpiphanyWorkerLaunchDocument::Reorient(_) => { + Err(anyhow!("typed role result carried reorientation authority")) + } + } +} + #[derive(Clone, Debug, PartialEq, DatabaseEntry)] #[cultcache( type = "epiphany.runtime.role_worker_result", schema = "EpiphanyRuntimeRoleWorkerResult" )] pub struct EpiphanyRuntimeRoleWorkerResult { - #[cultcache(key = 0)] - pub schema_version: String, #[cultcache(key = 1)] pub result_id: String, #[cultcache(key = 2)] @@ -502,39 +423,18 @@ pub struct EpiphanyRuntimeRoleWorkerResult { pub risks: Vec, #[cultcache(key = 16, default)] pub research_decision_msgpack: Option>, - #[cultcache(key = 17, default)] #[cultcache(key = 18, default)] pub item_error: Option, #[cultcache(key = 19, default)] pub metadata: BTreeMap, #[cultcache(key = 20, default)] pub repo_model_mutation_proposal_msgpack: Option>, - #[cultcache(key = 21, default)] - pub verification_request_id: Option, - #[cultcache(key = 22, default)] - pub frontier_route_id: Option, - #[cultcache(key = 23, default)] - pub repo_frontier_modeling_request_id: Option, - #[cultcache(key = 24, default)] - pub proposal_modeling_request_id: Option, - #[cultcache(key = 25, default)] - pub repo_frontier_research_request_id: Option, - #[cultcache(key = 26, default)] - pub frontier_planning_request_id: Option, #[cultcache(key = 27, default)] pub frontier_plan_candidate_msgpack: Option>, - #[cultcache(key = 28, default)] - pub frontier_plan_mind_request_id: Option, #[cultcache(key = 29, default)] pub frontier_plan_mind_decision_msgpack: Option>, - #[cultcache(key = 30, default)] - pub repository_body_observation_basis: Option, - #[cultcache(key = 31, default)] - pub imagination_consideration_request_id: Option, #[cultcache(key = 32, default)] pub imagination_consideration_candidate_msgpack: Option>, - #[cultcache(key = 33, default)] - pub admitted_model_direction_consideration_request_id: Option, #[cultcache(key = 34, default)] pub admitted_model_direction_consideration_result_msgpack: Option>, #[cultcache(key = 35)] @@ -598,8 +498,6 @@ impl EpiphanyRuntimeRoleWorkerResult { schema = "EpiphanyRuntimeReorientWorkerResult" )] pub struct EpiphanyRuntimeReorientWorkerResult { - #[cultcache(key = 0)] - pub schema_version: String, #[cultcache(key = 1)] pub result_id: String, #[cultcache(key = 2)] @@ -634,46 +532,10 @@ pub struct EpiphanyRuntimeReorientWorkerResult { #[derive(Clone, Debug, PartialEq, DatabaseEntry)] #[cultcache( - type = "epiphany.runtime.job_result", - schema = "EpiphanyRuntimeJobResult" -)] -pub struct EpiphanyRuntimeJobResult { - #[cultcache(key = 0)] - pub schema_version: String, - #[cultcache(key = 1)] - pub result_id: String, - #[cultcache(key = 2)] - pub job_id: String, - #[cultcache(key = 3)] - pub session_id: String, - #[cultcache(key = 4)] - pub role: String, - #[cultcache(key = 5)] - pub verdict: String, - #[cultcache(key = 6)] - pub summary: String, - #[cultcache(key = 7)] - pub completed_at: String, - #[cultcache(key = 8, default)] - pub next_safe_move: String, - #[cultcache(key = 9, default)] - pub evidence_refs: Vec, - #[cultcache(key = 10, default)] - pub artifact_refs: Vec, - #[cultcache(key = 11, default)] - pub metadata: BTreeMap, - #[cultcache(key = 12, default)] - pub decision_context_id: Option, -} - -#[derive(Clone, Debug, PartialEq, DatabaseEntry)] -#[cultcache( - type = "epiphany.coordinator_run_receipt.v0", + type = "epiphany.coordinator_run_receipt.v1", schema = "EpiphanyCoordinatorRunReceipt" )] pub struct EpiphanyCoordinatorRunReceipt { - #[cultcache(key = 0)] - pub schema_version: String, #[cultcache(key = 1)] pub receipt_id: String, #[cultcache(key = 2)] @@ -689,49 +551,49 @@ pub struct EpiphanyCoordinatorRunReceipt { #[cultcache(key = 7, default)] pub final_reason: Option, #[cultcache(key = 8)] - pub step_count: u64, - #[cultcache(key = 9)] pub created_at: String, - #[cultcache(key = 10, default)] - pub model_provider: Option, - #[cultcache(key = 11, default)] - pub runtime_store: String, - #[cultcache(key = 12, default)] - pub artifact_refs: Vec, - #[cultcache(key = 13, default)] - pub sealed_artifact_refs: Vec, - #[cultcache(key = 14, default)] - pub metadata: BTreeMap, - #[cultcache(key = 15, default)] + #[cultcache(key = 9, default)] pub resident_grant_id: Option, - #[cultcache(key = 16, default)] + #[cultcache(key = 10, default)] pub resident_launch_digest: Option, - #[cultcache(key = 17, default)] + #[cultcache(key = 11, default)] pub resident_policy_digest: Option, - #[cultcache(key = 18, default)] + #[cultcache(key = 12, default)] pub resident_argv_digest: Option, - #[cultcache(key = 19, default)] + #[cultcache(key = 13, default)] pub resident_objective_digest: Option, - #[cultcache(key = 20, default)] + #[cultcache(key = 14, default)] pub resident_release_commit: Option, - #[cultcache(key = 21, default)] + #[cultcache(key = 15, default)] pub resident_release_manifest_digest: Option, - #[cultcache(key = 22, default)] + #[cultcache(key = 16, default)] pub resident_executable_digest: Option, - #[cultcache(key = 23, default)] - pub final_runtime_job_id: Option, +} + +#[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)] +#[cultcache( + type = "epiphany.coordinator_run_terminality.v0", + schema = "EpiphanyCoordinatorRunTerminality" +)] +pub struct EpiphanyCoordinatorRunTerminality { + #[cultcache(key = 1)] + pub terminal_id: String, + #[cultcache(key = 2)] + pub session_id: String, + #[cultcache(key = 3)] + pub terminal_kind: String, + #[cultcache(key = 4)] + pub authority_id: String, + #[cultcache(key = 5)] + pub authority_sha256: String, } #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub enum EpiphanyRuntimeSessionStatus { #[default] - Proposed, Active, - WaitingForReview, - Sleeping, Completed, - Archived, } #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] @@ -739,11 +601,8 @@ pub enum EpiphanyRuntimeSessionStatus { pub enum EpiphanyRuntimeJobStatus { #[default] Queued, - Running, - WaitingForReview, Completed, Failed, - Cancelled, } #[derive(Clone, Debug, PartialEq, Eq)] @@ -753,21 +612,6 @@ pub struct RuntimeSpineInitOptions { pub created_at: String, } -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct RuntimeSpineSessionOptions { - pub session_id: String, - pub objective: String, - pub created_at: String, - pub coordinator_note: String, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct RuntimeSpineSessionClosureOptions { - pub session_id: String, - pub completed_at: String, - pub summary: String, -} - #[derive(Clone, Debug, PartialEq, Eq)] pub struct ModelPassFailureTerminalOptions { pub decision_context_id: String, @@ -780,31 +624,11 @@ pub struct ModelPassFailureTerminalOptions { pub struct RuntimeSpineJobOptions { pub job_id: String, pub session_id: String, - pub role: String, - pub created_at: String, - pub summary: String, - pub artifact_refs: Vec, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct RuntimeSpineJobResultOptions { - pub result_id: String, - pub job_id: String, - pub completed_at: String, - pub verdict: String, - pub summary: String, - pub next_safe_move: String, - pub evidence_refs: Vec, - pub artifact_refs: Vec, - pub decision_context_id: Option, } #[derive(Clone, Debug, PartialEq)] pub struct RuntimeSpineHeartbeatJobOptions { pub runtime_id: String, - pub session_id: String, - pub objective: String, - pub coordinator_note: String, pub job_id: String, pub role: String, pub binding_id: String, @@ -812,29 +636,19 @@ pub struct RuntimeSpineHeartbeatJobOptions { pub instruction: String, pub launch_document: EpiphanyWorkerLaunchDocument, pub output_contract_id: String, - pub proposal_modeling_request_id: Option, - pub frontier_planning_request_id: Option, - pub frontier_plan_mind_request_id: Option, - pub imagination_consideration_request_id: Option, - pub admitted_model_direction_consideration_request_id: Option, - pub repo_frontier_modeling_request_id: Option, - pub repo_frontier_research_request_id: Option, - pub repo_frontier_verification_request_id: Option, pub created_at: String, } -#[derive(Clone, Debug, PartialEq)] -pub struct EpiphanyRuntimeJobSnapshot { - pub job: EpiphanyRuntimeJob, - pub result: Option, +pub fn runtime_spine_cache(store_path: impl AsRef) -> Result { + open_runtime_spine_cache(runtime_spine_backing_store(store_path.as_ref())?) } -pub fn runtime_spine_cache(store_path: impl AsRef) -> Result { - let store_path = store_path.as_ref(); - let backing_store = runtime_spine_backing_store(store_path)?; +pub(crate) fn open_runtime_spine_cache( + backing_store: SingleFileMessagePackBackingStore, +) -> Result { validate_runtime_store_epoch(&backing_store.pull_all()?)?; let mut cache = runtime_spine_schema_cache()?; - cache.add_generic_backing_store(backing_store); + cache.add_generic_backing_store(backing_store)?; Ok(cache) } @@ -847,6 +661,8 @@ fn runtime_spine_schema_cache() -> Result { cache.register_entry_type::()?; cache.register_entry_type::()?; cache.register_entry_type::()?; + cache.register_entry_type::()?; + cache.register_entry_type::()?; cache.register_entry_type::()?; cache.register_entry_type::()?; cache.register_entry_type::()?; @@ -864,54 +680,37 @@ fn runtime_spine_schema_cache() -> Result { cache.register_entry_type::()?; cache.register_entry_type::()?; cache.register_entry_type::()?; - cache.register_entry_type::()?; cache.register_entry_type::()?; cache.register_entry_type::()?; - cache.register_entry_type::()?; cache.register_entry_type::()?; cache.register_entry_type::()?; - cache.register_entry_type::()?; cache.register_entry_type::()?; cache.register_entry_type::()?; - cache.register_entry_type::()?; cache.register_entry_type::()?; cache.register_entry_type::()?; cache.register_entry_type::()?; - cache.register_entry_type::()?; cache.register_entry_type::()?; cache.register_entry_type::()?; cache.register_entry_type::()?; - cache.register_entry_type::()?; cache.register_entry_type::()?; cache.register_entry_type::()?; - cache.register_entry_type::()?; cache.register_entry_type::()?; cache.register_entry_type::()?; - cache.register_entry_type::()?; cache.register_entry_type::()?; cache.register_entry_type::()?; - cache.register_entry_type::()?; - cache.register_entry_type::()?; cache.register_entry_type::()?; cache.register_entry_type::()?; - cache.register_entry_type::()?; cache.register_entry_type::()?; cache.register_entry_type::()?; cache.register_entry_type::()?; cache.register_entry_type::()?; - cache.register_entry_type::()?; - cache.register_entry_type::()?; - cache.register_entry_type::()?; - cache.register_entry_type::()?; - cache.register_entry_type::()?; - cache.register_entry_type::()?; + cache.register_entry_type::()?; cache.register_entry_type::()?; cache.register_entry_type::()?; cache.register_entry_type::()?; cache.register_entry_type::()?; cache.register_entry_type::()?; cache.register_entry_type::()?; - cache.register_entry_type::()?; cache.register_entry_type::()?; cache.register_entry_type::()?; cache.register_entry_type::()?; @@ -1080,129 +879,49 @@ pub fn bind_runtime_to_swarm( } } -pub fn create_runtime_session( - store_path: impl AsRef, - options: RuntimeSpineSessionOptions, -) -> Result { - validate_non_empty(&options.session_id, "session id")?; - validate_non_empty(&options.objective, "objective")?; - validate_non_empty(&options.created_at, "created at")?; - let mut cache = runtime_spine_cache(store_path.as_ref())?; - cache.pull_all_backing_stores()?; - require_identity(&cache)?; - require_runtime_identity_not_archived(&cache, "session", &options.session_id)?; - if cache - .get::(&options.session_id)? - .is_some() - { - return Err(anyhow!( - "runtime session {:?} already exists", - options.session_id - )); - } - let session = EpiphanyRuntimeSession { - schema_version: RUNTIME_SPINE_SCHEMA_VERSION.to_string(), - session_id: options.session_id.clone(), - objective: options.objective, - status: EpiphanyRuntimeSessionStatus::Active, - created_at: options.created_at.clone(), - updated_at: options.created_at, - coordinator_note: options.coordinator_note, - metadata: BTreeMap::new(), - }; - cache.put(&options.session_id, &session)?; - Ok(session) -} - -pub fn ensure_runtime_session( - store_path: impl AsRef, - options: RuntimeSpineSessionOptions, -) -> Result { - validate_non_empty(&options.session_id, "session id")?; - validate_non_empty(&options.objective, "objective")?; - validate_non_empty(&options.created_at, "created at")?; - let mut cache = runtime_spine_cache(store_path.as_ref())?; - cache.pull_all_backing_stores()?; - require_identity(&cache)?; - require_runtime_identity_not_archived(&cache, "session", &options.session_id)?; - if let Some(existing) = cache.get::(&options.session_id)? { - if matches!( - existing.status, - EpiphanyRuntimeSessionStatus::Completed | EpiphanyRuntimeSessionStatus::Archived - ) { - return Err(anyhow!( - "runtime session {:?} is terminal and cannot accept jobs", - options.session_id - )); - } - return Ok(existing); - } - let session = EpiphanyRuntimeSession { - schema_version: RUNTIME_SPINE_SCHEMA_VERSION.to_string(), - session_id: options.session_id.clone(), - objective: options.objective, - status: EpiphanyRuntimeSessionStatus::Active, - created_at: options.created_at.clone(), - updated_at: options.created_at, - coordinator_note: options.coordinator_note, - metadata: BTreeMap::new(), - }; - cache.put(&options.session_id, &session)?; - Ok(session) -} - pub fn close_runtime_session( store_path: impl AsRef, - options: RuntimeSpineSessionClosureOptions, + session_id: &str, ) -> Result { - validate_non_empty(&options.session_id, "session id")?; - validate_non_empty(&options.completed_at, "session completion time")?; - validate_non_empty(&options.summary, "session completion summary")?; - if options.session_id == EPIPHANY_RUNTIME_ROOT_SESSION_ID { + validate_non_empty(session_id, "session id")?; + if session_id == EPIPHANY_RUNTIME_ROOT_SESSION_ID { return Err(anyhow!( "runtime root session {:?} is long-lived and cannot be generically completed", - options.session_id + session_id )); } let mut cache = runtime_spine_cache(store_path.as_ref())?; cache.pull_all_backing_stores()?; require_identity(&cache)?; let mut session = cache - .get::(&options.session_id)? - .ok_or_else(|| anyhow!("runtime session {:?} does not exist", options.session_id))?; + .get::(session_id)? + .ok_or_else(|| anyhow!("runtime session {session_id:?} does not exist"))?; if session.status == EpiphanyRuntimeSessionStatus::Completed { return Ok(session); } - if session.status == EpiphanyRuntimeSessionStatus::Archived { - return Err(anyhow!( - "runtime session {:?} is archived and cannot be completed", - options.session_id - )); - } + let session_job_ids = cache + .get_all::()? + .into_iter() + .filter(|binding| binding.session_id == session_id) + .map(|binding| binding.job_id) + .collect::>(); let open_job_ids = cache .get_all::()? .into_iter() .filter(|job| { - job.session_id == options.session_id - && matches!( - job.status, - EpiphanyRuntimeJobStatus::Queued - | EpiphanyRuntimeJobStatus::Running - | EpiphanyRuntimeJobStatus::WaitingForReview - ) + session_job_ids.contains(&job.job_id) + && matches!(job.status, EpiphanyRuntimeJobStatus::Queued) }) .map(|job| job.job_id) .collect::>(); if !open_job_ids.is_empty() { return Err(anyhow!( "runtime session {:?} has open jobs: {}", - options.session_id, + session_id, open_job_ids.join(", ") )); } session.status = EpiphanyRuntimeSessionStatus::Completed; - session.updated_at = options.completed_at.clone(); - session.coordinator_note = options.summary.clone(); cache.put(&session.session_id, &session)?; Ok(session) } @@ -1277,44 +996,20 @@ pub fn terminalize_model_pass_failure_session( let mut model_job = cache .get::(&binding.job_id)? .ok_or_else(|| anyhow!("model pass failure runtime job is absent"))?; - if model_job.session_id != binding.session_id { - return Err(anyhow!( - "model pass failure runtime job is outside its exact session" - )); - } - let model_job_is_live = matches!( - model_job.status, - EpiphanyRuntimeJobStatus::Queued - | EpiphanyRuntimeJobStatus::Running - | EpiphanyRuntimeJobStatus::WaitingForReview - ); - let model_job_results = cache - .get_all::()? + let model_job_is_live = matches!(model_job.status, EpiphanyRuntimeJobStatus::Queued); + let session_job_ids = cache + .get_all::()? .into_iter() - .filter(|result| result.job_id == binding.job_id) - .collect::>(); - if model_job_is_live && !model_job_results.is_empty() { - return Err(anyhow!("live model pass job already has a terminal result")); - } - if !model_job_is_live - && (model_job_results.len() != 1 || model_job_results[0].decision_context_id.is_some()) - { - return Err(anyhow!( - "terminal model transport job lost its non-authoritative result" - )); - } + .filter(|candidate| candidate.session_id == binding.session_id) + .map(|candidate| candidate.job_id) + .collect::>(); let open_job_ids = cache .get_all::()? .into_iter() .filter(|job| { - job.session_id == binding.session_id + session_job_ids.contains(&job.job_id) && (!model_job_is_live || job.job_id != binding.job_id) - && matches!( - job.status, - EpiphanyRuntimeJobStatus::Queued - | EpiphanyRuntimeJobStatus::Running - | EpiphanyRuntimeJobStatus::WaitingForReview - ) + && matches!(job.status, EpiphanyRuntimeJobStatus::Queued) }) .map(|job| job.job_id) .collect::>(); @@ -1325,8 +1020,6 @@ pub fn terminalize_model_pass_failure_session( )); } session.status = EpiphanyRuntimeSessionStatus::Completed; - session.updated_at = options.failed_at.clone(); - session.coordinator_note = options.summary.clone(); let mut expected = vec![session_envelope]; let mut replacements = vec![ cache.prepare_entry(&failure.failure_id, &failure)?.0, @@ -1334,32 +1027,8 @@ pub fn terminalize_model_pass_failure_session( ]; if model_job_is_live { model_job.status = EpiphanyRuntimeJobStatus::Failed; - model_job.updated_at = options.failed_at.clone(); - model_job.summary = options.summary.clone(); - let model_result = EpiphanyRuntimeJobResult { - schema_version: RUNTIME_SPINE_SCHEMA_VERSION.to_string(), - result_id: format!("result-model-pass-failure-{}", failure.failure_id), - job_id: binding.job_id.clone(), - session_id: binding.session_id.clone(), - role: model_job.role.clone(), - verdict: "failed".to_string(), - summary: options.summary.clone(), - completed_at: options.failed_at.clone(), - next_safe_move: - "Inspect the sealed decision context and typed model-pass failure before retrying." - .to_string(), - evidence_refs: Vec::new(), - artifact_refs: Vec::new(), - metadata: BTreeMap::new(), - decision_context_id: None, - }; expected.push(model_job_envelope); replacements.push(cache.prepare_entry(&model_job.job_id, &model_job)?.0); - replacements.push( - cache - .prepare_entry(&model_result.result_id, &model_result)? - .0, - ); } if !runtime_spine_backing_store(store_path)?.compare_and_swap_batch(&expected, replacements)? { return terminalize_model_pass_failure_session(store_path, options); @@ -1404,82 +1073,22 @@ pub fn model_pass_failure_for_request( Ok(Some(failure)) } -pub fn create_runtime_job( - store_path: impl AsRef, - options: RuntimeSpineJobOptions, -) -> Result { - validate_non_empty(&options.job_id, "job id")?; - validate_non_empty(&options.session_id, "session id")?; - validate_non_empty(&options.role, "role")?; - validate_non_empty(&options.created_at, "created at")?; - let mut cache = runtime_spine_cache(store_path.as_ref())?; - cache.pull_all_backing_stores()?; - require_identity(&cache)?; - require_runtime_identity_not_archived(&cache, "job", &options.job_id)?; - let session = cache - .get::(&options.session_id)? - .ok_or_else(|| anyhow!("runtime session {:?} does not exist", options.session_id))?; - if matches!( - session.status, - EpiphanyRuntimeSessionStatus::Completed | EpiphanyRuntimeSessionStatus::Archived - ) { - return Err(anyhow!( - "runtime session {:?} is not open for jobs", - options.session_id - )); - } - if cache.get::(&options.job_id)?.is_some() { - return Err(anyhow!("runtime job {:?} already exists", options.job_id)); - } - let job = EpiphanyRuntimeJob { - schema_version: RUNTIME_SPINE_SCHEMA_VERSION.to_string(), - job_id: options.job_id.clone(), - session_id: options.session_id.clone(), - role: options.role, - status: EpiphanyRuntimeJobStatus::Queued, - created_at: options.created_at.clone(), - updated_at: options.created_at.clone(), - summary: options.summary, - artifact_refs: options.artifact_refs, - metadata: BTreeMap::new(), - }; - cache.put(&options.job_id, &job)?; - Ok(job) -} - pub fn open_runtime_model_execution( store_path: impl AsRef, - session_options: RuntimeSpineSessionOptions, job_options: RuntimeSpineJobOptions, model_request: &EpiphanyModelRequest, - bound_at: &str, ) -> Result { - validate_non_empty(&session_options.session_id, "model execution session id")?; - validate_non_empty(&session_options.objective, "model execution objective")?; - validate_non_empty( - &session_options.created_at, - "model execution session creation time", - )?; + validate_non_empty(&job_options.session_id, "model execution session id")?; validate_non_empty(&job_options.job_id, "model execution job id")?; - validate_non_empty(&job_options.role, "model execution job role")?; - validate_non_empty(&job_options.created_at, "model execution job creation time")?; - if job_options.session_id != session_options.session_id { - return Err(anyhow!( - "model execution job and session options disagree on session identity" - )); - } validate_non_empty(&model_request.request_id, "model execution request id")?; validate_non_empty(&model_request.provider, "model execution provider")?; - validate_non_empty(bound_at, "model execution binding time")?; - chrono::DateTime::parse_from_rfc3339(bound_at) - .map_err(|error| anyhow!("model execution binding time is invalid: {error}"))?; - let provider_request = epiphany_openai_adapter::request_from_native(model_request); + let provider_request = epiphany_model_adapter::request_from_native(model_request)?; let store_path = store_path.as_ref(); let mut cache = runtime_spine_cache(store_path)?; cache.pull_all_backing_stores()?; require_identity(&cache)?; - require_runtime_identity_not_archived(&cache, "session", &session_options.session_id)?; + require_runtime_identity_not_archived(&cache, "session", &job_options.session_id)?; require_runtime_identity_not_archived(&cache, "job", &job_options.job_id)?; require_runtime_identity_not_archived(&cache, "model-request", &model_request.request_id)?; let mut reasoning_basis_envelope = None; @@ -1515,14 +1124,10 @@ pub fn open_runtime_model_execution( let worker_job = cache .get::(worker_job_id)? .ok_or_else(|| anyhow!("model execution source worker has no runtime job"))?; - if worker_job.role != launch.role - || matches!( - worker_job.status, - EpiphanyRuntimeJobStatus::Completed - | EpiphanyRuntimeJobStatus::Failed - | EpiphanyRuntimeJobStatus::Cancelled - ) - { + if matches!( + worker_job.status, + EpiphanyRuntimeJobStatus::Completed | EpiphanyRuntimeJobStatus::Failed + ) { return Err(anyhow!( "model execution source worker job is foreign or terminal" )); @@ -1538,23 +1143,14 @@ pub fn open_runtime_model_execution( } else { Vec::new() }; - let existing_session = cache.get::(&session_options.session_id)?; + let existing_session = cache.get::(&job_options.session_id)?; let session = existing_session .clone() .unwrap_or_else(|| EpiphanyRuntimeSession { - schema_version: RUNTIME_SPINE_SCHEMA_VERSION.to_string(), - session_id: session_options.session_id.clone(), - objective: session_options.objective.clone(), + session_id: job_options.session_id.clone(), status: EpiphanyRuntimeSessionStatus::Active, - created_at: session_options.created_at.clone(), - updated_at: session_options.created_at.clone(), - coordinator_note: session_options.coordinator_note.clone(), - metadata: BTreeMap::new(), }); - if matches!( - session.status, - EpiphanyRuntimeSessionStatus::Completed | EpiphanyRuntimeSessionStatus::Archived - ) { + if session.status == EpiphanyRuntimeSessionStatus::Completed { return Err(anyhow!( "model execution session {:?} is terminal", session.session_id @@ -1570,16 +1166,8 @@ pub fn open_runtime_model_execution( )); } let job = EpiphanyRuntimeJob { - schema_version: RUNTIME_SPINE_SCHEMA_VERSION.to_string(), job_id: job_options.job_id.clone(), - session_id: session.session_id.clone(), - role: job_options.role, status: EpiphanyRuntimeJobStatus::Queued, - created_at: job_options.created_at.clone(), - updated_at: job_options.created_at.clone(), - summary: job_options.summary, - artifact_refs: job_options.artifact_refs, - metadata: BTreeMap::new(), }; let binding_id = model_request.request_id.clone(); if cache @@ -1589,7 +1177,7 @@ pub fn open_runtime_model_execution( .get::(&model_request.request_id)? .is_some() || cache - .get::(&provider_request.request_id)? + .get::(provider_request.request_id())? .is_some() { return Err(anyhow!( @@ -1598,15 +1186,9 @@ pub fn open_runtime_model_execution( )); } let binding = EpiphanyRuntimeModelExecutionBinding { - schema_version: RUNTIME_MODEL_EXECUTION_BINDING_SCHEMA_VERSION.to_string(), - binding_id: binding_id.clone(), request_id: model_request.request_id.clone(), session_id: session.session_id.clone(), job_id: job.job_id.clone(), - provider: model_request.provider.clone(), - bound_at: bound_at.to_string(), - source_worker_job_id: model_request.source_worker_job_id.clone(), - reasoning_basis_id: model_request.reasoning_basis_id.clone(), }; let identity_envelope = cache .get_envelope::(RUNTIME_IDENTITY_KEY)? @@ -1632,7 +1214,7 @@ pub fn open_runtime_model_execution( .prepare_entry(&model_request.request_id, model_request)? .0, cache - .prepare_entry(&provider_request.request_id, &provider_request)? + .prepare_entry(provider_request.request_id(), &provider_request)? .0, ]; replacements.extend(source_worker_envelopes); @@ -1652,7 +1234,6 @@ pub fn put_runtime_tool_execution_intent( session_id: &str, job_id: &str, intent: &EpiphanyToolInvocationIntent, - bound_at: &str, ) -> Result { validate_non_empty(session_id, "tool execution session id")?; validate_non_empty(job_id, "tool execution job id")?; @@ -1660,9 +1241,6 @@ pub fn put_runtime_tool_execution_intent( validate_non_empty(&intent.adapter, "tool execution adapter")?; validate_non_empty(&intent.server, "tool execution server")?; validate_non_empty(&intent.tool_name, "tool execution tool name")?; - chrono::DateTime::parse_from_rfc3339(bound_at) - .map_err(|error| anyhow!("tool execution binding time is invalid: {error}"))?; - let store_path = store_path.as_ref(); let mut cache = runtime_spine_cache(store_path)?; cache.pull_all_backing_stores()?; @@ -1671,21 +1249,16 @@ pub fn put_runtime_tool_execution_intent( let session = cache .get::(session_id)? .ok_or_else(|| anyhow!("tool execution session {session_id:?} does not exist"))?; - if matches!( - session.status, - EpiphanyRuntimeSessionStatus::Completed | EpiphanyRuntimeSessionStatus::Archived - ) { + if session.status == EpiphanyRuntimeSessionStatus::Completed { return Err(anyhow!("tool execution session {session_id:?} is terminal")); } let job = cache .get::(job_id)? .ok_or_else(|| anyhow!("tool execution job {job_id:?} does not exist"))?; - if job.session_id != session_id + if !runtime_job_belongs_to_session(&cache, job_id, session_id)? || matches!( job.status, - EpiphanyRuntimeJobStatus::Completed - | EpiphanyRuntimeJobStatus::Failed - | EpiphanyRuntimeJobStatus::Cancelled + EpiphanyRuntimeJobStatus::Completed | EpiphanyRuntimeJobStatus::Failed ) { return Err(anyhow!( @@ -1742,28 +1315,16 @@ pub fn put_runtime_tool_execution_intent( intent.intent_id ) })?; - if model_binding.request_id != model_request_id - || model_binding.session_id != session_id - || model_binding.job_id != job_id - { + if model_binding.session_id != session_id || model_binding.job_id != job_id { return Err(anyhow!( "model-derived tool intent {:?} has foreign execution ownership", intent.intent_id )); } - let persisted_model_request = cache - .get::(model_request_id)? - .ok_or_else(|| anyhow!("model-derived tool intent lost its model request"))?; - if persisted_model_request.source_worker_job_id != model_binding.source_worker_job_id { - return Err(anyhow!( - "model-derived tool intent {:?} has substituted worker provenance", - intent.intent_id - )); - } for envelope in [ cache.get_envelope::(model_request_id)?, cache.get_envelope::(model_request_id)?, - cache.get_envelope::(model_request_id)?, + cache.get_envelope::(model_request_id)?, ] { let envelope = envelope.ok_or_else(|| { anyhow!( @@ -1776,15 +1337,11 @@ pub fn put_runtime_tool_execution_intent( } } let binding = EpiphanyRuntimeToolExecutionBinding { - schema_version: RUNTIME_TOOL_EXECUTION_BINDING_SCHEMA_VERSION.to_string(), - binding_id: intent.intent_id.clone(), intent_id: intent.intent_id.clone(), session_id: session_id.to_string(), job_id: job_id.to_string(), - model_request_id: intent.model_request_id.clone(), - bound_at: bound_at.to_string(), }; - replacements.push(cache.prepare_entry(&binding.binding_id, &binding)?.0); + replacements.push(cache.prepare_entry(&binding.intent_id, &binding)?.0); replacements.push( cache .prepare_entry(&tool_invocation_intent_key(&intent.intent_id), intent)? @@ -1798,6 +1355,31 @@ pub fn put_runtime_tool_execution_intent( Ok(binding) } +fn runtime_job_belongs_to_session( + cache: &CultCache, + job_id: &str, + session_id: &str, +) -> Result { + if cache + .get::(job_id)? + .is_some() + { + return Ok(session_id == EPIPHANY_RUNTIME_ROOT_SESSION_ID); + } + let bindings = cache + .get_all::()? + .into_iter() + .filter(|binding| binding.job_id == job_id) + .collect::>(); + match bindings.as_slice() { + [binding] => Ok(binding.session_id == session_id), + [] => Ok(false), + _ => Err(anyhow!( + "runtime job has multiple model-session authorities" + )), + } +} + pub(crate) fn validate_runtime_model_execution_binding( cache: &CultCache, request_id: &str, @@ -1810,33 +1392,25 @@ pub(crate) fn validate_runtime_model_execution_binding( .get::(request_id)? .ok_or_else(|| anyhow!("model execution binding {request_id:?} lost its native request"))?; let provider = cache - .get::(request_id)? + .get::(request_id)? .ok_or_else(|| { anyhow!("model execution binding {request_id:?} lost its provider request") })?; - let session = cache + cache .get::(&binding.session_id)? .ok_or_else(|| anyhow!("model execution binding {request_id:?} lost its session"))?; - let job = cache + cache .get::(&binding.job_id)? .ok_or_else(|| anyhow!("model execution binding {request_id:?} lost its job"))?; - if binding.schema_version != RUNTIME_MODEL_EXECUTION_BINDING_SCHEMA_VERSION - || binding.binding_id != request_id - || binding.request_id != request_id - || binding.provider != native.provider - || binding.source_worker_job_id != native.source_worker_job_id - || binding.reasoning_basis_id != native.reasoning_basis_id - || chrono::DateTime::parse_from_rfc3339(&binding.bound_at).is_err() - || provider != epiphany_openai_adapter::request_from_native(&native) - || session.status == EpiphanyRuntimeSessionStatus::Archived - || job.session_id != binding.session_id + if binding.request_id != request_id + || provider != epiphany_model_adapter::request_from_native(&native)? { return Err(anyhow!( "model execution binding {request_id:?} is not one exact request family" )); } - if let Some(worker_job_id) = binding.source_worker_job_id.as_deref() { - let basis_id = binding + if let Some(worker_job_id) = native.source_worker_job_id.as_deref() { + let basis_id = native .reasoning_basis_id .as_deref() .ok_or_else(|| anyhow!("decision-bearing model execution lost its reasoning basis"))?; @@ -1846,13 +1420,10 @@ pub(crate) fn validate_runtime_model_execution_binding( let launch = cache .get::(worker_job_id)? .ok_or_else(|| anyhow!("model execution binding lost its source worker launch"))?; - let worker_job = cache + cache .get::(worker_job_id)? .ok_or_else(|| anyhow!("model execution binding lost its source worker job"))?; - if basis.pass_id != worker_job_id - || launch.job_id != worker_job_id - || worker_job.role != launch.role - { + if basis.pass_id != worker_job_id || launch.job_id != worker_job_id { return Err(anyhow!( "model execution binding {request_id:?} has foreign worker authority" )); @@ -1880,11 +1451,7 @@ pub(crate) fn validate_runtime_tool_execution_binding( let binding = cache .get::(intent_id)? .ok_or_else(|| anyhow!("tool execution intent {intent_id:?} is unbound"))?; - if binding.schema_version != RUNTIME_TOOL_EXECUTION_BINDING_SCHEMA_VERSION - || binding.binding_id != intent_id - || binding.intent_id != intent_id - || chrono::DateTime::parse_from_rfc3339(&binding.bound_at).is_err() - { + if binding.intent_id != intent_id { return Err(anyhow!( "tool execution intent {intent_id:?} has an invalid binding" )); @@ -1892,32 +1459,26 @@ pub(crate) fn validate_runtime_tool_execution_binding( let intent = cache .get::(&tool_invocation_intent_key(intent_id))? .ok_or_else(|| anyhow!("tool execution binding {intent_id:?} lost its intent"))?; - if intent.intent_id != intent_id || intent.model_request_id != binding.model_request_id { + if intent.intent_id != intent_id { return Err(anyhow!( "tool execution binding {intent_id:?} disagrees with its intent" )); } - let session = cache + cache .get::(&binding.session_id)? .ok_or_else(|| anyhow!("tool execution binding {intent_id:?} lost its session"))?; let job = cache .get::(&binding.job_id)? .ok_or_else(|| anyhow!("tool execution binding {intent_id:?} lost its job"))?; - if session.status == EpiphanyRuntimeSessionStatus::Archived - || job.session_id != binding.session_id - { + if !runtime_job_belongs_to_session(cache, &job.job_id, &binding.session_id)? { return Err(anyhow!( "tool execution binding {intent_id:?} has foreign or archived ownership" )); } let _ = validate_governed_source_tool_intent(&cache, &binding.job_id, &intent)?; - if let Some(model_request_id) = binding.model_request_id.as_deref() { - let model_binding = cache - .get::(model_request_id)? - .ok_or_else(|| anyhow!("tool execution binding lost its model execution"))?; - if model_binding.session_id != binding.session_id - || model_binding.job_id != binding.job_id - || model_binding.request_id != model_request_id + if let Some(model_request_id) = intent.model_request_id.as_deref() { + let model_binding = validate_runtime_model_execution_binding(cache, model_request_id)?; + if model_binding.session_id != binding.session_id || model_binding.job_id != binding.job_id { return Err(anyhow!( "tool execution binding {intent_id:?} has foreign model ownership" @@ -1957,7 +1518,7 @@ pub fn put_runtime_tool_execution_receipt( )); } let binding_envelope = cache - .get_envelope::(&binding.binding_id)? + .get_envelope::(&binding.intent_id)? .ok_or_else(|| anyhow!("tool execution receipt lost its binding envelope"))?; let intent_envelope = cache .get_envelope::(&tool_invocation_intent_key( @@ -1984,10 +1545,7 @@ pub(crate) fn validate_terminal_tool_execution_family( intent: &EpiphanyToolInvocationIntent, receipt: &EpiphanyToolInvocationReceipt, ) -> Result<()> { - if binding.schema_version != RUNTIME_TOOL_EXECUTION_BINDING_SCHEMA_VERSION - || binding.binding_id != intent.intent_id - || binding.intent_id != intent.intent_id - || binding.model_request_id != intent.model_request_id + if binding.intent_id != intent.intent_id || intent.schema_id != TOOL_ADAPTER_INVOCATION_INTENT_SCHEMA_ID || receipt.schema_id != TOOL_ADAPTER_INVOCATION_RECEIPT_SCHEMA_ID || receipt.receipt_id.is_empty() @@ -2021,23 +1579,18 @@ pub fn retain_completed_runtime_sessions( .into_iter() .filter(|session| session.status == EpiphanyRuntimeSessionStatus::Completed) .filter(|session| { - let session_jobs = jobs + let session_job_ids = bindings .iter() - .filter(|job| job.session_id == session.session_id) - .collect::>(); - !session_jobs.is_empty() - && session_jobs.iter().all(|job| { - job.role == "openai-model-adapter" - && bindings.iter().any(|binding| binding.job_id == job.job_id) - }) + .filter(|binding| binding.session_id == session.session_id) + .map(|binding| binding.job_id.as_str()) + .collect::>(); + !session_job_ids.is_empty() + && session_job_ids + .iter() + .all(|job_id| jobs.iter().any(|job| job.job_id == **job_id)) }) .collect::>(); - candidates.sort_by(|left, right| { - right - .updated_at - .cmp(&left.updated_at) - .then_with(|| right.session_id.cmp(&left.session_id)) - }); + candidates.sort_by(|left, right| right.session_id.cmp(&left.session_id)); for session in candidates.into_iter().skip(retain_recent.max(1)) { archive_completed_model_session(store_path, &session.session_id)?; @@ -2055,8 +1608,7 @@ fn archive_completed_model_session( cache.pull_all_backing_stores()?; require_identity(&cache)?; if let Some(existing) = cache.get::(session_id)? { - if existing.schema_version != ARCHIVED_RUNTIME_SESSION_SCHEMA_VERSION - || existing.session_id != session_id + if existing.session_id != session_id || !existing.retired_chain_digest.starts_with("sha256:") { return Err(anyhow!("archived runtime session tombstone is invalid")); @@ -2074,22 +1626,26 @@ fn archive_completed_model_session( if session.status != EpiphanyRuntimeSessionStatus::Completed { return Err(anyhow!("runtime session {session_id:?} is not completed")); } + let mut model_bindings = cache + .get_all::()? + .into_iter() + .filter(|binding| binding.session_id == session_id) + .collect::>(); + model_bindings.sort_by(|left, right| left.request_id.cmp(&right.request_id)); + let session_job_ids = model_bindings + .iter() + .map(|binding| binding.job_id.as_str()) + .collect::>(); let mut jobs = cache .get_all::()? .into_iter() - .filter(|job| job.session_id == session_id) + .filter(|job| session_job_ids.contains(job.job_id.as_str())) .collect::>(); jobs.sort_by(|left, right| left.job_id.cmp(&right.job_id)); if jobs.is_empty() - || jobs.iter().any(|job| { - job.role != "openai-model-adapter" - || matches!( - job.status, - EpiphanyRuntimeJobStatus::Queued - | EpiphanyRuntimeJobStatus::Running - | EpiphanyRuntimeJobStatus::WaitingForReview - ) - }) + || jobs + .iter() + .any(|job| matches!(job.status, EpiphanyRuntimeJobStatus::Queued)) { return Err(anyhow!( "runtime session archive accepts only terminal model-adapter jobs" @@ -2099,26 +1655,6 @@ fn archive_completed_model_session( .iter() .map(|job| job.job_id.clone()) .collect::>(); - let mut job_results = cache - .get_all::()? - .into_iter() - .filter(|result| result.session_id == session_id) - .collect::>(); - job_results.sort_by(|left, right| left.result_id.cmp(&right.result_id)); - if jobs.iter().any(|job| { - job_results - .iter() - .filter(|result| result.job_id == job.job_id) - .count() - != 1 - }) || job_results - .iter() - .any(|result| !job_ids.contains(&result.job_id)) - { - return Err(anyhow!( - "runtime session archive requires exactly one terminal result per job" - )); - } if cache .get_all::()? .iter() @@ -2132,21 +1668,17 @@ fn archive_completed_model_session( .iter() .any(|item| job_ids.contains(&item.job_id)) || cache - .get_all::()? - .iter() - .any(|item| item.session_id == session_id) + .get::(session_id)? + .is_some() + || cache + .get::(session_id)? + .is_some() { return Err(anyhow!( "runtime session archive refuses outer-worker or coordinator authority" )); } - let mut model_bindings = cache - .get_all::()? - .into_iter() - .filter(|binding| binding.session_id == session_id) - .collect::>(); - model_bindings.sort_by(|left, right| left.request_id.cmp(&right.request_id)); if jobs.iter().any(|job| { model_bindings .iter() @@ -2165,9 +1697,17 @@ fn archive_completed_model_session( .iter() .map(|binding| binding.request_id.clone()) .collect::>(); - let reasoning_basis_ids = model_bindings + let native_requests = model_request_ids .iter() - .filter_map(|binding| binding.reasoning_basis_id.clone()) + .map(|request_id| { + cache + .get::(request_id)? + .ok_or_else(|| anyhow!("archived model execution lost native request")) + }) + .collect::>>()?; + let reasoning_basis_ids = native_requests + .iter() + .filter_map(|request| request.reasoning_basis_id.clone()) .collect::>(); for basis_id in &reasoning_basis_ids { cache @@ -2186,25 +1726,22 @@ fn archive_completed_model_session( decision_context_ids.insert(context.context_id); } } - if model_bindings + if native_requests .iter() - .any(|binding| binding.source_worker_job_id.is_some()) + .any(|request| request.source_worker_job_id.is_some()) && decision_context_ids.is_empty() { return Err(anyhow!( "decision-bearing model session archive requires its terminal context" )); } - for request_id in &model_request_ids { - let native_request = cache - .get::(request_id)? - .ok_or_else(|| anyhow!("archived model execution lost native request"))?; + for native_request in &native_requests { + let request_id = &native_request.request_id; let provider_request = cache - .get::(request_id)? + .get::(request_id)? .ok_or_else(|| anyhow!("archived model execution lost provider request"))?; - if native_request.request_id != *request_id - || provider_request.request_id != *request_id - || provider_request != epiphany_openai_adapter::request_from_native(&native_request) + if provider_request.request_id() != request_id + || provider_request != epiphany_model_adapter::request_from_native(native_request)? { return Err(anyhow!( "archived model execution request family is inconsistent" @@ -2226,36 +1763,18 @@ fn archive_completed_model_session( ) }) .collect::>(); - let mut provider_events = cache - .get_all::()? - .into_iter() - .filter(|event| event.request_id == *request_id) - .collect::>(); - provider_events.sort_by_key(|event| event.sequence); - let provider_terminals = provider_events - .iter() - .filter(|event| { - matches!( - &event.payload, - EpiphanyOpenAiStreamPayload::Completed { .. } - | EpiphanyOpenAiStreamPayload::Failed { .. } - ) - }) - .collect::>(); if native_terminals.len() != 1 - || provider_terminals.len() != 1 || native_events.last().map(|event| event.sequence) != native_terminals.first().map(|event| event.sequence) - || provider_events.last().map(|event| event.sequence) - != provider_terminals.first().map(|event| event.sequence) { return Err(anyhow!( - "runtime session archive requires one native and provider terminal stream event" + "runtime session archive requires one terminal model stream event" )); } match &native_terminals[0].payload { EpiphanyModelStreamPayload::Completed { receipt } => { - if cache.get::(request_id)?.as_ref() != Some(receipt) { + if cache.get::(request_id)?.as_ref() != Some(receipt.as_ref()) + { return Err(anyhow!( "runtime session archive found inconsistent native model receipt" )); @@ -2270,30 +1789,6 @@ fn archive_completed_model_session( } _ => unreachable!("terminal event filtered above"), } - match &provider_terminals[0].payload { - EpiphanyOpenAiStreamPayload::Completed { receipt } => { - if cache - .get::(request_id)? - .as_ref() - != Some(receipt) - { - return Err(anyhow!( - "runtime session archive found inconsistent provider model receipt" - )); - } - } - EpiphanyOpenAiStreamPayload::Failed { .. } => { - if cache - .get::(request_id)? - .is_some() - { - return Err(anyhow!( - "failed provider model stream retained a success receipt" - )); - } - } - _ => unreachable!("terminal event filtered above"), - } } let mut tool_bindings = cache @@ -2302,13 +1797,10 @@ fn archive_completed_model_session( .filter(|binding| binding.session_id == session_id) .collect::>(); tool_bindings.sort_by(|left, right| left.intent_id.cmp(&right.intent_id)); - if tool_bindings.iter().any(|binding| { - !job_ids.contains(&binding.job_id) - || binding - .model_request_id - .as_ref() - .is_some_and(|request_id| !model_request_ids.contains(request_id)) - }) { + if tool_bindings + .iter() + .any(|binding| !job_ids.contains(&binding.job_id)) + { return Err(anyhow!( "runtime session archive found foreign tool execution ownership" )); @@ -2333,6 +1825,18 @@ fn archive_completed_model_session( )); } for binding in &tool_bindings { + let intent = cache + .get::(&tool_invocation_intent_key(&binding.intent_id))? + .ok_or_else(|| anyhow!("runtime session archive lost a tool intent"))?; + if intent + .model_request_id + .as_ref() + .is_some_and(|request_id| !model_request_ids.contains(request_id)) + { + return Err(anyhow!( + "runtime session archive found foreign tool execution ownership" + )); + } require_runtime_tool_execution_binding(store_path, &binding.intent_id)?; let receipt = cache .get::(&tool_invocation_receipt_key(&binding.intent_id))? @@ -2355,23 +1859,17 @@ fn archive_completed_model_session( for job in &jobs { retired_identities.insert((EpiphanyRuntimeJob::TYPE.to_string(), job.job_id.clone())); } - for result in &job_results { - retired_identities.insert(( - EpiphanyRuntimeJobResult::TYPE.to_string(), - result.result_id.clone(), - )); - } for binding in &model_bindings { retired_identities.insert(( EpiphanyRuntimeModelExecutionBinding::TYPE.to_string(), - binding.binding_id.clone(), + binding.request_id.clone(), )); retired_identities.insert(( EpiphanyModelRequest::TYPE.to_string(), binding.request_id.clone(), )); retired_identities.insert(( - EpiphanyOpenAiModelRequest::TYPE.to_string(), + EpiphanyProviderRequest::TYPE.to_string(), binding.request_id.clone(), )); for event in cache @@ -2384,16 +1882,6 @@ fn archive_completed_model_session( format!("{}:{:08}", event.request_id, event.sequence), )); } - for event in cache - .get_all::()? - .into_iter() - .filter(|event| event.request_id == binding.request_id) - { - retired_identities.insert(( - EpiphanyOpenAiStreamEvent::TYPE.to_string(), - format!("{}:{:08}", event.request_id, event.sequence), - )); - } if cache .get::(&binding.request_id)? .is_some() @@ -2403,20 +1891,11 @@ fn archive_completed_model_session( binding.request_id.clone(), )); } - if cache - .get::(&binding.request_id)? - .is_some() - { - retired_identities.insert(( - EpiphanyOpenAiModelReceipt::TYPE.to_string(), - binding.request_id.clone(), - )); - } } for binding in &tool_bindings { retired_identities.insert(( EpiphanyRuntimeToolExecutionBinding::TYPE.to_string(), - binding.binding_id.clone(), + binding.intent_id.clone(), )); retired_identities.insert(( EpiphanyToolInvocationIntent::TYPE.to_string(), @@ -2457,7 +1936,6 @@ fn archive_completed_model_session( } } let archive = EpiphanyArchivedRuntimeSession { - schema_version: ARCHIVED_RUNTIME_SESSION_SCHEMA_VERSION.to_string(), session_id: session_id.to_string(), job_ids: job_ids.into_iter().collect(), model_request_ids: model_request_ids.into_iter().collect(), @@ -2482,44 +1960,54 @@ pub fn prepare_runtime_spine_heartbeat_job( options: RuntimeSpineHeartbeatJobOptions, ) -> Result> { validate_non_empty(&options.runtime_id, "runtime id")?; - validate_non_empty(&options.session_id, "session id")?; - validate_non_empty(&options.objective, "objective")?; validate_non_empty(&options.job_id, "job id")?; validate_non_empty(&options.role, "role")?; validate_repository_body_launch_carrier(&options.role, &options.launch_document)?; validate_proposal_modeling_launch_carrier( &options.role, &options.binding_id, - options.proposal_modeling_request_id.as_deref(), &options.launch_document, )?; validate_frontier_planning_launch_carrier( &options.role, &options.binding_id, - options.frontier_planning_request_id.as_deref(), + &options.launch_document, + )?; + validate_frontier_plan_mind_launch_carrier( + &options.role, + &options.binding_id, &options.launch_document, )?; validate_frontier_research_launch_carrier( &options.role, &options.binding_id, - options.repo_frontier_research_request_id.as_deref(), &options.launch_document, )?; validate_frontier_verification_launch_carrier( &options.role, &options.binding_id, - options.repo_frontier_verification_request_id.as_deref(), &options.launch_document, )?; validate_imagination_consideration_launch_carrier( &options.role, &options.binding_id, - options.imagination_consideration_request_id.as_deref(), &options.launch_document, )?; + validate_admitted_model_direction_launch_carrier( + &options.role, + &options.binding_id, + &options.launch_document, + )?; + if matches!( + &options.launch_document, + EpiphanyWorkerLaunchDocument::Role(document) if document.family_context_count() > 1 + ) { + return Err(anyhow!( + "worker launch carries multiple family request authorities" + )); + } validate_repo_frontier_verdict_modeling_launch_authority( &options.role, - options.repo_frontier_modeling_request_id.as_deref(), &options.launch_document, )?; validate_non_empty(&options.binding_id, "binding id")?; @@ -2548,28 +2036,17 @@ pub fn prepare_runtime_spine_heartbeat_job( "runtime job preparation cannot substitute runtime identity" )); } - let session = match cache.get::(&options.session_id)? { - Some(existing) - if matches!( - existing.status, - EpiphanyRuntimeSessionStatus::Completed | EpiphanyRuntimeSessionStatus::Archived - ) => - { + let session = match cache.get::(EPIPHANY_RUNTIME_ROOT_SESSION_ID)? { + Some(existing) if existing.status == EpiphanyRuntimeSessionStatus::Completed => { return Err(anyhow!( "runtime session {:?} is terminal and cannot accept jobs", - options.session_id + EPIPHANY_RUNTIME_ROOT_SESSION_ID )); } Some(existing) => existing, None => EpiphanyRuntimeSession { - schema_version: RUNTIME_SPINE_SCHEMA_VERSION.to_string(), - session_id: options.session_id.clone(), - objective: options.objective, + session_id: EPIPHANY_RUNTIME_ROOT_SESSION_ID.into(), status: EpiphanyRuntimeSessionStatus::Active, - created_at: options.created_at.clone(), - updated_at: options.created_at.clone(), - coordinator_note: options.coordinator_note, - metadata: BTreeMap::new(), }, }; if cache.get::(&options.job_id)?.is_some() { @@ -2585,22 +2062,10 @@ pub fn prepare_runtime_spine_heartbeat_job( )); } let job = EpiphanyRuntimeJob { - schema_version: RUNTIME_SPINE_SCHEMA_VERSION.to_string(), job_id: options.job_id.clone(), - session_id: options.session_id.clone(), - role: options.role.clone(), status: EpiphanyRuntimeJobStatus::Queued, - created_at: options.created_at.clone(), - updated_at: options.created_at.clone(), - summary: format!( - "Heartbeat activation queued for binding {} with authority {}.", - options.binding_id, options.authority_scope - ), - artifact_refs: Vec::new(), - metadata: BTreeMap::new(), }; let request = EpiphanyRuntimeWorkerLaunchRequest { - schema_version: RUNTIME_WORKER_LAUNCH_REQUEST_SCHEMA_VERSION.to_string(), job_id: options.job_id.clone(), binding_id: options.binding_id, role: options.role, @@ -2610,15 +2075,6 @@ pub fn prepare_runtime_spine_heartbeat_job( document_kind: worker_launch_document_kind(&options.launch_document).to_string(), launch_document_msgpack: encode_worker_launch_document(&options.launch_document)?, metadata: BTreeMap::new(), - proposal_modeling_request_id: options.proposal_modeling_request_id, - frontier_planning_request_id: options.frontier_planning_request_id, - frontier_plan_mind_request_id: options.frontier_plan_mind_request_id, - imagination_consideration_request_id: options.imagination_consideration_request_id, - admitted_model_direction_consideration_request_id: options - .admitted_model_direction_consideration_request_id, - repo_frontier_modeling_request_id: options.repo_frontier_modeling_request_id, - repo_frontier_research_request_id: options.repo_frontier_research_request_id, - repo_frontier_verification_request_id: options.repo_frontier_verification_request_id, }; let session_envelope = match cache.get_envelope::(&session.session_id)? { @@ -2636,7 +2092,6 @@ pub fn prepare_runtime_spine_heartbeat_job( fn validate_repo_frontier_verdict_modeling_launch_authority( role: &str, - request_id: Option<&str>, launch_document: &EpiphanyWorkerLaunchDocument, ) -> Result<()> { let authority = match launch_document { @@ -2645,25 +2100,18 @@ fn validate_repo_frontier_verdict_modeling_launch_authority( } EpiphanyWorkerLaunchDocument::Reorient(_) => None, }; - match (request_id, authority) { - (None, None) => Ok(()), - (Some(_), None) => Err(anyhow!( - "verdict-bound Modeling launch omitted its typed authority body" - )), - (None, Some(_)) => Err(anyhow!( - "verdict-bound Modeling authority body has no indexed request id" - )), - (Some(request_id), Some(authority)) => { + match authority { + None => Ok(()), + Some(authority) => { if role != EPIPHANY_MODELING_OWNER_ROLE { return Err(anyhow!( "verdict-bound Modeling authority belongs only to the Modeling owner role" )); } - if request_id != authority.request.request_id { - return Err(anyhow!( - "verdict-bound Modeling request id does not match its typed authority body" - )); - } + validate_non_empty( + &authority.request.request_id, + "verdict-bound Modeling request id", + )?; if authority.request.frontier_item_id != authority.frontier_item.id { return Err(anyhow!( "verdict-bound Modeling authority does not carry its exact frontier item" @@ -2754,33 +2202,21 @@ fn validate_repo_frontier_verdict_modeling_mutation( fn validate_proposal_modeling_launch_carrier( role: &str, binding_id: &str, - proposal_modeling_request_id: Option<&str>, launch_document: &EpiphanyWorkerLaunchDocument, ) -> Result<()> { let projection = match launch_document { EpiphanyWorkerLaunchDocument::Role(document) => document.proposal_modeling_context.as_ref(), EpiphanyWorkerLaunchDocument::Reorient(_) => None, }; - let Some(request_id) = proposal_modeling_request_id else { - if projection.is_some() { - return Err(anyhow!( - "proposal Modeling context requires its typed request id" - )); - } + let Some(projection) = projection else { return Ok(()); }; - validate_non_empty(request_id, "proposal Modeling request id")?; + validate_non_empty(&projection.request_id, "proposal Modeling request id")?; if role != EPIPHANY_MODELING_OWNER_ROLE || binding_id != EPIPHANY_MODELING_ROLE_BINDING_ID { return Err(anyhow!( "proposal Modeling request id may only be transported by the Modeling role launch" )); } - let projection = projection.ok_or_else(|| { - anyhow!("proposal Modeling request id requires coordinator-owned typed context") - })?; - if projection.request_id != request_id { - return Err(anyhow!("proposal Modeling context/request mismatch")); - } Ok(()) } @@ -2818,32 +2254,47 @@ fn validate_repository_body_launch_carrier( fn validate_frontier_planning_launch_carrier( role: &str, binding_id: &str, - planning_request_id: Option<&str>, launch_document: &EpiphanyWorkerLaunchDocument, ) -> Result<()> { let projection = match launch_document { EpiphanyWorkerLaunchDocument::Role(document) => document.frontier_planning_context.as_ref(), EpiphanyWorkerLaunchDocument::Reorient(_) => None, }; - let Some(request_id) = planning_request_id else { - if projection.is_some() { - return Err(anyhow!( - "frontier planning context requires its typed request id" - )); - } + let Some(projection) = projection else { return Ok(()); }; - validate_non_empty(request_id, "frontier planning request id")?; + validate_non_empty(&projection.request_id, "frontier planning request id")?; if role != EPIPHANY_IMAGINATION_OWNER_ROLE || binding_id != EPIPHANY_IMAGINATION_ROLE_BINDING_ID { return Err(anyhow!( "frontier planning request may only be transported by the Imagination role launch" )); } - let projection = projection - .ok_or_else(|| anyhow!("frontier planning request requires its typed context"))?; - if projection.request_id != request_id { - return Err(anyhow!("frontier planning context/request mismatch")); + Ok(()) +} + +fn validate_frontier_plan_mind_launch_carrier( + role: &str, + binding_id: &str, + launch_document: &EpiphanyWorkerLaunchDocument, +) -> Result<()> { + let projection = match launch_document { + EpiphanyWorkerLaunchDocument::Role(document) => { + document.frontier_plan_mind_context.as_ref() + } + EpiphanyWorkerLaunchDocument::Reorient(_) => None, + }; + let Some(projection) = projection else { + return Ok(()); + }; + validate_non_empty( + &projection.request.request_id, + "frontier plan Mind request id", + )?; + if role != EPIPHANY_MIND_OWNER_ROLE || binding_id != EPIPHANY_MIND_ROLE_BINDING_ID { + return Err(anyhow!( + "frontier plan Mind request may only be transported by the Mind role launch" + )); } Ok(()) } @@ -2851,7 +2302,6 @@ fn validate_frontier_planning_launch_carrier( fn validate_imagination_consideration_launch_carrier( role: &str, binding_id: &str, - request_id: Option<&str>, launch_document: &EpiphanyWorkerLaunchDocument, ) -> Result<()> { let projection = match launch_document { @@ -2860,31 +2310,58 @@ fn validate_imagination_consideration_launch_carrier( } EpiphanyWorkerLaunchDocument::Reorient(_) => None, }; - let Some(request_id) = request_id else { - if projection.is_some() { - return Err(anyhow!( - "consideration context requires its typed request id" - )); - } + let Some(projection) = projection else { return Ok(()); }; - validate_non_empty(request_id, "imagination consideration request id")?; + validate_non_empty( + &projection.request.request_id, + "imagination consideration request id", + )?; if role != EPIPHANY_IMAGINATION_OWNER_ROLE || binding_id != EPIPHANY_IMAGINATION_ROLE_BINDING_ID { return Err(anyhow!( "consideration may only be transported by Imagination" )); } - if projection.map(|p| p.request.request_id.as_str()) != Some(request_id) { - return Err(anyhow!("consideration context/request mismatch")); + Ok(()) +} + +fn validate_admitted_model_direction_launch_carrier( + role: &str, + binding_id: &str, + launch_document: &EpiphanyWorkerLaunchDocument, +) -> Result<()> { + let projection = match launch_document { + EpiphanyWorkerLaunchDocument::Role(document) => document + .admitted_model_direction_consideration_context + .as_ref(), + EpiphanyWorkerLaunchDocument::Reorient(_) => None, + }; + let Some(projection) = projection else { + return Ok(()); + }; + validate_non_empty( + &projection.request.request_id, + "admitted model direction consideration request id", + )?; + if role != EPIPHANY_IMAGINATION_OWNER_ROLE + || binding_id != EPIPHANY_IMAGINATION_ROLE_BINDING_ID + || projection.schema_version + != crate::surfaces::ADMITTED_MODEL_DIRECTION_CONSIDERATION_CONTEXT_SCHEMA_VERSION + || projection.contract + != crate::surfaces::ADMITTED_MODEL_DIRECTION_CONSIDERATION_CONTEXT_CONTRACT + { + return Err(anyhow!( + "admitted model direction consideration belongs only to its typed Imagination launch" + )); } Ok(()) } -pub fn runtime_job_snapshot( +pub fn runtime_job( store_path: impl AsRef, job_id: &str, -) -> Result> { +) -> Result> { validate_non_empty(job_id, "job id")?; let store_path = store_path.as_ref(); if !store_path.exists() { @@ -2892,19 +2369,7 @@ pub fn runtime_job_snapshot( } let mut cache = runtime_spine_cache(store_path)?; cache.pull_all_backing_stores()?; - let Some(job) = cache.get::(job_id)? else { - return Ok(None); - }; - let result = cache - .get_all::()? - .into_iter() - .filter(|result| result.job_id == job_id) - .max_by(|left, right| { - left.completed_at - .cmp(&right.completed_at) - .then_with(|| left.result_id.cmp(&right.result_id)) - }); - Ok(Some(EpiphanyRuntimeJobSnapshot { job, result })) + cache.get::(job_id) } pub fn runtime_worker_launch_request( @@ -2934,22 +2399,16 @@ pub fn runtime_worker_process_claim( fn validate_frontier_research_launch_carrier( role: &str, binding_id: &str, - research_request_id: Option<&str>, launch_document: &EpiphanyWorkerLaunchDocument, ) -> Result<()> { let projection = match launch_document { EpiphanyWorkerLaunchDocument::Role(document) => document.frontier_research_context.as_ref(), EpiphanyWorkerLaunchDocument::Reorient(_) => None, }; - let Some(request_id) = research_request_id else { - if projection.is_some() { - return Err(anyhow!( - "frontier Research context requires its typed request id" - )); - } + let Some(projection) = projection else { return Ok(()); }; - validate_non_empty(request_id, "frontier Research request id")?; + validate_non_empty(&projection.request_id, "frontier Research request id")?; if role != crate::EPIPHANY_RESEARCH_OWNER_ROLE || binding_id != crate::EPIPHANY_RESEARCH_ROLE_BINDING_ID { @@ -2957,10 +2416,7 @@ fn validate_frontier_research_launch_carrier( "frontier Research request may only be transported by the Research role launch" )); } - let projection = projection - .ok_or_else(|| anyhow!("frontier Research request requires its typed context"))?; - if projection.request_id != request_id - || projection.schema_version != crate::REPO_FRONTIER_RESEARCH_CONTEXT_SCHEMA_VERSION + if projection.schema_version != crate::REPO_FRONTIER_RESEARCH_CONTEXT_SCHEMA_VERSION || projection.contract != crate::REPO_FRONTIER_RESEARCH_CONTEXT_CONTRACT { return Err(anyhow!("frontier Research context/request mismatch")); @@ -2971,7 +2427,6 @@ fn validate_frontier_research_launch_carrier( fn validate_frontier_verification_launch_carrier( role: &str, binding_id: &str, - verification_request_id: Option<&str>, launch_document: &EpiphanyWorkerLaunchDocument, ) -> Result<()> { let projection = match launch_document { @@ -2980,15 +2435,13 @@ fn validate_frontier_verification_launch_carrier( } EpiphanyWorkerLaunchDocument::Reorient(_) => None, }; - let Some(request_id) = verification_request_id else { - if projection.is_some() { - return Err(anyhow!( - "frontier Verification context requires its typed request id" - )); - } + let Some(projection) = projection else { return Ok(()); }; - validate_non_empty(request_id, "frontier Verification request id")?; + validate_non_empty( + &projection.request.request_id, + "frontier Verification request id", + )?; if role != crate::EPIPHANY_VERIFICATION_OWNER_ROLE || binding_id != crate::EPIPHANY_VERIFICATION_ROLE_BINDING_ID { @@ -2996,10 +2449,7 @@ fn validate_frontier_verification_launch_carrier( "frontier Verification request may only be transported by the Verification role launch" )); } - let projection = projection - .ok_or_else(|| anyhow!("frontier Verification request requires its typed context"))?; - if projection.request.request_id != request_id - || projection.schema_version != crate::REPO_FRONTIER_VERIFICATION_CONTEXT_SCHEMA_VERSION + if projection.schema_version != crate::REPO_FRONTIER_VERIFICATION_CONTEXT_SCHEMA_VERSION || projection.contract != crate::REPO_FRONTIER_VERIFICATION_CONTEXT_CONTRACT { return Err(anyhow!("frontier Verification context/request mismatch")); @@ -3023,7 +2473,7 @@ pub fn put_runtime_requested_public_source_intents( .ok_or_else(|| anyhow!("requested public source worker has no launch request"))?; let request = frontier_research_request_for_launch(&cache, &launch)? .ok_or_else(|| anyhow!("requested public source worker has no typed Research request"))?; - let job = cache + cache .get::(worker_job_id)? .ok_or_else(|| anyhow!("requested public source worker has no runtime job"))?; drop(cache); @@ -3061,8 +2511,7 @@ pub fn put_runtime_requested_public_source_intents( || chrono::DateTime::parse_from_rfc3339(&existing_intent.created_at).is_err() || existing_binding.intent_id != intent_id || existing_binding.job_id != worker_job_id - || existing_binding.session_id != job.session_id - || existing_binding.model_request_id.is_some() + || existing_binding.session_id != EPIPHANY_RUNTIME_ROOT_SESSION_ID { return Err(anyhow!( "requested public source intent {intent_id:?} collides with foreign authority" @@ -3082,10 +2531,9 @@ pub fn put_runtime_requested_public_source_intents( drop(cache); put_runtime_tool_execution_intent( store_path, - &job.session_id, + EPIPHANY_RUNTIME_ROOT_SESSION_ID, worker_job_id, &intent, - created_at, )?; intents.push(intent); } @@ -3113,15 +2561,15 @@ pub(crate) fn frontier_research_request_for_launch( ) -> Result> { let document: EpiphanyWorkerLaunchDocument = rmp_serde::from_slice(&launch.launch_document_msgpack)?; - validate_frontier_research_launch_carrier( - &launch.role, - &launch.binding_id, - launch.repo_frontier_research_request_id.as_deref(), - &document, - )?; - let Some(request_id) = launch.repo_frontier_research_request_id.as_deref() else { + validate_frontier_research_launch_carrier(&launch.role, &launch.binding_id, &document)?; + let carried = match &document { + EpiphanyWorkerLaunchDocument::Role(document) => document.frontier_research_context.as_ref(), + EpiphanyWorkerLaunchDocument::Reorient(_) => None, + }; + let Some(carried) = carried else { return Ok(None); }; + let request_id = carried.request_id.as_str(); let request = cache .get::(request_id)? .ok_or_else(|| anyhow!("frontier Research launch lost its typed request"))?; @@ -3132,15 +2580,7 @@ pub(crate) fn frontier_research_request_for_launch( "frontier Research launch names an invalid typed request" )); } - let carried = match document { - EpiphanyWorkerLaunchDocument::Role(document) => document.frontier_research_context, - EpiphanyWorkerLaunchDocument::Reorient(_) => None, - }; - if carried.as_ref() - != Some(&crate::RepoFrontierResearchContextProjection::from( - &request, - )) - { + if carried != &crate::RepoFrontierResearchContextProjection::from(&request) { return Err(anyhow!( "frontier Research launch carries substituted context" )); @@ -3154,31 +2594,26 @@ pub(crate) fn frontier_verification_request_for_launch( ) -> Result> { let document: EpiphanyWorkerLaunchDocument = rmp_serde::from_slice(&launch.launch_document_msgpack)?; - validate_frontier_verification_launch_carrier( - &launch.role, - &launch.binding_id, - launch.repo_frontier_verification_request_id.as_deref(), - &document, - )?; - let Some(request_id) = launch.repo_frontier_verification_request_id.as_deref() else { + validate_frontier_verification_launch_carrier(&launch.role, &launch.binding_id, &document)?; + let carried = match &document { + EpiphanyWorkerLaunchDocument::Role(document) => { + document.frontier_verification_context.as_ref() + } + EpiphanyWorkerLaunchDocument::Reorient(_) => None, + }; + let Some(carried) = carried else { return Ok(None); }; + let request_id = carried.request.request_id.as_str(); let request = cache .get::(request_id)? .ok_or_else(|| anyhow!("frontier Verification launch lost its typed request"))?; validate_repo_frontier_verification_request_intrinsic(&request)?; - let carried = match document { - EpiphanyWorkerLaunchDocument::Role(document) => document.frontier_verification_context, - EpiphanyWorkerLaunchDocument::Reorient(_) => None, - } - .ok_or_else(|| anyhow!("frontier Verification launch lost its typed context"))?; if carried.request != request || carried.route.route_id != request.route_id || carried.hands_authority.route_id != request.route_id || carried.hands_authority.hands_intent_id != request.hands_intent_id - || carried.hands_authority.hands_review_id != request.hands_review_id || carried.hands_intent.intent_id != request.hands_intent_id - || carried.hands_review.review_id != request.hands_review_id || carried.patch_receipt.receipt_id != request.hands_patch_receipt_id || carried.command_receipt.receipt_id != request.hands_command_receipt_id || carried.commit_receipt.receipt_id != request.hands_commit_receipt_id @@ -3190,21 +2625,16 @@ pub(crate) fn frontier_verification_request_for_launch( Ok(Some(request)) } -pub(crate) fn runtime_authenticated_public_source_lookups_for_worker( - store_path: impl AsRef, - worker_job_id: &str, -) -> Result> { - validate_non_empty(worker_job_id, "public source worker job id")?; - let mut cache = runtime_spine_cache(store_path)?; - cache.pull_all_backing_stores()?; - require_identity(&cache)?; - authenticated_requested_public_source_lookups_for_worker(&cache, worker_job_id) +#[derive(Clone, Debug, PartialEq, Eq)] +pub(crate) struct AuthenticatedPublicSourceLookup { + pub receipt_id: String, + pub source_ref: String, } fn authenticated_public_source_lookup_receipts_for_worker( cache: &CultCache, worker_job_id: &str, -) -> Result> { +) -> Result> { validate_non_empty(worker_job_id, "public source worker job id")?; require_identity(cache)?; let launch = cache @@ -3215,26 +2645,25 @@ fn authenticated_public_source_lookup_receipts_for_worker( { return Err(anyhow!("public source evidence is Eyes-owned")); } - let grant_id = format!("substrate-grant-{worker_job_id}"); let model_request_ids = cache - .get_all::()? + .get_all::()? .into_iter() - .filter(|binding| binding.source_worker_job_id.as_deref() == Some(worker_job_id)) - .map(|binding| binding.request_id) + .filter(|request| request.source_worker_job_id.as_deref() == Some(worker_job_id)) + .map(|request| request.request_id) .collect::>(); let mut lookups = Vec::new(); for binding in cache.get_all::()? { - let model_owned = binding - .model_request_id - .as_deref() - .is_some_and(|model_request_id| model_request_ids.contains(model_request_id)); - let request_owned = binding.model_request_id.is_none() && binding.job_id == worker_job_id; - if !model_owned && !request_owned { - continue; - } let intent = cache .get::(&tool_invocation_intent_key(&binding.intent_id))? .ok_or_else(|| anyhow!("public source tool binding lost its intent"))?; + let model_owned = intent + .model_request_id + .as_deref() + .is_some_and(|model_request_id| model_request_ids.contains(model_request_id)); + let request_owned = intent.model_request_id.is_none() && binding.job_id == worker_job_id; + if !model_owned && !request_owned { + continue; + } if intent.server != "epiphany_public" || intent.tool_name != "github_file" { continue; } @@ -3280,22 +2709,9 @@ fn authenticated_public_source_lookup_receipts_for_worker( { return Err(anyhow!("public source result provenance is substituted")); } - lookups.push(EyesSourceLookupReceipt { - schema_version: crate::EYES_SOURCE_LOOKUP_RECEIPT_SCHEMA_VERSION.to_string(), + lookups.push(AuthenticatedPublicSourceLookup { receipt_id: lookup_receipt_id, - source_job_id: worker_job_id.to_string(), - substrate_grant_receipt_id: grant_id.clone(), - tool_intent_id: intent.intent_id, - tool_receipt_id: receipt.receipt_id, - provider: "github".to_string(), - repository, - revision: source.revision().to_string(), - path: source.path().to_string(), source_ref, - content_sha256, - byte_count, - observed_at: receipt.completed_at, - contract: "Eyes admitted one bounded immutable public source only after authenticating its worker, causal request or model execution, tool, grant, provider identity, and content digest.".to_string(), }); } lookups.sort_by(|left, right| left.receipt_id.cmp(&right.receipt_id)); @@ -3309,7 +2725,7 @@ fn authenticated_public_source_lookup_receipts_for_worker( pub(crate) fn authenticated_requested_public_source_lookups_for_worker( cache: &CultCache, worker_job_id: &str, -) -> Result> { +) -> Result> { validate_non_empty(worker_job_id, "frontier public source worker job id")?; let launch = cache .get::(worker_job_id)? @@ -3354,9 +2770,7 @@ fn validate_governed_source_tool_intent( .ok_or_else(|| anyhow!("governed source tool intent lost its source worker job"))?; if matches!( source_worker_job.status, - EpiphanyRuntimeJobStatus::Completed - | EpiphanyRuntimeJobStatus::Failed - | EpiphanyRuntimeJobStatus::Cancelled + EpiphanyRuntimeJobStatus::Completed | EpiphanyRuntimeJobStatus::Failed ) { return Err(anyhow!( "governed source tool intent {:?} is outside its launch grant", @@ -3381,15 +2795,15 @@ fn governed_source_tool_authority( crate::substrate_gate_operation_for_governed_tool(&intent.server, &intent.tool_name); let source_worker_job_id = match intent.model_request_id.as_deref() { Some(model_request_id) => { - let model_binding = cache - .get::(model_request_id)? + let model_request = cache + .get::(model_request_id)? .ok_or_else(|| { anyhow!( - "model-derived tool intent {:?} has no execution binding", + "model-derived tool intent {:?} has no native request", intent.intent_id ) })?; - match model_binding.source_worker_job_id { + match model_request.source_worker_job_id { Some(worker_job_id) => Some(worker_job_id), None if required_operation.is_some() => { return Err(anyhow!( @@ -3428,9 +2842,6 @@ fn governed_source_tool_authority( let launch = cache .get::(&source_worker_job_id)? .ok_or_else(|| anyhow!("governed source tool intent lost its source worker launch"))?; - let source_worker_job = cache - .get::(&source_worker_job_id)? - .ok_or_else(|| anyhow!("governed source tool intent lost its source worker job"))?; if !matches!( launch.binding_id.as_str(), crate::EPIPHANY_RESEARCH_ROLE_BINDING_ID @@ -3458,8 +2869,7 @@ fn governed_source_tool_authority( intent.intent_id ) })?; - if source_worker_job.role != launch.role - || grant.runtime_job_id != source_worker_job_id + if grant.runtime_job_id != source_worker_job_id || grant.binding_id != launch.binding_id || grant.role != launch.role || grant.authority_scope != launch.authority_scope @@ -3598,7 +3008,6 @@ pub fn claim_runtime_worker_process( } let claim_id = worker_process_claim_id(job_id); let claim = EpiphanyRuntimeWorkerProcessClaim { - schema_version: RUNTIME_WORKER_PROCESS_CLAIM_SCHEMA_VERSION.into(), claim_id: claim_id.clone(), job_id: job_id.into(), process_id: process.process_id, @@ -3726,7 +3135,6 @@ fn terminal_worker_request_id(cache: &CultCache, worker_job_id: &str) -> Result< let bindings = cache .get_all::()? .into_iter() - .filter(|binding| binding.source_worker_job_id.as_deref() == Some(worker_job_id)) .map(|binding| { let validated = validate_runtime_model_execution_binding(cache, &binding.request_id)?; let job = cache @@ -3735,23 +3143,22 @@ fn terminal_worker_request_id(cache: &CultCache, worker_job_id: &str) -> Result< let request = cache .get::(&validated.request_id)? .ok_or_else(|| anyhow!("worker model execution lost its native request"))?; - Ok((validated, job, request)) + Ok( + (request.source_worker_job_id.as_deref() == Some(worker_job_id)) + .then_some((validated, job, request)), + ) }) - .collect::>>()?; + .collect::>>()? + .into_iter() + .flatten() + .collect::>(); if bindings.is_empty() { return Ok(None); } let live = bindings .iter() - .filter(|(_, job, _)| { - matches!( - job.status, - EpiphanyRuntimeJobStatus::Queued - | EpiphanyRuntimeJobStatus::Running - | EpiphanyRuntimeJobStatus::WaitingForReview - ) - }) + .filter(|(_, job, _)| matches!(job.status, EpiphanyRuntimeJobStatus::Queued)) .collect::>(); if live.len() > 1 { return Err(anyhow!( @@ -3787,60 +3194,13 @@ fn terminal_worker_request_id(cache: &CultCache, worker_job_id: &str) -> Result< } } -fn complete_persisted_worker_outcome( - store_path: &Path, - job_id: &str, - completed_at: &str, -) -> Result> { - let mut cache = runtime_spine_cache(store_path)?; - cache.pull_all_backing_stores()?; - let role = cache.get::(job_id)?; - let reorient = cache.get::(job_id)?; - match (role, reorient) { - (Some(_), Some(_)) => Err(anyhow!( - "worker attempt has both role and reorientation terminal outcomes" - )), - (Some(result), None) => complete_runtime_job( - store_path, - RuntimeSpineJobResultOptions { - result_id: result.result_id, - job_id: job_id.to_string(), - completed_at: completed_at.to_string(), - verdict: result.verdict, - summary: result.summary, - next_safe_move: result.next_safe_move, - evidence_refs: result.evidence_ids, - artifact_refs: result.artifact_refs, - decision_context_id: Some(result.decision_context_id), - }, - ) - .map(Some), - (None, Some(result)) => complete_runtime_job( - store_path, - RuntimeSpineJobResultOptions { - result_id: result.result_id, - job_id: job_id.to_string(), - completed_at: completed_at.to_string(), - verdict: result.mode, - summary: result.summary, - next_safe_move: result.next_safe_move, - evidence_refs: result.evidence_ids, - artifact_refs: result.artifact_refs, - decision_context_id: Some(result.decision_context_id), - }, - ) - .map(Some), - (None, None) => Ok(None), - } -} - fn commit_runtime_worker_process_death( store_path: impl AsRef, job_id: &str, terminal_authority_id: &str, terminal_at: &str, decision_context_id: Option<&str>, -) -> Result { +) -> Result { let store_path = store_path.as_ref(); validate_non_empty(terminal_authority_id, "worker death terminal authority")?; chrono::DateTime::parse_from_rfc3339(terminal_at) @@ -3855,22 +3215,15 @@ fn commit_runtime_worker_process_death( if status == WorkerProcessStatus::TerminalDeath && current.terminal_authority_id.as_deref() == Some(terminal_authority_id) { - let result_id = format!("result-worker-death-{job_id}"); - let result = cache - .get::(&result_id)? - .ok_or_else(|| anyhow!("worker death claim lost its terminal job result"))?; let job = cache .get::(job_id)? .ok_or_else(|| anyhow!("worker death claim lost its runtime job"))?; - if job.status != EpiphanyRuntimeJobStatus::Failed - || result.job_id != job_id - || result.decision_context_id.as_deref() != decision_context_id - { + if job.status != EpiphanyRuntimeJobStatus::Failed { return Err(anyhow!( "worker death replay found split terminal authority" )); } - return Ok(result); + return Ok(job); } if !status.is_live() { return Err(anyhow!( @@ -3889,24 +3242,11 @@ fn commit_runtime_worker_process_death( let mut job = cache .get::(job_id)? .ok_or_else(|| anyhow!("worker death recovery requires its runtime job"))?; - if !matches!( - job.status, - EpiphanyRuntimeJobStatus::Queued - | EpiphanyRuntimeJobStatus::Running - | EpiphanyRuntimeJobStatus::WaitingForReview - ) { + if !matches!(job.status, EpiphanyRuntimeJobStatus::Queued) { return Err(anyhow!( "worker death recovery found a terminal runtime job" )); } - let existing_results = cache - .get_all::()? - .into_iter() - .filter(|result| result.job_id == job_id) - .collect::>(); - if !existing_results.is_empty() { - return Err(anyhow!("live worker death recovery found a job result")); - } let job_envelope = cache .get_envelope::(job_id)? .ok_or_else(|| anyhow!("worker death recovery lost its job envelope"))?; @@ -3917,31 +3257,7 @@ fn commit_runtime_worker_process_death( next.status = WorkerProcessStatus::TerminalDeath.as_str().into(); next.terminal_at = Some(terminal_at.into()); next.terminal_authority_id = Some(terminal_authority_id.into()); - let summary = "Runtime Continuity proved the exact worker process terminal before a structured outcome was admitted.".to_string(); job.status = EpiphanyRuntimeJobStatus::Failed; - job.updated_at = terminal_at.to_string(); - job.summary = summary.clone(); - let result = EpiphanyRuntimeJobResult { - schema_version: RUNTIME_SPINE_SCHEMA_VERSION.to_string(), - result_id: format!("result-worker-death-{job_id}"), - job_id: job_id.to_string(), - session_id: job.session_id.clone(), - role: job.role.clone(), - verdict: "failed".to_string(), - summary: summary.clone(), - completed_at: terminal_at.to_string(), - next_safe_move: "Derive a fresh attempt from current typed work; never rebase the abandoned model output.".to_string(), - evidence_refs: Vec::new(), - artifact_refs: Vec::new(), - metadata: BTreeMap::new(), - decision_context_id: decision_context_id.map(str::to_string), - }; - if cache - .get::(&result.result_id)? - .is_some() - { - return Err(anyhow!("worker death terminal identities already exist")); - } let mut expected = vec![current_envelope, job_envelope]; let mut unchanged_strong_reads = Vec::new(); if let Some(context_id) = decision_context_id { @@ -3974,9 +3290,9 @@ fn commit_runtime_worker_process_death( expected.extend([context_envelope.clone(), failure_envelope.clone()]); unchanged_strong_reads.extend([context_envelope, failure_envelope]); } else if cache - .get_all::()? + .get_all::()? .iter() - .any(|binding| binding.source_worker_job_id.as_deref() == Some(job_id)) + .any(|request| request.source_worker_job_id.as_deref() == Some(job_id)) { return Err(anyhow!( "model-backed worker death cannot terminalize without its exact context" @@ -3985,7 +3301,6 @@ fn commit_runtime_worker_process_death( let mut writes = vec![ cache.prepare_entry(&claim_id, &next)?.0, cache.prepare_entry(job_id, &job)?.0, - cache.prepare_entry(&result.result_id, &result)?.0, ]; writes.extend(unchanged_strong_reads); if !SingleFileMessagePackBackingStore::new(store_path) @@ -3999,7 +3314,7 @@ fn commit_runtime_worker_process_death( decision_context_id, ); } - Ok(result) + Ok(job) } pub(crate) fn terminalize_dead_runtime_worker_attempt( @@ -4007,13 +3322,44 @@ pub(crate) fn terminalize_dead_runtime_worker_attempt( job_id: &str, terminal_authority_id: &str, terminal_at: &str, -) -> Result { +) -> Result { let store_path = store_path.as_ref(); - if let Some(result) = complete_persisted_worker_outcome(store_path, job_id, terminal_at)? { - return Ok(result); - } let mut cache = runtime_spine_cache(store_path)?; cache.pull_all_backing_stores()?; + let role = cache.get::(job_id)?; + let reorient = cache.get::(job_id)?; + if role.is_some() && reorient.is_some() { + return Err(anyhow!( + "worker attempt has both role and reorientation terminal outcomes" + )); + } + if role.is_some() || reorient.is_some() { + let job = cache + .get::(job_id)? + .ok_or_else(|| anyhow!("structured worker outcome lost its runtime job"))?; + let claim = cache + .get::(&worker_process_claim_id(job_id))? + .ok_or_else(|| anyhow!("structured worker outcome lost its process claim"))?; + let result = role + .as_ref() + .map(|result| (&result.result_id, result.item_error.as_deref())) + .or_else(|| { + reorient + .as_ref() + .map(|result| (&result.result_id, result.item_error.as_deref())) + }) + .expect("one structured outcome checked above"); + let (expected_job_status, expected_process_status) = worker_outcome_status(result.1)?; + if job.status != expected_job_status + || WorkerProcessStatus::parse(&claim.status)? != expected_process_status + || claim.terminal_authority_id.as_deref() != Some(result.0.as_str()) + { + return Err(anyhow!( + "structured worker outcome did not atomically terminalize its physiology" + )); + } + return Ok(job); + } let terminal_request_id = terminal_worker_request_id(&cache, job_id)?; drop(cache); let decision_context_id = if let Some(request_id) = terminal_request_id { @@ -4049,6 +3395,16 @@ pub fn put_runtime_role_worker_result( result: &EpiphanyRuntimeRoleWorkerResult, ) -> Result<()> { let store_path = store_path.as_ref(); + let mut cache = runtime_spine_cache(store_path)?; + cache.pull_all_backing_stores()?; + put_runtime_role_worker_result_from_cache(store_path, &cache, result) +} + +fn put_runtime_role_worker_result_from_cache( + store_path: &Path, + cache: &CultCache, + result: &EpiphanyRuntimeRoleWorkerResult, +) -> Result<()> { validate_non_empty(&result.job_id, "role worker result job id")?; validate_non_empty(&result.result_id, "role worker result id")?; validate_non_empty(&result.role_id, "role worker result role id")?; @@ -4056,22 +3412,37 @@ pub fn put_runtime_role_worker_result( &result.decision_context_id, "role worker result decision context id", )?; - if result.schema_version != RUNTIME_ROLE_WORKER_RESULT_SCHEMA_VERSION { - return Err(anyhow!("role worker result schema version mismatch")); - } - let mut cache = runtime_spine_cache(store_path)?; - cache.pull_all_backing_stores()?; - require_worker_decision_context(&cache, &result.decision_context_id, &result.job_id)?; + require_worker_decision_context(cache, &result.decision_context_id, &result.job_id)?; let worker_launch = cache .get::(&result.job_id)? .ok_or_else(|| anyhow!("role worker result requires its immutable worker launch"))?; let launch_document = worker_launch.launch_document()?; - let launch_body_basis = match &launch_document { - EpiphanyWorkerLaunchDocument::Role(document) => { - document.repository_body_observation_basis.as_ref() - } - EpiphanyWorkerLaunchDocument::Reorient(_) => None, + let EpiphanyWorkerLaunchDocument::Role(role_launch) = &launch_document else { + return Err(anyhow!( + "role worker result requires a role launch document" + )); }; + if !role_launch.role_id.eq_ignore_ascii_case(&result.role_id) { + return Err(anyhow!( + "role worker result cannot substitute its sealed launch role" + )); + } + let (terminal_job_status, terminal_process_status) = + worker_outcome_status(result.item_error.as_deref())?; + let is_failure = terminal_job_status == EpiphanyRuntimeJobStatus::Failed; + let carries_semantic_success = result.research_decision_msgpack.is_some() + || result.repo_model_mutation_proposal_msgpack.is_some() + || result.frontier_plan_candidate_msgpack.is_some() + || result.frontier_plan_mind_decision_msgpack.is_some() + || result.imagination_consideration_candidate_msgpack.is_some() + || result + .admitted_model_direction_consideration_result_msgpack + .is_some(); + if is_failure && carries_semantic_success { + return Err(anyhow!( + "typed role worker failure cannot carry successful semantic cargo" + )); + } let launch_is_modeling = worker_launch.role == EPIPHANY_MODELING_OWNER_ROLE; let result_is_modeling = result.role_id.eq_ignore_ascii_case("modeling"); if launch_is_modeling != result_is_modeling { @@ -4080,137 +3451,71 @@ pub fn put_runtime_role_worker_result( )); } if launch_is_modeling { - let expected = launch_body_basis.ok_or_else(|| { - anyhow!("Modeling worker launch is missing its repository Body observation basis") - })?; - if result.repository_body_observation_basis.as_ref() != Some(expected) { + role_launch + .repository_body_observation_basis + .as_ref() + .ok_or_else(|| { + anyhow!("Modeling worker launch is missing its repository Body observation basis") + })?; + } else if role_launch.repository_body_observation_basis.is_some() { + return Err(anyhow!( + "non-Modeling worker launch must not carry a repository Body observation basis" + )); + } + let launch_context_count = role_launch.family_context_count(); + if launch_context_count > 1 { + return Err(anyhow!( + "role worker launch carries multiple family request authorities" + )); + } + + if role_launch.frontier_research_context.is_some() { + if !result.role_id.eq_ignore_ascii_case("research") { return Err(anyhow!( - "Modeling runtime result must exactly bind its immutable launch repository Body observation basis" + "frontier Research result must retain its launch role" )); } - } else if launch_body_basis.is_some() || result.repository_body_observation_basis.is_some() { - return Err(anyhow!( - "non-Modeling worker launch and result must not carry a repository Body observation basis" - )); - } - let is_verification = result.role_id == "verification"; - if is_verification - != (result - .verification_request_id - .as_ref() - .is_some_and(|id| !id.trim().is_empty()) - && result - .frontier_route_id - .as_ref() - .is_some_and(|id| !id.trim().is_empty())) - { - return Err(anyhow!( - "Verification results require verificationRequestId and frontierRouteId; other roles must not claim them" - )); - } - if result.repo_frontier_modeling_request_id.is_some() - && !result.role_id.eq_ignore_ascii_case("modeling") - { - return Err(anyhow!( - "only Modeling results may carry a frontier Modeling request binding" - )); - } - if result.proposal_modeling_request_id.is_some() - && !result.role_id.eq_ignore_ascii_case("modeling") - { - return Err(anyhow!( - "only Modeling results may carry a proposal Modeling request binding" - )); - } - let is_modeling = result.role_id.eq_ignore_ascii_case("modeling"); - let modeling_binding_count = [ - result.repo_frontier_modeling_request_id.is_some(), - result.proposal_modeling_request_id.is_some(), - ] - .into_iter() - .filter(|present| *present) - .count(); - if is_modeling && modeling_binding_count > 1 { - return Err(anyhow!( - "Modeling result authority bindings are mutually exclusive" - )); - } - if is_modeling - && (result.repo_frontier_modeling_request_id - != worker_launch.repo_frontier_modeling_request_id - || result.proposal_modeling_request_id != worker_launch.proposal_modeling_request_id) - { - return Err(anyhow!( - "Modeling result must exactly preserve its runtime-owned request authority" - )); - } - let is_frontier_research = worker_launch.repo_frontier_research_request_id.is_some(); - if is_frontier_research - != result - .repo_frontier_research_request_id - .as_ref() - .is_some_and(|id| !id.trim().is_empty()) - || result.repo_frontier_research_request_id - != worker_launch.repo_frontier_research_request_id - || (is_frontier_research && !result.role_id.eq_ignore_ascii_case("research")) - || (!is_frontier_research && result.repo_frontier_research_request_id.is_some()) - { - return Err(anyhow!( - "Research result must exactly preserve its runtime-owned frontier request" - )); - } - if is_frontier_research { - let request = frontier_research_request_for_launch(&cache, &worker_launch)? + frontier_research_request_for_launch(&cache, &worker_launch)? .ok_or_else(|| anyhow!("Research result lost its typed request"))?; - if result.repo_frontier_research_request_id.as_deref() != Some(request.request_id.as_str()) - { - return Err(anyhow!("Research result substituted its typed request")); + if !is_failure { + result + .research_decision()? + .ok_or_else(|| anyhow!("Research result requires its typed decision"))? + .validate()?; } - result - .research_decision()? - .ok_or_else(|| anyhow!("Research result requires its typed decision"))? - .validate()?; } else if result.research_decision_msgpack.is_some() { return Err(anyhow!( "only an exact frontier Research result may carry a Research decision" )); } - let is_frontier_verification = worker_launch - .repo_frontier_verification_request_id - .is_some(); - if is_verification != is_frontier_verification { + let is_verification = result.role_id.eq_ignore_ascii_case("verification"); + if is_verification != role_launch.frontier_verification_context.is_some() { return Err(anyhow!( "Verification result and launch must share one exact frontier request" )); } - if is_frontier_verification { - let request = frontier_verification_request_for_launch(&cache, &worker_launch)? + if is_verification { + frontier_verification_request_for_launch(&cache, &worker_launch)? .ok_or_else(|| anyhow!("Verification result lost its typed request"))?; - if result.verification_request_id.as_deref() != Some(request.request_id.as_str()) - || result.frontier_route_id.as_deref() != Some(request.route_id.as_str()) - { - return Err(anyhow!( - "Verification result substituted its exact request or route" - )); - } } - if is_modeling - && (result.verification_request_id.is_some() || result.frontier_route_id.is_some()) - { + + let has_frontier_modeling_context = role_launch.proposal_modeling_context.is_some() + || role_launch.frontier_verdict_modeling_context.is_some(); + if !result_is_modeling && result.repo_model_mutation_proposal_msgpack.is_some() { return Err(anyhow!( - "Modeling result cannot claim Verification route authority" + "only Modeling results may carry a RepoModel mutation proposal" )); } - if is_modeling - && (modeling_binding_count == 1 || result.repo_model_mutation_proposal_msgpack.is_some()) + if result_is_modeling + && !is_failure + && (has_frontier_modeling_context || result.repo_model_mutation_proposal_msgpack.is_some()) { let proposal = result.repo_model_mutation_proposal()?.ok_or_else(|| { anyhow!("Modeling authority binding requires a RepoModel mutation proposal") })?; proposal.validate()?; let operations = proposal.operations()?; - if (worker_launch.repo_frontier_modeling_request_id.is_some() - || worker_launch.proposal_modeling_request_id.is_some()) + if has_frontier_modeling_context && operations.iter().any(|operation| { !matches!( operation, @@ -4222,21 +3527,29 @@ pub fn put_runtime_role_worker_result( "frontier Modeling request may authorize only semantic RepoModel frontier mutation" )); } - if worker_launch.repo_frontier_modeling_request_id.is_some() { + if role_launch.frontier_verdict_modeling_context.is_some() { validate_repo_frontier_verdict_modeling_mutation(&cache, &launch_document, &proposal)?; } } - let has_planning_echo = result.frontier_planning_request_id.is_some(); + + let planning_context = role_launch.frontier_planning_context.as_ref(); let has_planning_candidate = result.frontier_plan_candidate_msgpack.is_some(); - if has_planning_echo != has_planning_candidate { + if planning_context.is_none() && has_planning_candidate { return Err(anyhow!( - "frontier planning result requires both its request echo and typed candidate" + "frontier planning candidate requires its immutable launch context" )); } - if has_planning_echo { + if planning_context.is_some() && !is_failure && !has_planning_candidate { + return Err(anyhow!( + "successful frontier planning result requires its typed candidate" + )); + } + if let Some(planning_context) = planning_context + && has_planning_candidate + { if !result.role_id.eq_ignore_ascii_case("imagination") { return Err(anyhow!( - "only Imagination results may echo a frontier planning request" + "only Imagination results may carry a frontier planning candidate" )); } if result.item_error.is_some() { @@ -4244,21 +3557,7 @@ pub fn put_runtime_role_worker_result( "frontier planning result with an item error cannot carry an executable candidate" )); } - if result.research_decision_msgpack.is_some() - || result.repo_model_mutation_proposal_msgpack.is_some() - || result.verification_request_id.is_some() - || result.frontier_route_id.is_some() - || result.repo_frontier_modeling_request_id.is_some() - || result.proposal_modeling_request_id.is_some() - { - return Err(anyhow!( - "frontier planning result may carry only its request echo and typed candidate authority" - )); - } - let request_id = result - .frontier_planning_request_id - .as_deref() - .ok_or_else(|| anyhow!("frontier planning request echo disappeared"))?; + let request_id = planning_context.request_id.as_str(); let request = cache .get::(request_id)? .ok_or_else(|| anyhow!("frontier planning result requires persisted request"))?; @@ -4267,92 +3566,54 @@ pub fn put_runtime_role_worker_result( .frontier_plan_candidate()? .ok_or_else(|| anyhow!("frontier planning candidate disappeared"))?; validate_repo_frontier_plan_candidate_against_request(&cache, &candidate, &request)?; - let bindings = cache - .get_all::()? - .into_iter() - .filter(|binding| { - binding.planning_request_id == request.request_id && binding.job_id == result.job_id - }) - .collect::>(); - if bindings.len() != 1 { - return Err(anyhow!( - "frontier planning result requires exactly one coordinator launch binding" - )); - } - let binding = &bindings[0]; - let worker_launch = cache - .get::(&result.job_id)? - .ok_or_else(|| anyhow!("frontier planning result requires its worker launch"))?; - let launch_document = worker_launch.launch_document()?; - let projection = match &launch_document { - EpiphanyWorkerLaunchDocument::Role(document) => { - document.frontier_planning_context.as_ref() - } - EpiphanyWorkerLaunchDocument::Reorient(_) => None, - }; let expected_projection = crate::RepoFrontierPlanningContextProjection::from_request(&request); - let launch_hash = format!( - "{:x}", - Sha256::digest(&worker_launch.launch_document_msgpack) - ); - let expected_binding_record_id = if binding.attempt_ordinal == 0 { - format!("repo-frontier-planning-launch-{}", request.request_id) - } else { - format!( - "repo-frontier-planning-launch-{}-attempt-{}", - request.request_id, binding.attempt_ordinal - ) - }; - if binding.schema_version != REPO_FRONTIER_PLANNING_LAUNCH_BINDING_SCHEMA_VERSION - || binding.contract != REPO_FRONTIER_PLANNING_LAUNCH_BINDING_CONTRACT - || binding.binding_record_id != expected_binding_record_id - || binding.job_id != result.job_id - || binding.binding_id != EPIPHANY_IMAGINATION_ROLE_BINDING_ID - || binding.runtime_id != request.runtime_id - || binding.thread_id != launch_document.thread_id() - || binding.worker_launch_document_sha256 != launch_hash - || worker_launch.job_id != result.job_id + crate::current_work::frontier_planning_attempt_ordinal( + &request.request_id, + &result.job_id, + )?; + if worker_launch.job_id != result.job_id || worker_launch.role != EPIPHANY_IMAGINATION_OWNER_ROLE || worker_launch.binding_id != EPIPHANY_IMAGINATION_ROLE_BINDING_ID - || worker_launch.frontier_planning_request_id.as_deref() - != Some(request.request_id.as_str()) - || worker_launch.proposal_modeling_request_id.is_some() - || projection != Some(&expected_projection) + || launch_document.thread_id() != result.job_id + || planning_context != &expected_projection { return Err(anyhow!( - "frontier planning result does not exactly bind request, launch, runtime, launch provenance, and candidate" + "frontier planning result does not exactly bind request, immutable launch, and candidate" )); } } let mut model_direction_companion = None; - let has_model_direction_echo = result - .admitted_model_direction_consideration_request_id - .is_some(); + let model_direction_context = role_launch + .admitted_model_direction_consideration_context + .as_ref(); let has_model_direction_result = result .admitted_model_direction_consideration_result_msgpack .is_some(); - if has_model_direction_echo != has_model_direction_result { + if model_direction_context.is_none() && has_model_direction_result { return Err(anyhow!( - "model direction result requires exact request echo and result" + "model direction result requires its immutable launch context" )); } - if has_model_direction_echo { + if model_direction_context.is_some() && !is_failure && !has_model_direction_result { + return Err(anyhow!( + "successful model direction result requires its typed decision" + )); + } + if let Some(model_direction_context) = model_direction_context + && has_model_direction_result + { if !result.role_id.eq_ignore_ascii_case("imagination") || result.item_error.is_some() || result.research_decision_msgpack.is_some() || result.repo_model_mutation_proposal_msgpack.is_some() - || result.imagination_consideration_request_id.is_some() || result.imagination_consideration_candidate_msgpack.is_some() { return Err(anyhow!( "model direction result carries foreign authority cargo" )); } - let request_id = result - .admitted_model_direction_consideration_request_id - .as_deref() - .unwrap(); + let request_id = model_direction_context.request.request_id.as_str(); let request = cache .get::(request_id)? .ok_or_else(|| anyhow!("model direction result request disappeared"))?; @@ -4371,25 +3632,11 @@ pub fn put_runtime_role_worker_result( "model direction result identity was not assigned by exact launch" )); } - let worker = cache - .get::(&result.job_id)? - .ok_or_else(|| anyhow!("model direction result requires worker launch"))?; - let document = worker.launch_document()?; - let projection = match &document { - EpiphanyWorkerLaunchDocument::Role(document) => document - .admitted_model_direction_consideration_context - .as_ref(), - EpiphanyWorkerLaunchDocument::Reorient(_) => None, - }; - if worker.role != EPIPHANY_IMAGINATION_OWNER_ROLE - || worker.binding_id != EPIPHANY_IMAGINATION_ROLE_BINDING_ID - || worker - .admitted_model_direction_consideration_request_id - .as_deref() - != Some(request_id) - || projection.map(|projection| &projection.request) != Some(&request) - || projection.map(|projection| projection.model.reasoning_basis()) - != Some(crate::EpiphanyRepoModelBasis { + if worker_launch.role != EPIPHANY_IMAGINATION_OWNER_ROLE + || worker_launch.binding_id != EPIPHANY_IMAGINATION_ROLE_BINDING_ID + || model_direction_context.request != request + || model_direction_context.model.reasoning_basis() + != (crate::EpiphanyRepoModelBasis { projection_digest: request.model_projection_digest.clone(), source_documents: request.model_source_documents.clone(), }) @@ -4401,35 +3648,33 @@ pub fn put_runtime_role_worker_result( model_direction_companion = Some(direction_result); } let mut imagination_candidate_companion = None; - let has_consideration_echo = result.imagination_consideration_request_id.is_some(); + let consideration_context = role_launch.imagination_consideration_context.as_ref(); let has_consideration_candidate = result.imagination_consideration_candidate_msgpack.is_some(); - if has_consideration_echo != has_consideration_candidate { + if consideration_context.is_none() && has_consideration_candidate { return Err(anyhow!( - "consideration result requires request echo and candidate" + "consideration candidate requires its immutable launch context" )); } - if has_consideration_echo { + if consideration_context.is_some() && !is_failure && !has_consideration_candidate { + return Err(anyhow!( + "successful consideration result requires its typed candidate" + )); + } + if let Some(consideration_context) = consideration_context + && has_consideration_candidate + { if !result.role_id.eq_ignore_ascii_case("imagination") || result.item_error.is_some() || result.research_decision_msgpack.is_some() || result.repo_model_mutation_proposal_msgpack.is_some() - || result.verification_request_id.is_some() - || result.frontier_route_id.is_some() - || result.repo_frontier_modeling_request_id.is_some() - || result.proposal_modeling_request_id.is_some() - || result.frontier_planning_request_id.is_some() || result.frontier_plan_candidate_msgpack.is_some() - || result.frontier_plan_mind_request_id.is_some() || result.frontier_plan_mind_decision_msgpack.is_some() { return Err(anyhow!( "consideration result carries foreign authority cargo" )); } - let request_id = result - .imagination_consideration_request_id - .as_deref() - .unwrap(); + let request_id = consideration_context.request.request_id.as_str(); let request = cache .get::(request_id)? .ok_or_else(|| anyhow!("consideration result request disappeared"))?; @@ -4445,39 +3690,14 @@ pub fn put_runtime_role_worker_result( "consideration candidate identity was not assigned by exact launch" )); } - let bindings = cache - .get_all::()? - .into_iter() - .filter(|binding| binding.request_id == request_id && binding.job_id == result.job_id) - .collect::>(); - if bindings.len() != 1 { - return Err(anyhow!( - "consideration result requires one exact attempt binding" - )); - } - let binding = &bindings[0]; - let worker = cache - .get::(&result.job_id)? - .ok_or_else(|| anyhow!("consideration result requires worker launch"))?; - let document = worker.launch_document()?; - let projection = match &document { - EpiphanyWorkerLaunchDocument::Role(document) => { - document.imagination_consideration_context.as_ref() - } - EpiphanyWorkerLaunchDocument::Reorient(_) => None, - }; - let launch_hash = format!("{:x}", Sha256::digest(&worker.launch_document_msgpack)); - if binding.job_id != result.job_id - || binding.binding_id != EPIPHANY_IMAGINATION_ROLE_BINDING_ID - || binding.runtime_id != request.runtime_id - || binding.thread_id != request.thread_id - || binding.worker_launch_document_sha256 != launch_hash - || worker.role != EPIPHANY_IMAGINATION_OWNER_ROLE - || worker.binding_id != EPIPHANY_IMAGINATION_ROLE_BINDING_ID - || worker.imagination_consideration_request_id.as_deref() != Some(request_id) - || projection.map(|projection| &projection.request) != Some(&request) - || projection.map(|projection| projection.model.reasoning_basis()) - != Some(crate::EpiphanyRepoModelBasis { + crate::current_work::consideration_attempt_ordinal(request_id, &result.job_id)?; + if worker_launch.job_id != result.job_id + || worker_launch.role != EPIPHANY_IMAGINATION_OWNER_ROLE + || worker_launch.binding_id != EPIPHANY_IMAGINATION_ROLE_BINDING_ID + || launch_document.thread_id() != result.job_id + || consideration_context.request != request + || consideration_context.model.reasoning_basis() + != (crate::EpiphanyRepoModelBasis { projection_digest: request.model_projection_digest.clone(), source_documents: request.model_source_documents.clone(), }) @@ -4488,30 +3708,32 @@ pub fn put_runtime_role_worker_result( } imagination_candidate_companion = Some(candidate); } - let has_mind_echo = result.frontier_plan_mind_request_id.is_some(); + let mind_context = role_launch.frontier_plan_mind_context.as_ref(); let has_mind_decision = result.frontier_plan_mind_decision_msgpack.is_some(); - if has_mind_echo != has_mind_decision { + if mind_context.is_none() && has_mind_decision { return Err(anyhow!( - "Mind result requires both exact request echo and typed decision" + "Mind decision requires its immutable launch context" )); } - if has_mind_echo { + if mind_context.is_some() && !is_failure && !has_mind_decision { + return Err(anyhow!( + "successful Mind result requires its typed decision" + )); + } + if let Some(mind_context) = mind_context + && has_mind_decision + { if !result.role_id.eq_ignore_ascii_case("mindAdmissionReview") || result.item_error.is_some() || result.research_decision_msgpack.is_some() || result.repo_model_mutation_proposal_msgpack.is_some() - || result.frontier_planning_request_id.is_some() || result.frontier_plan_candidate_msgpack.is_some() - || result.verification_request_id.is_some() - || result.frontier_route_id.is_some() - || result.repo_frontier_modeling_request_id.is_some() - || result.proposal_modeling_request_id.is_some() { return Err(anyhow!( "Mind decision result carries foreign authority cargo" )); } - let request_id = result.frontier_plan_mind_request_id.as_deref().unwrap(); + let request_id = mind_context.request.request_id.as_str(); let request = cache .get::(request_id)? .ok_or_else(|| anyhow!("Mind result request disappeared"))?; @@ -4531,56 +3753,40 @@ pub fn put_runtime_role_worker_result( "Mind decision substituted request echo or immutable candidate identity" )); } - let bindings = cache - .get_all::()? - .into_iter() - .filter(|binding| { - binding.mind_request_id == request.request_id && binding.job_id == result.job_id - }) - .collect::>(); - if bindings.len() != 1 { - return Err(anyhow!( - "Mind result requires exactly one coordinator launch binding" - )); - } - let binding = &bindings[0]; - let launch = cache - .get::(&result.job_id)? - .ok_or_else(|| anyhow!("Mind result launch disappeared"))?; - let document = launch.launch_document()?; - let projection = match &document { - EpiphanyWorkerLaunchDocument::Role(d) => d.frontier_plan_mind_context.as_ref(), - _ => None, - }; let expected = RepoFrontierPlanMindContextProjection::new(&request, &planning, &candidate); - let hash = format!("{:x}", Sha256::digest(&launch.launch_document_msgpack)); - if binding.schema_version != REPO_FRONTIER_PLAN_MIND_LAUNCH_BINDING_SCHEMA_VERSION - || binding.contract != REPO_FRONTIER_PLAN_MIND_LAUNCH_BINDING_CONTRACT - || binding.job_id != result.job_id - || binding.binding_id != EPIPHANY_MIND_ROLE_BINDING_ID - || binding.runtime_id != request.runtime_id - || binding.thread_id != document.thread_id() - || binding.worker_launch_document_sha256 != hash - || launch.role != EPIPHANY_MIND_OWNER_ROLE - || launch.binding_id != EPIPHANY_MIND_ROLE_BINDING_ID - || launch.frontier_plan_mind_request_id.as_deref() != Some(request.request_id.as_str()) - || projection != Some(&expected) + crate::current_work::frontier_plan_mind_attempt_ordinal( + &request.request_id, + &result.job_id, + )?; + if worker_launch.job_id != result.job_id + || worker_launch.role != EPIPHANY_MIND_OWNER_ROLE + || worker_launch.binding_id != EPIPHANY_MIND_ROLE_BINDING_ID + || launch_document.thread_id() != result.job_id + || mind_context != &expected { return Err(anyhow!( - "Mind result does not exactly bind request, launch, runtime, launch provenance, and candidate" + "Mind result does not exactly bind request, immutable launch, and candidate" )); } } let process_claim_id = worker_process_claim_id(&result.job_id); let process_claim = cache.get::(&process_claim_id)?; + let mut job = cache + .get::(&result.job_id)? + .ok_or_else(|| anyhow!("role worker result requires its runtime job"))?; if let Some(existing) = cache.get::(&result.job_id)? { if existing != *result { return Err(anyhow!( "role worker result is immutable for its runtime job" )); } + if job.status != terminal_job_status { + return Err(anyhow!( + "role worker result is not terminal authority for its runtime job" + )); + } if let Some(claim) = process_claim.as_ref() - && (!crate::WorkerProcessStatus::parse(&claim.status)?.is_fulfilled_terminal() + && (crate::WorkerProcessStatus::parse(&claim.status)? != terminal_process_status || claim.terminal_authority_id.as_deref() != Some(result.result_id.as_str())) { return Err(anyhow!( @@ -4611,9 +3817,18 @@ pub fn put_runtime_role_worker_result( } return Ok(()); } + if job.status != EpiphanyRuntimeJobStatus::Queued { + return Err(anyhow!( + "role worker result requires its queued runtime job" + )); + } + let job_envelope = cache + .get_envelope::(&result.job_id)? + .ok_or_else(|| anyhow!("role worker result lost its job envelope"))?; + job.status = terminal_job_status.clone(); let (envelope, _) = cache.prepare_entry(&result.job_id, result)?; - let mut writes = vec![envelope]; - let mut expected = Vec::new(); + let mut writes = vec![envelope, cache.prepare_entry(&result.job_id, &job)?.0]; + let mut expected = vec![job_envelope]; if let Some(claim) = process_claim.as_ref() { if crate::WorkerProcessStatus::parse(&claim.status)? != crate::WorkerProcessStatus::Active { return Err(anyhow!( @@ -4624,7 +3839,7 @@ pub fn put_runtime_role_worker_result( .get_envelope::(&process_claim_id)? .ok_or_else(|| anyhow!("role worker result lost its process claim envelope"))?; let mut terminal_claim = claim.clone(); - terminal_claim.status = crate::WorkerProcessStatus::TerminalResult.as_str().into(); + terminal_claim.status = terminal_process_status.as_str().into(); terminal_claim.terminal_at = Some(chrono::Utc::now().to_rfc3339()); terminal_claim.terminal_authority_id = Some(result.result_id.clone()); expected.push(current_envelope); @@ -4680,12 +3895,16 @@ pub fn put_runtime_role_worker_result( .get::(&process_claim_id)? .is_some_and(|claim| { crate::WorkerProcessStatus::parse(&claim.status) - .is_ok_and(|status| status.is_fulfilled_terminal()) + .is_ok_and(|status| status == terminal_process_status) && claim.terminal_authority_id.as_deref() == Some(result.result_id.as_str()) }), None => true, }; + let job_matches = reloaded + .get::(&result.job_id)? + .is_some_and(|job| job.status == terminal_job_status); if worker_matches + && job_matches && model_direction_companion_matches && imagination_candidate_companion_matches && claim_matches @@ -4723,15 +3942,21 @@ fn validated_proposal_modeling_worker_fulfillment( cache: &CultCache, result: &EpiphanyRuntimeRoleWorkerResult, ) -> Result { - let request_id = result - .proposal_modeling_request_id - .as_deref() - .ok_or_else(|| anyhow!("proposal Modeling fulfillment lost request echo"))?; if !result.role_id.eq_ignore_ascii_case("modeling") || result.item_error.is_some() { return Err(anyhow!( "proposal Modeling fulfillment requires a successful Modeling result" )); } + let launch = cache + .get::(&result.job_id)? + .ok_or_else(|| anyhow!("proposal Modeling fulfillment worker launch is missing"))?; + let document = launch.launch_document()?; + let projection = match &document { + EpiphanyWorkerLaunchDocument::Role(document) => document.proposal_modeling_context.as_ref(), + EpiphanyWorkerLaunchDocument::Reorient(_) => None, + } + .ok_or_else(|| anyhow!("proposal Modeling fulfillment lost immutable launch context"))?; + let request_id = projection.request_id.as_str(); let mutation_proposal = result .repo_model_mutation_proposal()? .ok_or_else(|| anyhow!("proposal Modeling fulfillment lost RepoModel mutation proposal"))?; @@ -4745,22 +3970,7 @@ fn validated_proposal_modeling_worker_fulfillment( .get::(&request.proposal_id)? .ok_or_else(|| anyhow!("proposal Modeling fulfillment proposal is missing"))?; validate_repo_frontier_work_proposal(&proposal)?; - validate_autonomous_proposal_origin_binding(cache, &proposal)?; - let binding_id = format!( - "repo-frontier-proposal-modeling-launch-{}", - result.job_id - ); - let binding = cache - .get::(&binding_id)? - .ok_or_else(|| anyhow!("proposal Modeling fulfillment lost its launch binding"))?; - let launch = cache - .get::(&result.job_id)? - .ok_or_else(|| anyhow!("proposal Modeling fulfillment worker launch is missing"))?; - let document = launch.launch_document()?; - let projection = match &document { - EpiphanyWorkerLaunchDocument::Role(document) => document.proposal_modeling_context.as_ref(), - EpiphanyWorkerLaunchDocument::Reorient(_) => None, - }; + validate_autonomous_proposal_origin_request(cache, &proposal)?; let identity = require_identity(cache)?; let proposal_payload_sha256 = crate::repo_frontier_proposal_payload_sha256( &proposal.title, @@ -4775,7 +3985,7 @@ fn validated_proposal_modeling_worker_fulfillment( request_id, attempt_ordinal, )?; - let projection_matches_authenticated_request = projection.is_some_and(|projection| { + let projection_matches_authenticated_request = { let expected = crate::RepoFrontierProposalModelingContextProjection { schema_version: crate::REPO_FRONTIER_PROPOSAL_MODELING_CONTEXT_SCHEMA_VERSION.into(), contract: crate::REPO_FRONTIER_PROPOSAL_MODELING_CONTEXT_CONTRACT.into(), @@ -4795,8 +4005,7 @@ fn validated_proposal_modeling_worker_fulfillment( prior_admission_refusals: prior_admission_refusals.clone(), }; projection == &expected - }); - let launch_sha256 = format!("{:x}", Sha256::digest(&launch.launch_document_msgpack)); + }; let mismatches = [ ( "request.payload", @@ -4816,33 +4025,12 @@ fn validated_proposal_modeling_worker_fulfillment( proposal.payload_sha256 != proposal_payload_sha256, ), ("request.runtime", request.runtime_id != identity.runtime_id), - ( - "binding.schema", - binding.schema_version != REPO_FRONTIER_PROPOSAL_MODELING_LAUNCH_BINDING_SCHEMA_VERSION, - ), - ( - "binding.request", - binding.proposal_modeling_request_id != request.request_id, - ), - ("binding.job", binding.job_id != result.job_id), - ( - "binding.hash", - binding.worker_launch_document_sha256 != launch_sha256, - ), - ( - "launch.schema", - launch.schema_version != RUNTIME_WORKER_LAUNCH_REQUEST_SCHEMA_VERSION, - ), ("launch.job", launch.job_id != result.job_id), ( "launch.binding", launch.binding_id != EPIPHANY_MODELING_ROLE_BINDING_ID, ), ("launch.role", launch.role != EPIPHANY_MODELING_OWNER_ROLE), - ( - "launch.request", - launch.proposal_modeling_request_id.as_deref() != Some(request.request_id.as_str()), - ), ( "launch.projection", !projection_matches_authenticated_request, @@ -4920,15 +4108,13 @@ pub(crate) fn validate_proposal_modeling_worker_admission( Ok(()) } -pub(crate) fn runtime_typed_request_fulfillment( - store_path: impl AsRef, +pub(crate) fn runtime_typed_request_fulfillment_from_cache( + store_path: &Path, + cache: &CultCache, request: RuntimeTypedRequestRef<'_>, ) -> Result> { - let store_path = store_path.as_ref(); let request_id = request.request_id(); validate_non_empty(request_id, "typed fulfillment request id")?; - let mut cache = runtime_spine_cache(store_path)?; - cache.pull_all_backing_stores()?; let admission_refusals = cache .get_all::()? .into_iter() @@ -4959,8 +4145,7 @@ pub(crate) fn runtime_typed_request_fulfillment( )); } if let Some(attempt) = archived_matches.first() { - if attempt.schema_version != ARCHIVED_RUNTIME_WORKER_ATTEMPT_SCHEMA_VERSION - || attempt.request_kind != request.kind() + if attempt.request_kind != request.kind() || !crate::WorkerProcessStatus::parse(&attempt.terminal_process_status)? .is_fulfilled_terminal() || !attempt.retired_chain_digest.starts_with("sha256:") @@ -4968,16 +4153,6 @@ pub(crate) fn runtime_typed_request_fulfillment( return Err(anyhow!("archived typed fulfillment tombstone is invalid")); } attempt.validate_decision_record(true)?; - let archived_role_result = attempt - .decision - .as_ref() - .and_then(|decision| decision.role_result.as_ref()) - .ok_or_else(|| anyhow!("archived typed fulfillment lost its structured result"))?; - if !request.matches_result(archived_role_result) { - return Err(anyhow!( - "archived typed fulfillment substituted its structured result" - )); - } if cache .get::(&attempt.job_id)? .is_some() @@ -5002,11 +4177,20 @@ pub(crate) fn runtime_typed_request_fulfillment( .expect("validated archived result"), })); } + let matching_job_ids = cache + .get_all::()? + .into_iter() + .filter_map(|launch| match request.matches_launch(&launch) { + Ok(true) => Some(Ok(launch.job_id)), + Ok(false) => None, + Err(error) => Some(Err(error)), + }) + .collect::>>()?; let matches = cache .get_all::()? .into_iter() .filter(|result| { - request.matches_result(result) + matching_job_ids.contains(&result.job_id) && !admission_refusals.iter().any(|refusal| { refusal.pass_family.request_kind() == request.kind() && refusal.request_id == request_id @@ -5032,7 +4216,7 @@ pub(crate) fn runtime_typed_request_fulfillment( } // This is the runtime admission owner. Replaying an already persisted // immutable result performs the same full validation without writing. - put_runtime_role_worker_result(store_path, result)?; + put_runtime_role_worker_result_from_cache(store_path, cache, result)?; match request { RuntimeTypedRequestRef::ProposalModeling(_) => { validate_proposal_modeling_worker_fulfillment(&cache, result)?; @@ -5078,9 +4262,7 @@ pub(crate) fn runtime_typed_request_fulfillment( pub(crate) fn validate_repo_frontier_work_proposal( proposal: &RepoFrontierWorkProposal, ) -> Result<()> { - if proposal.schema_version != REPO_FRONTIER_WORK_PROPOSAL_SCHEMA_VERSION - || proposal.contract != REPO_FRONTIER_WORK_PROPOSAL_CONTRACT - || proposal.proposal_id.trim().is_empty() + if proposal.proposal_id.trim().is_empty() || proposal.title.trim().is_empty() || proposal.body.trim().is_empty() { @@ -5098,48 +4280,36 @@ pub(crate) fn validate_repo_frontier_work_proposal( Ok(()) } -fn validate_autonomous_proposal_origin_binding( +fn validate_autonomous_proposal_origin_request( cache: &CultCache, proposal: &RepoFrontierWorkProposal, -) -> Result<( - RepoFrontierAutonomousProposalBinding, - RepoFrontierProposalModelingRequest, -)> { - let binding_id = format!("autonomous-proposal-binding-{}", proposal.proposal_id); - let binding = cache - .get::(&binding_id)? - .ok_or_else(|| anyhow!("Imagination proposal lacks its autonomous origin binding"))?; - let result = cache - .get::(&binding.direction_result_id)? - .ok_or_else(|| anyhow!("autonomous proposal binding lost its direction result"))?; - let request = cache - .get::(&result.request_id)? - .ok_or_else(|| anyhow!("autonomous proposal binding lost its direction request"))?; +) -> Result { + let identity = require_identity(cache)?; let modeling_request_id = crate::proposal_modeling_request_id( - &request.runtime_id, + &identity.runtime_id, &proposal.proposal_id, &proposal.payload_sha256, ); let modeling_request = cache .get::(&modeling_request_id)? - .ok_or_else(|| anyhow!("autonomous proposal binding lost its Modeling request"))?; + .ok_or_else(|| anyhow!("Imagination proposal lacks its Modeling request"))?; validate_repo_frontier_proposal_modeling_request(&modeling_request)?; + let result = cache + .get::( + &modeling_request.direction_result_id, + )? + .ok_or_else(|| anyhow!("autonomous proposal request lost its direction result"))?; + let request = cache + .get::(&result.request_id)? + .ok_or_else(|| anyhow!("autonomous proposal request lost its direction request"))?; let worker_result = cache - .get::(&binding.direction_worker_job_id)? - .ok_or_else(|| anyhow!("autonomous proposal binding lost its Imagination worker result"))?; + .get::(&modeling_request.direction_worker_job_id)? + .ok_or_else(|| anyhow!("autonomous proposal request lost its Imagination worker result"))?; let worker_launch = cache - .get::(&binding.direction_worker_job_id)? - .ok_or_else(|| anyhow!("autonomous proposal binding lost its Imagination worker launch"))?; + .get::(&modeling_request.direction_worker_job_id)? + .ok_or_else(|| anyhow!("autonomous proposal request lost its Imagination worker launch"))?; crate::validate_admitted_model_direction_consideration_request(&request)?; crate::validate_admitted_model_direction_consideration_result(&request, &result)?; - let worker_result_sha256 = format!( - "{:x}", - Sha256::digest(rmp_serde::to_vec_named(&worker_result)?) - ); - let worker_launch_sha256 = format!( - "{:x}", - Sha256::digest(rmp_serde::to_vec_named(&worker_launch)?) - ); let worker_direction = worker_result .admitted_model_direction_consideration_result()? .ok_or_else(|| anyhow!("autonomous proposal worker result lost its direction cargo"))?; @@ -5151,8 +4321,8 @@ fn validate_autonomous_proposal_origin_binding( }; let option = result .option_drafts - .get(binding.option_ordinal as usize) - .ok_or_else(|| anyhow!("autonomous proposal binding names a missing option"))?; + .get(modeling_request.direction_option_ordinal as usize) + .ok_or_else(|| anyhow!("autonomous proposal request names a missing option"))?; let route = cache .get::(crate::RUNTIME_BODY_STORE_BINDING_KEY)? .ok_or_else(|| anyhow!("autonomous proposal requires repository Body binding"))?; @@ -5160,36 +4330,21 @@ fn validate_autonomous_proposal_origin_binding( .get::(RUNTIME_REPOSITORY_DOMAIN_BINDING_KEY)? .ok_or_else(|| anyhow!("autonomous proposal requires repository domain binding"))?; let chain_checks = [ - ( - "worker result hash", - binding.direction_worker_result_sha256 == worker_result_sha256, - ), - ( - "worker launch hash", - binding.direction_worker_launch_sha256 == worker_launch_sha256, - ), ( "worker job", - worker_result.job_id == binding.direction_worker_job_id, + worker_result.job_id == modeling_request.direction_worker_job_id, ), ( "worker role", worker_result.role_id.eq_ignore_ascii_case("imagination"), ), - ( - "worker request echo", - worker_result - .admitted_model_direction_consideration_request_id - .as_deref() - == Some(request.request_id.as_str()), - ), ("worker direction cargo", worker_direction == result), ( "direction launch identity", result.result_id == crate::admitted_model_direction_consideration_result_id_for_launch( &request.request_id, - &binding.direction_worker_job_id, + &modeling_request.direction_worker_job_id, ), ), ( @@ -5199,15 +4354,12 @@ fn validate_autonomous_proposal_origin_binding( .eq_ignore_ascii_case(EPIPHANY_IMAGINATION_OWNER_ROLE), ), ( - "launch binding", - worker_launch.binding_id == EPIPHANY_IMAGINATION_ROLE_BINDING_ID, + "launch job", + worker_launch.job_id == modeling_request.direction_worker_job_id, ), ( - "launch request echo", - worker_launch - .admitted_model_direction_consideration_request_id - .as_deref() - == Some(request.request_id.as_str()), + "launch binding", + worker_launch.binding_id == EPIPHANY_IMAGINATION_ROLE_BINDING_ID, ), ( "launch request projection", @@ -5219,33 +4371,26 @@ fn validate_autonomous_proposal_origin_binding( "autonomous proposal Imagination chain mismatch: {failed}" )); } - if binding.schema_version != REPO_FRONTIER_AUTONOMOUS_PROPOSAL_BINDING_SCHEMA_VERSION - || binding.direction_result_id != result.result_id + if modeling_request.direction_result_id != result.result_id || modeling_request.proposal_id != proposal.proposal_id || modeling_request.proposal_payload_sha256 != proposal.payload_sha256 || modeling_request.runtime_id != request.runtime_id || modeling_request.thread_id != request.thread_id - || domain.schema_version != RUNTIME_REPOSITORY_DOMAIN_BINDING_SCHEMA_VERSION - || domain.contract != RUNTIME_REPOSITORY_DOMAIN_BINDING_CONTRACT - || domain.binding_id != RUNTIME_REPOSITORY_DOMAIN_BINDING_KEY || domain.repository_full_name != modeling_request.repository - || domain.runtime_id != route.runtime_id - || domain.swarm_id != route.swarm_id - || domain.workspace_id != route.workspace_id || domain.body_binding_sha256 != route.body_binding_sha256 || proposal.title != option.title || proposal.body != option.summary { return Err(anyhow!("autonomous proposal origin binding mismatch")); } - Ok((binding, modeling_request)) + Ok(modeling_request) } -pub(crate) fn validate_autonomous_proposal_binding( +pub(crate) fn validate_autonomous_proposal_origin( cache: &CultCache, proposal: &RepoFrontierWorkProposal, -) -> Result { - let (binding, modeling_request) = validate_autonomous_proposal_origin_binding(cache, proposal)?; +) -> Result<()> { + let modeling_request = validate_autonomous_proposal_origin_request(cache, proposal)?; let route = cache .get::(crate::RUNTIME_BODY_STORE_BINDING_KEY)? .ok_or_else(|| anyhow!("autonomous proposal requires repository Body binding"))?; @@ -5260,21 +4405,21 @@ pub(crate) fn validate_autonomous_proposal_binding( { return Err(anyhow!("autonomous proposal Body binding mismatch")); } - Ok(binding) + Ok(()) } pub(crate) fn validate_repo_frontier_proposal_modeling_request( request: &RepoFrontierProposalModelingRequest, ) -> Result<()> { - if request.schema_version != REPO_FRONTIER_PROPOSAL_MODELING_REQUEST_SCHEMA_VERSION - || request.contract != REPO_FRONTIER_PROPOSAL_MODELING_REQUEST_CONTRACT - || request.request_id.trim().is_empty() + if request.request_id.trim().is_empty() || request.proposal_id.trim().is_empty() || request.proposal_payload_sha256.trim().is_empty() || request.runtime_id.trim().is_empty() || request.thread_id.trim().is_empty() || request.repository.trim().is_empty() || request.workspace.trim().is_empty() + || request.direction_result_id.trim().is_empty() + || request.direction_worker_job_id.trim().is_empty() || request.request_id != crate::proposal_modeling_request_id( &request.runtime_id, @@ -5293,7 +4438,6 @@ pub(crate) fn validate_repo_frontier_proposal_modeling_request( pub fn bind_runtime_repository_domain( runtime_store: impl AsRef, repository_full_name: &str, - bound_at: &str, ) -> Result { if !repository_full_name.starts_with("GameCult/") || repository_full_name["GameCult/".len()..].trim().is_empty() @@ -5303,8 +4447,6 @@ pub fn bind_runtime_repository_domain( "runtime repository domain requires a canonical GameCult name" )); } - chrono::DateTime::parse_from_rfc3339(bound_at) - .map_err(|_| anyhow!("runtime repository domain timestamp must be RFC3339"))?; let runtime_store = runtime_store.as_ref(); let mut cache = runtime_spine_cache(runtime_store)?; cache.pull_all_backing_stores()?; @@ -5323,22 +4465,13 @@ pub fn bind_runtime_repository_domain( return Err(anyhow!("runtime repository domain Body authority mismatch")); } let binding = RuntimeRepositoryDomainBinding { - schema_version: RUNTIME_REPOSITORY_DOMAIN_BINDING_SCHEMA_VERSION.into(), - binding_id: RUNTIME_REPOSITORY_DOMAIN_BINDING_KEY.into(), repository_full_name: repository_full_name.into(), - runtime_id: route.runtime_id.clone(), - swarm_id: route.swarm_id.clone(), - workspace_id: route.workspace_id.clone(), body_binding_sha256: route.body_binding_sha256.clone(), - bound_at: bound_at.into(), - contract: RUNTIME_REPOSITORY_DOMAIN_BINDING_CONTRACT.into(), }; if let Some(existing) = cache.get::(RUNTIME_REPOSITORY_DOMAIN_BINDING_KEY)? { - let mut replay = binding; - replay.bound_at = existing.bound_at.clone(); - return if replay == existing { + return if binding == existing { Ok(existing) } else { Err(anyhow!("runtime repository domain is immutable")) @@ -5420,13 +4553,7 @@ pub(crate) fn promote_autonomous_direction_options_for_modeling( != route.body_binding_sha256 || Path::new(workspace).canonicalize()? != Path::new(&body_binding.git_top_level).canonicalize()? - || domain.schema_version != RUNTIME_REPOSITORY_DOMAIN_BINDING_SCHEMA_VERSION - || domain.contract != RUNTIME_REPOSITORY_DOMAIN_BINDING_CONTRACT - || domain.binding_id != RUNTIME_REPOSITORY_DOMAIN_BINDING_KEY || domain.repository_full_name != repository - || domain.runtime_id != route.runtime_id - || domain.swarm_id != route.swarm_id - || domain.workspace_id != route.workspace_id || domain.body_binding_sha256 != route.body_binding_sha256 { return Err(anyhow!( @@ -5458,17 +4585,6 @@ pub(crate) fn promote_autonomous_direction_options_for_modeling( )); } let direction_worker = direction_workers.remove(0); - let direction_launch = opening - .get::(&direction_worker.job_id)? - .ok_or_else(|| anyhow!("autonomous proposal direction worker lost its launch"))?; - let direction_worker_result_sha256 = format!( - "{:x}", - Sha256::digest(rmp_serde::to_vec_named(&direction_worker)?) - ); - let direction_worker_launch_sha256 = format!( - "{:x}", - Sha256::digest(rmp_serde::to_vec_named(&direction_launch)?) - ); for (ordinal, option) in result.option_drafts.iter().enumerate() { if option.title.trim().is_empty() || option.summary.trim().is_empty() { return Err(anyhow!("autonomous direction option is empty")); @@ -5508,27 +4624,15 @@ pub(crate) fn promote_autonomous_direction_options_for_modeling( &evidence_refs, )?; let proposal = RepoFrontierWorkProposal { - schema_version: REPO_FRONTIER_WORK_PROPOSAL_SCHEMA_VERSION.into(), proposal_id: proposal_id.clone(), payload_sha256: payload_sha256.clone(), title: option.title.clone(), body: option.summary.clone(), constraints: result.uncertainties.clone(), evidence_refs, - contract: REPO_FRONTIER_WORK_PROPOSAL_CONTRACT.into(), }; validate_repo_frontier_work_proposal(&proposal)?; - let binding_id = format!("autonomous-proposal-binding-{proposal_id}"); - let binding = RepoFrontierAutonomousProposalBinding { - schema_version: REPO_FRONTIER_AUTONOMOUS_PROPOSAL_BINDING_SCHEMA_VERSION.into(), - direction_result_id: result.result_id.clone(), - option_ordinal: ordinal as u32, - direction_worker_job_id: direction_worker.job_id.clone(), - direction_worker_result_sha256: direction_worker_result_sha256.clone(), - direction_worker_launch_sha256: direction_worker_launch_sha256.clone(), - }; let selection = RepoFrontierProposalModelingRequest { - schema_version: REPO_FRONTIER_PROPOSAL_MODELING_REQUEST_SCHEMA_VERSION.into(), request_id: crate::proposal_modeling_request_id( &identity.runtime_id, &proposal_id, @@ -5541,41 +4645,40 @@ pub(crate) fn promote_autonomous_direction_options_for_modeling( repository: repository.into(), workspace: body_binding.git_top_level.clone(), selected_at: selected_at.into(), - contract: REPO_FRONTIER_PROPOSAL_MODELING_REQUEST_CONTRACT.into(), + direction_result_id: result.result_id.clone(), + direction_option_ordinal: ordinal as u32, + direction_worker_job_id: direction_worker.job_id.clone(), }; validate_repo_frontier_proposal_modeling_request(&selection)?; let mut current = runtime_spine_cache(runtime_store)?; current.pull_all_backing_stores()?; let existing = ( current.get::(&proposal.proposal_id)?, - current.get::(&binding_id)?, current.get::(&selection.request_id)?, ); - let replay_selection_matches = existing.2.as_ref().is_some_and(|existing_selection| { + let replay_selection_matches = existing.1.as_ref().is_some_and(|existing_selection| { let mut replay_selection = selection.clone(); replay_selection.selected_at = existing_selection.selected_at.clone(); existing_selection == &replay_selection }); - if let (Some(existing_proposal), Some(_), Some(existing_selection)) = &existing { - validate_autonomous_proposal_binding(¤t, existing_proposal)?; + if let (Some(existing_proposal), Some(existing_selection)) = &existing { + validate_autonomous_proposal_origin(¤t, existing_proposal)?; if existing_proposal == &proposal && replay_selection_matches { promoted.push(existing_selection.clone()); continue; } } - if existing.0.is_some() || existing.1.is_some() || existing.2.is_some() { + if existing.0.is_some() || existing.1.is_some() { return Err(anyhow!( - "autonomous proposal promotion companion collision for {}: proposalPresent={} bindingPresent={} selectionPresent={} proposalMatches={} selectionMatches={}", + "autonomous proposal promotion companion collision for {}: proposalPresent={} selectionPresent={} proposalMatches={} selectionMatches={}", proposal.proposal_id, existing.0.is_some(), existing.1.is_some(), - existing.2.is_some(), existing.0.as_ref() == Some(&proposal), replay_selection_matches, )); } let (proposal_envelope, _) = current.prepare_entry(&proposal.proposal_id, &proposal)?; - let (binding_envelope, _) = current.prepare_entry(&binding_id, &binding)?; let (selection_envelope, _) = current.prepare_entry(&selection.request_id, &selection)?; let mut expected = vec![ @@ -5626,7 +4729,7 @@ pub(crate) fn promote_autonomous_direction_options_for_modeling( expected.push(envelope); } let mut replacement = expected.clone(); - replacement.extend([proposal_envelope, binding_envelope, selection_envelope]); + replacement.extend([proposal_envelope, selection_envelope]); if !SingleFileMessagePackBackingStore::new(runtime_store) .compare_and_swap_batch(&expected, replacement)? { @@ -5716,7 +4819,6 @@ pub fn select_and_commit_repo_frontier_planning_request( return Ok(existing); } let request = RepoFrontierPlanningRequest { - schema_version: REPO_FRONTIER_PLANNING_REQUEST_SCHEMA_VERSION.into(), request_id: request_id.clone(), model_projection_digest: basis.projection_digest.clone(), model_source_documents: basis.source_documents.clone(), @@ -5725,7 +4827,6 @@ pub fn select_and_commit_repo_frontier_planning_request( selected_organ: "Imagination".into(), repository_scope: item.repository_scope.clone(), requested_at: at.into(), - contract: REPO_FRONTIER_PLANNING_CONTRACT.into(), runtime_id: identity.runtime_id, frontier_authority_documents, claim_obligation_documents, @@ -5765,9 +4866,7 @@ fn validate_repo_frontier_planning_request( source_documents: request.model_source_documents.clone(), }; basis.validate()?; - if request.schema_version != REPO_FRONTIER_PLANNING_REQUEST_SCHEMA_VERSION - || request.contract != REPO_FRONTIER_PLANNING_CONTRACT - || request.runtime_id.trim().is_empty() + if request.runtime_id.trim().is_empty() || request.frontier_item_id.trim().is_empty() || request.frontier_item_hash.trim().is_empty() || request.selected_organ != "Imagination" @@ -5908,11 +5007,13 @@ pub fn commit_repo_frontier_plan_mind_request( )); } let result = &results[0]; - put_runtime_role_worker_result(runtime_store, result)?; - let planning_request_id = result - .frontier_planning_request_id - .as_deref() - .ok_or_else(|| anyhow!("Mind request source lacks planning request echo"))?; + put_runtime_role_worker_result_from_cache(runtime_store, &cache, result)?; + let source_launch = role_launch_document_for_job(&cache, &result.job_id)?; + let planning_request_id = source_launch + .frontier_planning_context + .as_ref() + .map(|context| context.request_id.as_str()) + .ok_or_else(|| anyhow!("Mind request source lacks typed planning authority"))?; let planning = cache .get::(planning_request_id)? .ok_or_else(|| anyhow!("Mind request source planning request disappeared"))?; @@ -5929,7 +5030,6 @@ pub fn commit_repo_frontier_plan_mind_request( &candidate_sha256, ); let request = RepoFrontierPlanMindRequest { - schema_version: REPO_FRONTIER_PLAN_MIND_REQUEST_SCHEMA_VERSION.into(), request_id: request_id.clone(), planning_request_id: planning.request_id.clone(), imagination_result_id: result.result_id.clone(), @@ -5938,7 +5038,6 @@ pub fn commit_repo_frontier_plan_mind_request( candidate_sha256, runtime_id: planning.runtime_id.clone(), requested_at: requested_at.into(), - contract: REPO_FRONTIER_PLAN_MIND_REQUEST_CONTRACT.into(), }; if let Some(existing) = cache.get::(&request_id)? { let mut retry = request.clone(); @@ -5991,10 +5090,7 @@ fn validate_repo_frontier_plan_mind_request_identity( cache: &CultCache, request: &RepoFrontierPlanMindRequest, ) -> Result<(RepoFrontierPlanningRequest, RepoFrontierPlanCandidate)> { - if request.schema_version != REPO_FRONTIER_PLAN_MIND_REQUEST_SCHEMA_VERSION - || request.contract != REPO_FRONTIER_PLAN_MIND_REQUEST_CONTRACT - || chrono::DateTime::parse_from_rfc3339(&request.requested_at).is_err() - { + if chrono::DateTime::parse_from_rfc3339(&request.requested_at).is_err() { return Err(anyhow!("invalid typed Mind request")); } let result = cache @@ -6009,9 +5105,13 @@ fn validate_repo_frontier_plan_mind_request_identity( .frontier_plan_candidate()? .ok_or_else(|| anyhow!("Mind request candidate disappeared"))?; let hash = format!("{:x}", Sha256::digest(rmp_serde::to_vec_named(&candidate)?)); + let source_launch = role_launch_document_for_job(cache, &result.job_id)?; + let launch_planning_request_id = source_launch + .frontier_planning_context + .as_ref() + .map(|context| context.request_id.as_str()); if result.job_id != request.imagination_job_id - || result.frontier_planning_request_id.as_deref() - != Some(request.planning_request_id.as_str()) + || launch_planning_request_id != Some(request.planning_request_id.as_str()) || candidate.candidate_id != request.candidate_id || hash != request.candidate_sha256 || request.runtime_id != planning.runtime_id @@ -6064,9 +5164,7 @@ fn validate_repo_frontier_plan_candidate_against_request( candidate: &RepoFrontierPlanCandidate, request: &RepoFrontierPlanningRequest, ) -> Result<()> { - if candidate.schema_version != REPO_FRONTIER_PLAN_CANDIDATE_SCHEMA_VERSION - || candidate.contract != REPO_FRONTIER_PLANNING_CONTRACT - || candidate.selected_fields_invalid() + if candidate.selected_fields_invalid() || candidate.planning_request_id != request.request_id || candidate.model_projection_digest != request.model_projection_digest || candidate.model_source_documents != request.model_source_documents @@ -6157,11 +5255,14 @@ fn commit_repo_frontier_plan_decision_inner( let typed = result.frontier_plan_mind_decision()?.ok_or_else(|| { anyhow!("frontier plan decision requires a typed Mind decision") })?; + let launch = role_launch_document_for_job(&cache, &result.job_id)?; + let mind_request_id = launch + .frontier_plan_mind_context + .as_ref() + .map(|context| context.request.request_id.clone()) + .ok_or_else(|| anyhow!("Mind result lacks typed plan authority"))?; ( - result - .frontier_plan_mind_request_id - .clone() - .ok_or_else(|| anyhow!("Mind result lacks its plan request echo"))?, + mind_request_id, typed.decision, typed.rationale, typed.decided_at, @@ -6186,7 +5287,6 @@ fn commit_repo_frontier_plan_decision_inner( Sha256::digest(planning.request_id.as_bytes()) ); let receipt = RepoFrontierPlanDecisionReceipt { - schema_version: REPO_FRONTIER_PLAN_DECISION_RECEIPT_SCHEMA_VERSION.into(), decision_id: decision_id.clone(), planning_request_id: planning.request_id.clone(), candidate_id: candidate.candidate_id.clone(), @@ -6198,7 +5298,6 @@ fn commit_repo_frontier_plan_decision_inner( decision, rationale, decided_at: decided_at.clone(), - contract: REPO_FRONTIER_PLAN_DECISION_CONTRACT.into(), decision_source: decision_source.clone(), }; if let Some(existing) = cache.get::(&decision_id)? { @@ -6360,7 +5459,6 @@ pub fn select_and_commit_repo_frontier_route( Sha256::digest(route_seed.as_bytes()) ); let route = RepoFrontierRoute { - schema_version: REPO_FRONTIER_ROUTE_SCHEMA_VERSION.to_string(), route_id: route_id.clone(), next_organ: RepoFrontierNextOrgan::Hands, model_projection_digest: basis.projection_digest.clone(), @@ -6378,7 +5476,6 @@ pub fn select_and_commit_repo_frontier_route( .unwrap_or_else(|| item.repository_scope.clone()), adopted_plan: item.adopted_plan.clone(), selected_at: at.to_string(), - contract: REPO_FRONTIER_ROUTE_CONTRACT.to_string(), }; if let Some(existing) = cache.get::(&route_id)? { let mut retry = route.clone(); @@ -6448,8 +5545,8 @@ fn frontier_item_is_actionable_for_organ( /// admitted exactly once and contains an item the route committer can hand to /// Hands. Status projection must use this instead of assuming that a clear /// CRRC lane implies implementation authority. -pub(crate) fn runtime_has_actionable_hands_frontier(runtime_store: impl AsRef) -> Result { - runtime_has_actionable_frontier_for_organ(runtime_store, "Hands") +pub(crate) fn has_actionable_hands_frontier(cache: &CultCache) -> Result { + has_actionable_frontier_for_organ(cache, "Hands") } fn frontier_authority_documents( @@ -6568,9 +5665,7 @@ fn planning_claim_obligation_envelopes( fn validate_repo_frontier_research_request( request: &RepoFrontierResearchRequest, ) -> Result { - if request.schema_version != REPO_FRONTIER_RESEARCH_REQUEST_SCHEMA_VERSION - || request.contract != REPO_FRONTIER_RESEARCH_REQUEST_CONTRACT - || request.runtime_id.is_empty() + if request.runtime_id.is_empty() || chrono::DateTime::parse_from_rfc3339(&request.requested_at).is_err() || request.frontier_item_id.is_empty() || request.frontier_item_hash.is_empty() @@ -6701,8 +5796,7 @@ pub(crate) fn select_and_commit_repo_frontier_research_request( let (view, basis) = current_keyed_repo_model(&cache)?; let model = view.memory_context_projection(); let launches = cache.get_all::()?; - let packets = cache.get_all::()?; - let item = match next_repo_frontier_research_work(&cache, &model, &launches, &packets)? { + let item = match next_repo_frontier_research_work(&cache, &model, &launches)? { Some(NextRepoFrontierResearchWork::Existing(request)) => return Ok(request), Some(NextRepoFrontierResearchWork::Unrequested(item)) => item, None => { @@ -6779,7 +5873,6 @@ fn repo_frontier_research_request_for_admitted_item( )); } Ok(RepoFrontierResearchRequest { - schema_version: REPO_FRONTIER_RESEARCH_REQUEST_SCHEMA_VERSION.to_string(), request_id: crate::frontier_research_request_id(runtime_id, &item.id, &item_hash), model_projection_digest: basis.projection_digest.clone(), model_source_documents: basis.source_documents.clone(), @@ -6789,7 +5882,6 @@ fn repo_frontier_research_request_for_admitted_item( repository_scope: item.repository_scope.clone(), requested_at: requested_at.to_string(), runtime_id: runtime_id.to_string(), - contract: REPO_FRONTIER_RESEARCH_REQUEST_CONTRACT.to_string(), public_source_refs, }) } @@ -6844,25 +5936,20 @@ impl RepoFrontierResearchLifecycle { } /// Projects the next exact actionable Eyes frontier through request, worker, -/// review, and accepted-packet lifecycle. This is the launch-currency owner: +/// review, and accepted Mind-commit lifecycle. This is the launch-currency owner: /// an uncovered frontier is not launchable while its current attempt is still /// running or awaiting review. -pub(crate) fn runtime_repo_frontier_research_lifecycle( - runtime_store: impl AsRef, +pub(crate) fn repo_frontier_research_lifecycle( + cache: &CultCache, ) -> Result { - let runtime_store = runtime_store.as_ref(); - let mut cache = runtime_spine_cache(runtime_store)?; - cache.pull_all_backing_stores()?; - require_identity(&cache)?; + require_identity(cache)?; let launches = cache.get_all::()?; - let packets = cache.get_all::()?; let jobs = cache.get_all::()?; - let job_results = cache.get_all::()?; let role_results = cache.get_all::()?; - let (view, _basis) = current_keyed_repo_model(&cache)?; + let (view, _basis) = current_keyed_repo_model(cache)?; let model = view.memory_context_projection(); - let work = next_repo_frontier_research_work(&cache, &model, &launches, &packets)?; + let work = next_repo_frontier_research_work(cache, &model, &launches)?; let request = match work { Some(NextRepoFrontierResearchWork::Unrequested(item)) => { return Ok(RepoFrontierResearchLifecycle { @@ -6882,13 +5969,20 @@ pub(crate) fn runtime_repo_frontier_research_lifecycle( }); } }; - let mut attempts = launches + let attempts = launches .iter() - .filter(|launch| { - launch.repo_frontier_research_request_id.as_deref() == Some(request.request_id.as_str()) - }) .map(|launch| { - let carried_request = frontier_research_request_for_launch(&cache, launch)? + Ok( + crate::RuntimeTypedRequestRef::FrontierResearch(&request.request_id) + .matches_launch(launch)? + .then_some(launch), + ) + }) + .filter_map(Result::transpose) + .collect::>>()? + .into_iter() + .map(|launch| { + let carried_request = frontier_research_request_for_launch(cache, launch)? .ok_or_else(|| anyhow!("frontier Research launch lost its typed request"))?; if carried_request != request { return Err(anyhow!( @@ -6902,62 +5996,94 @@ pub(crate) fn runtime_repo_frontier_research_lifecycle( Ok((launch, job)) }) .collect::>>()?; - attempts.sort_by(|(_, left), (_, right)| { - left.created_at - .cmp(&right.created_at) - .then_with(|| left.job_id.cmp(&right.job_id)) - }); - let Some((launch, job)) = attempts.last() else { + if attempts.is_empty() { return Ok(RepoFrontierResearchLifecycle { stage: RepoFrontierResearchLifecycleStage::LaunchReady, frontier_item_id: Some(request.frontier_item_id.clone()), request_id: Some(request.request_id.clone()), worker_job_id: None, }); - }; - let stage = match job.status { - EpiphanyRuntimeJobStatus::Queued - | EpiphanyRuntimeJobStatus::Running - | EpiphanyRuntimeJobStatus::WaitingForReview => { - RepoFrontierResearchLifecycleStage::WorkerRunning - } - EpiphanyRuntimeJobStatus::Completed => { - let result = role_results - .iter() - .find(|result| result.job_id == job.job_id) - .ok_or_else(|| anyhow!("completed frontier Research job lost its typed result"))?; - if result.repo_frontier_research_request_id.as_deref() - != Some(request.request_id.as_str()) - || !result.role_id.eq_ignore_ascii_case("research") - { - return Err(anyhow!( - "frontier Research result crossed request-family authority" - )); + } + let live = attempts + .iter() + .filter(|(_, job)| job.status == EpiphanyRuntimeJobStatus::Queued) + .collect::>(); + if live.len() > 1 { + return Err(anyhow!( + "frontier Research request has multiple live worker attempts" + )); + } + if let Some((launch, _)) = live.first() { + return Ok(RepoFrontierResearchLifecycle { + stage: RepoFrontierResearchLifecycleStage::WorkerRunning, + frontier_item_id: Some(request.frontier_item_id.clone()), + request_id: Some(request.request_id.clone()), + worker_job_id: Some(launch.job_id.clone()), + }); + } + + let mut successful = Vec::new(); + for (launch, job) in &attempts { + match job.status { + EpiphanyRuntimeJobStatus::Completed => { + let result = role_results + .iter() + .find(|result| result.job_id == job.job_id) + .ok_or_else(|| { + anyhow!("completed frontier Research job lost its typed result") + })?; + if !result.role_id.eq_ignore_ascii_case("research") { + return Err(anyhow!( + "frontier Research result crossed request-family authority" + )); + } + if result.item_error.is_none() { + successful.push(*launch); + } } - if result.item_error.is_some() { - RepoFrontierResearchLifecycleStage::LaunchReady - } else { - RepoFrontierResearchLifecycleStage::ResultReady + EpiphanyRuntimeJobStatus::Failed => { + let claim = cache + .get::(&worker_process_claim_id( + &job.job_id, + ))? + .ok_or_else(|| { + anyhow!("failed frontier Research attempt lost its process claim") + })?; + let claim_status = WorkerProcessStatus::parse(&claim.status)?; + if !matches!( + claim_status, + WorkerProcessStatus::TerminalResult + | WorkerProcessStatus::TerminalFailure + | WorkerProcessStatus::TerminalDeath + ) || claim.terminal_authority_id.is_none() + { + return Err(anyhow!( + "failed frontier Research attempt lost its exact terminal authority" + )); + } } + EpiphanyRuntimeJobStatus::Queued => unreachable!("live attempts returned above"), } - EpiphanyRuntimeJobStatus::Failed | EpiphanyRuntimeJobStatus::Cancelled => { - let terminal = job_results - .iter() - .filter(|result| result.job_id == job.job_id) - .count(); - if terminal != 1 { - return Err(anyhow!( - "failed frontier Research attempt lost its exact terminal result" - )); - } - RepoFrontierResearchLifecycleStage::LaunchReady - } - }; + } + if successful.len() > 1 { + return Err(anyhow!( + "frontier Research request has multiple successful worker authorities" + )); + } + let (stage, worker_job_id) = successful.first().map_or( + (RepoFrontierResearchLifecycleStage::LaunchReady, None), + |launch| { + ( + RepoFrontierResearchLifecycleStage::ResultReady, + Some(launch.job_id.clone()), + ) + }, + ); Ok(RepoFrontierResearchLifecycle { stage, frontier_item_id: Some(request.frontier_item_id.clone()), request_id: Some(request.request_id.clone()), - worker_job_id: Some(launch.job_id.clone()), + worker_job_id, }) } @@ -6970,14 +6096,13 @@ fn next_repo_frontier_research_work( cache: &CultCache, model: &crate::EpiphanyMemoryGraphSnapshot, launches: &[EpiphanyRuntimeWorkerLaunchRequest], - packets: &[EyesEvidencePacket], ) -> Result> { let existing_actionable = actionable_repo_frontier_research_requests(cache)?; let mut existing_uncovered = existing_actionable .iter() .cloned() .filter_map(|request| { - match repo_frontier_research_request_is_covered(cache, &request, launches, packets) { + match repo_frontier_research_request_is_covered(cache, &request, launches) { Ok(false) => Some(Ok(request)), Ok(true) => None, Err(error) => Some(Err(error)), @@ -7009,11 +6134,12 @@ fn repo_frontier_research_request_is_covered( cache: &CultCache, request: &RepoFrontierResearchRequest, launches: &[EpiphanyRuntimeWorkerLaunchRequest], - packets: &[EyesEvidencePacket], ) -> Result { let mut matching_jobs = BTreeSet::new(); for launch in launches { - if launch.repo_frontier_research_request_id.as_deref() != Some(&request.request_id) { + if !crate::RuntimeTypedRequestRef::FrontierResearch(&request.request_id) + .matches_launch(launch)? + { continue; } let carried_request = frontier_research_request_for_launch(&cache, launch)? @@ -7025,32 +6151,26 @@ fn repo_frontier_research_request_is_covered( } matching_jobs.insert(launch.job_id.as_str()); } - for packet in packets - .iter() - .filter(|packet| packet.research_request_id == request.request_id) - { - let result = cache.get::(&packet.source_job_id)?; - let archived = cache.get::(&packet.source_job_id)?; - let exact_live = result.as_ref().is_some_and(|result| { - matching_jobs.contains(result.job_id.as_str()) - && result.result_id == packet.source_result_id - && result.decision_context_id == packet.decision_context_id - && result.repo_frontier_research_request_id.as_deref() - == Some(request.request_id.as_str()) - }); - let exact_archived = archived.as_ref().is_some_and(|attempt| { - attempt.request_kind == "frontier-research" - && attempt.request_id == request.request_id - && attempt.fulfilled_result_id() == Some(packet.source_result_id.as_str()) - && attempt.decision_context_id() == Some(packet.decision_context_id.as_str()) - && attempt.decision.as_ref().is_some_and(|decision| { - decision.role_result.as_ref().is_some_and(|result| { - result.repo_frontier_research_request_id.as_deref() - == Some(request.request_id.as_str()) - }) - }) - }); - if exact_live || exact_archived { + for job_id in matching_jobs { + if let Some(result) = cache.get::(job_id)? + && result.role_id.eq_ignore_ascii_case("research") + && result.item_error.is_none() + && worker_result_has_keyed_mind_commit(cache, &result)? + { + return Ok(true); + } + } + for attempt in cache.get_all::()? { + if attempt.request_kind == "frontier-research" + && attempt.request_id == request.request_id + && let Some(decision) = attempt.decision.as_ref() + && let Some(result) = decision.role_result.as_ref() + && result.role_id.eq_ignore_ascii_case("research") + && result.item_error.is_none() + && attempt.fulfilled_result_id() == Some(result.result_id.as_str()) + && attempt.decision_context_id() == Some(result.decision_context_id.as_str()) + && worker_result_has_keyed_mind_commit(cache, result)? + { return Ok(true); } } @@ -7077,9 +6197,7 @@ fn repo_frontier_research_request_is_actionable( cache: &CultCache, request: &RepoFrontierResearchRequest, ) -> Result { - if request.schema_version != REPO_FRONTIER_RESEARCH_REQUEST_SCHEMA_VERSION - || request.contract != REPO_FRONTIER_RESEARCH_REQUEST_CONTRACT - || request.repository_scope.is_empty() + if request.repository_scope.is_empty() || !crate::memory_graph::repo_paths_are_canonical_and_safe(&request.repository_scope) { return Err(anyhow!("invalid frontier Research request")); @@ -7101,8 +6219,7 @@ pub(crate) fn repo_frontier_planning_failure_review( let Some(review) = cache.get::(&review_id)? else { return Ok(None); }; - if review.schema_version != REPO_FRONTIER_PLANNING_FAILURE_REVIEW_SCHEMA_VERSION - || review.review_id != review_id + if review.review_id != review_id || review.planning_request_id != planning_request_id || review.pass_kind != pass_kind || review.job_id != result.job_id @@ -7123,7 +6240,9 @@ pub fn review_repo_frontier_planning_failure( chrono::DateTime::parse_from_rfc3339(reviewed_at) .map_err(|_| anyhow!("planning failure review time must be RFC3339"))?; let runtime_store = runtime_store.as_ref(); - let lifecycle = runtime_repo_frontier_planning_lifecycle(runtime_store)?; + let mut cache = runtime_spine_cache(runtime_store)?; + cache.pull_all_backing_stores()?; + let lifecycle = repo_frontier_planning_lifecycle(&cache)?; let (pass_kind, expected_job_id) = match lifecycle.stage { RepoFrontierPlanningLifecycleStage::ImaginationFailed => { ("imagination", lifecycle.imagination_job_id.as_deref()) @@ -7139,13 +6258,10 @@ pub fn review_repo_frontier_planning_failure( let planning_request_id = lifecycle .planning_request_id .ok_or_else(|| anyhow!("planning failure review lost its request"))?; - let mut cache = runtime_spine_cache(runtime_store)?; - cache.pull_all_backing_stores()?; let result = cache .get::(job_id)? .ok_or_else(|| anyhow!("planning failure review lost its typed result"))?; let review = RepoFrontierPlanningFailureReview { - schema_version: REPO_FRONTIER_PLANNING_FAILURE_REVIEW_SCHEMA_VERSION.into(), review_id: repo_frontier_planning_failure_review_id(&result.result_id), planning_request_id: planning_request_id.clone(), pass_kind: pass_kind.into(), @@ -7216,7 +6332,13 @@ pub fn runtime_repo_frontier_planning_lifecycle( let runtime_store = runtime_store.as_ref(); let mut cache = runtime_spine_cache(runtime_store)?; cache.pull_all_backing_stores()?; - require_identity(&cache)?; + repo_frontier_planning_lifecycle(&cache) +} + +pub(crate) fn repo_frontier_planning_lifecycle( + cache: &CultCache, +) -> Result { + require_identity(cache)?; let empty = |stage| RepoFrontierPlanningLifecycle { stage, planning_request_id: None, @@ -7265,7 +6387,7 @@ pub fn runtime_repo_frontier_planning_lifecycle( ..empty(RepoFrontierPlanningLifecycleStage::Terminal) }); } - if runtime_has_actionable_frontier_for_organ(runtime_store, "Imagination")? { + if has_actionable_frontier_for_organ(cache, "Imagination")? { return Ok(empty(RepoFrontierPlanningLifecycleStage::Ready)); } let latest_terminal = decisions.iter().max_by(|a, b| { @@ -7294,20 +6416,40 @@ pub fn runtime_repo_frontier_planning_lifecycle( decision_id: None, }; let mut imagination_launches = cache - .get_all::()? + .get_all::()? .into_iter() - .filter(|binding| binding.planning_request_id == request.request_id) - .collect::>(); - imagination_launches.sort_by_key(|binding| binding.attempt_ordinal); - for (expected, binding) in imagination_launches.iter().enumerate() { - if binding.attempt_ordinal != expected as u64 { + .map(|launch| { + let matches = match launch.launch_document()? { + EpiphanyWorkerLaunchDocument::Role(document) => document + .frontier_planning_context + .is_some_and(|context| context.request_id == request.request_id), + EpiphanyWorkerLaunchDocument::Reorient(_) => false, + }; + Ok(matches.then_some(launch)) + }) + .filter_map(Result::transpose) + .collect::>>()? + .into_iter() + .map(|launch| { + Ok(( + crate::current_work::frontier_planning_attempt_ordinal( + &request.request_id, + &launch.job_id, + )?, + launch, + )) + }) + .collect::>>()?; + imagination_launches.sort_by_key(|(ordinal, _)| *ordinal); + for (expected, (ordinal, _)) in imagination_launches.iter().enumerate() { + if *ordinal != expected { return Err(anyhow!( "Self found noncontiguous frontier planning attempt identity" )); } } - if let Some(binding) = imagination_launches.last() { - lifecycle.imagination_job_id = Some(binding.job_id.clone()); + if let Some((_, launch)) = imagination_launches.last() { + lifecycle.imagination_job_id = Some(launch.job_id.clone()); } let imagination_results = cache .get_all::()? @@ -7327,9 +6469,7 @@ pub fn runtime_repo_frontier_planning_lifecycle( }; lifecycle.imagination_job_id = Some(imagination_result.job_id.clone()); lifecycle.imagination_result_id = Some(imagination_result.result_id.clone()); - if imagination_result.frontier_planning_request_id.as_deref() - != Some(request.request_id.as_str()) - { + if imagination_result.item_error.is_some() { if !imagination_result .role_id .eq_ignore_ascii_case("imagination") @@ -7356,6 +6496,15 @@ pub fn runtime_repo_frontier_planning_lifecycle( }; return Ok(lifecycle); } + if !imagination_result + .role_id + .eq_ignore_ascii_case("imagination") + || imagination_result.frontier_plan_candidate_msgpack.is_none() + { + return Err(anyhow!( + "Self found malformed typed frontier planning result" + )); + } let mind_requests = cache .get_all::()? .into_iter() @@ -7374,18 +6523,38 @@ pub fn runtime_repo_frontier_planning_lifecycle( lifecycle.mind_request_id = Some(mind_request.request_id.clone()); lifecycle.stage = RepoFrontierPlanningLifecycleStage::MindLaunchReady; let mut mind_launches = cache - .get_all::()? + .get_all::()? .into_iter() - .filter(|binding| binding.mind_request_id == mind_request.request_id) - .collect::>(); - mind_launches.sort_by_key(|binding| binding.attempt_ordinal); - for (expected, binding) in mind_launches.iter().enumerate() { - if binding.attempt_ordinal != expected as u64 { + .map(|launch| { + let matches = match launch.launch_document()? { + EpiphanyWorkerLaunchDocument::Role(document) => document + .frontier_plan_mind_context + .is_some_and(|context| context.request.request_id == mind_request.request_id), + EpiphanyWorkerLaunchDocument::Reorient(_) => false, + }; + Ok(matches.then_some(launch)) + }) + .filter_map(Result::transpose) + .collect::>>()? + .into_iter() + .map(|launch| { + Ok(( + crate::current_work::frontier_plan_mind_attempt_ordinal( + &mind_request.request_id, + &launch.job_id, + )?, + launch, + )) + }) + .collect::>>()?; + mind_launches.sort_by_key(|(ordinal, _)| *ordinal); + for (expected, (ordinal, _)) in mind_launches.iter().enumerate() { + if *ordinal != expected { return Err(anyhow!("Self found noncontiguous Mind attempt identity")); } } - if let Some(binding) = mind_launches.last() { - lifecycle.mind_job_id = Some(binding.job_id.clone()); + if let Some((_, launch)) = mind_launches.last() { + lifecycle.mind_job_id = Some(launch.job_id.clone()); } let mind_results = cache .get_all::()? @@ -7405,9 +6574,7 @@ pub fn runtime_repo_frontier_planning_lifecycle( }; lifecycle.mind_job_id = Some(mind_result.job_id.clone()); lifecycle.mind_result_id = Some(mind_result.result_id.clone()); - if mind_result.frontier_plan_mind_request_id.as_deref() - != Some(mind_request.request_id.as_str()) - { + if mind_result.item_error.is_some() { if !mind_result .role_id .eq_ignore_ascii_case("mindAdmissionReview") @@ -7429,19 +6596,20 @@ pub fn runtime_repo_frontier_planning_lifecycle( }; return Ok(lifecycle); } + if !mind_result + .role_id + .eq_ignore_ascii_case("mindAdmissionReview") + || mind_result.frontier_plan_mind_decision_msgpack.is_none() + { + return Err(anyhow!("Self found malformed typed Mind result")); + } lifecycle.stage = RepoFrontierPlanningLifecycleStage::MindResultReady; Ok(lifecycle) } -fn runtime_has_actionable_frontier_for_organ( - runtime_store: impl AsRef, - organ: &str, -) -> Result { - let runtime_store = runtime_store.as_ref(); - let mut cache = runtime_spine_cache(runtime_store)?; - cache.pull_all_backing_stores()?; - require_identity(&cache)?; - let Ok((view, _basis)) = current_keyed_repo_model(&cache) else { +fn has_actionable_frontier_for_organ(cache: &CultCache, organ: &str) -> Result { + require_identity(cache)?; + let Ok((view, _basis)) = current_keyed_repo_model(cache) else { return Ok(false); }; let model = view.memory_context_projection(); @@ -7452,6 +6620,7 @@ pub fn put_runtime_reorient_worker_result( store_path: impl AsRef, result: &EpiphanyRuntimeReorientWorkerResult, ) -> Result<()> { + let store_path = store_path.as_ref(); validate_non_empty(&result.job_id, "reorient worker result job id")?; validate_non_empty(&result.result_id, "reorient worker result id")?; validate_non_empty(&result.mode, "reorient worker result mode")?; @@ -7462,8 +6631,91 @@ pub fn put_runtime_reorient_worker_result( let mut cache = runtime_spine_cache(store_path)?; cache.pull_all_backing_stores()?; require_worker_decision_context(&cache, &result.decision_context_id, &result.job_id)?; - cache.put(&result.job_id, result)?; - Ok(()) + let launch = cache + .get::(&result.job_id)? + .ok_or_else(|| anyhow!("reorientation result requires its immutable launch"))?; + if !matches!( + launch.launch_document()?, + EpiphanyWorkerLaunchDocument::Reorient(_) + ) { + return Err(anyhow!("reorientation result crossed a role launch")); + } + let (terminal_job_status, terminal_process_status) = + worker_outcome_status(result.item_error.as_deref())?; + let mut job = cache + .get::(&result.job_id)? + .ok_or_else(|| anyhow!("reorientation result requires its runtime job"))?; + let claim_id = worker_process_claim_id(&result.job_id); + let claim = cache.get::(&claim_id)?; + if let Some(existing) = cache.get::(&result.job_id)? { + if existing != *result + || job.status != terminal_job_status + || claim.as_ref().is_some_and(|claim| { + WorkerProcessStatus::parse(&claim.status).ok() != Some(terminal_process_status) + || claim.terminal_authority_id.as_deref() != Some(result.result_id.as_str()) + }) + { + return Err(anyhow!("reorientation result replay was substituted")); + } + return Ok(()); + } + if job.status != EpiphanyRuntimeJobStatus::Queued { + return Err(anyhow!( + "reorientation result requires its queued runtime job" + )); + } + let job_envelope = cache + .get_envelope::(&result.job_id)? + .ok_or_else(|| anyhow!("reorientation result lost its job envelope"))?; + job.status = terminal_job_status.clone(); + let mut expected = vec![job_envelope]; + let mut writes = vec![ + cache.prepare_entry(&result.job_id, result)?.0, + cache.prepare_entry(&result.job_id, &job)?.0, + ]; + if let Some(claim) = claim { + if WorkerProcessStatus::parse(&claim.status)? != WorkerProcessStatus::Active { + return Err(anyhow!( + "reorientation result requires its active process claim" + )); + } + let claim_envelope = cache + .get_envelope::(&claim_id)? + .ok_or_else(|| anyhow!("reorientation result lost its claim envelope"))?; + let mut terminal = claim; + terminal.status = terminal_process_status.as_str().into(); + terminal.terminal_at = Some(chrono::Utc::now().to_rfc3339()); + terminal.terminal_authority_id = Some(result.result_id.clone()); + expected.push(claim_envelope); + writes.push(cache.prepare_entry(&claim_id, &terminal)?.0); + } + if SingleFileMessagePackBackingStore::new(store_path) + .compare_and_swap_batch(&expected, writes)? + { + return Ok(()); + } + let mut reloaded = runtime_spine_cache(store_path)?; + reloaded.pull_all_backing_stores()?; + if reloaded + .get::(&result.job_id)? + .is_some_and(|existing| existing == *result) + && reloaded + .get::(&result.job_id)? + .is_some_and(|job| job.status == terminal_job_status) + && reloaded + .get::(&claim_id)? + .is_none_or(|claim| { + WorkerProcessStatus::parse(&claim.status) + .is_ok_and(|status| status == terminal_process_status) + && claim.terminal_authority_id.as_deref() == Some(result.result_id.as_str()) + }) + { + Ok(()) + } else { + Err(anyhow!( + "reorientation result lost immutable insertion to a different outcome" + )) + } } fn require_worker_decision_context( @@ -7486,7 +6738,7 @@ fn require_worker_decision_context( Ok(()) } -pub(crate) fn runtime_reorient_worker_result( +pub fn runtime_reorient_worker_result( store_path: impl AsRef, job_id: &str, ) -> Result> { @@ -7549,24 +6801,13 @@ pub fn put_hands_action_intent( ) -> Result<()> { validate_non_empty(&intent.intent_id, "Hands action intent id")?; validate_non_empty(&intent.runtime_job_id, "Hands action runtime job")?; - validate_non_empty(&intent.binding_id, "Hands action binding")?; - validate_non_empty(&intent.role, "Hands action role")?; - validate_non_empty(&intent.authority_scope, "Hands action authority scope")?; - validate_non_empty(&intent.requested_action, "Hands requested action")?; validate_non_empty( &intent.substrate_gate_grant_receipt_id, "Hands Substrate Gate grant receipt", )?; - validate_non_empty(&intent.requested_at, "Hands action requested timestamp")?; if intent.requested_paths.is_empty() { return Err(anyhow!("Hands action intent must name requested paths")); } - if intent.schema_version != HANDS_ACTION_INTENT_SCHEMA_VERSION - || chrono::DateTime::parse_from_rfc3339(&intent.requested_at).is_err() - || intent.contract.trim().is_empty() - { - return Err(anyhow!("invalid Hands action intent contract")); - } let store_path = store_path.as_ref(); let mut cache = runtime_spine_cache(store_path)?; cache.pull_all_backing_stores()?; @@ -7577,9 +6818,6 @@ pub fn put_hands_action_intent( anyhow!("Hands action intent requires its persisted Substrate Gate grant") })?; if grant.runtime_job_id != intent.runtime_job_id - || grant.binding_id != intent.binding_id - || grant.role != intent.role - || grant.authority_scope != intent.authority_scope || !grant .granted_operations .iter() @@ -7609,40 +6847,6 @@ pub fn put_hands_action_intent( } } -pub fn put_hands_action_review( - store_path: impl AsRef, - review: &HandsActionReview, -) -> Result<()> { - validate_non_empty(&review.review_id, "Hands action review id")?; - validate_non_empty(&review.intent_id, "Hands action review intent")?; - validate_non_empty(&review.decision, "Hands action review decision")?; - validate_non_empty(&review.reviewed_at, "Hands action review timestamp")?; - if review.allowed_operations.is_empty() { - return Err(anyhow!("Hands action review must name allowed operations")); - } - if review.schema_version != HANDS_ACTION_REVIEW_SCHEMA_VERSION - || chrono::DateTime::parse_from_rfc3339(&review.reviewed_at).is_err() - || review.contract.trim().is_empty() - { - return Err(anyhow!("invalid Hands action review contract")); - } - let store_path = store_path.as_ref(); - let mut cache = runtime_spine_cache(store_path)?; - cache.pull_all_backing_stores()?; - require_identity(&cache)?; - let (envelope, _) = cache.prepare_entry(&review.review_id, review)?; - let backing = SingleFileMessagePackBackingStore::new(store_path); - if backing.compare_and_swap_batch(&[], vec![envelope])? { - return Ok(()); - } - let mut reloaded = runtime_spine_cache(store_path)?; - reloaded.pull_all_backing_stores()?; - match reloaded.get::(&review.review_id)? { - Some(existing) if existing == *review => Ok(()), - _ => Err(anyhow!("Hands action review ids are immutable")), - } -} - fn validate_repo_frontier_hands_authority_chain( cache: &CultCache, authority: &RepoFrontierHandsAuthority, @@ -7676,82 +6880,28 @@ fn validate_repo_frontier_hands_authority_chain( let intent = cache .get::(&authority.hands_intent_id)? .ok_or_else(|| anyhow!("Hands authority requires its persisted intent"))?; - let review = cache - .get::(&authority.hands_review_id)? - .ok_or_else(|| anyhow!("Hands authority requires its persisted review"))?; let grant = cache .get::(&authority.substrate_grant_receipt_id)? .ok_or_else(|| anyhow!("Hands authority requires its persisted Substrate grant"))?; - let within_scope = authority.requested_paths.iter().all(|path| { - route.authorized_paths.iter().any(|scope| { - path == scope || path.starts_with(&format!("{}/", scope.trim_end_matches(['/', '\\']))) - }) - }); - let requested_operations: &[&str] = match intent.requested_action.as_str() { - "patch" => &["patch"], - "continueImplementation" => &["patch", "command", "commit"], - _ => { - return Err(anyhow!( - "Hands authority names an unsupported requested action" - )); - } - }; - let adopted_plan_binding_is_exact = match route.adopted_plan.as_ref() { - Some(plan) => { - intent.frontier_route_id == route.route_id - && intent.plan_candidate_sha256 == plan.candidate_sha256 - && intent.plan_action == plan.effective_action() - } - None => { - intent.frontier_route_id.is_empty() - && intent.plan_candidate_sha256.is_empty() - && intent.plan_action.is_empty() - } - }; - if route.schema_version != REPO_FRONTIER_ROUTE_SCHEMA_VERSION - || route.contract != REPO_FRONTIER_ROUTE_CONTRACT - || intent.schema_version != HANDS_ACTION_INTENT_SCHEMA_VERSION - || review.schema_version != HANDS_ACTION_REVIEW_SCHEMA_VERSION - || grant.schema_version != SUBSTRATE_GATE_REPO_ACCESS_GRANT_RECEIPT_SCHEMA_VERSION - || intent.contract.trim().is_empty() - || review.contract.trim().is_empty() + if grant.schema_version != SUBSTRATE_GATE_REPO_ACCESS_GRANT_RECEIPT_SCHEMA_VERSION || grant.contract.trim().is_empty() - || chrono::DateTime::parse_from_rfc3339(&intent.requested_at).is_err() - || chrono::DateTime::parse_from_rfc3339(&review.reviewed_at).is_err() || chrono::DateTime::parse_from_rfc3339(&grant.granted_at).is_err() || route.next_organ != RepoFrontierNextOrgan::Hands - || authority.model_projection_digest != route.model_projection_digest - || authority.model_source_documents != route.model_source_documents - || authority.frontier_item_id != route.frontier_item_id - || authority.frontier_item_hash != route.frontier_item_hash || current_item.adopted_plan != route.adopted_plan || route .adopted_plan .as_ref() .is_some_and(|plan| route.authorized_paths != plan.safe_paths) - || !adopted_plan_binding_is_exact - || review.intent_id != intent.intent_id - || review.decision != "approved" - || !requested_operations.iter().all(|required| { - review - .allowed_operations - .iter() - .any(|operation| operation == required) - }) || intent.substrate_gate_grant_receipt_id != grant.receipt_id || grant.runtime_job_id != intent.runtime_job_id - || grant.binding_id != intent.binding_id - || grant.role != intent.role - || grant.authority_scope != intent.authority_scope - || !requested_operations.iter().all(|required| { + || !["patch", "command", "commit"].iter().all(|required| { grant .granted_operations .iter() .any(|operation| operation == required) }) - || authority.requested_paths != intent.requested_paths - || authority.requested_paths != grant.granted_paths - || !within_scope + || intent.requested_paths != grant.granted_paths + || intent.requested_paths != route.authorized_paths { return Err(anyhow!( "repo frontier Hands authority chain violates its full authority contract" @@ -7765,11 +6915,14 @@ pub fn put_repo_frontier_hands_authority( authority: &RepoFrontierHandsAuthority, ) -> Result<()> { let store_path = store_path.as_ref(); - if authority.schema_version != REPO_FRONTIER_HANDS_AUTHORITY_SCHEMA_VERSION - || authority.contract != REPO_FRONTIER_HANDS_AUTHORITY_CONTRACT - || chrono::DateTime::parse_from_rfc3339(&authority.granted_at).is_err() - || !crate::memory_graph::repo_paths_are_canonical_and_safe(&authority.requested_paths) - || authority.requested_paths.is_empty() + if [ + authority.authority_id.as_str(), + authority.route_id.as_str(), + authority.hands_intent_id.as_str(), + authority.substrate_grant_receipt_id.as_str(), + ] + .into_iter() + .any(str::is_empty) { return Err(anyhow!("invalid repo frontier Hands authority contract")); } @@ -7783,42 +6936,11 @@ pub fn put_repo_frontier_hands_authority( let intent = cache .get::(&authority.hands_intent_id)? .ok_or_else(|| anyhow!("repo frontier Hands authority requires its persisted intent"))?; - let review = cache - .get::(&authority.hands_review_id)? - .ok_or_else(|| anyhow!("repo frontier Hands authority requires its persisted review"))?; let grant = cache .get::(&authority.substrate_grant_receipt_id)? .ok_or_else(|| { anyhow!("repo frontier Hands authority requires its persisted Substrate grant") })?; - let within_scope = authority.requested_paths.iter().all(|path| { - route.authorized_paths.iter().any(|scope| { - path == scope || path.starts_with(&format!("{}/", scope.trim_end_matches(['/', '\\']))) - }) - }); - if route.schema_version != REPO_FRONTIER_ROUTE_SCHEMA_VERSION - || route.contract != REPO_FRONTIER_ROUTE_CONTRACT - || route.next_organ != RepoFrontierNextOrgan::Hands - || authority.route_id != route.route_id - || authority.model_projection_digest != route.model_projection_digest - || authority.model_source_documents != route.model_source_documents - || authority.frontier_item_id != route.frontier_item_id - || authority.frontier_item_hash != route.frontier_item_hash - || review.intent_id != intent.intent_id - || review.decision != "approved" - || intent.substrate_gate_grant_receipt_id != grant.receipt_id - || grant.runtime_job_id != intent.runtime_job_id - || grant.binding_id != intent.binding_id - || grant.role != intent.role - || grant.authority_scope != intent.authority_scope - || authority.requested_paths != intent.requested_paths - || authority.requested_paths != grant.granted_paths - || !within_scope - { - return Err(anyhow!( - "repo frontier Hands authority does not exactly bind route, model, intent, review, grant, and scope" - )); - } let (envelope, _) = cache.prepare_entry(&authority.authority_id, authority)?; let backing = SingleFileMessagePackBackingStore::new(store_path); let expected = vec![ @@ -7828,14 +6950,11 @@ pub fn put_repo_frontier_hands_authority( cache .get_envelope::(&intent.intent_id)? .ok_or_else(|| anyhow!("repo frontier Hands authority lost its intent envelope"))?, - cache - .get_envelope::(&review.review_id)? - .ok_or_else(|| anyhow!("repo frontier Hands authority lost its review envelope"))?, cache .get_envelope::(&grant.receipt_id)? .ok_or_else(|| anyhow!("repo frontier Hands authority lost its grant envelope"))?, cache - .get_envelope::(&authority.frontier_item_id)? + .get_envelope::(&route.frontier_item_id)? .ok_or_else(|| anyhow!("repo frontier Hands authority lost its frontier envelope"))?, ]; let mut writes = expected.clone(); @@ -7856,54 +6975,52 @@ fn worker_result_has_keyed_mind_commit( result: &EpiphanyRuntimeRoleWorkerResult, ) -> Result { require_worker_decision_context(cache, &result.decision_context_id, &result.job_id)?; - Ok(cache - .get_all::()? - .into_iter() - .any(|receipt| { - matches!( - receipt.authority, - crate::EpiphanyMindCommitAuthority::ModelDecisionContext { - ref decision_context_id - } if decision_context_id == &result.decision_context_id - ) && if result.role_id.eq_ignore_ascii_case("modeling") { - receipt.invariant_owner.starts_with("Modeling.") - && receipt - .writes - .iter() - .any(|write| write.document_type.starts_with("epiphany.mind.repo_model.")) - } else if result.role_id.eq_ignore_ascii_case("research") { - receipt.invariant_owner == "Eyes.frontier_research" - && receipt.writes.iter().any(|write| { - matches!( - write.document_type.as_str(), - crate::EpiphanyMindEvidenceDocument::TYPE - | crate::EpiphanyMindObservationDocument::TYPE - ) - }) - } else if result.role_id.eq_ignore_ascii_case("verification") { - receipt.invariant_owner == "Soul.verification" - && receipt.writes.iter().any(|write| { - write.document_type == crate::EpiphanyMindVerificationAuditDocument::TYPE - }) - } else { - false - } - })) + for receipt in cache.get_all::()? { + receipt.validate()?; + if matches!( + receipt.authority, + crate::EpiphanyMindCommitAuthority::ModelDecisionContext { + ref decision_context_id + } if decision_context_id == &result.decision_context_id + ) && if result.role_id.eq_ignore_ascii_case("modeling") { + receipt.invariant_owner.starts_with("Modeling.") + && receipt + .writes + .iter() + .any(|write| write.document_type.starts_with("epiphany.mind.repo_model.")) + } else if result.role_id.eq_ignore_ascii_case("research") { + receipt.invariant_owner == "Eyes.frontier_research" + && receipt.writes.iter().any(|write| { + matches!( + write.document_type.as_str(), + crate::EpiphanyMindEvidenceDocument::TYPE + | crate::EpiphanyMindObservationDocument::TYPE + ) + }) + } else if result.role_id.eq_ignore_ascii_case("verification") { + receipt.invariant_owner == "Soul.verification" + && receipt.writes.iter().any(|write| { + write.document_type == crate::EpiphanyMindVerificationAuditDocument::TYPE + }) + } else { + false + } { + return Ok(true); + } + } + Ok(false) } pub(crate) fn validate_repo_frontier_verification_request_intrinsic( request: &RepoFrontierVerificationRequest, ) -> Result { - if request.schema_version != REPO_FRONTIER_VERIFICATION_REQUEST_SCHEMA_VERSION - || request.contract != REPO_FRONTIER_VERIFICATION_REQUEST_CONTRACT - || chrono::DateTime::parse_from_rfc3339(&request.requested_at).is_err() + if chrono::DateTime::parse_from_rfc3339(&request.requested_at).is_err() || [ request.request_id.as_str(), request.route_id.as_str(), request.frontier_item_id.as_str(), request.frontier_item_hash.as_str(), request.hands_intent_id.as_str(), - request.hands_review_id.as_str(), request.hands_patch_receipt_id.as_str(), request.hands_command_receipt_id.as_str(), request.hands_commit_receipt_id.as_str(), @@ -7990,7 +7107,6 @@ fn repo_frontier_verification_context_with_commit( .filter(|authority| { authority.route_id == request.route_id && authority.hands_intent_id == request.hands_intent_id - && authority.hands_review_id == request.hands_review_id }) .collect::>(); let [hands_authority] = authorities.as_slice() else { @@ -8002,9 +7118,6 @@ fn repo_frontier_verification_context_with_commit( let hands_intent = cache .get::(&request.hands_intent_id)? .ok_or_else(|| anyhow!("Verification context lost its Hands intent"))?; - let hands_review = cache - .get::(&request.hands_review_id)? - .ok_or_else(|| anyhow!("Verification context lost its Hands review"))?; let patch_receipt = cache .get::(&request.hands_patch_receipt_id)? .ok_or_else(|| anyhow!("Verification context lost its patch receipt"))?; @@ -8014,8 +7127,7 @@ fn repo_frontier_verification_context_with_commit( let commit_receipt = match prospective_commit { Some(receipt) if receipt.receipt_id == request.hands_commit_receipt_id - && receipt.intent_id == request.hands_intent_id - && receipt.review_id == request.hands_review_id => + && receipt.intent_id == request.hands_intent_id => { receipt.clone() } @@ -8034,7 +7146,6 @@ fn repo_frontier_verification_context_with_commit( route, hands_authority: hands_authority.clone(), hands_intent, - hands_review, patch_receipt, command_receipt, commit_receipt, @@ -8047,8 +7158,7 @@ pub(crate) fn derive_repo_frontier_modeling_request( verdict: &SoulVerdictReceipt, ) -> Result { let identity = require_identity(cache)?; - if verdict.schema_version != SOUL_VERDICT_RECEIPT_SCHEMA_VERSION - || verdict.receipt_id.trim().is_empty() + if verdict.receipt_id.trim().is_empty() || verdict.source_result_id.trim().is_empty() || verdict.source_job_id.trim().is_empty() || chrono::DateTime::parse_from_rfc3339(&verdict.emitted_at).is_err() @@ -8063,6 +7173,13 @@ pub(crate) fn derive_repo_frontier_modeling_request( let verification_request = cache .get::(&verdict.verification_request_id)? .ok_or_else(|| anyhow!("frontier Modeling request requires the exact Soul request"))?; + let verification_launch = cache + .get::(&result.job_id)? + .ok_or_else(|| anyhow!("frontier Modeling request lost the Verification launch"))?; + let launched_verification_request = + frontier_verification_request_for_launch(&cache, &verification_launch)?.ok_or_else( + || anyhow!("frontier Modeling request lost typed Verification authority"), + )?; let route = cache .get::(&verdict.frontier_route_id)? .ok_or_else(|| anyhow!("frontier Modeling request requires the exact frontier route"))?; @@ -8086,20 +7203,15 @@ pub(crate) fn derive_repo_frontier_modeling_request( "needs-review" | "needs-evidence" | "fail" => RepoFrontierVerdictDisposition::Blocked, _ => return Err(anyhow!("Soul verdict has no allowed frontier disposition")), }; - if result.schema_version != RUNTIME_ROLE_WORKER_RESULT_SCHEMA_VERSION - || !result.role_id.eq_ignore_ascii_case("verification") + if !result.role_id.eq_ignore_ascii_case("verification") || result.item_error.is_some() || result.result_id != verdict.source_result_id || result.job_id != verdict.source_job_id - || result.verification_request_id.as_deref() - != Some(verification_request.request_id.as_str()) - || result.frontier_route_id.as_deref() != Some(route.route_id.as_str()) + || launched_verification_request != verification_request || verdict.verdict != result.verdict || verdict.summary != result.summary || verdict.risks != result.risks || verdict_evidence != result_evidence - || verification_request.schema_version != REPO_FRONTIER_VERIFICATION_REQUEST_SCHEMA_VERSION - || verification_request.contract != REPO_FRONTIER_VERIFICATION_REQUEST_CONTRACT || verification_request.route_id != route.route_id || verification_request.model_projection_digest != route.model_projection_digest || verification_request.model_source_documents != route.model_source_documents @@ -8120,7 +7232,6 @@ pub(crate) fn derive_repo_frontier_modeling_request( &route.route_id, ); let request = RepoFrontierModelingRequest { - schema_version: REPO_FRONTIER_MODELING_REQUEST_SCHEMA_VERSION.to_string(), request_id: request_id.clone(), model_projection_digest: route.model_projection_digest.clone(), model_source_documents: route.model_source_documents.clone(), @@ -8133,7 +7244,6 @@ pub(crate) fn derive_repo_frontier_modeling_request( verification_job_id: result.job_id.clone(), allowed_disposition: disposition, requested_at: verdict.emitted_at.clone(), - contract: REPO_FRONTIER_MODELING_REQUEST_CONTRACT.to_string(), }; Ok(request) } @@ -8153,46 +7263,6 @@ pub(crate) fn validate_repo_frontier_modeling_request( Ok(()) } -pub fn commit_repo_frontier_modeling_request( - store_path: impl AsRef, - verdict: &SoulVerdictReceipt, -) -> Result { - let store_path = store_path.as_ref(); - let mut cache = runtime_spine_cache(store_path)?; - cache.pull_all_backing_stores()?; - let persisted = cache - .get::(&verdict.receipt_id)? - .ok_or_else(|| anyhow!("frontier Modeling request requires its persisted Soul verdict"))?; - if persisted != *verdict { - return Err(anyhow!("frontier Modeling request Soul verdict changed")); - } - let request = derive_repo_frontier_modeling_request(&cache, verdict)?; - let request_id = request.request_id.clone(); - let (envelope, _) = cache.prepare_entry(&request_id, &request)?; - let verdict_envelope = cache - .get_envelope::(&verdict.receipt_id)? - .ok_or_else(|| anyhow!("frontier Modeling request lost its Soul verdict envelope"))?; - let frontier_envelope = cache - .get_envelope::(&request.frontier_item_id)? - .ok_or_else(|| anyhow!("frontier Modeling request lost its frontier envelope"))?; - let expected = vec![verdict_envelope, frontier_envelope]; - let mut writes = expected.clone(); - writes.push(envelope); - if SingleFileMessagePackBackingStore::new(store_path) - .compare_and_swap_batch(&expected, writes)? - { - return Ok(request); - } - let mut reloaded = runtime_spine_cache(store_path)?; - reloaded.pull_all_backing_stores()?; - match reloaded.get::(&request_id)? { - Some(existing) if existing == request => Ok(existing), - _ => Err(anyhow!( - "frontier Modeling request deterministic identity collision" - )), - } -} - fn derive_repo_frontier_verification_request_for_chain( cache: &CultCache, patch: &HandsPatchReceipt, @@ -8202,9 +7272,7 @@ fn derive_repo_frontier_verification_request_for_chain( let authorities = cache .get_all::()? .into_iter() - .filter(|value| { - value.hands_intent_id == commit.intent_id && value.hands_review_id == commit.review_id - }) + .filter(|value| value.hands_intent_id == commit.intent_id) .collect::>(); if authorities.len() != 1 { return Err(anyhow!( @@ -8212,8 +7280,11 @@ fn derive_repo_frontier_verification_request_for_chain( )); } let authority = &authorities[0]; + let route = cache + .get::(&authority.route_id)? + .ok_or_else(|| anyhow!("Hands authority lost its exact route"))?; let frontier_envelope = cache - .get_envelope::(&authority.frontier_item_id)? + .get_envelope::(&route.frontier_item_id)? .ok_or_else(|| anyhow!("Hands authority lost its exact frontier document"))?; let frontier_authority_documents = vec![crate::EpiphanyMindDocumentVersion::from_envelope( "epiphany-mind", @@ -8228,20 +7299,17 @@ fn derive_repo_frontier_verification_request_for_chain( .map(|existing| existing.requested_at) .unwrap_or_else(|| commit.emitted_at.clone()); let request = RepoFrontierVerificationRequest { - schema_version: REPO_FRONTIER_VERIFICATION_REQUEST_SCHEMA_VERSION.to_string(), request_id, route_id: authority.route_id.clone(), - model_projection_digest: authority.model_projection_digest.clone(), - model_source_documents: authority.model_source_documents.clone(), - frontier_item_id: authority.frontier_item_id.clone(), - frontier_item_hash: authority.frontier_item_hash.clone(), + model_projection_digest: route.model_projection_digest.clone(), + model_source_documents: route.model_source_documents.clone(), + frontier_item_id: route.frontier_item_id.clone(), + frontier_item_hash: route.frontier_item_hash.clone(), hands_intent_id: commit.intent_id.clone(), - hands_review_id: commit.review_id.clone(), hands_patch_receipt_id: patch.receipt_id.clone(), hands_command_receipt_id: command.receipt_id.clone(), hands_commit_receipt_id: commit.receipt_id.clone(), requested_at, - contract: REPO_FRONTIER_VERIFICATION_REQUEST_CONTRACT.to_string(), frontier_authority_documents, }; Ok(request) @@ -8250,11 +7318,8 @@ fn derive_repo_frontier_verification_request_for_chain( fn validate_hands_consequence_grant( store_path: &Path, intent_id: &str, - review_id: &str, - runtime_job_id: &str, operation: &str, changed_paths: &[String], - stated_grant_id: Option<&str>, stated_command: Option<&str>, ) -> Result<()> { let mut cache = runtime_spine_cache(store_path)?; @@ -8263,9 +7328,6 @@ fn validate_hands_consequence_grant( let intent = cache .get::(intent_id)? .ok_or_else(|| anyhow!("Hands consequence requires its persisted intent"))?; - let review = cache - .get::(review_id)? - .ok_or_else(|| anyhow!("Hands consequence requires its persisted review"))?; let grant = cache .get::(&intent.substrate_gate_grant_receipt_id)? .ok_or_else(|| anyhow!("Hands consequence requires its persisted Substrate Gate grant"))?; @@ -8285,8 +7347,8 @@ fn validate_hands_consequence_grant( .ok_or_else(|| anyhow!("Hands consequence requires its persisted repo frontier route"))?; require_keyed_repo_model_basis( &cache, - &authority.model_projection_digest, - &authority.model_source_documents, + &route.model_projection_digest, + &route.model_source_documents, )?; let paths_covered = changed_paths.iter().all(|path| { grant.granted_paths.iter().any(|granted| { @@ -8295,48 +7357,31 @@ fn validate_hands_consequence_grant( || path.starts_with(&format!("{}/", granted.trim_end_matches(['/', '\\']))) }) }); - if intent.runtime_job_id != runtime_job_id - || review.intent_id != intent.intent_id - || review.decision != "approved" - || !review - .allowed_operations - .iter() - .any(|allowed| allowed == operation) - || grant.runtime_job_id != intent.runtime_job_id - || grant.binding_id != intent.binding_id - || grant.role != intent.role - || grant.authority_scope != intent.authority_scope + if grant.runtime_job_id != intent.runtime_job_id || !grant .granted_operations .iter() .any(|allowed| allowed == operation) - || stated_grant_id.is_some_and(|id| id != grant.receipt_id) || stated_command.is_some_and(|command| { route .adopted_plan .as_ref() - .is_some_and(|plan| command != plan.effective_command()) + .is_some_and(|plan| command != plan.command.as_str()) }) || !paths_covered - || authority.schema_version != REPO_FRONTIER_HANDS_AUTHORITY_SCHEMA_VERSION - || authority.contract != REPO_FRONTIER_HANDS_AUTHORITY_CONTRACT - || authority.hands_review_id != review.review_id || authority.substrate_grant_receipt_id != grant.receipt_id - || authority.requested_paths != intent.requested_paths || authority.route_id != route.route_id - || authority.model_projection_digest != route.model_projection_digest - || authority.model_source_documents != route.model_source_documents - || authority.frontier_item_id != route.frontier_item_id - || authority.frontier_item_hash != route.frontier_item_hash + || authority.hands_intent_id != intent.intent_id + || intent.requested_paths != route.authorized_paths || !changed_paths.iter().all(|path| { - authority.requested_paths.iter().any(|scope| { + intent.requested_paths.iter().any(|scope| { path == scope || path.starts_with(&format!("{}/", scope.trim_end_matches(['/', '\\']))) }) }) { return Err(anyhow!( - "Hands consequence does not match its approved review and Substrate Gate grant" + "Hands consequence does not match its route, intent, and Substrate Gate grant" )); } Ok(()) @@ -8349,12 +7394,6 @@ pub fn put_hands_patch_receipt( let store_path = store_path.as_ref(); validate_non_empty(&receipt.receipt_id, "Hands patch receipt id")?; validate_non_empty(&receipt.intent_id, "Hands patch intent")?; - validate_non_empty(&receipt.review_id, "Hands patch review")?; - validate_non_empty( - &receipt.substrate_gate_grant_receipt_id, - "Hands patch Substrate Gate grant receipt", - )?; - validate_non_empty(&receipt.runtime_job_id, "Hands patch runtime job")?; validate_non_empty(&receipt.summary, "Hands patch summary")?; validate_non_empty(&receipt.emitted_at, "Hands patch timestamp")?; if receipt.changed_paths.is_empty() { @@ -8363,11 +7402,8 @@ pub fn put_hands_patch_receipt( validate_hands_consequence_grant( store_path.as_ref(), &receipt.intent_id, - &receipt.review_id, - &receipt.runtime_job_id, "patch", &receipt.changed_paths, - Some(&receipt.substrate_gate_grant_receipt_id), None, )?; let mut cache = runtime_spine_cache(store_path)?; @@ -8387,16 +7423,6 @@ pub fn put_hands_patch_receipt( } } -pub fn runtime_hands_patch_receipt( - store_path: impl AsRef, - receipt_id: &str, -) -> Result> { - validate_non_empty(receipt_id, "Hands patch receipt id")?; - let mut cache = runtime_spine_cache(store_path)?; - cache.pull_all_backing_stores()?; - cache.get::(receipt_id) -} - pub fn put_hands_command_receipt( store_path: impl AsRef, receipt: &HandsCommandReceipt, @@ -8404,23 +7430,14 @@ pub fn put_hands_command_receipt( let store_path = store_path.as_ref(); validate_non_empty(&receipt.receipt_id, "Hands command receipt id")?; validate_non_empty(&receipt.intent_id, "Hands command intent")?; - validate_non_empty(&receipt.review_id, "Hands command review")?; - validate_non_empty( - &receipt.substrate_gate_grant_receipt_id, - "Hands command Substrate Gate grant receipt", - )?; - validate_non_empty(&receipt.runtime_job_id, "Hands command runtime job")?; validate_non_empty(&receipt.command, "Hands command")?; validate_non_empty(&receipt.exit_code, "Hands command exit code")?; validate_non_empty(&receipt.emitted_at, "Hands command timestamp")?; validate_hands_consequence_grant( store_path.as_ref(), &receipt.intent_id, - &receipt.review_id, - &receipt.runtime_job_id, "command", &[], - Some(&receipt.substrate_gate_grant_receipt_id), Some(&receipt.command), )?; let mut cache = runtime_spine_cache(store_path)?; @@ -8440,16 +7457,6 @@ pub fn put_hands_command_receipt( } } -pub fn runtime_hands_command_receipt( - store_path: impl AsRef, - receipt_id: &str, -) -> Result> { - validate_non_empty(receipt_id, "Hands command receipt id")?; - let mut cache = runtime_spine_cache(store_path)?; - cache.pull_all_backing_stores()?; - cache.get::(receipt_id) -} - pub fn put_hands_commit_receipt( store_path: impl AsRef, receipt: &HandsCommitReceipt, @@ -8457,8 +7464,6 @@ pub fn put_hands_commit_receipt( let store_path = store_path.as_ref(); validate_non_empty(&receipt.receipt_id, "Hands commit receipt id")?; validate_non_empty(&receipt.intent_id, "Hands commit intent")?; - validate_non_empty(&receipt.review_id, "Hands commit review")?; - validate_non_empty(&receipt.runtime_job_id, "Hands commit runtime job")?; validate_non_empty(&receipt.commit_sha, "Hands commit sha")?; validate_non_empty(&receipt.branch, "Hands commit branch")?; validate_non_empty(&receipt.summary, "Hands commit summary")?; @@ -8469,41 +7474,33 @@ pub fn put_hands_commit_receipt( validate_hands_consequence_grant( store_path.as_ref(), &receipt.intent_id, - &receipt.review_id, - &receipt.runtime_job_id, "commit", &receipt.changed_paths, None, - None, )?; let mut cache = runtime_spine_cache(store_path)?; cache.pull_all_backing_stores()?; require_identity(&cache)?; - let patch = cache + let patches = cache .get_all::()? .into_iter() - .filter(|patch| { - patch.intent_id == receipt.intent_id - && patch.review_id == receipt.review_id - && patch.runtime_job_id == receipt.runtime_job_id - && patch.emitted_at <= receipt.emitted_at - }) - .max_by(|left, right| left.emitted_at.cmp(&right.emitted_at)) - .ok_or_else(|| anyhow!("Hands commit requires its exact patch receipt"))?; - let command = cache + .filter(|patch| patch.intent_id == receipt.intent_id) + .collect::>(); + let [patch] = patches.as_slice() else { + return Err(anyhow!("Hands commit requires one exact patch receipt")); + }; + let commands = cache .get_all::()? .into_iter() - .filter(|command| { - command.intent_id == receipt.intent_id - && command.review_id == receipt.review_id - && command.runtime_job_id == receipt.runtime_job_id - && command.exit_code == "0" - && command.emitted_at <= receipt.emitted_at - }) - .max_by(|left, right| left.emitted_at.cmp(&right.emitted_at)) - .ok_or_else(|| anyhow!("Hands commit requires its successful command receipt"))?; + .filter(|command| command.intent_id == receipt.intent_id && command.exit_code == "0") + .collect::>(); + let [command] = commands.as_slice() else { + return Err(anyhow!( + "Hands commit requires one exact successful command receipt" + )); + }; let request = - derive_repo_frontier_verification_request_for_chain(&cache, &patch, &command, receipt)?; + derive_repo_frontier_verification_request_for_chain(&cache, patch, command, receipt)?; let context = repo_frontier_verification_context_with_commit(&cache, &request, Some(receipt))?; let snapshot = cache.snapshot_envelopes(); let mut expected = Vec::new(); @@ -8514,7 +7511,6 @@ pub fn put_hands_commit_receipt( context.hands_authority.authority_id.as_str(), ), (HandsActionIntent::TYPE, request.hands_intent_id.as_str()), - (HandsActionReview::TYPE, request.hands_review_id.as_str()), ( HandsPatchReceipt::TYPE, request.hands_patch_receipt_id.as_str(), @@ -8559,21 +7555,13 @@ pub fn put_hands_commit_receipt( } } -pub fn runtime_hands_commit_receipt( - store_path: impl AsRef, - receipt_id: &str, -) -> Result> { - validate_non_empty(receipt_id, "Hands commit receipt id")?; - let mut cache = runtime_spine_cache(store_path)?; - cache.pull_all_backing_stores()?; - cache.get::(receipt_id) -} - fn validate_coordinator_run_receipt(receipt: &EpiphanyCoordinatorRunReceipt) -> Result<()> { validate_non_empty(&receipt.receipt_id, "coordinator run receipt id")?; validate_non_empty(&receipt.session_id, "coordinator run receipt session id")?; validate_non_empty(&receipt.thread_id, "coordinator run receipt thread id")?; - validate_non_empty(&receipt.mode, "coordinator run receipt mode")?; + if !matches!(receipt.mode.as_str(), "plan" | "execute") { + return Err(anyhow!("coordinator run receipt mode is invalid")); + } validate_non_empty(&receipt.status, "coordinator run receipt status")?; validate_non_empty( &receipt.final_action, @@ -8582,9 +7570,31 @@ fn validate_coordinator_run_receipt(receipt: &EpiphanyCoordinatorRunReceipt) -> validate_non_empty(&receipt.created_at, "coordinator run receipt created at")?; chrono::DateTime::parse_from_rfc3339(&receipt.created_at) .map_err(|error| anyhow!("coordinator run receipt timestamp is invalid: {error}"))?; + if receipt.receipt_id != format!("coordinator-run-terminal-{}", receipt.session_id) { + return Err(anyhow!( + "coordinator run receipt identity is not its session's terminal identity" + )); + } Ok(()) } +fn coordinator_run_terminality( + session_id: &str, + terminal_kind: &str, + authority_id: &str, + authority: &T, +) -> Result { + let payload = rmp_serde::to_vec_named(authority) + .context("failed to encode coordinator terminal authority")?; + Ok(EpiphanyCoordinatorRunTerminality { + terminal_id: format!("coordinator-run-terminal-{session_id}"), + session_id: session_id.to_string(), + terminal_kind: terminal_kind.to_string(), + authority_id: authority_id.to_string(), + authority_sha256: format!("sha256:{:x}", Sha256::digest(payload)), + }) +} + pub(crate) fn runtime_typed_request_attempt_exists( store_path: impl AsRef, request: RuntimeTypedRequestRef<'_>, @@ -8603,15 +7613,16 @@ pub(crate) fn runtime_typed_request_attempt_exists( )) }) .collect::>>()?; - Ok(cache - .get_all::()? - .iter() - .filter(|launch| { - claims - .get(&launch.job_id) - .is_none_or(|status| !status.allows_retry()) - }) - .any(|launch| request.matches_launch(launch))) + for launch in cache.get_all::()? { + if claims + .get(&launch.job_id) + .is_none_or(|status| !status.allows_retry()) + && request.matches_launch(&launch)? + { + return Ok(true); + } + } + Ok(false) } fn validate_archivable_typed_worker_launch( @@ -8620,24 +7631,16 @@ fn validate_archivable_typed_worker_launch( request_kind: &str, request_id: &str, ) -> Result<()> { - if launch.schema_version != RUNTIME_WORKER_LAUNCH_REQUEST_SCHEMA_VERSION - || launch.job_id.trim().is_empty() - { + if launch.job_id.trim().is_empty() { return Err(anyhow!( "worker attempt archive found invalid immutable launch" )); } let document = launch.launch_document()?; let identity = require_identity(cache)?; - let launch_sha256 = format!("{:x}", Sha256::digest(&launch.launch_document_msgpack)); match request_kind { "proposal-modeling" => { - validate_proposal_modeling_launch_carrier( - &launch.role, - &launch.binding_id, - Some(request_id), - &document, - )?; + validate_proposal_modeling_launch_carrier(&launch.role, &launch.binding_id, &document)?; validate_repository_body_launch_carrier(&launch.role, &document)?; let request = cache .get::(request_id)? @@ -8647,14 +7650,7 @@ fn validate_archivable_typed_worker_launch( .get::(&request.proposal_id)? .ok_or_else(|| anyhow!("archived proposal Modeling launch lost its proposal"))?; validate_repo_frontier_work_proposal(&proposal)?; - validate_autonomous_proposal_origin_binding(cache, &proposal)?; - let binding_id = format!( - "repo-frontier-proposal-modeling-launch-{}", - launch.job_id - ); - let binding = cache - .get::(&binding_id)? - .ok_or_else(|| anyhow!("archived proposal Modeling launch lost its binding"))?; + validate_autonomous_proposal_origin_request(cache, &proposal)?; let projection = match &document { EpiphanyWorkerLaunchDocument::Role(document) => { document.proposal_modeling_context.as_ref() @@ -8664,11 +7660,6 @@ fn validate_archivable_typed_worker_launch( .ok_or_else(|| anyhow!("archived proposal Modeling launch lost its context"))?; if request.runtime_id != identity.runtime_id || request.proposal_payload_sha256 != proposal.payload_sha256 - || binding.schema_version - != REPO_FRONTIER_PROPOSAL_MODELING_LAUNCH_BINDING_SCHEMA_VERSION - || binding.proposal_modeling_request_id != request.request_id - || binding.job_id != launch.job_id - || binding.worker_launch_document_sha256 != launch_sha256 || projection.request_id != request.request_id || projection.proposal_id != proposal.proposal_id || projection.proposal_payload_sha256 != proposal.payload_sha256 @@ -8683,11 +7674,7 @@ fn validate_archivable_typed_worker_launch( } } "frontier-verdict-modeling" => { - validate_repo_frontier_verdict_modeling_launch_authority( - &launch.role, - Some(request_id), - &document, - )?; + validate_repo_frontier_verdict_modeling_launch_authority(&launch.role, &document)?; validate_repository_body_launch_carrier(&launch.role, &document)?; let request = cache .get::(request_id)? @@ -8731,7 +7718,6 @@ fn validate_archivable_typed_worker_launch( validate_imagination_consideration_launch_carrier( &launch.role, &launch.binding_id, - Some(request_id), &document, )?; let request = cache @@ -8745,15 +7731,6 @@ fn validate_archivable_typed_worker_launch( { return Err(anyhow!("archived Imagination launch request is invalid")); } - let bindings = cache - .get_all::()? - .into_iter() - .filter(|binding| binding.job_id == launch.job_id) - .collect::>(); - if bindings.len() != 1 { - return Err(anyhow!("archived Imagination launch requires one binding")); - } - let binding = &bindings[0]; let projection = match &document { EpiphanyWorkerLaunchDocument::Role(document) => { document.imagination_consideration_context.as_ref() @@ -8762,11 +7739,8 @@ fn validate_archivable_typed_worker_launch( } .ok_or_else(|| anyhow!("archived Imagination launch lost its context"))?; if request.runtime_id != identity.runtime_id - || binding.request_id != request.request_id - || binding.binding_id != EPIPHANY_IMAGINATION_ROLE_BINDING_ID - || binding.runtime_id != request.runtime_id - || binding.thread_id != request.thread_id - || binding.worker_launch_document_sha256 != launch_sha256 + || launch.job_id.trim().is_empty() + || document.thread_id() != launch.job_id || projection.request != request || projection.model.reasoning_basis() != (crate::EpiphanyRepoModelBasis { @@ -8836,10 +7810,7 @@ fn archive_runtime_worker_attempt( cache.pull_all_backing_stores()?; require_identity(&cache)?; if let Some(existing) = cache.get::(job_id)? { - if existing.schema_version != ARCHIVED_RUNTIME_WORKER_ATTEMPT_SCHEMA_VERSION - || existing.job_id != job_id - || !existing.retired_chain_digest.starts_with("sha256:") - { + if existing.job_id != job_id || !existing.retired_chain_digest.starts_with("sha256:") { return Err(anyhow!("archived worker attempt tombstone is invalid")); } existing.validate_decision_record(fulfilled)?; @@ -8862,7 +7833,8 @@ fn archive_runtime_worker_attempt( let launch = cache .get::(job_id)? .ok_or_else(|| anyhow!("worker attempt archive requires its immutable launch"))?; - let request = launch.typed_request_ref()?.ok_or_else(|| { + let document = launch.launch_document()?; + let request = document.typed_request_ref()?.ok_or_else(|| { anyhow!("worker attempt archive requires exactly one supported typed request") })?; let request_kind = request.kind(); @@ -8874,11 +7846,19 @@ fn archive_runtime_worker_attempt( )); } let role_result = cache.get::(job_id)?; - if fulfilled != role_result.is_some() { + if fulfilled && role_result.is_none() { return Err(anyhow!( "worker attempt archive terminal result shape disagrees with requested archive kind" )); } + if role_result + .as_ref() + .is_some_and(|result| fulfilled == result.item_error.is_some()) + { + return Err(anyhow!( + "worker attempt archive structured result disagrees with process terminality" + )); + } let claim_id = worker_process_claim_id(job_id); let claim = cache .get::(&claim_id)? @@ -8900,10 +7880,7 @@ fn archive_runtime_worker_attempt( let valid_job = if fulfilled { job.status == EpiphanyRuntimeJobStatus::Completed } else { - matches!( - job.status, - EpiphanyRuntimeJobStatus::Failed | EpiphanyRuntimeJobStatus::Cancelled - ) + matches!(job.status, EpiphanyRuntimeJobStatus::Failed) }; if !valid_job { return Err(anyhow!( @@ -8927,9 +7904,10 @@ fn archive_runtime_worker_attempt( _ => unreachable!("typed request kind was selected above"), }; let evidence = - runtime_typed_request_fulfillment(store_path, request_ref)?.ok_or_else(|| { - anyhow!("fulfilled worker attempt archive lost authenticated fulfillment") - })?; + runtime_typed_request_fulfillment_from_cache(store_path, &cache, request_ref)? + .ok_or_else(|| { + anyhow!("fulfilled worker attempt archive lost authenticated fulfillment") + })?; if evidence.job_id != job_id || claim.terminal_authority_id.as_deref() != Some(evidence.result_id.as_str()) { @@ -8955,28 +7933,14 @@ fn archive_runtime_worker_attempt( } } let snapshot = cache.snapshot_envelopes(); - let proposal_binding_id = format!("repo-frontier-proposal-modeling-launch-{job_id}"); - let imagination_bindings = cache - .get_all::()? - .into_iter() - .filter(|item| item.job_id == job_id) - .map(|item| item.binding_record_id) - .collect::>(); - let worker_job_results = cache - .get_all::()? - .into_iter() - .filter(|item| item.job_id == job_id) - .collect::>(); - let job_results = worker_job_results - .iter() - .map(|item| item.result_id.clone()) - .collect::>(); let role_decision_context_id = role_result .as_ref() .map(|result| result.decision_context_id.clone()); - let mut decision_context_ids = worker_job_results + let mut decision_context_ids = cache + .get_all::()? .iter() - .filter_map(|result| result.decision_context_id.clone()) + .filter(|failure| failure.pass_id == job_id) + .map(|failure| failure.decision_context_id.clone()) .chain(role_decision_context_id) .collect::>(); if decision_context_ids.len() > 1 { @@ -8993,12 +7957,9 @@ fn archive_runtime_worker_attempt( if let Some(context_id) = decision_context_id.as_deref() { require_worker_decision_context(&cache, context_id, job_id)?; } - let mut archived_job_results = worker_job_results.clone(); - archived_job_results.sort_by(|left, right| left.result_id.cmp(&right.result_id)); let decision = decision_context_id.map(|context_id| EpiphanyArchivedRuntimeWorkerDecision { decision_context_id: context_id, role_result: role_result.clone(), - job_results: archived_job_results, }); let mut deletions = snapshot .iter() @@ -9006,16 +7967,8 @@ fn archive_runtime_worker_attempt( (entry.r#type == EpiphanyRuntimeWorkerLaunchRequest::TYPE && entry.key == job_id) || (entry.r#type == EpiphanyRuntimeWorkerProcessClaim::TYPE && entry.key == claim_id) - || (entry.r#type == EpiphanyRuntimeRoleWorkerResult::TYPE - && fulfilled - && entry.key == job_id) + || (entry.r#type == EpiphanyRuntimeRoleWorkerResult::TYPE && entry.key == job_id) || (entry.r#type == EpiphanyRuntimeJob::TYPE && entry.key == job_id) - || (entry.r#type == EpiphanyRuntimeJobResult::TYPE - && job_results.contains(&entry.key)) - || (entry.r#type == RepoFrontierProposalModelingLaunchBinding::TYPE - && entry.key == proposal_binding_id) - || (entry.r#type == crate::ImaginationConsiderationLaunchBinding::TYPE - && imagination_bindings.contains(&entry.key)) }) .cloned() .collect::>(); @@ -9045,7 +7998,6 @@ fn archive_runtime_worker_attempt( } } let tombstone = EpiphanyArchivedRuntimeWorkerAttempt { - schema_version: ARCHIVED_RUNTIME_WORKER_ATTEMPT_SCHEMA_VERSION.into(), job_id: job_id.into(), request_kind: request_kind.into(), request_id: request_id.into(), @@ -9081,14 +8033,14 @@ pub fn retain_failed_runtime_worker_attempts( .into_iter() .map(|launch| (launch.job_id.clone(), launch)) .collect::>(); - let typed_request_ids = launches + let typed_requests = launches .values() .map(|launch| { + let document = launch.launch_document()?; + let request = document.typed_request_ref()?; Ok(( launch.job_id.clone(), - launch - .typed_request_ref()? - .map(|request| request.request_id().to_string()), + request.map(|request| (request.kind(), request.request_id().to_string())), )) }) .collect::>>()?; @@ -9107,13 +8059,10 @@ pub fn retain_failed_runtime_worker_attempts( .filter(|(status, _)| status.is_failed_terminal()) .filter_map(|(_, claim)| { let job = jobs.get(&claim.job_id)?; - if !matches!( - job.status, - EpiphanyRuntimeJobStatus::Failed | EpiphanyRuntimeJobStatus::Cancelled - ) { + if !matches!(job.status, EpiphanyRuntimeJobStatus::Failed) { return None; } - let request_id = typed_request_ids.get(&claim.job_id)?.as_deref()?; + let request_id = typed_requests.get(&claim.job_id)?.as_ref()?.1.as_str(); (!live_resident_request_ids.contains(request_id)).then_some(claim) }) .collect::>(); @@ -9147,14 +8096,14 @@ pub fn retain_fulfilled_runtime_worker_attempts( .into_iter() .map(|launch| (launch.job_id.clone(), launch)) .collect::>(); - let typed_request_ids = launches + let typed_requests = launches .values() .map(|launch| { + let document = launch.launch_document()?; + let request = document.typed_request_ref()?; Ok(( launch.job_id.clone(), - launch - .typed_request_ref()? - .map(|request| request.request_id().to_string()), + request.map(|request| (request.kind(), request.request_id().to_string())), )) }) .collect::>>()?; @@ -9177,16 +8126,19 @@ pub fn retain_fulfilled_runtime_worker_attempts( .into_iter() .filter(|(status, _)| status.is_fulfilled_terminal()) .filter_map(|(_, claim)| { - let launch = launches.get(&claim.job_id)?; + launches.get(&claim.job_id)?; let job = jobs.get(&claim.job_id)?; let result = role_results.get(&claim.job_id)?; if job.status != EpiphanyRuntimeJobStatus::Completed - || (launch.proposal_modeling_request_id.is_some() + || (typed_requests + .get(&claim.job_id)? + .as_ref() + .is_some_and(|(kind, _)| *kind == "proposal-modeling") && !worker_result_has_keyed_mind_commit(&cache, result).ok()?) { return None; } - let request_id = typed_request_ids.get(&claim.job_id)?.as_deref()?; + let request_id = typed_requests.get(&claim.job_id)?.as_ref()?.1.as_str(); (!live_resident_request_ids.contains(request_id)).then_some(claim) }) .collect::>(); @@ -9201,24 +8153,12 @@ pub fn retain_fulfilled_runtime_worker_attempts( Ok(()) } -fn coordinator_completion_summary(receipt: &EpiphanyCoordinatorRunReceipt) -> String { - format!( - "Coordinator run {:?} terminalized with status {:?}.", - receipt.receipt_id, receipt.status - ) -} - -pub const COORDINATOR_DEATH_RECOVERY_SCHEMA_VERSION: &str = - "epiphany.coordinator_death_recovery.v0"; - #[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)] #[cultcache( type = "epiphany.coordinator_death_recovery.v0", schema = "EpiphanyCoordinatorDeathRecovery" )] pub struct EpiphanyCoordinatorDeathRecovery { - #[cultcache(key = 0)] - pub schema_version: String, #[cultcache(key = 1)] pub recovery_id: String, #[cultcache(key = 2)] @@ -9278,7 +8218,7 @@ pub fn open_coordinator_run( resident_launch_digest: Option<&str>, objective: &str, started_at: &str, -) -> Result { +) -> Result { validate_non_empty(session_id, "coordinator run session id")?; validate_non_empty(thread_id, "coordinator run thread id")?; validate_non_empty(objective, "coordinator run objective")?; @@ -9293,58 +8233,30 @@ pub fn open_coordinator_run( let mut cache = runtime_spine_cache(store_path)?; cache.pull_all_backing_stores()?; require_identity(&cache)?; - if cache.get::(session_id)?.is_some() + if cache + .get::(session_id)? + .is_some() || cache - .get::(session_id)? + .get::(session_id)? .is_some() - || cache - .get_all::()? - .iter() - .any(|receipt| { - receipt.session_id == session_id - || (resident_launch_digest.is_none() && receipt.thread_id == thread_id) - }) - || cache - .get_all::()? - .iter() - .any(|recovery| recovery.session_id == session_id) + || cache.get::(session_id)?.is_some() { return Err(anyhow!( "coordinator run session or thread authority already exists" )); } - let session = EpiphanyRuntimeSession { - schema_version: RUNTIME_SPINE_SCHEMA_VERSION.to_string(), + let basis = EpiphanyCoordinatorRunBasis { session_id: session_id.to_string(), + thread_id: thread_id.to_string(), + resident_launch_digest: resident_launch_digest.map(str::to_string), objective: objective.to_string(), - status: EpiphanyRuntimeSessionStatus::Active, - created_at: started_at.to_string(), - updated_at: started_at.to_string(), - coordinator_note: "Coordinator owns native runtime receipts before process exit." - .to_string(), - metadata: BTreeMap::new(), + started_at: started_at.to_string(), }; - let snapshot = cache.snapshot_envelopes(); - let replacements = vec![cache.prepare_entry(session_id, &session)?.0]; - if !runtime_spine_backing_store(store_path)? - .replace_and_append_if_snapshot_unchanged(&snapshot, replacements)? - { - return Err(anyhow!( - "coordinator run opening lost its full snapshot fence" - )); + let envelope = cache.prepare_entry(session_id, &basis)?.0; + if !runtime_spine_backing_store(store_path)?.compare_and_swap_batch(&[], vec![envelope])? { + return Err(anyhow!("coordinator run opening lost immutable admission")); } - Ok(session) -} - -fn coordinator_death_recovery_summary(recovery: &EpiphanyCoordinatorDeathRecovery) -> String { - format!( - "Continuity terminalized coordinator session after exact process observation {:?}{}.", - recovery.observation, - recovery - .exit_code - .map(|code| format!(" with exit code {code}")) - .unwrap_or_default() - ) + Ok(basis) } pub(crate) fn coordinator_run_incarnation_is_absent( @@ -9356,32 +8268,22 @@ pub(crate) fn coordinator_run_incarnation_is_absent( let mut cache = runtime_spine_cache(store_path)?; cache.pull_all_backing_stores()?; require_identity(&cache)?; - Ok(cache.get::(&session_id)?.is_none() + Ok(cache + .get::(&session_id)? + .is_none() && cache - .get::(&session_id)? + .get::(&session_id)? .is_none() - && !cache - .get_all::()? - .iter() - .any(|job| job.session_id == session_id) - && !cache - .get_all::()? - .iter() - .any(|receipt| receipt.session_id == session_id) - && !cache - .get_all::()? - .iter() - .any(|recovery| recovery.session_id == session_id)) + && cache.get::(&session_id)?.is_none()) } pub(crate) fn recover_coordinator_run_after_exact_process_death( store_path: impl AsRef, recovery: &EpiphanyCoordinatorDeathRecovery, expected_objective: &str, -) -> Result { +) -> Result<()> { validate_non_empty(expected_objective, "recovered coordinator objective")?; - if recovery.schema_version != COORDINATOR_DEATH_RECOVERY_SCHEMA_VERSION - || recovery.recovery_id != format!("coordinator-death-recovery-{}", recovery.session_id) + if recovery.recovery_id != format!("coordinator-death-recovery-{}", recovery.session_id) || recovery.session_id != coordinator_run_session_id( &recovery.thread_id, @@ -9405,76 +8307,70 @@ pub(crate) fn recover_coordinator_run_after_exact_process_death( let mut cache = runtime_spine_cache(store_path)?; cache.pull_all_backing_stores()?; require_identity(&cache)?; - let mut session = cache - .get::(&recovery.session_id)? - .ok_or_else(|| anyhow!("coordinator death recovery session is absent"))?; - let completion_summary = coordinator_death_recovery_summary(recovery); - if session.status == EpiphanyRuntimeSessionStatus::Completed { - let existing_recovery = - cache.get::(&recovery.recovery_id)?; - if existing_recovery.as_ref() == Some(recovery) - && session.objective == expected_objective - && session.updated_at == recovery.recovered_at - && session.coordinator_note == completion_summary + let terminality = coordinator_run_terminality( + &recovery.session_id, + "death-recovery", + &recovery.recovery_id, + recovery, + )?; + if let Some(existing) = cache.get::(&recovery.session_id)? { + if existing == terminality + && cache.get::(&recovery.recovery_id)? + == Some(recovery.clone()) { - return Ok(session); + return Ok(()); } - return Err(anyhow!( - "completed coordinator session does not match exact death recovery" - )); + return Err(anyhow!("coordinator terminal authority conflict")); } - if session.status != EpiphanyRuntimeSessionStatus::Active - || session.objective != expected_objective + let basis = cache + .get::(&recovery.session_id)? + .ok_or_else(|| anyhow!("coordinator death recovery basis is absent"))?; + if basis.objective != expected_objective + || basis.thread_id != recovery.thread_id + || basis.resident_launch_digest.as_deref() != Some(&recovery.resident_launch_digest) { return Err(anyhow!( - "coordinator death recovery requires the exact active session objective" + "coordinator death recovery requires the exact run basis" )); } - let session_started_at = chrono::DateTime::parse_from_rfc3339(&session.created_at) + let session_started_at = chrono::DateTime::parse_from_rfc3339(&basis.started_at) .map_err(|error| anyhow!("coordinator session start timestamp is invalid: {error}"))?; let recovered_at = chrono::DateTime::parse_from_rfc3339(&recovery.recovered_at) .map_err(|error| anyhow!("coordinator death recovery timestamp is invalid: {error}"))?; if (session_started_at.timestamp_millis().max(0) as u64) < recovery.resident_started_at_millis || recovered_at < session_started_at || cache - .get_all::()? - .iter() - .any(|job| job.session_id == recovery.session_id) + .get::(&recovery.session_id)? + .is_some() || cache - .get_all::()? - .iter() - .any(|receipt| receipt.session_id == recovery.session_id) - || cache - .get_all::()? - .iter() - .any(|existing| existing.session_id == recovery.session_id) + .get::(&recovery.session_id)? + .is_some() { return Err(anyhow!( "coordinator death recovery found substituted or competing authority" )); } - let snapshot = cache.snapshot_envelopes(); - session.status = EpiphanyRuntimeSessionStatus::Completed; - session.updated_at = recovery.recovered_at.clone(); - session.coordinator_note = completion_summary; + let basis_envelope = + cache.get_required_envelope::(&recovery.session_id)?; let replacements = vec![ - cache.prepare_entry(&session.session_id, &session)?.0, + basis_envelope.clone(), + cache + .prepare_entry(&terminality.session_id, &terminality)? + .0, cache.prepare_entry(&recovery.recovery_id, recovery)?.0, ]; if !runtime_spine_backing_store(store_path)? - .replace_and_append_if_snapshot_unchanged(&snapshot, replacements)? + .compare_and_swap_batch(&[basis_envelope], replacements)? { - return Err(anyhow!( - "coordinator death recovery lost its full snapshot fence" - )); + return Err(anyhow!("coordinator death recovery lost exact admission")); } - Ok(session) + Ok(()) } pub fn finalize_coordinator_run( store_path: impl AsRef, receipt: &EpiphanyCoordinatorRunReceipt, -) -> Result { +) -> Result<()> { validate_coordinator_run_receipt(receipt)?; if receipt.session_id != coordinator_run_session_id( @@ -9490,68 +8386,53 @@ pub fn finalize_coordinator_run( let mut cache = runtime_spine_cache(store_path)?; cache.pull_all_backing_stores()?; require_identity(&cache)?; - let mut session = cache - .get::(&receipt.session_id)? + let terminality = + coordinator_run_terminality(&receipt.session_id, "receipt", &receipt.receipt_id, receipt)?; + if let Some(existing) = cache.get::(&receipt.session_id)? { + if existing == terminality + && cache.get::(&receipt.receipt_id)? + == Some(receipt.clone()) + { + return Ok(()); + } + return Err(anyhow!("coordinator terminal authority conflict")); + } + let basis = cache + .get::(&receipt.session_id)? .ok_or_else(|| { anyhow!( - "coordinator run receipt session {:?} does not exist", + "coordinator run basis {:?} does not exist", receipt.session_id ) })?; - let completion_summary = coordinator_completion_summary(receipt); - if session.status == EpiphanyRuntimeSessionStatus::Completed { - let existing_receipt = cache.get::(&receipt.receipt_id)?; - if existing_receipt.as_ref() == Some(receipt) - && session.updated_at == receipt.created_at - && session.coordinator_note == completion_summary - { - return Ok(session); - } - return Err(anyhow!( - "completed coordinator session does not match the terminal run transaction" - )); - } - if session.status != EpiphanyRuntimeSessionStatus::Active { - return Err(anyhow!( - "coordinator run session {:?} is not active", - receipt.session_id - )); + if basis.thread_id != receipt.thread_id + || basis.resident_launch_digest != receipt.resident_launch_digest + { + return Err(anyhow!("coordinator run receipt substituted its run basis")); } if cache - .get_all::()? - .iter() - .any(|job| job.session_id == receipt.session_id) + .get::(&receipt.session_id)? + .is_some() { return Err(anyhow!( "coordinator run finalization refuses a session with runtime jobs" )); } - if cache - .get_all::()? - .iter() - .any(|existing| existing.session_id == receipt.session_id) - { - return Err(anyhow!( - "coordinator run session already has partial terminal authority" - )); - } - let expected_session = - cache.get_required_envelope::(&receipt.session_id)?; - session.status = EpiphanyRuntimeSessionStatus::Completed; - session.updated_at = receipt.created_at.clone(); - session.coordinator_note = completion_summary; + let basis_envelope = + cache.get_required_envelope::(&receipt.session_id)?; let replacements = vec![ - cache.prepare_entry(&session.session_id, &session)?.0, + basis_envelope.clone(), + cache + .prepare_entry(&terminality.session_id, &terminality)? + .0, cache.prepare_entry(&receipt.receipt_id, receipt)?.0, ]; if !runtime_spine_backing_store(store_path)? - .compare_and_swap_batch(&[expected_session], replacements)? + .compare_and_swap_batch(&[basis_envelope], replacements)? { - return Err(anyhow!( - "coordinator run finalization lost its exact session transaction" - )); + return Err(anyhow!("coordinator run finalization lost exact admission")); } - Ok(session) + Ok(()) } pub fn coordinator_run_receipts( @@ -9563,126 +8444,86 @@ pub fn coordinator_run_receipts( cache.get_all::() } -pub fn complete_runtime_job( +pub fn terminalize_runtime_job( store_path: impl AsRef, - options: RuntimeSpineJobResultOptions, -) -> Result { - validate_non_empty(&options.result_id, "result id")?; - validate_non_empty(&options.job_id, "job id")?; - validate_non_empty(&options.completed_at, "completed at")?; - validate_non_empty(&options.verdict, "verdict")?; - validate_non_empty(&options.summary, "summary")?; - let mut cache = runtime_spine_cache(store_path.as_ref())?; + job_id: &str, + status: EpiphanyRuntimeJobStatus, + terminal_at: &str, + terminal_authority_id: &str, +) -> Result { + validate_non_empty(job_id, "job id")?; + validate_non_empty(terminal_authority_id, "terminal authority id")?; + chrono::DateTime::parse_from_rfc3339(terminal_at) + .map_err(|error| anyhow!("runtime job terminal time is invalid: {error}"))?; + if status == EpiphanyRuntimeJobStatus::Queued { + return Err(anyhow!("runtime job terminal status cannot be queued")); + } + let store_path = store_path.as_ref(); + let mut cache = runtime_spine_cache(store_path)?; cache.pull_all_backing_stores()?; require_identity(&cache)?; - let mut job = cache - .get::(&options.job_id)? - .ok_or_else(|| anyhow!("runtime job {:?} does not exist", options.job_id))?; - if matches!( - job.status, - EpiphanyRuntimeJobStatus::Completed - | EpiphanyRuntimeJobStatus::Failed - | EpiphanyRuntimeJobStatus::Cancelled - ) { - return Err(anyhow!( - "runtime job {:?} is already terminal", - options.job_id - )); - } if cache - .get::(&options.result_id)? + .get::(job_id)? .is_some() + || cache + .get::(job_id)? + .is_some() { return Err(anyhow!( - "runtime job result {:?} already exists", - options.result_id + "structured worker outcomes terminalize their own runtime job" )); } - let snapshot = cache.snapshot_envelopes(); - let job_envelope = snapshot - .iter() - .find(|entry| entry.r#type == EpiphanyRuntimeJob::TYPE && entry.key == options.job_id) - .cloned() - .ok_or_else(|| anyhow!("runtime job lost its exact envelope"))?; - let terminal_status = terminal_status_for_verdict(&options.verdict); - job.status = terminal_status; - job.updated_at = options.completed_at.clone(); - job.summary = options.summary.clone(); - job.artifact_refs = merge_refs(&job.artifact_refs, &options.artifact_refs); - let result = EpiphanyRuntimeJobResult { - schema_version: RUNTIME_SPINE_SCHEMA_VERSION.to_string(), - result_id: options.result_id.clone(), - job_id: options.job_id.clone(), - session_id: job.session_id.clone(), - role: job.role.clone(), - verdict: options.verdict, - summary: options.summary, - completed_at: options.completed_at.clone(), - next_safe_move: options.next_safe_move, - evidence_refs: options.evidence_refs, - artifact_refs: options.artifact_refs, - metadata: BTreeMap::new(), - decision_context_id: options.decision_context_id, - }; - let mut expected = vec![job_envelope]; - let mut writes = vec![ - cache.prepare_entry(&job.job_id, &job)?.0, - cache.prepare_entry(&result.result_id, &result)?.0, - ]; - let claim_id = worker_process_claim_id(&result.job_id); - if let Some(claim) = cache.get::(&claim_id)? { - match crate::WorkerProcessStatus::parse(&claim.status)? { - crate::WorkerProcessStatus::Active => { - let claim_envelope = snapshot - .iter() - .find(|entry| { - entry.r#type == EpiphanyRuntimeWorkerProcessClaim::TYPE - && entry.key == claim_id - }) - .cloned() - .ok_or_else(|| anyhow!("runtime worker completion lost its claim envelope"))?; - let mut terminal = claim; - let role = cache.get::(&result.job_id)?; - let reorient = cache.get::(&result.job_id)?; - if role.is_some() && reorient.is_some() { - return Err(anyhow!( - "runtime worker job has both role and reorientation terminal outcomes" - )); - } - if let Some(terminal_result_id) = role - .map(|result| result.result_id) - .or_else(|| reorient.map(|result| result.result_id)) - { - terminal.status = crate::WorkerProcessStatus::TerminalResult.as_str().into(); - terminal.terminal_authority_id = Some(terminal_result_id); - } else { - terminal.status = crate::WorkerProcessStatus::TerminalFailure.as_str().into(); - terminal.terminal_authority_id = Some(result.result_id.clone()); - } - terminal.terminal_at = Some(result.completed_at.clone()); - expected.push(claim_envelope); - writes.push(cache.prepare_entry(&claim_id, &terminal)?.0); - } - crate::WorkerProcessStatus::TerminalResult => {} - crate::WorkerProcessStatus::Claimed => { - return Err(anyhow!( - "runtime worker job cannot complete before activation" - )); - } - status => { - return Err(anyhow!( - "runtime worker job completion found process status {:?}", - status.as_str() - )); + let mut job = cache + .get::(job_id)? + .ok_or_else(|| anyhow!("runtime job {job_id:?} does not exist"))?; + let claim_id = worker_process_claim_id(job_id); + let claim = cache.get::(&claim_id)?; + if job.status != EpiphanyRuntimeJobStatus::Queued { + if job.status != status { + return Err(anyhow!("runtime job terminal status was substituted")); + } + if let Some(claim) = claim { + let expected_claim_status = process_status_for_terminal_job(&status)?; + if WorkerProcessStatus::parse(&claim.status)? != expected_claim_status + || claim.terminal_authority_id.as_deref() != Some(terminal_authority_id) + { + return Err(anyhow!("runtime job replay crossed its process authority")); } } + return Ok(job); } - if !runtime_spine_backing_store(store_path.as_ref())? - .compare_and_swap_batch(&expected, writes)? - { - return Err(anyhow!("runtime job completion lost its exact snapshot")); + let job_envelope = cache + .get_envelope::(job_id)? + .ok_or_else(|| anyhow!("runtime job lost its exact envelope"))?; + job.status = status.clone(); + let mut expected = vec![job_envelope]; + let mut writes = vec![cache.prepare_entry(job_id, &job)?.0]; + if let Some(claim) = claim { + if WorkerProcessStatus::parse(&claim.status)? != WorkerProcessStatus::Active { + return Err(anyhow!( + "runtime worker job cannot terminalize without its active process claim" + )); + } + let claim_envelope = cache + .get_envelope::(&claim_id)? + .ok_or_else(|| anyhow!("runtime worker terminality lost its claim envelope"))?; + let mut terminal = claim; + terminal.status = process_status_for_terminal_job(&status)?.as_str().into(); + terminal.terminal_at = Some(terminal_at.to_string()); + terminal.terminal_authority_id = Some(terminal_authority_id.to_string()); + expected.push(claim_envelope); + writes.push(cache.prepare_entry(&claim_id, &terminal)?.0); } - Ok(result) + if runtime_spine_backing_store(store_path)?.compare_and_swap_batch(&expected, writes)? { + return Ok(job); + } + terminalize_runtime_job( + store_path, + job_id, + status, + terminal_at, + terminal_authority_id, + ) } pub fn runtime_identity(store_path: impl AsRef) -> Result> { @@ -9697,10 +8538,6 @@ pub fn runtime_identity(store_path: impl AsRef) -> Result(RUNTIME_IDENTITY_KEY) } -pub fn runtime_registered_document_types() -> Result> { - Ok(runtime_spine_schema_cache()?.registered_entry_types()) -} - fn require_identity(cache: &CultCache) -> Result { cache .get::(RUNTIME_IDENTITY_KEY)? @@ -9760,27 +8597,6 @@ where .transpose() } -fn terminal_status_for_verdict(verdict: &str) -> EpiphanyRuntimeJobStatus { - if matches!( - verdict, - "failed" | "fail" | "error" | "blocked" | "cancelled" | "canceled" - ) { - EpiphanyRuntimeJobStatus::Failed - } else { - EpiphanyRuntimeJobStatus::Completed - } -} - -fn merge_refs(existing: &[String], incoming: &[String]) -> Vec { - let mut merged = existing.to_vec(); - for item in incoming { - if !merged.contains(item) { - merged.push(item.clone()); - } - } - merged -} - #[cfg(test)] pub(crate) mod tests { use super::*; @@ -9816,6 +8632,26 @@ pub(crate) mod tests { Ok(()) } + #[test] + fn structured_worker_failure_cannot_claim_fulfillment() -> Result<()> { + assert_eq!( + worker_outcome_status(None)?, + ( + EpiphanyRuntimeJobStatus::Completed, + WorkerProcessStatus::TerminalResult, + ) + ); + assert_eq!( + worker_outcome_status(Some("provider refused"))?, + ( + EpiphanyRuntimeJobStatus::Failed, + WorkerProcessStatus::TerminalFailure, + ) + ); + assert!(worker_outcome_status(Some(" ")).is_err()); + Ok(()) + } + #[test] fn current_runtime_refuses_old_writable_epoch_without_mutation() -> Result<()> { let temp = tempfile::tempdir()?; @@ -9823,7 +8659,7 @@ pub(crate) mod tests { let mut historical = CultCache::new(); historical.register_entry_type::()?; historical.register_entry_type::()?; - historical.add_generic_backing_store(runtime_spine_backing_store(&store)?); + historical.add_generic_backing_store(runtime_spine_backing_store(&store)?)?; historical.put( "epiphany.mind.epoch.v1", &crate::EpiphanyMindIdentity { @@ -9859,7 +8695,7 @@ pub(crate) mod tests { let mut archive_v0 = CultCache::new(); archive_v0.register_entry_type::()?; archive_v0.register_entry_type::()?; - archive_v0.add_generic_backing_store(runtime_spine_backing_store(&archive_v0_store)?); + archive_v0.add_generic_backing_store(runtime_spine_backing_store(&archive_v0_store)?)?; archive_v0.put( crate::MIND_SCHEMA_EPOCH, &crate::EpiphanyMindIdentity { @@ -9888,7 +8724,7 @@ pub(crate) mod tests { responses_only.register_entry_type::()?; responses_only.register_entry_type::()?; responses_only - .add_generic_backing_store(runtime_spine_backing_store(&responses_only_store)?); + .add_generic_backing_store(runtime_spine_backing_store(&responses_only_store)?)?; responses_only.put( crate::MIND_SCHEMA_EPOCH, &crate::EpiphanyMindIdentity { @@ -9914,4 +8750,206 @@ pub(crate) mod tests { ); Ok(()) } + + /// A stand-in for a document of the Eureka pipeline, which + /// `epiphany-pipeline` owns and this package deliberately does not depend + /// on. The rule under test is the spine registry's, not the document's: + /// what the spine cache sees is a type id it never registered, and that is + /// all this needs to carry. + #[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)] + #[cultcache( + type = "epiphany.pipeline.campaign.v1", + schema = "EpiphanyPipelineCampaignDocument" + )] + struct ForeignPipelineDocument { + #[cultcache(key = 0)] + slug: String, + } + + /// Soul F4: the spine store and a pipeline store are different stores, and + /// the spine cache refuses the latter *by type* — it registers its own + /// types and nothing else. The epoch checks beside this one cannot stand in + /// for it: a pipeline store carries no runtime or Mind identity at all, so + /// `validate_runtime_store_epoch` returns `Ok` and the registry is the only + /// thing left refusing. + #[test] + fn runtime_spine_cache_refuses_a_pipeline_store() -> Result<()> { + let temp = tempfile::tempdir()?; + let store = temp.path().join("pipeline.cc"); + let mut pipeline = CultCache::new(); + pipeline.register_entry_type::()?; + let envelope = pipeline + .prepare_entry_named( + "eureka-state", + &ForeignPipelineDocument { slug: "eureka-state".into() }, + )? + .0; + assert!( + envelope.r#type.starts_with("epiphany.pipeline."), + "the sample is a pipeline document: {}", + envelope.r#type + ); + let mut backing = runtime_spine_backing_store(&store)?; + backing.push(&envelope)?; + + assert!( + validate_runtime_store_epoch(&runtime_spine_backing_store(&store)?.pull_all()?).is_ok(), + "the epoch check passes a pipeline store, so only the registry refuses it" + ); + let error = runtime_spine_cache(&store) + .and_then(|mut runtime| runtime.pull_all_backing_stores()) + .unwrap_err(); + assert!(error.to_string().contains("epiphany.pipeline."), "{error:#}"); + Ok(()) + } + + #[test] + fn coordinator_terminal_receipts_preserve_exact_immutable_run_basis() -> Result<()> { + let temp = tempfile::tempdir()?; + let completed_store = temp.path().join("completed.cc"); + initialize_runtime_spine( + &completed_store, + RuntimeSpineInitOptions { + runtime_id: "coordinator-terminal-state".into(), + display_name: "Coordinator terminal state test".into(), + created_at: "2026-08-24T00:00:00Z".into(), + }, + )?; + let session_id = coordinator_run_session_id("thread-completed", None)?; + open_coordinator_run( + &completed_store, + &session_id, + "thread-completed", + None, + "Complete one coordinator run.", + "2026-08-24T00:00:01Z", + )?; + let receipt = EpiphanyCoordinatorRunReceipt { + receipt_id: format!("coordinator-run-terminal-{session_id}"), + session_id: session_id.clone(), + thread_id: "thread-completed".into(), + mode: "execute".into(), + status: "completed".into(), + final_action: "stop".into(), + final_reason: None, + created_at: "2026-08-24T00:00:02Z".into(), + resident_grant_id: None, + resident_launch_digest: None, + resident_policy_digest: None, + resident_argv_digest: None, + resident_objective_digest: None, + resident_release_commit: None, + resident_release_manifest_digest: None, + resident_executable_digest: None, + }; + finalize_coordinator_run(&completed_store, &receipt)?; + finalize_coordinator_run(&completed_store, &receipt)?; + let mut cache = runtime_spine_cache(&completed_store)?; + cache.pull_all_backing_stores()?; + assert!( + cache + .get::(&session_id)? + .is_some() + ); + assert!(cache.get::(&session_id)?.is_none()); + assert_eq!( + cache.get::(&receipt.receipt_id)?, + Some(receipt.clone()) + ); + assert_eq!( + cache + .get::(&session_id)? + .expect("completed run should have one terminality owner") + .authority_id, + receipt.receipt_id + ); + let mut competing_receipt = receipt; + competing_receipt.status = "failed".into(); + assert!(finalize_coordinator_run(&completed_store, &competing_receipt).is_err()); + + let recovered_store = temp.path().join("recovered.cc"); + initialize_runtime_spine( + &recovered_store, + RuntimeSpineInitOptions { + runtime_id: "coordinator-recovery-state".into(), + display_name: "Coordinator recovery state test".into(), + created_at: "2026-08-24T00:00:00Z".into(), + }, + )?; + let digest = format!("sha256:{}", "a".repeat(64)); + let recovered_session_id = coordinator_run_session_id("thread-recovered", Some(&digest))?; + let started_at = "2026-08-24T00:00:03Z"; + open_coordinator_run( + &recovered_store, + &recovered_session_id, + "thread-recovered", + Some(&digest), + "Resident coordinator launch objective-digest.", + started_at, + )?; + let recovery = EpiphanyCoordinatorDeathRecovery { + recovery_id: format!("coordinator-death-recovery-{recovered_session_id}"), + session_id: recovered_session_id.clone(), + thread_id: "thread-recovered".into(), + resident_grant_id: "grant-1".into(), + resident_launch_digest: digest, + process_id: 42, + process_creation_token: 7, + process_executable_path: "F:\\Epiphany\\epiphany-mvp-coordinator.exe".into(), + resident_started_at_millis: chrono::DateTime::parse_from_rfc3339(started_at)? + .timestamp_millis() as u64, + observation: "missing".into(), + recovered_at: "2026-08-24T00:00:04Z".into(), + private_state_exposed: false, + exit_code: None, + }; + recover_coordinator_run_after_exact_process_death( + &recovered_store, + &recovery, + "Resident coordinator launch objective-digest.", + )?; + recover_coordinator_run_after_exact_process_death( + &recovered_store, + &recovery, + "Resident coordinator launch objective-digest.", + )?; + let mut cache = runtime_spine_cache(&recovered_store)?; + cache.pull_all_backing_stores()?; + assert!( + cache + .get::(&recovered_session_id)? + .is_some() + ); + assert_eq!( + cache.get::(&recovery.recovery_id)?, + Some(recovery.clone()) + ); + assert_eq!( + cache + .get::(&recovered_session_id)? + .expect("recovered run should have one terminality owner") + .authority_id, + recovery.recovery_id + ); + let competing_receipt = EpiphanyCoordinatorRunReceipt { + receipt_id: format!("coordinator-run-terminal-{recovered_session_id}"), + session_id: recovered_session_id, + thread_id: "thread-recovered".into(), + mode: "execute".into(), + status: "completed".into(), + final_action: "stop".into(), + final_reason: None, + created_at: "2026-08-24T00:00:05Z".into(), + resident_grant_id: Some("grant-1".into()), + resident_launch_digest: Some(recovery.resident_launch_digest), + resident_policy_digest: None, + resident_argv_digest: None, + resident_objective_digest: None, + resident_release_commit: None, + resident_release_manifest_digest: None, + resident_executable_digest: None, + }; + assert!(finalize_coordinator_run(&recovered_store, &competing_receipt).is_err()); + Ok(()) + } } diff --git a/epiphany-core/src/runtime_store_backend.rs b/epiphany-core/src/runtime_store_backend.rs index 9e09b2d7..42310a2a 100644 --- a/epiphany-core/src/runtime_store_backend.rs +++ b/epiphany-core/src/runtime_store_backend.rs @@ -74,21 +74,6 @@ impl RuntimeSpineBackingStore { ), } } - - pub(crate) fn replace_and_append_if_snapshot_unchanged( - &self, - expected_snapshot: &[CultCacheEnvelope], - replacements: Vec, - ) -> Result { - match self.selected()? { - SelectedBackingStore::Snapshot(store) => { - store.replace_and_append_if_snapshot_unchanged(expected_snapshot, replacements) - } - SelectedBackingStore::Keyed(store) => { - store.replace_and_append_if_snapshot_unchanged(expected_snapshot, replacements) - } - } - } } impl CacheBackingStore for RuntimeSpineBackingStore { @@ -123,7 +108,11 @@ impl CacheBackingStore for RuntimeSpineBackingStore { pub(crate) fn runtime_spine_backing_store(path: &Path) -> Result { match path.extension().and_then(|extension| extension.to_str()) { - Some("cc" | "msgpack" | "redb") => Ok(RuntimeSpineBackingStore::new(path)), + Some("cc" | "msgpack" | "redb") => { + #[cfg(test)] + record_backing_store_resolution(path); + Ok(RuntimeSpineBackingStore::new(path)) + } extension => Err(anyhow!( "runtime spine store must use an explicit .cc, .msgpack, or .redb extension; got {:?}", extension @@ -131,6 +120,39 @@ pub(crate) fn runtime_spine_backing_store(path: &Path) -> Result> = + std::sync::Mutex::new(Vec::new()); + +#[cfg(test)] +fn record_backing_store_resolution(path: &Path) { + RESOLVED_BACKING_STORES + .lock() + .expect("the backing-store resolution log is never poisoned") + .push(path.to_path_buf()); +} + +/// How many times this path has been resolved into a handle. Tests run in +/// parallel, so a caller compares this around its own call against its own +/// temporary path rather than reading a global total. +#[cfg(test)] +pub(crate) fn backing_store_resolutions(path: &Path) -> usize { + RESOLVED_BACKING_STORES + .lock() + .expect("the backing-store resolution log is never poisoned") + .iter() + .filter(|resolved| resolved.as_path() == path) + .count() +} + #[cfg(test)] mod tests { use super::*; diff --git a/epiphany-core/src/runtime_worker_attempt.rs b/epiphany-core/src/runtime_worker_attempt.rs index bb52aa2d..e96ddc28 100644 --- a/epiphany-core/src/runtime_worker_attempt.rs +++ b/epiphany-core/src/runtime_worker_attempt.rs @@ -84,77 +84,50 @@ impl<'a> RuntimeTypedRequestRef<'a> { } } - pub(crate) fn matches_launch(self, launch: &crate::EpiphanyRuntimeWorkerLaunchRequest) -> bool { - match self { - Self::ProposalModeling(id) => { - launch.proposal_modeling_request_id.as_deref() == Some(id) - } - Self::FrontierVerdictModeling(id) => { - launch.repo_frontier_modeling_request_id.as_deref() == Some(id) - } - Self::FrontierResearch(id) => { - launch.repo_frontier_research_request_id.as_deref() == Some(id) - } - Self::FrontierVerification(id) => { - launch.repo_frontier_verification_request_id.as_deref() == Some(id) - } - Self::ImaginationConsideration(id) => { - launch.imagination_consideration_request_id.as_deref() == Some(id) - } - Self::AdmittedModelDirection(id) => { - launch - .admitted_model_direction_consideration_request_id - .as_deref() - == Some(id) - } - } - } - - pub(crate) fn matches_result(self, result: &crate::EpiphanyRuntimeRoleWorkerResult) -> bool { - match self { - Self::ProposalModeling(id) => { - result.proposal_modeling_request_id.as_deref() == Some(id) - } - Self::FrontierVerdictModeling(id) => { - result.repo_frontier_modeling_request_id.as_deref() == Some(id) - } - Self::FrontierResearch(id) => { - result.repo_frontier_research_request_id.as_deref() == Some(id) - } - Self::FrontierVerification(id) => result.verification_request_id.as_deref() == Some(id), - Self::ImaginationConsideration(id) => { - result.imagination_consideration_request_id.as_deref() == Some(id) - } - Self::AdmittedModelDirection(id) => { - result - .admitted_model_direction_consideration_request_id - .as_deref() - == Some(id) - } - } + pub(crate) fn matches_launch( + self, + launch: &crate::EpiphanyRuntimeWorkerLaunchRequest, + ) -> Result { + let document = launch.launch_document()?; + Ok(document.typed_request_ref()? == Some(self)) } } -impl crate::EpiphanyRuntimeWorkerLaunchRequest { +impl crate::EpiphanyWorkerLaunchDocument { pub(crate) fn typed_request_ref(&self) -> Result>> { + let crate::EpiphanyWorkerLaunchDocument::Role(document) = self else { + return Ok(None); + }; let requests = [ - self.proposal_modeling_request_id - .as_deref() + document + .proposal_modeling_context + .as_ref() + .map(|context| context.request_id.as_str()) .map(RuntimeTypedRequestRef::ProposalModeling), - self.repo_frontier_modeling_request_id - .as_deref() + document + .frontier_verdict_modeling_context + .as_ref() + .map(|context| context.request.request_id.as_str()) .map(RuntimeTypedRequestRef::FrontierVerdictModeling), - self.repo_frontier_research_request_id - .as_deref() + document + .frontier_research_context + .as_ref() + .map(|context| context.request_id.as_str()) .map(RuntimeTypedRequestRef::FrontierResearch), - self.repo_frontier_verification_request_id - .as_deref() + document + .frontier_verification_context + .as_ref() + .map(|context| context.request.request_id.as_str()) .map(RuntimeTypedRequestRef::FrontierVerification), - self.imagination_consideration_request_id - .as_deref() + document + .imagination_consideration_context + .as_ref() + .map(|context| context.request.request_id.as_str()) .map(RuntimeTypedRequestRef::ImaginationConsideration), - self.admitted_model_direction_consideration_request_id - .as_deref() + document + .admitted_model_direction_consideration_context + .as_ref() + .map(|context| context.request.request_id.as_str()) .map(RuntimeTypedRequestRef::AdmittedModelDirection), ] .into_iter() diff --git a/epiphany-core/src/soul_gateway.rs b/epiphany-core/src/soul_gateway.rs index b14fd0a2..93613a37 100644 --- a/epiphany-core/src/soul_gateway.rs +++ b/epiphany-core/src/soul_gateway.rs @@ -1,19 +1,8 @@ use cultcache_rs::DatabaseEntry; -pub const SOUL_VERDICT_RECEIPT_TYPE: &str = "epiphany.soul.verdict_receipt"; -pub const REPO_FRONTIER_VERIFICATION_REQUEST_TYPE: &str = - "epiphany.soul.repo_frontier_verification_request"; -pub const REPO_FRONTIER_VERIFICATION_REQUEST_SCHEMA_VERSION: &str = - "epiphany.soul.repo_frontier_verification_request.v2"; -pub const REPO_FRONTIER_VERIFICATION_REQUEST_CONTRACT: &str = - "epiphany.repo_frontier_verification_request.v2"; -pub const SOUL_VERDICT_RECEIPT_SCHEMA_VERSION: &str = "epiphany.soul.verdict_receipt.v1"; - #[derive(Debug, Clone, PartialEq, Eq, DatabaseEntry)] #[cultcache(type = "epiphany.soul.verdict_receipt", schema = "SoulVerdictReceipt")] pub struct SoulVerdictReceipt { - #[cultcache(key = 0)] - pub schema_version: String, #[cultcache(key = 1)] pub receipt_id: String, #[cultcache(key = 2)] @@ -30,8 +19,6 @@ pub struct SoulVerdictReceipt { pub risks: Vec, #[cultcache(key = 8)] pub emitted_at: String, - #[cultcache(key = 9)] - pub contract: String, #[cultcache(key = 10, default)] pub verification_request_id: String, #[cultcache(key = 11, default)] @@ -44,8 +31,6 @@ pub struct SoulVerdictReceipt { schema = "RepoFrontierVerificationRequest" )] pub struct RepoFrontierVerificationRequest { - #[cultcache(key = 0)] - pub schema_version: String, #[cultcache(key = 1)] pub request_id: String, #[cultcache(key = 2)] @@ -61,17 +46,13 @@ pub struct RepoFrontierVerificationRequest { #[cultcache(key = 7)] pub hands_intent_id: String, #[cultcache(key = 8)] - pub hands_review_id: String, - #[cultcache(key = 9)] pub hands_patch_receipt_id: String, - #[cultcache(key = 10)] + #[cultcache(key = 9)] pub hands_command_receipt_id: String, - #[cultcache(key = 11)] + #[cultcache(key = 10)] pub hands_commit_receipt_id: String, - #[cultcache(key = 12)] + #[cultcache(key = 11)] pub requested_at: String, - #[cultcache(key = 13)] - pub contract: String, - #[cultcache(key = 14)] + #[cultcache(key = 12)] pub frontier_authority_documents: Vec, } diff --git a/epiphany-core/src/state_ledger.rs b/epiphany-core/src/state_ledger.rs index ed5c1f93..7ae104bd 100644 --- a/epiphany-core/src/state_ledger.rs +++ b/epiphany-core/src/state_ledger.rs @@ -8,7 +8,6 @@ use serde::Serialize; use serde_json::Value; use std::path::Path; -pub const STATE_LEDGER_STORE_TYPE: &str = "epiphany.state_ledger"; pub const STATE_LEDGER_KEY: &str = "default"; pub const STATE_LEDGER_SCHEMA_VERSION: &str = "epiphany.state_ledger.v0"; @@ -43,34 +42,6 @@ pub struct EpiphanyLedgerEvidenceRecord { pub branch: Option, } -pub fn state_ledger_status(store_path: impl AsRef) -> Result { - let store_path = store_path.as_ref(); - if !store_path.exists() { - return Ok(serde_json::json!({ - "ok": false, - "present": false, - "store": store_path, - "entryType": STATE_LEDGER_STORE_TYPE, - })); - } - let entry = load_state_ledger(store_path)?; - let active = entry - .branches - .iter() - .filter(|branch| branch.status == "active") - .count(); - Ok(serde_json::json!({ - "ok": true, - "present": true, - "store": store_path, - "entryType": STATE_LEDGER_STORE_TYPE, - "branches": entry.branches.len(), - "activeBranches": active, - "evidence": entry.evidence.len(), - "latestEvidence": entry.evidence.iter().rev().take(8).collect::>(), - })) -} - pub fn load_state_ledger(store_path: impl AsRef) -> Result { let store_path = store_path.as_ref(); let mut cache = state_ledger_cache(store_path)?; @@ -162,7 +133,7 @@ pub fn close_state_branch( fn state_ledger_cache(store_path: &Path) -> Result { let mut cache = CultCache::new(); cache.register_entry_type::()?; - cache.add_generic_backing_store(SingleFileMessagePackBackingStore::new(store_path)); + cache.add_generic_backing_store(SingleFileMessagePackBackingStore::new(store_path))?; Ok(cache) } diff --git a/epiphany-core/src/state_model.rs b/epiphany-core/src/state_model.rs new file mode 100644 index 00000000..356811a3 --- /dev/null +++ b/epiphany-core/src/state_model.rs @@ -0,0 +1,302 @@ +//! Core-owned semantic state contracts. + +use std::path::PathBuf; + +use serde::Deserialize; +use serde::Serialize; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Default)] +pub struct EpiphanySubgoal { + pub id: String, + pub title: String, + pub status: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub summary: Option, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Default)] +pub struct EpiphanyInvariant { + pub id: String, + pub description: String, + pub status: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub rationale: Option, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Default)] +pub struct EpiphanyCodeRef { + pub path: PathBuf, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub start_line: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub end_line: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub symbol: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub note: Option, +} + +#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq, Default)] +#[serde(rename_all = "snake_case")] +pub enum EpiphanyInvestigationDisposition { + #[default] + ResumeReady, + RegatherRequired, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Default)] +pub struct EpiphanyInvestigationCheckpoint { + pub checkpoint_id: String, + pub kind: String, + pub disposition: EpiphanyInvestigationDisposition, + pub focus: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub summary: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub next_action: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub captured_at_turn_id: Option, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub open_questions: Vec, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub code_refs: Vec, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub evidence_ids: Vec, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Default)] +pub struct EpiphanyObservation { + pub id: String, + pub summary: String, + pub source_kind: String, + pub status: String, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub code_refs: Vec, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub evidence_ids: Vec, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Default)] +pub struct EpiphanyEvidenceRecord { + pub id: String, + pub kind: String, + pub status: String, + pub summary: String, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub code_refs: Vec, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Default)] +pub struct EpiphanyModeState { + pub name: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub kind: Option, +} + +#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq, Hash, Default)] +#[serde(rename_all = "snake_case")] +pub enum EpiphanyModeKind { + Plan, + #[default] + #[serde( + alias = "code", + alias = "pair_programming", + alias = "execute", + alias = "custom" + )] + Default, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Default)] +pub struct EpiphanyMemoryGraphSnapshot { + pub graph_id: String, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub domains: Vec, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub nodes: Vec, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub edges: Vec, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub frontier: Vec, +} + +/// A durable piece of unfinished repository anatomy. This is Modeling-owned state, +/// not a scheduler job or a presentation card. +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Default)] +pub struct RepoFrontierItem { + pub id: String, + pub migration_body: String, + pub question: String, + pub gap: String, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub target_claim_ids: Vec, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + /// Canonical repository-relative path ceiling for this wound. This is the + /// scope that downstream Planning may narrow and Hands may eventually + /// change; inspected files and evidence provenance belong elsewhere. + pub repository_scope: Vec, + pub recommended_next_organ: String, + /// The exact plan admitted by Mind for this frontier item. Imagination may + /// propose this payload, but only the dedicated model transition may make + /// it part of Modeling-owned truth. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub adopted_plan: Option, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub dependency_item_ids: Vec, + #[serde(default)] + pub status: RepoFrontierStatus, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub evidence_refs: Vec, + /// Exact immutable public sources selected upstream for Eyes. These are + /// causal retrieval inputs, not evidence produced by Modeling. + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub public_source_refs: Vec, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub created_at: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub updated_at: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub retired_at: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub superseded_by: Option, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Default)] +pub struct RepoFrontierAdoptedPlan { + pub planning_request_id: String, + pub result_id: String, + pub job_id: String, + pub candidate_id: String, + pub candidate_sha256: String, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub safe_paths: Vec, + pub action: String, + pub command: String, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub checks: Vec, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub stop_conditions: Vec, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub rollback_steps: Vec, + pub commit_message: String, +} + +#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq, Default)] +#[serde(rename_all = "snake_case")] +pub enum RepoFrontierStatus { + #[default] + Proposed, + Active, + Blocked, + Resolved, + Retired, + Superseded, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Default)] +pub struct EpiphanyMemoryDomain { + pub id: String, + pub title: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub description: Option, + #[serde(default)] + pub lifecycle: EpiphanyMemoryLifecycle, +} + +#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq, Hash, Default)] +#[serde(rename_all = "snake_case")] +pub enum EpiphanyMemoryLifecycle { + #[default] + Observed, + Proposed, + Accepted, + Retired, + Stale, +} + +#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq, Hash, Default)] +#[serde(rename_all = "snake_case")] +pub enum EpiphanyMemoryNodeKind { + #[default] + Domain, + Module, + Crate, + Binary, + Schema, + RuntimeContract, + Adapter, + TestSeam, + StateStore, + Summary, + Other, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Default)] +pub struct EpiphanyMemoryNode { + pub id: String, + pub domain_id: String, + pub kind: EpiphanyMemoryNodeKind, + pub title: String, + pub claim: String, + pub question: String, + pub tension: String, + pub action_implication: String, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub anchors: Vec, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub source_hashes: Vec, + #[serde(default)] + pub lifecycle: EpiphanyMemoryLifecycle, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub created_at: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub updated_at: Option, +} + +#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq, Hash, Default)] +#[serde(rename_all = "snake_case")] +pub enum EpiphanyMemoryEdgeKind { + #[default] + Owns, + Reads, + Writes, + Derives, + Adapts, + Persists, + Launches, + Verifies, + Supports, + Contradicts, + Grounds, + Triggers, + DependsOn, + Other, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Default)] +pub struct EpiphanyMemoryEdge { + pub id: String, + pub source_id: String, + pub target_id: String, + pub kind: EpiphanyMemoryEdgeKind, + pub claim: String, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub anchors: Vec, + #[serde(default)] + pub lifecycle: EpiphanyMemoryLifecycle, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Default)] +pub struct EpiphanyMemoryAnchor { + pub id: String, + pub kind: String, + pub target: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub code_ref: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub evidence_id: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub source_hash: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub note: Option, +} diff --git a/epiphany-core/src/substrate_gate.rs b/epiphany-core/src/substrate_gate.rs index 7e5feb00..1217db80 100644 --- a/epiphany-core/src/substrate_gate.rs +++ b/epiphany-core/src/substrate_gate.rs @@ -1,7 +1,5 @@ use cultcache_rs::DatabaseEntry; -pub const SUBSTRATE_GATE_REPO_ACCESS_GRANT_RECEIPT_TYPE: &str = - "epiphany.substrate_gate.repo_access_grant_receipt"; pub const SUBSTRATE_GATE_REPO_ACCESS_GRANT_RECEIPT_SCHEMA_VERSION: &str = "epiphany.substrate_gate.repo_access_grant_receipt.v0"; pub const SUBSTRATE_GATE_SOURCE_READ_OPERATION: &str = "read"; @@ -71,10 +69,7 @@ pub fn substrate_gate_operation_for_governed_tool( tool_name: &str, ) -> Option<&'static str> { match (server, tool_name) { - ( - "epiphany_source", - "read_file" | "directory_inventory" | "git_show" | "read_hands_receipt", - ) + ("epiphany_source", "read_file" | "directory_inventory" | "git_show") | ("epiphany_state", "resident_grant_lifecycle") => { Some(SUBSTRATE_GATE_SOURCE_READ_OPERATION) } @@ -83,26 +78,6 @@ pub fn substrate_gate_operation_for_governed_tool( } } -pub fn substrate_gate_repo_work_planning_grant( - receipt_id: String, - runtime_job_id: String, - granted_paths: Vec, - granted_at: String, -) -> SubstrateGateRepoAccessGrantReceipt { - SubstrateGateRepoAccessGrantReceipt { - schema_version: SUBSTRATE_GATE_REPO_ACCESS_GRANT_RECEIPT_SCHEMA_VERSION.to_string(), - receipt_id, - runtime_job_id, - binding_id: "repo-work-runner".to_string(), - role: "epiphany-hands".to_string(), - authority_scope: "repo.branch_local_work".to_string(), - granted_operations: vec!["read".to_string(), "snapshot".to_string()], - granted_paths, - granted_at, - contract: "Substrate Gate grants read/snapshot access for repo-work planning only; mutation awaits an approved Hands review.".to_string(), - } -} - pub fn substrate_gate_coordinator_implementation_grant( receipt_id: String, runtime_job_id: String, diff --git a/epiphany-core/src/surfaces/coordinator_contract.rs b/epiphany-core/src/surfaces/coordinator_contract.rs index 479e432b..642c1787 100644 --- a/epiphany-core/src/surfaces/coordinator_contract.rs +++ b/epiphany-core/src/surfaces/coordinator_contract.rs @@ -1,4 +1,3 @@ -use super::EpiphanyCrrcAction; use crate::EpiphanyCurrentWorkProjection; use serde::{Deserialize, Serialize}; @@ -12,20 +11,6 @@ pub enum EpiphanyCoordinatorRoleStatus { Blocked, } -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub enum EpiphanyCoordinatorRoleResultStatus { - MissingState, - MissingBinding, - BackendUnavailable, - BackendMissing, - Pending, - Running, - Completed, - Failed, - Cancelled, -} - #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub enum EpiphanyCoordinatorAction { @@ -60,7 +45,6 @@ pub enum EpiphanyCoordinatorAction { #[derive(Debug, Clone, PartialEq, Eq)] pub struct EpiphanyCoordinatorInput { pub mind_present: bool, - pub crrc_action: EpiphanyCrrcAction, pub current_work: EpiphanyCurrentWorkProjection, } diff --git a/epiphany-core/src/surfaces/coordinator_decision.rs b/epiphany-core/src/surfaces/coordinator_decision.rs index 92503752..15b4aa38 100644 --- a/epiphany-core/src/surfaces/coordinator_decision.rs +++ b/epiphany-core/src/surfaces/coordinator_decision.rs @@ -1,6 +1,6 @@ use crate::{ EpiphanyAgentPassContinuationAction, EpiphanyCoordinatorAction, EpiphanyCoordinatorDecision, - EpiphanyCoordinatorInput, EpiphanyCrrcAction, RepoFrontierPlanningLifecycleStage, + EpiphanyCoordinatorInput, RepoFrontierPlanningLifecycleStage, RepoFrontierResearchContinuationAction, }; @@ -24,7 +24,7 @@ pub fn recommend_coordinator_action( ); } - if input.crrc_action == EpiphanyCrrcAction::RegatherManually { + if input.current_work.operator_regather_required { return decision( EpiphanyCoordinatorAction::RegatherManually, "The accepted continuity decision requires explicit operator regather.", @@ -134,10 +134,9 @@ fn continuation_decision( launch, &format!("The exact {label} obligation has no live attempt."), ), - EpiphanyAgentPassContinuationAction::Wait => decision( - wait, - &format!("The exact {label} attempt is still live."), - ), + EpiphanyAgentPassContinuationAction::Wait => { + decision(wait, &format!("The exact {label} attempt is still live.")) + } EpiphanyAgentPassContinuationAction::Review => decision( review, &format!("The exact {label} result awaits its family admission owner."), diff --git a/epiphany-core/src/surfaces/crrc.rs b/epiphany-core/src/surfaces/crrc.rs deleted file mode 100644 index 76581140..00000000 --- a/epiphany-core/src/surfaces/crrc.rs +++ /dev/null @@ -1,27 +0,0 @@ -use serde::Deserialize; -use serde::Serialize; - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub enum EpiphanyCrrcResultStatus { - MissingState, - MissingBinding, - BackendUnavailable, - BackendMissing, - Pending, - Running, - Completed, - Failed, - Cancelled, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub enum EpiphanyCrrcAction { - Continue, - LaunchReorientWorker, - WaitForReorientWorker, - ReviewReorientResult, - AcceptReorientResult, - RegatherManually, -} diff --git a/epiphany-core/src/surfaces/mod.rs b/epiphany-core/src/surfaces/mod.rs index 80eb8eb9..1cbc767a 100644 --- a/epiphany-core/src/surfaces/mod.rs +++ b/epiphany-core/src/surfaces/mod.rs @@ -1,7 +1,5 @@ mod coordinator_contract; mod coordinator_decision; -mod crrc; -mod reorient; mod research_decision; mod role_board; mod role_result; @@ -9,9 +7,6 @@ mod worker_launch; pub use coordinator_contract::*; pub use coordinator_decision::recommend_coordinator_action; -pub use crrc::EpiphanyCrrcAction; -pub use crrc::EpiphanyCrrcResultStatus; -pub use reorient::EpiphanyReorientAction; pub use research_decision::EpiphanyResearchDecision; pub use role_board::EpiphanyRoleBoardInput; pub use role_board::EpiphanyRoleBoardLane; @@ -21,6 +16,8 @@ pub use role_result::EpiphanyRoleFindingInterpretation; pub use role_result::EpiphanyRoleResultRoleId; pub use role_result::interpret_runtime_reorient_worker_result; pub use role_result::interpret_runtime_role_worker_result; +pub use worker_launch::ADMITTED_MODEL_DIRECTION_CONSIDERATION_CONTEXT_CONTRACT; +pub use worker_launch::ADMITTED_MODEL_DIRECTION_CONSIDERATION_CONTEXT_SCHEMA_VERSION; pub use worker_launch::AdmittedModelDirectionConsiderationContextProjection; pub use worker_launch::EpiphanyReorientWorkerLaunchDocument; pub use worker_launch::EpiphanyRoleWorkerLaunchDocument; diff --git a/epiphany-core/src/surfaces/reorient.rs b/epiphany-core/src/surfaces/reorient.rs deleted file mode 100644 index adfc9033..00000000 --- a/epiphany-core/src/surfaces/reorient.rs +++ /dev/null @@ -1,13 +0,0 @@ -use serde::{Deserialize, Serialize}; - -/// Display projection of an accepted keyed continuity decision. -/// -/// This enum does not decide whether Reorientation should run. The keyed -/// current-work owner in `reorientation_work` owns launch, wait, review, and -/// admission behavior. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub enum EpiphanyReorientAction { - Resume, - Regather, -} diff --git a/epiphany-core/src/surfaces/research_decision.rs b/epiphany-core/src/surfaces/research_decision.rs index b4030934..e562044e 100644 --- a/epiphany-core/src/surfaces/research_decision.rs +++ b/epiphany-core/src/surfaces/research_decision.rs @@ -1,7 +1,7 @@ +use crate::state_model::EpiphanyEvidenceRecord; +use crate::state_model::EpiphanyInvestigationCheckpoint; +use crate::state_model::EpiphanyObservation; use anyhow::{Result, anyhow}; -use epiphany_state_model::EpiphanyEvidenceRecord; -use epiphany_state_model::EpiphanyInvestigationCheckpoint; -use epiphany_state_model::EpiphanyObservation; use serde::{Deserialize, Serialize}; use std::collections::BTreeSet; diff --git a/epiphany-core/src/surfaces/role_board.rs b/epiphany-core/src/surfaces/role_board.rs index d0d3cb59..80d2fa9a 100644 --- a/epiphany-core/src/surfaces/role_board.rs +++ b/epiphany-core/src/surfaces/role_board.rs @@ -21,20 +21,18 @@ pub struct EpiphanyRoleBoardLane { pub fn derive_role_board(input: EpiphanyRoleBoardInput) -> Vec { let missing = !input.mind_present; - let lane = |title: &str, status, note: &str| { - EpiphanyRoleBoardLane { - title: title.into(), - status: if missing { - EpiphanyCoordinatorRoleStatus::Blocked - } else { - status - }, - note: if missing { - "Canonical keyed Mind is missing.".into() - } else { - note.into() - }, - } + let lane = |title: &str, status, note: &str| EpiphanyRoleBoardLane { + title: title.into(), + status: if missing { + EpiphanyCoordinatorRoleStatus::Blocked + } else { + status + }, + note: if missing { + "Canonical keyed Mind is missing.".into() + } else { + note.into() + }, }; let modeling_action = input diff --git a/epiphany-core/src/surfaces/role_result.rs b/epiphany-core/src/surfaces/role_result.rs index 740cd055..fc700c16 100644 --- a/epiphany-core/src/surfaces/role_result.rs +++ b/epiphany-core/src/surfaces/role_result.rs @@ -23,7 +23,7 @@ pub struct EpiphanyRoleFindingInterpretation { pub frontier_node_ids: Vec, pub evidence_ids: Vec, pub artifact_refs: Vec, - pub runtime_result_id: Option, + pub result_id: Option, pub runtime_job_id: Option, pub open_questions: Vec, pub evidence_gaps: Vec, @@ -32,9 +32,6 @@ pub struct EpiphanyRoleFindingInterpretation { pub repo_model_mutation_proposal: Option, pub job_error: Option, pub item_error: Option, - pub verification_request_id: Option, - pub frontier_route_id: Option, - pub proposal_modeling_request_id: Option, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] @@ -48,7 +45,7 @@ pub struct EpiphanyReorientFindingInterpretation { pub frontier_node_ids: Vec, pub evidence_ids: Vec, pub artifact_refs: Vec, - pub runtime_result_id: Option, + pub result_id: Option, pub runtime_job_id: Option, pub job_error: Option, pub item_error: Option, @@ -92,7 +89,7 @@ pub fn interpret_runtime_role_worker_result( frontier_node_ids: result.frontier_node_ids.clone(), evidence_ids: result.evidence_ids.clone(), artifact_refs: result.artifact_refs.clone(), - runtime_result_id: Some(result.result_id.clone()), + result_id: Some(result.result_id.clone()), runtime_job_id: Some(result.job_id.clone()), open_questions: result.open_questions.clone(), evidence_gaps: result.evidence_gaps.clone(), @@ -122,9 +119,6 @@ pub fn interpret_runtime_role_worker_result( | EpiphanyRoleResultRoleId::Verification | EpiphanyRoleResultRoleId::Reorientation => item_error, }, - verification_request_id: result.verification_request_id.clone(), - frontier_route_id: result.frontier_route_id.clone(), - proposal_modeling_request_id: result.proposal_modeling_request_id.clone(), } } @@ -140,7 +134,7 @@ pub fn interpret_runtime_reorient_worker_result( frontier_node_ids: result.frontier_node_ids.clone(), evidence_ids: result.evidence_ids.clone(), artifact_refs: result.artifact_refs.clone(), - runtime_result_id: Some(result.result_id.clone()), + result_id: Some(result.result_id.clone()), runtime_job_id: Some(result.job_id.clone()), job_error: None, item_error: result.item_error.clone(), diff --git a/epiphany-core/src/surfaces/worker_launch.rs b/epiphany-core/src/surfaces/worker_launch.rs index 5b9464bb..e8a8adce 100644 --- a/epiphany-core/src/surfaces/worker_launch.rs +++ b/epiphany-core/src/surfaces/worker_launch.rs @@ -1,7 +1,7 @@ use serde::Deserialize; use serde::Serialize; -pub const ROLE_WORKER_OUTPUT_CONTRACT_ID: &str = "epiphany.worker.role_result.v4"; +pub const ROLE_WORKER_OUTPUT_CONTRACT_ID: &str = "epiphany.worker.role_result.v5"; pub const REORIENT_WORKER_OUTPUT_CONTRACT_ID: &str = "epiphany.worker.reorient_result.v0"; pub const REPO_FRONTIER_PROPOSAL_MODELING_CONTEXT_SCHEMA_VERSION: &str = "epiphany.worker.repo_frontier_proposal_modeling_context.v4"; @@ -16,9 +16,9 @@ pub const REPO_FRONTIER_RESEARCH_CONTEXT_SCHEMA_VERSION: &str = pub const REPO_FRONTIER_RESEARCH_CONTEXT_CONTRACT: &str = "epiphany.repo_frontier_research_context.v3"; pub const REPO_FRONTIER_VERIFICATION_CONTEXT_SCHEMA_VERSION: &str = - "epiphany.worker.repo_frontier_verification_context.v1"; + "epiphany.worker.repo_frontier_verification_context.v2"; pub const REPO_FRONTIER_VERIFICATION_CONTEXT_CONTRACT: &str = - "epiphany.repo_frontier_verification_context.v1"; + "epiphany.repo_frontier_verification_context.v2"; pub const REPO_FRONTIER_PLAN_MIND_CONTEXT_SCHEMA_VERSION: &str = "epiphany.worker.repo_frontier_plan_mind_context.v0"; pub const REPO_FRONTIER_PLAN_MIND_CONTEXT_CONTRACT: &str = @@ -102,6 +102,25 @@ pub struct EpiphanyRoleWorkerLaunchDocument { Option, } +impl EpiphanyRoleWorkerLaunchDocument { + pub(crate) fn family_context_count(&self) -> usize { + [ + self.proposal_modeling_context.is_some(), + self.frontier_verdict_modeling_context.is_some(), + self.frontier_planning_context.is_some(), + self.frontier_research_context.is_some(), + self.frontier_verification_context.is_some(), + self.frontier_plan_mind_context.is_some(), + self.imagination_consideration_context.is_some(), + self.admitted_model_direction_consideration_context + .is_some(), + ] + .into_iter() + .filter(|present| *present) + .count() + } +} + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct RepoFrontierResearchContextProjection { @@ -222,7 +241,6 @@ pub struct RepoFrontierVerificationContextProjection { pub route: crate::RepoFrontierRoute, pub hands_authority: crate::RepoFrontierHandsAuthority, pub hands_intent: crate::HandsActionIntent, - pub hands_review: crate::HandsActionReview, pub patch_receipt: crate::HandsPatchReceipt, pub command_receipt: crate::HandsCommandReceipt, pub commit_receipt: crate::HandsCommitReceipt, diff --git a/epiphany-model-adapter/Cargo.toml b/epiphany-model-adapter/Cargo.toml index f1a9badf..693650ea 100644 --- a/epiphany-model-adapter/Cargo.toml +++ b/epiphany-model-adapter/Cargo.toml @@ -10,5 +10,9 @@ name = "epiphany_model_adapter" path = "src/lib.rs" [dependencies] -cultcache-rs = { path = "../vendor/cultcache-rs" } +anyhow = "1" +codex-connector = { git = "https://github.com/GameCult/CodexConnector", rev = "be1776ab455fd79bd74b393518ab07211dc9c1f0", default-features = false } +cultcache-rs = { git = "https://github.com/GameCult/CultLib.git", rev = "a0813c6eed24d30bf88073ef615b633c77ebfcd6", package = "cultcache-rs" } serde = { version = "1", features = ["derive"] } +serde_json = "1" +sha2 = "0.10" diff --git a/epiphany-model-adapter/src/lib.rs b/epiphany-model-adapter/src/lib.rs index 8abea7e0..6d5f59fc 100644 --- a/epiphany-model-adapter/src/lib.rs +++ b/epiphany-model-adapter/src/lib.rs @@ -1,298 +1,5 @@ -use cultcache_rs::DatabaseEntry; -use serde::Deserialize; -use serde::Serialize; +mod native; +mod provider; -pub const MODEL_ADAPTER_REQUEST_SCHEMA_ID: &str = "epiphany.model_request.v0"; -pub const MODEL_ADAPTER_EVENT_SCHEMA_ID: &str = "epiphany.model_stream_event.v0"; -pub const MODEL_ADAPTER_RECEIPT_SCHEMA_ID: &str = "epiphany.model_receipt.v0"; -pub const MODEL_ADAPTER_STATUS_SCHEMA_ID: &str = "epiphany.model_adapter_status.v0"; -pub const MODEL_CONNECTOR_ENVELOPE_SCHEMA_ID: &str = "epiphany.model_connector_envelope.v1"; -pub const MODEL_CONNECTOR_INVOCATION_SCHEMA_ID: &str = "epiphany.model_connector_invocation.v1"; -pub const MODEL_CONNECTOR_RESULT_SCHEMA_ID: &str = "epiphany.model_connector_result.v1"; -pub const MODEL_CONNECTOR_STATUS_SCHEMA_ID: &str = "epiphany.model_connector_status.v1"; - -#[derive(Debug, Clone, PartialEq, Eq, DatabaseEntry)] -#[cultcache( - type = "epiphany.model_adapter_status.v0", - schema = "EpiphanyModelAdapterStatus" -)] -pub struct EpiphanyModelAdapterStatus { - #[cultcache(key = 0)] - pub schema_id: String, - #[cultcache(key = 1)] - pub adapter_id: String, - #[cultcache(key = 2)] - pub provider: String, - #[cultcache(key = 3, default)] - pub default_model: Option, - #[cultcache(key = 4)] - pub streaming_supported: bool, - #[cultcache(key = 5)] - pub provider_transport_attached: bool, -} - -#[derive(Debug, Clone, PartialEq, DatabaseEntry)] -#[cultcache(type = "epiphany.model_request.v0", schema = "EpiphanyModelRequest")] -pub struct EpiphanyModelRequest { - #[cultcache(key = 0)] - pub schema_id: String, - #[cultcache(key = 1)] - pub request_id: String, - #[cultcache(key = 2)] - pub conversation_id: String, - #[cultcache(key = 3)] - pub provider: String, - #[cultcache(key = 4)] - pub model: String, - #[cultcache(key = 5)] - pub instructions: String, - #[cultcache(key = 6, default)] - pub input: Vec, - #[cultcache(key = 7, default)] - pub reasoning_effort: Option, - #[cultcache(key = 8, default)] - pub reasoning_summary: Option, - #[cultcache(key = 9, default)] - pub service_tier: Option, - #[cultcache(key = 10, default)] - pub output_contract_id: Option, - #[cultcache(key = 11, default)] - pub previous_response_id: Option, - #[cultcache(key = 12, default)] - pub tools: Vec, - #[cultcache(key = 13, default)] - pub output_schema_json: Option, - #[cultcache(key = 14, default)] - pub source_worker_job_id: Option, - #[cultcache(key = 15, default)] - pub reasoning_basis_id: Option, - #[cultcache(key = 16, default)] - pub max_output_tokens: Option, - #[cultcache(key = 17, default)] - pub prompt_cache_key: Option, -} - -impl EpiphanyModelRequest { - pub fn new( - request_id: impl Into, - conversation_id: impl Into, - provider: impl Into, - model: impl Into, - instructions: impl Into, - ) -> Self { - Self { - schema_id: MODEL_ADAPTER_REQUEST_SCHEMA_ID.to_string(), - request_id: request_id.into(), - conversation_id: conversation_id.into(), - provider: provider.into(), - model: model.into(), - instructions: instructions.into(), - input: Vec::new(), - reasoning_effort: None, - reasoning_summary: None, - service_tier: None, - output_contract_id: None, - previous_response_id: None, - tools: Vec::new(), - output_schema_json: None, - source_worker_job_id: None, - reasoning_basis_id: None, - max_output_tokens: None, - prompt_cache_key: None, - } - } -} - -/// Encrypted CultNet cargo. The correlation fields are deliberately repeated -/// inside the authenticated plaintext and must match after decryption. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct EpiphanyModelConnectorEnvelope { - pub schema_id: String, - pub request_id: String, - pub message_kind: String, - pub nonce: Vec, - pub ciphertext: Vec, -} - -#[derive(Debug, Clone, PartialEq, DatabaseEntry)] -#[cultcache( - type = "epiphany.model_connector_invocation.v1", - schema = "EpiphanyModelConnectorInvocation" -)] -pub struct EpiphanyModelConnectorInvocation { - #[cultcache(key = 0)] - pub schema_id: String, - #[cultcache(key = 1)] - pub request_id: String, - #[cultcache(key = 2)] - pub caller_runtime_id: String, - #[cultcache(key = 3)] - pub expires_at_unix_ms: u64, - #[cultcache(key = 4)] - pub request: EpiphanyModelRequest, -} - -#[derive(Debug, Clone, PartialEq, DatabaseEntry)] -#[cultcache( - type = "epiphany.model_connector_result.v1", - schema = "EpiphanyModelConnectorResult" -)] -pub struct EpiphanyModelConnectorResult { - #[cultcache(key = 0)] - pub schema_id: String, - #[cultcache(key = 1)] - pub request_id: String, - #[cultcache(key = 2)] - pub accepted: bool, - #[cultcache(key = 3, default)] - pub events: Vec, - #[cultcache(key = 4, default)] - pub error: Option, -} - -#[derive(Debug, Clone, PartialEq, Eq, DatabaseEntry)] -#[cultcache( - type = "epiphany.model_connector_status.v1", - schema = "EpiphanyModelConnectorStatus" -)] -pub struct EpiphanyModelConnectorStatus { - #[cultcache(key = 0)] - pub schema_id: String, - #[cultcache(key = 1)] - pub provider_id: String, - #[cultcache(key = 2)] - pub capability_id: String, - #[cultcache(key = 3)] - pub request_schema_id: String, - #[cultcache(key = 4)] - pub result_schema_id: String, - #[cultcache(key = 5)] - pub envelope_schema_id: String, - #[cultcache(key = 6)] - pub transport_protocol: String, - #[cultcache(key = 7)] - pub host: String, - #[cultcache(key = 8)] - pub port: u16, - #[cultcache(key = 9)] - pub max_payload_bytes: u32, - #[cultcache(key = 10)] - pub max_parallel_requests: u32, - #[cultcache(key = 11)] - pub model: String, - #[cultcache(key = 12)] - pub ready: bool, - #[cultcache(key = 13)] - pub updated_at_utc: String, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct EpiphanyModelToolDefinition { - pub name: String, - pub description: String, - pub parameters_json: String, -} - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub enum EpiphanyModelInputItem { - UserText { - text: String, - }, - AssistantText { - text: String, - }, - ToolCall { - call_id: String, - name: String, - arguments: String, - }, - ToolResult { - call_id: String, - output: String, - }, -} - -#[derive(Debug, Clone, PartialEq, DatabaseEntry)] -#[cultcache( - type = "epiphany.model_stream_event.v0", - schema = "EpiphanyModelStreamEvent" -)] -pub struct EpiphanyModelStreamEvent { - #[cultcache(key = 0)] - pub schema_id: String, - #[cultcache(key = 1)] - pub request_id: String, - #[cultcache(key = 2)] - pub provider: String, - #[cultcache(key = 3)] - pub sequence: u64, - #[cultcache(key = 4)] - pub payload: EpiphanyModelStreamPayload, -} - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub enum EpiphanyModelStreamPayload { - TextDelta { - text: String, - }, - ReasoningDelta { - text: String, - }, - ToolCall { - call_id: String, - name: String, - arguments: String, - }, - Completed { - receipt: EpiphanyModelReceipt, - }, - Failed { - message: String, - }, -} - -#[derive(Debug, Clone, PartialEq, Eq, DatabaseEntry)] -#[cultcache(type = "epiphany.model_receipt.v0", schema = "EpiphanyModelReceipt")] -pub struct EpiphanyModelReceipt { - #[cultcache(key = 0)] - pub schema_id: String, - #[cultcache(key = 1)] - pub request_id: String, - #[cultcache(key = 2)] - pub provider: String, - #[cultcache(key = 3)] - pub model: String, - #[cultcache(key = 4, default)] - pub provider_response_id: Option, - #[cultcache(key = 5, default)] - pub input_tokens: Option, - #[cultcache(key = 6, default)] - pub output_tokens: Option, - #[cultcache(key = 7, default)] - pub reasoning_output_tokens: Option, - #[cultcache(key = 8, default)] - pub transport: Option, - #[cultcache(key = 9, default)] - pub cached_input_tokens: Option, -} - -impl EpiphanyModelReceipt { - pub fn new( - request_id: impl Into, - provider: impl Into, - model: impl Into, - ) -> Self { - Self { - schema_id: MODEL_ADAPTER_RECEIPT_SCHEMA_ID.to_string(), - request_id: request_id.into(), - provider: provider.into(), - model: model.into(), - provider_response_id: None, - input_tokens: None, - output_tokens: None, - reasoning_output_tokens: None, - transport: None, - cached_input_tokens: None, - } - } -} +pub use native::*; +pub use provider::*; diff --git a/epiphany-model-adapter/src/native.rs b/epiphany-model-adapter/src/native.rs new file mode 100644 index 00000000..7507717e --- /dev/null +++ b/epiphany-model-adapter/src/native.rs @@ -0,0 +1,192 @@ +use cultcache_rs::DatabaseEntry; +use serde::Deserialize; +use serde::Serialize; + +pub const MODEL_ADAPTER_REQUEST_SCHEMA_ID: &str = "epiphany.model_request.v0"; +pub const MODEL_ADAPTER_EVENT_SCHEMA_ID: &str = "epiphany.model_stream_event.v0"; +pub const MODEL_ADAPTER_RECEIPT_SCHEMA_ID: &str = "epiphany.model_receipt.v1"; +#[derive(Debug, Clone, PartialEq, DatabaseEntry)] +#[cultcache(type = "epiphany.model_request.v0", schema = "EpiphanyModelRequest")] +pub struct EpiphanyModelRequest { + #[cultcache(key = 0)] + pub schema_id: String, + #[cultcache(key = 1)] + pub request_id: String, + #[cultcache(key = 2)] + pub conversation_id: String, + #[cultcache(key = 3)] + pub provider: String, + #[cultcache(key = 4)] + pub model: String, + #[cultcache(key = 5)] + pub instructions: String, + #[cultcache(key = 6, default)] + pub input: Vec, + #[cultcache(key = 7, default)] + pub reasoning_effort: Option, + #[cultcache(key = 8, default)] + pub reasoning_summary: Option, + #[cultcache(key = 9, default)] + pub service_tier: Option, + #[cultcache(key = 10, default)] + pub output_contract_id: Option, + #[cultcache(key = 11, default)] + pub previous_response_id: Option, + #[cultcache(key = 12, default)] + pub tools: Vec, + #[cultcache(key = 13, default)] + pub output_schema_json: Option, + #[cultcache(key = 14, default)] + pub source_worker_job_id: Option, + #[cultcache(key = 15, default)] + pub reasoning_basis_id: Option, +} + +impl EpiphanyModelRequest { + pub fn new( + request_id: impl Into, + conversation_id: impl Into, + provider: impl Into, + model: impl Into, + instructions: impl Into, + ) -> Self { + Self { + schema_id: MODEL_ADAPTER_REQUEST_SCHEMA_ID.to_string(), + request_id: request_id.into(), + conversation_id: conversation_id.into(), + provider: provider.into(), + model: model.into(), + instructions: instructions.into(), + input: Vec::new(), + reasoning_effort: None, + reasoning_summary: None, + service_tier: None, + output_contract_id: None, + previous_response_id: None, + tools: Vec::new(), + output_schema_json: None, + source_worker_job_id: None, + reasoning_basis_id: None, + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct EpiphanyModelToolDefinition { + pub name: String, + pub description: String, + pub parameters_json: String, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum EpiphanyModelInputItem { + UserText { + text: String, + }, + AssistantText { + text: String, + }, + ToolCall { + call_id: String, + name: String, + arguments: String, + }, + ToolResult { + call_id: String, + output: String, + }, +} + +#[derive(Debug, Clone, PartialEq, DatabaseEntry)] +#[cultcache( + type = "epiphany.model_stream_event.v0", + schema = "EpiphanyModelStreamEvent" +)] +pub struct EpiphanyModelStreamEvent { + #[cultcache(key = 0)] + pub schema_id: String, + #[cultcache(key = 1)] + pub request_id: String, + #[cultcache(key = 2)] + pub provider: String, + #[cultcache(key = 3)] + pub sequence: u64, + #[cultcache(key = 4)] + pub payload: EpiphanyModelStreamPayload, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum EpiphanyModelStreamPayload { + TextDelta { + text: String, + }, + ReasoningDelta { + text: String, + }, + ToolCall { + call_id: String, + name: String, + arguments: String, + }, + Completed { + receipt: Box, + }, + Failed { + message: String, + }, +} + +#[derive(Debug, Clone, PartialEq, Eq, DatabaseEntry)] +#[cultcache(type = "epiphany.model_receipt.v1", schema = "EpiphanyModelReceipt")] +pub struct EpiphanyModelReceipt { + #[cultcache(key = 0)] + pub schema_id: String, + #[cultcache(key = 1)] + pub request_id: String, + #[cultcache(key = 2)] + pub provider: String, + #[cultcache(key = 3)] + pub model: String, + #[cultcache(key = 4, default)] + pub provider_response_id: Option, + #[cultcache(key = 5, default)] + pub input_tokens: Option, + #[cultcache(key = 6, default)] + pub output_tokens: Option, + #[cultcache(key = 7, default)] + pub reasoning_output_tokens: Option, + #[cultcache(key = 8, default)] + pub transport: Option, + #[cultcache(key = 9, default)] + pub caller_runtime_id: Option, + #[cultcache(key = 10, default)] + pub native_request_sha256: Option, + #[cultcache(key = 11, default)] + pub provider_request_sha256: Option, + #[cultcache(key = 12, default)] + pub cached_input_tokens: Option, +} + +impl EpiphanyModelReceipt { + pub fn new( + request_id: impl Into, + provider: impl Into, + model: impl Into, + ) -> Self { + Self { + schema_id: MODEL_ADAPTER_RECEIPT_SCHEMA_ID.to_string(), + request_id: request_id.into(), + provider: provider.into(), + model: model.into(), + provider_response_id: None, + input_tokens: None, + output_tokens: None, + reasoning_output_tokens: None, + transport: None, + caller_runtime_id: None, + native_request_sha256: None, + provider_request_sha256: None, + cached_input_tokens: None, + } + } +} diff --git a/epiphany-model-adapter/src/provider.rs b/epiphany-model-adapter/src/provider.rs new file mode 100644 index 00000000..da1a0a35 --- /dev/null +++ b/epiphany-model-adapter/src/provider.rs @@ -0,0 +1,922 @@ +use crate::{ + EpiphanyModelInputItem, EpiphanyModelReceipt, EpiphanyModelRequest, EpiphanyModelStreamEvent, + EpiphanyModelStreamPayload, EpiphanyModelToolDefinition, MODEL_ADAPTER_EVENT_SCHEMA_ID, +}; +use anyhow::{Result, anyhow}; +use codex_connector::{CodexInputItem, CodexProviderRequest, CodexToolChoice, CodexToolDefinition}; +use cultcache_rs::DatabaseEntry; +use serde::Deserialize; +use serde::Serialize; +use sha2::{Digest, Sha256}; + +#[derive(Debug, Clone, PartialEq, DatabaseEntry)] +#[cultcache( + type = "epiphany.provider_request.v2", + schema = "EpiphanyProviderRequest" +)] +pub struct EpiphanyProviderRequest { + #[cultcache(key = 0)] + pub payload: EpiphanyProviderRequestPayload, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum EpiphanyProviderRequestPayload { + Codex(CodexProviderRequest), + OpenRouter(EpiphanyOpenRouterRequest), +} + +impl EpiphanyProviderRequest { + pub fn request_id(&self) -> &str { + match &self.payload { + EpiphanyProviderRequestPayload::Codex(request) => &request.request_id, + EpiphanyProviderRequestPayload::OpenRouter(request) => &request.request_id, + } + } + + pub fn conversation_id(&self) -> &str { + match &self.payload { + EpiphanyProviderRequestPayload::Codex(request) => &request.conversation_id, + EpiphanyProviderRequestPayload::OpenRouter(request) => &request.conversation_id, + } + } + + pub fn model(&self) -> &str { + match &self.payload { + EpiphanyProviderRequestPayload::Codex(request) => &request.model, + EpiphanyProviderRequestPayload::OpenRouter(request) => &request.model, + } + } +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct EpiphanyOpenRouterRequest { + pub request_id: String, + pub conversation_id: String, + pub model: String, + pub instructions: String, + pub input: Vec, + pub reasoning_effort: Option, + pub reasoning_summary: Option, + pub service_tier: Option, + pub output_contract_id: Option, + pub previous_response_id: Option, + pub tools: Vec, + pub output_schema_json: Option, +} + +impl EpiphanyOpenRouterRequest { + pub fn new( + request_id: impl Into, + conversation_id: impl Into, + model: impl Into, + instructions: impl Into, + ) -> Self { + Self { + request_id: request_id.into(), + conversation_id: conversation_id.into(), + model: model.into(), + instructions: instructions.into(), + input: Vec::new(), + reasoning_effort: None, + reasoning_summary: None, + service_tier: None, + output_contract_id: None, + previous_response_id: None, + tools: Vec::new(), + output_schema_json: None, + } + } +} + +/// The only lowering from a native model request to an exact provider request. +pub fn request_from_native(request: &EpiphanyModelRequest) -> Result { + let payload = match request.provider.as_str() { + "openai-codex" | "openai" => { + EpiphanyProviderRequestPayload::Codex(codex_request_from_native(request)?) + } + "openrouter" => { + EpiphanyProviderRequestPayload::OpenRouter(openrouter_request_from_native(request)) + } + _ => return Err(anyhow!("unsupported model provider {:?}", request.provider)), + }; + Ok(EpiphanyProviderRequest { payload }) +} + +fn openrouter_request_from_native(request: &EpiphanyModelRequest) -> EpiphanyOpenRouterRequest { + EpiphanyOpenRouterRequest { + request_id: request.request_id.clone(), + conversation_id: request.conversation_id.clone(), + model: request.model.clone(), + instructions: request.instructions.clone(), + input: request.input.clone(), + reasoning_effort: request.reasoning_effort.clone(), + reasoning_summary: request.reasoning_summary.clone(), + service_tier: request.service_tier.clone(), + output_contract_id: request.output_contract_id.clone(), + previous_response_id: request.previous_response_id.clone(), + tools: request.tools.clone(), + output_schema_json: request.output_schema_json.clone(), + } +} + +fn codex_request_from_native(request: &EpiphanyModelRequest) -> Result { + let has_tool_result = request + .input + .iter() + .any(|item| matches!(item, EpiphanyModelInputItem::ToolResult { .. })); + let output_format_name = request.output_schema_json.as_ref().map(|_| { + provider_format_name( + request + .output_contract_id + .as_deref() + .unwrap_or(&request.request_id), + ) + }); + let output_schema_json = request + .output_schema_json + .as_deref() + .map(strict_provider_schema) + .transpose()? + .map(|schema| serde_json::to_string(&schema)) + .transpose()?; + let tools = request + .tools + .iter() + .map(|tool| { + Ok(CodexToolDefinition { + name: tool.name.clone(), + description: tool.description.clone(), + parameters_json: serde_json::to_string(&strict_provider_schema( + &tool.parameters_json, + )?)?, + }) + }) + .collect::>>()?; + let mut provider = CodexProviderRequest::new( + &request.request_id, + &request.conversation_id, + &request.model, + &request.instructions, + ); + provider.input = request.input.iter().map(codex_input_from_native).collect(); + provider.reasoning_effort = request.reasoning_effort.clone(); + provider.reasoning_summary = request.reasoning_summary.clone(); + provider.service_tier = request.service_tier.clone(); + provider.output_format_name = output_format_name; + provider.previous_response_id = request.previous_response_id.clone(); + provider.tools = tools; + provider.tool_choice = if !provider.tools.is_empty() && !has_tool_result { + CodexToolChoice::Required + } else { + CodexToolChoice::Auto + }; + provider.output_schema_json = output_schema_json; + provider.validate()?; + Ok(provider) +} + +fn codex_input_from_native(input: &EpiphanyModelInputItem) -> CodexInputItem { + match input { + EpiphanyModelInputItem::UserText { text } => { + CodexInputItem::UserText { text: text.clone() } + } + EpiphanyModelInputItem::AssistantText { text } => { + CodexInputItem::AssistantText { text: text.clone() } + } + EpiphanyModelInputItem::ToolCall { + call_id, + name, + arguments, + } => CodexInputItem::ToolCall { + call_id: provider_call_id(call_id), + name: name.clone(), + arguments: arguments.clone(), + }, + EpiphanyModelInputItem::ToolResult { call_id, output } => CodexInputItem::ToolResult { + call_id: provider_call_id(call_id), + output: output.clone(), + }, + } +} + +/// Project Epiphany's canonical JSON Schema into the strict subset accepted by +/// model providers. Native decoding remains the final validation authority. +pub fn strict_provider_schema(schema_json: &str) -> anyhow::Result { + let mut schema = serde_json::from_str(schema_json) + .map_err(|error| anyhow::anyhow!("provider schema is not valid JSON Schema: {error}"))?; + lower_provider_schema(&mut schema); + close_provider_objects(&mut schema, "$")?; + if !provider_schema_is_strict(&schema) { + return Err(anyhow::anyhow!( + "projected provider output schema is not strict" + )); + } + Ok(schema) +} + +/// Provider-safe output-format identity derived from the native contract. +pub fn provider_format_name(value: &str) -> String { + let mut name = value + .chars() + .map(|ch| { + if ch.is_ascii_alphanumeric() || matches!(ch, '_' | '-') { + ch + } else { + '_' + } + }) + .take(64) + .collect::(); + if name.is_empty() { + name.push_str("epiphany_worker_result"); + } + name +} + +/// Responses call IDs have a narrow alphabet and byte bound. Preserve already +/// legal IDs and derive a stable identity for every other native call ID. +pub fn provider_call_id(value: &str) -> String { + if !value.is_empty() + && value.len() <= 64 + && value + .bytes() + .all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'_' | b'-')) + { + return value.to_string(); + } + let digest = Sha256::digest(value.as_bytes()); + let mut projected = String::with_capacity(64); + for byte in digest { + use std::fmt::Write as _; + write!(&mut projected, "{byte:02x}").expect("writing to a String cannot fail"); + } + projected +} + +pub const OPENROUTER_TERMINAL_TOOL_NAME: &str = "epiphany_submit_typed_result"; + +/// Lower one typed Epiphany request to OpenRouter's terminal-tool dialect. +pub fn openrouter_request_body( + request: &EpiphanyOpenRouterRequest, +) -> anyhow::Result { + let has_tool_result = request + .input + .iter() + .any(|item| matches!(item, EpiphanyModelInputItem::ToolResult { .. })); + let output_schema = request + .output_schema_json + .as_deref() + .map(strict_provider_schema) + .transpose()?; + let include_terminal_tool = + output_schema.is_some() && (request.tools.is_empty() || has_tool_result); + if request + .tools + .iter() + .any(|tool| tool.name == OPENROUTER_TERMINAL_TOOL_NAME) + { + return Err(anyhow::anyhow!( + "native tool name collides with the OpenRouter terminal decision tool" + )); + } + + let mut messages = vec![serde_json::json!({ + "role": "system", + "content": request.instructions, + })]; + for item in &request.input { + messages.push(match item { + EpiphanyModelInputItem::UserText { text } => { + serde_json::json!({"role": "user", "content": text}) + } + EpiphanyModelInputItem::AssistantText { text } => { + serde_json::json!({"role": "assistant", "content": text}) + } + EpiphanyModelInputItem::ToolCall { + call_id, + name, + arguments, + } => serde_json::json!({ + "role": "assistant", + "tool_calls": [{ + "id": call_id, + "type": "function", + "function": {"name": name, "arguments": arguments}, + }], + }), + EpiphanyModelInputItem::ToolResult { call_id, output } => serde_json::json!({ + "role": "tool", + "tool_call_id": call_id, + "content": output, + }), + }); + } + + let mut tools = request + .tools + .iter() + .map(|tool| { + Ok(serde_json::json!({ + "type": "function", + "function": { + "name": tool.name, + "description": tool.description, + "strict": true, + "parameters": strict_provider_schema(&tool.parameters_json)?, + }, + })) + }) + .collect::>>()?; + if include_terminal_tool { + tools.push(serde_json::json!({ + "type": "function", + "function": { + "name": OPENROUTER_TERMINAL_TOOL_NAME, + "description": "Submit the final typed Epiphany decision. Call this only when the pass is complete.", + "strict": true, + "parameters": output_schema.expect("terminal schema exists"), + }, + })); + } + + let mut body = serde_json::json!({ + "model": request.model, + "messages": messages, + "stream": false, + "parallel_tool_calls": false, + }); + if !tools.is_empty() { + body["tools"] = serde_json::Value::Array(tools); + body["tool_choice"] = serde_json::Value::String( + if include_terminal_tool || (!request.tools.is_empty() && !has_tool_result) { + "required" + } else { + "auto" + } + .into(), + ); + } + if let Some(effort) = request.reasoning_effort.as_deref() { + body["reasoning"] = serde_json::json!({"effort": effort}); + } + Ok(body) +} + +/// Interpret one non-streaming OpenRouter response into Epiphany's transient +/// event family. The model transcript remains optional; the terminal receipt +/// is authoritative transport evidence. +pub fn openrouter_events_from_response( + request: &EpiphanyOpenRouterRequest, + response_bytes: &[u8], +) -> anyhow::Result> { + let response: OpenRouterResponse = serde_json::from_slice(response_bytes) + .map_err(|error| anyhow::anyhow!("OpenRouter returned an invalid response: {error}"))?; + let choice = response + .choices + .into_iter() + .next() + .ok_or_else(|| anyhow::anyhow!("OpenRouter response contained no completion choice"))?; + let mut events = Vec::new(); + if let Some(reasoning) = choice.message.reasoning.filter(|value| !value.is_empty()) { + push_openrouter_event( + &mut events, + &request.request_id, + EpiphanyModelStreamPayload::ReasoningDelta { text: reasoning }, + ); + } + let terminal_calls = choice + .message + .tool_calls + .iter() + .filter(|call| call.function.name == OPENROUTER_TERMINAL_TOOL_NAME) + .collect::>(); + if !terminal_calls.is_empty() { + if terminal_calls.len() != 1 || choice.message.tool_calls.len() != 1 { + return Err(anyhow::anyhow!( + "OpenRouter mixed the terminal decision tool with another tool call" + )); + } + let arguments = &terminal_calls[0].function.arguments; + if !matches!( + serde_json::from_str::(arguments), + Ok(serde_json::Value::Object(_)) + ) { + return Err(anyhow::anyhow!( + "OpenRouter terminal decision tool returned non-object arguments" + )); + } + push_openrouter_event( + &mut events, + &request.request_id, + EpiphanyModelStreamPayload::TextDelta { + text: arguments.clone(), + }, + ); + } else { + if let Some(content) = choice.message.content.filter(|value| !value.is_empty()) { + push_openrouter_event( + &mut events, + &request.request_id, + EpiphanyModelStreamPayload::TextDelta { text: content }, + ); + } + for call in choice.message.tool_calls { + push_openrouter_event( + &mut events, + &request.request_id, + EpiphanyModelStreamPayload::ToolCall { + call_id: call.id, + name: call.function.name, + arguments: call.function.arguments, + }, + ); + } + } + if events.is_empty() { + return Err(anyhow::anyhow!( + "OpenRouter response contained neither text nor tool calls" + )); + } + let usage = response.usage; + let mut receipt = EpiphanyModelReceipt::new( + &request.request_id, + "openrouter", + response.model.unwrap_or_else(|| request.model.clone()), + ); + receipt.provider_response_id = response.id; + receipt.input_tokens = usage.as_ref().and_then(|usage| usage.prompt_tokens); + receipt.output_tokens = usage.as_ref().and_then(|usage| usage.completion_tokens); + receipt.reasoning_output_tokens = usage + .as_ref() + .and_then(|usage| usage.completion_tokens_details.as_ref()) + .and_then(|details| details.reasoning_tokens); + receipt.transport = Some("openrouter-chat-completions".into()); + push_openrouter_event( + &mut events, + &request.request_id, + EpiphanyModelStreamPayload::Completed { + receipt: Box::new(receipt), + }, + ); + Ok(events) +} + +#[derive(Debug, Deserialize)] +struct OpenRouterResponse { + id: Option, + model: Option, + choices: Vec, + usage: Option, +} + +#[derive(Debug, Deserialize)] +struct OpenRouterChoice { + message: OpenRouterMessage, +} + +#[derive(Debug, Deserialize)] +struct OpenRouterMessage { + content: Option, + #[serde(default)] + reasoning: Option, + #[serde(default)] + tool_calls: Vec, +} + +#[derive(Debug, Deserialize)] +struct OpenRouterToolCall { + id: String, + function: OpenRouterFunctionCall, +} + +#[derive(Debug, Deserialize)] +struct OpenRouterFunctionCall { + name: String, + arguments: String, +} + +#[derive(Debug, Deserialize)] +struct OpenRouterUsage { + prompt_tokens: Option, + completion_tokens: Option, + completion_tokens_details: Option, +} + +#[derive(Debug, Deserialize)] +struct OpenRouterCompletionTokenDetails { + reasoning_tokens: Option, +} + +fn push_openrouter_event( + events: &mut Vec, + request_id: &str, + payload: EpiphanyModelStreamPayload, +) { + events.push(EpiphanyModelStreamEvent { + schema_id: MODEL_ADAPTER_EVENT_SCHEMA_ID.to_string(), + request_id: request_id.into(), + provider: "openrouter".into(), + sequence: events.len() as u64, + payload, + }); +} + +fn provider_schema_is_strict(schema: &serde_json::Value) -> bool { + match schema { + serde_json::Value::Object(map) => { + if (map.contains_key("const") || map.contains_key("enum")) && !map.contains_key("type") + { + return false; + } + if schema_map_describes_object(map) { + if map.get("additionalProperties") != Some(&serde_json::Value::Bool(false)) { + return false; + } + let Some(properties) = map.get("properties").and_then(serde_json::Value::as_object) + else { + return false; + }; + let Some(required) = map.get("required").and_then(serde_json::Value::as_array) + else { + return false; + }; + if properties + .keys() + .any(|key| !required.iter().any(|item| item.as_str() == Some(key))) + { + return false; + } + } + map.values().all(provider_schema_is_strict) + } + serde_json::Value::Array(values) => values.iter().all(provider_schema_is_strict), + _ => true, + } +} + +fn close_provider_objects(schema: &mut serde_json::Value, path: &str) -> anyhow::Result<()> { + match schema { + serde_json::Value::Object(map) => { + let describes_object = schema_map_describes_object(map); + if describes_object { + map.insert("type".into(), serde_json::Value::String("object".into())); + let properties = map + .get("properties") + .and_then(serde_json::Value::as_object) + .cloned() + .unwrap_or_default(); + let canonical_required = map + .get("required") + .and_then(serde_json::Value::as_array) + .cloned() + .unwrap_or_default(); + for required in &canonical_required { + let required = required.as_str().ok_or_else(|| { + anyhow::anyhow!("provider schema {path} has a non-string required key") + })?; + if !properties.contains_key(required) { + return Err(anyhow::anyhow!( + "provider schema {path} requires undeclared property {required:?}" + )); + } + } + let canonical_required = canonical_required + .into_iter() + .filter_map(|value| value.as_str().map(ToOwned::to_owned)) + .collect::>(); + let mut projected = serde_json::Map::new(); + for (name, mut property) in properties { + close_provider_objects(&mut property, &format!("{path}.properties.{name}"))?; + if !canonical_required.contains(&name) { + property = nullable_provider_property(property); + } + projected.insert(name, property); + } + let required = projected + .keys() + .cloned() + .map(serde_json::Value::String) + .collect(); + map.insert("properties".into(), serde_json::Value::Object(projected)); + map.insert("required".into(), serde_json::Value::Array(required)); + map.insert( + "additionalProperties".into(), + serde_json::Value::Bool(false), + ); + } + for (name, value) in map.iter_mut() { + if name != "properties" || !describes_object { + close_provider_objects(value, &format!("{path}.{name}"))?; + } + } + Ok(()) + } + serde_json::Value::Array(values) => { + for (index, value) in values.iter_mut().enumerate() { + close_provider_objects(value, &format!("{path}[{index}]"))?; + } + Ok(()) + } + _ => Ok(()), + } +} + +fn schema_map_describes_object(map: &serde_json::Map) -> bool { + map.get("type").and_then(serde_json::Value::as_str) == Some("object") + || [ + "properties", + "required", + "additionalProperties", + "patternProperties", + "propertyNames", + "minProperties", + "maxProperties", + ] + .iter() + .any(|keyword| map.contains_key(*keyword)) +} + +fn parent_relative_object_alternatives(value: &serde_json::Value) -> bool { + let Some(alternatives) = value.as_array() else { + return false; + }; + !alternatives.is_empty() + && alternatives.iter().all(|alternative| { + let Some(map) = alternative.as_object() else { + return false; + }; + !map.contains_key("type") + && !map.contains_key("$ref") + && map + .keys() + .any(|key| matches!(key.as_str(), "properties" | "required")) + && map.keys().all(|key| { + matches!( + key.as_str(), + "properties" | "required" | "title" | "description" | "$comment" + ) + }) + }) +} + +fn infer_literal_type(map: &mut serde_json::Map) { + if map.contains_key("type") { + return; + } + let mut types = std::collections::BTreeSet::new(); + if let Some(value) = map.get("const") { + types.insert(json_type(value)); + } else if let Some(values) = map.get("enum").and_then(serde_json::Value::as_array) { + types.extend(values.iter().map(json_type)); + } + match types.len() { + 0 => {} + 1 => { + map.insert( + "type".into(), + serde_json::Value::String(types.into_iter().next().expect("one type").into()), + ); + } + _ => { + map.insert( + "type".into(), + serde_json::Value::Array( + types + .into_iter() + .map(|value| serde_json::Value::String(value.into())) + .collect(), + ), + ); + } + } +} + +fn json_type(value: &serde_json::Value) -> &'static str { + match value { + serde_json::Value::Null => "null", + serde_json::Value::Bool(_) => "boolean", + serde_json::Value::Number(number) if number.is_i64() || number.is_u64() => "integer", + serde_json::Value::Number(_) => "number", + serde_json::Value::String(_) => "string", + serde_json::Value::Array(_) => "array", + serde_json::Value::Object(_) => "object", + } +} + +fn nullable_provider_property(property: serde_json::Value) -> serde_json::Value { + if property + .get("anyOf") + .and_then(serde_json::Value::as_array) + .is_some_and(|variants| { + variants.iter().any(|variant| { + variant.get("type").and_then(serde_json::Value::as_str) == Some("null") + }) + }) + { + property + } else { + serde_json::json!({"anyOf": [property, {"type": "null"}]}) + } +} + +const UNSUPPORTED_PROVIDER_SCHEMA_KEYWORDS: &[&str] = &[ + "allOf", + "not", + "dependentRequired", + "dependentSchemas", + "if", + "then", + "else", + "patternProperties", + "propertyNames", + "minProperties", + "maxProperties", + "unevaluatedProperties", + "uniqueItems", + "contains", + "minContains", + "maxContains", + "prefixItems", + "unevaluatedItems", + "default", + "examples", + "readOnly", + "writeOnly", + "$schema", + "$id", + "$anchor", + "$dynamicAnchor", + "$dynamicRef", + "$vocabulary", +]; + +fn lower_provider_schema(schema: &mut serde_json::Value) { + let serde_json::Value::Object(map) = schema else { + return; + }; + infer_literal_type(map); + if map.get("format").and_then(serde_json::Value::as_str) == Some("uuid") { + map.remove("format"); + map.entry("pattern").or_insert_with(|| { + serde_json::Value::String( + "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$" + .into(), + ) + }); + } + for keyword in UNSUPPORTED_PROVIDER_SCHEMA_KEYWORDS { + map.remove(*keyword); + } + if let Some(one_of) = map.remove("oneOf") { + map.insert("anyOf".into(), one_of); + } + if map + .get("anyOf") + .is_some_and(parent_relative_object_alternatives) + { + map.remove("anyOf"); + } + for collection in ["properties", "$defs", "definitions"] { + if let Some(serde_json::Value::Object(children)) = map.get_mut(collection) { + for child in children.values_mut() { + lower_provider_schema(child); + } + } + } + if let Some(items) = map.get_mut("items") { + match items { + serde_json::Value::Array(items) => { + for item in items { + lower_provider_schema(item); + } + } + item => lower_provider_schema(item), + } + } + if let Some(serde_json::Value::Array(alternatives)) = map.get_mut("anyOf") { + for alternative in alternatives { + lower_provider_schema(alternative); + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn native_provider_identity_selects_one_exact_request_contract() { + let openai = EpiphanyModelRequest::new( + "openai-request", + "conversation", + "openai-codex", + "gpt-test", + "decide", + ); + let openrouter = EpiphanyModelRequest::new( + "openrouter-request", + "conversation", + "openrouter", + "stealth/ox-alpha", + "decide", + ); + + let openai = request_from_native(&openai).unwrap(); + let openrouter = request_from_native(&openrouter).unwrap(); + assert!(matches!( + openai.payload, + EpiphanyProviderRequestPayload::Codex(_) + )); + assert!(matches!( + openrouter.payload, + EpiphanyProviderRequestPayload::OpenRouter(_) + )); + } + + #[test] + fn provider_projection_closes_objects_without_keeping_parent_conditions() { + let schema = strict_provider_schema( + r#"{ + "type":"object", + "properties":{"choice":{"type":"string"},"note":{"type":"string"}}, + "required":["choice"], + "allOf":[{"if":{"properties":{"choice":{"const":"x"}}}}], + "anyOf":[{"required":["choice"]},{"required":["note"]}] + }"#, + ) + .unwrap(); + + assert_eq!(schema["additionalProperties"], false); + assert_eq!(schema["required"], serde_json::json!(["choice", "note"])); + assert!(schema.get("allOf").is_none()); + assert!(schema.get("anyOf").is_none()); + assert_eq!(schema["properties"]["note"]["anyOf"][1]["type"], "null"); + } + + #[test] + fn provider_projection_types_literals_and_lowers_uuid() { + let schema = strict_provider_schema( + r#"{ + "type":"object", + "properties":{ + "kind":{"const":"ready"}, + "id":{"type":"string","format":"uuid"}, + "items":{"type":"array","uniqueItems":true,"items":{"enum":[1,2]}} + }, + "required":["kind","id","items"] + }"#, + ) + .unwrap(); + + assert_eq!(schema["properties"]["kind"]["type"], "string"); + assert!(schema["properties"]["id"].get("format").is_none()); + assert!(schema["properties"]["id"]["pattern"].is_string()); + assert!(schema["properties"]["items"].get("uniqueItems").is_none()); + assert_eq!(schema["properties"]["items"]["items"]["type"], "integer"); + } + + #[test] + fn provider_identities_are_stable_and_bounded() { + assert_eq!(provider_format_name("role/result.v1"), "role_result_v1"); + assert_eq!(provider_call_id("already-safe_1"), "already-safe_1"); + let first = provider_call_id("unsafe/call identity"); + assert_eq!(first, provider_call_id("unsafe/call identity")); + assert_eq!(first.len(), 64); + assert!(first.bytes().all(|byte| byte.is_ascii_hexdigit())); + } + + #[test] + fn openrouter_terminal_tool_preserves_structured_decision() { + let mut request = EpiphanyOpenRouterRequest::new("request", "conversation", "ox", "decide"); + request.output_schema_json = Some( + r#"{"type":"object","properties":{"verdict":{"type":"string"}},"required":["verdict"]}"#.into(), + ); + let body = openrouter_request_body(&request).unwrap(); + assert_eq!(body["tool_choice"], "required"); + assert_eq!( + body["tools"][0]["function"]["name"], + OPENROUTER_TERMINAL_TOOL_NAME + ); + + let response = serde_json::json!({ + "id": "response", + "model": "ox", + "choices": [{"message": {"tool_calls": [{ + "id": "terminal", + "function": {"name": OPENROUTER_TERMINAL_TOOL_NAME, "arguments": "{\"verdict\":\"pass\"}"} + }]}}] + }); + let events = + openrouter_events_from_response(&request, &serde_json::to_vec(&response).unwrap()) + .unwrap(); + assert!(matches!( + &events[0].payload, + EpiphanyModelStreamPayload::TextDelta { text } if text == "{\"verdict\":\"pass\"}" + )); + assert!(matches!( + &events[1].payload, + EpiphanyModelStreamPayload::Completed { receipt } + if receipt.transport.as_deref() == Some("openrouter-chat-completions") + )); + } +} diff --git a/epiphany-openai-adapter/Cargo.toml b/epiphany-openai-adapter/Cargo.toml deleted file mode 100644 index 9875b49d..00000000 --- a/epiphany-openai-adapter/Cargo.toml +++ /dev/null @@ -1,15 +0,0 @@ -[package] -name = "epiphany-openai-adapter" -version = "0.1.0" -edition = "2024" -publish = false -license-file = "../LICENSE" - -[lib] -name = "epiphany_openai_adapter" -path = "src/lib.rs" - -[dependencies] -cultcache-rs = { path = "../vendor/cultcache-rs" } -epiphany-model-adapter = { path = "../epiphany-model-adapter" } -serde = { version = "1", features = ["derive"] } diff --git a/epiphany-openai-adapter/src/lib.rs b/epiphany-openai-adapter/src/lib.rs deleted file mode 100644 index f286ecf9..00000000 --- a/epiphany-openai-adapter/src/lib.rs +++ /dev/null @@ -1,322 +0,0 @@ -use cultcache_rs::DatabaseEntry; -use epiphany_model_adapter::{EpiphanyModelInputItem, EpiphanyModelRequest}; -use serde::Deserialize; -use serde::Serialize; - -pub const OPENAI_ADAPTER_REQUEST_SCHEMA_ID: &str = "epiphany.openai_model_request.v1"; -pub const OPENAI_ADAPTER_EVENT_SCHEMA_ID: &str = "epiphany.openai_model_stream_event.v0"; -pub const OPENAI_ADAPTER_RECEIPT_SCHEMA_ID: &str = "epiphany.openai_model_receipt.v0"; -pub const OPENAI_ADAPTER_STATUS_SCHEMA_ID: &str = "epiphany.openai_adapter_status.v0"; - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub enum EpiphanyOpenAiAuthMode { - ChatGptSubscription, - ApiKey, - ExternalBearer, - Unknown, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] -pub enum EpiphanyOpenAiWireDialect { - Responses, - ChatCompletionsTerminalTool, -} - -#[derive(Debug, Clone, PartialEq, Eq, DatabaseEntry)] -#[cultcache( - type = "epiphany.openai_adapter_status.v0", - schema = "EpiphanyOpenAiAdapterStatus" -)] -pub struct EpiphanyOpenAiAdapterStatus { - #[cultcache(key = 0)] - pub schema_id: String, - #[cultcache(key = 1)] - pub adapter_id: String, - #[cultcache(key = 2)] - pub auth_mode: EpiphanyOpenAiAuthMode, - #[cultcache(key = 3, default)] - pub account_id: Option, - #[cultcache(key = 4, default)] - pub plan_type: Option, - #[cultcache(key = 5, default)] - pub default_model: Option, - #[cultcache(key = 6)] - pub supports_websockets: bool, - #[cultcache(key = 7)] - pub codex_transport_attached: bool, -} - -#[derive(Debug, Clone, PartialEq, DatabaseEntry)] -#[cultcache( - type = "epiphany.openai_model_request.v1", - schema = "EpiphanyOpenAiModelRequest" -)] -pub struct EpiphanyOpenAiModelRequest { - #[cultcache(key = 0)] - pub schema_id: String, - #[cultcache(key = 1)] - pub request_id: String, - #[cultcache(key = 2)] - pub conversation_id: String, - #[cultcache(key = 3)] - pub model: String, - #[cultcache(key = 4)] - pub instructions: String, - #[cultcache(key = 5, default)] - pub input: Vec, - #[cultcache(key = 6, default)] - pub reasoning_effort: Option, - #[cultcache(key = 7, default)] - pub reasoning_summary: Option, - #[cultcache(key = 8, default)] - pub service_tier: Option, - #[cultcache(key = 9, default)] - pub output_contract_id: Option, - #[cultcache(key = 10, default)] - pub previous_response_id: Option, - #[cultcache(key = 11, default)] - pub tools: Vec, - #[cultcache(key = 12, default)] - pub output_schema_json: Option, - #[cultcache(key = 13)] - pub provider_id: String, - #[cultcache(key = 14)] - pub wire_dialect: EpiphanyOpenAiWireDialect, - #[cultcache(key = 15, default)] - pub max_output_tokens: Option, - #[cultcache(key = 16, default)] - pub prompt_cache_key: Option, -} - -impl EpiphanyOpenAiModelRequest { - pub fn new( - request_id: impl Into, - conversation_id: impl Into, - model: impl Into, - instructions: impl Into, - ) -> Self { - Self { - schema_id: OPENAI_ADAPTER_REQUEST_SCHEMA_ID.to_string(), - request_id: request_id.into(), - conversation_id: conversation_id.into(), - model: model.into(), - instructions: instructions.into(), - input: Vec::new(), - reasoning_effort: None, - reasoning_summary: None, - service_tier: None, - output_contract_id: None, - previous_response_id: None, - tools: Vec::new(), - output_schema_json: None, - provider_id: "openai-codex".to_string(), - wire_dialect: EpiphanyOpenAiWireDialect::Responses, - max_output_tokens: None, - prompt_cache_key: None, - } - } -} - -/// The only lowering from a native model request to the OpenAI transport. -/// Internal audit identity stays on the native request; every provider-bearing -/// byte is derived here. -pub fn request_from_native(request: &EpiphanyModelRequest) -> EpiphanyOpenAiModelRequest { - EpiphanyOpenAiModelRequest { - schema_id: OPENAI_ADAPTER_REQUEST_SCHEMA_ID.to_string(), - request_id: request.request_id.clone(), - conversation_id: request.conversation_id.clone(), - model: request.model.clone(), - instructions: request.instructions.clone(), - input: request.input.iter().map(input_from_native).collect(), - reasoning_effort: request.reasoning_effort.clone(), - reasoning_summary: request.reasoning_summary.clone(), - service_tier: request.service_tier.clone(), - output_contract_id: request.output_contract_id.clone(), - previous_response_id: request.previous_response_id.clone(), - tools: request - .tools - .iter() - .map(|tool| EpiphanyOpenAiToolDefinition { - name: tool.name.clone(), - description: tool.description.clone(), - parameters_json: tool.parameters_json.clone(), - }) - .collect(), - output_schema_json: request.output_schema_json.clone(), - provider_id: request.provider.clone(), - wire_dialect: if request.provider == "openrouter" { - EpiphanyOpenAiWireDialect::ChatCompletionsTerminalTool - } else { - EpiphanyOpenAiWireDialect::Responses - }, - max_output_tokens: request.max_output_tokens, - prompt_cache_key: request.prompt_cache_key.clone(), - } -} - -fn input_from_native(input: &EpiphanyModelInputItem) -> EpiphanyOpenAiInputItem { - match input { - EpiphanyModelInputItem::UserText { text } => { - EpiphanyOpenAiInputItem::UserText { text: text.clone() } - } - EpiphanyModelInputItem::AssistantText { text } => { - EpiphanyOpenAiInputItem::AssistantText { text: text.clone() } - } - EpiphanyModelInputItem::ToolCall { - call_id, - name, - arguments, - } => EpiphanyOpenAiInputItem::ToolCall { - call_id: call_id.clone(), - name: name.clone(), - arguments: arguments.clone(), - }, - EpiphanyModelInputItem::ToolResult { call_id, output } => { - EpiphanyOpenAiInputItem::ToolResult { - call_id: call_id.clone(), - output: output.clone(), - } - } - } -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct EpiphanyOpenAiToolDefinition { - pub name: String, - pub description: String, - pub parameters_json: String, -} - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub enum EpiphanyOpenAiInputItem { - UserText { - text: String, - }, - AssistantText { - text: String, - }, - ToolCall { - call_id: String, - name: String, - arguments: String, - }, - ToolResult { - call_id: String, - output: String, - }, -} - -#[derive(Debug, Clone, PartialEq, DatabaseEntry)] -#[cultcache( - type = "epiphany.openai_model_stream_event.v0", - schema = "EpiphanyOpenAiStreamEvent" -)] -pub struct EpiphanyOpenAiStreamEvent { - #[cultcache(key = 0)] - pub schema_id: String, - #[cultcache(key = 1)] - pub request_id: String, - #[cultcache(key = 2)] - pub sequence: u64, - #[cultcache(key = 3)] - pub payload: EpiphanyOpenAiStreamPayload, -} - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub enum EpiphanyOpenAiStreamPayload { - TextDelta { - text: String, - }, - ReasoningDelta { - text: String, - }, - ToolCall { - call_id: String, - name: String, - arguments: String, - }, - Completed { - receipt: EpiphanyOpenAiModelReceipt, - }, - Failed { - message: String, - }, -} - -#[derive(Debug, Clone, PartialEq, Eq, DatabaseEntry)] -#[cultcache( - type = "epiphany.openai_model_receipt.v0", - schema = "EpiphanyOpenAiModelReceipt" -)] -pub struct EpiphanyOpenAiModelReceipt { - #[cultcache(key = 0)] - pub schema_id: String, - #[cultcache(key = 1)] - pub request_id: String, - #[cultcache(key = 2)] - pub model: String, - #[cultcache(key = 3, default)] - pub response_id: Option, - #[cultcache(key = 4, default)] - pub input_tokens: Option, - #[cultcache(key = 5, default)] - pub output_tokens: Option, - #[cultcache(key = 6, default)] - pub reasoning_output_tokens: Option, - #[cultcache(key = 7, default)] - pub transport: Option, - #[cultcache(key = 8, default)] - pub cached_input_tokens: Option, -} - -impl EpiphanyOpenAiModelReceipt { - pub fn new(request_id: impl Into, model: impl Into) -> Self { - Self { - schema_id: OPENAI_ADAPTER_RECEIPT_SCHEMA_ID.to_string(), - request_id: request_id.into(), - model: model.into(), - response_id: None, - input_tokens: None, - output_tokens: None, - reasoning_output_tokens: None, - transport: None, - cached_input_tokens: None, - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn native_provider_identity_selects_one_exact_wire_dialect() { - let mut openai = EpiphanyModelRequest::new( - "openai-request", - "conversation", - "openai-codex", - "gpt-test", - "decide", - ); - openai.max_output_tokens = Some(512); - let openrouter = EpiphanyModelRequest::new( - "openrouter-request", - "conversation", - "openrouter", - "stealth/ox-alpha", - "decide", - ); - - let openai = request_from_native(&openai); - let openrouter = request_from_native(&openrouter); - assert_eq!(openai.provider_id, "openai-codex"); - assert_eq!(openai.max_output_tokens, Some(512)); - assert_eq!(openai.wire_dialect, EpiphanyOpenAiWireDialect::Responses); - assert_eq!(openrouter.provider_id, "openrouter"); - assert_eq!( - openrouter.wire_dialect, - EpiphanyOpenAiWireDialect::ChatCompletionsTerminalTool - ); - assert_ne!(openai.schema_id, "epiphany.openai_model_request.v0"); - } -} diff --git a/epiphany-openai-auth-spine/.gitignore b/epiphany-openai-auth-spine/.gitignore deleted file mode 100644 index b83d2226..00000000 --- a/epiphany-openai-auth-spine/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/target/ diff --git a/epiphany-openai-codex-spine/Cargo.toml b/epiphany-openai-codex-spine/Cargo.toml deleted file mode 100644 index a8708abd..00000000 --- a/epiphany-openai-codex-spine/Cargo.toml +++ /dev/null @@ -1,22 +0,0 @@ -[package] -name = "epiphany-openai-codex-spine" -version = "0.1.0" -edition = "2024" -publish = false -license-file = "../LICENSE" - -[lib] -name = "epiphany_openai_codex_spine" -path = "src/lib.rs" - -[dependencies] -anyhow = "1" -codex-client = { path = "../vendor/codex/codex-rs/codex-client" } -codex-login = { path = "../vendor/codex/codex-rs/login" } -epiphany-openai-adapter = { path = "../epiphany-openai-adapter" } -futures = "0.3" -http = "1" -serde = "1" -serde_json = "1" -sha2 = "0.10" -tokio = { version = "1", features = ["macros", "rt-multi-thread"] } diff --git a/epiphany-openai-codex-spine/src/lib.rs b/epiphany-openai-codex-spine/src/lib.rs deleted file mode 100644 index 8f4f60ce..00000000 --- a/epiphany-openai-codex-spine/src/lib.rs +++ /dev/null @@ -1,2185 +0,0 @@ -use std::collections::HashMap; -use std::sync::Arc; -use std::time::Duration; - -use anyhow::Context; -use anyhow::Result; -use codex_client::HttpTransport; -use codex_client::Request; -use codex_client::ReqwestTransport; -use codex_client::TransportError; -use codex_client::sse_stream; -use codex_login::AuthCredentialsStoreMode; -use codex_login::AuthManager; -use codex_login::AuthMode; -use codex_login::CodexAuth; -use codex_login::default_client::build_reqwest_client; -use epiphany_openai_adapter::EpiphanyOpenAiAdapterStatus; -use epiphany_openai_adapter::EpiphanyOpenAiAuthMode; -use epiphany_openai_adapter::EpiphanyOpenAiInputItem; -use epiphany_openai_adapter::EpiphanyOpenAiModelReceipt; -use epiphany_openai_adapter::EpiphanyOpenAiModelRequest; -use epiphany_openai_adapter::EpiphanyOpenAiStreamEvent; -use epiphany_openai_adapter::EpiphanyOpenAiStreamPayload; -use epiphany_openai_adapter::EpiphanyOpenAiWireDialect; -use epiphany_openai_adapter::OPENAI_ADAPTER_STATUS_SCHEMA_ID; -use serde::Deserialize; -use serde::Serialize; -use serde_json::Value; -use sha2::Digest; -use sha2::Sha256; - -const CHATGPT_CODEX_BASE_URL: &str = "https://chatgpt.com/backend-api/codex"; -const OPENAI_API_BASE_URL: &str = "https://api.openai.com/v1"; -const OPENROUTER_API_BASE_URL: &str = "https://openrouter.ai/api/v1"; -// Keep provider silence shorter than Epiphany worker watchdogs so the runtime can -// write a typed stream failure instead of being killed with no model evidence. -const RESPONSES_STREAM_IDLE_TIMEOUT: Duration = Duration::from_secs(45); -const RESPONSES_CALL_ID_MAX_BYTES: usize = 64; - -pub const CODEX_SPINE_ADAPTER_ID: &str = "codex-openai-subscription-spine"; -pub const OPENROUTER_SPINE_ADAPTER_ID: &str = "openrouter-chat-completions-spine"; -pub const OPENROUTER_TERMINAL_TOOL_NAME: &str = "epiphany_submit_typed_result"; - -pub fn default_codex_home() -> Result { - if let Ok(path) = std::env::var("CODEX_HOME") { - return Ok(std::path::PathBuf::from(path)); - } - let home = std::env::var("USERPROFILE") - .or_else(|_| std::env::var("HOME")) - .context("CODEX_HOME is unset and no home directory environment variable exists")?; - Ok(std::path::PathBuf::from(home).join(".codex")) -} - -pub fn auth_manager(codex_home: std::path::PathBuf) -> Arc { - AuthManager::shared( - codex_home, - /*enable_codex_api_key_env*/ true, - AuthCredentialsStoreMode::Auto, - /*chatgpt_base_url*/ None, - ) -} - -pub async fn status_from_auth_manager( - auth_manager: &AuthManager, - default_model: Option, - supports_websockets: bool, -) -> EpiphanyOpenAiAdapterStatus { - let auth = auth_manager.auth().await; - let auth_mode = auth_mode_from_manager(auth_manager, auth.as_ref()); - let account_id = auth.as_ref().and_then(CodexAuth::get_account_id); - let plan_type = auth - .as_ref() - .and_then(CodexAuth::account_plan_type) - .map(|plan| format!("{plan:?}")); - - EpiphanyOpenAiAdapterStatus { - schema_id: OPENAI_ADAPTER_STATUS_SCHEMA_ID.to_string(), - adapter_id: CODEX_SPINE_ADAPTER_ID.to_string(), - auth_mode, - account_id, - plan_type, - default_model, - supports_websockets, - codex_transport_attached: true, - } -} - -pub fn status_from_static_api_key( - adapter_id: &str, - default_model: Option, -) -> EpiphanyOpenAiAdapterStatus { - EpiphanyOpenAiAdapterStatus { - schema_id: OPENAI_ADAPTER_STATUS_SCHEMA_ID.to_string(), - adapter_id: adapter_id.to_string(), - auth_mode: EpiphanyOpenAiAuthMode::ApiKey, - account_id: None, - plan_type: None, - default_model, - supports_websockets: false, - codex_transport_attached: false, - } -} - -pub struct EpiphanyCodexOpenAiTransport { - auth_manager: Arc, - base_url: Option, -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct EpiphanyResponsesFrameObservation { - pub frame_sequence: u64, - pub kind: String, - pub recognized: bool, - pub delta_preview: Option, -} - -impl EpiphanyCodexOpenAiTransport { - pub fn new(auth_manager: Arc, base_url: Option) -> Self { - Self { - auth_manager, - base_url, - } - } - - pub fn openai(auth_manager: Arc) -> Self { - Self::new(auth_manager, None) - } - - pub async fn collect_model_events( - &self, - request: EpiphanyOpenAiModelRequest, - ) -> Result> { - self.collect_model_events_with_frame_observer(request, |_| {}) - .await - } - - pub async fn collect_model_events_with_frame_observer( - &self, - request: EpiphanyOpenAiModelRequest, - mut observe_frame: impl FnMut(EpiphanyResponsesFrameObservation), - ) -> Result> { - let request_id = request.request_id.clone(); - let model = request.model.clone(); - let auth = self - .auth_manager - .auth() - .await - .ok_or_else(|| anyhow::anyhow!("Codex auth is unavailable"))?; - let stream_response = self.open_responses_stream(&auth, request).await?; - let (tx, mut rx) = tokio::sync::mpsc::channel(1600); - sse_stream(stream_response.bytes, RESPONSES_STREAM_IDLE_TIMEOUT, tx); - - let mut stream_state = EpiphanyResponsesStreamState::new(&request_id, &model); - while let Some(frame) = rx.recv().await { - match frame { - Ok(frame) => { - let observation = stream_state.push_sse_frame(&frame); - observe_frame(observation); - } - Err(err) => stream_state.push_failed(err.to_string()), - } - if stream_state.completed { - break; - } - } - - if stream_state.events.is_empty() { - stream_state.push_failed("Responses stream closed without typed events".to_string()); - } - Ok(stream_state.events) - } - - async fn open_responses_stream( - &self, - auth: &CodexAuth, - request: EpiphanyOpenAiModelRequest, - ) -> Result { - let base_url = self - .base_url - .clone() - .unwrap_or_else(|| default_base_url_for_auth(auth).to_string()); - let url = format!("{}/responses", base_url.trim_end_matches('/')); - let conversation_id = request.conversation_id.clone(); - let request = request_for_responses_backend(request, auth.uses_codex_backend()); - let mut outbound = Request::new(http::Method::POST, url) - .with_json(&responses_body_from_epiphany(request)?); - attach_codex_auth_headers(auth, &mut outbound.headers) - .context("failed to attach Codex auth headers")?; - outbound - .headers - .insert(http::header::ACCEPT, "text/event-stream".parse()?); - outbound - .headers - .insert("session_id", conversation_id.parse()?); - outbound - .headers - .insert("x-client-request-id", conversation_id.parse()?); - attach_optional_env_header( - &mut outbound.headers, - "OpenAI-Organization", - "OPENAI_ORGANIZATION", - ); - attach_optional_env_header(&mut outbound.headers, "OpenAI-Project", "OPENAI_PROJECT"); - - let transport = ReqwestTransport::new(build_reqwest_client()); - transport - .stream(outbound) - .await - .map_err(transport_error_to_anyhow) - } -} - -fn request_for_responses_backend( - mut request: EpiphanyOpenAiModelRequest, - uses_codex_backend: bool, -) -> EpiphanyOpenAiModelRequest { - if uses_codex_backend { - // The ChatGPT Codex subscription boundary rejects this otherwise - // standard Responses parameter. Callers retain and verify their bound. - request.max_output_tokens = None; - } - request -} - -/// OpenRouter owns a distinct credential and wire dialect. It is intentionally -/// not routed through Codex auth or the Responses transport. -pub struct EpiphanyOpenRouterTransport { - api_key: String, - base_url: String, - request_timeout: Option, -} - -impl EpiphanyOpenRouterTransport { - pub fn new(api_key: impl Into, request_timeout: Option) -> Result { - let api_key = api_key.into(); - if api_key.trim().is_empty() || api_key.trim() != api_key { - return Err(anyhow::anyhow!( - "OpenRouter API key must be a non-empty trimmed credential" - )); - } - if request_timeout.is_some_and(|timeout| timeout.is_zero()) { - return Err(anyhow::anyhow!( - "OpenRouter request timeout must be positive when present" - )); - } - Ok(Self { - api_key, - base_url: OPENROUTER_API_BASE_URL.to_string(), - request_timeout, - }) - } - - pub async fn collect_model_events( - &self, - request: EpiphanyOpenAiModelRequest, - ) -> Result> { - self.collect_model_events_with_frame_observer(request, |_| {}) - .await - } - - pub async fn collect_model_events_with_frame_observer( - &self, - request: EpiphanyOpenAiModelRequest, - mut observe_frame: impl FnMut(EpiphanyResponsesFrameObservation), - ) -> Result> { - let url = format!("{}/chat/completions", self.base_url.trim_end_matches('/')); - let body = chat_completions_body_from_epiphany(&request)?; - let mut outbound = Request::new(http::Method::POST, url).with_json(&body); - let mut authorization = format!("Bearer {}", self.api_key).parse::()?; - authorization.set_sensitive(true); - outbound - .headers - .insert(http::header::AUTHORIZATION, authorization); - outbound.timeout = self.request_timeout; - let transport = ReqwestTransport::new(build_reqwest_client()); - let response = transport - .execute(outbound) - .await - .map_err(transport_error_to_anyhow)?; - observe_frame(EpiphanyResponsesFrameObservation { - frame_sequence: 1, - kind: "chat.completion".to_string(), - recognized: true, - delta_preview: None, - }); - let response: OpenRouterChatCompletionResponse = serde_json::from_slice(&response.body) - .context("OpenRouter returned an invalid Chat Completions response")?; - openrouter_events_from_chat_completion(&request, response) - } -} - -pub fn chat_completions_body_from_epiphany( - request: &EpiphanyOpenAiModelRequest, -) -> Result { - if request.provider_id != "openrouter" - || request.wire_dialect != EpiphanyOpenAiWireDialect::ChatCompletionsTerminalTool - { - return Err(anyhow::anyhow!( - "OpenRouter transport requires its exact provider identity and Chat Completions dialect" - )); - } - let has_tool_result = request - .input - .iter() - .any(|item| matches!(item, EpiphanyOpenAiInputItem::ToolResult { .. })); - let output_schema = request - .output_schema_json - .as_deref() - .map(strict_provider_schema) - .transpose()?; - let include_terminal_tool = - output_schema.is_some() && (request.tools.is_empty() || has_tool_result); - if request - .tools - .iter() - .any(|tool| tool.name == OPENROUTER_TERMINAL_TOOL_NAME) - { - return Err(anyhow::anyhow!( - "native tool name collides with the OpenRouter terminal decision tool" - )); - } - - let mut messages = vec![serde_json::json!({ - "role": "system", - "content": request.instructions, - })]; - for item in &request.input { - messages.push(match item { - EpiphanyOpenAiInputItem::UserText { text } => serde_json::json!({ - "role": "user", - "content": text, - }), - EpiphanyOpenAiInputItem::AssistantText { text } => serde_json::json!({ - "role": "assistant", - "content": text, - }), - EpiphanyOpenAiInputItem::ToolCall { - call_id, - name, - arguments, - } => serde_json::json!({ - "role": "assistant", - "tool_calls": [{ - "id": call_id, - "type": "function", - "function": {"name": name, "arguments": arguments}, - }], - }), - EpiphanyOpenAiInputItem::ToolResult { call_id, output } => serde_json::json!({ - "role": "tool", - "tool_call_id": call_id, - "content": output, - }), - }); - } - - let mut tools = request - .tools - .iter() - .map(|tool| { - let parameters = strict_provider_schema(&tool.parameters_json)?; - Ok(serde_json::json!({ - "type": "function", - "function": { - "name": tool.name, - "description": tool.description, - "strict": true, - "parameters": parameters, - }, - })) - }) - .collect::>>()?; - if let Some(schema) = output_schema { - if include_terminal_tool { - tools.push(serde_json::json!({ - "type": "function", - "function": { - "name": OPENROUTER_TERMINAL_TOOL_NAME, - "description": "Submit the final typed Epiphany decision. Call this only when the pass is complete.", - "strict": true, - "parameters": schema, - }, - })); - } - } - - let mut body = serde_json::json!({ - "model": request.model, - "messages": messages, - "stream": false, - "parallel_tool_calls": false, - }); - if !tools.is_empty() { - body["tools"] = serde_json::Value::Array(tools); - body["tool_choice"] = serde_json::Value::String( - if include_terminal_tool || (!request.tools.is_empty() && !has_tool_result) { - "required" - } else { - "auto" - } - .to_string(), - ); - } - if let Some(effort) = request.reasoning_effort.as_deref() { - body["reasoning"] = serde_json::json!({"effort": effort}); - } - if let Some(max_output_tokens) = request.max_output_tokens { - body["max_tokens"] = serde_json::json!(max_output_tokens); - } - Ok(body) -} - -fn strict_provider_schema(schema_json: &str) -> Result { - let mut schema: serde_json::Value = serde_json::from_str(schema_json) - .context("provider tool parameters are not valid JSON Schema")?; - project_strict_responses_schema(&mut schema)?; - Ok(schema) -} - -#[derive(Debug, Deserialize)] -struct OpenRouterChatCompletionResponse { - id: Option, - model: Option, - choices: Vec, - usage: Option, -} - -#[derive(Debug, Deserialize)] -struct OpenRouterChatChoice { - message: OpenRouterChatMessage, -} - -#[derive(Debug, Deserialize)] -struct OpenRouterChatMessage { - content: Option, - #[serde(default)] - reasoning: Option, - #[serde(default)] - tool_calls: Vec, -} - -#[derive(Debug, Deserialize)] -struct OpenRouterChatToolCall { - id: String, - function: OpenRouterChatFunctionCall, -} - -#[derive(Debug, Deserialize)] -struct OpenRouterChatFunctionCall { - name: String, - arguments: String, -} - -#[derive(Debug, Deserialize)] -struct OpenRouterChatUsage { - prompt_tokens: Option, - completion_tokens: Option, - completion_tokens_details: Option, - prompt_tokens_details: Option, -} - -#[derive(Debug, Deserialize)] -struct OpenRouterPromptTokenDetails { - cached_tokens: Option, -} - -#[derive(Debug, Deserialize)] -struct OpenRouterCompletionTokenDetails { - reasoning_tokens: Option, -} - -fn openrouter_events_from_chat_completion( - request: &EpiphanyOpenAiModelRequest, - response: OpenRouterChatCompletionResponse, -) -> Result> { - let choice = response - .choices - .into_iter() - .next() - .ok_or_else(|| anyhow::anyhow!("OpenRouter response contained no completion choice"))?; - let mut events = Vec::new(); - if let Some(reasoning) = choice.message.reasoning.filter(|value| !value.is_empty()) { - push_openrouter_event( - &mut events, - &request.request_id, - EpiphanyOpenAiStreamPayload::ReasoningDelta { text: reasoning }, - ); - } - let terminal_calls = choice - .message - .tool_calls - .iter() - .filter(|call| call.function.name == OPENROUTER_TERMINAL_TOOL_NAME) - .collect::>(); - if !terminal_calls.is_empty() { - if terminal_calls.len() != 1 || choice.message.tool_calls.len() != 1 { - return Err(anyhow::anyhow!( - "OpenRouter mixed the terminal decision tool with another tool call" - )); - } - let arguments = &terminal_calls[0].function.arguments; - if !matches!( - serde_json::from_str::(arguments), - Ok(serde_json::Value::Object(_)) - ) { - return Err(anyhow::anyhow!( - "OpenRouter terminal decision tool returned non-object arguments" - )); - } - push_openrouter_event( - &mut events, - &request.request_id, - EpiphanyOpenAiStreamPayload::TextDelta { - text: arguments.clone(), - }, - ); - } else { - if let Some(content) = choice.message.content.filter(|value| !value.is_empty()) { - push_openrouter_event( - &mut events, - &request.request_id, - EpiphanyOpenAiStreamPayload::TextDelta { text: content }, - ); - } - for call in choice.message.tool_calls { - push_openrouter_event( - &mut events, - &request.request_id, - EpiphanyOpenAiStreamPayload::ToolCall { - call_id: call.id, - name: call.function.name, - arguments: call.function.arguments, - }, - ); - } - } - if events.is_empty() { - return Err(anyhow::anyhow!( - "OpenRouter response contained neither text nor tool calls" - )); - } - let usage = response.usage; - let mut receipt = EpiphanyOpenAiModelReceipt::new( - &request.request_id, - response.model.unwrap_or_else(|| request.model.clone()), - ); - receipt.response_id = response.id; - receipt.input_tokens = usage.as_ref().and_then(|item| item.prompt_tokens); - receipt.output_tokens = usage.as_ref().and_then(|item| item.completion_tokens); - receipt.reasoning_output_tokens = usage - .as_ref() - .and_then(|item| item.completion_tokens_details.as_ref()) - .and_then(|item| item.reasoning_tokens); - receipt.cached_input_tokens = usage - .as_ref() - .and_then(|item| item.prompt_tokens_details.as_ref()) - .and_then(|item| item.cached_tokens); - receipt.transport = Some("openrouter-chat-completions".to_string()); - push_openrouter_event( - &mut events, - &request.request_id, - EpiphanyOpenAiStreamPayload::Completed { receipt }, - ); - Ok(events) -} - -fn push_openrouter_event( - events: &mut Vec, - request_id: &str, - payload: EpiphanyOpenAiStreamPayload, -) { - events.push(EpiphanyOpenAiStreamEvent { - schema_id: epiphany_openai_adapter::OPENAI_ADAPTER_EVENT_SCHEMA_ID.to_string(), - request_id: request_id.to_string(), - sequence: events.len() as u64, - payload, - }); -} - -pub fn responses_body_from_epiphany( - request: EpiphanyOpenAiModelRequest, -) -> Result { - if !matches!(request.provider_id.as_str(), "openai-codex" | "openai") - || request.wire_dialect != EpiphanyOpenAiWireDialect::Responses - { - return Err(anyhow::anyhow!( - "Responses transport requires an OpenAI provider identity and Responses dialect" - )); - } - let requires_initial_tool = !request.tools.is_empty() - && !request - .input - .iter() - .any(|item| matches!(item, EpiphanyOpenAiInputItem::ToolResult { .. })); - let text = responses_text_format( - request.output_contract_id.as_deref(), - request.output_schema_json.as_deref(), - &request.request_id, - )?; - let body = EpiphanyResponsesBody { - model: request.model, - instructions: request.instructions, - input: request - .input - .into_iter() - .map(openai_input_item_from_epiphany_input) - .collect(), - tools: request - .tools - .into_iter() - .map(openai_tool_from_epiphany_tool) - .collect::>>()?, - tool_choice: if requires_initial_tool { - "required" - } else { - "auto" - } - .to_string(), - parallel_tool_calls: false, - reasoning: Some(EpiphanyResponsesReasoning { - effort: parse_reasoning_effort(request.reasoning_effort.as_deref())?, - summary: parse_reasoning_summary(request.reasoning_summary.as_deref())?, - }), - store: false, - stream: true, - include: Vec::new(), - service_tier: parse_service_tier(request.service_tier.as_deref())?, - previous_response_id: request.previous_response_id, - prompt_cache_key: request.prompt_cache_key, - max_output_tokens: request.max_output_tokens, - text, - client_metadata: None, - }; - serde_json::to_value(body).context("failed to encode typed Epiphany Responses body") -} - -fn responses_text_format( - output_contract_id: Option<&str>, - output_schema_json: Option<&str>, - request_id: &str, -) -> Result> { - let Some(schema_json) = output_schema_json else { - return Ok(None); - }; - let mut schema: serde_json::Value = - serde_json::from_str(schema_json).context("output_schema_json is not valid JSON Schema")?; - project_strict_responses_schema(&mut schema)?; - let raw_name = output_contract_id.unwrap_or(request_id); - let mut name = raw_name - .chars() - .map(|ch| { - if ch.is_ascii_alphanumeric() || ch == '_' || ch == '-' { - ch - } else { - '_' - } - }) - .take(64) - .collect::(); - if name.is_empty() { - name = "epiphany_worker_result".to_string(); - } - Ok(Some(serde_json::json!({ - "format": { - "type": "json_schema", - "name": name, - "strict": true, - "schema": schema - } - }))) -} - -fn responses_schema_is_strict(schema: &serde_json::Value) -> bool { - match schema { - serde_json::Value::Object(map) => { - if (map.contains_key("const") || map.contains_key("enum")) && !map.contains_key("type") - { - return false; - } - if schema_map_describes_object(map) { - if map.get("additionalProperties") != Some(&serde_json::Value::Bool(false)) { - return false; - } - let Some(properties) = map.get("properties").and_then(serde_json::Value::as_object) - else { - return false; - }; - let Some(required) = map.get("required").and_then(serde_json::Value::as_array) - else { - return false; - }; - if properties - .keys() - .any(|key| !required.iter().any(|item| item.as_str() == Some(key))) - { - return false; - } - } - map.values().all(responses_schema_is_strict) - } - serde_json::Value::Array(values) => values.iter().all(responses_schema_is_strict), - _ => true, - } -} - -fn project_strict_responses_schema(schema: &mut serde_json::Value) -> Result<()> { - lower_schema_for_responses_format(schema); - require_closed_responses_objects(schema, "$")?; - if !responses_schema_is_strict(schema) { - return Err(anyhow::anyhow!( - "projected Responses output schema is not strict" - )); - } - Ok(()) -} - -fn require_closed_responses_objects(schema: &mut serde_json::Value, path: &str) -> Result<()> { - match schema { - serde_json::Value::Object(map) => { - let describes_object = schema_map_describes_object(map); - if describes_object { - map.insert( - "type".to_string(), - serde_json::Value::String("object".to_string()), - ); - let properties = map - .get("properties") - .and_then(serde_json::Value::as_object) - .cloned() - .unwrap_or_default(); - let canonical_required = map - .get("required") - .and_then(serde_json::Value::as_array) - .cloned() - .unwrap_or_default(); - for required in &canonical_required { - let required = required.as_str().ok_or_else(|| { - anyhow::anyhow!("Responses schema {path} has a non-string required key") - })?; - if !properties.contains_key(required) { - return Err(anyhow::anyhow!( - "Responses schema {path} requires undeclared property {required:?}" - )); - } - } - - let canonical_required = canonical_required - .into_iter() - .filter_map(|value| value.as_str().map(ToString::to_string)) - .collect::>(); - let mut projected = serde_json::Map::new(); - for (name, mut property) in properties { - let property_path = format!("{path}.properties.{name}"); - require_closed_responses_objects(&mut property, &property_path)?; - if !canonical_required.contains(&name) { - property = nullable_responses_property(property); - } - projected.insert(name, property); - } - let required = projected - .keys() - .cloned() - .map(serde_json::Value::String) - .collect(); - map.insert( - "properties".to_string(), - serde_json::Value::Object(projected), - ); - map.insert("required".to_string(), serde_json::Value::Array(required)); - map.insert( - "additionalProperties".to_string(), - serde_json::Value::Bool(false), - ); - } - - for (name, value) in map.iter_mut() { - if name == "properties" && describes_object { - continue; - } - require_closed_responses_objects(value, &format!("{path}.{name}"))?; - } - Ok(()) - } - serde_json::Value::Array(values) => { - for (index, value) in values.iter_mut().enumerate() { - require_closed_responses_objects(value, &format!("{path}[{index}]"))?; - } - Ok(()) - } - _ => Ok(()), - } -} - -fn schema_map_describes_object(map: &serde_json::Map) -> bool { - map.get("type").and_then(serde_json::Value::as_str) == Some("object") - || [ - "properties", - "required", - "additionalProperties", - "patternProperties", - "propertyNames", - "minProperties", - "maxProperties", - ] - .iter() - .any(|keyword| map.contains_key(*keyword)) -} - -fn parent_relative_object_alternatives(value: &serde_json::Value) -> bool { - let Some(alternatives) = value.as_array() else { - return false; - }; - !alternatives.is_empty() - && alternatives.iter().all(|alternative| { - let Some(map) = alternative.as_object() else { - return false; - }; - !map.contains_key("type") - && !map.contains_key("$ref") - && map - .keys() - .any(|key| matches!(key.as_str(), "properties" | "required")) - && map.keys().all(|key| { - matches!( - key.as_str(), - "properties" | "required" | "title" | "description" | "$comment" - ) - }) - }) -} - -fn inferred_json_type(value: &serde_json::Value) -> &'static str { - match value { - serde_json::Value::Null => "null", - serde_json::Value::Bool(_) => "boolean", - serde_json::Value::Number(number) if number.is_i64() || number.is_u64() => "integer", - serde_json::Value::Number(_) => "number", - serde_json::Value::String(_) => "string", - serde_json::Value::Array(_) => "array", - serde_json::Value::Object(_) => "object", - } -} - -fn infer_responses_literal_type(map: &mut serde_json::Map) { - if map.contains_key("type") { - return; - } - let mut types = std::collections::BTreeSet::new(); - if let Some(value) = map.get("const") { - types.insert(inferred_json_type(value)); - } else if let Some(values) = map.get("enum").and_then(serde_json::Value::as_array) { - for value in values { - types.insert(inferred_json_type(value)); - } - } - match types.len() { - 0 => {} - 1 => { - map.insert( - "type".to_string(), - serde_json::Value::String( - types.into_iter().next().expect("one literal type").into(), - ), - ); - } - _ => { - map.insert( - "type".to_string(), - serde_json::Value::Array( - types - .into_iter() - .map(|value| serde_json::Value::String(value.into())) - .collect(), - ), - ); - } - } -} - -fn lower_responses_known_format(map: &mut serde_json::Map) { - if map.get("format").and_then(serde_json::Value::as_str) == Some("uuid") { - // JSON Schema `format` may be annotation-only. Responses supports - // `pattern` for standard models, so give generation the same lexical - // boundary that native UUID decoding will later enforce. - map.remove("format"); - map.entry("pattern".to_string()).or_insert_with(|| { - serde_json::Value::String( - "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$" - .to_string(), - ) - }); - } -} - -fn nullable_responses_property(property: serde_json::Value) -> serde_json::Value { - if property - .get("anyOf") - .and_then(serde_json::Value::as_array) - .is_some_and(|variants| { - variants.iter().any(|variant| { - variant.get("type").and_then(serde_json::Value::as_str) == Some("null") - }) - }) - { - return property; - } - serde_json::json!({ - "anyOf": [property, {"type": "null"}] - }) -} - -const RESPONSES_UNSUPPORTED_SCHEMA_KEYWORDS: &[&str] = &[ - // Composition and conditional validation are outside the Responses subset. - "allOf", - "not", - "dependentRequired", - "dependentSchemas", - "if", - "then", - "else", - // Standard-model Responses accepts type-specific assertions which remain - // useful generation constraints; native ingress still owns final - // validation. `uniqueItems` is excluded because the live strict endpoint - // rejects it even though ordinary JSON Schema permits it. - "patternProperties", - "propertyNames", - "minProperties", - "maxProperties", - "unevaluatedProperties", - "uniqueItems", - "contains", - "minContains", - "maxContains", - "prefixItems", - "unevaluatedItems", - // Annotation and dialect metadata do not belong in the provider contract. - // Title and description are retained because they help generation without - // claiming validation authority. - "default", - "examples", - "readOnly", - "writeOnly", - "$schema", - "$id", - "$anchor", - "$dynamicAnchor", - "$dynamicRef", - "$vocabulary", -]; - -fn lower_schema_for_responses_format(schema: &mut serde_json::Value) { - let serde_json::Value::Object(map) = schema else { - return; - }; - infer_responses_literal_type(map); - lower_responses_known_format(map); - // Responses structured output accepts only a JSON-Schema subset. - // Canonical assertion authority remains enforced by Epiphany ingress - // and Mind admission; this projection owns provider formatting only. - for unsupported in RESPONSES_UNSUPPORTED_SCHEMA_KEYWORDS { - map.remove(*unsupported); - } - if let Some(one_of) = map.remove("oneOf") { - map.insert("anyOf".to_string(), one_of); - } - // A canonical object may use anyOf solely to require one of several - // sibling properties. Responses strict schemas require every object - // alternative to repeat a complete closed property declaration, so - // those parent-relative fragments are not a valid provider shape. - // Runtime ingress remains the owner of the conditional invariant. - if map - .get("anyOf") - .is_some_and(parent_relative_object_alternatives) - { - map.remove("anyOf"); - } - - // Recurse only through positions that contain schemas. A `properties` or - // `$defs` map contains user-authored names; treating those names as schema - // keywords would silently delete legitimate fields such as `format`. - for collection in ["properties", "$defs", "definitions"] { - if let Some(serde_json::Value::Object(children)) = map.get_mut(collection) { - for child in children.values_mut() { - lower_schema_for_responses_format(child); - } - } - } - if let Some(items) = map.get_mut("items") { - match items { - serde_json::Value::Array(items) => { - for item in items { - lower_schema_for_responses_format(item); - } - } - item => lower_schema_for_responses_format(item), - } - } - if let Some(serde_json::Value::Array(alternatives)) = map.get_mut("anyOf") { - for alternative in alternatives { - lower_schema_for_responses_format(alternative); - } - } -} - -fn openai_tool_from_epiphany_tool( - tool: epiphany_openai_adapter::EpiphanyOpenAiToolDefinition, -) -> Result { - let mut parameters: serde_json::Value = serde_json::from_str(&tool.parameters_json) - .with_context(|| format!("tool {} parameters_json is not valid JSON", tool.name))?; - project_strict_responses_schema(&mut parameters) - .with_context(|| format!("tool {} parameters cannot be projected strictly", tool.name))?; - Ok(serde_json::json!({ - "type": "function", - "name": tool.name, - "description": tool.description, - "parameters": parameters, - "strict": true - })) -} - -#[derive(Debug, Clone, Serialize)] -struct EpiphanyResponsesBody { - model: String, - #[serde(skip_serializing_if = "String::is_empty")] - instructions: String, - input: Vec, - tools: Vec, - tool_choice: String, - parallel_tool_calls: bool, - reasoning: Option, - store: bool, - stream: bool, - include: Vec, - #[serde(skip_serializing_if = "Option::is_none")] - service_tier: Option, - #[serde(skip_serializing_if = "Option::is_none")] - previous_response_id: Option, - #[serde(skip_serializing_if = "Option::is_none")] - prompt_cache_key: Option, - #[serde(skip_serializing_if = "Option::is_none")] - max_output_tokens: Option, - #[serde(skip_serializing_if = "Option::is_none")] - text: Option, - #[serde(skip_serializing_if = "Option::is_none")] - client_metadata: Option>, -} - -#[derive(Debug, Clone, Serialize)] -struct EpiphanyResponsesReasoning { - #[serde(skip_serializing_if = "Option::is_none")] - effort: Option, - #[serde(skip_serializing_if = "Option::is_none")] - summary: Option, -} - -#[derive(Debug, Clone, Serialize)] -#[serde(tag = "type", rename_all = "snake_case")] -enum EpiphanyResponsesInputItem { - Message { - role: String, - content: Vec, - }, - FunctionCall { - call_id: String, - name: String, - arguments: String, - }, - FunctionCallOutput { - call_id: String, - output: String, - }, -} - -#[derive(Debug, Clone, Serialize)] -#[serde(tag = "type", rename_all = "snake_case")] -enum EpiphanyResponsesContentItem { - InputText { text: String }, - OutputText { text: String }, -} - -fn openai_input_item_from_epiphany_input( - input: EpiphanyOpenAiInputItem, -) -> EpiphanyResponsesInputItem { - match input { - EpiphanyOpenAiInputItem::UserText { text } => EpiphanyResponsesInputItem::Message { - role: "user".to_string(), - content: vec![EpiphanyResponsesContentItem::InputText { text }], - }, - EpiphanyOpenAiInputItem::AssistantText { text } => EpiphanyResponsesInputItem::Message { - role: "assistant".to_string(), - content: vec![EpiphanyResponsesContentItem::OutputText { text }], - }, - EpiphanyOpenAiInputItem::ToolCall { - call_id, - name, - arguments, - } => EpiphanyResponsesInputItem::FunctionCall { - call_id: responses_call_id(&call_id), - name, - arguments, - }, - EpiphanyOpenAiInputItem::ToolResult { call_id, output } => { - EpiphanyResponsesInputItem::FunctionCallOutput { - call_id: responses_call_id(&call_id), - output, - } - } - } -} - -fn responses_call_id(internal_call_id: &str) -> String { - if !internal_call_id.is_empty() - && internal_call_id.is_ascii() - && internal_call_id.len() <= RESPONSES_CALL_ID_MAX_BYTES - { - return internal_call_id.to_string(); - } - - let digest = format!("{:x}", Sha256::digest(internal_call_id.as_bytes())); - format!("epi-{}", &digest[..RESPONSES_CALL_ID_MAX_BYTES - 4]) -} - -#[derive(Debug, Deserialize)] -struct EpiphanyResponsesStreamEvent { - #[serde(rename = "type")] - kind: String, - response: Option, - item: Option, - item_id: Option, - call_id: Option, - delta: Option, -} - -#[derive(Debug, Deserialize)] -struct EpiphanyResponseCompleted { - id: String, - #[serde(default)] - usage: Option, -} - -#[derive(Debug, Deserialize)] -struct EpiphanyResponseCompletedUsage { - input_tokens: i64, - output_tokens: i64, - input_tokens_details: Option, - output_tokens_details: Option, -} - -#[derive(Debug, Deserialize)] -struct EpiphanyResponseCompletedInputTokensDetails { - cached_tokens: i64, -} - -#[derive(Debug, Deserialize)] -struct EpiphanyResponseCompletedOutputTokensDetails { - reasoning_tokens: i64, -} - -struct EpiphanyResponsesStreamState { - request_id: String, - requested_model: String, - sequence: u64, - frame_sequence: u64, - completed: bool, - pending_tool_calls: HashMap, - events: Vec, -} - -#[derive(Debug, Clone, Default)] -struct PendingToolCall { - call_id: Option, - name: Option, - arguments: String, -} - -impl EpiphanyResponsesStreamState { - fn new(request_id: &str, requested_model: &str) -> Self { - Self { - request_id: request_id.to_string(), - requested_model: requested_model.to_string(), - sequence: 0, - frame_sequence: 0, - completed: false, - pending_tool_calls: HashMap::new(), - events: Vec::new(), - } - } - - fn push_sse_frame(&mut self, frame: &str) -> EpiphanyResponsesFrameObservation { - let frame_sequence = self.frame_sequence; - self.frame_sequence += 1; - let Ok(event) = serde_json::from_str::(frame) else { - return EpiphanyResponsesFrameObservation { - frame_sequence, - kind: "unparseable".to_string(), - recognized: false, - delta_preview: None, - }; - }; - let kind = event.kind.clone(); - let delta_preview = event.delta.as_deref().map(delta_preview); - let recognized = match event.kind.as_str() { - "response.output_text.delta" => { - if let Some(text) = event.delta { - self.push_payload(EpiphanyOpenAiStreamPayload::TextDelta { text }); - } - true - } - "response.reasoning_summary_text.delta" | "response.reasoning_text.delta" => { - if let Some(text) = event.delta { - self.push_payload(EpiphanyOpenAiStreamPayload::ReasoningDelta { text }); - } - true - } - "response.output_item.added" => { - if let Some(item) = event.item { - self.seed_tool_call_from_item(&item); - } - true - } - "response.function_call_arguments.delta" => { - if let (Some(arguments), Some(item_id)) = - (event.delta, event.item_id.clone().or(event.call_id.clone())) - { - self.pending_tool_calls - .entry(item_id.clone()) - .or_insert_with(|| PendingToolCall { - call_id: event.call_id.clone().or(Some(item_id)), - name: None, - arguments: String::new(), - }) - .arguments - .push_str(&arguments); - } - true - } - "response.custom_tool_call_input.delta" => { - if let (Some(arguments), Some(item_id)) = - (event.delta, event.item_id.clone().or(event.call_id.clone())) - { - self.pending_tool_calls - .entry(item_id.clone()) - .or_insert_with(|| PendingToolCall { - call_id: event.call_id.clone().or(Some(item_id)), - name: None, - arguments: String::new(), - }) - .arguments - .push_str(&arguments); - } - true - } - "response.output_item.done" => { - if let Some(item) = event.item { - self.push_tool_call_from_done_item(&item); - } - true - } - "response.completed" => { - if let Some(response) = event.response { - match serde_json::from_value::(response) { - Ok(completed) => self.push_completed(completed), - Err(err) => self.push_failed(format!( - "failed to parse response.completed event: {err}" - )), - } - } - self.completed = true; - true - } - "response.failed" | "response.incomplete" => { - self.push_failed(response_error_message(event.response.as_ref())); - true - } - _ => false, - }; - EpiphanyResponsesFrameObservation { - frame_sequence, - kind, - recognized, - delta_preview, - } - } - - fn push_completed(&mut self, completed: EpiphanyResponseCompleted) { - let mut receipt = EpiphanyOpenAiModelReceipt::new(&self.request_id, &self.requested_model); - receipt.response_id = Some(completed.id); - receipt.transport = Some("epiphany_direct_responses_http".to_string()); - if let Some(usage) = completed.usage { - receipt.input_tokens = nonnegative_i64_to_u64(usage.input_tokens); - receipt.output_tokens = nonnegative_i64_to_u64(usage.output_tokens); - receipt.reasoning_output_tokens = usage - .output_tokens_details - .and_then(|details| nonnegative_i64_to_u64(details.reasoning_tokens)); - receipt.cached_input_tokens = usage - .input_tokens_details - .and_then(|details| nonnegative_i64_to_u64(details.cached_tokens)); - } - self.push_payload(EpiphanyOpenAiStreamPayload::Completed { receipt }); - } - - fn seed_tool_call_from_item(&mut self, item: &Value) { - let Some(kind) = item_type(item) else { - return; - }; - if kind != "function_call" && kind != "custom_tool_call" { - return; - } - let Some(item_id) = item_identity(item) else { - return; - }; - let pending = self.pending_tool_calls.entry(item_id.clone()).or_default(); - if pending.call_id.is_none() { - pending.call_id = item_call_id(item).or(Some(item_id)); - } - if pending.name.is_none() { - pending.name = item_name(item); - } - if pending.arguments.is_empty() - && let Some(arguments) = item_arguments(item) - { - pending.arguments = arguments; - } - } - - fn push_tool_call_from_done_item(&mut self, item: &Value) { - let Some(kind) = item_type(item) else { - return; - }; - if kind != "function_call" && kind != "custom_tool_call" { - return; - } - let item_id = item_identity(item); - let pending = item_id - .as_ref() - .and_then(|id| self.pending_tool_calls.remove(id)) - .unwrap_or_default(); - let Some(name) = item_name(item).or(pending.name) else { - return; - }; - let Some(call_id) = item_call_id(item).or(pending.call_id).or(item_id) else { - return; - }; - let arguments = item_arguments(item).unwrap_or(pending.arguments); - self.push_payload(EpiphanyOpenAiStreamPayload::ToolCall { - call_id, - name, - arguments, - }); - } - - fn push_failed(&mut self, message: String) { - self.push_payload(EpiphanyOpenAiStreamPayload::Failed { message }); - self.completed = true; - } - - fn push_payload(&mut self, payload: EpiphanyOpenAiStreamPayload) { - self.events.push(EpiphanyOpenAiStreamEvent { - schema_id: epiphany_openai_adapter::OPENAI_ADAPTER_EVENT_SCHEMA_ID.to_string(), - request_id: self.request_id.clone(), - sequence: self.sequence, - payload, - }); - self.sequence += 1; - } -} - -fn delta_preview(delta: &str) -> String { - const MAX_PREVIEW_CHARS: usize = 120; - let mut preview = delta.chars().take(MAX_PREVIEW_CHARS).collect::(); - if delta.chars().count() > MAX_PREVIEW_CHARS { - preview.push_str("..."); - } - preview.replace(['\r', '\n', '\t'], " ") -} - -fn item_type(item: &Value) -> Option<&str> { - item.get("type").and_then(Value::as_str) -} - -fn item_identity(item: &Value) -> Option { - item.get("id") - .and_then(Value::as_str) - .or_else(|| item.get("item_id").and_then(Value::as_str)) - .or_else(|| item.get("call_id").and_then(Value::as_str)) - .map(ToString::to_string) -} - -fn item_call_id(item: &Value) -> Option { - item.get("call_id") - .and_then(Value::as_str) - .map(ToString::to_string) -} - -fn item_name(item: &Value) -> Option { - item.get("name") - .and_then(Value::as_str) - .map(ToString::to_string) -} - -fn item_arguments(item: &Value) -> Option { - item.get("arguments") - .or_else(|| item.get("input")) - .and_then(Value::as_str) - .map(ToString::to_string) -} - -fn parse_reasoning_effort(value: Option<&str>) -> Result> { - match value { - None => Ok(None), - Some("none" | "minimal" | "low" | "medium" | "high" | "xhigh") => { - Ok(value.map(ToString::to_string)) - } - Some(other) => anyhow::bail!("invalid reasoning_effort: {other}"), - } -} - -fn parse_reasoning_summary(value: Option<&str>) -> Result> { - match value { - None => Ok(None), - Some("auto" | "concise" | "detailed" | "none") => Ok(value.map(ToString::to_string)), - Some(other) => anyhow::bail!("invalid reasoning_summary: {other}"), - } -} - -fn parse_service_tier(value: Option<&str>) -> Result> { - match value { - None => Ok(None), - Some("fast" | "flex") => Ok(value.map(ToString::to_string)), - Some(other) => anyhow::bail!("invalid service_tier: {other}"), - } -} - -fn nonnegative_i64_to_u64(value: i64) -> Option { - u64::try_from(value).ok() -} - -fn default_base_url_for_auth(auth: &CodexAuth) -> &'static str { - if auth.uses_codex_backend() { - CHATGPT_CODEX_BASE_URL - } else { - OPENAI_API_BASE_URL - } -} - -fn attach_codex_auth_headers( - auth: &CodexAuth, - headers: &mut http::HeaderMap, -) -> std::io::Result<()> { - let token = auth.get_token()?; - let value = format!("Bearer {token}"); - if let Ok(value) = value.parse() { - headers.insert(http::header::AUTHORIZATION, value); - } - if let Some(account_id) = auth.get_account_id() - && let Ok(value) = account_id.parse() - { - headers.insert("ChatGPT-Account-ID", value); - } - if auth.is_fedramp_account() { - headers.insert("X-OpenAI-Fedramp", "true".parse().expect("valid header")); - } - Ok(()) -} - -fn attach_optional_env_header(headers: &mut http::HeaderMap, name: &'static str, env_key: &str) { - let Ok(value) = std::env::var(env_key) else { - return; - }; - let value = value.trim(); - if value.is_empty() { - return; - } - if let Ok(value) = value.parse() { - headers.insert(name, value); - } -} - -fn response_error_message(response: Option<&Value>) -> String { - response - .and_then(|response| response.get("error")) - .and_then(|error| error.get("message")) - .and_then(Value::as_str) - .or_else(|| { - response - .and_then(|response| response.get("incomplete_details")) - .and_then(|details| details.get("reason")) - .and_then(Value::as_str) - }) - .unwrap_or("Responses stream failed") - .to_string() -} - -fn transport_error_to_anyhow(err: TransportError) -> anyhow::Error { - anyhow::anyhow!("failed to open provider transport: {err}") -} - -fn auth_mode_from_manager( - auth_manager: &AuthManager, - auth: Option<&CodexAuth>, -) -> EpiphanyOpenAiAuthMode { - match auth_manager.auth_mode() { - Some(AuthMode::ApiKey) => EpiphanyOpenAiAuthMode::ApiKey, - Some(AuthMode::Chatgpt) | Some(AuthMode::ChatgptAuthTokens) => { - EpiphanyOpenAiAuthMode::ChatGptSubscription - } - Some(AuthMode::AgentIdentity) => EpiphanyOpenAiAuthMode::ExternalBearer, - None => auth_mode_from_codex_auth(auth), - } -} - -fn auth_mode_from_codex_auth(auth: Option<&CodexAuth>) -> EpiphanyOpenAiAuthMode { - let Some(auth) = auth else { - return EpiphanyOpenAiAuthMode::Unknown; - }; - if auth.is_api_key_auth() { - EpiphanyOpenAiAuthMode::ApiKey - } else if auth.is_chatgpt_auth() { - EpiphanyOpenAiAuthMode::ChatGptSubscription - } else { - EpiphanyOpenAiAuthMode::ExternalBearer - } -} - -#[cfg(test)] -mod tests { - use super::*; - - fn openrouter_request( - request_id: &str, - conversation_id: &str, - instructions: &str, - ) -> EpiphanyOpenAiModelRequest { - let mut request = EpiphanyOpenAiModelRequest::new( - request_id, - conversation_id, - "stealth/ox-alpha", - instructions, - ); - request.provider_id = "openrouter".to_string(); - request.wire_dialect = EpiphanyOpenAiWireDialect::ChatCompletionsTerminalTool; - request - } - - #[test] - fn static_openrouter_status_does_not_claim_codex_transport() { - let status = status_from_static_api_key( - OPENROUTER_SPINE_ADAPTER_ID, - Some("stealth/ox-alpha".to_string()), - ); - - assert_eq!(status.adapter_id, OPENROUTER_SPINE_ADAPTER_ID); - assert_eq!(status.auth_mode, EpiphanyOpenAiAuthMode::ApiKey); - assert_eq!(status.default_model.as_deref(), Some("stealth/ox-alpha")); - assert!(!status.supports_websockets); - assert!(!status.codex_transport_attached); - } - - #[test] - fn openrouter_whole_request_timeout_is_explicit() -> Result<()> { - let bounded = EpiphanyOpenRouterTransport::new("test-key", Some(Duration::from_secs(90)))?; - assert_eq!(bounded.request_timeout, Some(Duration::from_secs(90))); - let outer_budget_owned = EpiphanyOpenRouterTransport::new("test-key", None)?; - assert_eq!(outer_budget_owned.request_timeout, None); - assert!(EpiphanyOpenRouterTransport::new("test-key", Some(Duration::ZERO)).is_err()); - Ok(()) - } - - #[test] - fn openrouter_terminal_tool_owns_typed_output_instead_of_response_format() { - let mut request = openrouter_request( - "req-terminal", - "conversation-terminal", - "Return the typed result.", - ); - request.input.push(EpiphanyOpenAiInputItem::UserText { - text: "Decide.".to_string(), - }); - request.output_schema_json = Some( - serde_json::json!({ - "type": "object", - "additionalProperties": false, - "properties": {"status": {"type": "string", "enum": ["ok"]}}, - "required": ["status"] - }) - .to_string(), - ); - request.max_output_tokens = Some(768); - - let body = chat_completions_body_from_epiphany(&request).expect("request should map"); - - assert!(body.get("response_format").is_none()); - assert_eq!(body["tool_choice"], "required"); - assert_eq!(body["max_tokens"], 768); - assert_eq!(body["tools"].as_array().expect("tools").len(), 1); - assert_eq!( - body["tools"][0]["function"]["name"], - OPENROUTER_TERMINAL_TOOL_NAME - ); - assert_eq!( - body["tools"][0]["function"]["parameters"]["properties"]["status"]["enum"][0], - "ok" - ); - } - - #[test] - fn openrouter_requires_source_sight_before_exposing_terminal_decision() { - let mut request = - openrouter_request("req-tools", "conversation-tools", "Inspect, then decide."); - request.input.push(EpiphanyOpenAiInputItem::UserText { - text: "Inspect the source.".to_string(), - }); - request - .tools - .push(epiphany_openai_adapter::EpiphanyOpenAiToolDefinition { - name: "mcp__epiphany_source__read_file".to_string(), - description: "Read a file.".to_string(), - parameters_json: serde_json::json!({ - "type": "object", - "additionalProperties": false, - "properties": {"path": {"type": "string"}}, - "required": ["path"] - }) - .to_string(), - }); - request.output_schema_json = Some( - serde_json::json!({ - "type": "object", - "additionalProperties": false, - "properties": {"status": {"type": "string"}}, - "required": ["status"] - }) - .to_string(), - ); - - let initial = chat_completions_body_from_epiphany(&request).expect("initial request"); - assert_eq!(initial["tool_choice"], "required"); - assert_eq!(initial["tools"].as_array().expect("tools").len(), 1); - assert_eq!( - initial["tools"][0]["function"]["name"], - "mcp__epiphany_source__read_file" - ); - - request.input.push(EpiphanyOpenAiInputItem::ToolCall { - call_id: "call-1".to_string(), - name: "mcp__epiphany_source__read_file".to_string(), - arguments: r#"{"path":"README.md"}"#.to_string(), - }); - request.input.push(EpiphanyOpenAiInputItem::ToolResult { - call_id: "call-1".to_string(), - output: r#"{"ok":true}"#.to_string(), - }); - let followup = chat_completions_body_from_epiphany(&request).expect("followup request"); - assert_eq!(followup["tool_choice"], "required"); - assert_eq!(followup["tools"].as_array().expect("tools").len(), 2); - assert_eq!( - followup["tools"][1]["function"]["name"], - OPENROUTER_TERMINAL_TOOL_NAME - ); - assert_eq!(followup["messages"][2]["tool_calls"][0]["id"], "call-1"); - assert_eq!(followup["messages"][3]["tool_call_id"], "call-1"); - } - - #[test] - fn openrouter_terminal_call_becomes_exact_assistant_json() { - let request = openrouter_request( - "req-terminal-events", - "conversation-terminal-events", - "Return the typed result.", - ); - let response = OpenRouterChatCompletionResponse { - id: Some("generation-1".to_string()), - model: Some("stealth/ox-alpha".to_string()), - choices: vec![OpenRouterChatChoice { - message: OpenRouterChatMessage { - content: None, - reasoning: None, - tool_calls: vec![OpenRouterChatToolCall { - id: "call-terminal".to_string(), - function: OpenRouterChatFunctionCall { - name: OPENROUTER_TERMINAL_TOOL_NAME.to_string(), - arguments: r#"{"status":"ok"}"#.to_string(), - }, - }], - }, - }], - usage: Some(OpenRouterChatUsage { - prompt_tokens: Some(12), - completion_tokens: Some(3), - completion_tokens_details: Some(OpenRouterCompletionTokenDetails { - reasoning_tokens: Some(1), - }), - prompt_tokens_details: Some(OpenRouterPromptTokenDetails { - cached_tokens: Some(8), - }), - }), - }; - - let events = openrouter_events_from_chat_completion(&request, response) - .expect("response should normalize"); - assert!(matches!( - &events[0].payload, - EpiphanyOpenAiStreamPayload::TextDelta { text } - if text == r#"{"status":"ok"}"# - )); - assert!(matches!( - &events[1].payload, - EpiphanyOpenAiStreamPayload::Completed { receipt } - if receipt.transport.as_deref() == Some("openrouter-chat-completions") - && receipt.input_tokens == Some(12) - && receipt.cached_input_tokens == Some(8) - && receipt.reasoning_output_tokens == Some(1) - )); - } - - #[test] - fn maps_typed_request_to_responses_body_without_codex_protocol_payload() { - let mut request = EpiphanyOpenAiModelRequest::new( - "req-1", - "conversation-1", - "gpt-5.4", - "Answer plainly.", - ); - request.input.push(EpiphanyOpenAiInputItem::UserText { - text: "hello".to_string(), - }); - request.input.push(EpiphanyOpenAiInputItem::ToolCall { - call_id: "call-1".to_string(), - name: "mcp__epiphany_source__read_file".to_string(), - arguments: "{\"path\":\"README.md\"}".to_string(), - }); - request.input.push(EpiphanyOpenAiInputItem::ToolResult { - call_id: "call-1".to_string(), - output: "{\"ok\":true}".to_string(), - }); - request.reasoning_effort = Some("low".to_string()); - request.reasoning_summary = Some("concise".to_string()); - request.service_tier = Some("flex".to_string()); - request.previous_response_id = Some("resp-1".to_string()); - request.max_output_tokens = Some(1_024); - request.prompt_cache_key = Some("ghostlight:narrator:v1".to_string()); - request - .tools - .push(epiphany_openai_adapter::EpiphanyOpenAiToolDefinition { - name: "mcp__epiphany_source__read_file".to_string(), - description: "Read a bounded file slice.".to_string(), - parameters_json: serde_json::json!({ - "type": "object", - "properties": {"path": {"type": "string"}}, - "required": ["path"] - }) - .to_string(), - }); - - let responses = responses_body_from_epiphany(request).expect("request should map"); - - assert_eq!(responses["model"], "gpt-5.4"); - assert_eq!(responses["instructions"], "Answer plainly."); - assert_eq!(responses["previous_response_id"], "resp-1"); - assert_eq!(responses["input"][0]["content"][0]["type"], "input_text"); - assert_eq!(responses["input"][1]["type"], "function_call"); - assert_eq!( - responses["input"][1]["name"], - "mcp__epiphany_source__read_file" - ); - assert_eq!(responses["input"][2]["type"], "function_call_output"); - assert_eq!(responses["stream"], true); - assert_eq!(responses["store"], false); - assert_eq!(responses["service_tier"], "flex"); - assert_eq!(responses["max_output_tokens"], 1_024); - assert_eq!(responses["prompt_cache_key"], "ghostlight:narrator:v1"); - assert_eq!(responses["tools"].as_array().expect("tools").len(), 1); - assert_eq!( - responses["tools"][0]["name"], - "mcp__epiphany_source__read_file" - ); - assert_eq!(responses["tool_choice"], "auto"); - } - - #[test] - fn codex_subscription_request_omits_unsupported_output_limit() { - let mut request = EpiphanyOpenAiModelRequest::new( - "req-codex-limit", - "conversation-codex-limit", - "gpt-5.4", - "Return a bounded answer.", - ); - request.max_output_tokens = Some(512); - - let codex = request_for_responses_backend(request.clone(), true); - let openai = request_for_responses_backend(request, false); - - assert_eq!(codex.max_output_tokens, None); - assert_eq!(openai.max_output_tokens, Some(512)); - } - - #[test] - fn responses_call_ids_are_one_shared_bounded_transport_projection() { - assert_eq!(responses_call_id("call-1"), "call-1"); - - let internal_call_id = "call-requested-public-source-3d04d447ac7eb759245b7c732c2942465f78611c7a3b1716169a92582f1361ae"; - let alias = responses_call_id(internal_call_id); - assert!(alias.is_ascii()); - assert_eq!(alias.len(), RESPONSES_CALL_ID_MAX_BYTES); - assert_eq!(alias, responses_call_id(internal_call_id)); - assert_ne!( - alias, - responses_call_id( - "call-requested-public-source-3d04d447ac7eb759245b7c732c2942465f78611c7a3b1716169a92582f1361af" - ) - ); - - let mut request = EpiphanyOpenAiModelRequest::new( - "req-long-call-id", - "conversation-long-call-id", - "gpt-5.4", - "Use the supplied evidence.", - ); - request.input.push(EpiphanyOpenAiInputItem::ToolCall { - call_id: internal_call_id.to_string(), - name: "github_file".to_string(), - arguments: "{}".to_string(), - }); - request.input.push(EpiphanyOpenAiInputItem::ToolResult { - call_id: internal_call_id.to_string(), - output: "evidence".to_string(), - }); - - let responses = responses_body_from_epiphany(request).expect("request should map"); - assert_eq!(responses["input"][0]["call_id"], alias); - assert_eq!(responses["input"][1]["call_id"], alias); - } - - #[test] - fn tool_bearing_initial_request_requires_one_tool_call() { - let mut request = EpiphanyOpenAiModelRequest::new( - "req-1", - "conversation-1", - "gpt-5.4", - "Inspect before answering.", - ); - request.input.push(EpiphanyOpenAiInputItem::UserText { - text: "verify".to_string(), - }); - request - .tools - .push(epiphany_openai_adapter::EpiphanyOpenAiToolDefinition { - name: "mcp__epiphany_source__read_file".to_string(), - description: "Read a bounded file slice.".to_string(), - parameters_json: serde_json::json!({ - "type": "object", - "properties": {"path": {"type": "string"}}, - "required": ["path"] - }) - .to_string(), - }); - - let responses = responses_body_from_epiphany(request).expect("request should map"); - - assert_eq!(responses["tool_choice"], "required"); - assert_eq!(responses["tools"][0]["strict"], true); - assert_eq!( - responses["tools"][0]["parameters"]["additionalProperties"], - false - ); - } - - #[test] - fn declared_output_schema_reaches_responses_text_format() { - let mut request = EpiphanyOpenAiModelRequest::new( - "req-schema", - "conversation-schema", - "gpt-5.4", - "Return the typed result.", - ); - request.output_contract_id = Some("epiphany.role_worker_output.v3".to_string()); - request.output_schema_json = Some( - serde_json::json!({ - "type": "object", - "additionalProperties": false, - "properties": {"summary": {"type": "string"}}, - "required": ["summary"] - }) - .to_string(), - ); - - let responses = responses_body_from_epiphany(request).expect("request should map"); - - assert_eq!(responses["text"]["format"]["type"], "json_schema"); - assert_eq!( - responses["text"]["format"]["name"], - "epiphany_role_worker_output_v3" - ); - assert_eq!(responses["text"]["format"]["strict"], true); - assert_eq!( - responses["text"]["format"]["schema"]["required"][0], - "summary" - ); - } - - #[test] - fn provider_schema_projection_drops_conditional_authority_only() { - let mut request = EpiphanyOpenAiModelRequest::new( - "req-conditional", - "conversation-conditional", - "gpt-5.4", - "Return the typed result.", - ); - request.output_contract_id = Some("epiphany.worker".to_string()); - request.output_schema_json = Some( - serde_json::json!({ - "type": "object", - "properties": { - "purpose": { - "oneOf": [ - {"type": "string", "const": "evolution"}, - {"type": "string", "const": "repair"} - ] - } - }, - "allOf": [{"if": {"properties": {"purpose": {"const": "repair"}}}, "then": {"required": ["receipt"]}}] - }) - .to_string(), - ); - - let responses = responses_body_from_epiphany(request).expect("request should map"); - let schema = &responses["text"]["format"]["schema"]; - assert_eq!(responses["text"]["format"]["strict"], true); - assert!(schema.get("allOf").is_none()); - assert!(schema["properties"]["purpose"].get("oneOf").is_none()); - assert!(schema["properties"]["purpose"]["anyOf"].is_array()); - assert_eq!(schema["additionalProperties"], false); - assert_eq!(schema["required"], serde_json::json!(["purpose"])); - } - - #[test] - fn provider_schema_projection_drops_parent_relative_presence_alternatives() { - let mut request = EpiphanyOpenAiModelRequest::new( - "req-object-presence", - "conversation-object-presence", - "gpt-5.4", - "Return the typed result.", - ); - request.output_contract_id = Some("epiphany.worker".to_string()); - request.output_schema_json = Some( - serde_json::json!({ - "type": "object", - "properties": { - "statePatch": { - "type": "object", - "properties": { - "scratch": { - "type": "object", - "properties": {"summary": {"type": "string"}}, - "required": ["summary"] - }, - "investigationCheckpoint": { - "type": "object", - "properties": {"focus": {"type": "string"}}, - "required": ["focus"] - } - }, - "anyOf": [ - {"required": ["scratch"]}, - {"required": ["investigationCheckpoint"]} - ] - } - }, - "required": ["statePatch"] - }) - .to_string(), - ); - - let responses = responses_body_from_epiphany(request).expect("request should map"); - let state_patch = &responses["text"]["format"]["schema"]["properties"]["statePatch"]; - assert!(state_patch.get("anyOf").is_none()); - assert_eq!(state_patch["additionalProperties"], false); - assert_eq!( - state_patch["required"], - serde_json::json!(["investigationCheckpoint", "scratch"]) - ); - assert_eq!( - state_patch["properties"]["scratch"]["anyOf"][0]["additionalProperties"], - false - ); - assert_eq!( - state_patch["properties"]["investigationCheckpoint"]["anyOf"][0]["additionalProperties"], - false - ); - } - - #[test] - fn provider_schema_projection_types_literals_and_drops_parent_property_refinements() { - let mut request = EpiphanyOpenAiModelRequest::new( - "req-literal-types", - "conversation-literal-types", - "gpt-5.4", - "Return the typed result.", - ); - request.output_contract_id = Some("epiphany.worker".to_string()); - request.output_schema_json = Some( - serde_json::json!({ - "type": "object", - "required": ["schemaVersion", "effects", "source_refs", "format", "claim_id", "node"], - "properties": { - "schemaVersion": {"const": "epiphany.test.v0"}, - "effects": { - "type": "array", - "items": { - "oneOf": [{ - "type": "object", - "required": ["kind", "memory_kind"], - "properties": { - "kind": {"const": "state_note"}, - "memory_kind": {"enum": ["memory", "social_read", "bond"]} - }, - "additionalProperties": false - }] - } - }, - "source_refs": { - "type": "array", - "minItems": 1, - "maxItems": 8, - "uniqueItems": true, - "items": { - "type": "string", - "minLength": 1, - "pattern": "^source:" - } - }, - "format": { - "type": "string", - "format": "uri" - }, - "claim_id": { - "type": "string", - "format": "uuid" - }, - "node": { - "type": "object", - "required": ["question", "tension"], - "properties": { - "question": {"type": "string"}, - "tension": {"type": "string"} - }, - "anyOf": [ - {"properties": {"question": {"minLength": 1}}}, - {"properties": {"tension": {"minLength": 1}}} - ], - "additionalProperties": false - } - }, - "additionalProperties": false - }) - .to_string(), - ); - - let responses = responses_body_from_epiphany(request).expect("request should map"); - let schema = &responses["text"]["format"]["schema"]; - assert_eq!(schema["properties"]["schemaVersion"]["type"], "string"); - assert_eq!( - schema["properties"]["effects"]["items"]["anyOf"][0]["properties"]["kind"]["type"], - "string" - ); - assert_eq!( - schema["properties"]["effects"]["items"]["anyOf"][0]["properties"]["memory_kind"]["type"], - "string" - ); - let source_refs = &schema["properties"]["source_refs"]; - assert_eq!(source_refs["minItems"], 1); - assert_eq!(source_refs["maxItems"], 8); - assert!(source_refs.get("uniqueItems").is_none()); - assert_eq!(source_refs["items"]["minLength"], 1); - assert_eq!(source_refs["items"]["pattern"], "^source:"); - assert_eq!(schema["properties"]["format"]["type"], "string"); - assert_eq!(schema["properties"]["format"]["format"], "uri"); - assert!(schema["properties"]["claim_id"].get("format").is_none()); - assert_eq!( - schema["properties"]["claim_id"]["pattern"], - "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$" - ); - assert!(schema["properties"]["node"].get("anyOf").is_none()); - } - - #[test] - fn strict_schema_check_recognizes_required_only_object_fragments() { - assert!(!responses_schema_is_strict(&serde_json::json!({ - "anyOf": [{"required": ["scratch"]}, {"required": ["checkpoint"]}] - }))); - assert!(!responses_schema_is_strict(&serde_json::json!({ - "const": "state_note" - }))); - assert!(!responses_schema_is_strict(&serde_json::json!({ - "enum": ["memory", "social_read", "bond"] - }))); - } - - #[test] - fn parses_function_call_output_item_done_as_complete_tool_call() { - let mut state = EpiphanyResponsesStreamState::new("req-tools", "gpt-5.4"); - let observation = state.push_sse_frame( - &serde_json::json!({ - "type": "response.output_item.done", - "item": { - "id": "fc_1", - "type": "function_call", - "name": "mcp__epiphany_source__read_file", - "arguments": "{\"path\":\"README.md\"}", - "call_id": "call_1" - } - }) - .to_string(), - ); - - assert!(observation.recognized); - assert_eq!(state.events.len(), 1); - assert_eq!( - state.events[0].payload, - EpiphanyOpenAiStreamPayload::ToolCall { - call_id: "call_1".to_string(), - name: "mcp__epiphany_source__read_file".to_string(), - arguments: "{\"path\":\"README.md\"}".to_string(), - } - ); - } - - #[test] - fn assembles_function_call_argument_deltas_until_output_item_done() { - let mut state = EpiphanyResponsesStreamState::new("req-tools", "gpt-5.4"); - state.push_sse_frame( - &serde_json::json!({ - "type": "response.output_item.added", - "item": { - "id": "fc_1", - "type": "function_call", - "name": "mcp__epiphany_source__git_show", - "call_id": "call_1", - "arguments": "" - } - }) - .to_string(), - ); - state.push_sse_frame( - &serde_json::json!({ - "type": "response.function_call_arguments.delta", - "item_id": "fc_1", - "call_id": "call_1", - "delta": "{\"revision\":" - }) - .to_string(), - ); - state.push_sse_frame( - &serde_json::json!({ - "type": "response.function_call_arguments.delta", - "item_id": "fc_1", - "call_id": "call_1", - "delta": "\"HEAD\"}" - }) - .to_string(), - ); - state.push_sse_frame( - &serde_json::json!({ - "type": "response.output_item.done", - "item": { - "id": "fc_1", - "type": "function_call", - "name": "mcp__epiphany_source__git_show", - "call_id": "call_1" - } - }) - .to_string(), - ); - - assert_eq!(state.events.len(), 1); - assert_eq!( - state.events[0].payload, - EpiphanyOpenAiStreamPayload::ToolCall { - call_id: "call_1".to_string(), - name: "mcp__epiphany_source__git_show".to_string(), - arguments: "{\"revision\":\"HEAD\"}".to_string(), - } - ); - } -} diff --git a/epiphany-openai-runtime/Cargo.toml b/epiphany-openai-runtime/Cargo.toml index 24911dbc..b3aba99c 100644 --- a/epiphany-openai-runtime/Cargo.toml +++ b/epiphany-openai-runtime/Cargo.toml @@ -13,22 +13,16 @@ path = "src/lib.rs" [dependencies] anyhow = "1" chrono = "0.4.44" -cultcache-rs = { path = "../vendor/cultcache-rs" } -cultmesh-rs = { path = "../vendor/cultmesh-rs" } -cultnet-rs = { path = "../vendor/cultnet-rs" } -codex-login = { path = "../vendor/codex/codex-rs/login" } +cultcache-rs = { git = "https://github.com/GameCult/CultLib.git", rev = "a0813c6eed24d30bf88073ef615b633c77ebfcd6", package = "cultcache-rs" } epiphany-core = { path = "../epiphany-core" } epiphany-model-adapter = { path = "../epiphany-model-adapter" } -epiphany-openai-adapter = { path = "../epiphany-openai-adapter" } -epiphany-openai-codex-spine = { path = "../epiphany-openai-codex-spine" } epiphany-tool-adapter = { path = "../epiphany-tool-adapter" } rmp-serde = "1" serde = { version = "1", features = ["derive"] } serde_json = "1" sha2 = "0.10" -tokio = { version = "1", features = ["macros", "net", "rt-multi-thread", "signal", "sync"] } +tokio = { version = "1", features = ["macros", "rt-multi-thread"] } uuid = { version = "1", features = ["v4"] } [dev-dependencies] -epiphany-state-model = { path = "../epiphany-state-model" } tempfile = "3" diff --git a/epiphany-openai-runtime/src/bin/epiphany-model-connector.rs b/epiphany-openai-runtime/src/bin/epiphany-model-connector.rs deleted file mode 100644 index 8012974e..00000000 --- a/epiphany-openai-runtime/src/bin/epiphany-model-connector.rs +++ /dev/null @@ -1,7 +0,0 @@ -use anyhow::Result; -use epiphany_openai_runtime::{EpiphanyModelConnectorOptions, serve_model_connector}; - -#[tokio::main] -async fn main() -> Result<()> { - serve_model_connector(EpiphanyModelConnectorOptions::from_env_args()?).await -} diff --git a/epiphany-openai-runtime/src/bin/epiphany-model-runtime.rs b/epiphany-openai-runtime/src/bin/epiphany-model-runtime.rs deleted file mode 100644 index 00d98da6..00000000 --- a/epiphany-openai-runtime/src/bin/epiphany-model-runtime.rs +++ /dev/null @@ -1 +0,0 @@ -include!("epiphany-openai-runtime.rs"); diff --git a/epiphany-openai-runtime/src/bin/epiphany-openai-runtime.rs b/epiphany-openai-runtime/src/bin/epiphany-openai-runtime.rs index 0791f100..4b4f3fb4 100644 --- a/epiphany-openai-runtime/src/bin/epiphany-openai-runtime.rs +++ b/epiphany-openai-runtime/src/bin/epiphany-openai-runtime.rs @@ -1,5 +1,6 @@ use std::env; use std::fs; +use std::net::SocketAddr; use std::path::Path; use std::path::PathBuf; use std::process; @@ -14,42 +15,35 @@ use std::time::Duration; use anyhow::Context; use anyhow::Result; use anyhow::anyhow; -use epiphany_model_adapter::EpiphanyModelInputItem; use epiphany_model_adapter::EpiphanyModelRequest; -use epiphany_model_adapter::MODEL_ADAPTER_REQUEST_SCHEMA_ID; -use epiphany_openai_adapter::EpiphanyOpenAiModelRequest; #[cfg(test)] -use epiphany_openai_adapter::{ - EpiphanyOpenAiModelReceipt, EpiphanyOpenAiStreamEvent, EpiphanyOpenAiStreamPayload, +use epiphany_model_adapter::{ + EpiphanyModelReceipt, EpiphanyModelStreamEvent, EpiphanyModelStreamPayload, + MODEL_ADAPTER_EVENT_SCHEMA_ID, }; +#[cfg(test)] +use epiphany_model_adapter::{EpiphanyOpenRouterRequest, EpiphanyProviderRequestPayload}; +use epiphany_openai_runtime::DEFAULT_CODEX_CONNECTOR_ENDPOINT; use epiphany_openai_runtime::DEFAULT_PROVIDER_REQUEST_TIMEOUT; use epiphany_openai_runtime::EpiphanyOpenAiRuntimeOptions; -use epiphany_openai_runtime::EpiphanyWorkerRuntimeOptions; -#[cfg(test)] -use epiphany_openai_runtime::OPENAI_RUNTIME_ROLE; use epiphany_openai_runtime::append_requested_public_source_receipts; use epiphany_openai_runtime::assistant_text_from_model_events; use epiphany_openai_runtime::build_tool_followup_model_request; use epiphany_openai_runtime::build_worker_model_request; use epiphany_openai_runtime::complete_worker_job_from_assistant_text; -use epiphany_openai_runtime::default_codex_home; -use epiphany_openai_runtime::default_options; use epiphany_openai_runtime::fail_model_backed_worker_job; use epiphany_openai_runtime::fail_worker_job; use epiphany_openai_runtime::load_worker_launch_request; #[cfg(test)] -use epiphany_openai_runtime::record_native_model_events; -use epiphany_openai_runtime::run_model_turn; -use epiphany_openai_runtime::run_tool_followup_model_turn; -use epiphany_openai_runtime::run_worker_launch_observed; +use epiphany_openai_runtime::record_model_turn_events; use epiphany_openai_runtime::worker_model_session_id; use epiphany_tool_adapter::EpiphanyToolInvocationIntent; use epiphany_tool_adapter::tool_invocation_intent_key; -use serde::Deserialize; use serde_json::json; -use sha2::Digest; -use sha2::Sha256; -use uuid::Uuid; + +#[path = "../provider_transport.rs"] +mod provider_transport; +use provider_transport::run_model_turn; const DEFAULT_STORE: &str = "state/runtime-spine.msgpack"; const DEFAULT_PROVIDER: &str = "openai-codex"; @@ -59,63 +53,6 @@ async fn main() -> Result<()> { let mut args = env::args().skip(1); let command = args.next().unwrap_or_else(|| "usage".to_string()); match command.as_str() { - "preflight" => { - let mut store = PathBuf::from(DEFAULT_STORE); - let mut required_document_types = Vec::new(); - let mut args = args.peekable(); - while let Some(arg) = args.next() { - match arg.as_str() { - "--store" => { - store = - PathBuf::from(args.next().context("preflight missing --store value")?) - } - "--require-document-type" => required_document_types.push( - args.next() - .context("preflight missing --require-document-type value")?, - ), - other => return Err(anyhow!("unknown preflight argument: {other}")), - } - } - let identity = epiphany_core::runtime_identity(&store)? - .ok_or_else(|| anyhow!("runtime spine is absent at {}", store.display()))?; - let registered_document_types = epiphany_core::runtime_registered_document_types()?; - let missing: Vec = required_document_types - .iter() - .filter(|required| !registered_document_types.contains(required)) - .cloned() - .collect(); - if !missing.is_empty() { - return Err(anyhow!( - "runtime does not register required document types: {}", - missing.join(", ") - )); - } - let executable = env::current_exe()?.canonicalize()?; - let executable_sha256 = format!("{:x}", Sha256::digest(fs::read(&executable)?)); - let schema_catalog_sha256 = format!( - "{:x}", - Sha256::digest(registered_document_types.join("\n").as_bytes()) - ); - let preflight_witness_id = format!( - "openai-runtime-preflight-{}", - executable_sha256.chars().take(16).collect::() - ); - print_json(&json!({ - "schemaVersion": "epiphany.openai_runtime.preflight.v0", - "status": "passed", - "runtimeVersion": env!("CARGO_PKG_VERSION"), - "executable": executable, - "executableSha256": executable_sha256, - "schemaCatalogSha256": schema_catalog_sha256, - "preflightWitnessId": preflight_witness_id, - "runtimeStore": store, - "runtimeId": identity.runtime_id, - "requiredDocumentTypes": required_document_types, - "registeredDocumentTypes": registered_document_types, - "schemaPreflightPassed": true, - "privateStateExposed": false - }))?; - } "audit-decision" => { let mut store = PathBuf::from(DEFAULT_STORE); let mut context_id = None; @@ -180,26 +117,6 @@ async fn main() -> Result<()> { } print_json(&epiphany_core::list_auditable_decision_contexts(&store)?)?; } - "model-turn" => { - let options = parse_model_turn_options(args.collect())?; - require_supported_provider(&options.provider)?; - let request_text = fs::read_to_string(&options.request_path) - .with_context(|| format!("failed to read {}", options.request_path.display()))?; - let output_last_message_path = options.output_last_message_path.clone(); - let request = parse_model_turn_request_json(&request_text) - .with_context(|| format!("failed to parse {}", options.request_path.display()))?; - let runtime_options = options.clone().into_runtime_options_for_model(&request); - let summary = - run_model_turn(&options.provider, runtime_options.clone(), request.clone()).await?; - let request_id = request.request_id; - if let Some(path) = output_last_message_path { - let text = - assistant_text_from_model_events(&runtime_options.store_path, &request_id)?; - fs::write(&path, text) - .with_context(|| format!("failed to write {}", path.display()))?; - } - print_json(&summary)?; - } "run-worker" => { let options = parse_run_worker_options(args.collect())?; require_supported_provider(&options.provider)?; @@ -224,22 +141,19 @@ async fn main() -> Result<()> { )?, Err(_) => { let summary = format!("Worker runtime timed out after {seconds} seconds."); - let result = fail_worker_and_openai_jobs( + fail_worker_and_openai_jobs( &timeout_store, &timeout_job_id, "runtime_timeout", summary.clone(), - "Inspect provider/tool transport before relaunching the worker." - .to_string(), pass_progress.terminal_request_id().as_deref(), )?; json!({ "status": "timeout", "jobId": timeout_job_id, - "workerResultId": result.result_id, - "verdict": result.verdict, + "verdict": "failed", "summary": summary, - "nextSafeMove": result.next_safe_move, + "nextSafeMove": "Inspect provider/tool transport before relaunching the worker.", }) } } @@ -254,199 +168,22 @@ async fn main() -> Result<()> { timeout_guard.store(true, Ordering::SeqCst); print_json(&summary)?; } - "tool-followup" => { - let options = parse_tool_followup_options(args.collect())?; - let request = build_tool_followup_model_request( - &options.store_path, - &options.request_id, - &options.followup_request_id, - )?; - if let Some(parent) = options.output.parent() { - fs::create_dir_all(parent) - .with_context(|| format!("failed to create {}", parent.display()))?; - } - fs::write(&options.output, serde_json::to_string_pretty(&request)?) - .with_context(|| format!("failed to write {}", options.output.display()))?; - print_json(&json!({ - "requestId": request.request_id, - "previousResponseId": request.previous_response_id, - "inputItems": request.input.len(), - "output": options.output, - }))?; - } - "tool-followup-turn" => { - let options = parse_tool_followup_turn_options(args.collect())?; - require_supported_provider(&options.provider)?; - let request = build_tool_followup_model_request( - &options.store_path, - &options.request_id, - &options.followup_request_id, - )?; - let output_last_message_path = options.output_last_message_path.clone(); - let provider = options.provider.clone(); - let runtime_options = options.into_runtime_options_for_model(&request); - let summary = - run_model_turn(&provider, runtime_options.clone(), request.clone()).await?; - if let Some(path) = output_last_message_path { - let text = assistant_text_from_model_events( - &runtime_options.store_path, - &request.request_id, - )?; - fs::write(&path, text) - .with_context(|| format!("failed to write {}", path.display()))?; - } - print_json(&summary)?; - } _ => return Err(anyhow!(usage())), } Ok(()) } -#[derive(Clone)] -struct ModelTurnCliOptions { - provider: String, - store_path: PathBuf, - codex_home: PathBuf, - provider_credential_path: Option, - request_path: PathBuf, - session_id: Option, - job_id: Option, - objective: Option, - default_model: Option, - output_last_message_path: Option, -} - -#[derive(Deserialize)] -#[serde(deny_unknown_fields)] -struct NativeModelRequestJson { - schema_id: String, - request_id: String, - conversation_id: String, - provider: String, - model: String, - instructions: String, - input: Vec, - reasoning_effort: Option, - reasoning_summary: Option, - service_tier: Option, - output_contract_id: Option, -} - -#[derive(Deserialize)] -enum ModelInputItemJson { - UserText(ModelTextJson), - AssistantText(ModelTextJson), - ToolResult(ModelToolResultJson), -} - -#[derive(Deserialize)] -#[serde(deny_unknown_fields)] -struct ModelTextJson { - text: String, -} - -#[derive(Deserialize)] -#[serde(deny_unknown_fields)] -struct ModelToolResultJson { - call_id: String, - output: String, -} - -impl ModelInputItemJson { - fn into_native(self) -> EpiphanyModelInputItem { - match self { - Self::UserText(item) => EpiphanyModelInputItem::UserText { text: item.text }, - Self::AssistantText(item) => EpiphanyModelInputItem::AssistantText { text: item.text }, - Self::ToolResult(item) => EpiphanyModelInputItem::ToolResult { - call_id: item.call_id, - output: item.output, - }, - } - } -} - -fn parse_model_turn_request_json(text: &str) -> Result { - let value: serde_json::Value = - serde_json::from_str(text).context("model-turn request is not valid JSON")?; - let schema_id = value - .as_object() - .and_then(|object| object.get("schema_id")) - .and_then(serde_json::Value::as_str) - .context("model-turn request must be an object with string schema_id")?; - match schema_id { - MODEL_ADAPTER_REQUEST_SCHEMA_ID => { - let request: NativeModelRequestJson = serde_json::from_value(value) - .context("model-turn native request violates epiphany.model_request.v0")?; - anyhow::ensure!( - request.schema_id == MODEL_ADAPTER_REQUEST_SCHEMA_ID, - "native model request schema_id is invalid" - ); - let mut typed = EpiphanyModelRequest::new( - request.request_id, - request.conversation_id, - request.provider, - request.model, - request.instructions, - ); - typed.input = request - .input - .into_iter() - .map(ModelInputItemJson::into_native) - .collect(); - typed.reasoning_effort = request.reasoning_effort; - typed.reasoning_summary = request.reasoning_summary; - typed.service_tier = request.service_tier; - typed.output_contract_id = request.output_contract_id; - Ok(typed) - } - other => Err(anyhow!( - "unsupported model-turn request schema_id {other:?}" - )), - } -} - -impl ModelTurnCliOptions { - fn into_runtime_options_for_model( - self, - request: &EpiphanyModelRequest, - ) -> EpiphanyOpenAiRuntimeOptions { - let openai_request = epiphany_openai_runtime::openai_request_from_model_request(request); - self.into_runtime_options(&openai_request) - } - - fn into_runtime_options( - self, - request: &EpiphanyOpenAiModelRequest, - ) -> EpiphanyOpenAiRuntimeOptions { - let mut options = default_options(self.store_path, self.codex_home, request); - options.provider_credential_path = self.provider_credential_path; - if let Some(session_id) = self.session_id { - options.session_id = session_id; - } - if let Some(job_id) = self.job_id { - options.job_id = job_id; - } - if let Some(objective) = self.objective { - options.objective = objective; - } - if let Some(default_model) = self.default_model { - options.default_model = Some(default_model); - } - options - } -} - #[derive(Clone)] struct RunWorkerCliOptions { provider: String, store_path: PathBuf, - codex_home: PathBuf, provider_credential_path: Option, + connector_endpoint: SocketAddr, + caller_runtime_id: String, mcp_config: Option, job_id: String, model: String, - auto_tools: bool, - tool_adapter_bin: Option, + tool_adapter_bin: PathBuf, cwd: Option, resident_store: Option, max_tool_rounds: usize, @@ -454,111 +191,15 @@ struct RunWorkerCliOptions { activation_token_sha256: String, } -struct ToolFollowupCliOptions { - store_path: PathBuf, - request_id: String, - followup_request_id: String, - output: PathBuf, -} - -struct ToolFollowupTurnCliOptions { - provider: String, - store_path: PathBuf, - codex_home: PathBuf, - provider_credential_path: Option, - request_id: String, - followup_request_id: String, - session_id: Option, - job_id: Option, - objective: Option, - default_model: Option, - output_last_message_path: Option, -} - -impl ToolFollowupTurnCliOptions { - fn into_runtime_options_for_model( - self, - request: &EpiphanyModelRequest, - ) -> EpiphanyOpenAiRuntimeOptions { - let openai_request = epiphany_openai_runtime::openai_request_from_model_request(request); - let mut options = default_options(self.store_path, self.codex_home, &openai_request); - options.provider_credential_path = self.provider_credential_path; - if let Some(session_id) = self.session_id { - options.session_id = session_id; - } - if let Some(job_id) = self.job_id { - options.job_id = job_id; - } - if let Some(objective) = self.objective { - options.objective = objective; - } - if let Some(default_model) = self.default_model { - options.default_model = Some(default_model); - } - options - } -} - -fn parse_model_turn_options(args: Vec) -> Result { - let mut provider = DEFAULT_PROVIDER.to_string(); - let mut store_path = PathBuf::from(DEFAULT_STORE); - let mut codex_home = default_codex_home()?; - let mut provider_credential_path = None; - let mut request_path = None; - let mut session_id = None; - let mut job_id = None; - let mut objective = None; - let mut default_model = None; - let mut output_last_message_path = None; - let mut iter = args.into_iter(); - while let Some(arg) = iter.next() { - match arg.as_str() { - "--provider" => provider = next_value(&mut iter, "--provider")?, - "--store" => store_path = PathBuf::from(next_value(&mut iter, "--store")?), - "--codex-home" => codex_home = PathBuf::from(next_value(&mut iter, "--codex-home")?), - "--provider-credential" => { - provider_credential_path = Some(PathBuf::from(next_value( - &mut iter, - "--provider-credential", - )?)) - } - "--request" => request_path = Some(PathBuf::from(next_value(&mut iter, "--request")?)), - "--session-id" => session_id = Some(next_value(&mut iter, "--session-id")?), - "--job-id" => job_id = Some(next_value(&mut iter, "--job-id")?), - "--objective" => objective = Some(next_value(&mut iter, "--objective")?), - "--default-model" => default_model = Some(next_value(&mut iter, "--default-model")?), - "--output-last-message" => { - output_last_message_path = Some(PathBuf::from(next_value( - &mut iter, - "--output-last-message", - )?)) - } - other => return Err(anyhow!("unknown model-turn argument: {other}")), - } - } - Ok(ModelTurnCliOptions { - provider, - store_path, - codex_home, - provider_credential_path, - request_path: request_path.context("model-turn requires --request")?, - session_id, - job_id, - objective, - default_model, - output_last_message_path, - }) -} - fn parse_run_worker_options(args: Vec) -> Result { let mut provider = DEFAULT_PROVIDER.to_string(); let mut store_path = PathBuf::from(DEFAULT_STORE); - let mut codex_home = default_codex_home()?; let mut provider_credential_path = None; + let mut connector_endpoint = DEFAULT_CODEX_CONNECTOR_ENDPOINT; + let mut caller_runtime_id = "epiphany-model-runtime".to_string(); let mut mcp_config = None; let mut job_id = None; let mut model = default_worker_model(); - let mut auto_tools = false; let mut tool_adapter_bin = None; let mut cwd = None; let mut resident_store = None; @@ -570,19 +211,23 @@ fn parse_run_worker_options(args: Vec) -> Result { match arg.as_str() { "--provider" => provider = next_value(&mut iter, "--provider")?, "--store" => store_path = PathBuf::from(next_value(&mut iter, "--store")?), - "--codex-home" => codex_home = PathBuf::from(next_value(&mut iter, "--codex-home")?), "--provider-credential" => { provider_credential_path = Some(PathBuf::from(next_value( &mut iter, "--provider-credential", )?)) } + "--connector-endpoint" => { + connector_endpoint = next_value(&mut iter, "--connector-endpoint")?.parse()? + } + "--caller-runtime-id" => { + caller_runtime_id = next_value(&mut iter, "--caller-runtime-id")? + } "--mcp-config" => { mcp_config = Some(PathBuf::from(next_value(&mut iter, "--mcp-config")?)) } "--job-id" => job_id = Some(next_value(&mut iter, "--job-id")?), "--model" | "--default-model" => model = next_value(&mut iter, "--model")?, - "--auto-tools" => auto_tools = true, "--tool-adapter-bin" => { tool_adapter_bin = Some(PathBuf::from(next_value(&mut iter, "--tool-adapter-bin")?)) } @@ -605,13 +250,13 @@ fn parse_run_worker_options(args: Vec) -> Result { Ok(RunWorkerCliOptions { provider, store_path, - codex_home, provider_credential_path, + connector_endpoint, + caller_runtime_id, mcp_config, job_id: job_id.context("run-worker requires --job-id")?, model, - auto_tools, - tool_adapter_bin, + tool_adapter_bin: tool_adapter_bin.context("run-worker requires --tool-adapter-bin")?, cwd, resident_store, max_tool_rounds, @@ -655,13 +300,7 @@ fn claim_and_wait_for_worker_activation(options: &RunWorkerCliOptions) -> Result &process, &chrono::Utc::now().to_rfc3339(), )?; - fail_worker_job( - &options.store_path, - &options.job_id, - "Worker activation expired before provider/tool work.".into(), - "Runtime Continuity may supersede only from the terminal unactivated process claim." - .into(), - )?; + fail_worker_job(&options.store_path, &options.job_id)?; return Err(anyhow!( "worker activation gate expired before model/tool work" )); @@ -675,32 +314,6 @@ fn claim_and_wait_for_worker_activation(options: &RunWorkerCliOptions) -> Result } } -fn parse_tool_followup_options(args: Vec) -> Result { - let mut store_path = PathBuf::from(DEFAULT_STORE); - let mut request_id = None; - let mut followup_request_id = None; - let mut output = None; - let mut iter = args.into_iter(); - while let Some(arg) = iter.next() { - match arg.as_str() { - "--store" => store_path = PathBuf::from(next_value(&mut iter, "--store")?), - "--request-id" => request_id = Some(next_value(&mut iter, "--request-id")?), - "--followup-request-id" => { - followup_request_id = Some(next_value(&mut iter, "--followup-request-id")?) - } - "--output" => output = Some(PathBuf::from(next_value(&mut iter, "--output")?)), - other => return Err(anyhow!("unknown tool-followup argument: {other}")), - } - } - Ok(ToolFollowupCliOptions { - store_path, - request_id: request_id.context("tool-followup requires --request-id")?, - followup_request_id: followup_request_id - .unwrap_or_else(|| format!("tool-followup-{}", Uuid::new_v4())), - output: output.context("tool-followup requires --output")?, - }) -} - fn default_worker_model() -> String { env::var("EPIPHANY_MODEL") .or_else(|_| env::var("CODEX_MODEL")) @@ -748,7 +361,6 @@ fn start_run_worker_timeout_watchdog( &job_id, "runtime_timeout", summary.clone(), - "Inspect provider/tool transport before relaunching the worker.".to_string(), pass_progress.terminal_request_id().as_deref(), ); process::exit(124); @@ -761,24 +373,16 @@ fn fail_worker_and_openai_jobs( job_id: &str, failure_kind: &str, summary: String, - next_safe_move: String, terminal_request_id: Option<&str>, -) -> Result { - let result = if let Some(request_id) = terminal_request_id +) -> Result<()> { + if let Some(request_id) = terminal_request_id && persisted_model_request_exists(store_path, request_id)? { - fail_model_backed_worker_job( - store_path, - job_id, - request_id, - failure_kind, - summary.clone(), - next_safe_move, - )? + fail_model_backed_worker_job(store_path, job_id, request_id, failure_kind, summary)?; } else { - fail_worker_job(store_path, job_id, summary.clone(), next_safe_move)? - }; - Ok(result) + fail_worker_job(store_path, job_id)?; + } + Ok(()) } fn persisted_model_request_exists(store_path: &Path, request_id: &str) -> Result { @@ -794,22 +398,19 @@ fn fail_worker_for_runtime_error( terminal_request_id: Option<&str>, ) -> Result { let summary = format!("Worker runtime failed before producing usable output: {error}"); - let result = fail_worker_and_openai_jobs( + fail_worker_and_openai_jobs( store_path, job_id, "worker_runtime_error", summary.clone(), - "Inspect provider/tool transport and runtime adapter errors before relaunching the worker." - .to_string(), terminal_request_id, )?; Ok(json!({ "status": "runtime-error", "jobId": job_id, - "workerResultId": result.result_id, - "verdict": result.verdict, + "verdict": "failed", "summary": summary, - "nextSafeMove": result.next_safe_move, + "nextSafeMove": "Inspect provider/tool transport and runtime adapter errors before relaunching the worker.", })) } @@ -830,30 +431,31 @@ fn seal_worker_runtime_result( } } -async fn run_worker_launch_with_tool_continuation( +async fn run_worker_launch( options: RunWorkerCliOptions, pass_progress: WorkerPassProgress, ) -> Result { let request_timeout = provider_request_timeout(options.max_runtime_seconds); - let tool_adapter_bin = options - .tool_adapter_bin - .clone() - .context("run-worker --auto-tools requires --tool-adapter-bin")?; + let tool_adapter_bin = options.tool_adapter_bin.clone(); let launch_request = load_worker_launch_request(&options.store_path, &options.job_id)?; let basis = epiphany_core::worker_reasoning_basis(&options.store_path, &launch_request)?; epiphany_core::put_reasoning_basis(&options.store_path, &basis)?; let mut initial_request = build_worker_model_request(&launch_request, &options.provider, &options.model, &basis)?; - let requested_public_source_intents = - if launch_request.repo_frontier_research_request_id.is_some() { - epiphany_core::put_runtime_requested_public_source_intents( - &options.store_path, - &options.job_id, - &now(), - )? - } else { - Vec::new() - }; + let is_frontier_research = matches!( + launch_request.launch_document()?, + epiphany_core::EpiphanyWorkerLaunchDocument::Role(document) + if document.frontier_research_context.is_some() + ); + let requested_public_source_intents = if is_frontier_research { + epiphany_core::put_runtime_requested_public_source_intents( + &options.store_path, + &options.job_id, + &now(), + )? + } else { + Vec::new() + }; let mut requested_public_source_runs = Vec::new(); for intent in &requested_public_source_intents { requested_public_source_runs.push(run_tool_adapter( @@ -872,8 +474,9 @@ async fn run_worker_launch_with_tool_continuation( )?; let openai_options = EpiphanyOpenAiRuntimeOptions { store_path: options.store_path.clone(), - codex_home: options.codex_home.clone(), provider_credential_path: options.provider_credential_path.clone(), + connector_endpoint: Some(options.connector_endpoint), + caller_runtime_id: options.caller_runtime_id.clone(), session_id: worker_model_session_id(&launch_request.job_id), job_id: format!("openai-worker-{}", launch_request.job_id), objective: format!( @@ -958,24 +561,59 @@ async fn run_worker_launch_with_tool_continuation( let assistant_text = assistant_text_from_model_events(&options.store_path, ¤t_request_id)?; - let worker_result = complete_worker_job_from_assistant_text( + if let Some(failure) = complete_worker_job_from_assistant_text( &options.store_path, &launch_request, ¤t_request_id, &openai_summary, &assistant_text, - )?; - epiphany_core::close_runtime_session( - &options.store_path, - epiphany_core::RuntimeSpineSessionClosureOptions { - session_id: openai_options.session_id.clone(), - completed_at: chrono::Utc::now().to_rfc3339(), - summary: format!( - "Worker model execution {} reached terminal result {}.", - launch_request.job_id, worker_result.result_id - ), - }, - )?; + )? { + return Ok(json!({ + "store": options.store_path, + "jobId": launch_request.job_id, + "bindingId": launch_request.binding_id, + "role": launch_request.role, + "requestId": current_request_id, + "openaiVerdict": openai_summary.verdict, + "openaiSummary": openai_summary.summary, + "workerResultId": null, + "verdict": "failed", + "summary": failure.summary, + "nextSafeMove": "Inspect the typed model-pass failure before launching a fresh request identity.", + "requestedPublicSourceRuns": requested_public_source_runs, + "toolRounds": tool_rounds, + })); + } + let (worker_result_id, verdict, summary, next_safe_move) = + match launch_request.launch_document()? { + epiphany_core::EpiphanyWorkerLaunchDocument::Role(_) => { + let result = epiphany_core::runtime_role_worker_result( + &options.store_path, + &launch_request.job_id, + )? + .ok_or_else(|| anyhow!("worker completion lost its typed role result"))?; + ( + result.result_id, + result.verdict, + result.summary, + result.next_safe_move, + ) + } + epiphany_core::EpiphanyWorkerLaunchDocument::Reorient(_) => { + let result = epiphany_core::runtime_reorient_worker_result( + &options.store_path, + &launch_request.job_id, + )? + .ok_or_else(|| anyhow!("worker completion lost its typed reorientation result"))?; + ( + result.result_id, + result.mode, + result.summary, + result.next_safe_move, + ) + } + }; + epiphany_core::close_runtime_session(&options.store_path, &openai_options.session_id)?; Ok(json!({ "store": options.store_path, @@ -983,15 +621,12 @@ async fn run_worker_launch_with_tool_continuation( "bindingId": launch_request.binding_id, "role": launch_request.role, "requestId": current_request_id, - "openaiResultId": openai_summary.result_id, "openaiVerdict": openai_summary.verdict, "openaiSummary": openai_summary.summary, - "workerResultId": worker_result.result_id, - "verdict": worker_result.verdict, - "summary": worker_result.summary, - "nextSafeMove": worker_result.next_safe_move, - "evidenceRefs": worker_result.evidence_refs, - "artifactRefs": worker_result.artifact_refs, + "workerResultId": worker_result_id, + "verdict": verdict, + "summary": summary, + "nextSafeMove": next_safe_move, "requestedPublicSourceRuns": requested_public_source_runs, "toolRounds": tool_rounds, })) @@ -1046,14 +681,12 @@ fn fail_worker_for_tool_round_limit( &openai_summary.tool_intent_ids, "worker tool round limit closed this intent before execution", )?; - let result = fail_model_backed_worker_job( + let failure = fail_model_backed_worker_job( store_path, &launch_request.job_id, current_request_id, "tool_round_limit", summary.clone(), - "Inspect the worker request, tool receipts, and model/tool loop before relaunching." - .to_string(), )?; Ok(json!({ "status": "tool-round-limit", @@ -1062,13 +695,12 @@ fn fail_worker_for_tool_round_limit( "bindingId": launch_request.binding_id, "role": launch_request.role, "requestId": current_request_id, - "openaiResultId": openai_summary.result_id, "openaiVerdict": openai_summary.verdict, "openaiSummary": openai_summary.summary, - "workerResultId": result.result_id, - "verdict": result.verdict, + "modelPassFailureId": failure.failure_id, + "verdict": "failed", "summary": summary, - "nextSafeMove": result.next_safe_move, + "nextSafeMove": "Inspect the worker request, tool receipts, and model/tool loop before relaunching.", "pendingToolIntentIds": openai_summary.tool_intent_ids, "toolRounds": tool_rounds, })) @@ -1092,14 +724,12 @@ fn fail_worker_for_repeated_tool_loop( &openai_summary.tool_intent_ids, "worker repeated an identical tool round; intent closed before execution", )?; - let result = fail_model_backed_worker_job( + let failure = fail_model_backed_worker_job( store_path, &launch_request.job_id, current_request_id, "repeated_tool_loop", summary.clone(), - "Inspect the repeated tool fingerprints and decide whether the worker needs a narrower evidence bundle, a repaired tool, or a higher explicit limit." - .to_string(), )?; Ok(json!({ "status": "tool-loop-stalled", @@ -1108,13 +738,12 @@ fn fail_worker_for_repeated_tool_loop( "bindingId": launch_request.binding_id, "role": launch_request.role, "requestId": current_request_id, - "openaiResultId": openai_summary.result_id, "openaiVerdict": openai_summary.verdict, "openaiSummary": openai_summary.summary, - "workerResultId": result.result_id, - "verdict": result.verdict, + "modelPassFailureId": failure.failure_id, + "verdict": "failed", "summary": summary, - "nextSafeMove": result.next_safe_move, + "nextSafeMove": "Inspect the repeated tool fingerprints and decide whether the worker needs a narrower evidence bundle, a repaired tool, or a higher explicit limit.", "pendingToolIntentIds": openai_summary.tool_intent_ids, "pendingToolFingerprints": tool_fingerprints, "toolRounds": tool_rounds, @@ -1190,7 +819,7 @@ mod tests { use epiphany_core::EpiphanyRuntimeJobStatus; use epiphany_core::EpiphanyWorkerLaunchDocument; use epiphany_core::RuntimeSpineHeartbeatJobOptions; - use epiphany_core::runtime_job_snapshot; + use epiphany_core::runtime_job; use epiphany_core::runtime_worker_launch_request; use epiphany_tool_adapter::EpiphanyToolInvocationIntent; use tempfile::tempdir; @@ -1204,15 +833,6 @@ mod tests { created_at: options.created_at.clone(), }, )?; - epiphany_core::ensure_runtime_session( - store, - epiphany_core::RuntimeSpineSessionOptions { - session_id: options.session_id.clone(), - objective: options.objective.clone(), - created_at: options.created_at.clone(), - coordinator_note: options.coordinator_note.clone(), - }, - )?; let mut cache = epiphany_core::runtime_spine_cache(store)?; cache.pull_all_backing_stores()?; let prepared = epiphany_core::prepare_runtime_spine_heartbeat_job(&cache, options)?; @@ -1230,29 +850,13 @@ mod tests { .expect("typed model-pass failure"); assert_eq!(failure.failure_kind, failure_kind); assert_eq!(failure.model_request_id, request_id); - let outer = runtime_job_snapshot(store, outer_job_id)?.expect("outer worker snapshot"); - assert_eq!( - outer - .result - .expect("outer worker result") - .decision_context_id - .as_deref(), - Some(failure.decision_context_id.as_str()) - ); - let transport = runtime_job_snapshot(store, &failure.runtime_job_id)? - .expect("model transport snapshot"); + let outer = runtime_job(store, outer_job_id)?.expect("outer worker job"); + assert_eq!(outer.status, EpiphanyRuntimeJobStatus::Failed); + let transport = runtime_job(store, &failure.runtime_job_id)?.expect("model transport job"); assert!(matches!( - transport.job.status, + transport.status, EpiphanyRuntimeJobStatus::Completed | EpiphanyRuntimeJobStatus::Failed )); - assert!( - transport - .result - .expect("model transport result") - .decision_context_id - .is_none(), - "generic model transport cannot own decision authority" - ); let mut cache = epiphany_core::runtime_spine_cache(store)?; cache.pull_all_backing_stores()?; assert_eq!( @@ -1275,6 +879,8 @@ mod tests { "job-with-gate".into(), "--activation-token-sha256".into(), "a".repeat(64), + "--tool-adapter-bin".into(), + "epiphany-tool-mcp-runtime".into(), ])?; assert_eq!(options.activation_token_sha256, "a".repeat(64)); Ok(()) @@ -1293,6 +899,8 @@ mod tests { "job-ox".into(), "--activation-token-sha256".into(), "b".repeat(64), + "--tool-adapter-bin".into(), + "epiphany-tool-mcp-runtime".into(), ])?; require_supported_provider(&options.provider)?; @@ -1305,6 +913,199 @@ mod tests { Ok(()) } + #[test] + fn codex_connector_projection_is_one_strict_deterministic_request() -> Result<()> { + let mut request = EpiphanyModelRequest::new( + "connector-request", + "connector-conversation", + "openai-codex", + "gpt-test", + "Decide.", + ); + request.output_contract_id = Some("role/result.v1".into()); + request.output_schema_json = Some( + r#"{"type":"object","properties":{"verdict":{"const":"pass"}},"required":["verdict"]}"# + .into(), + ); + request + .tools + .push(epiphany_model_adapter::EpiphanyModelToolDefinition { + name: "inspect_body".into(), + description: "Inspect the typed Body.".into(), + parameters_json: + r#"{"type":"object","properties":{"path":{"type":"string"}},"required":["path"]}"# + .into(), + }); + request + .input + .push(epiphany_model_adapter::EpiphanyModelInputItem::ToolCall { + call_id: "native/call".into(), + name: "inspect_body".into(), + arguments: r#"{"path":"src/lib.rs"}"#.into(), + }); + request + .input + .push(epiphany_model_adapter::EpiphanyModelInputItem::ToolResult { + call_id: "native/call".into(), + output: r#"{"ok":true}"#.into(), + }); + + let projected = epiphany_model_adapter::request_from_native(&request)?; + let EpiphanyProviderRequestPayload::Codex(projected) = projected.payload else { + panic!("Codex provider must lower to the connector contract") + }; + assert_eq!( + projected.output_format_name.as_deref(), + Some("role_result_v1") + ); + assert_eq!( + projected.tool_choice, + codex_connector::CodexToolChoice::Auto + ); + let (call, result) = (&projected.input[0], &projected.input[1]); + let codex_connector::CodexInputItem::ToolCall { call_id, .. } = call else { + panic!("first input must remain a tool call") + }; + let codex_connector::CodexInputItem::ToolResult { + call_id: result_id, .. + } = result + else { + panic!("second input must remain a tool result") + }; + assert_eq!(call_id, result_id); + assert_ne!(call_id, "native/call"); + assert_eq!(call_id.len(), 64); + assert_eq!( + serde_json::from_str::( + projected.output_schema_json.as_deref().unwrap() + )?["properties"]["verdict"]["type"], + "string" + ); + Ok(()) + } + + #[test] + fn connector_receipt_digests_survive_without_transport_transcript() -> Result<()> { + let result = codex_connector::CodexTransportResult { + schema_id: codex_connector::RESULT_SCHEMA_ID.into(), + request_id: "connector-request".into(), + caller_runtime_id: "epiphany-test".into(), + disposition: codex_connector::CodexTransportDisposition::Transported { + events: Vec::new(), + receipt: Box::new(codex_connector::CodexTransportReceipt { + schema_id: codex_connector::RECEIPT_SCHEMA_ID.into(), + request_id: "connector-request".into(), + caller_runtime_id: "epiphany-test".into(), + native_request_sha256: [0x11; 32], + provider_request_sha256: [0x22; 32], + model: "gpt-test".into(), + transport: "fake-connector".into(), + outcome: codex_connector::CodexTransportOutcome::Completed { + provider_response_id: Some("response".into()), + input_tokens: Some(10), + output_tokens: Some(4), + reasoning_output_tokens: Some(2), + cached_input_tokens: Some(6), + }, + }), + }, + }; + + let events = provider_transport::events_from_connector_result("openai-codex", result)?; + assert_eq!(events.len(), 1); + let EpiphanyModelStreamPayload::Completed { receipt } = &events[0].payload else { + panic!("connector result must produce one terminal receipt") + }; + let native_digest = "11".repeat(32); + let provider_digest = "22".repeat(32); + assert_eq!(receipt.caller_runtime_id.as_deref(), Some("epiphany-test")); + assert_eq!( + receipt.native_request_sha256.as_deref(), + Some(native_digest.as_str()) + ); + assert_eq!( + receipt.provider_request_sha256.as_deref(), + Some(provider_digest.as_str()) + ); + assert_eq!(receipt.cached_input_tokens, Some(6)); + assert_eq!(receipt.transport.as_deref(), Some("fake-connector")); + Ok(()) + } + + #[test] + fn every_static_worker_contract_projects_to_one_strict_provider_shape() -> Result<()> { + let schemas = vec![ + epiphany_core::epiphany_role_launch_output_schema( + epiphany_core::EpiphanyRoleResultRoleId::Imagination, + ), + epiphany_core::epiphany_role_launch_output_schema( + epiphany_core::EpiphanyRoleResultRoleId::Research, + ), + epiphany_core::epiphany_role_launch_output_schema( + epiphany_core::EpiphanyRoleResultRoleId::Modeling, + ), + epiphany_core::epiphany_role_launch_output_schema( + epiphany_core::EpiphanyRoleResultRoleId::Verification, + ), + epiphany_core::epiphany_frontier_planning_output_schema(), + epiphany_core::epiphany_frontier_plan_mind_output_schema(), + epiphany_core::epiphany_imagination_consideration_output_schema(), + epiphany_core::epiphany_admitted_model_direction_consideration_output_schema(), + epiphany_core::epiphany_proposal_modeling_output_schema(), + epiphany_core::epiphany_reorient_launch_output_schema(), + ]; + + for (index, schema) in schemas.into_iter().enumerate() { + let mut request = EpiphanyOpenRouterRequest::new( + format!("strict-worker-schema-{index}"), + "strict-worker-schema", + "gpt-5.4", + "Return the typed result.", + ); + request.output_contract_id = Some(format!("worker-schema-{index}")); + request.output_schema_json = Some(serde_json::to_string(&schema)?); + let projected = epiphany_model_adapter::strict_provider_schema( + request.output_schema_json.as_deref().unwrap(), + )?; + assert_eq!(projected["additionalProperties"], false); + } + Ok(()) + } + + #[test] + fn research_contract_projects_typed_decision_to_provider_strict_shape() -> Result<()> { + let mut request = EpiphanyOpenRouterRequest::new( + "strict-research-schema", + "strict-worker-schema", + "gpt-5.4", + "Return the typed result.", + ); + request.output_contract_id = + Some(epiphany_core::ROLE_WORKER_OUTPUT_CONTRACT_ID.to_string()); + request.output_schema_json = Some(serde_json::to_string( + &epiphany_core::epiphany_role_launch_output_schema( + epiphany_core::EpiphanyRoleResultRoleId::Research, + ), + )?); + + let projected = epiphany_model_adapter::strict_provider_schema( + request.output_schema_json.as_deref().unwrap(), + )?; + let decision = &projected["properties"]["researchDecision"]; + assert!(decision.get("anyOf").is_none()); + assert_eq!(decision["additionalProperties"], false); + assert_eq!( + decision["required"], + serde_json::json!(["evidence", "investigationCheckpoint", "observations"]) + ); + assert!(decision["properties"].get("scratch").is_none()); + assert_eq!( + decision["properties"]["investigationCheckpoint"]["anyOf"][0]["additionalProperties"], + false + ); + Ok(()) + } + #[test] fn typed_worker_budget_is_the_only_whole_request_deadline() { assert_eq!( @@ -1314,90 +1115,6 @@ mod tests { assert_eq!(provider_request_timeout(Some(600)), None); } - #[test] - fn model_turn_json_ingress_accepts_published_native_object() -> Result<()> { - let request = parse_model_turn_request_json( - r#"{ - "schema_id":"epiphany.model_request.v0", - "request_id":"request-json-native", - "conversation_id":"conversation-json-native", - "provider":"openai-codex", - "model":"gpt-test", - "instructions":"Return awake.", - "input":[{"UserText":{"text":"awake"}}], - "reasoning_effort":"low" - }"#, - )?; - assert_eq!(request.request_id, "request-json-native"); - assert_eq!(request.reasoning_effort.as_deref(), Some("low")); - assert_eq!(request.input.len(), 1); - assert!(request.tools.is_empty()); - Ok(()) - } - - #[test] - fn model_turn_json_ingress_refuses_caller_authored_provider_object() { - let error = parse_model_turn_request_json( - r#"{ - "schema_id":"epiphany.openai_model_request.v1", - "request_id":"request-json-provider", - "conversation_id":"conversation-json-provider", - "model":"gpt-test", - "instructions":"Return awake.", - "input":[{"ToolResult":{"call_id":"call-1","output":"awake"}}], - "output_contract_id":"contract-1" - }"#, - ) - .expect_err("provider requests must be derived internally from native requests"); - assert!( - error - .to_string() - .contains("unsupported model-turn request schema_id") - ); - } - - #[test] - fn model_turn_json_ingress_refuses_persistence_arrays_and_unknown_fields() { - assert!(parse_model_turn_request_json(r#"["epiphany.model_request.v0"]"#).is_err()); - assert!( - parse_model_turn_request_json( - r#"{ - "schema_id":"epiphany.model_request.v0", - "request_id":"request-json-hostile", - "conversation_id":"conversation-json-hostile", - "provider":"openai-codex", - "model":"gpt-test", - "instructions":"Return awake.", - "input":[], - "reasoning_effort":null, - "reasoning_summary":null, - "service_tier":null, - "output_contract_id":null, - "persistence_payload":"forbidden" - }"#, - ) - .is_err() - ); - assert!( - parse_model_turn_request_json( - r#"{ - "schema_id":"epiphany.model_request.v0", - "request_id":"request-json-toolcall", - "conversation_id":"conversation-json-toolcall", - "provider":"openai-codex", - "model":"gpt-test", - "instructions":"Return awake.", - "input":[{"ToolCall":{"call_id":"call-1","name":"x","arguments":"{}"}}], - "reasoning_effort":null, - "reasoning_summary":null, - "service_tier":null, - "output_contract_id":null - }"#, - ) - .is_err() - ); - } - fn seed_pending_tool_summary( store: &Path, worker_job_id: &str, @@ -1422,32 +1139,22 @@ mod tests { model_request.reasoning_basis_id = Some(basis.basis_id); epiphany_core::open_runtime_model_execution( store, - epiphany_core::RuntimeSpineSessionOptions { - session_id: session_id.clone(), - objective: format!("Run model execution for {worker_job_id}."), - created_at: chrono::Utc::now().to_rfc3339(), - coordinator_note: "test".to_string(), - }, epiphany_core::RuntimeSpineJobOptions { job_id: job_id.clone(), session_id: session_id.clone(), - role: OPENAI_RUNTIME_ROLE.to_string(), - created_at: chrono::Utc::now().to_rfc3339(), - summary: "Pending tool fixture.".to_string(), - artifact_refs: Vec::new(), }, &model_request, - &chrono::Utc::now().to_rfc3339(), )?; - let mut receipt = EpiphanyOpenAiModelReceipt::new(request_id, "gpt-test"); - receipt.response_id = Some(format!("response-{request_id}")); + let mut receipt = EpiphanyModelReceipt::new(request_id, DEFAULT_PROVIDER, "gpt-test"); + receipt.provider_response_id = Some(format!("response-{request_id}")); receipt.transport = Some("test".to_string()); - record_native_model_events( + record_model_turn_events( store, &EpiphanyOpenAiRuntimeOptions { store_path: store.to_path_buf(), - codex_home: PathBuf::from(".codex"), provider_credential_path: None, + connector_endpoint: Some(DEFAULT_CODEX_CONNECTOR_ENDPOINT), + caller_runtime_id: "epiphany-model-runtime-test".to_string(), session_id, job_id, objective: format!("Run model execution for {worker_job_id}."), @@ -1457,21 +1164,25 @@ mod tests { }, &model_request, &[ - EpiphanyOpenAiStreamEvent { - schema_id: epiphany_openai_adapter::OPENAI_ADAPTER_EVENT_SCHEMA_ID.to_string(), + EpiphanyModelStreamEvent { + schema_id: MODEL_ADAPTER_EVENT_SCHEMA_ID.to_string(), request_id: request_id.to_string(), + provider: DEFAULT_PROVIDER.to_string(), sequence: 0, - payload: EpiphanyOpenAiStreamPayload::ToolCall { + payload: EpiphanyModelStreamPayload::ToolCall { call_id: format!("call-{request_id}"), name: tool_name.to_string(), arguments: arguments.to_string(), }, }, - EpiphanyOpenAiStreamEvent { - schema_id: epiphany_openai_adapter::OPENAI_ADAPTER_EVENT_SCHEMA_ID.to_string(), + EpiphanyModelStreamEvent { + schema_id: MODEL_ADAPTER_EVENT_SCHEMA_ID.to_string(), request_id: request_id.to_string(), + provider: DEFAULT_PROVIDER.to_string(), sequence: 1, - payload: EpiphanyOpenAiStreamPayload::Completed { receipt }, + payload: EpiphanyModelStreamPayload::Completed { + receipt: Box::new(receipt), + }, }, ], ) @@ -1532,9 +1243,6 @@ mod tests { &store, RuntimeSpineHeartbeatJobOptions { runtime_id: "epiphany-test".to_string(), - session_id: "epiphany-main".to_string(), - objective: "Run typed worker.".to_string(), - coordinator_note: "test".to_string(), job_id: "worker-job-loop".to_string(), role: "verification".to_string(), binding_id: "verification-review-worker".to_string(), @@ -1558,14 +1266,6 @@ mod tests { }, ), output_contract_id: epiphany_core::ROLE_WORKER_OUTPUT_CONTRACT_ID.to_string(), - proposal_modeling_request_id: None, - frontier_planning_request_id: None, - frontier_plan_mind_request_id: None, - imagination_consideration_request_id: None, - admitted_model_direction_consideration_request_id: None, - repo_frontier_modeling_request_id: None, - repo_frontier_research_request_id: None, - repo_frontier_verification_request_id: None, created_at: now(), }, )?; @@ -1591,9 +1291,8 @@ mod tests { )?; assert_eq!(status["status"], "tool-loop-stalled"); - let snapshot = runtime_job_snapshot(&store, "worker-job-loop")?.expect("worker snapshot"); - assert_eq!(snapshot.job.status, EpiphanyRuntimeJobStatus::Failed); - assert_eq!(snapshot.result.expect("worker result").verdict, "failed"); + let job = runtime_job(&store, "worker-job-loop")?.expect("worker job"); + assert_eq!(job.status, EpiphanyRuntimeJobStatus::Failed); let mut cache = epiphany_core::runtime_spine_cache(&store)?; cache.pull_all_backing_stores()?; assert_eq!( @@ -1631,9 +1330,6 @@ mod tests { &store, RuntimeSpineHeartbeatJobOptions { runtime_id: "epiphany-test".to_string(), - session_id: "epiphany-main".to_string(), - objective: "Run typed worker.".to_string(), - coordinator_note: "test".to_string(), job_id: "worker-job-runtime-error".to_string(), role: "verification".to_string(), binding_id: "verification-review-worker".to_string(), @@ -1657,14 +1353,6 @@ mod tests { }, ), output_contract_id: epiphany_core::ROLE_WORKER_OUTPUT_CONTRACT_ID.to_string(), - proposal_modeling_request_id: None, - frontier_planning_request_id: None, - frontier_plan_mind_request_id: None, - imagination_consideration_request_id: None, - admitted_model_direction_consideration_request_id: None, - repo_frontier_modeling_request_id: None, - repo_frontier_research_request_id: None, - repo_frontier_verification_request_id: None, created_at: now(), }, )?; @@ -1685,22 +1373,11 @@ mod tests { request.reasoning_basis_id = Some(basis.basis_id); epiphany_core::open_runtime_model_execution( &store, - epiphany_core::RuntimeSpineSessionOptions { - session_id: "openai-runtime-error".into(), - objective: "Exercise model-backed runtime failure.".into(), - created_at: now(), - coordinator_note: "test".into(), - }, epiphany_core::RuntimeSpineJobOptions { job_id: "openai-job-runtime-error".into(), session_id: "openai-runtime-error".into(), - role: OPENAI_RUNTIME_ROLE.into(), - created_at: now(), - summary: "provider admitted request".into(), - artifact_refs: Vec::new(), }, &request, - &now(), )?; let pass_progress = WorkerPassProgress::default(); pass_progress.note_model_request(&request.request_id); @@ -1718,14 +1395,12 @@ mod tests { .as_str() .is_some_and(|summary| summary.contains("tool adapter exploded")) ); - let snapshot = - runtime_job_snapshot(&store, "worker-job-runtime-error")?.expect("worker snapshot"); - assert_eq!(snapshot.job.status, EpiphanyRuntimeJobStatus::Failed); - let result = snapshot.result.expect("worker result"); - assert_eq!(result.verdict, "failed"); - let context_id = result - .decision_context_id - .expect("model-backed runtime failure must retain its context"); + let job = runtime_job(&store, "worker-job-runtime-error")?.expect("worker job"); + assert_eq!(job.status, EpiphanyRuntimeJobStatus::Failed); + let failure = + epiphany_core::model_pass_failure_for_request(&store, "request-runtime-error")? + .expect("model-backed runtime failure must retain its typed owner"); + let context_id = failure.decision_context_id; let mut cache = epiphany_core::runtime_spine_cache(&store)?; cache.pull_all_backing_stores()?; assert_eq!( @@ -1752,9 +1427,6 @@ mod tests { &store, RuntimeSpineHeartbeatJobOptions { runtime_id: "epiphany-test".to_string(), - session_id: "epiphany-main".to_string(), - objective: "Run typed worker.".to_string(), - coordinator_note: "test".to_string(), job_id: "worker-job-round-limit".to_string(), role: "verification".to_string(), binding_id: "verification-review-worker".to_string(), @@ -1778,14 +1450,6 @@ mod tests { }, ), output_contract_id: epiphany_core::ROLE_WORKER_OUTPUT_CONTRACT_ID.to_string(), - proposal_modeling_request_id: None, - frontier_planning_request_id: None, - frontier_plan_mind_request_id: None, - imagination_consideration_request_id: None, - admitted_model_direction_consideration_request_id: None, - repo_frontier_modeling_request_id: None, - repo_frontier_research_request_id: None, - repo_frontier_verification_request_id: None, created_at: now(), }, )?; @@ -1817,10 +1481,8 @@ mod tests { assert_eq!(status["status"], "tool-round-limit"); assert_eq!(status["pendingToolIntentIds"][0], pending_intent_id); assert_ne!(status["status"], "tool-loop-stalled"); - let snapshot = - runtime_job_snapshot(&store, "worker-job-round-limit")?.expect("worker snapshot"); - assert_eq!(snapshot.job.status, EpiphanyRuntimeJobStatus::Failed); - assert_eq!(snapshot.result.expect("worker result").verdict, "failed"); + let job = runtime_job(&store, "worker-job-round-limit")?.expect("worker job"); + assert_eq!(job.status, EpiphanyRuntimeJobStatus::Failed); let mut cache = epiphany_core::runtime_spine_cache(&store)?; cache.pull_all_backing_stores()?; assert_eq!( @@ -1875,26 +1537,7 @@ async fn run_worker_options( options: RunWorkerCliOptions, pass_progress: WorkerPassProgress, ) -> Result { - if options.auto_tools { - run_worker_launch_with_tool_continuation(options, pass_progress).await - } else { - let request_timeout = provider_request_timeout(options.max_runtime_seconds); - Ok(serde_json::to_value( - run_worker_launch_observed( - EpiphanyWorkerRuntimeOptions { - store_path: options.store_path, - codex_home: options.codex_home, - provider_credential_path: options.provider_credential_path, - provider: options.provider, - job_id: options.job_id, - model: options.model, - request_timeout, - }, - |request_id| pass_progress.note_model_request(request_id), - ) - .await?, - )?) - } + run_worker_launch(options, pass_progress).await } fn provider_request_timeout(max_runtime_seconds: Option) -> Option { @@ -1903,6 +1546,20 @@ fn provider_request_timeout(max_runtime_seconds: Option) -> Option Result { + let request = build_tool_followup_model_request( + &options.store_path, + original_request_id, + followup_request_id, + )?; + run_model_turn(provider, options, request).await +} + fn run_tool_adapter( tool_adapter_bin: &PathBuf, store_path: &PathBuf, @@ -1940,63 +1597,6 @@ fn run_tool_adapter( serde_json::from_slice(&output.stdout).context("tool adapter returned invalid JSON") } -fn parse_tool_followup_turn_options(args: Vec) -> Result { - let mut provider = DEFAULT_PROVIDER.to_string(); - let mut store_path = PathBuf::from(DEFAULT_STORE); - let mut codex_home = default_codex_home()?; - let mut provider_credential_path = None; - let mut request_id = None; - let mut followup_request_id = None; - let mut session_id = None; - let mut job_id = None; - let mut objective = None; - let mut default_model = None; - let mut output_last_message_path = None; - let mut iter = args.into_iter(); - while let Some(arg) = iter.next() { - match arg.as_str() { - "--provider" => provider = next_value(&mut iter, "--provider")?, - "--store" => store_path = PathBuf::from(next_value(&mut iter, "--store")?), - "--codex-home" => codex_home = PathBuf::from(next_value(&mut iter, "--codex-home")?), - "--provider-credential" => { - provider_credential_path = Some(PathBuf::from(next_value( - &mut iter, - "--provider-credential", - )?)) - } - "--request-id" => request_id = Some(next_value(&mut iter, "--request-id")?), - "--followup-request-id" => { - followup_request_id = Some(next_value(&mut iter, "--followup-request-id")?) - } - "--session-id" => session_id = Some(next_value(&mut iter, "--session-id")?), - "--job-id" => job_id = Some(next_value(&mut iter, "--job-id")?), - "--objective" => objective = Some(next_value(&mut iter, "--objective")?), - "--default-model" => default_model = Some(next_value(&mut iter, "--default-model")?), - "--output-last-message" => { - output_last_message_path = Some(PathBuf::from(next_value( - &mut iter, - "--output-last-message", - )?)) - } - other => return Err(anyhow!("unknown tool-followup-turn argument: {other}")), - } - } - Ok(ToolFollowupTurnCliOptions { - provider, - store_path, - codex_home, - provider_credential_path, - request_id: request_id.context("tool-followup-turn requires --request-id")?, - followup_request_id: followup_request_id - .unwrap_or_else(|| format!("tool-followup-{}", Uuid::new_v4())), - session_id, - job_id, - objective, - default_model, - output_last_message_path, - }) -} - fn require_supported_provider(provider: &str) -> Result<()> { if matches!(provider, "openai-codex" | "openai" | "openrouter") { return Ok(()); @@ -2021,5 +1621,5 @@ fn now() -> String { } fn usage() -> &'static str { - "usage: epiphany-model-runtime [--provider openai-codex|openrouter] [--provider-credential path] [--store path] [--codex-home path] [--request path] [--request-id id] [--context-id id] [--followup-request-id id] [--output path] [--session-id id] [--job-id id] [--activation-token-sha256 hex] [--objective text] [--default-model model] [--output-last-message path] [--auto-tools --tool-adapter-bin path --mcp-config path --cwd path --max-tool-rounds n]" + "usage: epiphany-model-runtime [--provider openai-codex|openrouter] [--provider-credential path] [--connector-endpoint 127.0.0.1:17891] [--caller-runtime-id id] [--store path] [--context-id id] [--output path] [--job-id id] [--activation-token-sha256 hex] [--default-model model] --tool-adapter-bin path [--mcp-config path] [--cwd path] [--max-tool-rounds n]" } diff --git a/epiphany-openai-runtime/src/bin/epiphany-persona-service.rs b/epiphany-openai-runtime/src/bin/epiphany-persona-service.rs index 5250ee2a..ef1ec32c 100644 --- a/epiphany-openai-runtime/src/bin/epiphany-persona-service.rs +++ b/epiphany-openai-runtime/src/bin/epiphany-persona-service.rs @@ -1,4 +1,4 @@ -use std::{env, net::SocketAddr, path::PathBuf, thread, time::Duration}; +use std::{env, future::Future, net::SocketAddr, path::PathBuf, pin::Pin, thread, time::Duration}; use anyhow::{Context, Result, anyhow}; use cultcache_rs::DatabaseEntry; @@ -17,10 +17,87 @@ use epiphany_core::{ pulse_persona_social, reconcile_terminal_persona_conversation, retain_terminal_persona_conversations, validate_persona_discord_request_anchor, }; +use epiphany_model_adapter::EpiphanyModelRequest; use epiphany_openai_runtime::{ - NativePersonaModelRunner, PersonaModelExecutionPlan, execute_persona_model_turn, + DEFAULT_CODEX_CONNECTOR_ENDPOINT, EpiphanyOpenAiRuntimeOptions, PersonaModelExecutionPlan, + PersonaModelRunner, assistant_text_from_model_events, execute_persona_model_turn, }; +#[path = "../provider_transport.rs"] +mod provider_transport; + +#[derive(Clone, Debug)] +struct NativePersonaModelRunner { + store_path: PathBuf, + provider_credential_path: Option, + connector_endpoint: SocketAddr, + caller_runtime_id: String, + provider: String, + model: String, + turn_timeout: Duration, +} + +impl PersonaModelRunner for NativePersonaModelRunner { + fn run<'a>( + &'a mut self, + _store_path: &'a PathBuf, + stage: &'a str, + turn_id: &'a str, + request: EpiphanyModelRequest, + ) -> Pin> + Send + 'a>> { + Box::pin(async move { + if request.provider != self.provider || request.model != self.model { + return Err(anyhow!("Persona execution plan and model runner disagree")); + } + let replay = assistant_text_from_model_events(&self.store_path, &request.request_id)?; + if !replay.trim().is_empty() { + return Ok(replay); + } + let options = EpiphanyOpenAiRuntimeOptions { + store_path: self.store_path.clone(), + provider_credential_path: self.provider_credential_path.clone(), + connector_endpoint: Some(self.connector_endpoint), + caller_runtime_id: self.caller_runtime_id.clone(), + session_id: format!("persona-turn-{turn_id}"), + job_id: format!("persona-{stage}-{turn_id}"), + objective: format!("Run Persona {stage} stage for {turn_id}"), + coordinator_note: "Native Persona model executor; transport owns inference only." + .to_string(), + default_model: Some(self.model.clone()), + request_timeout: None, + }; + let summary = tokio::time::timeout( + self.turn_timeout, + provider_transport::run_model_turn( + &self.provider, + options.clone(), + request.clone(), + ), + ) + .await + .map_err(|_| { + anyhow!( + "Persona {stage} stage exceeded its {} second outer turn budget", + self.turn_timeout.as_secs() + ) + })??; + let output = assistant_text_from_model_events(&self.store_path, &request.request_id)?; + if summary.verdict != "pass" || output.trim().is_empty() { + return Err(anyhow!(if summary.verdict != "pass" { + summary.summary + } else { + format!("Persona {stage} stage completed without assistant text") + })); + } + Ok(output) + }) + } + + fn recover(&mut self, request_id: &str) -> Result { + assistant_text_from_model_events(&self.store_path, request_id) + } +} + #[tokio::main] async fn main() -> Result<()> { let options = Options::parse()?; @@ -98,28 +175,30 @@ async fn poll_once(options: &Options) -> Result { )?; let mut runner = NativePersonaModelRunner { store_path: options.runtime_store.clone(), - codex_home: options.codex_home.clone(), provider_credential_path: options.provider_credential_path.clone(), + connector_endpoint: options.connector_endpoint, + caller_runtime_id: options.runtime_id.clone(), provider: options.provider.clone(), model: options.model.clone(), turn_timeout: Duration::from_secs(options.turn_timeout_seconds), }; - let model_terminal = match execute_persona_model_turn(&plan, &mut runner).await { - Ok(receipt) => receipt, - Err(error) if error.to_string().contains("braked") => return Ok(false), - Err(error) => { - complete_persona_social_turn( - &options.social_store, - PersonaTurnTerminalOptions { - request_id: request.request_id, - outcome: "failed".into(), - delivery_evidence: None, - blocked_evidence: None, - }, - )?; - return Err(error); - } - }; + let model_terminal = + match execute_persona_model_turn(&options.runtime_store, &plan, &mut runner).await { + Ok(receipt) => receipt, + Err(error) if error.to_string().contains("braked") => return Ok(false), + Err(error) => { + complete_persona_social_turn( + &options.social_store, + PersonaTurnTerminalOptions { + request_id: request.request_id, + outcome: "failed".into(), + delivery_receipt: None, + blocked_evidence: None, + }, + )?; + return Err(error); + } + }; let signer = open_persona_discord_request_identity(&options.mouth_identity_store)?; let request_anchor = load_persona_discord_service_anchor(&options.mouth_request_anchor)?; validate_persona_discord_request_anchor(&request_anchor, &options.runtime_id)?; @@ -289,8 +368,8 @@ struct Options { runtime_store: PathBuf, social_store: PathBuf, cultmesh_store: PathBuf, - codex_home: PathBuf, provider_credential_path: Option, + connector_endpoint: SocketAddr, runtime_id: String, provider: String, model: String, @@ -336,8 +415,14 @@ impl Options { runtime_store: path("--runtime-store")?, social_store: path("--persona-social-store")?, cultmesh_store: path("--cultmesh-store")?, - codex_home: path("--codex-home")?, provider_credential_path: values.get("--provider-credential").map(PathBuf::from), + connector_endpoint: value( + &values, + "--connector-endpoint", + &DEFAULT_CODEX_CONNECTOR_ENDPOINT.to_string(), + ) + .parse() + .context("--connector-endpoint must be an IP:port socket address")?, _repo_root: path("--repo-root")?, mouth_request_store: path("--mouth-request-store")?, mouth_receipt_store: path("--mouth-receipt-store")?, @@ -435,8 +520,8 @@ mod tests { runtime_store: runtime_store.clone(), social_store: absent.join("persona-social.cc"), cultmesh_store: absent.join("cultmesh.cc"), - codex_home: absent.join("codex-home"), provider_credential_path: None, + connector_endpoint: DEFAULT_CODEX_CONNECTOR_ENDPOINT, runtime_id: "persona-reentry".into(), provider: "test".into(), model: "test-model".into(), diff --git a/epiphany-openai-runtime/src/lib.rs b/epiphany-openai-runtime/src/lib.rs index cc93faaa..6538cf2a 100644 --- a/epiphany-openai-runtime/src/lib.rs +++ b/epiphany-openai-runtime/src/lib.rs @@ -1,4 +1,5 @@ use std::collections::{BTreeMap, BTreeSet}; +use std::net::SocketAddr; use std::path::Path; use std::path::PathBuf; use std::time::Duration; @@ -7,17 +8,13 @@ use anyhow::Context; use anyhow::Result; use anyhow::anyhow; use chrono::SecondsFormat; +use epiphany_core::EpiphanyRuntimeJobStatus; use epiphany_core::EpiphanyRuntimeReorientWorkerResult; use epiphany_core::EpiphanyRuntimeRoleWorkerResult; use epiphany_core::EpiphanyRuntimeWorkerLaunchRequest; use epiphany_core::EpiphanyWorkerLaunchDocument; use epiphany_core::RuntimeSpineInitOptions; use epiphany_core::RuntimeSpineJobOptions; -use epiphany_core::RuntimeSpineJobResultOptions; -use epiphany_core::RuntimeSpineSessionClosureOptions; -use epiphany_core::RuntimeSpineSessionOptions; -use epiphany_core::close_runtime_session; -use epiphany_core::complete_runtime_job; use epiphany_core::initialize_runtime_spine; use epiphany_core::open_runtime_model_execution; use epiphany_core::put_runtime_reorient_worker_result; @@ -25,23 +22,13 @@ use epiphany_core::put_runtime_role_worker_result; use epiphany_core::put_runtime_tool_execution_intent; use epiphany_core::runtime_identity; use epiphany_core::runtime_spine_cache; +use epiphany_core::terminalize_runtime_job; use epiphany_model_adapter::EpiphanyModelInputItem; -use epiphany_model_adapter::EpiphanyModelReceipt; use epiphany_model_adapter::EpiphanyModelRequest; use epiphany_model_adapter::EpiphanyModelStreamEvent; use epiphany_model_adapter::EpiphanyModelStreamPayload; use epiphany_model_adapter::EpiphanyModelToolDefinition; -use epiphany_openai_adapter::EpiphanyOpenAiAdapterStatus; -use epiphany_openai_adapter::EpiphanyOpenAiModelRequest; -use epiphany_openai_adapter::EpiphanyOpenAiStreamEvent; -use epiphany_openai_adapter::EpiphanyOpenAiStreamPayload; -use epiphany_openai_codex_spine::EpiphanyCodexOpenAiTransport; -use epiphany_openai_codex_spine::EpiphanyOpenRouterTransport; -use epiphany_openai_codex_spine::OPENROUTER_SPINE_ADAPTER_ID; -use epiphany_openai_codex_spine::auth_manager; -pub use epiphany_openai_codex_spine::default_codex_home; -use epiphany_openai_codex_spine::status_from_auth_manager; -use epiphany_openai_codex_spine::status_from_static_api_key; +use epiphany_model_adapter::EpiphanyProviderRequest; use epiphany_tool_adapter::EPIPHANY_TOOL_RUNTIME_ADAPTER_ID; use epiphany_tool_adapter::EpiphanyToolInvocationIntent; use epiphany_tool_adapter::EpiphanyToolInvocationReceipt; @@ -56,33 +43,20 @@ use sha2::Digest; mod persona_executor; pub use persona_executor::*; -mod model_connector; -pub use model_connector::*; pub const OPENAI_RUNTIME_ROLE: &str = "openai-model-adapter"; pub const DEFAULT_PROVIDER_REQUEST_TIMEOUT: Duration = Duration::from_secs(90); pub const DEFAULT_MODEL_PROVIDER: &str = "openai-codex"; pub const OPENROUTER_MODEL_PROVIDER: &str = "openrouter"; +pub const DEFAULT_CODEX_CONNECTOR_ENDPOINT: SocketAddr = + SocketAddr::new(std::net::IpAddr::V4(std::net::Ipv4Addr::LOCALHOST), 17891); -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum EpiphanyProviderProfile { - OpenAiCodex, - OpenRouter, -} - -impl EpiphanyProviderProfile { - fn parse(provider: &str) -> Result { - match provider { - "openai-codex" | "openai" => Ok(Self::OpenAiCodex), - OPENROUTER_MODEL_PROVIDER => Ok(Self::OpenRouter), - _ => Err(anyhow!( - "unsupported model runtime provider {provider:?}; current providers: openai-codex, openrouter" - )), - } - } - - fn streaming_supported(self) -> bool { - matches!(self, Self::OpenAiCodex) +fn validate_model_provider(provider: &str) -> Result<()> { + match provider { + "openai-codex" | "openai" | OPENROUTER_MODEL_PROVIDER => Ok(()), + _ => Err(anyhow!( + "unsupported model runtime provider {provider:?}; current providers: openai-codex, openrouter" + )), } } @@ -93,8 +67,9 @@ pub fn worker_model_session_id(worker_job_id: &str) -> String { #[derive(Debug, Clone, PartialEq, Eq)] pub struct EpiphanyOpenAiRuntimeOptions { pub store_path: PathBuf, - pub codex_home: PathBuf, pub provider_credential_path: Option, + pub connector_endpoint: Option, + pub caller_runtime_id: String, pub session_id: String, pub job_id: String, pub objective: String, @@ -108,286 +83,52 @@ pub struct EpiphanyOpenAiRuntimeOptions { #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)] #[serde(rename_all = "camelCase")] pub struct EpiphanyOpenAiRuntimeRunSummary { - pub store: String, - pub session_id: String, - pub job_id: String, - pub request_id: String, - pub event_count: usize, pub verdict: String, pub summary: String, - pub result_id: String, - pub receipt_id: Option, pub tool_intent_ids: Vec, } -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct EpiphanyWorkerRuntimeOptions { - pub store_path: PathBuf, - pub codex_home: PathBuf, - pub provider_credential_path: Option, - pub provider: String, - pub job_id: String, - pub model: String, - pub request_timeout: Option, -} - -#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)] -#[serde(rename_all = "camelCase")] -pub struct EpiphanyWorkerRuntimeRunSummary { - pub store: String, - pub job_id: String, - pub binding_id: String, - pub role: String, - pub request_id: String, - pub openai_result_id: String, - pub worker_result_id: String, - pub verdict: String, - pub summary: String, - pub next_safe_move: String, - pub evidence_refs: Vec, - pub artifact_refs: Vec, -} - -async fn run_openai_model_turn_bound( - options: EpiphanyOpenAiRuntimeOptions, - model_request: EpiphanyModelRequest, - request: EpiphanyOpenAiModelRequest, -) -> Result { - ensure_openai_runtime_ready(&options)?; - let provider = model_request.provider.clone(); - let profile = EpiphanyProviderProfile::parse(&provider)?; - open_runtime_model_execution( - &options.store_path, - RuntimeSpineSessionOptions { - session_id: options.session_id.clone(), - objective: options.objective.clone(), - created_at: now(), - coordinator_note: options.coordinator_note.clone(), - }, - RuntimeSpineJobOptions { - job_id: options.job_id.clone(), - session_id: options.session_id.clone(), - role: OPENAI_RUNTIME_ROLE.to_string(), - created_at: now(), - summary: format!("OpenAI model request {}", request.request_id), - artifact_refs: Vec::new(), - }, - &model_request, - &now(), - )?; - let (status, events) = match profile { - EpiphanyProviderProfile::OpenAiCodex => { - let auth_manager = auth_manager(options.codex_home.clone()); - let status = - status_from_auth_manager(&auth_manager, options.default_model.clone(), true).await; - let transport = EpiphanyCodexOpenAiTransport::openai(auth_manager); - let events = - collect_transport_events(transport.collect_model_events(request.clone()), &request) - .await; - (status, events) - } - EpiphanyProviderProfile::OpenRouter => { - let credential = read_static_provider_credential( - options.provider_credential_path.as_deref(), - OPENROUTER_MODEL_PROVIDER, - )?; - let status = status_from_static_api_key( - OPENROUTER_SPINE_ADAPTER_ID, - options.default_model.clone(), - ); - let transport = EpiphanyOpenRouterTransport::new(credential, options.request_timeout)?; - let events = - collect_transport_events(transport.collect_model_events(request.clone()), &request) - .await; - (status, events) - } - }; - store_openai_status(&options.store_path, &status)?; - store_model_status( - &options.store_path, - &status, - &provider, - profile.streaming_supported(), - true, - )?; - record_openai_events(&options.store_path, &options, &request, &provider, &events) -} - -async fn collect_transport_events( - future: F, - request: &EpiphanyOpenAiModelRequest, -) -> Vec -where - F: std::future::Future>>, -{ - match future.await { - Ok(events) => events, - Err(err) => vec![EpiphanyOpenAiStreamEvent { - schema_id: epiphany_openai_adapter::OPENAI_ADAPTER_EVENT_SCHEMA_ID.to_string(), - request_id: request.request_id.clone(), - sequence: 0, - payload: EpiphanyOpenAiStreamPayload::Failed { - message: err.to_string(), - }, - }], - } -} - -fn read_static_provider_credential(path: Option<&Path>, provider: &str) -> Result { - let path = path.ok_or_else(|| { - anyhow!("model provider {provider:?} requires an explicit credential file") - })?; - let raw = std::fs::read_to_string(path).with_context(|| { - format!( - "failed to read {provider} credential file {}", - path.display() - ) - })?; - let credential = raw.trim(); - if credential.is_empty() || raw.trim_matches(['\r', '\n']) != credential { - return Err(anyhow!( - "model provider {provider:?} credential file is empty or contains surrounding whitespace" - )); - } - Ok(credential.to_string()) -} - -pub async fn run_model_turn( +pub fn open_model_turn( provider: &str, - options: EpiphanyOpenAiRuntimeOptions, - request: EpiphanyModelRequest, -) -> Result { - EpiphanyProviderProfile::parse(provider)?; - if !provider_matches_request(provider, &request.provider) { + options: &EpiphanyOpenAiRuntimeOptions, + model_request: &EpiphanyModelRequest, +) -> Result { + validate_model_provider(provider)?; + if !provider_matches_request(provider, &model_request.provider) { return Err(anyhow!( "model request provider {:?} does not match selected provider {:?}", - request.provider, + model_request.provider, provider )); } - let provider_request = openai_request_from_model_request(&request); - run_openai_model_turn_bound(options, request, provider_request).await -} - -pub async fn run_tool_followup_model_turn( - provider: &str, - options: EpiphanyOpenAiRuntimeOptions, - original_request_id: &str, - followup_request_id: &str, -) -> Result { - EpiphanyProviderProfile::parse(provider)?; - let request = build_tool_followup_model_request( + ensure_openai_runtime_ready(&options)?; + open_runtime_model_execution( &options.store_path, - original_request_id, - followup_request_id, - )?; - run_model_turn(provider, options, request).await -} - -pub async fn run_worker_launch( - options: EpiphanyWorkerRuntimeOptions, -) -> Result { - run_worker_launch_observed(options, |_| {}).await -} - -pub async fn run_worker_launch_observed( - options: EpiphanyWorkerRuntimeOptions, - on_model_request: F, -) -> Result -where - F: FnOnce(&str), -{ - let launch_request = load_worker_launch_request(&options.store_path, &options.job_id)?; - if !epiphany_core::runtime_requested_public_source_refs_for_worker( - &options.store_path, - &options.job_id, - )? - .is_empty() - { - return Err(anyhow!( - "worker {:?} has request-owned public sources and must run through the governed automatic tool route", - options.job_id - )); - } - let basis = epiphany_core::worker_reasoning_basis(&options.store_path, &launch_request)?; - epiphany_core::put_reasoning_basis(&options.store_path, &basis)?; - let model_request = - build_worker_model_request(&launch_request, &options.provider, &options.model, &basis)?; - let openai_options = EpiphanyOpenAiRuntimeOptions { - store_path: options.store_path.clone(), - codex_home: options.codex_home, - provider_credential_path: options.provider_credential_path, - session_id: worker_model_session_id(&launch_request.job_id), - job_id: format!("openai-worker-{}", launch_request.job_id), - objective: format!( - "Run Epiphany worker {} for {}", - launch_request.job_id, launch_request.binding_id - ), - coordinator_note: "Native worker runtime route; Codex is auth/model transport only." - .to_string(), - default_model: Some(options.model), - request_timeout: options.request_timeout, - }; - on_model_request(&model_request.request_id); - let openai_summary = run_model_turn( - &options.provider, - openai_options.clone(), - model_request.clone(), - ) - .await?; - let assistant_text = - assistant_text_from_model_events(&openai_options.store_path, &model_request.request_id)?; - let worker_result = complete_worker_job_from_assistant_text( - &openai_options.store_path, - &launch_request, - &model_request.request_id, - &openai_summary, - &assistant_text, - )?; - close_runtime_session( - &openai_options.store_path, - RuntimeSpineSessionClosureOptions { - session_id: openai_options.session_id.clone(), - completed_at: now(), - summary: format!( - "Worker model execution {} reached terminal result {}.", - launch_request.job_id, worker_result.result_id - ), + RuntimeSpineJobOptions { + job_id: options.job_id.clone(), + session_id: options.session_id.clone(), }, + model_request, )?; - Ok(EpiphanyWorkerRuntimeRunSummary { - store: openai_options.store_path.display().to_string(), - job_id: launch_request.job_id, - binding_id: launch_request.binding_id, - role: launch_request.role, - request_id: model_request.request_id, - openai_result_id: openai_summary.result_id, - worker_result_id: worker_result.result_id, - verdict: worker_result.verdict, - summary: worker_result.summary, - next_safe_move: worker_result.next_safe_move, - evidence_refs: worker_result.evidence_refs, - artifact_refs: worker_result.artifact_refs, - }) + provider_request_from_model_request(model_request) } -fn record_openai_events( +pub fn record_model_turn_events( store_path: impl AsRef, options: &EpiphanyOpenAiRuntimeOptions, - request: &EpiphanyOpenAiModelRequest, - provider: &str, - events: &[EpiphanyOpenAiStreamEvent], + request: &EpiphanyModelRequest, + events: &[EpiphanyModelStreamEvent], ) -> Result { let store_path = store_path.as_ref(); - let events = compact_openai_events_for_storage(events); - let tool_intents = tool_invocation_intents_from_openai_events(provider, &events); + let provider_request = provider_request_from_model_request(request)?; + let events = compact_model_events_for_storage(events); + let tool_intents = tool_invocation_intents_from_model_events(&events); for intent in &tool_intents { put_runtime_tool_execution_intent( store_path, &options.session_id, &options.job_id, intent, - &now(), )?; } let mut receipt = None; @@ -396,22 +137,15 @@ fn record_openai_events( let mut cache = runtime_spine_cache(store_path)?; cache.pull_all_backing_stores()?; for event in &events { - let model_event = model_event_from_openai_event(provider, event); - cache.put( - model_event_key(&model_event.request_id, model_event.sequence), - &model_event, - )?; - if let EpiphanyModelStreamPayload::Completed { receipt } = &model_event.payload { - cache.put(model_receipt_key(&receipt.request_id), receipt)?; + cache.put(model_event_key(&event.request_id, event.sequence), event)?; + if let EpiphanyModelStreamPayload::Completed { receipt } = &event.payload { + cache.put(model_receipt_key(&receipt.request_id), receipt.as_ref())?; } - let key = openai_event_key(&event.request_id, event.sequence); - cache.put(key, event)?; match &event.payload { - EpiphanyOpenAiStreamPayload::Completed { receipt: completed } => { - cache.put(openai_receipt_key(&completed.request_id), completed)?; - receipt = Some(completed.clone()); + EpiphanyModelStreamPayload::Completed { receipt: completed } => { + receipt = Some(completed.as_ref().clone()); } - EpiphanyOpenAiStreamPayload::Failed { message } => { + EpiphanyModelStreamPayload::Failed { message } => { failure = Some(message.clone()); } _ => {} @@ -427,12 +161,12 @@ fn record_openai_events( let summary = if let Some(message) = failure { format!( "OpenAI model request {} failed: {message}", - request.request_id + provider_request.request_id() ) } else if let Some(receipt) = &receipt { format!( "OpenAI model request {} completed through {}.", - request.request_id, + provider_request.request_id(), receipt .transport .clone() @@ -441,36 +175,24 @@ fn record_openai_events( } else { format!( "OpenAI model request {} ended without a terminal receipt.", - request.request_id + provider_request.request_id() ) }; - let result_id = format!("result-openai-{}", options.job_id); - complete_runtime_job( + terminalize_runtime_job( store_path, - RuntimeSpineJobResultOptions { - result_id: result_id.clone(), - job_id: options.job_id.clone(), - completed_at: now(), - verdict: verdict.to_string(), - summary: summary.clone(), - next_safe_move: "Review typed OpenAI receipt before accepting downstream state." - .to_string(), - evidence_refs: Vec::new(), - artifact_refs: Vec::new(), - decision_context_id: None, + &options.job_id, + if verdict == "pass" { + EpiphanyRuntimeJobStatus::Completed + } else { + EpiphanyRuntimeJobStatus::Failed }, + &now(), + provider_request.request_id(), )?; Ok(EpiphanyOpenAiRuntimeRunSummary { - store: store_path.display().to_string(), - session_id: options.session_id.clone(), - job_id: options.job_id.clone(), - request_id: request.request_id.clone(), - event_count: events.len(), verdict: verdict.to_string(), summary, - result_id, - receipt_id: receipt.map(|item| openai_receipt_key(&item.request_id)), tool_intent_ids: tool_intents .into_iter() .map(|intent| intent.intent_id) @@ -478,38 +200,19 @@ fn record_openai_events( }) } -/// Diagnostic event admission is native-request owned. The provider request -/// used for summaries and event binding is always lowered internally, never -/// supplied as an independent caller-authored document. -pub fn record_native_model_events( - store_path: impl AsRef, - options: &EpiphanyOpenAiRuntimeOptions, - request: &EpiphanyModelRequest, - events: &[EpiphanyOpenAiStreamEvent], -) -> Result { - let provider_request = epiphany_openai_adapter::request_from_native(request); - record_openai_events( - store_path, - options, - &provider_request, - &request.provider, - events, - ) -} - -fn compact_openai_events_for_storage( - events: &[EpiphanyOpenAiStreamEvent], -) -> Vec { +fn compact_model_events_for_storage( + events: &[EpiphanyModelStreamEvent], +) -> Vec { let mut compacted = Vec::new(); let mut text_buffer = String::new(); let mut reasoning_buffer = String::new(); for event in events { match &event.payload { - EpiphanyOpenAiStreamPayload::TextDelta { text } => { + EpiphanyModelStreamPayload::TextDelta { text } => { flush_reasoning_buffer(&mut compacted, event, &mut reasoning_buffer); text_buffer.push_str(text); } - EpiphanyOpenAiStreamPayload::ReasoningDelta { text } => { + EpiphanyModelStreamPayload::ReasoningDelta { text } => { flush_text_buffer(&mut compacted, event, &mut text_buffer); reasoning_buffer.push_str(text); } @@ -528,8 +231,8 @@ fn compact_openai_events_for_storage( } fn flush_text_buffer( - compacted: &mut Vec, - source: &EpiphanyOpenAiStreamEvent, + compacted: &mut Vec, + source: &EpiphanyModelStreamEvent, buffer: &mut String, ) { if !buffer.is_empty() { @@ -537,14 +240,14 @@ fn flush_text_buffer( push_compacted_event( compacted, source, - EpiphanyOpenAiStreamPayload::TextDelta { text }, + EpiphanyModelStreamPayload::TextDelta { text }, ); } } fn flush_reasoning_buffer( - compacted: &mut Vec, - source: &EpiphanyOpenAiStreamEvent, + compacted: &mut Vec, + source: &EpiphanyModelStreamEvent, buffer: &mut String, ) { if !buffer.is_empty() { @@ -552,19 +255,20 @@ fn flush_reasoning_buffer( push_compacted_event( compacted, source, - EpiphanyOpenAiStreamPayload::ReasoningDelta { text }, + EpiphanyModelStreamPayload::ReasoningDelta { text }, ); } } fn push_compacted_event( - compacted: &mut Vec, - source: &EpiphanyOpenAiStreamEvent, - payload: EpiphanyOpenAiStreamPayload, + compacted: &mut Vec, + source: &EpiphanyModelStreamEvent, + payload: EpiphanyModelStreamPayload, ) { - compacted.push(EpiphanyOpenAiStreamEvent { + compacted.push(EpiphanyModelStreamEvent { schema_id: source.schema_id.clone(), request_id: source.request_id.clone(), + provider: source.provider.clone(), sequence: compacted.len() as u64, payload, }); @@ -611,7 +315,7 @@ pub fn build_worker_model_request( )); } } - let output_schema_json = worker_output_schema_json(launch_request, &launch_document)?; + let output_schema_json = worker_output_schema_json(&launch_document)?; let request_id = format!( "worker-{}-{}", sanitize_request_id(&launch_request.job_id), @@ -621,7 +325,7 @@ pub fn build_worker_model_request( .context("failed to render sealed worker reasoning projection for model input")?; let mut instructions = worker_instructions(launch_request, &launch_document); if launch_request.binding_id == epiphany_core::EPIPHANY_VERIFICATION_ROLE_BINDING_ID { - instructions.push_str("\n\nTool mandate: before returning `needs-evidence` because source files, artifact directories, command artifacts, commit diffs, Hands receipt bodies, or resident grant lifecycle are not inspectable, call the governed read-only tools available on this request. Use `mcp__epiphany_source__read_file` for cited source/artifact files, `mcp__epiphany_source__directory_inventory` for bounded workspace directory counts and bytes, `mcp__epiphany_source__git_show` for commit diffs, `mcp__epiphany_source__read_hands_receipt` for Hands patch/command/commit receipts, and `mcp__epiphany_state__resident_grant_lifecycle` for exact or bounded recent grant-owned lifecycle state. Directory totals are authoritative only when the tool reports `complete=true`. Grant launchability is authoritative only from the typed state projection, never artifact names or acknowledgement presence. If a tool fails, cite that failed tool result and the exact remaining blocker."); + instructions.push_str("\n\nTool mandate: before returning `needs-evidence` because source files, artifact directories, command artifacts, commit diffs, or resident grant lifecycle are not inspectable, call the governed read-only tools available on this request. Use `mcp__epiphany_source__read_file` for cited source/artifact files, `mcp__epiphany_source__directory_inventory` for bounded workspace directory counts and bytes, `mcp__epiphany_source__git_show` for commit diffs, and `mcp__epiphany_state__resident_grant_lifecycle` for exact or bounded recent grant-owned lifecycle state. The sealed Verification projection already contains the exact Hands patch, command, and commit receipts. Directory totals are authoritative only when the tool reports `complete=true`. Grant launchability is authoritative only from the typed state projection, never artifact names or acknowledgement presence. If a tool fails, cite that failed tool result and the exact remaining blocker."); } else if launch_request.binding_id == epiphany_core::EPIPHANY_RESEARCH_ROLE_BINDING_ID { instructions.push_str("\n\nEvidence mandate: the runtime obtains every immutable public GitHub source named by the typed Research request before this model turn and supplies the exact tool calls and receipts in the input. Cite each requested sourceRef in filesInspected and its evidenceReceiptId in evidence, preserve its contentSha256 in the finding, and report a source gap if any supplied lookup failed. Use the remaining bounded tools only for additional repository or resident-state inspection appropriate to the claim. Never substitute a branch, tag, arbitrary URL, or model memory for immutable public evidence. Directory totals are authoritative only when the inventory reports `complete=true`; grant launchability is authoritative only from the typed lifecycle projection."); } else if launch_request.binding_id == epiphany_core::EPIPHANY_MODELING_ROLE_BINDING_ID { @@ -662,7 +366,7 @@ pub fn complete_worker_job_from_assistant_text( openai_request_id: &str, openai_summary: &EpiphanyOpenAiRuntimeRunSummary, assistant_text: &str, -) -> Result { +) -> Result> { let decision_context = epiphany_core::seal_model_decision_context(store_path.as_ref(), openai_request_id)?; let launch_document = launch_request.launch_document()?; @@ -688,14 +392,7 @@ pub fn complete_worker_job_from_assistant_text( .unwrap_or("structured output was absent") ) }; - let next_safe_move = if contract_failed { - "Repair the worker prompt/output-schema boundary before relaunching; no typed role result was admitted." - .to_string() - } else { - "Inspect provider/tool transport and the sealed model-pass failure before relaunching." - .to_string() - }; - return fail_model_backed_worker_job( + let failure = fail_model_backed_worker_job( store_path, &launch_request.job_id, openai_request_id, @@ -705,51 +402,18 @@ pub fn complete_worker_job_from_assistant_text( "output_contract_failure" }, summary, - next_safe_move, - ); + )?; + return Ok(Some(failure)); } - let verdict = if openai_failed || contract_failed { - "failed".to_string() - } else { - parsed - .as_ref() - .and_then(WorkerResultIngress::verdict) - .unwrap_or_else(|| "completed".to_string()) - }; - let summary = if openai_failed { - format!("Worker model request {openai_request_id} failed before producing usable output.") - } else if let Some(error) = parse_error.as_deref() { - format!( - "Worker model response failed declared output contract {}: {error}", - launch_request.output_contract_id - ) - } else { - parsed - .as_ref() - .and_then(WorkerResultIngress::summary) - .unwrap_or_else(|| "Worker completed without a structured summary.".to_string()) - }; - let next_safe_move = if contract_failed { - "Repair the worker prompt/output-schema boundary before relaunching; no typed role result was admitted." - .to_string() - } else { - parsed - .as_ref() - .and_then(WorkerResultIngress::next_safe_move) - .unwrap_or_else(|| { - "Review the typed worker runtime result before accepting state.".to_string() - }) - }; let mut evidence_refs = parsed .as_ref() .map(WorkerResultIngress::evidence_ids) .unwrap_or_default(); evidence_refs.push(format!("openai-request:{openai_request_id}")); - let mut artifact_refs = parsed + let artifact_refs = parsed .as_ref() .map(WorkerResultIngress::artifact_refs) .unwrap_or_default(); - artifact_refs.push(format!("openai-result:{}", openai_summary.result_id)); let result_id = format!("result-worker-{}", launch_request.job_id); let completed_at = now(); if let Some(parsed) = parsed.as_ref() { @@ -793,41 +457,19 @@ pub fn complete_worker_job_from_assistant_text( } } } - complete_runtime_job( - store_path, - RuntimeSpineJobResultOptions { - result_id, - job_id: launch_request.job_id.clone(), - completed_at, - verdict, - summary, - next_safe_move, - evidence_refs, - artifact_refs, - decision_context_id: Some(decision_context.context_id), - }, - ) + Ok(None) } pub fn fail_worker_job( store_path: impl AsRef, job_id: &str, - summary: String, - next_safe_move: String, -) -> Result { - complete_runtime_job( +) -> Result { + terminalize_runtime_job( store_path, - RuntimeSpineJobResultOptions { - result_id: format!("result-worker-{job_id}"), - job_id: job_id.to_string(), - completed_at: now(), - verdict: "failed".to_string(), - summary, - next_safe_move, - evidence_refs: Vec::new(), - artifact_refs: Vec::new(), - decision_context_id: None, - }, + job_id, + EpiphanyRuntimeJobStatus::Failed, + &now(), + &format!("worker-runtime-failure-{job_id}"), ) } @@ -837,8 +479,7 @@ pub fn fail_model_backed_worker_job( terminal_request_id: &str, failure_kind: &str, summary: String, - next_safe_move: String, -) -> Result { +) -> Result { let context = epiphany_core::seal_model_decision_context(store_path.as_ref(), terminal_request_id)?; let failure = epiphany_core::terminalize_model_pass_failure_session( @@ -851,26 +492,30 @@ pub fn fail_model_backed_worker_job( }, )?; let launch = load_worker_launch_request(store_path.as_ref(), job_id)?; - if epiphany_core::runtime_role_worker_result(store_path.as_ref(), job_id)?.is_none() + let role_result = epiphany_core::runtime_role_worker_result(store_path.as_ref(), job_id)?; + if role_result.is_none() && let Some(role_failure) = failed_frontier_planning_role_result(&launch, &summary, &context.context_id)? { epiphany_core::put_runtime_role_worker_result(store_path.as_ref(), &role_failure)?; + return Ok(failure); } - complete_runtime_job( - store_path, - RuntimeSpineJobResultOptions { - result_id: format!("result-worker-{job_id}"), - job_id: job_id.to_string(), - completed_at: now(), - verdict: "failed".to_string(), - summary, - next_safe_move, - evidence_refs: Vec::new(), - artifact_refs: Vec::new(), - decision_context_id: Some(failure.decision_context_id), - }, - ) + if role_result.is_none() { + terminalize_runtime_job( + store_path.as_ref(), + job_id, + EpiphanyRuntimeJobStatus::Failed, + &failure.failed_at, + &failure.failure_id, + )?; + } else if epiphany_core::runtime_job(store_path.as_ref(), job_id)? + .is_none_or(|job| job.status != EpiphanyRuntimeJobStatus::Failed) + { + return Err(anyhow!( + "model-backed worker failure lost typed result terminality" + )); + } + Ok(failure) } pub fn ensure_openai_runtime_ready(options: &EpiphanyOpenAiRuntimeOptions) -> Result<()> { @@ -888,69 +533,6 @@ pub fn ensure_openai_runtime_ready(options: &EpiphanyOpenAiRuntimeOptions) -> Re Ok(()) } -pub fn store_openai_status( - store_path: impl AsRef, - status: &EpiphanyOpenAiAdapterStatus, -) -> Result<()> { - let mut cache = runtime_spine_cache(store_path)?; - cache.pull_all_backing_stores()?; - cache.put(status.adapter_id.clone(), status)?; - Ok(()) -} - -pub fn store_model_status( - store_path: impl AsRef, - status: &EpiphanyOpenAiAdapterStatus, - provider: &str, - streaming_supported: bool, - provider_transport_attached: bool, -) -> Result<()> { - let mut cache = runtime_spine_cache(store_path)?; - cache.pull_all_backing_stores()?; - let status = epiphany_model_adapter::EpiphanyModelAdapterStatus { - schema_id: epiphany_model_adapter::MODEL_ADAPTER_STATUS_SCHEMA_ID.to_string(), - adapter_id: status.adapter_id.clone(), - provider: provider.to_string(), - default_model: status.default_model.clone(), - streaming_supported, - provider_transport_attached, - }; - cache.put(status.adapter_id.clone(), &status)?; - Ok(()) -} - -pub fn store_model_request( - store_path: impl AsRef, - request: &EpiphanyModelRequest, -) -> Result<()> { - let mut cache = runtime_spine_cache(store_path)?; - cache.pull_all_backing_stores()?; - cache.put(model_request_key(&request.request_id), request)?; - Ok(()) -} - -pub fn assistant_text_from_openai_events( - store_path: impl AsRef, - request_id: &str, -) -> Result { - let mut cache = runtime_spine_cache(store_path)?; - cache.pull_all_backing_stores()?; - let mut events = cache - .get_all::()? - .into_iter() - .filter(|event| event.request_id == request_id) - .collect::>(); - events.sort_by_key(|event| event.sequence); - - let mut text = String::new(); - for event in events { - if let EpiphanyOpenAiStreamPayload::TextDelta { text: delta } = event.payload { - text.push_str(&delta); - } - } - Ok(text) -} - pub fn assistant_text_from_model_events( store_path: impl AsRef, request_id: &str, @@ -1107,7 +689,7 @@ pub fn append_requested_public_source_receipts( } let binding = epiphany_core::require_runtime_tool_execution_binding(store_path, &intent.intent_id)?; - if binding.job_id != source_worker_job_id || binding.model_request_id.is_some() { + if binding.job_id != source_worker_job_id { return Err(anyhow!( "requested public source intent {:?} is not owned by worker {:?}", intent.intent_id, @@ -1151,31 +733,23 @@ pub fn append_requested_public_source_receipts( pub fn default_options( store_path: PathBuf, - codex_home: PathBuf, - request: &EpiphanyOpenAiModelRequest, + request: &EpiphanyProviderRequest, ) -> EpiphanyOpenAiRuntimeOptions { EpiphanyOpenAiRuntimeOptions { store_path, - codex_home, provider_credential_path: None, - session_id: format!("openai-session-{}", request.conversation_id), - job_id: format!("openai-job-{}", request.request_id), - objective: format!("Run typed OpenAI model request {}", request.request_id), + connector_endpoint: Some(DEFAULT_CODEX_CONNECTOR_ENDPOINT), + caller_runtime_id: "epiphany-model-runtime".to_string(), + session_id: format!("openai-session-{}", request.conversation_id()), + job_id: format!("openai-job-{}", request.request_id()), + objective: format!("Run typed provider request {}", request.request_id()), coordinator_note: "Native OpenAI runtime route; Codex is auth/model transport only." .to_string(), - default_model: Some(request.model.clone()), + default_model: Some(request.model().to_string()), request_timeout: Some(DEFAULT_PROVIDER_REQUEST_TIMEOUT), } } -pub fn openai_event_key(request_id: &str, sequence: u64) -> String { - format!("{request_id}:{sequence:08}") -} - -pub fn openai_receipt_key(request_id: &str) -> String { - request_id.to_string() -} - pub fn model_request_key(request_id: &str) -> String { request_id.to_string() } @@ -1188,10 +762,10 @@ pub fn model_receipt_key(request_id: &str) -> String { request_id.to_string() } -pub fn openai_request_from_model_request( +pub fn provider_request_from_model_request( request: &EpiphanyModelRequest, -) -> EpiphanyOpenAiModelRequest { - epiphany_openai_adapter::request_from_native(request) +) -> Result { + epiphany_model_adapter::request_from_native(request) } fn repository_source_tools() -> Vec { @@ -1242,20 +816,6 @@ fn repository_source_tools() -> Vec { }) .to_string(), }, - EpiphanyModelToolDefinition { - name: "mcp__epiphany_source__read_hands_receipt".to_string(), - description: "Read a typed Hands patch, command, or commit receipt body from the runtime-spine store for Soul verification.".to_string(), - parameters_json: serde_json::json!({ - "type": "object", - "additionalProperties": false, - "properties": { - "receiptId": {"type": "string"}, - "kind": {"type": "string", "enum": ["patch", "command", "commit"]} - }, - "required": ["receiptId", "kind"] - }) - .to_string(), - }, EpiphanyModelToolDefinition { name: "mcp__epiphany_state__resident_grant_lifecycle".to_string(), description: "Read grant-owned resident Self lifecycle state from the explicitly launch-bound resident store. Use an exact grantId when known or a bounded recent limit. This is observation only; terminal and launchable fields remain derived from the typed grant/state owner.".to_string(), @@ -1272,71 +832,12 @@ fn repository_source_tools() -> Vec { ] } -pub fn model_event_from_openai_event( - provider: &str, - event: &EpiphanyOpenAiStreamEvent, -) -> EpiphanyModelStreamEvent { - EpiphanyModelStreamEvent { - schema_id: epiphany_model_adapter::MODEL_ADAPTER_EVENT_SCHEMA_ID.to_string(), - request_id: event.request_id.clone(), - provider: provider.to_string(), - sequence: event.sequence, - payload: match &event.payload { - EpiphanyOpenAiStreamPayload::TextDelta { text } => { - EpiphanyModelStreamPayload::TextDelta { text: text.clone() } - } - EpiphanyOpenAiStreamPayload::ReasoningDelta { text } => { - EpiphanyModelStreamPayload::ReasoningDelta { text: text.clone() } - } - EpiphanyOpenAiStreamPayload::ToolCall { - call_id, - name, - arguments, - } => EpiphanyModelStreamPayload::ToolCall { - call_id: call_id.clone(), - name: name.clone(), - arguments: arguments.clone(), - }, - EpiphanyOpenAiStreamPayload::Completed { receipt } => { - EpiphanyModelStreamPayload::Completed { - receipt: model_receipt_from_openai_receipt(provider, receipt), - } - } - EpiphanyOpenAiStreamPayload::Failed { message } => EpiphanyModelStreamPayload::Failed { - message: message.clone(), - }, - }, - } -} - -pub fn model_receipt_from_openai_receipt( - provider: &str, - receipt: &epiphany_openai_adapter::EpiphanyOpenAiModelReceipt, -) -> EpiphanyModelReceipt { - EpiphanyModelReceipt { - schema_id: epiphany_model_adapter::MODEL_ADAPTER_RECEIPT_SCHEMA_ID.to_string(), - request_id: receipt.request_id.clone(), - provider: provider.to_string(), - model: receipt.model.clone(), - provider_response_id: receipt.response_id.clone(), - input_tokens: receipt.input_tokens, - output_tokens: receipt.output_tokens, - reasoning_output_tokens: receipt.reasoning_output_tokens, - transport: receipt.transport.clone(), - cached_input_tokens: receipt.cached_input_tokens, - } -} - -pub fn tool_invocation_intents_from_openai_events( - provider: &str, - events: &[EpiphanyOpenAiStreamEvent], +pub fn tool_invocation_intents_from_model_events( + events: &[EpiphanyModelStreamEvent], ) -> Vec { events .iter() - .filter_map(|event| { - let model_event = model_event_from_openai_event(provider, event); - tool_invocation_intent_from_model_event(&model_event) - }) + .filter_map(tool_invocation_intent_from_model_event) .collect() } @@ -1410,7 +911,7 @@ fn worker_instructions( launch_request: &EpiphanyRuntimeWorkerLaunchRequest, launch_document: &EpiphanyWorkerLaunchDocument, ) -> String { - let output_contract = worker_output_contract_text(launch_request, launch_document); + let output_contract = worker_output_contract_text(launch_document); let dynamic_context = launch_document .dynamic_prompt_context() .map(|context| format!("\n\n{context}")) @@ -1421,10 +922,7 @@ fn worker_instructions( ) } -fn worker_output_schema_json( - launch_request: &EpiphanyRuntimeWorkerLaunchRequest, - document: &EpiphanyWorkerLaunchDocument, -) -> Result { +fn worker_output_schema_json(document: &EpiphanyWorkerLaunchDocument) -> Result { let schema = match document { EpiphanyWorkerLaunchDocument::Role(document) => { if document.frontier_plan_mind_context.is_some() { @@ -1447,27 +945,12 @@ fn worker_output_schema_json( } else if document.proposal_modeling_context.is_some() { epiphany_core::epiphany_proposal_modeling_output_schema() } else if role_id == epiphany_core::EpiphanyRoleResultRoleId::Modeling - && launch_request.repo_frontier_modeling_request_id.is_some() + && document.frontier_verdict_modeling_context.is_some() { - let request_id = launch_request - .repo_frontier_modeling_request_id - .as_deref() - .expect("checked verdict-bound Modeling request identity"); let authority = document .frontier_verdict_modeling_context .as_ref() - .ok_or_else(|| { - anyhow!( - "verdict-bound Modeling launch {request_id:?} omitted its typed authority body" - ) - })?; - if authority.request.request_id != request_id { - return Err(anyhow!( - "verdict-bound Modeling launch identity mismatch: indexed {:?}, authority {:?}", - request_id, - authority.request.request_id - )); - } + .expect("checked verdict-bound Modeling authority"); epiphany_core::epiphany_frontier_verdict_modeling_output_schema(authority) } else { epiphany_core::epiphany_role_launch_output_schema(role_id) @@ -1494,13 +977,10 @@ fn role_result_id_for_launch_role( } } -fn worker_output_contract_text( - launch_request: &EpiphanyRuntimeWorkerLaunchRequest, - document: &EpiphanyWorkerLaunchDocument, -) -> &'static str { +fn worker_output_contract_text(document: &EpiphanyWorkerLaunchDocument) -> &'static str { match document { - EpiphanyWorkerLaunchDocument::Role(_) - if launch_request.repo_frontier_modeling_request_id.is_some() => + EpiphanyWorkerLaunchDocument::Role(document) + if document.frontier_verdict_modeling_context.is_some() => { "Required verdict-bound Modeling fields: roleId=modeling, verdict, summary, nextSafeMove, filesInspected, frontierNodeIds, evidenceIds, frontierVerdictGap. Emit only the semantic gap explaining the allowed resolved or blocked transition. Runtime binds the exact request, keyed RepoModel basis, frontier item and disposition, evidence identities, timestamps, proposal, strong reads, writes, and receipts. Do not emit repoModelOperations, repositoryBodyObservationBasis, request ids, generic Mind patches, selfPatch, commands, release, or deployment cargo." } @@ -1662,33 +1142,6 @@ enum WorkerResultIngress { } impl WorkerResultIngress { - fn verdict(&self) -> Option { - match self { - WorkerResultIngress::Role(result) => clean_optional_string(result.verdict.as_deref()), - WorkerResultIngress::Reorient(result) => clean_optional_string(result.mode.as_deref()), - } - } - - fn summary(&self) -> Option { - match self { - WorkerResultIngress::Role(result) => clean_optional_string(result.summary.as_deref()), - WorkerResultIngress::Reorient(result) => { - clean_optional_string(result.summary.as_deref()) - } - } - } - - fn next_safe_move(&self) -> Option { - match self { - WorkerResultIngress::Role(result) => { - clean_optional_string(result.next_safe_move.as_deref()) - } - WorkerResultIngress::Reorient(result) => { - clean_optional_string(result.next_safe_move.as_deref()) - } - } - } - fn evidence_ids(&self) -> Vec { match self { WorkerResultIngress::Role(result) => clean_string_vec(&result.evidence_ids), @@ -1745,14 +1198,6 @@ fn role_worker_result_from_ingress( runtime_evidence_ids: Vec, artifact_refs: Vec, ) -> EpiphanyRuntimeRoleWorkerResult { - let verification_authority = rmp_serde::from_slice::( - &launch_request.launch_document_msgpack, - ) - .ok() - .and_then(|document| match document { - EpiphanyWorkerLaunchDocument::Role(role) => role.frontier_verification_context, - EpiphanyWorkerLaunchDocument::Reorient(_) => None, - }); let accepted_evidence_ids = { let mut evidence_ids = clean_string_vec(&result.evidence_ids); evidence_ids.extend(clean_string_vec(&runtime_evidence_ids)); @@ -1930,50 +1375,47 @@ fn role_worker_result_from_ingress( } else { (None, None) }; - let (frontier_plan_candidate_msgpack, frontier_plan_candidate_error) = if let Some(ingress) = - result.frontier_plan_candidate.as_ref() - { - let mut safe_paths = clean_string_vec(&ingress.safe_paths); - safe_paths.sort(); - safe_paths.dedup(); - let mut candidate = epiphany_core::RepoFrontierPlanCandidate { - schema_version: epiphany_core::REPO_FRONTIER_PLAN_CANDIDATE_SCHEMA_VERSION.to_string(), - candidate_id: String::new(), - planning_request_id: frontier_planning_context - .map(|context| context.request_id.clone()) - .unwrap_or_default(), - model_projection_digest: frontier_planning_context - .map(|context| context.model_projection_digest.clone()) - .unwrap_or_default(), - model_source_documents: frontier_planning_context - .map(|context| context.model_source_documents.clone()) - .unwrap_or_default(), - frontier_item_id: frontier_planning_context - .map(|context| context.frontier_item_id.clone()) - .unwrap_or_default(), - frontier_item_hash: frontier_planning_context - .map(|context| context.frontier_item_hash.clone()) - .unwrap_or_default(), - safe_paths, - action: ingress.action.trim().to_string(), - command: ingress.command.trim().to_string(), - checks: clean_string_vec(&ingress.checks), - stop_conditions: clean_string_vec(&ingress.stop_conditions), - rollback_steps: clean_string_vec(&ingress.rollback_steps), - commit_message: ingress.commit_message.trim().to_string(), - proposed_at: completed_at.to_string(), - contract: epiphany_core::REPO_FRONTIER_PLANNING_CONTRACT.to_string(), - }; - match epiphany_core::canonical_repo_frontier_plan_candidate_id(&candidate) { - Ok(candidate_id) => { - candidate.candidate_id = candidate_id; - encode_optional_document(&Some(candidate), "frontierPlanCandidate") + let (frontier_plan_candidate_msgpack, frontier_plan_candidate_error) = + if let Some(ingress) = result.frontier_plan_candidate.as_ref() { + let mut safe_paths = clean_string_vec(&ingress.safe_paths); + safe_paths.sort(); + safe_paths.dedup(); + let mut candidate = epiphany_core::RepoFrontierPlanCandidate { + candidate_id: String::new(), + planning_request_id: frontier_planning_context + .map(|context| context.request_id.clone()) + .unwrap_or_default(), + model_projection_digest: frontier_planning_context + .map(|context| context.model_projection_digest.clone()) + .unwrap_or_default(), + model_source_documents: frontier_planning_context + .map(|context| context.model_source_documents.clone()) + .unwrap_or_default(), + frontier_item_id: frontier_planning_context + .map(|context| context.frontier_item_id.clone()) + .unwrap_or_default(), + frontier_item_hash: frontier_planning_context + .map(|context| context.frontier_item_hash.clone()) + .unwrap_or_default(), + safe_paths, + action: ingress.action.trim().to_string(), + command: ingress.command.trim().to_string(), + checks: clean_string_vec(&ingress.checks), + stop_conditions: clean_string_vec(&ingress.stop_conditions), + rollback_steps: clean_string_vec(&ingress.rollback_steps), + commit_message: ingress.commit_message.trim().to_string(), + proposed_at: completed_at.to_string(), + }; + match epiphany_core::canonical_repo_frontier_plan_candidate_id(&candidate) { + Ok(candidate_id) => { + candidate.candidate_id = candidate_id; + encode_optional_document(&Some(candidate), "frontierPlanCandidate") + } + Err(error) => (None, Some(format!("frontierPlanCandidate: {error}"))), } - Err(error) => (None, Some(format!("frontierPlanCandidate: {error}"))), - } - } else { - (None, None) - }; + } else { + (None, None) + }; let (frontier_plan_mind_decision_msgpack, frontier_plan_mind_decision_error) = if let Some(ingress) = result.frontier_plan_mind_decision.as_ref() { if let Some(decision) = ingress.decision { @@ -2116,11 +1558,6 @@ fn role_worker_result_from_ingress( (None, None) }; EpiphanyRuntimeRoleWorkerResult { - schema_version: epiphany_core::RUNTIME_ROLE_WORKER_RESULT_SCHEMA_VERSION.to_string(), - repository_body_observation_basis: role_id - .eq_ignore_ascii_case("modeling") - .then(|| repository_body_observation_basis.cloned()) - .flatten(), result_id: result_id.to_string(), job_id: launch_request.job_id.clone(), role_id: clean_optional_string(result.role_id.as_deref()) @@ -2157,26 +1594,9 @@ fn role_worker_result_from_ingress( ), metadata: std::collections::BTreeMap::new(), repo_model_mutation_proposal_msgpack, - verification_request_id: verification_authority - .as_ref() - .map(|authority| authority.request.request_id.clone()), - frontier_route_id: verification_authority - .as_ref() - .map(|authority| authority.route.route_id.clone()), - repo_frontier_modeling_request_id: launch_request.repo_frontier_modeling_request_id.clone(), - proposal_modeling_request_id: launch_request.proposal_modeling_request_id.clone(), - repo_frontier_research_request_id: launch_request.repo_frontier_research_request_id.clone(), - frontier_planning_request_id: launch_request.frontier_planning_request_id.clone(), frontier_plan_candidate_msgpack, - frontier_plan_mind_request_id: frontier_plan_mind_context - .map(|context| context.request.request_id.clone()), frontier_plan_mind_decision_msgpack, - imagination_consideration_request_id: imagination_consideration_context - .map(|context| context.request.request_id.clone()), imagination_consideration_candidate_msgpack, - admitted_model_direction_consideration_request_id: - admitted_model_direction_consideration_context - .map(|context| context.request.request_id.clone()), admitted_model_direction_consideration_result_msgpack, decision_context_id: decision_context_id.to_string(), } @@ -2191,8 +1611,13 @@ pub fn failed_frontier_planning_role_result( failure_summary: &str, decision_context_id: &str, ) -> Result> { - if launch_request.frontier_planning_request_id.is_none() - && launch_request.frontier_plan_mind_request_id.is_none() + let document = launch_request.launch_document()?; + let EpiphanyWorkerLaunchDocument::Role(document) = document else { + return Err(anyhow!( + "frontier planning failure projection requires a role launch" + )); + }; + if document.frontier_planning_context.is_none() && document.frontier_plan_mind_context.is_none() { return Ok(None); } @@ -2201,15 +1626,8 @@ pub fn failed_frontier_planning_role_result( "model-backed planning failure requires its sealed decision context" )); } - let document = launch_request.launch_document()?; - let EpiphanyWorkerLaunchDocument::Role(document) = document else { - return Err(anyhow!( - "frontier planning failure projection requires a role launch" - )); - }; let summary = failure_summary.trim().to_string(); Ok(Some(EpiphanyRuntimeRoleWorkerResult { - schema_version: epiphany_core::RUNTIME_ROLE_WORKER_RESULT_SCHEMA_VERSION.to_string(), result_id: format!("result-worker-{}", launch_request.job_id), job_id: launch_request.job_id.clone(), role_id: document.role_id, @@ -2230,19 +1648,9 @@ pub fn failed_frontier_planning_role_result( item_error: Some(summary.clone()), metadata: std::collections::BTreeMap::new(), repo_model_mutation_proposal_msgpack: None, - verification_request_id: None, - frontier_route_id: None, - repo_frontier_modeling_request_id: None, - proposal_modeling_request_id: None, - repo_frontier_research_request_id: None, - frontier_planning_request_id: None, frontier_plan_candidate_msgpack: None, - frontier_plan_mind_request_id: None, frontier_plan_mind_decision_msgpack: None, - repository_body_observation_basis: document.repository_body_observation_basis, - imagination_consideration_request_id: None, imagination_consideration_candidate_msgpack: None, - admitted_model_direction_consideration_request_id: None, admitted_model_direction_consideration_result_msgpack: None, decision_context_id: decision_context_id.to_string(), })) @@ -2256,7 +1664,6 @@ fn reorient_worker_result_from_ingress( artifact_refs: Vec, ) -> EpiphanyRuntimeReorientWorkerResult { EpiphanyRuntimeReorientWorkerResult { - schema_version: epiphany_core::RUNTIME_REORIENT_WORKER_RESULT_SCHEMA_VERSION.to_string(), result_id: result_id.to_string(), job_id: launch_request.job_id.clone(), mode: clean_optional_string(result.mode.as_deref()) @@ -2454,8 +1861,8 @@ mod tests { use super::*; use epiphany_core::EpiphanyWorkerLaunchDocument; use epiphany_core::RuntimeSpineHeartbeatJobOptions; - use epiphany_core::runtime_job_snapshot; - use epiphany_openai_adapter::EpiphanyOpenAiModelReceipt; + use epiphany_core::runtime_job; + use epiphany_model_adapter::EpiphanyModelReceipt; use sha2::{Digest, Sha256}; use tempfile::tempdir; @@ -2468,15 +1875,6 @@ mod tests { created_at: options.created_at.clone(), }, )?; - epiphany_core::ensure_runtime_session( - store, - epiphany_core::RuntimeSpineSessionOptions { - session_id: options.session_id.clone(), - objective: options.objective.clone(), - created_at: options.created_at.clone(), - coordinator_note: options.coordinator_note.clone(), - }, - )?; let mut cache = epiphany_core::runtime_spine_cache(store)?; cache.pull_all_backing_stores()?; let prepared = epiphany_core::prepare_runtime_spine_heartbeat_job(&cache, options)?; @@ -2534,7 +1932,6 @@ mod tests { agent_pass_admission_refusals: Vec::new(), investigation_checkpoint: None, mode: None, - planning: Default::default(), repository_body_observation: None, repo_model: None, }; @@ -2592,76 +1989,6 @@ mod tests { Ok(()) } - #[test] - fn every_static_worker_contract_projects_to_one_strict_provider_shape() -> Result<()> { - let schemas = vec![ - epiphany_core::epiphany_role_launch_output_schema( - epiphany_core::EpiphanyRoleResultRoleId::Imagination, - ), - epiphany_core::epiphany_role_launch_output_schema( - epiphany_core::EpiphanyRoleResultRoleId::Research, - ), - epiphany_core::epiphany_role_launch_output_schema( - epiphany_core::EpiphanyRoleResultRoleId::Modeling, - ), - epiphany_core::epiphany_role_launch_output_schema( - epiphany_core::EpiphanyRoleResultRoleId::Verification, - ), - epiphany_core::epiphany_frontier_planning_output_schema(), - epiphany_core::epiphany_frontier_plan_mind_output_schema(), - epiphany_core::epiphany_imagination_consideration_output_schema(), - epiphany_core::epiphany_admitted_model_direction_consideration_output_schema(), - epiphany_core::epiphany_proposal_modeling_output_schema(), - epiphany_core::epiphany_reorient_launch_output_schema(), - ]; - - for (index, schema) in schemas.into_iter().enumerate() { - let mut request = EpiphanyOpenAiModelRequest::new( - format!("strict-worker-schema-{index}"), - "strict-worker-schema", - "gpt-5.4", - "Return the typed result.", - ); - request.output_contract_id = Some(format!("worker-schema-{index}")); - request.output_schema_json = Some(serde_json::to_string(&schema)?); - let body = epiphany_openai_codex_spine::responses_body_from_epiphany(request)?; - assert_eq!(body["text"]["format"]["strict"], true); - } - Ok(()) - } - - #[test] - fn research_contract_projects_typed_decision_to_provider_strict_shape() -> Result<()> { - let mut request = EpiphanyOpenAiModelRequest::new( - "strict-research-schema", - "strict-worker-schema", - "gpt-5.4", - "Return the typed result.", - ); - request.output_contract_id = - Some(epiphany_core::ROLE_WORKER_OUTPUT_CONTRACT_ID.to_string()); - request.output_schema_json = Some(serde_json::to_string( - &epiphany_core::epiphany_role_launch_output_schema( - epiphany_core::EpiphanyRoleResultRoleId::Research, - ), - )?); - - let body = epiphany_openai_codex_spine::responses_body_from_epiphany(request)?; - let decision = &body["text"]["format"]["schema"]["properties"]["researchDecision"]; - assert!(decision.get("anyOf").is_none()); - assert_eq!(decision["additionalProperties"], false); - assert_eq!( - decision["required"], - serde_json::json!(["evidence", "investigationCheckpoint", "observations"]) - ); - assert!(decision["properties"].get("scratch").is_none()); - assert_eq!( - decision["properties"]["investigationCheckpoint"]["anyOf"][0]["additionalProperties"], - false - ); - Ok(()) - } - #[test] fn role_ingress_rejects_duplicate_top_level_fields() { let error = parse_assistant_json::( @@ -2674,7 +2001,6 @@ mod tests { #[test] fn proposal_modeling_ingress_composes_runtime_owned_patch_and_identity() -> Result<()> { let launch = EpiphanyRuntimeWorkerLaunchRequest { - schema_version: epiphany_core::RUNTIME_WORKER_LAUNCH_REQUEST_SCHEMA_VERSION.into(), job_id: "proposal-job-1".into(), binding_id: epiphany_core::EPIPHANY_MODELING_ROLE_BINDING_ID.into(), role: epiphany_core::EPIPHANY_MODELING_OWNER_ROLE.into(), @@ -2684,14 +2010,6 @@ mod tests { document_kind: "role".into(), launch_document_msgpack: Vec::new(), metadata: std::collections::BTreeMap::new(), - proposal_modeling_request_id: Some("proposal-request-1".into()), - frontier_planning_request_id: None, - frontier_plan_mind_request_id: None, - imagination_consideration_request_id: None, - admitted_model_direction_consideration_request_id: None, - repo_frontier_modeling_request_id: None, - repo_frontier_research_request_id: None, - repo_frontier_verification_request_id: None, }; let basis = epiphany_core::RepositoryBodyObservationBasis { schema_version: "epiphany.repository_body.v2".into(), @@ -2763,11 +2081,6 @@ mod tests { vec!["openai-request:proposal".into()], Vec::new(), ); - assert_eq!(result.repository_body_observation_basis, Some(basis)); - assert_eq!( - result.proposal_modeling_request_id.as_deref(), - Some("proposal-request-1") - ); assert!(result.evidence_ids.contains(&"proposal-1".to_string())); let proposal: epiphany_core::EpiphanyRepoModelMutationProposal = rmp_serde::from_slice( result @@ -2808,7 +2121,6 @@ mod tests { scan_finished_at: "2026-08-14T00:00:01Z".into(), }; let launch = EpiphanyRuntimeWorkerLaunchRequest { - schema_version: epiphany_core::RUNTIME_WORKER_LAUNCH_REQUEST_SCHEMA_VERSION.into(), job_id: "atlas-modeling-job".into(), binding_id: epiphany_core::EPIPHANY_MODELING_ROLE_BINDING_ID.into(), role: epiphany_core::EPIPHANY_MODELING_OWNER_ROLE.into(), @@ -2818,14 +2130,6 @@ mod tests { document_kind: "role".into(), launch_document_msgpack: Vec::new(), metadata: std::collections::BTreeMap::new(), - proposal_modeling_request_id: None, - frontier_planning_request_id: None, - frontier_plan_mind_request_id: None, - imagination_consideration_request_id: None, - admitted_model_direction_consideration_request_id: None, - repo_frontier_modeling_request_id: None, - repo_frontier_research_request_id: None, - repo_frontier_verification_request_id: None, }; let atlas = RoleWorkerResultIngress { role_id: Some("modeling".into()), @@ -2947,7 +2251,6 @@ mod tests { admitted_model_direction_consideration_context: None, }); let launch = EpiphanyRuntimeWorkerLaunchRequest { - schema_version: epiphany_core::RUNTIME_WORKER_LAUNCH_REQUEST_SCHEMA_VERSION.into(), job_id: "proposal-job-1".into(), binding_id: epiphany_core::EPIPHANY_MODELING_ROLE_BINDING_ID.into(), role: epiphany_core::EPIPHANY_MODELING_OWNER_ROLE.into(), @@ -2957,14 +2260,6 @@ mod tests { document_kind: "role".into(), launch_document_msgpack: rmp_serde::to_vec_named(&document)?, metadata: std::collections::BTreeMap::new(), - proposal_modeling_request_id: Some("proposal-request-1".into()), - frontier_planning_request_id: None, - frontier_plan_mind_request_id: None, - imagination_consideration_request_id: None, - admitted_model_direction_consideration_request_id: None, - repo_frontier_modeling_request_id: None, - repo_frontier_research_request_id: None, - repo_frontier_verification_request_id: None, }; let request = build_worker_model_request( @@ -3025,7 +2320,6 @@ mod tests { }"#, )?; let launch = EpiphanyRuntimeWorkerLaunchRequest { - schema_version: epiphany_core::RUNTIME_WORKER_LAUNCH_REQUEST_SCHEMA_VERSION.to_string(), job_id: "planning-job-1".into(), binding_id: epiphany_core::EPIPHANY_IMAGINATION_ROLE_BINDING_ID.into(), role: epiphany_core::EPIPHANY_IMAGINATION_OWNER_ROLE.into(), @@ -3035,14 +2329,6 @@ mod tests { document_kind: "role".into(), launch_document_msgpack: Vec::new(), metadata: std::collections::BTreeMap::new(), - proposal_modeling_request_id: None, - frontier_planning_request_id: Some("planning-request-1".into()), - frontier_plan_mind_request_id: None, - imagination_consideration_request_id: None, - admitted_model_direction_consideration_request_id: None, - repo_frontier_modeling_request_id: None, - repo_frontier_research_request_id: None, - repo_frontier_verification_request_id: None, }; let planning_context = epiphany_core::RepoFrontierPlanningContextProjection { schema_version: epiphany_core::REPO_FRONTIER_PLANNING_CONTEXT_SCHEMA_VERSION.into(), @@ -3076,10 +2362,6 @@ mod tests { Vec::new(), Vec::new(), ); - assert_eq!( - result.frontier_planning_request_id.as_deref(), - Some("planning-request-1") - ); assert!(result.research_decision_msgpack.is_none()); let candidate = result .frontier_plan_candidate()? @@ -3089,10 +2371,6 @@ mod tests { candidate.candidate_id, epiphany_core::canonical_repo_frontier_plan_candidate_id(&candidate)? ); - assert_eq!( - candidate.schema_version, - epiphany_core::REPO_FRONTIER_PLAN_CANDIDATE_SCHEMA_VERSION - ); assert_eq!(candidate.safe_paths, ["src", "tests"]); Ok(()) } @@ -3188,7 +2466,6 @@ mod tests { }, }; let launch = EpiphanyRuntimeWorkerLaunchRequest { - schema_version: epiphany_core::RUNTIME_WORKER_LAUNCH_REQUEST_SCHEMA_VERSION.into(), job_id: "consideration-job-1".into(), binding_id: epiphany_core::EPIPHANY_IMAGINATION_ROLE_BINDING_ID.into(), role: epiphany_core::EPIPHANY_IMAGINATION_OWNER_ROLE.into(), @@ -3198,14 +2475,6 @@ mod tests { document_kind: "role".into(), launch_document_msgpack: Vec::new(), metadata: std::collections::BTreeMap::new(), - proposal_modeling_request_id: None, - frontier_planning_request_id: None, - frontier_plan_mind_request_id: None, - imagination_consideration_request_id: Some(request.request_id.clone()), - admitted_model_direction_consideration_request_id: None, - repo_frontier_modeling_request_id: None, - repo_frontier_research_request_id: None, - repo_frontier_verification_request_id: None, }; let result = role_worker_result_from_ingress( &launch, @@ -3224,10 +2493,6 @@ mod tests { Vec::new(), Vec::new(), ); - assert_eq!( - result.imagination_consideration_request_id.as_deref(), - Some("request-1") - ); let candidate = result .imagination_consideration_candidate()? .expect("runtime-composed candidate"); @@ -3271,7 +2536,24 @@ mod tests { repository_body_observation_basis: None, proposal_modeling_context: None, frontier_verdict_modeling_context: None, - frontier_planning_context: None, + frontier_planning_context: Some( + epiphany_core::RepoFrontierPlanningContextProjection { + schema_version: + epiphany_core::REPO_FRONTIER_PLANNING_CONTEXT_SCHEMA_VERSION.into(), + contract: epiphany_core::REPO_FRONTIER_PLANNING_CONTEXT_CONTRACT.into(), + request_id: "planning-request-failed".into(), + model_projection_digest: "model-digest".into(), + model_source_documents: Vec::new(), + frontier_item_id: "frontier-failed".into(), + frontier_item_hash: "frontier-hash".into(), + selected_organ: "Imagination".into(), + repository_scope: vec!["src".into()], + requested_at: "2026-07-15T09:59:00Z".into(), + runtime_id: "runtime-1".into(), + frontier_authority_documents: Vec::new(), + claim_obligation_documents: Vec::new(), + }, + ), frontier_research_context: None, frontier_verification_context: None, frontier_plan_mind_context: None, @@ -3279,7 +2561,6 @@ mod tests { admitted_model_direction_consideration_context: None, }); let launch = EpiphanyRuntimeWorkerLaunchRequest { - schema_version: epiphany_core::RUNTIME_WORKER_LAUNCH_REQUEST_SCHEMA_VERSION.into(), job_id: "planning-job-failed".into(), binding_id: epiphany_core::EPIPHANY_IMAGINATION_ROLE_BINDING_ID.into(), role: epiphany_core::EPIPHANY_IMAGINATION_OWNER_ROLE.into(), @@ -3289,14 +2570,6 @@ mod tests { document_kind: "role".into(), launch_document_msgpack: rmp_serde::to_vec_named(&document)?, metadata: std::collections::BTreeMap::new(), - proposal_modeling_request_id: None, - frontier_planning_request_id: Some("planning-request-1".into()), - frontier_plan_mind_request_id: None, - imagination_consideration_request_id: None, - admitted_model_direction_consideration_request_id: None, - repo_frontier_modeling_request_id: None, - repo_frontier_research_request_id: None, - repo_frontier_verification_request_id: None, }; let failed = failed_frontier_planning_role_result( &launch, @@ -3307,7 +2580,6 @@ mod tests { assert_eq!(failed.job_id, launch.job_id); assert_eq!(failed.role_id, "imagination"); assert_eq!(failed.item_error.as_deref(), Some("candidate mismatch")); - assert!(failed.frontier_planning_request_id.is_none()); assert!(failed.frontier_plan_candidate_msgpack.is_none()); assert!(failed.research_decision_msgpack.is_none()); assert!(failed.repo_model_mutation_proposal_msgpack.is_none()); @@ -3325,7 +2597,6 @@ mod tests { r#"{"roleId":"mindAdmissionReview","verdict":"adopt","summary":"bounded","nextSafeMove":"admit","filesInspected":[],"frontierPlanMindDecision":{"decision":"adopt","rationale":"Exact candidate is bounded and falsifiable."}}"#, )?; let launch = EpiphanyRuntimeWorkerLaunchRequest { - schema_version: epiphany_core::RUNTIME_WORKER_LAUNCH_REQUEST_SCHEMA_VERSION.into(), job_id: "mind-job-1".into(), binding_id: epiphany_core::EPIPHANY_MIND_ROLE_BINDING_ID.into(), role: epiphany_core::EPIPHANY_MIND_OWNER_ROLE.into(), @@ -3335,21 +2606,11 @@ mod tests { document_kind: "role".into(), launch_document_msgpack: Vec::new(), metadata: std::collections::BTreeMap::new(), - proposal_modeling_request_id: None, - frontier_planning_request_id: None, - frontier_plan_mind_request_id: Some("mind-request-1".into()), - imagination_consideration_request_id: None, - admitted_model_direction_consideration_request_id: None, - repo_frontier_modeling_request_id: None, - repo_frontier_research_request_id: None, - repo_frontier_verification_request_id: None, }; let context = epiphany_core::RepoFrontierPlanMindContextProjection { schema_version: epiphany_core::REPO_FRONTIER_PLAN_MIND_CONTEXT_SCHEMA_VERSION.into(), contract: epiphany_core::REPO_FRONTIER_PLAN_MIND_CONTEXT_CONTRACT.into(), request: epiphany_core::RepoFrontierPlanMindRequest { - schema_version: epiphany_core::REPO_FRONTIER_PLAN_MIND_REQUEST_SCHEMA_VERSION - .into(), request_id: "mind-request-1".into(), planning_request_id: "planning-request-1".into(), imagination_result_id: "imagination-result-1".into(), @@ -3358,10 +2619,8 @@ mod tests { candidate_sha256: "candidate-hash-1".into(), runtime_id: "runtime-1".into(), requested_at: "2026-07-15T09:59:00Z".into(), - contract: epiphany_core::REPO_FRONTIER_PLAN_MIND_REQUEST_CONTRACT.into(), }, planning_request: epiphany_core::RepoFrontierPlanningRequest { - schema_version: epiphany_core::REPO_FRONTIER_PLANNING_REQUEST_SCHEMA_VERSION.into(), request_id: "planning-request-1".into(), model_projection_digest: "model-projection-1".into(), model_source_documents: Vec::new(), @@ -3372,11 +2631,9 @@ mod tests { selected_organ: "Imagination".into(), repository_scope: vec!["epiphany-openai-runtime".into()], requested_at: "2026-07-15T09:58:00Z".into(), - contract: epiphany_core::REPO_FRONTIER_PLANNING_CONTRACT.into(), runtime_id: "runtime-1".into(), }, candidate: epiphany_core::RepoFrontierPlanCandidate { - schema_version: epiphany_core::REPO_FRONTIER_PLAN_CANDIDATE_SCHEMA_VERSION.into(), candidate_id: "candidate-1".into(), planning_request_id: "planning-request-1".into(), model_projection_digest: "model-projection-1".into(), @@ -3391,7 +2648,6 @@ mod tests { rollback_steps: vec!["revert".into()], commit_message: "Derive Mind identity".into(), proposed_at: "2026-07-15T09:59:00Z".into(), - contract: epiphany_core::REPO_FRONTIER_PLANNING_CONTRACT.into(), }, }; let result = role_worker_result_from_ingress( @@ -3411,10 +2667,6 @@ mod tests { Vec::new(), Vec::new(), ); - assert_eq!( - result.frontier_plan_mind_request_id.as_deref(), - Some("mind-request-1") - ); let decision = result .frontier_plan_mind_decision()? .expect("typed Mind decision"); @@ -3433,79 +2685,84 @@ mod tests { Ok(()) } - fn test_openai_event( + fn test_model_event( request_id: &str, sequence: u64, - payload: EpiphanyOpenAiStreamPayload, - ) -> EpiphanyOpenAiStreamEvent { - EpiphanyOpenAiStreamEvent { - schema_id: epiphany_openai_adapter::OPENAI_ADAPTER_EVENT_SCHEMA_ID.to_string(), + payload: EpiphanyModelStreamPayload, + ) -> EpiphanyModelStreamEvent { + EpiphanyModelStreamEvent { + schema_id: epiphany_model_adapter::MODEL_ADAPTER_EVENT_SCHEMA_ID.to_string(), request_id: request_id.to_string(), + provider: DEFAULT_MODEL_PROVIDER.to_string(), sequence, payload, } } #[test] - fn compacts_openai_text_and_reasoning_deltas_before_storage() { + fn compacts_native_model_text_and_reasoning_deltas_before_storage() { let events = vec![ - test_openai_event( + test_model_event( "req-1", 0, - EpiphanyOpenAiStreamPayload::ReasoningDelta { + EpiphanyModelStreamPayload::ReasoningDelta { text: "think".to_string(), }, ), - test_openai_event( + test_model_event( "req-1", 1, - EpiphanyOpenAiStreamPayload::ReasoningDelta { + EpiphanyModelStreamPayload::ReasoningDelta { text: " small".to_string(), }, ), - test_openai_event( + test_model_event( "req-1", 2, - EpiphanyOpenAiStreamPayload::TextDelta { + EpiphanyModelStreamPayload::TextDelta { text: "{\"role".to_string(), }, ), - test_openai_event( + test_model_event( "req-1", 3, - EpiphanyOpenAiStreamPayload::TextDelta { + EpiphanyModelStreamPayload::TextDelta { text: "Id\":\"modeling\"}".to_string(), }, ), - test_openai_event( + test_model_event( "req-1", 4, - EpiphanyOpenAiStreamPayload::Completed { - receipt: EpiphanyOpenAiModelReceipt::new("req-1", "gpt-5.4"), + EpiphanyModelStreamPayload::Completed { + receipt: Box::new(EpiphanyModelReceipt::new( + "req-1", + DEFAULT_MODEL_PROVIDER, + "gpt-5.4", + )), }, ), ]; - let compacted = compact_openai_events_for_storage(&events); + let compacted = compact_model_events_for_storage(&events); assert_eq!(compacted.len(), 3); assert_eq!(compacted[0].sequence, 0); assert!(matches!( &compacted[0].payload, - EpiphanyOpenAiStreamPayload::ReasoningDelta { text } if text == "think small" + EpiphanyModelStreamPayload::ReasoningDelta { text } if text == "think small" )); assert!(matches!( &compacted[1].payload, - EpiphanyOpenAiStreamPayload::TextDelta { text } if text == "{\"roleId\":\"modeling\"}" + EpiphanyModelStreamPayload::TextDelta { text } if text == "{\"roleId\":\"modeling\"}" )); assert!(matches!( compacted[2].payload, - EpiphanyOpenAiStreamPayload::Completed { .. } + EpiphanyModelStreamPayload::Completed { .. } )); } #[test] - fn records_typed_openai_documents_in_runtime_store() -> Result<()> { + fn records_one_native_model_stream_in_runtime_store() -> Result<()> { let temp = tempdir()?; let store = temp.path().join("runtime.msgpack"); let native_request = EpiphanyModelRequest::new( @@ -3515,43 +2772,33 @@ mod tests { "gpt-5.4", "Answer plainly.", ); - let request = epiphany_openai_adapter::request_from_native(&native_request); - let options = default_options(store.clone(), PathBuf::from(".codex"), &request); + let request = epiphany_model_adapter::request_from_native(&native_request)?; + let options = default_options(store.clone(), &request); ensure_openai_runtime_ready(&options)?; open_runtime_model_execution( &store, - RuntimeSpineSessionOptions { - session_id: options.session_id.clone(), - objective: options.objective.clone(), - created_at: now(), - coordinator_note: options.coordinator_note.clone(), - }, RuntimeSpineJobOptions { job_id: options.job_id.clone(), session_id: options.session_id.clone(), - role: OPENAI_RUNTIME_ROLE.to_string(), - created_at: now(), - summary: "test job".to_string(), - artifact_refs: Vec::new(), }, &native_request, - &now(), )?; - let mut receipt = EpiphanyOpenAiModelReceipt::new("req-1", "gpt-5.4"); - receipt.response_id = Some("resp-1".to_string()); + let mut receipt = EpiphanyModelReceipt::new("req-1", DEFAULT_MODEL_PROVIDER, "gpt-5.4"); + receipt.provider_response_id = Some("resp-1".to_string()); receipt.transport = Some("test".to_string()); - let events = vec![EpiphanyOpenAiStreamEvent { - schema_id: epiphany_openai_adapter::OPENAI_ADAPTER_EVENT_SCHEMA_ID.to_string(), + let events = vec![EpiphanyModelStreamEvent { + schema_id: epiphany_model_adapter::MODEL_ADAPTER_EVENT_SCHEMA_ID.to_string(), request_id: "req-1".to_string(), + provider: DEFAULT_MODEL_PROVIDER.to_string(), sequence: 0, - payload: EpiphanyOpenAiStreamPayload::Completed { receipt }, + payload: EpiphanyModelStreamPayload::Completed { + receipt: Box::new(receipt), + }, }]; - let summary = - record_openai_events(&store, &options, &request, DEFAULT_MODEL_PROVIDER, &events)?; + let summary = record_model_turn_events(&store, &options, &native_request, &events)?; assert_eq!(summary.verdict, "pass"); - assert_eq!(assistant_text_from_openai_events(&store, "req-1")?, ""); assert_eq!(assistant_text_from_model_events(&store, "req-1")?, ""); let mut cache = runtime_spine_cache(&store)?; cache.pull_all_backing_stores()?; @@ -3562,17 +2809,10 @@ mod tests { .is_some() ); assert!(cache.get::("req-1")?.is_some()); - assert!(cache.get::("req-1")?.is_some()); - assert!( - cache - .get::("req-1:00000000")? - .is_some() - ); - assert!(cache.get::("req-1")?.is_some()); + assert!(cache.get::("req-1")?.is_some()); assert_eq!( - runtime_job_snapshot(&store, &options.job_id)? + runtime_job(&store, &options.job_id)? .expect("snapshot") - .job .status, epiphany_core::EpiphanyRuntimeJobStatus::Completed ); @@ -3603,180 +2843,26 @@ mod tests { epiphany_core::bind_runtime_to_swarm(&store, "swarm-test", "2026-08-08T00:00:00Z")?; epiphany_core::bind_repository_body(&repo, &body_store, &store, "workspace-test")?; let body_basis = epiphany_core::observe_runtime_repository_body_basis(&store)?; - let frontier_item = epiphany_core::RepoFrontierItem { - id: "frontier-1".to_string(), - migration_body: "runtime".to_string(), - question: "Did the verified consequence hold?".to_string(), - gap: "Awaiting verdict incorporation.".to_string(), - target_claim_ids: vec!["claim-1".to_string()], - repository_scope: vec!["epiphany-core".to_string()], - recommended_next_organ: "Hands".to_string(), - adopted_plan: None, - dependency_item_ids: Vec::new(), - status: epiphany_core::RepoFrontierStatus::Active, - evidence_refs: vec!["prior-evidence".to_string()], - public_source_refs: Vec::new(), - created_at: Some("2026-08-07T00:00:00Z".to_string()), - updated_at: Some("2026-08-07T00:00:00Z".to_string()), - retired_at: None, - superseded_by: None, - }; - let frontier_item_hash = format!( - "{:x}", - sha2::Sha256::digest(rmp_serde::to_vec_named(&frontier_item)?) - ); - let mut authority_cache = runtime_spine_cache(&store)?; - authority_cache.put( - &frontier_item.id, - &epiphany_core::EpiphanyRepoModelFrontierDocument::new(&frontier_item)?, - )?; - let frontier_version = epiphany_core::EpiphanyMindDocumentVersion::from_envelope( - "epiphany-mind", - &authority_cache - .get_envelope::( - &frontier_item.id, - )? - .expect("persisted frontier envelope"), - )?; - let route = epiphany_core::RepoFrontierRoute { - schema_version: epiphany_core::REPO_FRONTIER_ROUTE_SCHEMA_VERSION.into(), - route_id: "route-1".into(), - next_organ: epiphany_core::RepoFrontierNextOrgan::Hands, - model_projection_digest: "model-projection".into(), - model_source_documents: vec![frontier_version], - frontier_item_id: frontier_item.id.clone(), - frontier_item_hash, - migration_body: frontier_item.migration_body.clone(), - question: frontier_item.question.clone(), - gap: frontier_item.gap.clone(), - target_claim_ids: frontier_item.target_claim_ids.clone(), - authorized_paths: frontier_item.repository_scope.clone(), - adopted_plan: frontier_item.adopted_plan.clone(), - selected_at: "2026-08-08T00:00:00Z".into(), - contract: epiphany_core::REPO_FRONTIER_ROUTE_CONTRACT.into(), - }; - let verification_request = epiphany_core::RepoFrontierVerificationRequest { - schema_version: epiphany_core::REPO_FRONTIER_VERIFICATION_REQUEST_SCHEMA_VERSION.into(), - request_id: "verification-request-1".into(), - route_id: route.route_id.clone(), - model_projection_digest: route.model_projection_digest.clone(), - model_source_documents: route.model_source_documents.clone(), - frontier_item_id: route.frontier_item_id.clone(), - frontier_item_hash: route.frontier_item_hash.clone(), - hands_intent_id: "hands-intent-1".into(), - hands_review_id: "hands-review-1".into(), - hands_patch_receipt_id: "hands-patch-1".into(), - hands_command_receipt_id: "hands-command-1".into(), - hands_commit_receipt_id: "hands-commit-1".into(), - requested_at: "2026-08-08T00:00:00Z".into(), - contract: epiphany_core::REPO_FRONTIER_VERIFICATION_REQUEST_CONTRACT.into(), - frontier_authority_documents: route.model_source_documents.clone(), - }; - let verification_result = EpiphanyRuntimeRoleWorkerResult { - schema_version: epiphany_core::RUNTIME_ROLE_WORKER_RESULT_SCHEMA_VERSION.into(), - result_id: "verification-result-1".into(), - job_id: "verification-job-1".into(), - role_id: "verification".into(), - verdict: "pass".into(), - summary: "verified".into(), - next_safe_move: "incorporate".into(), - checkpoint_summary: None, - scratch_summary: None, - files_inspected: Vec::new(), - frontier_node_ids: vec![frontier_item.id.clone()], - evidence_ids: Vec::new(), - artifact_refs: Vec::new(), - open_questions: Vec::new(), - evidence_gaps: Vec::new(), - risks: Vec::new(), - research_decision_msgpack: None, - item_error: None, - metadata: Default::default(), - repo_model_mutation_proposal_msgpack: None, - verification_request_id: Some(verification_request.request_id.clone()), - frontier_route_id: Some(route.route_id.clone()), - repo_frontier_modeling_request_id: None, - proposal_modeling_request_id: None, - repo_frontier_research_request_id: None, - frontier_planning_request_id: None, - frontier_plan_candidate_msgpack: None, - frontier_plan_mind_request_id: None, - frontier_plan_mind_decision_msgpack: None, - repository_body_observation_basis: None, - imagination_consideration_request_id: None, - imagination_consideration_candidate_msgpack: None, - admitted_model_direction_consideration_request_id: None, - admitted_model_direction_consideration_result_msgpack: None, - decision_context_id: "verification-context-1".into(), - }; - let soul_verdict = epiphany_core::SoulVerdictReceipt { - schema_version: epiphany_core::SOUL_VERDICT_RECEIPT_SCHEMA_VERSION.into(), - receipt_id: "soul-verdict-1".into(), - source_result_id: verification_result.result_id.clone(), - source_job_id: verification_result.job_id.clone(), - verdict: verification_result.verdict.clone(), - summary: verification_result.summary.clone(), - evidence_ids: verification_result.evidence_ids.clone(), - risks: verification_result.risks.clone(), - emitted_at: "2026-08-08T00:00:00Z".into(), - contract: "test Soul verdict".into(), - verification_request_id: verification_request.request_id.clone(), - frontier_route_id: route.route_id.clone(), - }; - authority_cache.put(&route.route_id, &route)?; - authority_cache.put(&verification_request.request_id, &verification_request)?; - authority_cache.put(&verification_result.job_id, &verification_result)?; - authority_cache.put(&soul_verdict.receipt_id, &soul_verdict)?; - let frontier_request = - epiphany_core::commit_repo_frontier_modeling_request(&store, &soul_verdict)?; - seed_test_runtime_job( + epiphany_core::initialize_keyed_repo_model( &store, - RuntimeSpineHeartbeatJobOptions { - runtime_id: "epiphany-test".to_string(), - session_id: "epiphany-main".to_string(), - objective: "Run typed worker.".to_string(), - coordinator_note: "test".to_string(), - job_id: "worker-job-1".to_string(), - role: epiphany_core::EPIPHANY_MODELING_OWNER_ROLE.to_string(), - binding_id: "modeling-checkpoint-worker".to_string(), - authority_scope: "epiphany.role.modeling".to_string(), - instruction: "Return the required role-result JSON.".to_string(), - launch_document: EpiphanyWorkerLaunchDocument::Role( - epiphany_core::EpiphanyRoleWorkerLaunchDocument { - thread_id: "thread-1".to_string(), - role_id: "modeling".to_string(), - objective: Some("Map the machine.".to_string()), - dynamic_prompt_context: None, - repository_body_observation_basis: Some(body_basis.clone()), - proposal_modeling_context: None, - frontier_verdict_modeling_context: Some( - epiphany_core::RepoFrontierVerdictModelingLaunchAuthority { - request: frontier_request.clone(), - frontier_item: frontier_item.clone(), - soul_verdict: soul_verdict.clone(), - }, - ), - frontier_planning_context: None, - frontier_research_context: None, - frontier_verification_context: None, - frontier_plan_mind_context: None, - imagination_consideration_context: None, - admitted_model_direction_consideration_context: None, - }, - ), - output_contract_id: epiphany_core::ROLE_WORKER_OUTPUT_CONTRACT_ID.to_string(), - proposal_modeling_request_id: None, - frontier_planning_request_id: None, - frontier_plan_mind_request_id: None, - imagination_consideration_request_id: None, - admitted_model_direction_consideration_request_id: None, - repo_frontier_modeling_request_id: Some(frontier_request.request_id.clone()), - repo_frontier_research_request_id: None, - repo_frontier_verification_request_id: None, - created_at: now(), - }, + &epiphany_core::EpiphanyRepoModelSeed::new( + "body-seed", + "body-graph", + "swarm-test", + "workspace-test", + body_basis.body_binding_sha256.clone(), + epiphany_core::EpiphanyRepoModelSeedDocuments { + domains: Vec::new(), + nodes: Vec::new(), + edges: Vec::new(), + frontier: Vec::new(), + }, + )?, + "2026-08-08T00:00:01Z", )?; - let launch_request = load_worker_launch_request(&store, "worker-job-1")?; + let worker_job_id = + epiphany_core::launch_current_body_modeling_work(&store, "2026-08-08T00:00:02Z")?; + let launch_request = load_worker_launch_request(&store, &worker_job_id)?; let reasoning_basis = epiphany_core::worker_reasoning_basis(&store, &launch_request)?; epiphany_core::put_reasoning_basis(&store, &reasoning_basis)?; let model_request = build_worker_model_request( @@ -3787,22 +2873,11 @@ mod tests { )?; epiphany_core::open_runtime_model_execution( &store, - RuntimeSpineSessionOptions { - session_id: "openai-worker-session-modeling-checkpoint-worker".into(), - objective: "Run the exact Modeling pass.".into(), - created_at: "2026-08-14T00:00:00Z".into(), - coordinator_note: "decision context fixture".into(), - }, RuntimeSpineJobOptions { job_id: "openai-worker-worker-job-1".into(), session_id: "openai-worker-session-modeling-checkpoint-worker".into(), - role: "openai-model-adapter".into(), - created_at: "2026-08-14T00:00:00Z".into(), - summary: "Execute the Modeling request.".into(), - artifact_refs: Vec::new(), }, &model_request, - "2026-08-14T00:00:00Z", )?; assert_eq!( model_request.output_contract_id.as_deref(), @@ -3816,7 +2891,7 @@ mod tests { assert!( output_schema_value["properties"] .get("frontierVerdictGap") - .is_some() + .is_none() ); assert!( output_schema_value["properties"] @@ -3826,12 +2901,11 @@ mod tests { assert!( output_schema_value["properties"] .get("repoModelOperations") - .is_none() + .is_some() ); - assert!(!output_schema.contains(&frontier_request.request_id)); assert!(!output_schema.contains("incorporate_frontier_verdict")); assert!(!model_request.instructions.contains("Output schema JSON")); - assert!(model_request.instructions.contains("frontierVerdictGap")); + assert!(model_request.instructions.contains("repoModelOperations")); assert!( model_request .instructions @@ -3847,16 +2921,13 @@ mod tests { let epiphany_core::EpiphanyReasoningProjection::RolePass(sealed_projection) = reasoning_basis.projection()? else { - panic!("frontier verdict Modeling must seal a role projection") + panic!("Body Modeling must seal a role projection") }; - assert_eq!( + assert!( sealed_projection .authority .frontier_verdict_modeling_context - .as_ref() - .expect("sealed frontier verdict authority") - .frontier_item, - frontier_item + .is_none() ); let sealed_body = sealed_projection .modeling_body @@ -3897,15 +2968,8 @@ mod tests { let provider_failure_store = temp.path().join("provider-failure.cc"); std::fs::copy(&store, &provider_failure_store)?; let provider_failure_summary = EpiphanyOpenAiRuntimeRunSummary { - store: provider_failure_store.display().to_string(), - session_id: "openai-worker-session-modeling-checkpoint-worker".to_string(), - job_id: "openai-worker-worker-job-1".to_string(), - request_id: model_request.request_id.clone(), - event_count: 1, verdict: "failed".to_string(), summary: "Provider refused the model pass.".to_string(), - result_id: "result-openai-worker-worker-job-1".to_string(), - receipt_id: None, tool_intent_ids: Vec::new(), }; let provider_failure = complete_worker_job_from_assistant_text( @@ -3914,8 +2978,18 @@ mod tests { &model_request.request_id, &provider_failure_summary, "", - )?; - assert_eq!(provider_failure.verdict, "failed"); + )? + .expect("provider failure outcome"); + assert_eq!( + provider_failure.failure_kind, + "provider_or_transport_failure" + ); + assert_eq!( + runtime_job(&provider_failure_store, &worker_job_id)? + .expect("provider failure worker job") + .status, + EpiphanyRuntimeJobStatus::Failed + ); assert_eq!( epiphany_core::model_pass_failure_for_request( &provider_failure_store, @@ -3926,16 +3000,14 @@ mod tests { "provider_or_transport_failure" ); assert!( - epiphany_core::runtime_role_worker_result(&provider_failure_store, "worker-job-1")? + epiphany_core::runtime_role_worker_result(&provider_failure_store, &worker_job_id)? .is_none() ); let contract_failure_store = temp.path().join("contract-failure.cc"); std::fs::copy(&store, &contract_failure_store)?; let contract_failure_summary = EpiphanyOpenAiRuntimeRunSummary { - store: contract_failure_store.display().to_string(), verdict: "pass".to_string(), - receipt_id: Some(model_request.request_id.clone()), ..provider_failure_summary.clone() }; let contract_failure = complete_worker_job_from_assistant_text( @@ -3944,8 +3016,15 @@ mod tests { &model_request.request_id, &contract_failure_summary, "not the declared structured contract", - )?; - assert_eq!(contract_failure.verdict, "failed"); + )? + .expect("contract failure outcome"); + assert_eq!(contract_failure.failure_kind, "output_contract_failure"); + assert_eq!( + runtime_job(&contract_failure_store, &worker_job_id)? + .expect("contract failure worker job") + .status, + EpiphanyRuntimeJobStatus::Failed + ); assert_eq!( epiphany_core::model_pass_failure_for_request( &contract_failure_store, @@ -3956,72 +3035,56 @@ mod tests { "output_contract_failure" ); assert!( - epiphany_core::runtime_role_worker_result(&contract_failure_store, "worker-job-1")? + epiphany_core::runtime_role_worker_result(&contract_failure_store, &worker_job_id)? .is_none() ); let openai_summary = EpiphanyOpenAiRuntimeRunSummary { - store: store.display().to_string(), - session_id: "openai-worker-session-modeling-checkpoint-worker".to_string(), - job_id: "openai-worker-worker-job-1".to_string(), - request_id: model_request.request_id.clone(), - event_count: 2, verdict: "pass".to_string(), summary: "OpenAI model request completed.".to_string(), - result_id: "result-openai-worker-worker-job-1".to_string(), - receipt_id: Some(model_request.request_id.clone()), tool_intent_ids: Vec::new(), }; let assistant_text = serde_json::json!({ "roleId": "modeling", - "verdict": "checkpoint-update-needed", + "verdict": "checkpoint-ready", "summary": "Mapped.", - "nextSafeMove": "Review the runtime-owned verdict mutation.", + "nextSafeMove": "Review the runtime-owned Body model.", "filesInspected": ["src/lib.rs"], - "frontierNodeIds": ["frontier-1"], - "evidenceIds": ["soul-verdict-1"], + "frontierNodeIds": [], + "evidenceIds": ["body-evidence"], "artifactRefs": ["artifact:model"], - "frontierVerdictGap": "Soul verified the consequence; close the exact frontier." + "repoModelOperations": [] }) .to_string(); - let result = complete_worker_job_from_assistant_text( + let completed = complete_worker_job_from_assistant_text( &store, &launch_request, &model_request.request_id, &openai_summary, &assistant_text, )?; + assert!(completed.is_none()); - assert_eq!(result.job_id, "worker-job-1"); - assert_eq!(result.verdict, "checkpoint-update-needed"); - assert_eq!(result.summary, "Mapped."); - assert_eq!( - result.next_safe_move, - "Review the runtime-owned verdict mutation." - ); let runtime_evidence_id = format!("openai-request:{}", model_request.request_id); - assert!(result.evidence_refs.contains(&runtime_evidence_id)); - let typed_result = epiphany_core::runtime_role_worker_result(&store, "worker-job-1")? + let typed_result = epiphany_core::runtime_role_worker_result(&store, &worker_job_id)? .expect("typed role worker result"); - assert_eq!(typed_result.verdict, "checkpoint-update-needed"); + assert_eq!(typed_result.verdict, "checkpoint-ready"); assert_eq!(typed_result.files_inspected, vec!["src/lib.rs".to_string()]); assert_eq!( typed_result.evidence_ids, - vec![runtime_evidence_id, "soul-verdict-1".to_string()] + vec!["body-evidence".to_string(), runtime_evidence_id] ); - assert_eq!(typed_result.artifact_refs, result.artifact_refs); assert_eq!( - typed_result - .repo_model_mutation_proposal()? - .expect("repo model mutation proposal") - .proposal_id, - "repo-model-mutation-proposal-worker-job-1" + typed_result.artifact_refs, + vec!["artifact:model".to_string()] ); + assert!(typed_result.repo_model_mutation_proposal()?.is_none()); assert!( - runtime_job_snapshot(&store, "worker-job-1")? + runtime_job(&store, &worker_job_id)? .expect("snapshot") - .result - .is_some() + .status + == EpiphanyRuntimeJobStatus::Completed, + "typed worker admission must atomically terminalize its runtime job" ); Ok(()) } @@ -4034,9 +3097,6 @@ mod tests { &store, RuntimeSpineHeartbeatJobOptions { runtime_id: "epiphany-test".to_string(), - session_id: "epiphany-main".to_string(), - objective: "Verify the machine.".to_string(), - coordinator_note: "test".to_string(), job_id: "verification-job-1".to_string(), role: "verification".to_string(), binding_id: epiphany_core::EPIPHANY_VERIFICATION_ROLE_BINDING_ID.to_string(), @@ -4063,14 +3123,6 @@ mod tests { }, ), output_contract_id: epiphany_core::ROLE_WORKER_OUTPUT_CONTRACT_ID.to_string(), - proposal_modeling_request_id: None, - frontier_planning_request_id: None, - frontier_plan_mind_request_id: None, - imagination_consideration_request_id: None, - admitted_model_direction_consideration_request_id: None, - repo_frontier_modeling_request_id: None, - repo_frontier_research_request_id: None, - repo_frontier_verification_request_id: None, created_at: now(), }, )?; @@ -4090,7 +3142,6 @@ mod tests { assert!(tool_names.contains(&"mcp__epiphany_source__read_file")); assert!(tool_names.contains(&"mcp__epiphany_source__directory_inventory")); assert!(tool_names.contains(&"mcp__epiphany_source__git_show")); - assert!(tool_names.contains(&"mcp__epiphany_source__read_hands_receipt")); assert!(tool_names.contains(&"mcp__epiphany_state__resident_grant_lifecycle")); assert!(!tool_names.contains(&"mcp__epiphany_public__github_file")); assert!( @@ -4173,53 +3224,47 @@ mod tests { "gpt-5.4", "Answer after tool output.", ); - native_request.output_schema_json = - Some(r#"{"type":"object","required":["researchDecision"]}"#.to_string()); - let request = epiphany_openai_adapter::request_from_native(&native_request); - let options = default_options(store.clone(), PathBuf::from(".codex"), &request); + native_request.output_schema_json = Some( + r#"{"type":"object","properties":{"researchDecision":{"type":"string"}},"required":["researchDecision"]}"# + .to_string(), + ); + let request = epiphany_model_adapter::request_from_native(&native_request)?; + let options = default_options(store.clone(), &request); ensure_openai_runtime_ready(&options)?; open_runtime_model_execution( &store, - RuntimeSpineSessionOptions { - session_id: options.session_id.clone(), - objective: options.objective.clone(), - created_at: now(), - coordinator_note: options.coordinator_note.clone(), - }, RuntimeSpineJobOptions { job_id: options.job_id.clone(), session_id: options.session_id.clone(), - role: OPENAI_RUNTIME_ROLE.to_string(), - created_at: now(), - summary: "tool test job".to_string(), - artifact_refs: Vec::new(), }, &native_request, - &now(), )?; - let mut receipt = EpiphanyOpenAiModelReceipt::new("req-tools", "gpt-5.4"); - receipt.response_id = Some("resp-tools".to_string()); + let mut receipt = EpiphanyModelReceipt::new("req-tools", DEFAULT_MODEL_PROVIDER, "gpt-5.4"); + receipt.provider_response_id = Some("resp-tools".to_string()); receipt.transport = Some("test".to_string()); let events = vec![ - EpiphanyOpenAiStreamEvent { - schema_id: epiphany_openai_adapter::OPENAI_ADAPTER_EVENT_SCHEMA_ID.to_string(), + EpiphanyModelStreamEvent { + schema_id: epiphany_model_adapter::MODEL_ADAPTER_EVENT_SCHEMA_ID.to_string(), request_id: "req-tools".to_string(), + provider: DEFAULT_MODEL_PROVIDER.to_string(), sequence: 0, - payload: EpiphanyOpenAiStreamPayload::ToolCall { + payload: EpiphanyModelStreamPayload::ToolCall { call_id: "call-original".to_string(), name: "mcp__smoke_server__smoke_tool".to_string(), arguments: "{}".to_string(), }, }, - EpiphanyOpenAiStreamEvent { - schema_id: epiphany_openai_adapter::OPENAI_ADAPTER_EVENT_SCHEMA_ID.to_string(), + EpiphanyModelStreamEvent { + schema_id: epiphany_model_adapter::MODEL_ADAPTER_EVENT_SCHEMA_ID.to_string(), request_id: "req-tools".to_string(), + provider: DEFAULT_MODEL_PROVIDER.to_string(), sequence: 1, - payload: EpiphanyOpenAiStreamPayload::Completed { receipt }, + payload: EpiphanyModelStreamPayload::Completed { + receipt: Box::new(receipt), + }, }, ]; - let summary = - record_openai_events(&store, &options, &request, DEFAULT_MODEL_PROVIDER, &events)?; + let summary = record_model_turn_events(&store, &options, &native_request, &events)?; let intent_id = summary .tool_intent_ids .first() @@ -4231,7 +3276,12 @@ mod tests { epiphany_core::require_runtime_tool_execution_binding(&store, &intent_id)?; assert_eq!(tool_binding.session_id, options.session_id); assert_eq!(tool_binding.job_id, options.job_id); - assert_eq!(tool_binding.model_request_id.as_deref(), Some("req-tools")); + let tool_intent = cache + .get::( + &epiphany_tool_adapter::tool_invocation_intent_key(&intent_id), + )? + .expect("tool intent"); + assert_eq!(tool_intent.model_request_id.as_deref(), Some("req-tools")); let mut tool_receipt = EpiphanyToolInvocationReceipt::new( "receipt-tool", intent_id.clone(), @@ -4251,7 +3301,9 @@ mod tests { assert_eq!(followup.previous_response_id, None); assert_eq!( followup.output_schema_json.as_deref(), - Some(r#"{"type":"object","required":["researchDecision"]}"#) + Some( + r#"{"type":"object","properties":{"researchDecision":{"type":"string"}},"required":["researchDecision"]}"# + ) ); assert_eq!(followup.input.len(), 2); assert_eq!( diff --git a/epiphany-openai-runtime/src/model_connector.rs b/epiphany-openai-runtime/src/model_connector.rs deleted file mode 100644 index f9a243f3..00000000 --- a/epiphany-openai-runtime/src/model_connector.rs +++ /dev/null @@ -1,996 +0,0 @@ -use std::collections::{HashMap, HashSet}; -use std::net::{SocketAddr, TcpStream}; -use std::path::PathBuf; -use std::sync::Arc; -use std::time::{SystemTime, UNIX_EPOCH}; - -use anyhow::{Context, Result, bail}; -use cultmesh_rs::{CultMesh, CultMeshNodeOptions, CultMeshRudpDocumentPublishOptions}; -use cultnet_rs::{ - CultNetClientSecurityOptions, CultNetMessage, CultNetRawDocumentRecord, - CultNetRawPayloadEncoding, CultNetSecret, CultNetWireContract, TcpFramedTransportConnection, - TcpFramedTransportProfileOptions, create_tcp_framed_transport_profile, - decode_cultnet_message_from_slice, encode_cultnet_message_to_vec, -}; -use epiphany_model_adapter::{ - EpiphanyModelConnectorEnvelope, EpiphanyModelConnectorInvocation, EpiphanyModelConnectorResult, - EpiphanyModelConnectorStatus, EpiphanyModelRequest, EpiphanyModelStreamEvent, - EpiphanyModelStreamPayload, MODEL_ADAPTER_EVENT_SCHEMA_ID, MODEL_ADAPTER_REQUEST_SCHEMA_ID, - MODEL_CONNECTOR_ENVELOPE_SCHEMA_ID, MODEL_CONNECTOR_INVOCATION_SCHEMA_ID, - MODEL_CONNECTOR_RESULT_SCHEMA_ID, MODEL_CONNECTOR_STATUS_SCHEMA_ID, -}; -use epiphany_openai_codex_spine::{EpiphanyCodexOpenAiTransport, auth_manager}; -use sha2::{Digest, Sha256}; -use tokio::net::TcpListener; -use tokio::sync::{Mutex, Semaphore}; - -const CONNECTOR_PROVIDER_ID: &str = "epiphany.codex-model"; -const CONNECTOR_CAPABILITY_ID: &str = "model.generate.structured"; -const CONNECTOR_STATUS_KEY: &str = "epiphany.codex-model"; -const CONNECTOR_RUNTIME_ID: &str = "epiphany-model-connector"; -const CONNECTOR_REQUEST_KIND: &str = "model_request"; -const CONNECTOR_RESULT_KIND: &str = "model_result"; -const CONNECTOR_MAX_EXPIRY_SKEW_MS: u64 = 5 * 60 * 1_000; -const CONNECTOR_MAX_OUTPUT_TOKENS: u32 = 32_768; - -cultmesh_rs::cultmesh_documents!(ModelConnectorDocuments { - EpiphanyModelConnectorStatus => MODEL_CONNECTOR_STATUS_SCHEMA_ID, -}); - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct EpiphanyModelConnectorOptions { - pub bind: SocketAddr, - pub state_path: PathBuf, - pub codex_home: PathBuf, - pub connection_key_path: PathBuf, - pub allowed_caller_runtime_id: String, - pub model: String, - pub max_parallel_requests: u32, - pub max_payload_bytes: u32, - pub odin_endpoint: Option, -} - -impl EpiphanyModelConnectorOptions { - pub fn from_env_args() -> Result { - let mut options = Self { - bind: "127.0.0.1:4103".parse()?, - state_path: PathBuf::from("state/epiphany-model-connector.cc"), - codex_home: crate::default_codex_home()?, - connection_key_path: PathBuf::new(), - allowed_caller_runtime_id: "ghostlight-dungeon-yggdrasil".to_string(), - model: "gpt-5.4".to_string(), - max_parallel_requests: 8, - max_payload_bytes: 1_048_576, - odin_endpoint: Some("127.0.0.1:17871".parse()?), - }; - let mut args = std::env::args().skip(1); - while let Some(arg) = args.next() { - match arg.as_str() { - "--bind" => options.bind = next_arg(&mut args, "--bind")?.parse()?, - "--state" => options.state_path = PathBuf::from(next_arg(&mut args, "--state")?), - "--codex-home" => { - options.codex_home = PathBuf::from(next_arg(&mut args, "--codex-home")?) - } - "--connection-key-file" => { - options.connection_key_path = - PathBuf::from(next_arg(&mut args, "--connection-key-file")?) - } - "--allowed-caller" => { - options.allowed_caller_runtime_id = next_arg(&mut args, "--allowed-caller")? - } - "--model" => options.model = next_arg(&mut args, "--model")?, - "--max-parallel" => { - options.max_parallel_requests = - next_arg(&mut args, "--max-parallel")?.parse()? - } - "--max-payload-bytes" => { - options.max_payload_bytes = - next_arg(&mut args, "--max-payload-bytes")?.parse()? - } - "--odin" => { - let endpoint = next_arg(&mut args, "--odin")?; - options.odin_endpoint = if endpoint == "none" { - None - } else { - Some(endpoint.parse()?) - }; - } - other => bail!("unknown epiphany-model-connector argument {other:?}"), - } - } - options.validate()?; - Ok(options) - } - - fn validate(&self) -> Result<()> { - if !self.bind.ip().is_loopback() { - bail!("model connector must bind to a loopback address") - } - if self.connection_key_path.as_os_str().is_empty() { - bail!("--connection-key-file is required") - } - if self.allowed_caller_runtime_id.trim().is_empty() { - bail!("--allowed-caller must be non-empty") - } - if self.model.trim().is_empty() { - bail!("--model must be non-empty") - } - if self.max_parallel_requests == 0 { - bail!("--max-parallel must be greater than zero") - } - if self.max_payload_bytes < 4_096 { - bail!("--max-payload-bytes must be at least 4096") - } - Ok(()) - } -} - -fn next_arg(args: &mut impl Iterator, name: &str) -> Result { - args.next().with_context(|| format!("missing {name} value")) -} - -#[derive(Default)] -struct ReplayCache { - active: HashSet, - completed: HashMap, -} - -#[derive(Clone)] -struct CachedResponse { - request_digest: [u8; 32], - expires_at_unix_ms: u64, - document: CultNetRawDocumentRecord, -} - -pub async fn serve_model_connector(options: EpiphanyModelConnectorOptions) -> Result<()> { - options.validate()?; - let connection_key = read_secret_file(&options.connection_key_path)?; - let security = CultNetClientSecurityOptions::new(connection_key)?; - let listener = TcpListener::bind(options.bind) - .await - .with_context(|| format!("failed to bind model connector at {}", options.bind))?; - let local_addr = listener.local_addr()?; - let auth = auth_manager(options.codex_home.clone()); - if auth.auth().await.is_none() { - bail!( - "Codex subscription authentication is unavailable at {}", - options.codex_home.display() - ) - } - publish_connector_status(&options, local_addr)?; - - let profile = create_tcp_framed_transport_profile( - CONNECTOR_RUNTIME_ID, - TcpFramedTransportProfileOptions { - host: Some(local_addr.ip().to_string()), - port: Some(local_addr.port()), - max_payload_bytes: Some(options.max_payload_bytes), - ..TcpFramedTransportProfileOptions::default() - }, - ); - let permits = Arc::new(Semaphore::new(options.max_parallel_requests as usize)); - let replay = Arc::new(Mutex::new(ReplayCache::default())); - eprintln!( - "{CONNECTOR_RUNTIME_ID} ready at {local_addr} for {} with {} concurrent calls", - options.allowed_caller_runtime_id, options.max_parallel_requests - ); - - loop { - let (stream, peer) = listener.accept().await?; - if !peer.ip().is_loopback() { - continue; - } - let stream = stream.into_std()?; - stream.set_nonblocking(false)?; - let profile = profile.clone(); - let security = security.clone(); - let permits = permits.clone(); - let replay = replay.clone(); - let auth = auth.clone(); - let allowed_caller = options.allowed_caller_runtime_id.clone(); - let model = options.model.clone(); - tokio::spawn(async move { - let _permit = match permits.acquire_owned().await { - Ok(permit) => permit, - Err(_) => return, - }; - if let Err(error) = serve_one_connection( - stream, - profile, - security, - replay, - auth, - &allowed_caller, - &model, - ) - .await - { - eprintln!("model connector request refused: {error:#}"); - } - }); - } -} - -async fn serve_one_connection( - stream: TcpStream, - profile: cultnet_rs::CultNetTransportProfile, - security: CultNetClientSecurityOptions, - replay: Arc>, - auth: Arc, - allowed_caller: &str, - model: &str, -) -> Result<()> { - let (connection, request_document) = tokio::task::spawn_blocking(move || { - let mut connection = TcpFramedTransportConnection::new(stream, profile); - let frame = connection.receive()?; - let message = decode_cultnet_message_from_slice( - &frame.payload, - CultNetWireContract::CultNetSchemaV0, - )?; - let document = request_document_from_message(message)?; - Ok::<_, anyhow::Error>((connection, document)) - }) - .await??; - - let outer_request_id = request_document.record_key.clone(); - let request_digest: [u8; 32] = Sha256::digest(&request_document.payload).into(); - match claim_request(&replay, &outer_request_id, request_digest).await { - Ok(Some(cached)) => return send_document(connection, cached).await, - Ok(None) => {} - Err(error) => { - eprintln!("model connector replay refusal for {outer_request_id}: {error:#}"); - return send_error(connection, "model connector rejected the replay").await; - } - } - - let outcome = - process_request_document(&request_document, &security, auth, allowed_caller, model).await; - let (response, expires_at) = match outcome { - Ok(value) => value, - Err(error) => { - release_request(&replay, &outer_request_id).await; - eprintln!("model connector pre-execution refusal for {outer_request_id}: {error:#}"); - return send_error(connection, "model connector rejected the invocation") - .await - .with_context(|| error.to_string()); - } - }; - complete_request( - &replay, - outer_request_id, - request_digest, - expires_at, - response.clone(), - ) - .await; - send_document(connection, response).await -} - -fn request_document_from_message(message: CultNetMessage) -> Result { - let CultNetMessage::DocumentPutRaw { - message_id, - document, - } = message - else { - bail!("model connector accepts only CultNet DocumentPutRaw invocations") - }; - if message_id != document.record_key { - bail!("model connector message id does not match the document key") - } - Ok(document) -} - -async fn claim_request( - cache: &Mutex, - request_id: &str, - request_digest: [u8; 32], -) -> Result> { - let now = unix_ms()?; - let mut cache = cache.lock().await; - cache - .completed - .retain(|_, response| response.expires_at_unix_ms >= now); - if let Some(response) = cache.completed.get(request_id) { - if response.request_digest != request_digest { - bail!("request id was reused with different encrypted cargo") - } - return Ok(Some(response.document.clone())); - } - if !cache.active.insert(request_id.to_string()) { - bail!("request is already in flight") - } - Ok(None) -} - -async fn release_request(cache: &Mutex, request_id: &str) { - cache.lock().await.active.remove(request_id); -} - -async fn complete_request( - cache: &Mutex, - request_id: String, - request_digest: [u8; 32], - expires_at_unix_ms: u64, - document: CultNetRawDocumentRecord, -) { - let mut cache = cache.lock().await; - cache.active.remove(&request_id); - cache.completed.insert( - request_id, - CachedResponse { - request_digest, - expires_at_unix_ms, - document, - }, - ); -} - -async fn process_request_document( - document: &CultNetRawDocumentRecord, - security: &CultNetClientSecurityOptions, - auth: Arc, - allowed_caller: &str, - allowed_model: &str, -) -> Result<(CultNetRawDocumentRecord, u64)> { - if document.schema_id != MODEL_CONNECTOR_ENVELOPE_SCHEMA_ID { - bail!("unexpected request envelope schema") - } - let envelope: EpiphanyModelConnectorEnvelope = - rmp_serde::from_slice(&document.payload).context("request envelope is malformed")?; - if envelope.schema_id != MODEL_CONNECTOR_ENVELOPE_SCHEMA_ID - || envelope.message_kind != CONNECTOR_REQUEST_KIND - || envelope.request_id != document.record_key - { - bail!("request envelope identity is inconsistent") - } - let plaintext = CultNetSecret::decrypt_bytes(&envelope.ciphertext, &envelope.nonce, security) - .context("request envelope authentication failed")?; - let invocation: EpiphanyModelConnectorInvocation = - rmp_serde::from_slice(&plaintext).context("request invocation is malformed")?; - let validation = validate_invocation( - &invocation, - &envelope.request_id, - allowed_caller, - allowed_model, - unix_ms()?, - ); - let result = match validation { - Ok(()) => execute_invocation(invocation.clone(), auth).await, - Err(error) => EpiphanyModelConnectorResult { - schema_id: MODEL_CONNECTOR_RESULT_SCHEMA_ID.to_string(), - request_id: invocation.request_id.clone(), - accepted: false, - events: Vec::new(), - error: Some(error.to_string()), - }, - }; - let response_envelope = encrypt_result(&result, security)?; - let response_payload = rmp_serde::to_vec_named(&response_envelope)?; - Ok(( - CultNetRawDocumentRecord { - schema_id: MODEL_CONNECTOR_ENVELOPE_SCHEMA_ID.to_string(), - record_key: invocation.request_id, - stored_at: now_utc(), - payload_encoding: CultNetRawPayloadEncoding::Messagepack, - payload: response_payload, - source_runtime_id: Some(CONNECTOR_RUNTIME_ID.to_string()), - source_agent_id: None, - source_role: Some("model-provider-connector".to_string()), - tags: Some(vec![CONNECTOR_CAPABILITY_ID.to_string()]), - }, - invocation.expires_at_unix_ms, - )) -} - -fn validate_invocation( - invocation: &EpiphanyModelConnectorInvocation, - outer_request_id: &str, - allowed_caller: &str, - allowed_model: &str, - now_unix_ms: u64, -) -> Result<()> { - if invocation.schema_id != MODEL_CONNECTOR_INVOCATION_SCHEMA_ID - || invocation.request.schema_id != MODEL_ADAPTER_REQUEST_SCHEMA_ID - { - bail!("unexpected model invocation schema") - } - if invocation.request_id != outer_request_id - || invocation.request.request_id != invocation.request_id - { - bail!("model invocation substituted request identity") - } - if invocation.caller_runtime_id != allowed_caller { - bail!("caller runtime is not admitted") - } - if invocation.expires_at_unix_ms < now_unix_ms - || invocation.expires_at_unix_ms > now_unix_ms + CONNECTOR_MAX_EXPIRY_SKEW_MS - { - bail!("model invocation is expired or exceeds the expiry horizon") - } - if invocation.request.provider != "openai-codex" { - bail!("connector admits only the openai-codex provider") - } - if invocation.request.model != allowed_model { - bail!("requested model is not admitted") - } - if invocation.request.instructions.trim().is_empty() { - bail!("model instructions must be non-empty") - } - if !invocation.request.tools.is_empty() { - bail!("shared model connector does not admit tool execution") - } - if invocation.request.previous_response_id.is_some() { - bail!("shared model connector admits stateless requests only") - } - if invocation.request.reasoning_summary.is_some() { - bail!("shared model connector does not expose reasoning summaries") - } - if matches!(invocation.request.max_output_tokens, Some(0)) - || invocation - .request - .max_output_tokens - .is_some_and(|value| value > CONNECTOR_MAX_OUTPUT_TOKENS) - { - bail!("max_output_tokens exceeds the connector bound") - } - if invocation - .request - .prompt_cache_key - .as_deref() - .is_some_and(|value| value.trim().is_empty() || value.len() > 128) - { - bail!("prompt_cache_key must contain at most 128 non-blank bytes") - } - Ok(()) -} - -async fn execute_invocation( - invocation: EpiphanyModelConnectorInvocation, - auth: Arc, -) -> EpiphanyModelConnectorResult { - let provider_request = epiphany_openai_adapter::request_from_native(&invocation.request); - let transport = EpiphanyCodexOpenAiTransport::openai(auth); - let events = match transport.collect_model_events(provider_request).await { - Ok(events) => sanitize_model_events( - &invocation.request, - events - .iter() - .map(|event| crate::model_event_from_openai_event("openai-codex", event)) - .collect(), - ), - Err(error) => { - eprintln!( - "model connector provider failure for {}: {error:#}", - invocation.request_id - ); - vec![EpiphanyModelStreamEvent { - schema_id: MODEL_ADAPTER_EVENT_SCHEMA_ID.to_string(), - request_id: invocation.request_id.clone(), - provider: "openai-codex".to_string(), - sequence: 0, - payload: EpiphanyModelStreamPayload::Failed { - message: "model provider transport failed".to_string(), - }, - }] - } - }; - EpiphanyModelConnectorResult { - schema_id: MODEL_CONNECTOR_RESULT_SCHEMA_ID.to_string(), - request_id: invocation.request_id, - accepted: true, - events, - error: None, - } -} - -fn sanitize_model_events( - request: &EpiphanyModelRequest, - events: Vec, -) -> Vec { - let mut sanitized = Vec::new(); - for event in events { - if event.request_id != request.request_id || event.provider != request.provider { - return vec![connector_failed_event( - &request.request_id, - "model provider substituted event identity", - )]; - } - match event.payload { - EpiphanyModelStreamPayload::ReasoningDelta { .. } => continue, - EpiphanyModelStreamPayload::ToolCall { .. } => { - return vec![connector_failed_event( - &request.request_id, - "model provider returned an inadmissible tool call", - )]; - } - EpiphanyModelStreamPayload::Completed { receipt } => { - if let Some(limit) = request.max_output_tokens - && !receipt_proves_public_output_bound(&receipt, limit) - { - return vec![connector_failed_event( - &request.request_id, - "model provider did not prove the requested output-token bound", - )]; - } - sanitized.push(EpiphanyModelStreamEvent { - schema_id: MODEL_ADAPTER_EVENT_SCHEMA_ID.to_string(), - request_id: request.request_id.clone(), - provider: request.provider.clone(), - sequence: sanitized.len() as u64, - payload: EpiphanyModelStreamPayload::Completed { receipt }, - }); - } - payload => sanitized.push(EpiphanyModelStreamEvent { - schema_id: MODEL_ADAPTER_EVENT_SCHEMA_ID.to_string(), - request_id: request.request_id.clone(), - provider: request.provider.clone(), - sequence: sanitized.len() as u64, - payload, - }), - } - } - if sanitized.is_empty() { - sanitized.push(connector_failed_event( - &request.request_id, - "model provider returned no public events", - )); - } - sanitized -} - -fn receipt_proves_public_output_bound( - receipt: &epiphany_model_adapter::EpiphanyModelReceipt, - limit: u32, -) -> bool { - let Some(total_output_tokens) = receipt.output_tokens else { - return false; - }; - let public_output_tokens = match receipt.reasoning_output_tokens { - Some(reasoning_tokens) if reasoning_tokens <= total_output_tokens => { - total_output_tokens - reasoning_tokens - } - Some(_) => return false, - None => total_output_tokens, - }; - public_output_tokens <= u64::from(limit) -} - -fn connector_failed_event(request_id: &str, message: &str) -> EpiphanyModelStreamEvent { - EpiphanyModelStreamEvent { - schema_id: MODEL_ADAPTER_EVENT_SCHEMA_ID.to_string(), - request_id: request_id.to_string(), - provider: "openai-codex".to_string(), - sequence: 0, - payload: EpiphanyModelStreamPayload::Failed { - message: message.to_string(), - }, - } -} - -pub fn encrypt_invocation( - invocation: &EpiphanyModelConnectorInvocation, - security: &CultNetClientSecurityOptions, -) -> Result { - let nonce = CultNetSecret::new_nonce(); - let plaintext = rmp_serde::to_vec_named(invocation)?; - Ok(EpiphanyModelConnectorEnvelope { - schema_id: MODEL_CONNECTOR_ENVELOPE_SCHEMA_ID.to_string(), - request_id: invocation.request_id.clone(), - message_kind: CONNECTOR_REQUEST_KIND.to_string(), - ciphertext: CultNetSecret::encrypt_bytes(&plaintext, &nonce, security)?, - nonce: nonce.to_vec(), - }) -} - -fn encrypt_result( - result: &EpiphanyModelConnectorResult, - security: &CultNetClientSecurityOptions, -) -> Result { - let nonce = CultNetSecret::new_nonce(); - let plaintext = rmp_serde::to_vec_named(result)?; - Ok(EpiphanyModelConnectorEnvelope { - schema_id: MODEL_CONNECTOR_ENVELOPE_SCHEMA_ID.to_string(), - request_id: result.request_id.clone(), - message_kind: CONNECTOR_RESULT_KIND.to_string(), - ciphertext: CultNetSecret::encrypt_bytes(&plaintext, &nonce, security)?, - nonce: nonce.to_vec(), - }) -} - -pub fn decrypt_result( - envelope: &EpiphanyModelConnectorEnvelope, - security: &CultNetClientSecurityOptions, -) -> Result { - if envelope.schema_id != MODEL_CONNECTOR_ENVELOPE_SCHEMA_ID - || envelope.message_kind != CONNECTOR_RESULT_KIND - { - bail!("unexpected connector result envelope") - } - let plaintext = CultNetSecret::decrypt_bytes(&envelope.ciphertext, &envelope.nonce, security)?; - let result: EpiphanyModelConnectorResult = rmp_serde::from_slice(&plaintext)?; - if result.schema_id != MODEL_CONNECTOR_RESULT_SCHEMA_ID - || result.request_id != envelope.request_id - { - bail!("connector result substituted request identity") - } - Ok(result) -} - -async fn send_document( - connection: TcpFramedTransportConnection, - document: CultNetRawDocumentRecord, -) -> Result<()> { - let message = CultNetMessage::SnapshotResponseRaw { - message_id: document.record_key.clone(), - documents: vec![document], - }; - let payload = encode_cultnet_message_to_vec(&message, CultNetWireContract::CultNetSchemaV0)?; - tokio::task::spawn_blocking(move || { - let mut connection = connection; - connection.send("schema", &payload) - }) - .await??; - Ok(()) -} - -async fn send_error( - connection: TcpFramedTransportConnection, - message: &str, -) -> Result<()> { - let payload = encode_cultnet_message_to_vec( - &CultNetMessage::Error { - error: message.to_string(), - }, - CultNetWireContract::CultNetSchemaV0, - )?; - tokio::task::spawn_blocking(move || { - let mut connection = connection; - connection.send("schema", &payload) - }) - .await??; - Ok(()) -} - -fn publish_connector_status( - options: &EpiphanyModelConnectorOptions, - bind: SocketAddr, -) -> Result<()> { - if let Some(parent) = options.state_path.parent() { - std::fs::create_dir_all(parent)?; - } - let status = EpiphanyModelConnectorStatus { - schema_id: MODEL_CONNECTOR_STATUS_SCHEMA_ID.to_string(), - provider_id: CONNECTOR_PROVIDER_ID.to_string(), - capability_id: CONNECTOR_CAPABILITY_ID.to_string(), - request_schema_id: MODEL_CONNECTOR_INVOCATION_SCHEMA_ID.to_string(), - result_schema_id: MODEL_CONNECTOR_RESULT_SCHEMA_ID.to_string(), - envelope_schema_id: MODEL_CONNECTOR_ENVELOPE_SCHEMA_ID.to_string(), - transport_protocol: "cultnet.tcp_framed.v0".to_string(), - host: bind.ip().to_string(), - port: bind.port(), - max_payload_bytes: options.max_payload_bytes, - max_parallel_requests: options.max_parallel_requests, - model: options.model.clone(), - ready: true, - updated_at_utc: now_utc(), - }; - let mut node = CultMesh::create_node( - &options.state_path, - ModelConnectorDocuments, - CultMeshNodeOptions { - runtime_id: CONNECTOR_RUNTIME_ID.to_string(), - pull_on_start: true, - }, - )?; - node.put(CONNECTOR_STATUS_KEY, &status)?; - node.flush()?; - if let Some(target) = options.odin_endpoint { - if let Err(error) = node.publish_document_to_rudp_catalog( - CONNECTOR_STATUS_KEY, - &status, - CultMeshRudpDocumentPublishOptions::odin(target, CONNECTOR_RUNTIME_ID), - ) { - eprintln!("model connector Odin publication failed: {error:#}"); - } - } - Ok(()) -} - -fn read_secret_file(path: &PathBuf) -> Result { - let raw = std::fs::read_to_string(path) - .with_context(|| format!("failed to read connector key {}", path.display()))?; - let key = raw.trim(); - if key.is_empty() || raw.trim_matches(['\r', '\n']) != key { - bail!("connector key file is empty or contains surrounding whitespace") - } - Ok(key.to_string()) -} - -fn unix_ms() -> Result { - Ok(SystemTime::now() - .duration_since(UNIX_EPOCH) - .context("system clock predates Unix epoch")? - .as_millis() - .try_into() - .context("system clock does not fit u64 milliseconds")?) -} - -fn now_utc() -> String { - chrono::Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Millis, true) -} - -#[cfg(test)] -mod tests { - use super::*; - use epiphany_model_adapter::EpiphanyModelReceipt; - - fn invocation(now: u64) -> EpiphanyModelConnectorInvocation { - let mut request = EpiphanyModelRequest::new( - "request-1", - "conversation-1", - "openai-codex", - "gpt-5.4", - "Return one bounded answer.", - ); - request.max_output_tokens = Some(512); - EpiphanyModelConnectorInvocation { - schema_id: MODEL_CONNECTOR_INVOCATION_SCHEMA_ID.to_string(), - request_id: request.request_id.clone(), - caller_runtime_id: "ghostlight-dungeon-yggdrasil".to_string(), - expires_at_unix_ms: now + 60_000, - request, - } - } - - #[test] - fn encrypted_connector_cargo_round_trips_without_exposing_plaintext() -> Result<()> { - let security = CultNetClientSecurityOptions::new("not-a-production-secret")?; - let invocation = invocation(1_000); - let envelope = encrypt_invocation(&invocation, &security)?; - assert!( - !envelope - .ciphertext - .windows(10) - .any(|part| part == b"conversation") - ); - let plaintext = - CultNetSecret::decrypt_bytes(&envelope.ciphertext, &envelope.nonce, &security)?; - let decoded: EpiphanyModelConnectorInvocation = rmp_serde::from_slice(&plaintext)?; - assert_eq!(decoded, invocation); - Ok(()) - } - - #[test] - fn connector_rejects_wrong_caller_expiry_tools_and_output_overrun() { - let now = 1_000; - let mut value = invocation(now); - assert!( - validate_invocation( - &value, - "request-1", - "ghostlight-dungeon-yggdrasil", - "gpt-5.4", - now, - ) - .is_ok() - ); - value.caller_runtime_id = "foreign-runtime".to_string(); - assert!( - validate_invocation( - &value, - "request-1", - "ghostlight-dungeon-yggdrasil", - "gpt-5.4", - now, - ) - .is_err() - ); - value = invocation(now); - value.expires_at_unix_ms = now - 1; - assert!( - validate_invocation( - &value, - "request-1", - "ghostlight-dungeon-yggdrasil", - "gpt-5.4", - now, - ) - .is_err() - ); - value = invocation(now); - value.request.max_output_tokens = Some(CONNECTOR_MAX_OUTPUT_TOKENS + 1); - assert!( - validate_invocation( - &value, - "request-1", - "ghostlight-dungeon-yggdrasil", - "gpt-5.4", - now, - ) - .is_err() - ); - value = invocation(now); - value.request.model = "another-model".to_string(); - assert!( - validate_invocation( - &value, - "request-1", - "ghostlight-dungeon-yggdrasil", - "gpt-5.4", - now, - ) - .is_err() - ); - value = invocation(now); - value - .request - .tools - .push(epiphany_model_adapter::EpiphanyModelToolDefinition { - name: "forbidden".to_string(), - description: "must not execute".to_string(), - parameters_json: "{}".to_string(), - }); - assert!( - validate_invocation( - &value, - "request-1", - "ghostlight-dungeon-yggdrasil", - "gpt-5.4", - now, - ) - .is_err() - ); - value = invocation(now); - value.request.previous_response_id = Some("provider-state".to_string()); - assert!( - validate_invocation( - &value, - "request-1", - "ghostlight-dungeon-yggdrasil", - "gpt-5.4", - now, - ) - .is_err() - ); - value = invocation(now); - value.request.reasoning_summary = Some("brief".to_string()); - assert!( - validate_invocation( - &value, - "request-1", - "ghostlight-dungeon-yggdrasil", - "gpt-5.4", - now, - ) - .is_err() - ); - } - - #[test] - fn connector_refuses_outer_message_and_document_identity_split() { - let document = CultNetRawDocumentRecord { - schema_id: MODEL_CONNECTOR_ENVELOPE_SCHEMA_ID.to_string(), - record_key: "request-1".to_string(), - stored_at: now_utc(), - payload_encoding: CultNetRawPayloadEncoding::Messagepack, - payload: Vec::new(), - source_runtime_id: None, - source_agent_id: None, - source_role: None, - tags: None, - }; - assert!( - request_document_from_message(CultNetMessage::DocumentPutRaw { - message_id: "request-2".to_string(), - document, - }) - .is_err() - ); - } - - #[test] - fn reasoning_is_removed_and_public_events_are_renumbered() { - let invocation = invocation(1_000); - let mut receipt = EpiphanyModelReceipt::new("request-1", "openai-codex", "gpt-5.4"); - receipt.output_tokens = Some(10); - let events = vec![ - EpiphanyModelStreamEvent { - schema_id: MODEL_ADAPTER_EVENT_SCHEMA_ID.to_string(), - request_id: "request-1".to_string(), - provider: "openai-codex".to_string(), - sequence: 0, - payload: EpiphanyModelStreamPayload::ReasoningDelta { - text: "private".to_string(), - }, - }, - EpiphanyModelStreamEvent { - schema_id: MODEL_ADAPTER_EVENT_SCHEMA_ID.to_string(), - request_id: "request-1".to_string(), - provider: "openai-codex".to_string(), - sequence: 1, - payload: EpiphanyModelStreamPayload::TextDelta { - text: "public".to_string(), - }, - }, - EpiphanyModelStreamEvent { - schema_id: MODEL_ADAPTER_EVENT_SCHEMA_ID.to_string(), - request_id: "request-1".to_string(), - provider: "openai-codex".to_string(), - sequence: 2, - payload: EpiphanyModelStreamPayload::Completed { receipt }, - }, - ]; - let events = sanitize_model_events(&invocation.request, events); - assert_eq!(events.len(), 2); - assert_eq!(events[0].sequence, 0); - assert_eq!(events[1].sequence, 1); - assert!(matches!( - events[0].payload, - EpiphanyModelStreamPayload::TextDelta { .. } - )); - assert!(matches!( - events[1].payload, - EpiphanyModelStreamPayload::Completed { .. } - )); - } - - #[test] - fn connector_refuses_unproven_or_excessive_provider_output() { - let invocation = invocation(1_000); - let event = |output_tokens| EpiphanyModelStreamEvent { - schema_id: MODEL_ADAPTER_EVENT_SCHEMA_ID.to_string(), - request_id: "request-1".to_string(), - provider: "openai-codex".to_string(), - sequence: 0, - payload: EpiphanyModelStreamPayload::Completed { - receipt: { - let mut receipt = - EpiphanyModelReceipt::new("request-1", "openai-codex", "gpt-5.4"); - receipt.output_tokens = output_tokens; - receipt - }, - }, - }; - - for output_tokens in [None, Some(513)] { - let events = sanitize_model_events(&invocation.request, vec![event(output_tokens)]); - assert!(matches!( - events.as_slice(), - [EpiphanyModelStreamEvent { - payload: EpiphanyModelStreamPayload::Failed { .. }, - .. - }] - )); - } - } - - #[test] - fn connector_bounds_public_output_without_exposing_hidden_reasoning() { - let mut receipt = EpiphanyModelReceipt::new("request-1", "openai-codex", "gpt-5.4"); - receipt.output_tokens = Some(700); - receipt.reasoning_output_tokens = Some(300); - assert!(receipt_proves_public_output_bound(&receipt, 512)); - - receipt.reasoning_output_tokens = None; - assert!(!receipt_proves_public_output_bound(&receipt, 512)); - - receipt.reasoning_output_tokens = Some(701); - assert!(!receipt_proves_public_output_bound(&receipt, 512)); - } - - #[test] - fn encrypted_result_refuses_outer_identity_substitution() -> Result<()> { - let security = CultNetClientSecurityOptions::new("not-a-production-secret")?; - let result = EpiphanyModelConnectorResult { - schema_id: MODEL_CONNECTOR_RESULT_SCHEMA_ID.to_string(), - request_id: "request-1".to_string(), - accepted: true, - events: Vec::new(), - error: None, - }; - let mut envelope = encrypt_result(&result, &security)?; - envelope.request_id = "request-2".to_string(); - assert!(decrypt_result(&envelope, &security).is_err()); - Ok(()) - } -} diff --git a/epiphany-openai-runtime/src/persona_executor.rs b/epiphany-openai-runtime/src/persona_executor.rs index bcb707ff..5a5192aa 100644 --- a/epiphany-openai-runtime/src/persona_executor.rs +++ b/epiphany-openai-runtime/src/persona_executor.rs @@ -1,7 +1,6 @@ use std::future::Future; use std::path::{Path, PathBuf}; use std::pin::Pin; -use std::time::Duration; use anyhow::{Context, Result, anyhow}; use chrono::SecondsFormat; @@ -11,18 +10,14 @@ use epiphany_core::{ PERSONA_MODEL_STAGE_RECEIPT_SCHEMA_VERSION, PERSONA_MODEL_TERMINAL_RECEIPT_SCHEMA_VERSION, PersonaInterpreterEffectDocument, PersonaInterpreterInput, PersonaModelStageReceipt, PersonaModelTerminalReceipt, PersonaProjectorInput, PersonaTranscriptMessage, PersonaTurnInput, - RuntimeSpineSessionClosureOptions, build_persona_interpreter_prompt, - build_persona_projector_prompt_with_transcript, build_persona_turn_prompt, - close_runtime_session, model_pass_failure_for_request, + build_persona_interpreter_prompt, build_persona_projector_prompt_with_transcript, + build_persona_turn_prompt, close_runtime_session, model_pass_failure_for_request, parse_and_validate_persona_interpreter_effect_set, persona_interpreter_effect_set_json_schema, - runtime_spine_cache, - terminalize_model_pass_failure_session, + runtime_spine_cache, terminalize_model_pass_failure_session, }; use epiphany_model_adapter::{EpiphanyModelInputItem, EpiphanyModelRequest}; use sha2::{Digest, Sha256}; -use crate::{EpiphanyOpenAiRuntimeOptions, assistant_text_from_model_events, run_model_turn}; - #[derive(Clone, Debug)] pub struct PersonaModelExecutionPlan { turn_id: String, @@ -62,16 +57,6 @@ impl PersonaModelExecutionPlan { } } -#[derive(Clone, Debug)] -pub struct NativePersonaModelRunner { - pub store_path: PathBuf, - pub codex_home: PathBuf, - pub provider_credential_path: Option, - pub provider: String, - pub model: String, - pub turn_timeout: Duration, -} - pub trait PersonaModelRunner { fn run<'a>( &'a mut self, @@ -83,97 +68,23 @@ pub trait PersonaModelRunner { fn recover(&mut self, request_id: &str) -> Result; } -impl PersonaModelRunner for NativePersonaModelRunner { - fn run<'a>( - &'a mut self, - _store_path: &'a PathBuf, - stage: &'a str, - turn_id: &'a str, - request: EpiphanyModelRequest, - ) -> Pin> + Send + 'a>> { - Box::pin(async move { - let replay = assistant_text_from_model_events(&self.store_path, &request.request_id)?; - if !replay.trim().is_empty() { - return Ok(replay); - } - let options = EpiphanyOpenAiRuntimeOptions { - store_path: self.store_path.clone(), - codex_home: self.codex_home.clone(), - provider_credential_path: self.provider_credential_path.clone(), - session_id: format!("persona-turn-{turn_id}"), - job_id: format!("persona-{stage}-{turn_id}"), - objective: format!("Run Persona {stage} stage for {turn_id}"), - coordinator_note: "Native Persona model executor; transport owns inference only." - .to_string(), - default_model: Some(self.model.clone()), - // Persona owns one outer typed turn budget. A second transport - // timer would give timeout policy two conflicting owners. - request_timeout: None, - }; - let summary = tokio::time::timeout( - self.turn_timeout, - run_model_turn(&self.provider, options.clone(), request.clone()), - ) - .await - .map_err(|_| { - anyhow!( - "Persona {stage} stage exceeded its {} second outer turn budget", - self.turn_timeout.as_secs() - ) - })??; - let output = assistant_text_from_model_events(&self.store_path, &request.request_id)?; - if summary.verdict != "pass" || output.trim().is_empty() { - let failure_summary = if summary.verdict != "pass" { - summary.summary.clone() - } else { - format!("Persona {stage} stage completed without assistant text") - }; - return Err(anyhow!(failure_summary)); - } - Ok(output) - }) - } - - fn recover(&mut self, request_id: &str) -> Result { - assistant_text_from_model_events(&self.store_path, request_id) - } -} - struct CompletedPersonaStage { receipt: PersonaModelStageReceipt, output: String, } -pub async fn execute_persona_model_turn( - plan: &PersonaModelExecutionPlan, - runner: &mut NativePersonaModelRunner, -) -> Result { - if runner.provider != plan.provider || runner.model != plan.model { - return Err(anyhow!("Persona execution plan and model runner disagree")); - } - let store_path = runner.store_path.clone(); - execute_persona_model_turn_owned(&store_path, plan, runner).await -} - -async fn execute_persona_model_turn_owned( +pub async fn execute_persona_model_turn( store_path: &PathBuf, plan: &PersonaModelExecutionPlan, runner: &mut R, ) -> Result { match execute_persona_model_turn_with_runner(store_path, plan, runner).await { Ok(terminal) => { - close_persona_session_if_active( - store_path, - plan, - format!( - "Persona turn {} reached terminal decision {}.", - plan.turn_id, terminal.receipt_id - ), - )?; + close_persona_session_if_active(store_path, plan)?; Ok(terminal) } Err(error) => { - terminalize_persona_execution_error(store_path, plan, &error)?; + terminalize_persona_execution_error(store_path, plan)?; Err(error) } } @@ -182,7 +93,6 @@ async fn execute_persona_model_turn_owned( fn terminalize_persona_execution_error( store_path: &Path, plan: &PersonaModelExecutionPlan, - error: &anyhow::Error, ) -> Result<()> { for stage in ["interpreter", "persona", "projector"] { let request_id = stage_request_id(&plan.turn_id, stage); @@ -190,17 +100,12 @@ fn terminalize_persona_execution_error( return Ok(()); } } - close_persona_session_if_active( - store_path, - plan, - format!("Persona turn orchestration refused: {error:#}"), - ) + close_persona_session_if_active(store_path, plan) } fn close_persona_session_if_active( store_path: &Path, plan: &PersonaModelExecutionPlan, - summary: String, ) -> Result<()> { let session_id = format!("persona-turn-{}", plan.turn_id); let mut cache = runtime_spine_cache(store_path)?; @@ -211,14 +116,7 @@ fn close_persona_session_if_active( session.status == epiphany_core::EpiphanyRuntimeSessionStatus::Active }) { - close_runtime_session( - store_path, - RuntimeSpineSessionClosureOptions { - session_id, - completed_at: now(), - summary, - }, - )?; + close_runtime_session(store_path, &session_id)?; } Ok(()) } @@ -692,22 +590,11 @@ mod tests { } epiphany_core::open_runtime_model_execution( store_path, - epiphany_core::RuntimeSpineSessionOptions { - session_id: format!("persona-turn-{turn_id}"), - objective: format!("Run Persona {stage} test stage"), - created_at: "2026-08-14T00:00:00Z".into(), - coordinator_note: "Persona test".into(), - }, epiphany_core::RuntimeSpineJobOptions { job_id: format!("persona-{stage}-{turn_id}"), session_id: format!("persona-turn-{turn_id}"), - role: "openai-model-adapter".into(), - created_at: "2026-08-14T00:00:00Z".into(), - summary: format!("Run Persona {stage} test stage"), - artifact_refs: Vec::new(), }, &request, - "2026-08-14T00:00:00Z", )?; let output = match stage { "projector" => "Epiphany feels the direct call as a live obligation, while remaining tired and sharply curious about her own machinery.".to_string(), @@ -715,19 +602,12 @@ mod tests { "interpreter" => r#"{"schemaVersion":"epiphany.persona_interpreter_effect_set.v0","effects":[{"kind":"say","channel_id":"aquarium","reply_to_message_id":"m1","content":"The native conversational nerve is being connected, and I can show the receipts.","speech_act":"status","register":"concise","target_audience":"the addressed Discord room","safety_notes":[]}]}"#.to_string(), _ => unreachable!(), }; - epiphany_core::complete_runtime_job( + epiphany_core::terminalize_runtime_job( store_path, - epiphany_core::RuntimeSpineJobResultOptions { - result_id: format!("persona-{stage}-{turn_id}-transport-result"), - job_id: format!("persona-{stage}-{turn_id}"), - completed_at: "2026-08-14T00:00:01Z".into(), - verdict: "completed".into(), - summary: format!("Persona {stage} test transport completed"), - next_safe_move: "Seal the stage decision context".into(), - evidence_refs: Vec::new(), - artifact_refs: Vec::new(), - decision_context_id: None, - }, + &format!("persona-{stage}-{turn_id}"), + epiphany_core::EpiphanyRuntimeJobStatus::Completed, + "2026-08-14T00:00:01Z", + &request.request_id, )?; Ok(output) }) @@ -807,7 +687,7 @@ mod tests { PersonaModelExecutionPlan::from_admitted_input( store, "turn-1", - "test", + crate::DEFAULT_MODEL_PROVIDER, "test-model", cultmesh_store, "epiphany-test", @@ -831,7 +711,7 @@ mod tests { release_brake(&cultmesh)?; let mut runner = FakeRunner { calls: vec![] }; let first = - execute_persona_model_turn_owned(&store, &plan(&store, cultmesh.clone())?, &mut runner) + execute_persona_model_turn(&store, &plan(&store, cultmesh.clone())?, &mut runner) .await?; assert_eq!(runner.calls, ["projector", "persona", "interpreter"]); drop(runner); @@ -840,8 +720,7 @@ mod tests { let admitted_source = restarted_plan.source_documents[0].clone(); let mut restarted_runner = FakeRunner { calls: vec![] }; let second = - execute_persona_model_turn_owned(&store, &restarted_plan, &mut restarted_runner) - .await?; + execute_persona_model_turn(&store, &restarted_plan, &mut restarted_runner).await?; assert_eq!(first, second); assert!(restarted_runner.calls.is_empty()); for receipt_id in &second.stage_receipt_ids { @@ -879,7 +758,7 @@ mod tests { let mut runner = FakeRunner { calls: vec![] }; let escaped = plan_with_channels(&store, cultmesh, vec!["elsewhere".into()])?; assert!( - execute_persona_model_turn_owned(&store, &escaped, &mut runner) + execute_persona_model_turn(&store, &escaped, &mut runner) .await .is_err() ); diff --git a/epiphany-openai-runtime/src/provider_transport.rs b/epiphany-openai-runtime/src/provider_transport.rs new file mode 100644 index 00000000..68d32d13 --- /dev/null +++ b/epiphany-openai-runtime/src/provider_transport.rs @@ -0,0 +1,308 @@ +use std::io::Read; +use std::path::Path; +use std::time::{Duration, SystemTime, UNIX_EPOCH}; + +use anyhow::{Context, Result, anyhow}; +use codex_connector::{ + CodexConnectorClient, CodexProviderRequest, CodexTransportDisposition, + CodexTransportEventPayload, CodexTransportInvocation, CodexTransportOutcome, +}; +use epiphany_model_adapter::{ + EpiphanyModelReceipt, EpiphanyModelRequest, EpiphanyModelStreamEvent, + EpiphanyModelStreamPayload, MODEL_ADAPTER_EVENT_SCHEMA_ID, +}; +use epiphany_model_adapter::{ + EpiphanyOpenRouterRequest, EpiphanyProviderRequestPayload, openrouter_events_from_response, + openrouter_request_body, +}; +use epiphany_openai_runtime::{ + EpiphanyOpenAiRuntimeOptions, EpiphanyOpenAiRuntimeRunSummary, OPENROUTER_MODEL_PROVIDER, + open_model_turn, record_model_turn_events, +}; +use sha2::{Digest, Sha256}; +use zeroize::Zeroizing; + +const OPENROUTER_COMPLETIONS_URL: &str = "https://openrouter.ai/api/v1/chat/completions"; +const MAX_CONNECTOR_FRAME_BYTES: usize = 32 * 1024 * 1024; +const MAX_OPENROUTER_RESPONSE_BYTES: u64 = 16 * 1024 * 1024; +const INVOCATION_ADMISSION_WINDOW: Duration = Duration::from_secs(60); + +pub async fn run_model_turn( + provider: &str, + options: EpiphanyOpenAiRuntimeOptions, + request: EpiphanyModelRequest, +) -> Result { + let provider_request = open_model_turn(provider, &options, &request)?; + let request_id = request.request_id.clone(); + let events = match (provider, provider_request.payload) { + ("openai-codex" | "openai", EpiphanyProviderRequestPayload::Codex(codex_request)) => { + collect_transport_events( + execute_codex_connector(&options, &request, codex_request), + &request_id, + provider, + ) + .await + } + (OPENROUTER_MODEL_PROVIDER, EpiphanyProviderRequestPayload::OpenRouter(request)) => { + collect_transport_events(execute_openrouter(&options, request), &request_id, provider) + .await + } + _ => { + return Err(anyhow!( + "provider request contract disagrees with selected provider" + )); + } + }; + record_model_turn_events(&options.store_path, &options, &request, &events) +} + +async fn execute_codex_connector( + options: &EpiphanyOpenAiRuntimeOptions, + native_request: &EpiphanyModelRequest, + provider_request: CodexProviderRequest, +) -> Result> { + let endpoint = options + .connector_endpoint + .ok_or_else(|| anyhow!("Codex provider requires an explicit connector endpoint"))?; + let caller_runtime_id = options.caller_runtime_id.clone(); + if caller_runtime_id.trim().is_empty() || caller_runtime_id.trim() != caller_runtime_id { + return Err(anyhow!( + "Codex connector caller runtime identity is invalid" + )); + } + let connection_key = Zeroizing::new(read_static_provider_credential( + options.provider_credential_path.as_deref(), + "codex-connector", + )?); + let native_request_sha256: [u8; 32] = Sha256::digest(rmp_serde::to_vec(native_request)?).into(); + let expires_at_unix_ms = unix_time_ms()? + .checked_add(INVOCATION_ADMISSION_WINDOW.as_millis() as u64) + .ok_or_else(|| anyhow!("Codex connector invocation expiry overflowed"))?; + let invocation = CodexTransportInvocation::new( + caller_runtime_id, + expires_at_unix_ms, + native_request_sha256, + provider_request, + )?; + let client = CodexConnectorClient::new( + endpoint, + connection_key.as_str().to_string(), + MAX_CONNECTOR_FRAME_BYTES, + options.request_timeout, + )?; + let provider = native_request.provider.clone(); + tokio::task::spawn_blocking(move || { + let result = client.execute(&invocation)?; + events_from_connector_result(&provider, result) + }) + .await + .context("Codex connector client task failed")? +} + +pub(super) fn events_from_connector_result( + provider: &str, + result: codex_connector::CodexTransportResult, +) -> Result> { + let request_id = result.request_id.clone(); + match result.disposition { + CodexTransportDisposition::Refused(reason) => Err(anyhow!( + "Codex connector refused request {request_id:?}: {reason:?}" + )), + CodexTransportDisposition::Transported { events, receipt } => { + let mut projected = events + .into_iter() + .map(|event| EpiphanyModelStreamEvent { + schema_id: MODEL_ADAPTER_EVENT_SCHEMA_ID.to_string(), + request_id: request_id.clone(), + provider: provider.to_string(), + sequence: event.sequence, + payload: match event.payload { + CodexTransportEventPayload::TextDelta { text } => { + EpiphanyModelStreamPayload::TextDelta { text } + } + CodexTransportEventPayload::ToolCall { + call_id, + name, + arguments, + } => EpiphanyModelStreamPayload::ToolCall { + call_id, + name, + arguments, + }, + }, + }) + .collect::>(); + let mut terminal = EpiphanyModelReceipt::new(&request_id, provider, &receipt.model); + terminal.transport = Some(receipt.transport.clone()); + terminal.caller_runtime_id = Some(receipt.caller_runtime_id.clone()); + terminal.native_request_sha256 = Some(hex_digest(receipt.native_request_sha256)); + terminal.provider_request_sha256 = Some(hex_digest(receipt.provider_request_sha256)); + match receipt.outcome { + CodexTransportOutcome::Completed { + provider_response_id, + input_tokens, + output_tokens, + reasoning_output_tokens, + cached_input_tokens, + } => { + terminal.provider_response_id = provider_response_id; + terminal.input_tokens = input_tokens; + terminal.output_tokens = output_tokens; + terminal.reasoning_output_tokens = reasoning_output_tokens; + terminal.cached_input_tokens = cached_input_tokens; + projected.push(EpiphanyModelStreamEvent { + schema_id: MODEL_ADAPTER_EVENT_SCHEMA_ID.to_string(), + request_id, + provider: provider.to_string(), + sequence: projected.len() as u64, + payload: EpiphanyModelStreamPayload::Completed { + receipt: Box::new(terminal), + }, + }); + } + CodexTransportOutcome::Failed { + failure_kind, + message, + } => { + projected.push(EpiphanyModelStreamEvent { + schema_id: MODEL_ADAPTER_EVENT_SCHEMA_ID.to_string(), + request_id, + provider: provider.to_string(), + sequence: projected.len() as u64, + payload: EpiphanyModelStreamPayload::Failed { + message: format!("Codex connector {failure_kind}: {message}"), + }, + }); + } + } + Ok(projected) + } + } +} + +async fn execute_openrouter( + options: &EpiphanyOpenAiRuntimeOptions, + request: EpiphanyOpenRouterRequest, +) -> Result> { + let api_key = Zeroizing::new(read_static_provider_credential( + options.provider_credential_path.as_deref(), + OPENROUTER_MODEL_PROVIDER, + )?); + let request_timeout = options.request_timeout; + tokio::task::spawn_blocking(move || { + let body = serde_json::to_vec(&openrouter_request_body(&request)?)?; + let agent: ureq::Agent = ureq::Agent::config_builder() + .timeout_connect(Some(Duration::from_secs(10))) + .timeout_recv_response(request_timeout) + .timeout_recv_body(request_timeout) + .max_redirects(0) + .build() + .into(); + let authorization = Zeroizing::new(format!("Bearer {}", api_key.as_str())); + let response = agent + .post(OPENROUTER_COMPLETIONS_URL) + .header("authorization", authorization.as_str()) + .header("content-type", "application/json") + .header("accept", "application/json") + .send(body) + .context("OpenRouter request failed")?; + let mut bytes = Vec::new(); + response + .into_body() + .into_reader() + .take(MAX_OPENROUTER_RESPONSE_BYTES + 1) + .read_to_end(&mut bytes) + .context("OpenRouter response read failed")?; + if bytes.len() as u64 > MAX_OPENROUTER_RESPONSE_BYTES { + return Err(anyhow!("OpenRouter response exceeded its byte bound")); + } + openrouter_events_from_response(&request, &bytes) + }) + .await + .context("OpenRouter client task failed")? +} + +async fn collect_transport_events( + future: F, + request_id: &str, + provider: &str, +) -> Vec +where + F: std::future::Future>>, +{ + match future.await { + Ok(events) => events, + Err(error) => vec![EpiphanyModelStreamEvent { + schema_id: MODEL_ADAPTER_EVENT_SCHEMA_ID.to_string(), + request_id: request_id.to_string(), + provider: provider.to_string(), + sequence: 0, + payload: EpiphanyModelStreamPayload::Failed { + message: format!("{error:#}"), + }, + }], + } +} + +fn read_static_provider_credential(path: Option<&Path>, provider: &str) -> Result { + let path = path.ok_or_else(|| { + anyhow!("model provider {provider:?} requires an explicit credential file") + })?; + let raw = std::fs::read_to_string(path).with_context(|| { + format!( + "failed to read {provider} credential file {}", + path.display() + ) + })?; + let credential = raw.trim(); + if credential.is_empty() || raw.trim_matches(['\r', '\n']) != credential { + return Err(anyhow!( + "model provider {provider:?} credential file is empty or contains surrounding whitespace" + )); + } + Ok(credential.to_string()) +} + +fn unix_time_ms() -> Result { + SystemTime::now() + .duration_since(UNIX_EPOCH) + .context("system clock predates Unix epoch")? + .as_millis() + .try_into() + .context("Unix time does not fit u64 milliseconds") +} + +fn hex_digest(digest: [u8; 32]) -> String { + let mut value = String::with_capacity(64); + for byte in digest { + use std::fmt::Write as _; + write!(&mut value, "{byte:02x}").expect("writing to a String cannot fail"); + } + value +} + +#[cfg(test)] +mod tests { + use super::*; + + #[tokio::test] + async fn failed_transport_event_preserves_the_bounded_error_chain() { + let events = collect_transport_events( + async { + Err(anyhow!("socket timed out")) + .context("Connector client could not read its response") + }, + "request-1", + "openai-codex", + ) + .await; + assert_eq!(events.len(), 1); + let EpiphanyModelStreamPayload::Failed { message } = &events[0].payload else { + panic!("transport failure must project one failed event"); + }; + assert_eq!( + message, + "Connector client could not read its response: socket timed out" + ); + } +} diff --git a/epiphany-pipeline/Cargo.toml b/epiphany-pipeline/Cargo.toml new file mode 100644 index 00000000..48d2fdd8 --- /dev/null +++ b/epiphany-pipeline/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "epiphany-pipeline" +version = "0.1.0" +edition = "2024" +publish = false +autobins = false +license-file = "../LICENSE" + +[lib] +name = "epiphany_pipeline" +path = "src/lib.rs" + +# A leaf type library. Every dependency here is one the document types or the +# write path need in a non-test build: the organ prepares, decodes and +# validates envelopes through this crate, so `anyhow` (the cache's error type) +# and `rmp-serde` (the payload decode) are normal dependencies. Only the +# schema-publication check needs JSON. +[dependencies] +anyhow = "1" +chrono = "0.4.44" +cultcache-rs = { git = "https://github.com/GameCult/CultLib.git", rev = "8fc74c709444b884a2bd339e0c306bf9484d99d2", package = "cultcache-rs" } +rmp-serde = "1" +schemars = "1" +serde = { version = "1", features = ["derive"] } + +[dev-dependencies] +serde_json = "1" diff --git a/epiphany-pipeline/src/lib.rs b/epiphany-pipeline/src/lib.rs new file mode 100644 index 00000000..c29146ec --- /dev/null +++ b/epiphany-pipeline/src/lib.rs @@ -0,0 +1,3187 @@ +//! Eureka pipeline documents: the typed shape of a Eureka campaign's state. +//! +//! This is a leaf type library, and deliberately nothing else. It owns document +//! shape, field bounds, formats, key derivation and the JSON schemas published +//! under `schemas/cultnet`; it owns no storage, no admission, no process and no +//! network. That is the whole reason it is a package: the organ that will admit +//! these documents depends on these types without depending on the harness +//! that used to hold them. +//! +//! Every kind is a plain `serde` + `JsonSchema` value inside a one-slot +//! `DatabaseEntry` wrapper, always prepared with `prepare_entry_named`, so the +//! stored payload is `[value]` and the value is the named map the published +//! schema describes. Keys are semantic: `pipeline_key` derives them from the +//! value, and the write validator refuses any envelope whose key differs. +//! +//! Every key is `::`, three segments for every kind with +//! none excused: the root is a `Slug`, the kind is the literal kind name, and +//! the local is `Label`s joined by `.`, so no local part carries a dot and a +//! reader recovers the parts by splitting. A root's local is the constant +//! `self`; a resolution's local is its subject's kind and local. A +//! resolution's local ends in its per-subject sequence, `n`, and a +//! stewardship's in its per-repo sequence, so a subject's resolutions and a +//! repo's assignments each share a prefix. +//! +//! **Validation follows the type.** `value_types!` is the single field list: it +//! emits the struct and its `Bounded` impl from the same tokens, so a field +//! cannot be declared without being validated. Format rules live in the field's +//! type (`Label`, `Slug`, `OrgRepo`, `Sha`, `Sha256Hex`, `Date`), never in a +//! hand-written impl that a later field can slip past. A `Vec` field must carry +//! a maximum, because `Vec` has no `Bounded` impl of its own. +//! +//! The wrappers are crate-private: outside code registers, prepares and +//! decodes them through `register_pipeline_document_types`, +//! `PipelineDocument::prepare` and `PipelineDocument::decode`, and validates a +//! write through `validate_pipeline_write_envelope`. + +// `anyhow` is the cache's own error type, which `prepare` and the registrar +// pass through; every refusal this crate decides is a `PipelineRefusal`. +use anyhow::Result; +use cultcache_rs::{CultCache, CultCacheEnvelope, DatabaseEntry}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +/// Typed refusals of the pipeline documents: bounds, formats and key identity +/// (D2's document half), and `ForeignStore`, raised by `decode` for an +/// envelope of any other type. This is every refusal the leaf decides; a +/// store's identity and its schema epoch are not decided here, since this +/// crate owns no store, and belong to the organ that will admit these +/// documents. +/// +/// Serialised externally tagged, serde's default for an enum: each variant is +/// a one-key map from the variant's name to its named fields, so `field` and +/// `value` are on the wire under their own names and a reader of a refusal +/// carried over a wire reads the same parts a local caller matches on. No +/// attribute buys that, which is why there is none: an internal or adjacent +/// tag would move the fields under a content key without making any of them +/// more visible. +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +pub enum PipelineRefusal { + FieldBound { field: String, limit: u32, actual: u32 }, + InvalidFormat { field: String, value: String }, + InvalidIdentity { kind: PipelineKind, key: String, expected: String }, + DuplicateRepo { field: String, repo: String }, + ForeignStore { r#type: String }, +} + +impl std::fmt::Display for PipelineRefusal { + fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "pipeline refusal: {self:?}") + } +} + +impl std::error::Error for PipelineRefusal {} + +pub(crate) trait Bounded { + fn validate(&self, field: &str) -> Result<(), PipelineRefusal>; +} + +impl Bounded for Option { + fn validate(&self, field: &str) -> Result<(), PipelineRefusal> { + self.as_ref().map_or(Ok(()), |value| value.validate(field)) + } +} + +macro_rules! unbounded_scalars { + ($($ty:ty),* $(,)?) => {$( + impl Bounded for $ty { + fn validate(&self, _field: &str) -> Result<(), PipelineRefusal> { + Ok(()) + } + } + )*}; +} + +unbounded_scalars!(u32, u64, bool); + +fn bound(field: &str, limit: usize, actual: usize) -> Result<(), PipelineRefusal> { + if actual > limit { + return Err(PipelineRefusal::FieldBound { + field: field.into(), + limit: limit as u32, + actual: actual as u32, + }); + } + Ok(()) +} + +/// List maximums only. Minimum counts are admission rules with their own +/// refusals (Cut 3b), so a bounds pass never pre-empts them. +fn list(field: &str, items: &[T], max: usize) -> Result<(), PipelineRefusal> { + bound(field, max, items.len())?; + for (index, item) in items.iter().enumerate() { + item.validate(&format!("{field}[{index}]"))?; + } + Ok(()) +} + +fn format_error(field: &str, value: &str) -> PipelineRefusal { + PipelineRefusal::InvalidFormat { + field: field.into(), + value: value.into(), + } +} + +fn hex(field: &str, value: &str, lengths: std::ops::RangeInclusive) -> Result<(), PipelineRefusal> { + let lower_hex = value.bytes().all(|byte| matches!(byte, b'0'..=b'9' | b'a'..=b'f')); + if !lengths.contains(&value.len()) || !lower_hex { + return Err(format_error(field, value)); + } + Ok(()) +} + +/// A key label: `[A-Za-z0-9_-]{1,64}`. Dots are excluded so that a composed key +/// segments unambiguously; see `local`. +fn label_text(field: &str, value: &str) -> Result<(), PipelineRefusal> { + let valid = value + .bytes() + .all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'_' | b'-')); + if !valid || value.is_empty() || value.len() > 64 { + return Err(format_error(field, value)); + } + Ok(()) +} + +/// A dotted key segment: label parts joined by `.`, at most 64 bytes. Every +/// part must be a label, so `..`, a leading or trailing dot, and an empty part +/// are all refused. +fn dotted_text(field: &str, value: &str) -> Result<(), PipelineRefusal> { + dotted_within(field, value, 64) +} + +/// `dotted_text`'s grammar with the bound as a parameter: the one place a +/// dotted segment is parsed, whoever owns the bound. +fn dotted_within(field: &str, value: &str, max: usize) -> Result<(), PipelineRefusal> { + if value.is_empty() || value.len() > max { + return Err(format_error(field, value)); + } + for part in value.split('.') { + label_text(field, part)?; + } + Ok(()) +} + +/// GitHub's own grammar for `owner/repo`, exactly one `/`: the owner is +/// `[A-Za-z0-9-]`, 1 to 39 bytes, with no leading or trailing hyphen; the repo +/// is `[A-Za-z0-9._-]`, 1 to 100 bytes, is never `.` or `..`, and does not end +/// in `.git`, ASCII-case-insensitively (`a/b.GIT` is refused exactly as +/// `a/b.git` is), so the suffix rule cannot be dodged by a case variant whose +/// `identity()` is refused. The repo's own byte alphabet excludes `/`, so a +/// second `/` inside it is already refused by the byte check; no separate +/// `contains('/')` check is needed. +fn org_repo_text(field: &str, value: &str) -> Result<(), PipelineRefusal> { + let valid = value.split_once('/').is_some_and(|(owner, repo)| { + (1..=39).contains(&owner.len()) + && owner.bytes().all(|byte| byte.is_ascii_alphanumeric() || byte == b'-') + && !owner.starts_with('-') + && !owner.ends_with('-') + && (1..=100).contains(&repo.len()) + && repo.bytes().all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'.' | b'_' | b'-')) + && repo != "." + && repo != ".." + && !(repo.len() >= 4 && repo[repo.len() - 4..].eq_ignore_ascii_case(".git")) + }); + if valid { + Ok(()) + } else { + Err(format_error(field, value)) + } +} + +macro_rules! bounded_text { + ($($(#[$doc:meta])* $name:ident = $limit:literal, $check:expr;)*) => {$( + #[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, JsonSchema)] + #[serde(transparent)] + #[schemars(extend("maxLength" = $limit))] + $(#[$doc])* + pub struct $name(pub String); + impl Bounded for $name { + fn validate(&self, field: &str) -> Result<(), PipelineRefusal> { + let check: fn(&str, &str) -> Result<(), PipelineRefusal> = $check; + check(field, &self.0) + } + } + impl From<&str> for $name { + fn from(value: &str) -> Self { + Self(value.into()) + } + } + )*}; +} + +fn within(limit: usize) -> impl Fn(&str, &str) -> Result<(), PipelineRefusal> { + move |field: &str, value: &str| bound(field, limit, value.len()) +} + +/// A title holds at least one `char::is_alphanumeric()` character, no +/// `char::is_control()` character, no U+2028 LINE SEPARATOR or U+2029 +/// PARAGRAPH SEPARATOR (both `Zl`/`Zp`, so `is_control()` does not reach +/// them, and a one-line field should carry no hard line break), and no bidi +/// control character: `U+061C`, `U+200E`, `U+200F`, `U+202A`-`U+202E`, +/// `U+2066`-`U+2069`. No denylist of individual zero-width or format +/// characters is maintained here; a character not covered by one of these +/// tests is allowed, so ZWJ and ZWNJ still work. +fn title_text(field: &str, value: &str) -> Result<(), PipelineRefusal> { + let alphanumeric = value.chars().any(|character| character.is_alphanumeric()); + let control = value + .chars() + .any(|character| character.is_control() || matches!(character, '\u{2028}' | '\u{2029}')); + let bidi = value.chars().any(|character| { + matches!( + character, + '\u{061C}' | '\u{200E}' | '\u{200F}' | '\u{202A}'..='\u{202E}' | '\u{2066}'..='\u{2069}' + ) + }); + if !alphanumeric || control || bidi { + return Err(format_error(field, value)); + } + within(200)(field, value) +} + +bounded_text! { + /// Text of at most 200 UTF-8 bytes. + Short = 200, |field, value| within(200)(field, value); + /// A title: 1 to 200 UTF-8 bytes, enforced by admission; JSON Schema + /// length counts characters. Holds at least one alphanumeric character, + /// no control character, and no bidi control character (`U+061C`, + /// `U+200E`, `U+200F`, `U+202A`-`U+202E`, `U+2066`-`U+2069`). + #[schemars(extend("minLength" = 1))] + Title = 200, title_text; + /// Text of at most 1,000 UTF-8 bytes. + Line = 1000, |field, value| within(1000)(field, value); + /// Text of at most 4,000 UTF-8 bytes; longer narrative is cited by `DocRef`. + Para = 4000, |field, value| within(4000)(field, value); + /// A key label: `[A-Za-z0-9_-]{1,64}`. + Label = 64, label_text; + /// A campaign slug or key segment: label parts joined by `.`. + Slug = 64, dotted_text; + /// A git commit id: 7-40 lowercase hex characters. + Sha = 40, |field, value| hex(field, value, 7..=40); + /// A full git commit id: exactly 40 lowercase hex characters. + FullSha = 40, |field, value| hex(field, value, 40..=40); + /// A SHA-256 digest: exactly 64 lowercase hex characters. + Sha256Hex = 64, |field, value| hex(field, value, 64..=64); +} + +impl Sha { + /// The type's identity rule: two spellings name one commit when one is a + /// prefix of the other, so a 7-character abbreviation and the full id of + /// the same commit are equal here though not `==`. Both are already 7-40 + /// lowercase hex. Two distinct commits that share a 7-character prefix are + /// git's ambiguity, not the mind's. + pub fn names_same_commit(&self, other: &Sha) -> bool { + self.0.starts_with(&other.0) || other.0.starts_with(&self.0) + } +} + +impl Slug { + /// The public door onto the slug grammar, for a caller outside this crate + /// that holds a bare `Slug` and no way to reach `Bounded`, which is + /// crate-private. On the pattern of `PipelineRef::validate_ref`: it + /// delegates so the grammar keeps one owner, and it applies exactly the + /// `dotted_text` check that every `Slug` field in this crate is held to, + /// so a caller that validates a declared name this way and a document + /// field of the same type refuse the same inputs. Named `validate_slug` + /// rather than a bare `validate`, because an inherent `validate` would + /// shadow `Bounded`'s for every in-crate caller holding a `Slug`. + pub fn validate_slug(&self) -> Result<(), PipelineRefusal> { + Bounded::validate(self, "slug") + } +} + +/// A GitHub repository as `Org/Repo`, at most 140 UTF-8 bytes (39 + 1 + +/// 100). The owner is 1 to 39 `[A-Za-z0-9-]` bytes with no leading or +/// trailing hyphen; the repo is 1 to 100 `[A-Za-z0-9._-]` bytes, is never +/// `.` or `..`, and does not end in `.git`, ASCII-case-insensitively. +/// Identity is case-insensitive: `GameCult/Epiphany` and `gamecult/epiphany` +/// are the same repository. +// +// The raw string is display-only. `OrgRepo` is not generated by +// `bounded_text!`: that macro derives `PartialEq`, `Eq`, `Hash`, `PartialOrd` +// and `Ord` on the raw string, with no per-member override, so a type built +// from it compares and keys on spelling. `OrgRepo` instead carries +// hand-written impls of those five traits, every one of them over +// `identity()` (the ASCII-lowercased value), so `==`, a `BTreeMap` key and a +// `HashMap` key are all case-insensitive by construction: `GameCult/Epiphany` +// and `gamecult/epiphany` compare equal, hash equal, order together and +// collapse to one map entry, and a caller cannot reach the case-sensitive +// behaviour by accident. `Hash` and `Eq` agree because both are defined over +// the same `identity()` string. Every other bounded type keeps the macro's +// derives untouched. This reasoning is a source comment, not a doc comment, +// so it does not enter the published schema description (RS-L closing fix 2): +// a consumer needs the grammar, the bound and the case-insensitivity, not the +// Rust-macro justification for how that got built. +#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] +#[serde(transparent)] +#[schemars(extend("maxLength" = 140))] +pub struct OrgRepo(pub String); + +impl Bounded for OrgRepo { + fn validate(&self, field: &str) -> Result<(), PipelineRefusal> { + org_repo_text(field, &self.0) + } +} + +impl From<&str> for OrgRepo { + fn from(value: &str) -> Self { + Self(value.into()) + } +} + +impl PartialEq for OrgRepo { + fn eq(&self, other: &Self) -> bool { + self.identity() == other.identity() + } +} + +impl Eq for OrgRepo {} + +impl std::hash::Hash for OrgRepo { + fn hash(&self, state: &mut H) { + self.identity().hash(state); + } +} + +impl PartialOrd for OrgRepo { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for OrgRepo { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.identity().cmp(&other.identity()) + } +} + +impl OrgRepo { + /// The public door onto the `org_repo` grammar, for a caller outside this + /// crate that holds a bare `OrgRepo` (the organ's `repo` alias, RS-3) and + /// no way to reach `Bounded`. On the pattern of `Slug::validate_slug`: it + /// delegates to `org_repo_text` through `Bounded::validate`, so the + /// grammar keeps one owner and a declared name checked this way refuses + /// exactly what a document's own `OrgRepo` field refuses. + pub fn validate_org_repo(&self) -> Result<(), PipelineRefusal> { + Bounded::validate(self, "org_repo") + } + + /// The one canonical key for an `OrgRepo`: the value, ASCII-lowercased. + /// Every comparison, hash, ordering and lookup keyed by repository + /// identity goes through this — `PartialEq`, `Hash` and `Ord` above are + /// all defined in terms of it — so `GameCult/Epiphany` and + /// `gamecult/epiphany` are the same repository everywhere, not only where + /// a caller remembers to ask. + pub fn identity(&self) -> String { + self.0.to_ascii_lowercase() + } +} + +impl Label { + /// The public door onto the label grammar, for a caller outside this + /// crate that holds a bare `Label` (the organ's `cut` alias, RS-3) and no + /// way to reach `Bounded`. On the pattern of `Slug::validate_slug`: it + /// delegates to `label_text` through `Bounded::validate`, so the grammar + /// keeps one owner and a declared name checked this way refuses exactly + /// what a document's own `Label` field refuses. + pub fn validate_label(&self) -> Result<(), PipelineRefusal> { + Bounded::validate(self, "label") + } +} + +/// A calendar date, `YYYY-MM-DD`. +#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, JsonSchema)] +#[serde(transparent)] +#[schemars(extend("maxLength" = 10))] +pub struct Date(pub String); + +impl Bounded for Date { + fn validate(&self, field: &str) -> Result<(), PipelineRefusal> { + match chrono::NaiveDate::parse_from_str(&self.0, "%Y-%m-%d") { + Ok(_) if self.0.len() == 10 => Ok(()), + _ => Err(format_error(field, &self.0)), + } + } +} + +macro_rules! bounded_field { + ($value:expr, $field:expr) => { + Bounded::validate(&$value, $field)? + }; + ($value:expr, $field:expr, $max:literal) => { + list($field, &$value, $max)? + }; +} + +/// The single field list: one invocation emits the value struct and its +/// `Bounded` impl, so a field can never be left out of validation. +macro_rules! value_types { + ($($(#[$attr:meta])* pub struct $name:ident { $($field:ident: $ty:ty $([$max:literal])?),* $(,)? } $(=> $extra:path)?)*) => {$( + $(#[$attr])* + #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] + pub struct $name { + $( + $(#[schemars(extend("maxItems" = $max))])? + pub $field: $ty + ),* + } + + impl Bounded for $name { + fn validate(&self, at: &str) -> Result<(), PipelineRefusal> { + $(bounded_field!(self.$field, &format!("{at}.{}", stringify!($field)) $(, $max)?);)* + $($extra(self, at)?;)? + Ok(()) + } + } + )*}; +} + +macro_rules! unit_enums { + ($($name:ident { $($variant:ident),* $(,)? })*) => {$( + #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, JsonSchema)] + pub enum $name { $($variant),* } + impl Bounded for $name { + fn validate(&self, _field: &str) -> Result<(), PipelineRefusal> { + Ok(()) + } + } + )*}; +} + +/// RS-L closing fix 4: `campaign.repos` refuses two entries naming the same +/// repository by identity, not by raw spelling, so `GameCult/Epiphany` and +/// `gamecult/epiphany` in one list refuse exactly as one entry twice would. +/// This is a cross-element invariant on `PipelineCampaign` alone, not a +/// per-type format rule any other `value_types!` struct shares, so it is +/// wired in as that struct's one `=> extra` hook rather than folded into +/// `Bounded for OrgRepo` or the list bound in `bounded_field!`. +fn campaign_repos_are_distinct(campaign: &PipelineCampaign, at: &str) -> Result<(), PipelineRefusal> { + let mut seen = std::collections::HashSet::new(); + for repo in &campaign.repos { + if !seen.insert(repo.identity()) { + return Err(PipelineRefusal::DuplicateRepo { + field: format!("{at}.repos"), + repo: repo.0.clone(), + }); + } + } + Ok(()) +} + +unit_enums! { + EvidenceKind { Command, Test, Mutation, Probe, SourceRead, Capture } + ClaimOutcome { Holds, Falsified, Unproven } + FindingConfidence { Confirmed, Plausible } + FindingSeverity { Blocker, High, Medium, Low } + RulingAuthority { Operator, Standing, Defaulted } + FindingOrigin { Introduced, PreExisting } +} + +value_types! { + pub struct CodeLocation { path: Short, line: u32, end_line: Option } + pub struct CommitRange { base: Sha, head: Sha } + pub struct Evidence { kind: EvidenceKind, locator: Line, result: Line } + /// A citation of a document in another repo's pipeline store (D6). + pub struct ForeignRef { repo: OrgRepo, commit: FullSha, kind: PipelineKind, id: Short, payload_sha256: Sha256Hex } + pub struct DocRef { path: Short, start_line: u32, end_line: u32, commit: Sha } + pub struct TargetInvariant { label: Label, statement: Line } + pub struct QuestionOption { label: Label, text: Line } + pub struct CutDelete { path: Short, lines: u32, note: Line } + pub struct FileChange { location: CodeLocation, change: Line } + pub struct AuthorityMap { + owner: Line, inputs: Vec[16], outputs: Vec[16], derived_state: Vec[16], + forbidden_writers: Vec[16], shared_paths: Vec[16], deletion_line: Line, + } + pub struct VerificationTest { name: Short, pins: Line } + pub struct NegativeCheck { pattern: Short, scope: Line } + pub struct CutVerification { + builds: Vec[64], tests: Vec[64], + negative: Vec[64], operator: Vec[64], + } + pub struct ReportCommit { sha: Sha, subject: Line, builds: bool } + /// One mutation a report ran: a key-safe label a verdict claim can name, + /// the exact edit, and the tree it was applied to. + pub struct MutationRecord { label: Label, rule: Line, location: CodeLocation, before: Line, after: Line, commit: Sha, failed_as_expected: bool } + pub struct Deviation { what: Line, why: Line } + // D1 tables no maximum for these lists, so they take the shared list + // default of 64 rather than a number invented for this field alone. + pub struct StructuralDelta { + lines_added: u32, lines_removed: u32, + dependencies_added: Vec[64], dependencies_removed: Vec[64], + formats_added: Vec[64], formats_removed: Vec[64], + targets_added: Vec[64], targets_removed: Vec[64], + } + /// A name the cut landed, and where it lives. + pub struct LandedName { name: Short, path: Short } + /// A promise a report makes about what it landed. Not a document and not + /// resolvable: it is identified by its report's id and its label, as a + /// `TargetInvariant` is by its target's. Soul measures every one (ruling + /// A), and the rule that every one is measured is admission's. + pub struct Promise { label: Label, text: Line } + /// The promise this claim measured, and the report's mutations it ran. An + /// `Unproven` claim with a promise is one Soul could not reach. + pub struct VerdictClaim { + claim: Line, outcome: ClaimOutcome, evidence: Vec[8], findings: Vec[16], + promise: Option