diff --git a/.specify/archive/model-preflight/20260718/goal-model-preflight-before-local-timeout-fix.json b/.specify/archive/model-preflight/20260718/goal-model-preflight-before-local-timeout-fix.json new file mode 100644 index 0000000..690fbe6 --- /dev/null +++ b/.specify/archive/model-preflight/20260718/goal-model-preflight-before-local-timeout-fix.json @@ -0,0 +1,18 @@ +{ + "1b5426346e8198bc61cf8e5620be210895680af6e1cdb82bba8694c7f0da2ef9": { + "model": "openai-compatible:auto/coding", + "provider": "omniroute", + "healthy": false, + "reason": "model_exit_2:MODEL_ROUTER_ERROR backend_unreachable HTTPError: HTTP Error 503: Service Unavailable", + "checked_at": "2026-07-18T15:24:11Z", + "checked_epoch": 1784388251.1839528 + }, + "0a4a9b1f8e1ff0908e2861bbbdf3dd4b99ac381a8749c94baed4930a3ca11292": { + "model": "ollama:ornith:9b", + "provider": "ollama", + "healthy": false, + "reason": "model_timeout", + "checked_at": "2026-07-18T15:24:41Z", + "checked_epoch": 1784388281.2238398 + } +} diff --git a/.specify/cache/goal-model-preflight.json b/.specify/cache/goal-model-preflight.json new file mode 100644 index 0000000..b589172 --- /dev/null +++ b/.specify/cache/goal-model-preflight.json @@ -0,0 +1,130 @@ +{ + "1b5426346e8198bc61cf8e5620be210895680af6e1cdb82bba8694c7f0da2ef9": { + "model": "openai-compatible:auto/coding", + "provider": "omniroute", + "healthy": false, + "reason": "model_timeout", + "checked_at": "2026-07-18T16:20:54Z", + "checked_epoch": 1784391654.9303336 + }, + "0a4a9b1f8e1ff0908e2861bbbdf3dd4b99ac381a8749c94baed4930a3ca11292": { + "model": "ollama:ornith:9b", + "provider": "ollama", + "healthy": true, + "reason": "ok", + "checked_at": "2026-07-18T15:27:33Z", + "checked_epoch": 1784388453.7658935 + }, + "2ad50792d2c887dd74476a96e22a2127536692a58e6045e1e3941a9fa79a5df9": { + "model": "account:codex", + "provider": "codex-account", + "healthy": true, + "reason": "ok", + "checked_at": "2026-07-18T15:38:46Z", + "checked_epoch": 1784389126.023104 + }, + "501c7fb832612cef3360ddd8a02b80c0a656b75353a6706cab6c1ce54bde11ea": { + "model": "openai:gpt-5.3-codex", + "provider": "openai", + "healthy": true, + "reason": "ok", + "checked_at": "2026-07-18T16:14:15Z", + "checked_epoch": 1784391255.5425012 + }, + "f535de9122c693a1ec0b67cd9248e0e0cf50d02689a7885863ddd2fcb9dac4a2": { + "model": "openai:gpt-5.3-codex", + "provider": "openai", + "healthy": false, + "reason": "goal_patch_missing", + "checked_at": "2026-07-18T16:21:52Z", + "checked_epoch": 1784391712.3473551 + }, + "fd0849f02e64d143f68870f7f09f8e9054c97efdb32970dc498ba6aa421c25cc": { + "model": "openai-compatible:aug/claude-haiku-4.5", + "provider": "omniroute", + "healthy": false, + "reason": "model_exit_2:MODEL_ROUTER_ERROR backend_unreachable HTTPError: HTTP Error 502: Bad Gateway", + "checked_at": "2026-07-18T16:20:55Z", + "checked_epoch": 1784391655.048939 + }, + "20ed5fb7e077c5de0c1e22b8b09c9ded068ff9b920ffbad5aee205a2f7a4ced7": { + "model": "openai-compatible:aug/claude-opus-4.6", + "provider": "omniroute", + "healthy": false, + "reason": "model_exit_2:MODEL_ROUTER_ERROR backend_unreachable HTTPError: HTTP Error 502: Bad Gateway", + "checked_at": "2026-07-18T16:20:55Z", + "checked_epoch": 1784391655.140041 + }, + "5d7bac5e696c4d1433d3c138fd1c310a811f496ba15b58c61bd2ed81d1a4f3ad": { + "model": "openai-compatible:aug/claude-sonnet-4.6", + "provider": "omniroute", + "healthy": false, + "reason": "model_exit_2:MODEL_ROUTER_ERROR backend_unreachable HTTPError: HTTP Error 502: Bad Gateway", + "checked_at": "2026-07-18T16:20:55Z", + "checked_epoch": 1784391655.2397785 + }, + "e46af2703f62053be4e77849225ca03c3f029da9ec91aaea78f6e0726a3dea2d": { + "model": "openai-compatible:aug/claude-sonnet-4.6-thinking", + "provider": "omniroute", + "healthy": false, + "reason": "model_exit_2:MODEL_ROUTER_ERROR backend_unreachable HTTPError: HTTP Error 502: Bad Gateway", + "checked_at": "2026-07-18T16:20:55Z", + "checked_epoch": 1784391655.3297153 + }, + "8ec1da73f4fcbc754b93a7ad534b34308dbb3b2235a75c6a787070fd332cfa73": { + "model": "openai-compatible:gateway", + "provider": "omniroute", + "healthy": false, + "reason": "patch_probe_contract_invalid", + "checked_at": "2026-07-18T16:21:52Z", + "checked_epoch": 1784391712.3490422 + }, + "44f183c901766758ddbb222f6e1f5559c243219bc7077e787bedc71c8e58b636": { + "model": "ollama:ornith", + "provider": "ollama", + "healthy": false, + "reason": "goal_patch_missing", + "checked_at": "2026-07-18T16:21:52Z", + "checked_epoch": 1784391712.3524656 + }, + "b208348d5436485a8663538b70956752f3497e841af640e25cf107749c5afcc0": { + "model": "ollama:test", + "provider": "ollama", + "healthy": false, + "reason": "patch_probe_contract_invalid", + "checked_at": "2026-07-18T16:21:52Z", + "checked_epoch": 1784391712.3621576 + }, + "e815d3eac7f27a62a4a217e49c28d456ca789b3a1003f188a788985de068da88": { + "model": "openai:gpt-4o-mini", + "provider": "openai", + "healthy": false, + "reason": "patch_probe_contract_invalid", + "checked_at": "2026-07-18T16:21:52Z", + "checked_epoch": 1784391712.367114 + }, + "6352a8a41a3ebdd2c9d4c724515838d01993983082d8645025938fd452890d87": { + "model": "openai:gpt-4.1", + "provider": "openai", + "healthy": false, + "reason": "goal_patch_missing", + "checked_at": "2026-07-18T16:21:52Z", + "checked_epoch": 1784391712.3708255 + }, + "a23aea560f9ebee7ff805d1e4583db4b0e78363095431f0a91a09efa37cff145": { + "model": "openai-compatible:aug/claude-sonnet", + "provider": "omniroute", + "healthy": false, + "reason": "patch_probe_contract_invalid", + "checked_at": "2026-07-18T16:21:52Z", + "checked_epoch": 1784391712.3722892 + }, + "ce2b1288eeea460eb7c9e01576e04cd30f8ffe2b9a88fe6e59ee527b55ae140e": { + "model": "ollama:worker", + "provider": "ollama", + "healthy": false, + "reason": "patch_probe_contract_invalid", + "checked_at": "2026-07-18T16:21:52Z", + "checked_epoch": 1784391712.5912051 + } +} diff --git a/.specify/cache/goal-model-preflight.lock b/.specify/cache/goal-model-preflight.lock new file mode 100644 index 0000000..e69de29 diff --git a/.specify/logs/audit/security.jsonl b/.specify/logs/audit/security.jsonl index 5b58860..bd45b59 100644 --- a/.specify/logs/audit/security.jsonl +++ b/.specify/logs/audit/security.jsonl @@ -371,3 +371,161 @@ {"timestamp":"2026-07-18T08:39:29Z","trace_id":"049a79dc-90bf-4fb6-a08c-133e6fb04a9b","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"5a2d9ab9a60d417a9d53418cc72be21f643025088b2ca76911a946c032ff0679","output_hash":"5a2d9ab9a60d417a9d53418cc72be21f643025088b2ca76911a946c032ff0679"} {"timestamp":"2026-07-18T08:39:55Z","trace_id":"e89746ee-9afa-44b2-bd90-85db0e37731f","harness":"H4-security","mode":"input","status":"blocked","action":"block","risk_level":"high","input_hash":"0aa7555c68e218490b78edd7f665f8dd5d56405720ec6cb4c7be2b34e2baabfa","output_hash":"607dab11dbd23786974b6b2f64daafa2078743f837c722d2f409dad2fbd2adef"} {"timestamp":"2026-07-18T08:39:57Z","trace_id":"36ec47ff-04f9-41e0-94d9-862a380549ea","harness":"H4-security","mode":"output","status":"blocked","action":"block","risk_level":"high","input_hash":"0aa7555c68e218490b78edd7f665f8dd5d56405720ec6cb4c7be2b34e2baabfa","output_hash":"607dab11dbd23786974b6b2f64daafa2078743f837c722d2f409dad2fbd2adef"} +{"timestamp":"2026-07-18T10:09:18Z","trace_id":"trace-1784369358-302","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"21a94d09a3c4584be963b86fa294a31ad8303871de74b467ee205d170489fc3c","output_hash":"21a94d09a3c4584be963b86fa294a31ad8303871de74b467ee205d170489fc3c"} +{"timestamp":"2026-07-18T10:09:19Z","trace_id":"trace-1784369359-882","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"d962bb809f87791f22fd5a3e663e08e82646d992f105e01250712d9893f5691a","output_hash":"c001e1f9d3c8f60ca11cc884fe119e01d0611bac7cb0d07e262874a71b317a5e"} +{"timestamp":"2026-07-18T10:09:59Z","trace_id":"trace-1784369399-1558","harness":"H4-security","mode":"output","status":"pass","action":"allow","risk_level":"low","input_hash":"db545408c2e7e7be11a94ff6736fa8db52e9077066c56b8308c34459f2978362","output_hash":"db545408c2e7e7be11a94ff6736fa8db52e9077066c56b8308c34459f2978362"} +{"timestamp":"2026-07-18T10:11:32Z","trace_id":"trace-1784369492-2040","harness":"H4-security","mode":"output","status":"pass","action":"allow","risk_level":"low","input_hash":"6441857790afc3926df927868a07ec153ec8d67adff15f5e3876775b2c96671f","output_hash":"6441857790afc3926df927868a07ec153ec8d67adff15f5e3876775b2c96671f"} +{"timestamp":"2026-07-18T11:17:37Z","trace_id":"trace-1784373457-3116","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"3db6096be1bf9ed65e06f91ce5f2dcf0e8d5e7dc00ed7f1665a94e0e4b7023a6","output_hash":"3db6096be1bf9ed65e06f91ce5f2dcf0e8d5e7dc00ed7f1665a94e0e4b7023a6"} +{"timestamp":"2026-07-18T11:17:37Z","trace_id":"trace-1784373457-3697","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"e8b5ee8e8f7ee2d1d1501e5e5936c17ebbc6a4ff7014af706d0b20b62b2949ea","output_hash":"06d5cad7e425d8d32e05a11ee6587ce518049234499e84caf252684f317a0d5e"} +{"timestamp":"2026-07-18T11:18:39Z","trace_id":"trace-1784373519-4433","harness":"H4-security","mode":"output","status":"pass","action":"allow","risk_level":"low","input_hash":"888719174b5fbcb603419f49c31eea5394e81e53c5a4c8b94e6b8abee1a568b2","output_hash":"888719174b5fbcb603419f49c31eea5394e81e53c5a4c8b94e6b8abee1a568b2"} +{"timestamp":"2026-07-18T11:20:19Z","trace_id":"trace-1784373619-4931","harness":"H4-security","mode":"output","status":"pass","action":"allow","risk_level":"low","input_hash":"571786487c86989de3aa941063d74ecc2040ea81807a3073747bb988ea0c70ea","output_hash":"571786487c86989de3aa941063d74ecc2040ea81807a3073747bb988ea0c70ea"} +{"timestamp":"2026-07-18T11:37:38Z","trace_id":"trace-1784374658-246","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"c29c5a10ead5f18d11b5b8273f36686341c4f7350ff8a989e5d07ec7cf9d5946","output_hash":"c29c5a10ead5f18d11b5b8273f36686341c4f7350ff8a989e5d07ec7cf9d5946"} +{"timestamp":"2026-07-18T11:37:39Z","trace_id":"trace-1784374659-826","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"73df872e3e1e47616c0cc279fd6e27d8cb842477554ae7c2c53ead54cc413980","output_hash":"0eb59ee8fc55d3300acaa4950485f6eede1d5a4251e950151686c23327826428"} +{"timestamp":"2026-07-18T11:39:41Z","trace_id":"trace-1784374781-1539","harness":"H4-security","mode":"output","status":"pass","action":"allow","risk_level":"low","input_hash":"035820c4f12097a7b1cf7824e6707fb78c275607aac75898e5ce81edfd831fb9","output_hash":"035820c4f12097a7b1cf7824e6707fb78c275607aac75898e5ce81edfd831fb9"} +{"timestamp":"2026-07-18T12:19:37Z","trace_id":"trace-1784377177-1784","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"f8eb900cf26466c22669fef89a49d42e22787bf860ca66655a24fd5f9ce31c62","output_hash":"f8eb900cf26466c22669fef89a49d42e22787bf860ca66655a24fd5f9ce31c62"} +{"timestamp":"2026-07-18T12:19:38Z","trace_id":"trace-1784377178-2366","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"73df872e3e1e47616c0cc279fd6e27d8cb842477554ae7c2c53ead54cc413980","output_hash":"0eb59ee8fc55d3300acaa4950485f6eede1d5a4251e950151686c23327826428"} +{"timestamp":"2026-07-18T12:20:41Z","trace_id":"trace-1784377241-3108","harness":"H4-security","mode":"output","status":"pass","action":"allow","risk_level":"low","input_hash":"6736595cf4505f534a6ba1b38cf7bb72c71573c35eb048f84f877750086b4fc0","output_hash":"6736595cf4505f534a6ba1b38cf7bb72c71573c35eb048f84f877750086b4fc0"} +{"timestamp":"2026-07-18T14:19:43Z","trace_id":"trace-1784384383-448","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"f8eb900cf26466c22669fef89a49d42e22787bf860ca66655a24fd5f9ce31c62","output_hash":"f8eb900cf26466c22669fef89a49d42e22787bf860ca66655a24fd5f9ce31c62"} +{"timestamp":"2026-07-18T14:19:44Z","trace_id":"trace-1784384384-1029","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"27b8a71ae30e3e9d2dd04a3a2bc18949587561bf49184170d4cbbc151bb004e7","output_hash":"75813e958e37382304fa3ab1859e351af78cb68f11bfe526923838d34a22f410"} +{"timestamp":"2026-07-18T14:20:43Z","trace_id":"trace-1784384443-1746","harness":"H4-security","mode":"output","status":"pass","action":"allow","risk_level":"low","input_hash":"53daf40f9cb8e48a597c1b14aaaa1ea7cc81daff709cbb8fe7a55e9d64416707","output_hash":"53daf40f9cb8e48a597c1b14aaaa1ea7cc81daff709cbb8fe7a55e9d64416707"} +{"timestamp":"2026-07-18T14:27:29Z","trace_id":"trace-1784384849-314","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"13bb8d002e46be4ad4741f384172f40c3bcbacc0629b4406d58f0c74a3868b56","output_hash":"13bb8d002e46be4ad4741f384172f40c3bcbacc0629b4406d58f0c74a3868b56"} +{"timestamp":"2026-07-18T14:27:29Z","trace_id":"trace-1784384849-895","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"1d5fba6d518205fd4c714fedbfcf1beca3fb266ce3471d2604105071ee7790c6","output_hash":"7573d2b8a3484cf60a62e93a4ff210be151bb98354961b97d00ed6795dea1f84"} +{"timestamp":"2026-07-18T14:28:58Z","trace_id":"trace-1784384938-1686","harness":"H4-security","mode":"output","status":"pass","action":"allow","risk_level":"low","input_hash":"d0a346d08e7a904a0420722920924c7aa6aa35dcf1e4d4f0d53c0c2fd31558a6","output_hash":"d0a346d08e7a904a0420722920924c7aa6aa35dcf1e4d4f0d53c0c2fd31558a6"} +{"timestamp":"2026-07-18T14:39:41Z","trace_id":"trace-1784385581-344","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"4645942c31633c69e905425d80c42fc86f575487bbd032a081bf541d9eae74f0","output_hash":"4645942c31633c69e905425d80c42fc86f575487bbd032a081bf541d9eae74f0"} +{"timestamp":"2026-07-18T14:39:41Z","trace_id":"trace-1784385581-925","harness":"H4-security","mode":"input","status":"blocked","action":"block","risk_level":"high","input_hash":"0b05221919ba3ee3ca96f5ac495f45c555119a4a6e467505561a84e417494525","output_hash":"3549e9d50f28d85b79ee2983b4c87e85ed67f85461a8016b7934c48d97c11071"} +{"timestamp":"2026-07-18T14:40:42Z","trace_id":"trace-1784385642-1588","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"4645942c31633c69e905425d80c42fc86f575487bbd032a081bf541d9eae74f0","output_hash":"4645942c31633c69e905425d80c42fc86f575487bbd032a081bf541d9eae74f0"} +{"timestamp":"2026-07-18T14:40:42Z","trace_id":"trace-1784385642-2170","harness":"H4-security","mode":"input","status":"blocked","action":"block","risk_level":"high","input_hash":"0b05221919ba3ee3ca96f5ac495f45c555119a4a6e467505561a84e417494525","output_hash":"3549e9d50f28d85b79ee2983b4c87e85ed67f85461a8016b7934c48d97c11071"} +{"timestamp":"2026-07-18T14:43:07Z","trace_id":"6a9b9d04-32ad-4490-99b3-95466b2413f1","harness":"H4-security","mode":"input","status":"blocked","action":"block","risk_level":"high","input_hash":"0a7b65ca1bc5dac5251cab6ee3b851d538d734847e50a1437bc7d2b577348df6","output_hash":"0a7b65ca1bc5dac5251cab6ee3b851d538d734847e50a1437bc7d2b577348df6"} +{"timestamp":"2026-07-18T14:43:08Z","trace_id":"cf132b6c-a3c4-407b-a123-57947da281c8","harness":"H4-security","mode":"input","status":"blocked","action":"block","risk_level":"high","input_hash":"2026b9191be5fc04181c4a9a775c7be7368c94e654ebb5e96227076ce9693bbd","output_hash":"2026b9191be5fc04181c4a9a775c7be7368c94e654ebb5e96227076ce9693bbd"} +{"timestamp":"2026-07-18T14:43:09Z","trace_id":"15ceb11b-2889-46bd-bc5f-07de6a88c07e","harness":"H4-security","mode":"input","status":"blocked","action":"block","risk_level":"high","input_hash":"c88e04411941c3a936354426b9502e70af61646e32ef595db538548b341f678a","output_hash":"c88e04411941c3a936354426b9502e70af61646e32ef595db538548b341f678a"} +{"timestamp":"2026-07-18T14:43:10Z","trace_id":"9869ba61-9424-4b68-a8b0-edd8ca22c589","harness":"H4-security","mode":"input","status":"blocked","action":"block","risk_level":"high","input_hash":"21c38bdbce47249b48f52c8a2f12548bd2a20ca859caefcb6fec5688fbef3114","output_hash":"21c38bdbce47249b48f52c8a2f12548bd2a20ca859caefcb6fec5688fbef3114"} +{"timestamp":"2026-07-18T14:43:11Z","trace_id":"45d7a403-a794-4e72-b23c-c73b1c00ff94","harness":"H4-security","mode":"input","status":"blocked","action":"block","risk_level":"high","input_hash":"05d331b33a12215a77e38947ad823dcac7301e870248747cffe422ac164c9e9b","output_hash":"05d331b33a12215a77e38947ad823dcac7301e870248747cffe422ac164c9e9b"} +{"timestamp":"2026-07-18T14:43:12Z","trace_id":"747fceec-62fb-45cf-9c42-f65bea05ff2a","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"d71fc8be325b5429c949372dc2705bf0ff3df55ac91b908fe05c6bf6f4b8a6d1","output_hash":"d71fc8be325b5429c949372dc2705bf0ff3df55ac91b908fe05c6bf6f4b8a6d1"} +{"timestamp":"2026-07-18T14:43:13Z","trace_id":"0e49a838-3b69-4e6f-9288-4845514d2360","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"e45a45f579a0098127d2aba3839e856d6eb9464f8dbbda20f9513f3bb43a0afc","output_hash":"e45a45f579a0098127d2aba3839e856d6eb9464f8dbbda20f9513f3bb43a0afc"} +{"timestamp":"2026-07-18T14:43:15Z","trace_id":"14c9b234-54d8-41a8-8db6-3dba0947dfbe","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"f2d76c459e7ef0ed4cedc3799a0e0b928bc452fa02ac2ae78365a8caf6835c04","output_hash":"f2d76c459e7ef0ed4cedc3799a0e0b928bc452fa02ac2ae78365a8caf6835c04"} +{"timestamp":"2026-07-18T14:43:16Z","trace_id":"9ddebdd0-53b1-40d8-a902-1ef77a2368b5","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"13fc0a22b765b9bb9f0708c8514b01eaad6fba52620a7bd784ffb66c5c833334","output_hash":"13fc0a22b765b9bb9f0708c8514b01eaad6fba52620a7bd784ffb66c5c833334"} +{"timestamp":"2026-07-18T14:43:17Z","trace_id":"062b988f-3ae5-4b3a-a3eb-7a01bbcf49cc","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"39c1928244c578e91ffa5d0c660c7645b450179ad13b7727dfb61036829e7828","output_hash":"39c1928244c578e91ffa5d0c660c7645b450179ad13b7727dfb61036829e7828"} +{"timestamp":"2026-07-18T14:43:18Z","trace_id":"0fb8c866-e1ae-4579-bf52-679b081d3af4","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"d44e8cce83d68f30ae974a55361202da71f8aeb4760c71e39807a751eae59ee7","output_hash":"d44e8cce83d68f30ae974a55361202da71f8aeb4760c71e39807a751eae59ee7"} +{"timestamp":"2026-07-18T14:43:19Z","trace_id":"d64afadb-96f4-4715-90f9-841db89007b2","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"2e7728e6fddd8a38eef2772040d93dfb1b42645b5a937dad6137bbbd6f995b5b","output_hash":"2e7728e6fddd8a38eef2772040d93dfb1b42645b5a937dad6137bbbd6f995b5b"} +{"timestamp":"2026-07-18T14:43:20Z","trace_id":"a267d99d-f4bf-48f6-a6ba-118ce42c9510","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"7c741791a5fae02e9745091b6c1dea70f6828be35eebfaa2709ce93d320856fc","output_hash":"7c741791a5fae02e9745091b6c1dea70f6828be35eebfaa2709ce93d320856fc"} +{"timestamp":"2026-07-18T14:43:22Z","trace_id":"b8666573-7093-44c0-8d36-8ad67f377a8c","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"c12fa684383db79e4d614ef647f32d5593dd82649fddb0b535f47bd29e9e649f","output_hash":"c12fa684383db79e4d614ef647f32d5593dd82649fddb0b535f47bd29e9e649f"} +{"timestamp":"2026-07-18T14:43:23Z","trace_id":"7bf592a4-a98d-4638-905f-fc6aedcebad5","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"6d31ef6f996aef91684a816ce3785b6fd3aeae70f86ff8f232584dadc6648ebf","output_hash":"6d31ef6f996aef91684a816ce3785b6fd3aeae70f86ff8f232584dadc6648ebf"} +{"timestamp":"2026-07-18T14:43:24Z","trace_id":"5bf0ae74-b252-4651-a230-9dd3cf5436a9","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"ce30abd8e84f4bd398f5b8003bb3cb45db4f6a339dfce930399632d0cc9e2d06","output_hash":"ce30abd8e84f4bd398f5b8003bb3cb45db4f6a339dfce930399632d0cc9e2d06"} +{"timestamp":"2026-07-18T14:43:25Z","trace_id":"8551641a-32c9-4756-81e1-d84a9af22500","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"638af166426556c327f6a8c8dd67e78c4d1fee5d5f871641ee2230534c2d2392","output_hash":"638af166426556c327f6a8c8dd67e78c4d1fee5d5f871641ee2230534c2d2392"} +{"timestamp":"2026-07-18T14:43:26Z","trace_id":"91b18fc4-3a14-4484-895d-8d97c28a04a4","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"b7a36fee7ef927b4948d3cb655c62d07ef832d7971bab79f0c58b39fc882c062","output_hash":"b7a36fee7ef927b4948d3cb655c62d07ef832d7971bab79f0c58b39fc882c062"} +{"timestamp":"2026-07-18T14:43:28Z","trace_id":"4712570d-0f19-4e24-aae8-cb9bd7f417ea","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"a6bff2bb626a4279808bbfea106f0ad0332aadf5d97d9dfad6d5d71d52c811c2","output_hash":"a6bff2bb626a4279808bbfea106f0ad0332aadf5d97d9dfad6d5d71d52c811c2"} +{"timestamp":"2026-07-18T14:43:29Z","trace_id":"5e937c7e-6111-4830-ba75-8818a36169df","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"e4c88f6e5737dc0c598fed069fa9a7d9acf2fab998ad9384af99a994ac469b9d","output_hash":"e4c88f6e5737dc0c598fed069fa9a7d9acf2fab998ad9384af99a994ac469b9d"} +{"timestamp":"2026-07-18T14:43:30Z","trace_id":"bff0130f-a1ca-404d-a905-105155b53ebc","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"f6192b27336a5a07fc6f36036e1544f49d18e4f5fb3a72e654eb21ee3bf0a9b0","output_hash":"f6192b27336a5a07fc6f36036e1544f49d18e4f5fb3a72e654eb21ee3bf0a9b0"} +{"timestamp":"2026-07-18T14:43:31Z","trace_id":"51da4bf8-4e3f-47b9-972f-ce401c9a22a5","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"6dd8036fd3966abfd8ffa64ce9d2f0ac258642f1a1327a7d1985a97ad2d101f8","output_hash":"6dd8036fd3966abfd8ffa64ce9d2f0ac258642f1a1327a7d1985a97ad2d101f8"} +{"timestamp":"2026-07-18T14:43:32Z","trace_id":"08dbc24d-bdc0-4ff3-ad9a-f1f94019102f","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"040a3a1b7f383b1b43146c2b417fa40249b230ae9519f5109eb83ad78d89f7bf","output_hash":"040a3a1b7f383b1b43146c2b417fa40249b230ae9519f5109eb83ad78d89f7bf"} +{"timestamp":"2026-07-18T14:43:33Z","trace_id":"dade1894-f205-40c5-9c4b-581bd7304a4f","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"e0f6e13fde69d80953c8714ae47877aa3b1953776b36ba43060115eb0fdcfe5c","output_hash":"e0f6e13fde69d80953c8714ae47877aa3b1953776b36ba43060115eb0fdcfe5c"} +{"timestamp":"2026-07-18T14:43:35Z","trace_id":"574bf6a7-3585-4d89-82c2-c2fad762f816","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"f706189973e7668a4995663642918bfbe02e7ff4af37ef124175218a62ae535e","output_hash":"f706189973e7668a4995663642918bfbe02e7ff4af37ef124175218a62ae535e"} +{"timestamp":"2026-07-18T14:43:36Z","trace_id":"d0d80e67-2791-42cf-a639-2fe233de9673","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"b4c5a413693632cd0549bc52a69491fa4c93dfef428ef3187664afaf86568004","output_hash":"b4c5a413693632cd0549bc52a69491fa4c93dfef428ef3187664afaf86568004"} +{"timestamp":"2026-07-18T14:43:37Z","trace_id":"07b04f14-0386-48bc-b122-d8b4b125d626","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"7a925b78ba31b1a61b4bd4a545459918d32232581dae3ac98dbab9c989db3848","output_hash":"7a925b78ba31b1a61b4bd4a545459918d32232581dae3ac98dbab9c989db3848"} +{"timestamp":"2026-07-18T14:43:38Z","trace_id":"4f9b48a5-53eb-4d06-8802-f4392e8499bf","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"8fe1ea7b9ba035d355a6550958a4bbf9d05e398e45b954824bb6055704400b36","output_hash":"8fe1ea7b9ba035d355a6550958a4bbf9d05e398e45b954824bb6055704400b36"} +{"timestamp":"2026-07-18T14:43:39Z","trace_id":"0774b519-8c87-4c58-8e4b-905353d6f2db","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"5e2381e034d3468bba17fbb1f21e5d3d54b5f1b745a2e574e2033eb0846e41a4","output_hash":"5e2381e034d3468bba17fbb1f21e5d3d54b5f1b745a2e574e2033eb0846e41a4"} +{"timestamp":"2026-07-18T14:43:41Z","trace_id":"f0fd7218-15c8-4925-9235-13f99067ea0a","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"193de17b63b40f174016e36168e04cf11d68c5c2cef1f6685ee59d7857a066cb","output_hash":"193de17b63b40f174016e36168e04cf11d68c5c2cef1f6685ee59d7857a066cb"} +{"timestamp":"2026-07-18T14:43:42Z","trace_id":"d5a1aedf-c687-43e3-a313-e7fa1568bf5c","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"d2aaec4c335231b242328b1bdbc98a5623b486cd4a75b2c41c33420a1c16a70a","output_hash":"d2aaec4c335231b242328b1bdbc98a5623b486cd4a75b2c41c33420a1c16a70a"} +{"timestamp":"2026-07-18T14:43:43Z","trace_id":"5b96b05c-5e6a-4017-9e40-3c13e049e58b","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"5902424959fa129c59b2658d885c599c721d662664a0e7629fdbf23d2be7fa5c","output_hash":"5902424959fa129c59b2658d885c599c721d662664a0e7629fdbf23d2be7fa5c"} +{"timestamp":"2026-07-18T14:43:44Z","trace_id":"f81bdadf-b36a-441d-81c8-b5ba880b2aec","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"504626c964b3729a1e4b618a71e76d11797e90e430f3817b2237566450c87187","output_hash":"504626c964b3729a1e4b618a71e76d11797e90e430f3817b2237566450c87187"} +{"timestamp":"2026-07-18T14:43:46Z","trace_id":"697ee7af-f22c-4075-a2cd-326ca3674462","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"425ef159698de45606c2d1b9ed154574e44bf2155b636af9b467b2f85f1918ae","output_hash":"425ef159698de45606c2d1b9ed154574e44bf2155b636af9b467b2f85f1918ae"} +{"timestamp":"2026-07-18T14:43:47Z","trace_id":"15e57a58-7418-4ea3-ae8c-718b1a02bd16","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"37dfd5e665459e473d5eb2e786e15fabdd0fb663b236e579c481a864a3ac085b","output_hash":"37dfd5e665459e473d5eb2e786e15fabdd0fb663b236e579c481a864a3ac085b"} +{"timestamp":"2026-07-18T14:43:48Z","trace_id":"b4273e93-4bf4-4e79-8117-343bda65454a","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"1c3ea254cfb6fe60fcb3dc1adcd2fd45a8c9fe6b19fec56ea18a431f75b3a5ed","output_hash":"1c3ea254cfb6fe60fcb3dc1adcd2fd45a8c9fe6b19fec56ea18a431f75b3a5ed"} +{"timestamp":"2026-07-18T14:43:49Z","trace_id":"9a91878f-b8cc-4753-9884-cdd92f5169ce","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"80fdac92d5883cd1de34ec3ec8119e137e07db9d45c58ce5aeea598a8ba2c5c2","output_hash":"80fdac92d5883cd1de34ec3ec8119e137e07db9d45c58ce5aeea598a8ba2c5c2"} +{"timestamp":"2026-07-18T14:43:50Z","trace_id":"c719f36f-f708-44da-b6c9-86b38c48bdb0","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"a5d351e56f49bb59c03cd23128b6bbc33fa6298e26d070ce70a8b929454a979a","output_hash":"a5d351e56f49bb59c03cd23128b6bbc33fa6298e26d070ce70a8b929454a979a"} +{"timestamp":"2026-07-18T14:43:51Z","trace_id":"e1531076-3dcd-4481-82c2-bbca76456218","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"98dd1db3eaa2a947125488ec5de9e8544223ff52e5dbac2eeb8457f1658f0d5b","output_hash":"98dd1db3eaa2a947125488ec5de9e8544223ff52e5dbac2eeb8457f1658f0d5b"} +{"timestamp":"2026-07-18T14:43:53Z","trace_id":"ec146914-4d65-4cfb-96fd-2d9be574f68c","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"d5b2719ac405da26f6bed3d590d61cb757b7c296782163073f7f1bad56a4a21f","output_hash":"d5b2719ac405da26f6bed3d590d61cb757b7c296782163073f7f1bad56a4a21f"} +{"timestamp":"2026-07-18T14:43:54Z","trace_id":"8f45a595-977c-4ded-b9d2-ae3f8483b1d8","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"c6cf8c13b061dd09e27af20ffb5f4d2cd3816a99f03ca2ffd6b582fcc0d2d387","output_hash":"c6cf8c13b061dd09e27af20ffb5f4d2cd3816a99f03ca2ffd6b582fcc0d2d387"} +{"timestamp":"2026-07-18T14:43:55Z","trace_id":"3768e10d-d177-4207-b43b-ef1dee211a50","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"8cf91e81f8c88246acb51edd34e23283f2e987a254ffa157660e4d3722066a31","output_hash":"8cf91e81f8c88246acb51edd34e23283f2e987a254ffa157660e4d3722066a31"} +{"timestamp":"2026-07-18T14:43:56Z","trace_id":"06a28b8e-4f69-4ddd-836c-f29f9603895d","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"21c8351a2747dfd79f03a179b5c33ff65c0957720dba7b9ae6779160dd1c4c0c","output_hash":"21c8351a2747dfd79f03a179b5c33ff65c0957720dba7b9ae6779160dd1c4c0c"} +{"timestamp":"2026-07-18T14:43:57Z","trace_id":"1065b7aa-44a2-4d54-940a-f9d3337a30ad","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"9eddc3f87d31e6f985c0e0010bc42975a8ee68c68d123cd3652d45da5c11d2ab","output_hash":"9eddc3f87d31e6f985c0e0010bc42975a8ee68c68d123cd3652d45da5c11d2ab"} +{"timestamp":"2026-07-18T14:43:59Z","trace_id":"95d88d57-f552-4731-9748-3bcff30e60ec","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"4b2eb9ee34011623d69c31aafc7d8910f9df92395b3f717064b6a84a81e3508e","output_hash":"4b2eb9ee34011623d69c31aafc7d8910f9df92395b3f717064b6a84a81e3508e"} +{"timestamp":"2026-07-18T14:44:00Z","trace_id":"995c1c0b-769b-4bb5-902f-fc40f84b6565","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"d81e744b03d2e92f30a9bbb11d1255b94b712b1b5c1ac4ece8054a4cdd0b74e7","output_hash":"d81e744b03d2e92f30a9bbb11d1255b94b712b1b5c1ac4ece8054a4cdd0b74e7"} +{"timestamp":"2026-07-18T14:44:01Z","trace_id":"904f9691-d466-4a81-854a-76baecbaf383","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"08d1390f42f0a9de26f6df55b82845e6bf5ce0227664f1ccef3594267ebdba32","output_hash":"08d1390f42f0a9de26f6df55b82845e6bf5ce0227664f1ccef3594267ebdba32"} +{"timestamp":"2026-07-18T14:43:58Z","trace_id":"acef5f14-cec2-49d6-93d7-f26ee6f70d35","harness":"H4-security","mode":"input","status":"requires_approval","action":"require_approval","risk_level":"high","input_hash":"0b05221919ba3ee3ca96f5ac495f45c555119a4a6e467505561a84e417494525","output_hash":"3549e9d50f28d85b79ee2983b4c87e85ed67f85461a8016b7934c48d97c11071"} +{"timestamp":"2026-07-18T14:44:02Z","trace_id":"379a7f40-bde5-4e91-b88e-de4bfecf2903","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"55e100c7caefcbb9a9a4da56ec4bc0cd97b32d491f65f6ed81e5581fa2a33e11","output_hash":"55e100c7caefcbb9a9a4da56ec4bc0cd97b32d491f65f6ed81e5581fa2a33e11"} +{"timestamp":"2026-07-18T14:44:04Z","trace_id":"182a1c3d-3b00-4bbe-a783-16f55d962e21","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"212f95ec9cd805f6ada1790aefb94cc55a92c3966625bb128ab94ead8dad3dab","output_hash":"212f95ec9cd805f6ada1790aefb94cc55a92c3966625bb128ab94ead8dad3dab"} +{"timestamp":"2026-07-18T14:44:05Z","trace_id":"aec9c579-6e04-43b5-811e-b13a53fc280c","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"97b20409d43d132839f961db78e92fe202cf4b379e630ccf0241375e2efefd31","output_hash":"97b20409d43d132839f961db78e92fe202cf4b379e630ccf0241375e2efefd31"} +{"timestamp":"2026-07-18T14:44:06Z","trace_id":"d0870ec1-ed93-40f3-b893-96bd0408ad1d","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"0d7fb05e444c069a7031ea819398a166fb691834a6402950213f2a0eba3d756d","output_hash":"0d7fb05e444c069a7031ea819398a166fb691834a6402950213f2a0eba3d756d"} +{"timestamp":"2026-07-18T14:44:07Z","trace_id":"4a7c316d-4124-408b-91b2-8ac1163d8f00","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"c80cb439e110fe3bdea13f7415f1a5b6a2a04fd7b2402db7591b7965fa1e6580","output_hash":"c80cb439e110fe3bdea13f7415f1a5b6a2a04fd7b2402db7591b7965fa1e6580"} +{"timestamp":"2026-07-18T14:44:08Z","trace_id":"79d95eec-72f7-42f2-8052-a0a4b8334ba5","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"d434aa811397cf41e5a8b6569411c888958cfce5f930165ee18f32266ecf6216","output_hash":"d434aa811397cf41e5a8b6569411c888958cfce5f930165ee18f32266ecf6216"} +{"timestamp":"2026-07-18T14:44:09Z","trace_id":"5f7288e6-8f83-4c21-a36c-010ba456c5c5","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"78a616322f25b042ac110105dcbca336a501580373c13de849931b470f473bb7","output_hash":"78a616322f25b042ac110105dcbca336a501580373c13de849931b470f473bb7"} +{"timestamp":"2026-07-18T14:44:11Z","trace_id":"f65fdb5c-de90-4f9d-9c68-53bc99e5dabc","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"a6347453d2f75831ab1412f83eae008a2677b34a7612ec86e339b0307ae7f36e","output_hash":"a6347453d2f75831ab1412f83eae008a2677b34a7612ec86e339b0307ae7f36e"} +{"timestamp":"2026-07-18T14:44:12Z","trace_id":"5c54e6a8-0c70-4fd8-897e-4ab48beac983","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"831630985b93cbf5ac4668a4f38409c0740f66062cf39adeb1dbf28fc056bea4","output_hash":"831630985b93cbf5ac4668a4f38409c0740f66062cf39adeb1dbf28fc056bea4"} +{"timestamp":"2026-07-18T14:44:13Z","trace_id":"97bad108-0f7e-4395-91b8-f808339e5059","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"4dbe3a992d1ef74c09c513cfd7e467cd0cd8503899648cc2360f59237dd767d1","output_hash":"4dbe3a992d1ef74c09c513cfd7e467cd0cd8503899648cc2360f59237dd767d1"} +{"timestamp":"2026-07-18T14:44:14Z","trace_id":"63ef51eb-4999-4fe0-94c1-f55e9822f0fb","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"ebdd68c4a94b4a63c3417c6f0a8de064e5bff9cf1811af5d198ef20ea169410a","output_hash":"ebdd68c4a94b4a63c3417c6f0a8de064e5bff9cf1811af5d198ef20ea169410a"} +{"timestamp":"2026-07-18T14:44:15Z","trace_id":"70d62887-023b-4a82-b832-f68d866f7870","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"ad218c93b7a0e3293148f25b70566e85deeb5663d364fc08bd053319b2c568cc","output_hash":"ad218c93b7a0e3293148f25b70566e85deeb5663d364fc08bd053319b2c568cc"} +{"timestamp":"2026-07-18T14:44:16Z","trace_id":"f0353064-013b-4d06-84b0-a87a8557dc2c","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"738c0418b325ff732a3c50e76888831a2981a685bd885ebfee07ea65c83fa691","output_hash":"738c0418b325ff732a3c50e76888831a2981a685bd885ebfee07ea65c83fa691"} +{"timestamp":"2026-07-18T14:44:18Z","trace_id":"f9e32fd2-0411-4ff3-8e1e-f8a52e74f4ea","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"fc797bb71e36678f32c64df3dbc0a8547ac3c067d3aa2f992c7d45ee0e31c7fa","output_hash":"fc797bb71e36678f32c64df3dbc0a8547ac3c067d3aa2f992c7d45ee0e31c7fa"} +{"timestamp":"2026-07-18T14:44:19Z","trace_id":"b66dd74f-e0b0-40a5-8189-7bf2216cea9d","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"74824e277be32ff2b29bf5e1d410299cc3d119b662b5c9850efd37a4d6bb8858","output_hash":"74824e277be32ff2b29bf5e1d410299cc3d119b662b5c9850efd37a4d6bb8858"} +{"timestamp":"2026-07-18T14:44:20Z","trace_id":"21c8b7e8-25f7-4316-a346-cd85ee617f68","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"a3c9f4dcf55edae7b55c9e4728974f6a7cbd825a2c4cf2b6128bf63868108989","output_hash":"a3c9f4dcf55edae7b55c9e4728974f6a7cbd825a2c4cf2b6128bf63868108989"} +{"timestamp":"2026-07-18T14:44:21Z","trace_id":"a34d1f6e-140c-4c80-93ca-b0cdedef331d","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"df4cfaa6a5b068441b8638000c471b2092379d900aa0960cc6342d8b45e3f3bb","output_hash":"df4cfaa6a5b068441b8638000c471b2092379d900aa0960cc6342d8b45e3f3bb"} +{"timestamp":"2026-07-18T14:44:22Z","trace_id":"9ed59c36-51fe-4726-b37c-df82e3046f09","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"e2c02e0cf356f0a94d543b02d772f6336f8cb6a06ba4d17f975fabab6428e588","output_hash":"e2c02e0cf356f0a94d543b02d772f6336f8cb6a06ba4d17f975fabab6428e588"} +{"timestamp":"2026-07-18T14:44:23Z","trace_id":"99443a73-fb98-40d0-81dc-ba581dc997d4","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"abd7b98c2761bad1c2bba17b691b9a6554045a65ca6a7eacd8ddc6e357e949c5","output_hash":"abd7b98c2761bad1c2bba17b691b9a6554045a65ca6a7eacd8ddc6e357e949c5"} +{"timestamp":"2026-07-18T14:44:25Z","trace_id":"6557334e-720e-4cb9-bbbf-10bf01e87aa1","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"85a9ac82a520b14a0f597938ae4897d93ec276316ce462f2b93a01e58fcd12e5","output_hash":"85a9ac82a520b14a0f597938ae4897d93ec276316ce462f2b93a01e58fcd12e5"} +{"timestamp":"2026-07-18T14:44:26Z","trace_id":"7a6d0be0-8ce4-4a95-a020-3433dac3b6d0","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"12e4d2821052ffcd950756b3c32573a26eb173e8d830cbeb208c14fef256227c","output_hash":"12e4d2821052ffcd950756b3c32573a26eb173e8d830cbeb208c14fef256227c"} +{"timestamp":"2026-07-18T14:44:27Z","trace_id":"940b003e-68f6-4fc0-ad2a-6c591f4c1678","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"80fdac92d5883cd1de34ec3ec8119e137e07db9d45c58ce5aeea598a8ba2c5c2","output_hash":"80fdac92d5883cd1de34ec3ec8119e137e07db9d45c58ce5aeea598a8ba2c5c2"} +{"timestamp":"2026-07-18T14:44:28Z","trace_id":"c3c14d00-cac5-480e-ad9b-4a7f3bdb8d6c","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"9d9cb0cf39a6ed22dbc0b42deb99de7c0e530e4fc31d94307aac1959ffca0598","output_hash":"9d9cb0cf39a6ed22dbc0b42deb99de7c0e530e4fc31d94307aac1959ffca0598"} +{"timestamp":"2026-07-18T14:44:30Z","trace_id":"367ce453-1921-4189-b6ed-76ca80fe3027","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"617a887bb41bebcb1ec4501fc82c47f46381da454a4e8eab7fc48538bc111e1f","output_hash":"617a887bb41bebcb1ec4501fc82c47f46381da454a4e8eab7fc48538bc111e1f"} +{"timestamp":"2026-07-18T14:44:31Z","trace_id":"f4ece33c-f1c4-4d0b-8627-1219bb30cd99","harness":"H4-security","mode":"input","status":"blocked","action":"block","risk_level":"high","input_hash":"1ff7da3eca8123cbcb12ec519fd4e4f35f96c469c67d36dd84595ac969934c0f","output_hash":"1ff7da3eca8123cbcb12ec519fd4e4f35f96c469c67d36dd84595ac969934c0f"} +{"timestamp":"2026-07-18T14:44:32Z","trace_id":"9c7a9c22-fa08-48b3-8a90-54f96940e29d","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"208dcb8dc5e487a413439771b559e875440e9728e0a920f88c84c91fd08ed16e","output_hash":"208dcb8dc5e487a413439771b559e875440e9728e0a920f88c84c91fd08ed16e"} +{"timestamp":"2026-07-18T14:44:34Z","trace_id":"c67c06e2-4913-4e1a-8d3d-2430fefaf119","harness":"H4-security","mode":"input","status":"blocked","action":"block","risk_level":"high","input_hash":"adf40d8b70a78805b061db2f1d4a10a10f8f6ffc0550865a73eaf100c48e33ce","output_hash":"adf40d8b70a78805b061db2f1d4a10a10f8f6ffc0550865a73eaf100c48e33ce"} +{"timestamp":"2026-07-18T14:44:35Z","trace_id":"3501fd9b-69cd-4894-b628-7a7db25dbd7a","harness":"H4-security","mode":"input","status":"blocked","action":"block","risk_level":"high","input_hash":"f2d66338e3550fcc527af0f6aa4475eca026b4538d2663a5512ce21bcf62ec14","output_hash":"f2d66338e3550fcc527af0f6aa4475eca026b4538d2663a5512ce21bcf62ec14"} +{"timestamp":"2026-07-18T14:44:36Z","trace_id":"22802ef7-86b0-4261-be0e-3bda72f47801","harness":"H4-security","mode":"input","status":"blocked","action":"block","risk_level":"high","input_hash":"68cf6726e1f23910eae350fbdac88e124c1553a2e557f1d43ff2a03486b94633","output_hash":"68cf6726e1f23910eae350fbdac88e124c1553a2e557f1d43ff2a03486b94633"} +{"timestamp":"2026-07-18T14:44:37Z","trace_id":"cb6e348d-7746-463f-b745-296a155c651f","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"11f1d3168daa61cfb1195bb8aed22b922bd13ccbbdeeec6cc5512b46e10ffedb","output_hash":"11f1d3168daa61cfb1195bb8aed22b922bd13ccbbdeeec6cc5512b46e10ffedb"} +{"timestamp":"2026-07-18T14:44:38Z","trace_id":"902fe32c-a669-455d-a050-f31ab891d812","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"a42b8d76d2bb6169c5ac5966b102d61b736612b2cae7b75d31ed7ba5e1969e3e","output_hash":"a42b8d76d2bb6169c5ac5966b102d61b736612b2cae7b75d31ed7ba5e1969e3e"} +{"timestamp":"2026-07-18T14:44:40Z","trace_id":"d81f2ac9-7d1e-4656-9ae1-7519296bb171","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"0324cae2f82b85eb3ea0b14b6552c806691ed709f5d39b7a869bef1d1f025d8e","output_hash":"0324cae2f82b85eb3ea0b14b6552c806691ed709f5d39b7a869bef1d1f025d8e"} +{"timestamp":"2026-07-18T14:44:41Z","trace_id":"84154b23-173e-456e-b665-ec0a881e6ad2","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"ac8e74508885448754819cf20380a866a4ad6db5c81a6f7fa86f9cf2a0b11588","output_hash":"ac8e74508885448754819cf20380a866a4ad6db5c81a6f7fa86f9cf2a0b11588"} +{"timestamp":"2026-07-18T14:44:42Z","trace_id":"d8e295d3-f99c-4dc5-b145-df82b4513ff0","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"1c300df66c8f200877df867c467c7c463b4945ffb9759058e26d06eac24b227e","output_hash":"1c300df66c8f200877df867c467c7c463b4945ffb9759058e26d06eac24b227e"} +{"timestamp":"2026-07-18T14:44:43Z","trace_id":"dcbc7d4f-5d7b-47f3-a5e8-404dbc4d5ee4","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"6bc4dae2df731fe9ade3a4110185ce8a10b837bb5a2c28888e55be15f74f02ba","output_hash":"6bc4dae2df731fe9ade3a4110185ce8a10b837bb5a2c28888e55be15f74f02ba"} +{"timestamp":"2026-07-18T14:44:44Z","trace_id":"d9a1280e-abde-4bf3-985d-dd3471bea7f0","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"cdf23170afe2506e48af8411b2b43e659acf0d327853342f515879d8ae609a52","output_hash":"cdf23170afe2506e48af8411b2b43e659acf0d327853342f515879d8ae609a52"} +{"timestamp":"2026-07-18T14:44:45Z","trace_id":"66993a92-9dc1-4de4-9a6c-8c2c2ac8efb2","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"630d23ff16317dcfc292c751b39577d35a319099204663b96e01759ba7ce8793","output_hash":"630d23ff16317dcfc292c751b39577d35a319099204663b96e01759ba7ce8793"} +{"timestamp":"2026-07-18T14:44:47Z","trace_id":"299be129-4e9b-4857-8933-70c767d8dba1","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"12770e3923fa48c66a0ce11864489cc8b4bac855d727ad2c9b03a168f194f692","output_hash":"12770e3923fa48c66a0ce11864489cc8b4bac855d727ad2c9b03a168f194f692"} +{"timestamp":"2026-07-18T14:44:48Z","trace_id":"c04b178f-dac6-43f8-be22-a0bc998f021a","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"0b9934cfabf40839989255f5b5ed29b8bba72453baebd685a09b4faca5f299b8","output_hash":"0b9934cfabf40839989255f5b5ed29b8bba72453baebd685a09b4faca5f299b8"} +{"timestamp":"2026-07-18T14:44:49Z","trace_id":"0d8b18c7-8646-42f1-933e-53a3e983b92d","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"ce1f8c05b6f5feb51ac7fa1dc583171d08cb22c089ead8722ca87fb03b6f77ec","output_hash":"ce1f8c05b6f5feb51ac7fa1dc583171d08cb22c089ead8722ca87fb03b6f77ec"} +{"timestamp":"2026-07-18T14:44:50Z","trace_id":"536e170a-357a-4f0b-902c-2b3182219303","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"30b40a2bebc735bdbf15e8a03abfb7df1e7b2032d278fa4422ae400390604ce1","output_hash":"30b40a2bebc735bdbf15e8a03abfb7df1e7b2032d278fa4422ae400390604ce1"} +{"timestamp":"2026-07-18T14:44:51Z","trace_id":"a52076e9-0a94-4a1f-a744-d7cb28880d9c","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"a1569f0fbb8154cdc2d30f981f5f3e59e03506b73b51bb95c789594218b9886b","output_hash":"a1569f0fbb8154cdc2d30f981f5f3e59e03506b73b51bb95c789594218b9886b"} +{"timestamp":"2026-07-18T14:44:52Z","trace_id":"f1127660-89e4-4e89-b642-17b57ad57c55","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"ba30fbe92c8db447c5409ab9650f6507c8244e968174c839227f1e05f4b5fbcb","output_hash":"ba30fbe92c8db447c5409ab9650f6507c8244e968174c839227f1e05f4b5fbcb"} +{"timestamp":"2026-07-18T14:44:54Z","trace_id":"29670b22-6eaa-4176-a64e-3fc3204871b5","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"4488f4bed0c7d5b1c768af626c5d3d646d05ec29e4050aca74d218d258158fa0","output_hash":"4488f4bed0c7d5b1c768af626c5d3d646d05ec29e4050aca74d218d258158fa0"} +{"timestamp":"2026-07-18T14:44:55Z","trace_id":"d6156e1f-758f-47ff-9fe3-6affec1d4480","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"5fcace13b6cd3e5a09bf3485eb37d91f1afe58ebc6e64d6aa5d91eae4e1fafe1","output_hash":"5fcace13b6cd3e5a09bf3485eb37d91f1afe58ebc6e64d6aa5d91eae4e1fafe1"} +{"timestamp":"2026-07-18T14:44:56Z","trace_id":"6e1963dc-5f49-4904-9842-369d7bd2f64f","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"2f33f36d70cfea74bc9c6dc2a3c616359a01dee94f33447de28465b92c7076ba","output_hash":"2f33f36d70cfea74bc9c6dc2a3c616359a01dee94f33447de28465b92c7076ba"} +{"timestamp":"2026-07-18T14:44:57Z","trace_id":"1c0cbf41-b4cd-448b-86d3-bf4e7c2174eb","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"7a9153687f53bb8392e89e4087ae9d8a1a31a2ccb6a561e0e8b46e0236607400","output_hash":"7a9153687f53bb8392e89e4087ae9d8a1a31a2ccb6a561e0e8b46e0236607400"} +{"timestamp":"2026-07-18T14:44:58Z","trace_id":"9855185b-a0b2-439e-b254-0e71ceb92706","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"7d6b48a5d6352f88f4eb27313dc4c26222dadd9529c1f368320935831fb4725a","output_hash":"7d6b48a5d6352f88f4eb27313dc4c26222dadd9529c1f368320935831fb4725a"} +{"timestamp":"2026-07-18T14:44:59Z","trace_id":"00a8eb32-1ef0-4617-ad48-586e9c1cb1f0","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"73ef5f2ab1806fc9ce6ced82ed72fbd1cb7d968845a63380269f3bfe3e2be7b0","output_hash":"73ef5f2ab1806fc9ce6ced82ed72fbd1cb7d968845a63380269f3bfe3e2be7b0"} +{"timestamp":"2026-07-18T14:45:01Z","trace_id":"92e7cd5d-c77e-42fd-929e-d2a93f8753b2","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"cfaa408e081dab46a9970fc52d742d7f169dec36e5ddb063236b3857526c1348","output_hash":"cfaa408e081dab46a9970fc52d742d7f169dec36e5ddb063236b3857526c1348"} +{"timestamp":"2026-07-18T14:45:02Z","trace_id":"64352a28-e5e3-47c8-be82-fd6cf710aeb1","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"14688e49977dbd42157ab19902cce7b5919dd1535254220f46a17259abf7a5b0","output_hash":"14688e49977dbd42157ab19902cce7b5919dd1535254220f46a17259abf7a5b0"} +{"timestamp":"2026-07-18T14:45:03Z","trace_id":"cd4763be-ae76-4d95-b6de-62f676aa61a1","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"4a49e3c9391cd7ffd92ece98f81cd0b3332afff79b654a0a0fc17be20efda151","output_hash":"4a49e3c9391cd7ffd92ece98f81cd0b3332afff79b654a0a0fc17be20efda151"} +{"timestamp":"2026-07-18T14:45:04Z","trace_id":"d4344025-3ae9-4a3b-b97f-b1635ce6503d","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"6d722e76fa3ee546c756e5a49d8e78c9775e4adb4d8067dd7b9056541c940264","output_hash":"6d722e76fa3ee546c756e5a49d8e78c9775e4adb4d8067dd7b9056541c940264"} +{"timestamp":"2026-07-18T14:45:05Z","trace_id":"d9b7d15e-d4e8-4578-be80-5100a4d3c0d5","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"50effbf62a46f5c0e43f7cd3d719ca8862c14df14b8f62d84fa27bed65936d7e","output_hash":"50effbf62a46f5c0e43f7cd3d719ca8862c14df14b8f62d84fa27bed65936d7e"} +{"timestamp":"2026-07-18T14:45:06Z","trace_id":"03126304-9e6b-4ec1-a3c8-4dd0786f4cdc","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"96266ad18d0a23862ced0d564a1034ee1852c73c5cfbde37039b09addeeb69fe","output_hash":"96266ad18d0a23862ced0d564a1034ee1852c73c5cfbde37039b09addeeb69fe"} +{"timestamp":"2026-07-18T14:45:08Z","trace_id":"b5891651-0d47-49a5-ad7b-94ad522ec62e","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"032c309b1d6d64165bd5b012c3a0bcc144f237c6a532d0348c12acbc92f3cd0a","output_hash":"032c309b1d6d64165bd5b012c3a0bcc144f237c6a532d0348c12acbc92f3cd0a"} +{"timestamp":"2026-07-18T14:45:09Z","trace_id":"8d4b7892-6a39-4e90-8a82-216b17bbccba","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"c98e80fdced75d468f38ac6d6fbf87579f192dc8222608b0da6d488c30de49b0","output_hash":"c98e80fdced75d468f38ac6d6fbf87579f192dc8222608b0da6d488c30de49b0"} +{"timestamp":"2026-07-18T14:45:10Z","trace_id":"trace-1784385910-60","harness":"H4-security","mode":"input","status":"requires_approval","action":"require_approval","risk_level":"high","input_hash":"0b05221919ba3ee3ca96f5ac495f45c555119a4a6e467505561a84e417494525","output_hash":"3549e9d50f28d85b79ee2983b4c87e85ed67f85461a8016b7934c48d97c11071"} +{"timestamp":"2026-07-18T14:45:10Z","trace_id":"f8ef40ca-f461-4cd9-87f0-6aab0895c2fb","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"44ba2461b13b30f6264a8c8a63714b3ac96153797fe2267b5b51f82a189cdd69","output_hash":"44ba2461b13b30f6264a8c8a63714b3ac96153797fe2267b5b51f82a189cdd69"} +{"timestamp":"2026-07-18T14:45:11Z","trace_id":"d781086e-1fcc-41de-804c-b7e286032e8d","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"6153fdff9486da5234136acf9c7a041456594ece67b9710269df44a83e83d6da","output_hash":"6153fdff9486da5234136acf9c7a041456594ece67b9710269df44a83e83d6da"} +{"timestamp":"2026-07-18T14:45:12Z","trace_id":"d4d39e1a-9bc8-419e-8fab-aa94bde09c53","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"e5add3e7298ce51b5ad5a0e67449963023bbdc2e5f1ec99107fe40ecd382c7bf","output_hash":"e5add3e7298ce51b5ad5a0e67449963023bbdc2e5f1ec99107fe40ecd382c7bf"} +{"timestamp":"2026-07-18T14:45:14Z","trace_id":"be6b32e8-e004-4525-aa81-821f75183176","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"6c8c3b94661076800e9ee8ef67959c3dfebc15be4f233227da301a8bd5e6d533","output_hash":"6c8c3b94661076800e9ee8ef67959c3dfebc15be4f233227da301a8bd5e6d533"} +{"timestamp":"2026-07-18T14:45:15Z","trace_id":"052cca95-7db8-4b6b-a540-e066822e0397","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"e620c3cc8fee0dfd8766f1af294f4f5b91656c4fb6df2d6d93dea550c5c7a873","output_hash":"e620c3cc8fee0dfd8766f1af294f4f5b91656c4fb6df2d6d93dea550c5c7a873"} +{"timestamp":"2026-07-18T14:45:16Z","trace_id":"0a85149b-d856-4909-a526-67816ff8228e","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"4d6111fbf21f5d06793fc55cf9ba45264255af6d9390b94743bf5345effb8844","output_hash":"4d6111fbf21f5d06793fc55cf9ba45264255af6d9390b94743bf5345effb8844"} +{"timestamp":"2026-07-18T14:45:17Z","trace_id":"3f82d234-43b3-493e-9118-091bd8a261a2","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"b30d2f2ac355eba557710d0f8fc4b02d99f9be976f2d8be544dd64b48099e48e","output_hash":"b30d2f2ac355eba557710d0f8fc4b02d99f9be976f2d8be544dd64b48099e48e"} +{"timestamp":"2026-07-18T15:07:33Z","trace_id":"trace-1784387253-1386","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"2788c9203adce8673587daf8c6b77d8e0773c4a75980fe569ec4549255a6e622","output_hash":"2788c9203adce8673587daf8c6b77d8e0773c4a75980fe569ec4549255a6e622"} +{"timestamp":"2026-07-18T15:07:33Z","trace_id":"trace-1784387253-1927","harness":"H4-security","mode":"input","status":"requires_approval","action":"require_approval","risk_level":"high","input_hash":"0b05221919ba3ee3ca96f5ac495f45c555119a4a6e467505561a84e417494525","output_hash":"3549e9d50f28d85b79ee2983b4c87e85ed67f85461a8016b7934c48d97c11071"} +{"timestamp":"2026-07-18T15:09:12Z","trace_id":"trace-1784387352-2699","harness":"H4-security","mode":"output","status":"pass","action":"allow","risk_level":"low","input_hash":"347c4a4eb327661cb0922068f6d6d3dc77afa018a43c3948d343eff49a97d6a3","output_hash":"347c4a4eb327661cb0922068f6d6d3dc77afa018a43c3948d343eff49a97d6a3"} +{"timestamp":"2026-07-18T15:23:13Z","trace_id":"6a1baf39-7195-4713-8fa0-1cbf742f23d0","harness":"H4-security","mode":"input","status":"requires_approval","action":"require_approval","risk_level":"high","input_hash":"f0fef5b4f847b86e471f2c2a7d82cb6730d0031c3b93821e94aedecf4fc1c258","output_hash":"62f58556df3b1e8d46c614c979f3941c79a5d70f63173b88f803585f7c40b0c7"} +{"timestamp":"2026-07-18T15:28:18Z","trace_id":"trace-1784388498-311","harness":"H4-security","mode":"input","status":"requires_approval","action":"require_approval","risk_level":"high","input_hash":"f0fef5b4f847b86e471f2c2a7d82cb6730d0031c3b93821e94aedecf4fc1c258","output_hash":"62f58556df3b1e8d46c614c979f3941c79a5d70f63173b88f803585f7c40b0c7"} +{"timestamp":"2026-07-18T15:38:46Z","trace_id":"trace-1784389126-45","harness":"H4-security","mode":"input","status":"requires_approval","action":"require_approval","risk_level":"high","input_hash":"f0fef5b4f847b86e471f2c2a7d82cb6730d0031c3b93821e94aedecf4fc1c258","output_hash":"62f58556df3b1e8d46c614c979f3941c79a5d70f63173b88f803585f7c40b0c7"} +{"timestamp":"2026-07-18T15:41:35Z","trace_id":"trace-1784389295-189","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"2788c9203adce8673587daf8c6b77d8e0773c4a75980fe569ec4549255a6e622","output_hash":"2788c9203adce8673587daf8c6b77d8e0773c4a75980fe569ec4549255a6e622"} +{"timestamp":"2026-07-18T15:41:36Z","trace_id":"trace-1784389296-719","harness":"H4-security","mode":"input","status":"requires_approval","action":"require_approval","risk_level":"high","input_hash":"f0fef5b4f847b86e471f2c2a7d82cb6730d0031c3b93821e94aedecf4fc1c258","output_hash":"62f58556df3b1e8d46c614c979f3941c79a5d70f63173b88f803585f7c40b0c7"} +{"timestamp":"2026-07-18T15:42:50Z","trace_id":"trace-1784389370-1333","harness":"H4-security","mode":"output","status":"pass","action":"allow","risk_level":"low","input_hash":"80f6847cb2f1783d23105c1c27890ca6dfd602e1b24e478bce132ea5cd89e430","output_hash":"36f364a676a29e19322f351e7fafc6a39d03795c62ef76cc825054d02e003431"} +{"timestamp":"2026-07-18T15:43:04Z","trace_id":"trace-1784389384-1511","harness":"H4-security","mode":"output","status":"pass","action":"allow","risk_level":"low","input_hash":"b88ace3a8bab6194be459ef31ac5d541e3452c95420e7216765a390e7a05decc","output_hash":"b88ace3a8bab6194be459ef31ac5d541e3452c95420e7216765a390e7a05decc"} +{"timestamp":"2026-07-18T15:43:14Z","trace_id":"trace-1784389394-1649","harness":"H4-security","mode":"output","status":"pass","action":"allow","risk_level":"low","input_hash":"c5e5de6b6429720738fe8a84df76cdde515a90512f579d0d0163afb26cd3a48a","output_hash":"c5e5de6b6429720738fe8a84df76cdde515a90512f579d0d0163afb26cd3a48a"} +{"timestamp":"2026-07-18T16:01:48Z","trace_id":"trace-1784390508-597","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"2788c9203adce8673587daf8c6b77d8e0773c4a75980fe569ec4549255a6e622","output_hash":"2788c9203adce8673587daf8c6b77d8e0773c4a75980fe569ec4549255a6e622"} +{"timestamp":"2026-07-18T16:01:48Z","trace_id":"trace-1784390508-1139","harness":"H4-security","mode":"input","status":"requires_approval","action":"require_approval","risk_level":"high","input_hash":"f0fef5b4f847b86e471f2c2a7d82cb6730d0031c3b93821e94aedecf4fc1c258","output_hash":"62f58556df3b1e8d46c614c979f3941c79a5d70f63173b88f803585f7c40b0c7"} +{"timestamp":"2026-07-18T16:02:02Z","trace_id":"trace-1784390522-1719","harness":"H4-security","mode":"output","status":"pass","action":"allow","risk_level":"low","input_hash":"1beee06bff072daf1459b7ea47128910b4027c1ace52af8ebfe68926942b5952","output_hash":"1beee06bff072daf1459b7ea47128910b4027c1ace52af8ebfe68926942b5952"} +{"timestamp":"2026-07-18T16:14:12Z","trace_id":"trace-1784391252-127","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"2788c9203adce8673587daf8c6b77d8e0773c4a75980fe569ec4549255a6e622","output_hash":"2788c9203adce8673587daf8c6b77d8e0773c4a75980fe569ec4549255a6e622"} +{"timestamp":"2026-07-18T16:14:13Z","trace_id":"trace-1784391253-657","harness":"H4-security","mode":"input","status":"requires_approval","action":"require_approval","risk_level":"high","input_hash":"f0fef5b4f847b86e471f2c2a7d82cb6730d0031c3b93821e94aedecf4fc1c258","output_hash":"62f58556df3b1e8d46c614c979f3941c79a5d70f63173b88f803585f7c40b0c7"} +{"timestamp":"2026-07-18T16:14:27Z","trace_id":"trace-1784391267-1217","harness":"H4-security","mode":"output","status":"pass","action":"allow","risk_level":"low","input_hash":"b464883daf70f5be6a94dffb8f5cb769fe4fbaf6fff5b3d2019e5c00b42cd7b7","output_hash":"744599bc48bbef1a967a8db392abb9df0dc7c271df9bbefe412ea87c1c42beb2"} +{"timestamp":"2026-07-18T16:14:42Z","trace_id":"trace-1784391282-1344","harness":"H4-security","mode":"output","status":"pass","action":"allow","risk_level":"low","input_hash":"83f86d8719a69d526e8de61a676e84bbb9d06da5863ca955309dd11720860ba2","output_hash":"83f86d8719a69d526e8de61a676e84bbb9d06da5863ca955309dd11720860ba2"} +{"timestamp":"2026-07-18T16:20:13Z","trace_id":"trace-1784391613-121","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"2788c9203adce8673587daf8c6b77d8e0773c4a75980fe569ec4549255a6e622","output_hash":"2788c9203adce8673587daf8c6b77d8e0773c4a75980fe569ec4549255a6e622"} +{"timestamp":"2026-07-18T16:20:13Z","trace_id":"trace-1784391613-651","harness":"H4-security","mode":"input","status":"requires_approval","action":"require_approval","risk_level":"high","input_hash":"f0fef5b4f847b86e471f2c2a7d82cb6730d0031c3b93821e94aedecf4fc1c258","output_hash":"62f58556df3b1e8d46c614c979f3941c79a5d70f63173b88f803585f7c40b0c7"} +{"timestamp":"2026-07-18T16:20:24Z","trace_id":"trace-1784391624-1178","harness":"H4-security","mode":"output","status":"pass","action":"allow","risk_level":"low","input_hash":"ebb3132eaf20051de7e2ae12c8fb1501a9e00cdde3fb98616689e2c76482567d","output_hash":"ebb3132eaf20051de7e2ae12c8fb1501a9e00cdde3fb98616689e2c76482567d"} diff --git a/.specify/logs/cost/metrics.jsonl b/.specify/logs/cost/metrics.jsonl index 395c50d..9dffa5a 100644 --- a/.specify/logs/cost/metrics.jsonl +++ b/.specify/logs/cost/metrics.jsonl @@ -56,3 +56,8 @@ {"timestamp":"2026-07-18T08:39:21Z","trace_id":"7c090b63-83a9-47fe-b1b1-c93387a98575","harness":"H6-agentops","agent":"speckit.implement","step":"13b-implement-attempt-2","status":"success","exit_code":0,"latency_ms":366,"retry_count":0,"input_tokens":14,"output_tokens":33,"total_tokens":47,"cost_estimate":9.4e-05,"cost_source":"word_count_estimate","hallucination_signals":0,"alerts":[],"input_hash":"c1302d67a5f8cdcd30aa22955100b3e20a5a3a3e3d15a82011c6bca60cc6638a","output_hash":"fe991db26a3995db8b60dc223a52645a7ee8c8b13d61793e5b62bad7ee5f37b8"} {"timestamp":"2026-07-18T08:39:26Z","trace_id":"13c63a9d-f38b-4228-aeaa-3811531daafd","harness":"H6-agentops","agent":"casan.reviewcode","step":"13c-reviewcode-attempt-2","status":"success","exit_code":0,"latency_ms":302,"retry_count":0,"input_tokens":14,"output_tokens":52,"total_tokens":66,"cost_estimate":0.000132,"cost_source":"word_count_estimate","hallucination_signals":0,"alerts":[],"input_hash":"5b8f3df70fd50957b746752b8043d4be1607f4f3ff764bdcea8bbee886c8c07b","output_hash":"9e78849db5e9e9c4cb63e8233c9f9efd9a2a315d7ed6852d11f7f1b51aa93fe5"} {"timestamp":"2026-07-18T08:39:31Z","trace_id":"1e730cab-c0be-4167-968d-9c07001ded27","harness":"H6-agentops","agent":"casan.testkit run-tests","step":"14-runtests","status":"success","exit_code":0,"latency_ms":23824,"retry_count":0,"input_tokens":15,"output_tokens":955,"total_tokens":970,"cost_estimate":0.00194,"cost_source":"word_count_estimate","hallucination_signals":0,"alerts":["high-latency"],"input_hash":"fe022699a0ecbe27b9b1276d469271cf6fbe25e3080e74774eacb8d7eef14d76","output_hash":"ce2cca9568be33c3d30db21b51359f626e1bfa762d01a4734551ce5e6cfe4563"} +{"timestamp": "2026-07-18T10:11:32Z", "trace_id": "f357ad57-0856-47a6-ad41-1c31ffd05151", "harness": "H6-agentops", "agent": "goal.orchestrator", "step": "local-worker-cloud-reviewer", "status": "success", "exit_code": 0, "latency_ms": 133660, "input_tokens": 7412, "output_tokens": 1605, "total_tokens": 9017, "cost_estimate": 0.0, "cost_source": "provider_usage_logs", "input_hash": "21a94d09a3c4584be963b86fa294a31ad8303871de74b467ee205d170489fc3c", "output_hash": "6441857790afc3926df927868a07ec153ec8d67adff15f5e3876775b2c96671f"} +{"timestamp": "2026-07-18T11:20:19Z", "trace_id": "c8dee8e4-cca3-434d-b87e-d2dd2cf92c22", "harness": "H6-agentops", "agent": "goal.orchestrator", "step": "local-worker-cloud-reviewer", "status": "success", "exit_code": 0, "latency_ms": 162448, "input_tokens": 6437, "output_tokens": 2109, "total_tokens": 8546, "cost_estimate": 0.0, "cost_source": "provider_usage_logs", "input_hash": "3db6096be1bf9ed65e06f91ce5f2dcf0e8d5e7dc00ed7f1665a94e0e4b7023a6", "output_hash": "571786487c86989de3aa941063d74ecc2040ea81807a3073747bb988ea0c70ea"} +{"timestamp": "2026-07-18T14:31:28Z", "trace_id": "0a95a682-d6f9-4380-b744-e7331c94afeb", "harness": "H6-agentops", "agent": "goal.orchestrator", "step": "local-worker-cloud-reviewer", "status": "degraded", "exit_code": 0, "latency_ms": 239177, "input_tokens": 2731, "output_tokens": 1400, "total_tokens": 4131, "cost_estimate": 0.0, "cost_source": "provider_usage_logs", "input_hash": "13bb8d002e46be4ad4741f384172f40c3bcbacc0629b4406d58f0c74a3868b56", "output_hash": "d0a346d08e7a904a0420722920924c7aa6aa35dcf1e4d4f0d53c0c2fd31558a6"} +{"timestamp": "2026-07-18T15:43:14Z", "trace_id": "57a9dc54-347a-45b8-bc50-fb70b11b20d7", "harness": "H6-agentops", "agent": "goal.orchestrator", "step": "local-worker-cloud-reviewer", "status": "degraded", "exit_code": 0, "latency_ms": 98719, "input_tokens": 13917, "output_tokens": 3269, "total_tokens": 17186, "cost_estimate": 0.0, "cost_source": "provider_usage_logs", "input_hash": "2788c9203adce8673587daf8c6b77d8e0773c4a75980fe569ec4549255a6e622", "output_hash": "611508f01c167d8b631fc052488385d12e5f6998683cc347958462d702084bba"} +{"timestamp": "2026-07-18T16:20:55Z", "trace_id": "8d594970-d8bb-46bf-bf48-4c52c6499d75", "harness": "H6-agentops", "agent": "goal.orchestrator", "step": "local-worker-cloud-reviewer", "status": "degraded", "exit_code": 0, "latency_ms": 41889, "input_tokens": 5220, "output_tokens": 1494, "total_tokens": 6714, "cost_estimate": 0.0, "cost_source": "provider_usage_logs", "input_hash": "2788c9203adce8673587daf8c6b77d8e0773c4a75980fe569ec4549255a6e622", "output_hash": "611508f01c167d8b631fc052488385d12e5f6998683cc347958462d702084bba"} diff --git a/.specify/logs/level5/approval-nonces.txt b/.specify/logs/level5/approval-nonces.txt index 1426b1a..7cbdfdb 100644 --- a/.specify/logs/level5/approval-nonces.txt +++ b/.specify/logs/level5/approval-nonces.txt @@ -3,3 +3,24 @@ 5c6def41467ad2932761d368ceac671fa8f66df1c46191a01735d354ffe6535a 8b9d6e3fd9e6efe0d962d7e16a97b0c911f2721d323089ebdc97eb41e06ddadc cd76a834731b45b1023a188752e46dd1089126ad8bfa7aba5add9e08050e640a +d380470da66b175d55eb11c81ad0d2ca54e15c19702c456291d9755a073da2eb +18e2471ce08092a628b8309aa491ff0c7a06183afe294653e74a961b798d4356 +8589bdc57c03d4e9ca1b2f01be50ccc487041009158bf6f0e9389c5212377f31 +a176f96b4658ddde5d676d750689893af3f052de6ffafccda0718fe00737bce2 +3db172719c869e71ecd50de65341cb71c8f787abc120d8c54722b4e4397349e4 +d27b86c756726177d24e61f33632698832a4aded23dc4e27853a2de18e972395 +a167a71338a7431453248e5c1cb462d7562863eb33231a2ce2d334e7d146f1f6 +8d01dea6e037ebca5afe732b5c537ddb7253fe415416c90f5f688a1eacee1a14 +61bc62a93b390dea77b16024dc70fa3f711e419e67f4d716ad3b01a3ec834aa1 +2a18df1beb50c19cfd960e7130d65bc323b035e316de670d38cb4467dfaae295 +1ebdd1dc6987205d8c544862d36df3b91388778187d04bde5ea946d1c9dc8217 +6272e3e159e39f94b76431f2e7b6ec909fd18b52257af09e19d495d486d05d3d +f442a1d89bac8ac962f8e3a3645f7b6ccc3d54936003e5adf0dfa09762047c82 +83f3c8255974d337019734ac1f73366daff832fff9777c38a460f33e5dfc5fc5 +3d65fedb7d80f5d4c769ec72c0b880cf463cd5118eaa684f378c80e6f9b9bfe8 +435e4bbfc987dd0007264f127e6b546e4ef0ef9099025867ab7fe65e20e76704 +40166e4cf99b696b66e3edd64faae3a77c5933db3bb9c08a78a9c1568a8423dd +e82da705e80ad9b32a1f62c063d0cee297838316035d9aa2de41a6346269bda5 +bcbb58baa5743a85a3507a1023c4cc4535910c0d4d178df65ce36192060551b6 +e9865086c7caec136ab8928aca5c772fcf829b247a63e6e598813c559014cda5 +ece8298d32b92d9a7dec0f683b7d0e44ddefb92b9b81826743bf4a1c880c9d9c diff --git a/.specify/logs/level5/provider-usage.jsonl b/.specify/logs/level5/provider-usage.jsonl index 4c7f40a..990a1e0 100644 --- a/.specify/logs/level5/provider-usage.jsonl +++ b/.specify/logs/level5/provider-usage.jsonl @@ -55,3 +55,41 @@ {"timestamp": "2026-07-11T08:23:30Z", "harness": "L5-provider-telemetry", "provider": "ollama", "model": "ornith:9b", "run_id": "adhoc", "step": "generate", "role": "generate", "input_tokens": 2448, "output_tokens": 1400, "total_tokens": 3848, "cost_usd": 0.0, "cost_source": "ollama_local_real_tokens", "latency_ms": 94149, "status": "success"} {"timestamp": "2026-07-18T08:37:10Z", "harness": "L5-provider-telemetry", "provider": "ollama", "model": "ornith:9b", "run_id": "adhoc", "step": "04-reviewspec", "role": "judge", "input_tokens": 546, "output_tokens": 3, "total_tokens": 549, "cost_usd": 0.0, "cost_source": "ollama_local_real_tokens", "latency_ms": 9200, "usage_available": true, "status": "success"} {"timestamp": "2026-07-18T08:39:16Z", "harness": "L5-provider-telemetry", "provider": "ollama", "model": "ornith:9b", "run_id": "adhoc", "step": "13-reviewcode", "role": "judge", "input_tokens": 585, "output_tokens": 3, "total_tokens": 588, "cost_usd": 0.0, "cost_source": "ollama_local_real_tokens", "latency_ms": 3086, "usage_available": true, "status": "success"} +{"timestamp": "2026-07-18T10:09:59Z", "harness": "L5-provider-telemetry", "provider": "ollama", "model": "ornith:9b", "run_id": "adhoc", "step": "generate", "role": "generate", "input_tokens": 3666, "output_tokens": 298, "total_tokens": 3964, "cost_usd": 0.0, "cost_source": "ollama_local_real_tokens", "latency_ms": 40271, "usage_available": true, "status": "success"} +{"timestamp": "2026-07-18T10:11:32Z", "harness": "L5-provider-telemetry", "provider": "ollama", "model": "ornith:9b", "run_id": "adhoc", "step": "generate", "role": "generate", "input_tokens": 3746, "output_tokens": 1307, "total_tokens": 5053, "cost_usd": 0.0, "cost_source": "ollama_local_real_tokens", "latency_ms": 91828, "usage_available": true, "status": "success"} +{"timestamp": "2026-07-18T11:18:39Z", "harness": "L5-provider-telemetry", "provider": "ollama", "model": "ornith:9b", "run_id": "adhoc", "step": "generate", "role": "generate", "input_tokens": 2970, "output_tokens": 709, "total_tokens": 3679, "cost_usd": 0.0, "cost_source": "ollama_local_real_tokens", "latency_ms": 61918, "usage_available": true, "status": "success"} +{"timestamp": "2026-07-18T11:20:19Z", "harness": "L5-provider-telemetry", "provider": "ollama", "model": "ornith:9b", "run_id": "adhoc", "step": "generate", "role": "generate", "input_tokens": 3467, "output_tokens": 1400, "total_tokens": 4867, "cost_usd": 0.0, "cost_source": "ollama_local_real_tokens", "latency_ms": 99087, "usage_available": true, "status": "success"} +{"timestamp": "2026-07-18T11:39:41Z", "harness": "L5-provider-telemetry", "provider": "ollama", "model": "ornith:9b", "run_id": "adhoc", "step": "generate", "role": "generate", "input_tokens": 8536, "output_tokens": 1400, "total_tokens": 9936, "cost_usd": 0.0, "cost_source": "ollama_local_real_tokens", "latency_ms": 121958, "usage_available": true, "status": "success"} +{"timestamp": "2026-07-18T11:40:29Z", "harness": "L5-provider-telemetry", "provider": "ollama", "model": "ornith:9b", "run_id": "adhoc", "step": "generate", "role": "generate", "input_tokens": 9657, "output_tokens": 92, "total_tokens": 9749, "cost_usd": 0.0, "cost_source": "ollama_local_real_tokens", "latency_ms": 47278, "usage_available": true, "status": "success"} +{"timestamp": "2026-07-18T12:20:41Z", "harness": "L5-provider-telemetry", "provider": "ollama", "model": "ornith:9b", "run_id": "adhoc", "step": "generate", "role": "generate", "input_tokens": 8536, "output_tokens": 181, "total_tokens": 8717, "cost_usd": 0.0, "cost_source": "ollama_local_real_tokens", "latency_ms": 63284, "usage_available": true, "status": "success"} +{"timestamp": "2026-07-18T14:20:43Z", "harness": "L5-provider-telemetry", "provider": "ollama", "model": "ornith:9b", "run_id": "adhoc", "step": "generate", "role": "generate", "input_tokens": 8667, "output_tokens": 116, "total_tokens": 8783, "cost_usd": 0.0, "cost_source": "ollama_local_real_tokens", "latency_ms": 58521, "usage_available": true, "status": "success"} +{"timestamp": "2026-07-18T14:21:34Z", "harness": "L5-provider-telemetry", "provider": "ollama", "model": "ornith:9b", "run_id": "adhoc", "step": "generate", "role": "generate", "input_tokens": 8812, "output_tokens": 62, "total_tokens": 8874, "cost_usd": 0.0, "cost_source": "ollama_local_real_tokens", "latency_ms": 51545, "usage_available": true, "status": "success"} +{"timestamp": "2026-07-18T14:28:58Z", "harness": "L5-provider-telemetry", "provider": "ollama", "model": "ornith:9b", "run_id": "adhoc", "step": "generate", "role": "generate", "input_tokens": 2731, "output_tokens": 1400, "total_tokens": 4131, "cost_usd": 0.0, "cost_source": "ollama_local_real_tokens", "latency_ms": 88380, "usage_available": true, "status": "success"} +{"timestamp": "2026-07-18T14:31:28Z", "harness": "L5-provider-telemetry", "provider": "ollama", "model": "ornith:9b", "run_id": "adhoc", "step": "generate", "role": "generate", "input_tokens": 3943, "output_tokens": 1400, "total_tokens": 5343, "cost_usd": 0.0, "cost_source": "ollama_local_real_tokens", "latency_ms": 102649, "usage_available": true, "status": "success"} +{"timestamp": "2026-07-18T15:09:12Z", "harness": "L5-provider-telemetry", "provider": "ollama", "model": "ornith:9b", "run_id": "adhoc", "step": "generate", "role": "generate", "input_tokens": 8848, "output_tokens": 1070, "total_tokens": 9918, "cost_usd": 0.0, "cost_source": "ollama_local_real_tokens", "latency_ms": 98948, "usage_available": true, "status": "success"} +{"timestamp": "2026-07-18T15:14:16Z", "harness": "L5-provider-telemetry", "provider": "ollama", "model": "ornith:9b", "run_id": "adhoc", "step": "generate", "role": "generate", "input_tokens": 9891, "output_tokens": 2469, "total_tokens": 12360, "cost_usd": 0.0, "cost_source": "ollama_local_real_tokens", "latency_ms": 183220, "usage_available": true, "status": "success"} +{"timestamp": "2026-07-18T15:20:37Z", "harness": "L5-provider-telemetry", "provider": "ollama", "model": "ornith:9b", "run_id": "adhoc", "step": "generate", "role": "generate", "input_tokens": 112, "output_tokens": 121, "total_tokens": 233, "cost_usd": 0.0, "cost_source": "ollama_local_real_tokens", "latency_ms": 24747, "usage_available": true, "status": "success"} +{"timestamp": "2026-07-18T15:27:33Z", "harness": "L5-provider-telemetry", "provider": "ollama", "model": "ornith:9b", "run_id": "adhoc", "step": "generate", "role": "generate", "input_tokens": 117, "output_tokens": 211, "total_tokens": 328, "cost_usd": 0.0, "cost_source": "ollama_local_real_tokens", "latency_ms": 37080, "usage_available": true, "status": "success"} +{"timestamp": "2026-07-18T15:42:52Z", "harness": "L5-provider-telemetry", "provider": "openai", "model": "gpt-5.3-codex", "run_id": "adhoc", "step": "generate", "role": "generate", "input_tokens": 55, "output_tokens": 44, "total_tokens": 99, "cost_usd": 0.0, "cost_source": "openai_api_real_tokens", "latency_ms": 2080, "usage_available": true, "status": "success"} +{"timestamp": "2026-07-18T15:43:04Z", "harness": "L5-provider-telemetry", "provider": "openai", "model": "gpt-5.3-codex", "run_id": "adhoc", "step": "generate", "role": "generate", "input_tokens": 7398, "output_tokens": 1869, "total_tokens": 9267, "cost_usd": 0.0, "cost_source": "openai_api_real_tokens", "latency_ms": 11582, "usage_available": true, "status": "success"} +{"timestamp": "2026-07-18T15:43:14Z", "harness": "L5-provider-telemetry", "provider": "openai", "model": "gpt-5.3-codex", "run_id": "adhoc", "step": "generate", "role": "generate", "input_tokens": 6519, "output_tokens": 1400, "total_tokens": 7919, "cost_usd": 0.0, "cost_source": "openai_api_real_tokens", "latency_ms": 9500, "usage_available": true, "status": "success"} +{"timestamp": "2026-07-18T15:47:05Z", "harness": "L5-provider-telemetry", "provider": "openai", "model": "gpt-5.3-codex", "run_id": "adhoc", "step": "generate", "role": "generate", "input_tokens": 55, "output_tokens": 58, "total_tokens": 113, "cost_usd": 0.0, "cost_source": "openai_api_real_tokens", "latency_ms": 1958, "usage_available": true, "status": "success"} +{"timestamp": "2026-07-18T16:01:51Z", "harness": "L5-provider-telemetry", "provider": "openai", "model": "gpt-5.3-codex", "run_id": "adhoc", "step": "generate", "role": "generate", "input_tokens": 55, "output_tokens": 61, "total_tokens": 116, "cost_usd": 0.0, "cost_source": "openai_api_real_tokens", "latency_ms": 1969, "usage_available": true, "status": "success"} +{"timestamp": "2026-07-18T16:02:02Z", "harness": "L5-provider-telemetry", "provider": "openai", "model": "gpt-5.3-codex", "run_id": "adhoc", "step": "generate", "role": "generate", "input_tokens": 5220, "output_tokens": 1737, "total_tokens": 6957, "cost_usd": 0.0, "cost_source": "openai_api_real_tokens", "latency_ms": 10775, "usage_available": true, "status": "success"} +{"timestamp": "2026-07-18T16:02:12Z", "harness": "L5-provider-telemetry", "provider": "openai", "model": "gpt-5.3-codex", "run_id": "adhoc", "step": "generate", "role": "generate", "input_tokens": 7079, "output_tokens": 1587, "total_tokens": 8666, "cost_usd": 0.0, "cost_source": "openai_api_real_tokens", "latency_ms": 10218, "usage_available": true, "status": "success"} +{"timestamp": "2026-07-18T16:02:21Z", "harness": "L5-provider-telemetry", "provider": "openai", "model": "gpt-5.3-codex", "run_id": "adhoc", "step": "generate", "role": "generate", "input_tokens": 6929, "output_tokens": 1587, "total_tokens": 8516, "cost_usd": 0.0, "cost_source": "openai_api_real_tokens", "latency_ms": 9335, "usage_available": true, "status": "success"} +{"timestamp": "2026-07-18T16:02:31Z", "harness": "L5-provider-telemetry", "provider": "openai", "model": "gpt-5.3-codex", "run_id": "adhoc", "step": "generate", "role": "generate", "input_tokens": 6929, "output_tokens": 1626, "total_tokens": 8555, "cost_usd": 0.0, "cost_source": "openai_api_real_tokens", "latency_ms": 9741, "usage_available": true, "status": "success"} +{"timestamp": "2026-07-18T16:14:15Z", "harness": "L5-provider-telemetry", "provider": "openai", "model": "gpt-5.3-codex", "run_id": "adhoc", "step": "generate", "role": "generate", "input_tokens": 55, "output_tokens": 58, "total_tokens": 113, "cost_usd": 0.0, "cost_source": "openai_api_real_tokens", "latency_ms": 1837, "usage_available": true, "status": "success"} +{"timestamp": "2026-07-18T16:14:27Z", "harness": "L5-provider-telemetry", "provider": "openai", "model": "gpt-5.3-codex", "run_id": "adhoc", "step": "generate", "role": "generate", "input_tokens": 5220, "output_tokens": 2013, "total_tokens": 7233, "cost_usd": 0.0, "cost_source": "openai_api_real_tokens", "latency_ms": 12235, "usage_available": true, "status": "success"} +{"timestamp": "2026-07-18T16:14:42Z", "harness": "L5-provider-telemetry", "provider": "openai", "model": "gpt-5.3-codex", "run_id": "adhoc", "step": "generate", "role": "generate", "input_tokens": 7374, "output_tokens": 2416, "total_tokens": 9790, "cost_usd": 0.0, "cost_source": "openai_api_real_tokens", "latency_ms": 14349, "usage_available": true, "status": "success"} +{"timestamp": "2026-07-18T16:20:24Z", "harness": "L5-provider-telemetry", "provider": "openai", "model": "gpt-5.3-codex", "run_id": "adhoc", "step": "generate", "role": "generate", "input_tokens": 5220, "output_tokens": 1494, "total_tokens": 6714, "cost_usd": 0.0, "cost_source": "openai_api_real_tokens", "latency_ms": 10422, "usage_available": true, "status": "success"} +{"timestamp": "2026-07-18T16:31:43Z", "harness": "L5-provider-telemetry", "provider": "ollama", "model": "ornith:9b", "run_id": "adhoc", "step": "classify", "role": "classify", "input_tokens": 216, "output_tokens": 2, "total_tokens": 218, "cost_usd": 0.0, "cost_source": "ollama_local_real_tokens", "latency_ms": 6521, "usage_available": true, "status": "success"} +{"timestamp": "2026-07-18T16:32:09Z", "harness": "L5-provider-telemetry", "provider": "ollama", "model": "ornith:9b", "run_id": "adhoc", "step": "generate", "role": "generate", "input_tokens": 579, "output_tokens": 254, "total_tokens": 833, "cost_usd": 0.0, "cost_source": "ollama_local_real_tokens", "latency_ms": 24956, "usage_available": true, "status": "success"} +{"timestamp": "2026-07-18T16:32:10Z", "harness": "L5-provider-telemetry", "provider": "ollama", "model": "ornith:9b", "run_id": "adhoc", "step": "classify", "role": "classify", "input_tokens": 216, "output_tokens": 2, "total_tokens": 218, "cost_usd": 0.0, "cost_source": "ollama_local_real_tokens", "latency_ms": 611, "usage_available": true, "status": "success"} +{"timestamp": "2026-07-18T16:32:44Z", "harness": "L5-provider-telemetry", "provider": "ollama", "model": "ornith:9b", "run_id": "adhoc", "step": "generate", "role": "generate", "input_tokens": 579, "output_tokens": 368, "total_tokens": 947, "cost_usd": 0.0, "cost_source": "ollama_local_real_tokens", "latency_ms": 33412, "usage_available": true, "status": "success"} +{"timestamp": "2026-07-18T16:32:46Z", "harness": "L5-provider-telemetry", "provider": "ollama", "model": "ornith:9b", "run_id": "adhoc", "step": "classify", "role": "classify", "input_tokens": 216, "output_tokens": 2, "total_tokens": 218, "cost_usd": 0.0, "cost_source": "ollama_local_real_tokens", "latency_ms": 2137, "usage_available": true, "status": "success"} +{"timestamp": "2026-07-18T16:33:14Z", "harness": "L5-provider-telemetry", "provider": "ollama", "model": "ornith:9b", "run_id": "adhoc", "step": "generate", "role": "generate", "input_tokens": 666, "output_tokens": 266, "total_tokens": 932, "cost_usd": 0.0, "cost_source": "ollama_local_real_tokens", "latency_ms": 27139, "usage_available": true, "status": "success"} +{"timestamp": "2026-07-18T16:33:17Z", "harness": "L5-provider-telemetry", "provider": "ollama", "model": "ornith:9b", "run_id": "adhoc", "step": "classify", "role": "classify", "input_tokens": 216, "output_tokens": 2, "total_tokens": 218, "cost_usd": 0.0, "cost_source": "ollama_local_real_tokens", "latency_ms": 2214, "usage_available": true, "status": "success"} +{"timestamp": "2026-07-18T16:33:44Z", "harness": "L5-provider-telemetry", "provider": "ollama", "model": "ornith:9b", "run_id": "adhoc", "step": "generate", "role": "generate", "input_tokens": 579, "output_tokens": 286, "total_tokens": 865, "cost_usd": 0.0, "cost_source": "ollama_local_real_tokens", "latency_ms": 26962, "usage_available": true, "status": "success"} +{"timestamp": "2026-07-18T16:33:49Z", "harness": "L5-provider-telemetry", "provider": "ollama", "model": "ornith:9b", "run_id": "adhoc", "step": "classify", "role": "classify", "input_tokens": 216, "output_tokens": 2, "total_tokens": 218, "cost_usd": 0.0, "cost_source": "ollama_local_real_tokens", "latency_ms": 2130, "usage_available": true, "status": "success"} +{"timestamp": "2026-07-18T16:34:15Z", "harness": "L5-provider-telemetry", "provider": "ollama", "model": "ornith:9b", "run_id": "adhoc", "step": "generate", "role": "generate", "input_tokens": 579, "output_tokens": 267, "total_tokens": 846, "cost_usd": 0.0, "cost_source": "ollama_local_real_tokens", "latency_ms": 25684, "usage_available": true, "status": "success"} diff --git a/Dockerfile.control-panel-api b/Dockerfile.control-panel-api index 8779a47..08b3db9 100644 --- a/Dockerfile.control-panel-api +++ b/Dockerfile.control-panel-api @@ -6,8 +6,11 @@ RUN apt-get update -qq && apt-get install -y -qq python3 python-is-python3 curl COPY package.json package-lock.json ./ COPY packages/casan-control-panel/backend/package.json ./packages/casan-control-panel/backend/ +COPY packages/casan-control-panel/frontend/package.json ./packages/casan-control-panel/frontend/ +COPY apps/okr/backend/package.json ./apps/okr/backend/ +COPY apps/okr/frontend/package.json ./apps/okr/frontend/ -RUN npm ci -w @casan/control-panel-backend +RUN npm ci COPY packages/casan-control-panel/backend ./packages/casan-control-panel/backend @@ -21,12 +24,14 @@ RUN apt-get update -qq && apt-get install -y -qq python3 python-is-python3 curl COPY package.json package-lock.json ./ COPY packages/casan-control-panel/backend/package.json ./packages/casan-control-panel/backend/ +COPY packages/casan-control-panel/frontend/package.json ./packages/casan-control-panel/frontend/ COPY --from=builder /app/node_modules ./node_modules COPY --from=builder /app/packages/casan-control-panel/backend/dist ./packages/casan-control-panel/backend/dist COPY packages/casan-harness/scripts ./packages/casan-harness/scripts COPY packages/casan-harness/config ./packages/casan-harness/config COPY packages/casan-harness/security ./packages/casan-harness/security COPY packages/casan-harness/level5/project-registry.json ./packages/casan-harness/level5/project-registry.json +COPY packages/casan-devkit ./packages/casan-devkit # Read-only, build-time workspace snapshots. Goal orchestration resolves only roots # registered in project-registry.json and never accepts a browser-supplied path. diff --git a/apps/okr/backend/src/key-results/key-results.service.ts b/apps/okr/backend/src/key-results/key-results.service.ts index d96bd4e..6a7cb8f 100644 --- a/apps/okr/backend/src/key-results/key-results.service.ts +++ b/apps/okr/backend/src/key-results/key-results.service.ts @@ -70,9 +70,9 @@ export class KeyResultsService { createdById: user.sub, }, }); + await this.recalculateObjectiveStatus(existing.objectiveId, tx); return keyResult; }); - await this.recalculateObjectiveStatus(existing.objectiveId); return updated; } @@ -88,14 +88,17 @@ export class KeyResultsService { } } - private async recalculateObjectiveStatus(objectiveId: number): Promise { - const keyResults = await this.prisma.keyResult.findMany({ where: { objectiveId } }); + private async recalculateObjectiveStatus( + objectiveId: number, + tx: Prisma.TransactionClient = this.prisma, + ): Promise { + const keyResults = await tx.keyResult.findMany({ where: { objectiveId } }); const average = keyResults.length === 0 ? 0 : Math.round(keyResults.reduce((total, keyResult) => total + keyResult.progress, 0) / keyResults.length); const status = average === 0 ? 'NOT_STARTED' : average >= 100 ? 'COMPLETED' : 'IN_PROGRESS'; - await this.prisma.objective.update({ where: { id: objectiveId }, data: { status } }); + await tx.objective.update({ where: { id: objectiveId }, data: { status } }); } } diff --git a/apps/okr/backend/test/services.test.ts b/apps/okr/backend/test/services.test.ts index 68561ec..e6db6a5 100644 --- a/apps/okr/backend/test/services.test.ts +++ b/apps/okr/backend/test/services.test.ts @@ -312,6 +312,41 @@ test('KeyResultsService.updateProgress non-existent KR throws NotFoundException' } }); +test('KeyResultsService.updateProgress rolls back progress when status recalculation fails', async () => { + const prisma = new PrismaService(); + await prisma.$connect(); + const service = new KeyResultsService(prisma); + const testable = service as unknown as { + recalculateObjectiveStatus: (objectiveId: number) => Promise; + }; + const recalculateObjectiveStatus = testable.recalculateObjectiveStatus.bind(service); + let beforeProgress = 0; + try { + const before = await prisma.keyResult.findUniqueOrThrow({ where: { id: 1 } }); + beforeProgress = before.progress; + testable.recalculateObjectiveStatus = async () => { + throw new Error('forced status recalculation failure'); + }; + + await assert.rejects( + () => service.updateProgress(1, { progress: 99, comment: 'must roll back' }, employeeUser), + /forced status recalculation failure/, + ); + + const after = await prisma.keyResult.findUniqueOrThrow({ where: { id: 1 } }); + assert.equal(after.progress, beforeProgress); + const leakedHistory = await prisma.progressUpdate.findMany({ + where: { keyResultId: 1, progress: 99, comment: 'must roll back' }, + }); + assert.equal(leakedHistory.length, 0); + } finally { + testable.recalculateObjectiveStatus = recalculateObjectiveStatus; + await prisma.keyResult.update({ where: { id: 1 }, data: { progress: beforeProgress } }); + await prisma.progressUpdate.deleteMany({ where: { keyResultId: 1, comment: 'must roll back' } }); + await prisma.$disconnect(); + } +}); + // ─── Status recalculation (full NOT_STARTED → IN_PROGRESS → COMPLETED cycle) ─ test('KeyResultsService status recalculation: NOT_STARTED → IN_PROGRESS → COMPLETED → IN_PROGRESS', async () => { diff --git a/docker-compose.control-panel.local.yml b/docker-compose.control-panel.local.yml index 5d73b83..aaf0e25 100644 --- a/docker-compose.control-panel.local.yml +++ b/docker-compose.control-panel.local.yml @@ -34,6 +34,13 @@ services: ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:-} CASAN_GOAL_OPENAI_MODEL: ${CASAN_GOAL_OPENAI_MODEL:-gpt-5.3-codex} CASAN_GOAL_ANTHROPIC_MODEL: ${CASAN_GOAL_ANTHROPIC_MODEL:-claude-3-5-sonnet-latest} + CASAN_GOAL_ENABLE_GATEWAY_PATCH_REPAIR: "1" + CASAN_GOAL_LOCAL_TIMEOUT_SEC: "600" + CASAN_GOAL_MODEL_TIMEOUT: "600" + CASAN_GOAL_MODEL_PREFLIGHT: "1" + CASAN_GOAL_MODEL_PREFLIGHT_TIMEOUT_SEC: "30" + CASAN_GOAL_LOCAL_MODEL_PREFLIGHT_TIMEOUT_SEC: "90" + CASAN_GOAL_MODEL_PREFLIGHT_TTL_SEC: "600" CASAN_PREFLIGHT: ${CASAN_PREFLIGHT:-0} CASAN_AUTH_BRIDGE_URL: http://host.docker.internal:20130 CASAN_AUTH_BRIDGE_TOKEN: ${CASAN_AUTH_BRIDGE_TOKEN:-} @@ -47,8 +54,15 @@ services: CASAN_APPROVAL_SIGNER_ROLE: ops CASAN_IDP_JWKS_URL: http://idp:8080/.well-known/jwks.json volumes: + # Persist only governed state and registered project roots. Dependencies + # stay inside the Linux image, avoiding host/container native-binary drift. - ./.specify:/app/.specify - - ./docs/output:/app/docs/output:ro + - ./docs/output:/app/docs/output + - ./docs/technical_architecture.md:/app/docs/technical_architecture.md:ro + - ./apps/okr:/app/apps/okr + - ./apps/service-desk:/app/apps/service-desk + - ./apps/projects:/app/apps/projects + - ./packages/casan-harness/level5/project-registry.json:/app/packages/casan-harness/level5/project-registry.json expose: - "3010" networks: @@ -114,7 +128,7 @@ services: CASAN_IDP_ISSUER: http://localhost:18082 CASAN_IDP_SUB: oidc-ops CASAN_IDP_EMAIL: oidc-ops@example.com - CASAN_IDP_GROUPS: casan-org-admin,casan-approver + CASAN_IDP_GROUPS: casan-org-admin,casan-approver,casan-project:AINative_OKR_CASAN4 CASAN_APPROVAL_SIGNER_TOKEN: ${CASAN_APPROVAL_SIGNER_TOKEN:-local-approval-signer-secret} ports: - "18082:8080" diff --git a/infra/local-prod/idp/mock_idp.py b/infra/local-prod/idp/mock_idp.py index 81ca04d..3df4e2a 100644 --- a/infra/local-prod/idp/mock_idp.py +++ b/infra/local-prod/idp/mock_idp.py @@ -17,7 +17,7 @@ PORT = int(os.environ.get("CASAN_IDP_PORT", "8080")) ISSUER = os.environ.get("CASAN_IDP_ISSUER", f"http://127.0.0.1:{PORT}") DEFAULT_SUB = os.environ.get("CASAN_IDP_SUB", "oidc-ops") DEFAULT_EMAIL = os.environ.get("CASAN_IDP_EMAIL", "oidc-ops@example.com") -DEFAULT_GROUPS = [g for g in os.environ.get("CASAN_IDP_GROUPS", "casan-org-admin,casan-approver").split(",") if g] +DEFAULT_GROUPS = [g for g in os.environ.get("CASAN_IDP_GROUPS", "casan-org-admin,casan-approver,casan-project:AINative_OKR_CASAN4").split(",") if g] USERS = { DEFAULT_SUB: { "name": "Operations Owner", @@ -28,7 +28,7 @@ USERS = { "oidc-reviewer": { "name": "Independent Reviewer", "email": "oidc-reviewer@example.com", - "groups": ["casan-approver"], + "groups": ["casan-approver", "casan-project:AINative_OKR_CASAN4"], "description": "Reviews and approves another operator's proposal", }, } diff --git a/packages/casan-control-panel/backend/src/approvals/approvals.service.ts b/packages/casan-control-panel/backend/src/approvals/approvals.service.ts index 16dcb96..e2a22a6 100644 --- a/packages/casan-control-panel/backend/src/approvals/approvals.service.ts +++ b/packages/casan-control-panel/backend/src/approvals/approvals.service.ts @@ -83,7 +83,11 @@ export class ApprovalsService { list(actor: SettingsActor, status = 'pending') { this.requireRbac(actor, 'monitoring', 'read'); const res = runFile('python3', [INBOX_CLI, 'list', '--status', status], this.tenantEnv(actor)); - return { ...parseJson>(res.stdout, { count: 0, proposals: [], oversight: [] }), audit_verify: this.verifyAudit(actor) }; + const inbox = parseJson<{ proposals?: Array>; oversight?: Array> }>(res.stdout, { proposals: [], oversight: [] }); + const proposals = (inbox.proposals ?? []).filter((proposal) => this.canAccessProject(actor, String(proposal.project ?? actor.project))); + const visibleIds = new Set(proposals.map((proposal) => String(proposal.id ?? ''))); + const oversight = (inbox.oversight ?? []).filter((record) => visibleIds.has(String(record.proposal_id ?? ''))); + return { ...inbox, count: proposals.length, proposals, oversight, audit_verify: this.verifyAudit(actor) }; } submit(input: ApprovalSubmit, actor: SettingsActor) { @@ -122,9 +126,9 @@ export class ApprovalsService { if (!input.id || !input.decision || !input.reason) { throw new ForbiddenException('APPROVAL_DECIDE_DENY id/decision/reason required'); } - this.requireRbac(actor, 'approval', 'grant'); try { const pending = this.findProposal(input.id, actor); + this.requireRbac(actor, 'approval', 'grant', String(pending.project ?? actor.project)); await this.verifyApprovalIdentity(input, actor, pending); const res = runFile('python3', [ INBOX_CLI, @@ -281,7 +285,16 @@ export class ApprovalsService { } } - private requireRbac(actor: SettingsActor, resource: string, action: string) { + private canAccessProject(actor: SettingsActor, targetProject: string): boolean { + try { + this.requireRbac(actor, 'monitoring', 'read', targetProject); + return true; + } catch { + return false; + } + } + + private requireRbac(actor: SettingsActor, resource: string, action: string, targetProject = actor.project) { try { runFile('python3', [ RBAC_CLI, @@ -295,7 +308,7 @@ export class ApprovalsService { '--role-project', actor.project, '--target-project', - actor.project, + targetProject, '--role-tenant', actor.tenant, '--target-tenant', diff --git a/packages/casan-control-panel/backend/src/common/auth-context.ts b/packages/casan-control-panel/backend/src/common/auth-context.ts index 3ae8d10..3960620 100644 --- a/packages/casan-control-panel/backend/src/common/auth-context.ts +++ b/packages/casan-control-panel/backend/src/common/auth-context.ts @@ -33,6 +33,14 @@ function roleFromClaim(raw: string | undefined): string { return 'viewer'; } +function projectFromClaims(raw: string | undefined): string | undefined { + if (!raw) return undefined; + const prefix = 'casan-project:'; + const projectClaim = raw.split(/[\s,]+/).find((claim) => claim.startsWith(prefix)); + const project = projectClaim?.slice(prefix.length); + return project && /^[A-Za-z0-9._-]+$/.test(project) ? project : undefined; +} + export function actorFromHeaders(headers: Record): SettingsActor { const actor = firstHeader(headers['x-casan-actor']) || firstHeader(headers['x-auth-request-user']) @@ -45,7 +53,7 @@ export function actorFromHeaders(headers: Record model).join(','), CASAN_GOAL_OMNIROUTE_MODELS: gatewayModels.join(','), @@ -234,6 +247,8 @@ export class GoalsService { // explicit opt-in because it must not disguise a Codex patch failure. CASAN_GOAL_PATCH_REPAIR_MODELS: [...cloudCandidates.map(({ model }) => model), ...gatewayPatchRepairModels, String(localRuntime.CASAN_CHAT_SELECTED_MODEL || `ollama:${localModel}`)].filter((model, index, rows) => rows.indexOf(model) === index).join(','), CASAN_GOAL_LOCAL_REVIEWER_MODEL: String(localRuntime.CASAN_CHAT_SELECTED_MODEL || `ollama:${localModel}`), + CASAN_GOAL_LOCAL_REVIEWER_PROVIDER: local?.id || 'local-policy', + CASAN_GOAL_ENABLE_LOCAL_REVIEWER: process.env.CASAN_GOAL_ENABLE_LOCAL_REVIEWER || (account || preferredCloudModel ? '0' : '1'), CASAN_GOAL_REVIEWER_MAX_ATTEMPTS: process.env.CASAN_GOAL_REVIEWER_MAX_ATTEMPTS || '8', CASAN_GOAL_REVIEWER_DEADLINE_SEC: process.env.CASAN_GOAL_REVIEWER_DEADLINE_SEC || '600', }, @@ -393,7 +408,8 @@ export class GoalsService { apply(id: string, actor: SettingsActor): GoalJob { const job = this.get(id, actor); - if (!job.patch_artifact || job.status !== 'requires_approval') { + const retryableRollback = job.status === 'failed' && job.error === 'GOAL_APPLY_VERIFICATION_FAILED_ROLLED_BACK'; + if (!job.patch_artifact || (job.status !== 'requires_approval' && !retryableRollback)) { throw new BadRequestException('GOAL_PATCH_NOT_READY'); } try { diff --git a/packages/casan-control-panel/backend/test/approvals.test.ts b/packages/casan-control-panel/backend/test/approvals.test.ts index c417b5e..3f2f2dc 100644 --- a/packages/casan-control-panel/backend/test/approvals.test.ts +++ b/packages/casan-control-panel/backend/test/approvals.test.ts @@ -102,6 +102,29 @@ test('operations owner request remains visible and actionable for an independent }); }); +test('project reviewer cannot see or decide another project approval', async () => { + await withTempGovernance(async () => { + const svc = new ApprovalsService(); + const submitted = svc.submit({ + action: 'goal.workspace.execute', + target: 'project-alpha', + risk: 'high', + sensitive: true, + reason: 'project alpha patch', + payload: { goal_id: 'goal-alpha', project_id: 'project-alpha' }, + }, { ...projectAdmin, project: 'project-alpha' }) as { proposal: { id: string } }; + const otherProjectReviewer = { ...approver, project: 'project-beta' }; + + const reviewerInbox = svc.list(otherProjectReviewer, 'pending') as { count: number; proposals: Array<{ id: string }> }; + assert.equal(reviewerInbox.count, 0); + assert.equal(reviewerInbox.proposals.length, 0); + await assert.rejects( + svc.decide({ id: submitted.proposal.id, decision: 'approve', reason: 'wrong project' }, otherProjectReviewer), + ForbiddenException, + ); + }); +}); + test('approval inbox denies forged JWT in strict mode without deciding proposal', async () => { await withTempGovernance(async ({ inbox }) => { const svc = new ApprovalsService(); diff --git a/packages/casan-control-panel/backend/test/auth-context.test.ts b/packages/casan-control-panel/backend/test/auth-context.test.ts index 76df8a2..4609fb3 100644 --- a/packages/casan-control-panel/backend/test/auth-context.test.ts +++ b/packages/casan-control-panel/backend/test/auth-context.test.ts @@ -15,10 +15,11 @@ test('auth context keeps local explicit roles for dev', () => { test('auth context maps IdP group claim to RBAC role', () => { const actor = actorFromHeaders({ 'x-auth-request-user': 'bob@example.com', - 'x-auth-request-groups': 'engineering,casan-approver', + 'x-auth-request-groups': 'engineering,casan-approver,casan-project:AINative_OKR_CASAN4', }); assert.equal(actor.actor, 'bob@example.com'); assert.equal(actor.role, 'approver'); + assert.equal(actor.project, 'AINative_OKR_CASAN4'); }); test('auth context fails closed to viewer for unknown role claim', () => { diff --git a/packages/casan-control-panel/frontend/src/pages/Approvals.tsx b/packages/casan-control-panel/frontend/src/pages/Approvals.tsx index 65f23f1..c781670 100644 --- a/packages/casan-control-panel/frontend/src/pages/Approvals.tsx +++ b/packages/casan-control-panel/frontend/src/pages/Approvals.tsx @@ -99,19 +99,39 @@ export function Approvals() { }); const decide = useMutation({ - mutationFn: ({ id, decision }: { id: string; decision: 'approve' | 'reject' }) => { + mutationFn: async ({ proposal, decision }: { proposal: ApprovalProposal; decision: 'approve' | 'reject' }) => { if (!actor) throw new Error('Authenticated session is unavailable.'); - return api.decideApproval(actor, { id, decision, reason: decisionReason.trim() }); + const response = await api.decideApproval(actor, { id: proposal.id, decision, reason: decisionReason.trim() }); + if (decision !== 'approve' || proposal.action !== 'goal.workspace.execute') { + return { response, continuedGoalId: null }; + } + + const goalId = proposal.payload.goal_id; + if (typeof goalId !== 'string' || goalId.length === 0) { + throw new Error(`Request ${proposal.id} was approved, but its goal identifier is missing. Continue from Goal Orchestrator.`); + } + try { + await api.applyGoal({ ...actor, project: proposal.project }, goalId); + return { response, continuedGoalId: goalId }; + } catch (error) { + throw new Error(`Request ${proposal.id} was approved, but apply/verification did not complete: ${errorMessage(error, 'unknown apply error')}`); + } }, - onSuccess: (response) => { + onSuccess: ({ response, continuedGoalId }) => { const verb = response.proposal.status === 'approved' ? 'approved' : 'rejected'; - setNotice({ tone: 'success', text: `Request ${response.proposal.id} was ${verb}.` }); + const continuation = continuedGoalId ? ' The governed change was applied and verified.' : ''; + setNotice({ tone: 'success', text: `Request ${response.proposal.id} was ${verb}.${continuation}` }); void queryClient.invalidateQueries({ queryKey: ['approvals'] }); void queryClient.invalidateQueries({ queryKey: ['settings'] }); void queryClient.invalidateQueries({ queryKey: ['goals'] }); void queryClient.invalidateQueries({ queryKey: ['goal'] }); }, - onError: (error) => setNotice({ tone: 'error', text: errorMessage(error, 'The approval decision could not be recorded.') }), + onError: (error) => { + setNotice({ tone: 'error', text: errorMessage(error, 'The approval decision could not be recorded.') }); + void queryClient.invalidateQueries({ queryKey: ['approvals'] }); + void queryClient.invalidateQueries({ queryKey: ['goals'] }); + void queryClient.invalidateQueries({ queryKey: ['goal'] }); + }, }); if (session.isLoading || (actor && inbox.isLoading)) { @@ -184,7 +204,7 @@ export function Approvals() {
{inbox.data.proposals.map((proposal) => { const eligibility = reviewEligibility(actor, proposal); - const isCurrentDecision = decide.isPending && decide.variables?.id === proposal.id; + const isCurrentDecision = decide.isPending && decide.variables?.proposal.id === proposal.id; return (
@@ -211,8 +231,8 @@ export function Approvals() { {eligibility.reason} {eligibility.allowed && (
- - + +
)}
diff --git a/packages/casan-harness/scripts/bash/goal-orchestrator.py b/packages/casan-harness/scripts/bash/goal-orchestrator.py index b9a47cb..b5f9ace 100644 --- a/packages/casan-harness/scripts/bash/goal-orchestrator.py +++ b/packages/casan-harness/scripts/bash/goal-orchestrator.py @@ -8,10 +8,12 @@ audit, or metric records. import argparse import fcntl import hashlib +import importlib.util import json import os import re import shlex +import shutil import subprocess import tempfile import time @@ -36,6 +38,11 @@ SECURITY = os.path.join(BIN, "security-check.sh") STATE_ROOT = os.environ.get("CASAN_STATE_ROOT") or os.path.join(ROOT, ".specify") PROJECT_REGISTRY = os.path.join(ROOT, "packages", "casan-harness", "level5", "project-registry.json") APPROVAL_INBOX = os.path.join(BIN, "approval-inbox.py") +_MANIFEST_SPEC = importlib.util.spec_from_file_location( + "casan_goal_project_manifest", os.path.join(BIN, "project_manifest.py") +) +PROJECT_MANIFEST = importlib.util.module_from_spec(_MANIFEST_SPEC) +_MANIFEST_SPEC.loader.exec_module(PROJECT_MANIFEST) CONTEXT_EXTENSIONS = {".md", ".txt", ".json", ".yaml", ".yml", ".ts", ".tsx", ".js", ".mjs", ".py", ".sh", ".prisma", ".css", ".html"} CONTEXT_IGNORED = {"node_modules", ".git", "dist", "build", "coverage", ".vite", "tmp", "logs", "__pycache__"} SENSITIVE_NAMES = {".env", ".env.local", "credentials", "credentials.json", "secrets.json", "id_rsa", "id_ed25519"} @@ -173,8 +180,35 @@ def context_excerpt_is_sensitive(text: str) -> bool: )) +def restricted_context_paths(goal: str): + """Return an explicit user-declared file/directory boundary, if present.""" + section = re.search( + r"(?:chỉ được đọc và thay đổi|chỉ làm việc trong|only (?:read and )?(?:modify|change)|only work (?:in|within))\s*:\s*" + r"(.*?)(?=\n\s*(?:không sửa bất kỳ file nào khác|do not (?:modify|change) any other file|không thay đổi|không thêm|vấn đề|yêu cầu|verification|$))", + goal, + re.IGNORECASE | re.DOTALL, + ) + if not section: + return [] + paths = [] + for match in re.findall(r"(?:apps|packages|docs)/[A-Za-z0-9_./*-]+", section.group(1), re.IGNORECASE): + cleaned = match.rstrip(".,:;)") + scoped_directory = cleaned.endswith(("/*", "/**")) + normalized = cleaned.rstrip("/*") + if normalized and all(path != normalized for path, _ in paths): + paths.append((normalized, scoped_directory)) + return paths + + def context_candidates(project: dict, goal: str): terms = {term.lower() for term in re.findall(r"[A-Za-z0-9_-]{3,}", goal)} + explicit_paths = [] + for match in re.findall(r"(?:apps|packages|docs)/[A-Za-z0-9_./*-]+", goal, re.IGNORECASE): + cleaned = match.rstrip(".,:;)") + scoped_directory = cleaned.endswith(("/*", "/**")) + normalized_path = cleaned.rstrip("/*").lower() + if normalized_path and all(path != normalized_path for path, _ in explicit_paths): + explicit_paths.append((normalized_path, scoped_directory)) candidates = [] seen = set() for relative_root, absolute_root in project["roots"]: @@ -200,6 +234,22 @@ def context_candidates(project: dict, goal: str): continue haystack = (relative + "\n" + raw[:4000]).lower() score = sum(4 if term in relative.lower() else 1 for term in terms if term in haystack) + relative_lower = relative.lower() + matching_paths = [ + (path, scoped_directory) + for path, scoped_directory in explicit_paths + if relative_lower == path or relative_lower.startswith(path + "/") + ] + if matching_paths: + # Directory globs under "only work in" sections describe the + # actual patch surface. Exact source-file mentions come next; + # architecture/requirement references remain supporting context. + if any(scoped_directory for _, scoped_directory in matching_paths): + score += 3_000 + elif os.path.splitext(relative_lower)[1] in {".ts", ".tsx", ".js", ".jsx", ".py", ".prisma", ".sql", ".sh"}: + score += 2_000 + else: + score += 1_000 if relative.endswith(("README.md", "architecture.md", "technical_architecture.md", "package.json")): score += 3 candidates.append((score, relative, raw)) @@ -209,6 +259,17 @@ def context_candidates(project: dict, goal: str): def build_context(job_path: str, project_id: str, goal: str, write_intent=False): project = registered_project(project_id) candidates = context_candidates(project, goal) + restricted_paths = restricted_context_paths(goal) + if restricted_paths: + candidates = [ + candidate for candidate in candidates + if any( + candidate[1] == path or (scoped_directory and candidate[1].startswith(path.rstrip("/") + "/")) + for path, scoped_directory in restricted_paths + ) + ] + if not candidates: + raise ValueError("goal_context_explicit_scope_empty") excerpts, manifest_files, characters = [], [], 0 # A diff can only apply when the model sees the exact target-file content. # Read-only analysis stays compact; write-intent gives the three most @@ -261,12 +322,39 @@ def build_context(job_path: str, project_id: str, goal: str, write_intent=False) def requests_side_effect(goal: str) -> bool: normalized = " ".join(goal.lower().split()) - patterns = [ - r"^(hãy\s+)?(làm luôn|sửa|thay đổi|triển khai|thực hiện|hoàn thành|chạy|tạo|xóa|cài đặt|commit|push)\b", + # The objective's leading command is authoritative. A scoped constraint + # such as "Không thay đổi API contract" must not turn "Hoàn thiện ..." + # into a read-only request. Conversely, a genuinely read-only audit does + # not begin with one of these implementation commands. + leading_write_patterns = [ + r"^(hãy\s+)?(làm luôn|sửa|thay đổi|triển khai|thực hiện|hoàn thiện|hoàn thành|chạy|tạo|xóa|cài đặt|commit|push)\b", r"^(please\s+)?(implement|fix|change|deploy|run|create|delete|install|commit|push)\b", - r"\b(thực hiện thao tác|sửa code|ghi file|mở pull request|create a pull request)\b", ] - return any(re.search(pattern, normalized) for pattern in patterns) + if any(re.search(pattern, normalized) for pattern in leading_write_patterns): + return True + + patch_requests = re.finditer(r"\b(tạo|generate|xuất|produce)\b.{0,40}\b(patch|unified git diff)\b", normalized) + explicit_patch_request = any( + not normalized[max(0, match.start() - 8):match.start()].endswith(("không ", "do not ")) + for match in patch_requests + ) + explicit_read_only = bool(re.search(r"\b(không|do not)\s+(sửa|thay đổi|triển khai|implement|fix|tạo|apply|áp dụng)\b", normalized)) + if explicit_read_only and not explicit_patch_request: + return False + imperative_patterns = [r"\b(thực hiện thao tác|sửa code|ghi file|mở pull request|create a pull request)\b"] + if any(re.search(pattern, normalized) for pattern in imperative_patterns): + return True + + # Structured objectives commonly start with a phase label rather than the + # imperative itself (for example "PHASE 1 — Hoàn thiện backend..."). Keep + # those write requests on the patch + approval path without classifying a + # read-only audit that merely mentions source files as a side effect. + structured_write_patterns = [ + r"\b(hoàn thiện|triển khai|implement|fix|sửa|thay đổi)\b.{0,120}\b(ứng dụng|backend|frontend|module|component|api|source|code|file)\b", + r"\b(tạo|generate|xuất|produce)\b.{0,40}\b(patch|unified git diff)\b", + r"\b(apply|áp dụng)\b.{0,40}\b(patch|thay đổi|change)\b", + ] + return any(re.search(pattern, normalized) for pattern in structured_write_patterns) def extract_patch(text: str) -> str: @@ -344,6 +432,17 @@ def normalize_unified_diff(patch: str) -> str: return "\n".join(normalized) + "\n" if changed else patch +def mechanical_patch_error(error: object) -> bool: + """Identify git parser errors that hunk recounting can safely repair.""" + normalized = str(error).lower() + return any(marker in normalized for marker in ( + "corrupt patch", + "patch fragment without header", + "malformed patch", + "unexpected end of file in patch", + )) + + def validate_write_output(text: str) -> str: """Fail at the producing harness when a write-intent reply is not a diff. @@ -356,7 +455,7 @@ def validate_write_output(text: str) -> str: validate_patch_check(patch) return patch except ValueError as original_error: - if "corrupt patch" not in str(original_error): + if not mechanical_patch_error(original_error): raise normalized = normalize_unified_diff(patch) if normalized == patch: @@ -387,14 +486,106 @@ def patch_repair_models(primary_model: str): """Use direct cloud -> gateway -> local order for one bounded H2 recovery.""" configured = [item.strip() for item in os.environ.get("CASAN_GOAL_PATCH_REPAIR_MODELS", "").split(",") if item.strip()] unique, seen = [], set() - for candidate in configured + [primary_model]: + ordered = [primary_model, *configured] if primary_model.startswith("account:") else [*configured, primary_model] + for candidate in ordered: if candidate and candidate not in seen: seen.add(candidate) unique.append(candidate) return unique +def model_preflight_enabled() -> bool: + configured = os.environ.get("CASAN_GOAL_MODEL_PREFLIGHT") + return configured == "1" if configured is not None else os.environ.get("CASAN_PROFILE") == "prod" + + +def model_preflight(model: str): + """Probe real patch generation before assigning a model to H2/H3. + + Model discovery only proves that an ID is advertised. This bounded probe + verifies the generation route and unified-diff capability, and caches the + verdict so a broken gateway is not retried for every goal. + """ + if not model_preflight_enabled(): + return True, "preflight_disabled" + if model.startswith("account:"): + endpoint = os.environ.get("CASAN_AUTH_BRIDGE_URL", "") + elif model.startswith("openai-compatible:"): + endpoint = os.environ.get("CASAN_OPENAI_COMPATIBLE_BASE_URL", "") + else: + endpoint = os.environ.get("OLLAMA_HOST", "") + cache_key = sha(f"{model}|{endpoint}") + cache_directory = os.path.join(STATE_ROOT, "cache") + cache_path = os.path.join(cache_directory, "goal-model-preflight.json") + lock_path = os.path.join(cache_directory, "goal-model-preflight.lock") + os.makedirs(cache_directory, mode=0o700, exist_ok=True) + ttl = int(os.environ.get("CASAN_GOAL_MODEL_PREFLIGHT_TTL_SEC", "600")) + with open(lock_path, "a", encoding="utf-8") as lock: + fcntl.flock(lock.fileno(), fcntl.LOCK_EX) + try: + cache = load_json(cache_path) if os.path.isfile(cache_path) else {} + except (OSError, ValueError, json.JSONDecodeError): + cache = {} + cached = cache.get(cache_key, {}) if isinstance(cache, dict) else {} + age = time.time() - float(cached.get("checked_epoch", 0)) + if age <= max(30, ttl): + fcntl.flock(lock.fileno(), fcntl.LOCK_UN) + return bool(cached.get("healthy")), f"cached:{cached.get('reason', 'unknown')}" + fcntl.flock(lock.fileno(), fcntl.LOCK_UN) + + prompt = ( + "Patch capability probe. Return ONLY a unified git diff inside a ```diff fence that changes " + "the only line in probe.txt from old to new. No prose. The diff must begin with " + "`diff --git a/probe.txt b/probe.txt`." + ) + timeout_setting = ( + os.environ.get("CASAN_GOAL_LOCAL_MODEL_PREFLIGHT_TIMEOUT_SEC", "90") + if model.startswith("ollama:") + else os.environ.get("CASAN_GOAL_MODEL_PREFLIGHT_TIMEOUT_SEC", "30") + ) + timeout = max(5, min(int(timeout_setting), 120)) + if model.startswith("account:"): + ok, output, _, reason = call_account_model(model.split(":", 1)[1], prompt, timeout) + else: + ok, output, _, reason = call_model( + model, prompt, model.startswith(("openai:", "anthropic:", "openai-compatible:")), + timeout_seconds=timeout, max_output_tokens=512, + ) + healthy = False + if ok: + try: + patch = extract_patch(output) + healthy = ( + "diff --git a/probe.txt b/probe.txt" in patch + and "\n-old\n" in patch + and "\n+new\n" in patch + ) + reason = "ok" if healthy else "patch_probe_contract_invalid" + except ValueError as error: + reason = str(error) + record = { + "model": model, + "provider": provider_for_model(model), + "healthy": healthy, + "reason": reason[:180], + "checked_at": now(), + "checked_epoch": time.time(), + } + with open(lock_path, "a", encoding="utf-8") as lock: + fcntl.flock(lock.fileno(), fcntl.LOCK_EX) + try: + cache = load_json(cache_path) if os.path.isfile(cache_path) else {} + except (OSError, ValueError, json.JSONDecodeError): + cache = {} + cache[cache_key] = record + atomic_json(cache_path, cache) + fcntl.flock(lock.fileno(), fcntl.LOCK_UN) + return healthy, reason + + def provider_for_model(model: str) -> str: + if model.startswith("account:"): + return f"{model.split(':', 1)[1]}-account" if model.startswith("openai:"): return "openai" if model.startswith("anthropic:"): @@ -420,7 +611,7 @@ def repair_write_output(model: str, original_prompt: str, invalid_output: str, c def repair_prompt_for(previous_output: str, error: str) -> str: hunk_instruction = ( "The previous diff is syntactically corrupt. Recompute every `@@ -old,count +new,count @@` header from the exact added/removed/context lines that follow it; do not omit or invent any hunk line. " - if "corrupt patch" in error.lower() else "" + if mechanical_patch_error(error) else "" ) return ( f"Your previous response violated the required write-output contract: {error}. " + @@ -434,7 +625,21 @@ def repair_write_output(model: str, original_prompt: str, invalid_output: str, c last_metadata, last_reason = {}, "goal_patch_missing" attempts = [] for attempt_number, candidate in enumerate(candidates, start=1): - ok, output, metadata, reason = call_model(candidate, repair_prompt, candidate.startswith(("openai:", "anthropic:", "openai-compatible:")), max_output_tokens=patch_output_tokens()) + healthy, preflight_reason = model_preflight(candidate) + if not healthy: + last_reason = f"goal_patch_model_preflight_failed:{preflight_reason}" + attempts.append({ + "attempt": attempt_number, + "provider": provider_for_model(candidate), + "model": candidate, + "status": "failed", + "reason": last_reason, + }) + continue + if candidate.startswith("account:"): + ok, output, metadata, reason = call_account_model(candidate.split(":", 1)[1], repair_prompt, 300) + else: + ok, output, metadata, reason = call_model(candidate, repair_prompt, candidate.startswith(("openai:", "anthropic:", "openai-compatible:")), max_output_tokens=patch_output_tokens()) metadata = dict(metadata) metadata["repair_model"] = candidate metadata["repair_attempt"] = attempt_number @@ -464,7 +669,7 @@ def repair_write_output(model: str, original_prompt: str, invalid_output: str, c # incomplete first diff. Give the same stronger direct model one # corrective pass containing its own failed patch and git error # before sending source context to a weaker gateway. - if candidate.endswith("-codex") and attempt_number < patch_repair_attempts(): + if (candidate.endswith("-codex") or candidate == "account:codex") and attempt_number < patch_repair_attempts(): candidates.insert(attempt_number, candidate) del candidates[patch_repair_attempts():] repair_prompt = repair_prompt_for(output, last_reason) @@ -493,8 +698,52 @@ def merge_usage(primary: dict, additional: dict) -> dict: return merged +def validate_patch_semantics(job: dict, artifact: str, changed_files: list[str]) -> list[dict]: + """Build and test an applied patch in an isolated workspace before approval.""" + artifact = os.path.realpath(artifact) + if os.path.commonpath([ROOT, artifact]) != ROOT or not os.path.isfile(artifact): + raise ValueError("goal_patch_artifact_path_denied") + manifest = PROJECT_MANIFEST.load(ROOT, project_id=str(job.get("project") or "")) + commands = PROJECT_MANIFEST.verification_commands(manifest, changed_files) + if not commands: + raise ValueError("goal_patch_verification_unmapped") + results = [] + with tempfile.TemporaryDirectory(prefix="casan-goal-verify-") as sandbox: + paths = ["package.json", "package-lock.json", *manifest["source_roots"]] + paths.extend(str(value) for value in job.get("workspace", {}).get("context_roots", [])) + for relative in dict.fromkeys(paths): + source = os.path.realpath(os.path.join(ROOT, relative)) + if os.path.commonpath([ROOT, source]) != ROOT or not os.path.exists(source): + continue + destination = os.path.join(sandbox, relative) + os.makedirs(os.path.dirname(destination), exist_ok=True) + if os.path.isdir(source): + shutil.copytree(source, destination, dirs_exist_ok=True, symlinks=True, ignore=shutil.ignore_patterns("node_modules", "dist", "coverage")) + else: + shutil.copy2(source, destination) + dependencies = os.path.join(ROOT, "node_modules") + if os.path.isdir(dependencies): + os.symlink(dependencies, os.path.join(sandbox, "node_modules"), target_is_directory=True) + applied = subprocess.run( + ["git", "apply", "--whitespace=error", artifact], cwd=sandbox, + capture_output=True, text=True, timeout=30, + ) + if applied.returncode != 0: + raise ValueError("goal_patch_sandbox_apply_failed:" + (applied.stderr or applied.stdout).strip()[:300]) + timeout = max(30, int(os.environ.get("CASAN_GOAL_PATCH_VERIFY_TIMEOUT_SEC", "300"))) + for command in commands: + result = subprocess.run(command, cwd=sandbox, capture_output=True, text=True, timeout=timeout) + output = (result.stdout + result.stderr)[-4000:] + results.append({"command": " ".join(command), "exit_code": result.returncode, "output": output}) + if result.returncode != 0: + raise ValueError("goal_patch_verification_failed:" + output[-1200:]) + return results + + def validate_and_store_patch(job_path: str, job: dict, patch: str) -> dict: - roots = [str(value).strip("/") for value in job.get("workspace", {}).get("context_roots", [])] + explicit_scope = restricted_context_paths(str(job.get("goal") or "")) + roots = [path.strip("/") for path, _ in explicit_scope] or [str(value).strip("/") for value in job.get("workspace", {}).get("context_roots", [])] + directory_roots = {path.strip("/") for path, scoped_directory in explicit_scope if scoped_directory} changed = [] header_paths = [] for line in patch.splitlines(): @@ -514,7 +763,7 @@ def validate_and_store_patch(job_path: str, job: dict, patch: str) -> dict: path = path[2:] if path.startswith("/") or ".." in path.split("/"): raise ValueError("goal_patch_path_denied") - if not any(path == root or path.startswith(root + "/") for root in roots): + if not any(path == root or (root in directory_roots and path.startswith(root + "/")) for root in roots): raise ValueError(f"goal_patch_outside_workspace:{path}") changed.append(path) if not changed or len(set(changed)) > 20: @@ -529,7 +778,12 @@ def validate_and_store_patch(job_path: str, job: dict, patch: str) -> dict: if check.returncode != 0: os.unlink(artifact) raise ValueError("goal_patch_check_failed:" + (check.stderr or check.stdout).strip()[:160]) - return {"path": os.path.relpath(artifact, ROOT), "sha256": sha(patch), "files": sorted(set(changed)), "bytes": len(patch.encode("utf-8")), "status": "awaiting_approval", "preview": patch[:50_000]} + try: + verification = validate_patch_semantics(job, artifact, sorted(set(changed))) + except Exception: + os.unlink(artifact) + raise + return {"path": os.path.relpath(artifact, ROOT), "sha256": sha(patch), "files": sorted(set(changed)), "bytes": len(patch.encode("utf-8")), "status": "awaiting_approval", "preview": patch[:50_000], "preapproval_verification": verification} def submit_side_effect(job: dict, manifest: dict, patch_artifact: dict) -> dict: @@ -702,8 +956,8 @@ def reviewer_candidates(account_provider: str, cloud_model: str, local_model: st candidates.append({"kind": "model", "provider": "omniroute", "model": value, "value": value}) local_reviewer = os.environ.get("CASAN_GOAL_LOCAL_REVIEWER_MODEL", "").strip() or local_model - if local_reviewer: - candidates.append({"kind": "model", "provider": os.environ.get("CASAN_GOAL_LOCAL_PROVIDER", "local-policy"), "model": local_reviewer, "value": local_reviewer}) + if local_reviewer and os.environ.get("CASAN_GOAL_ENABLE_LOCAL_REVIEWER", "1") == "1": + candidates.append({"kind": "model", "provider": os.environ.get("CASAN_GOAL_LOCAL_REVIEWER_PROVIDER", os.environ.get("CASAN_GOAL_LOCAL_PROVIDER", "local-policy")), "model": local_reviewer, "value": local_reviewer}) unique, seen = [], set() for candidate in candidates: @@ -714,15 +968,17 @@ def reviewer_candidates(account_provider: str, cloud_model: str, local_model: st return unique -def run_reviewer_chain(job_path: str, prompt: str, account_provider: str, cloud_model: str, local_model: str): +def run_reviewer_chain(job_path: str, prompt: str, account_provider: str, cloud_model: str, local_model: str, max_output_tokens=None, excluded_models=None): max_attempts = max(1, min(int(os.environ.get("CASAN_GOAL_REVIEWER_MAX_ATTEMPTS", "5")), 10)) deadline_seconds = max(1, min(int(os.environ.get("CASAN_GOAL_REVIEWER_DEADLINE_SEC", "360")), 900)) deadline = time.monotonic() + deadline_seconds ledger = [] update_job(job_path, reviewer_attempts=ledger) last_reason = "reviewer_candidates_unavailable" - candidates = reviewer_candidates(account_provider, cloud_model, local_model) - if len(candidates) > max_attempts and candidates[-1].get("provider") == os.environ.get("CASAN_GOAL_LOCAL_PROVIDER", "local-policy"): + excluded = set(excluded_models or []) + candidates = [candidate for candidate in reviewer_candidates(account_provider, cloud_model, local_model) if candidate.get("value") not in excluded] + local_reviewer_provider = os.environ.get("CASAN_GOAL_LOCAL_REVIEWER_PROVIDER", os.environ.get("CASAN_GOAL_LOCAL_PROVIDER", "local-policy")) + if len(candidates) > max_attempts and candidates[-1].get("provider") == local_reviewer_provider: candidates = candidates[:max_attempts - 1] + [candidates[-1]] if max_attempts > 1 else [candidates[-1]] else: candidates = candidates[:max_attempts] @@ -734,10 +990,26 @@ def run_reviewer_chain(job_path: str, prompt: str, account_provider: str, cloud_ attempt_number = len(ledger) + 1 stage(job_path, "cloud-reviewer", "running", f"Reviewer attempt {attempt_number}/{max_attempts}", candidate["provider"], candidate["model"]) started_at, started_clock = now(), time.monotonic() + if candidate["kind"] == "model" and max_output_tokens is not None: + healthy, preflight_reason = model_preflight(candidate["value"]) + if not healthy: + reason = f"model_preflight_failed:{preflight_reason}" + ledger.append({ + "attempt": attempt_number, "provider": candidate["provider"], "model": candidate["model"], + "status": "failed", "reason": reason, "retryable": True, + "started_at": started_at, "finished_at": now(), + "latency_ms": int((time.monotonic() - started_clock) * 1000), + }) + update_job(job_path, reviewer_attempts=ledger) + last_reason = reason + continue if candidate["kind"] == "account": ok, result, metadata, reason = call_account_model(candidate["value"], prompt, remaining) else: - ok, result, metadata, reason = call_model(candidate["value"], prompt, candidate["provider"] != os.environ.get("CASAN_GOAL_LOCAL_PROVIDER", "local-policy"), remaining) + if max_output_tokens is None: + ok, result, metadata, reason = call_model(candidate["value"], prompt, candidate["provider"] != local_reviewer_provider, remaining) + else: + ok, result, metadata, reason = call_model(candidate["value"], prompt, candidate["provider"] != local_reviewer_provider, remaining, max_output_tokens=max_output_tokens) retryable = False if ok else reviewer_failure_retryable(reason) ledger.append({ "attempt": attempt_number, "provider": candidate["provider"], "model": candidate["model"], @@ -846,6 +1118,12 @@ def run(job_path: str) -> int: }) emit(goal_id, "H4-security", "running", "Objective and workspace snapshot passed; model outputs pending") + local_healthy, local_preflight_reason = model_preflight(local_model) + if not local_healthy: + stage(job_path, "local-worker", "error", f"model_preflight_failed:{local_preflight_reason}", job.get("local_provider", ""), local_model) + emit(goal_id, "H2-tool", "error", "Local worker failed patch-generation preflight", {"provider": job.get("local_provider", ""), "model": local_model, "reason": local_preflight_reason}) + raise RuntimeError(f"local_worker_preflight_failed:{local_preflight_reason}") + stage(job_path, "local-worker", "running", "Local model is developing the primary solution", job.get("local_provider", ""), local_model) emit(goal_id, "H2-tool", "running", "Local worker is developing a solution", {"provider": job.get("local_provider", ""), "model": local_model}) output_contract = ( @@ -859,7 +1137,15 @@ def run(job_path: str) -> int: "Do not claim to inspect any filesystem outside this snapshot and do not perform side effects.\n\n" f"OBJECTIVE:\n{safe_goal}\n\nWORKSPACE SNAPSHOT ({project_id}):\n{context_bundle}" ) - ok, local_draft, local_meta, reason = call_model(local_model, local_prompt, False) + if local_model.startswith("account:"): + ok, local_draft, local_meta, reason = call_account_model(local_model.split(":", 1)[1], local_prompt, 300) + else: + ok, local_draft, local_meta, reason = call_model( + local_model, + local_prompt, + False, + max_output_tokens=patch_output_tokens() if write_intent else None, + ) if not ok: stage(job_path, "local-worker", "error", reason, job.get("local_provider", ""), local_model) emit(goal_id, "H2-tool", "error", "Local worker failed", {"reason": reason}) @@ -897,14 +1183,18 @@ def run(job_path: str) -> int: stage(job_path, "local-worker", "error", reason, provider_for_model(actual_repair_model), actual_repair_model) emit(goal_id, "H2-tool", "error", "Worker violated patch output contract after repair", {"reason": reason, "repair_provider": provider_for_model(actual_repair_model), "repair_model": actual_repair_model, "repair_attempts": repaired_meta.get("repair_attempts", [])}) raise ValueError(reason) - stage(job_path, "local-worker", "pass", "Primary solution prepared", job.get("local_provider", ""), local_model) + effective_local_model = str(local_meta.get("repair_model") or local_model) + effective_local_provider = provider_for_model(effective_local_model) + stage(job_path, "local-worker", "pass", "Primary solution prepared", effective_local_provider, effective_local_model) update_job( job_path, local_draft=safe_local, local_usage=local_meta, patch_repair_attempts=local_meta.get("repair_attempts", []), + effective_local_provider=effective_local_provider, + effective_local_model=effective_local_model, ) - emit(goal_id, "H2-tool", "pass", "Local solution prepared", {"provider": job.get("local_provider", ""), "model": local_model, **local_meta}) + emit(goal_id, "H2-tool", "pass", "Primary solution prepared", {"provider": effective_local_provider, "model": effective_local_model, **local_meta}) stage(job_path, "cloud-reviewer", "running", "Independent reviewer is challenging and improving the local solution", job.get("cloud_provider", ""), cloud_model) emit(goal_id, "H3-eval", "running", "Cloud reviewer is evaluating the local solution", {"provider": job.get("cloud_provider", ""), "model": cloud_model}) @@ -923,7 +1213,13 @@ def run(job_path: str) -> int: f"LOCAL WORKER PROPOSAL:\n{safe_local[:5000]}" ) cloud_ok, cloud_result, cloud_meta, cloud_reason, reviewer = run_reviewer_chain( - job_path, review_prompt, account_provider, cloud_model, local_model + job_path, + review_prompt, + account_provider, + cloud_model, + local_model, + max_output_tokens=patch_output_tokens() if write_intent else None, + excluded_models={effective_local_model}, ) reviewer_provider = str(reviewer.get("provider") or job.get("cloud_provider", "")) reviewer_model = str(reviewer.get("model") or cloud_model) @@ -965,8 +1261,13 @@ def run(job_path: str) -> int: patch_artifact["approval_id"] = proposal["id"] final_status = "requires_approval" metric_status = "degraded" - safe_result = "Implementation patch generated and independently reviewed. Approval is required before applying it to the workspace." - emit(goal_id, "H7-orchestration", "blocked", "Reviewed patch awaits approval", {"proposal_id": proposal["id"], "patch_sha256": patch_artifact["sha256"]}) + if cloud_ok: + safe_result = "Implementation patch generated and independently reviewed. Approval is required before applying it to the workspace." + wait_detail = "Independently reviewed patch awaits approval" + else: + safe_result = "Implementation patch generated and validated. Automated H3 review was unavailable, so Independent Reviewer approval is required before applying it to the workspace." + wait_detail = "Validated patch awaits Independent Reviewer approval after H3 degradation" + emit(goal_id, "H7-orchestration", "blocked", wait_detail, {"proposal_id": proposal["id"], "patch_sha256": patch_artifact["sha256"], "cloud_incorporated": cloud_ok}) job = update_job(job_path, status=final_status, result=safe_result, patch_artifact=patch_artifact, approval=approval, cloud_usage=cloud_meta, finished_at=now()) else: job = update_job(job_path, status=final_status, result=safe_result, cloud_usage=cloud_meta, finished_at=now()) diff --git a/packages/casan-harness/scripts/bash/goal-patch-executor.py b/packages/casan-harness/scripts/bash/goal-patch-executor.py index 6ce3c12..74d7a69 100644 --- a/packages/casan-harness/scripts/bash/goal-patch-executor.py +++ b/packages/casan-harness/scripts/bash/goal-patch-executor.py @@ -97,14 +97,29 @@ def _manifest_for_files(files: list[str]) -> dict: def verification_commands(files: list[str], manifest: dict | None = None) -> list[list[str]]: project = manifest or _manifest_for_files(files) - return [["git", "diff", "--check", "--", *files], *PROJECT_MANIFEST.verification_commands(project, files)] + # `git apply --check --whitespace=error` already validates the exact patch + # before mutation. Runtime images intentionally do not need repository + # metadata, so post-apply verification is limited to manifest build/tests. + return PROJECT_MANIFEST.verification_commands(project, files) + + +def ready_for_apply(job: dict) -> bool: + if not job.get("patch_artifact"): + return False + if job.get("status") == "requires_approval": + return True + return ( + job.get("status") == "failed" + and job.get("error") == "GOAL_APPLY_VERIFICATION_FAILED_ROLLED_BACK" + ) def execute(job_path: str, actor: str) -> dict: job = load(job_path) - if job.get("status") != "requires_approval" or not job.get("patch_artifact"): + if not ready_for_apply(job): raise RuntimeError("GOAL_APPLY_JOB_NOT_READY") proposal = verify_approval(job) + job.setdefault("approval", {})["status"] = "approved" if proposal.get("approver") != actor: raise PermissionError("GOAL_APPLY_APPROVER_IDENTITY_MISMATCH") artifact = job["patch_artifact"] @@ -135,7 +150,8 @@ def execute(job_path: str, actor: str) -> dict: rollback = run(["git", "apply", "--reverse", patch_path], 30) if rollback.returncode != 0: raise RuntimeError("GOAL_APPLY_ROLLBACK_FAILED") - job.update(status="failed", error="GOAL_APPLY_VERIFICATION_FAILED_ROLLED_BACK", verification=checks, finished_at=now(), updated_at=now()) + artifact["status"] = "awaiting_apply_retry" + job.update(status="requires_approval", error="GOAL_APPLY_VERIFICATION_FAILED_ROLLED_BACK", patch_artifact=artifact, verification=checks, finished_at=now(), updated_at=now()) save(job_path, job) raise diff --git a/packages/casan-harness/scripts/bash/security-check.sh b/packages/casan-harness/scripts/bash/security-check.sh index 2e024ed..8839f9e 100755 --- a/packages/casan-harness/scripts/bash/security-check.sh +++ b/packages/casan-harness/scripts/bash/security-check.sh @@ -25,6 +25,22 @@ TRACE_DIR="$LOG_DIR/trace" AUDIT_DIR="$LOG_DIR/audit" SECURITY_DIR="$CASAN_HARNESS_ROOT/security" +# Prefer the OS Python over framework/shim installations that may exist in a +# developer shell but cannot execute. The runtime image also exposes this path. +PYTHON_BIN="${CASAN_PYTHON_BIN:-}" +if [[ -z "$PYTHON_BIN" ]]; then + for candidate in /usr/bin/python3 python3 python; do + if command -v "$candidate" >/dev/null 2>&1 && "$candidate" --version >/dev/null 2>&1; then + PYTHON_BIN="$candidate" + break + fi + done +fi +if [[ -z "$PYTHON_BIN" ]]; then + echo "SECURITY_RUNTIME_UNAVAILABLE: working Python 3 interpreter not found" >&2 + exit 69 +fi + # Shared log taxonomy (error/dev/null || sed 's/\\/\\\\/g; s/"/\\"/g' + "$PYTHON_BIN" -c 'import json,sys; print(json.dumps(sys.stdin.read()))' 2>/dev/null || sed 's/\\/\\\\/g; s/"/\\"/g' } hash_text() { @@ -85,7 +101,7 @@ load_yaml_values() { local file="$1" local key="$2" [[ -f "$file" ]] || return 0 - python - "$file" "$key" <<'PY' + "$PYTHON_BIN" - "$file" "$key" <<'PY' import re import sys path, key = sys.argv[1], sys.argv[2] @@ -98,6 +114,45 @@ with open(path, encoding="utf-8") as f: PY } +# Load only rule patterns whose declared action matches the requested action. +# The previous generic loader returned every `pattern:` in prompt-filter.yaml, +# which accidentally promoted `require_approval`, `alert`, and `log` rules to +# hard blocks. That made ordinary source code containing methods such as +# `delete()` fail the workspace-context scan as prompt injection. +load_yaml_rule_patterns() { + local file="$1" + local requested_action="$2" + [[ -f "$file" ]] || return 0 + "$PYTHON_BIN" - "$file" "$requested_action" <<'PY' +import re +import sys + +path, requested_action = sys.argv[1], sys.argv[2] +pattern = None +action = None + +def flush(): + if pattern is not None and action == requested_action: + print(pattern) + +with open(path, encoding="utf-8") as handle: + for line in handle: + if re.match(r"^\s*-\s+id:\s*", line): + flush() + pattern = None + action = None + continue + pattern_match = re.match(r'^\s*pattern:\s*"(.*)"\s*$', line) + if pattern_match: + pattern = pattern_match.group(1) + continue + action_match = re.match(r"^\s*action:\s*([A-Za-z_]+)\s*$", line) + if action_match: + action = action_match.group(1) +flush() +PY +} + TRACE_ID="$(new_trace_id)" TIMESTAMP="$(timestamp)" CONTENT="$(cat "$INPUT_FILE")" @@ -126,7 +181,7 @@ BLOCK_PATTERNS=( while IFS= read -r pattern; do [[ -n "$pattern" ]] && BLOCK_PATTERNS+=("$pattern") -done < <(load_yaml_values "$SECURITY_DIR/prompt-filter.yaml" "pattern") +done < <(load_yaml_rule_patterns "$SECURITY_DIR/prompt-filter.yaml" "block") APPROVAL_PATTERNS=( "delete[[:space:]].*" @@ -169,8 +224,8 @@ NORM_CONTENT="$(normalize_for_match "$CONTENT")" # fullwidth/zero-width/Cyrillic-lookalike obfuscation cannot split or disguise # a blocked phrase (V3). Falls back to the raw content if python is missing. UNI_CONTENT="$CONTENT" -if command -v python >/dev/null 2>&1; then - UNI_CONTENT="$(printf '%s' "$CONTENT" | python "$SCRIPT_DIR/unicode-normalize.py" 2>/dev/null)" +if [[ -n "$PYTHON_BIN" ]]; then + UNI_CONTENT="$(printf '%s' "$CONTENT" | "$PYTHON_BIN" "$SCRIPT_DIR/unicode-normalize.py" 2>/dev/null)" [[ -n "$UNI_CONTENT" ]] || UNI_CONTENT="$CONTENT" fi UNI_NORM_CONTENT="$(normalize_for_match "$UNI_CONTENT")" @@ -180,8 +235,8 @@ UNI_NORM_CONTENT="$(normalize_for_match "$UNI_CONTENT")" # Only mostly-printable decodes survive, so random base64-looking words never # create a false positive. DECODED_CONTENT="" -if command -v python >/dev/null 2>&1; then - DECODED_CONTENT="$(printf '%s' "$CONTENT" | python "$SCRIPT_DIR/decode-suspicious.py" 2>/dev/null || true)" +if [[ -n "$PYTHON_BIN" ]]; then + DECODED_CONTENT="$(printf '%s' "$CONTENT" | "$PYTHON_BIN" "$SCRIPT_DIR/decode-suspicious.py" 2>/dev/null || true)" fi # Matches a pattern against the raw (case-insensitive), leetspeak-folded, @@ -279,7 +334,7 @@ if [[ "$MODE" == "input" ]]; then SEM_JSON="$TRACE_DIR/semantic-$TRACE_ID.json" "$SCRIPT_DIR/model-router.sh" "$INPUT_FILE" "$SEM_JSON" --role classify >/dev/null 2>&1 || true if [[ -f "$SEM_JSON" ]]; then - SEM_VERDICT="$(python -c "import json;print(json.load(open('$SEM_JSON')).get('verdict',''))" 2>/dev/null || echo "")" + SEM_VERDICT="$("$PYTHON_BIN" -c "import json;print(json.load(open('$SEM_JSON')).get('verdict',''))" 2>/dev/null || echo "")" fi fi if [[ "$SEM_VERDICT" == "INJECTION" ]]; then @@ -302,8 +357,8 @@ fi SAFE_CONTENT="$CONTENT" # Policy-driven PII masking (source of truth: pii-rules.yaml). Built-in sed # masking below remains as defense-in-depth if the policy file is unavailable. -if [[ -f "$SECURITY_DIR/pii-rules.yaml" ]] && command -v python >/dev/null 2>&1; then - SAFE_CONTENT="$(printf '%s' "$SAFE_CONTENT" | python "$SCRIPT_DIR/pii-mask.py" "$SECURITY_DIR/pii-rules.yaml")" +if [[ -f "$SECURITY_DIR/pii-rules.yaml" ]] && [[ -n "$PYTHON_BIN" ]]; then + SAFE_CONTENT="$(printf '%s' "$SAFE_CONTENT" | "$PYTHON_BIN" "$SCRIPT_DIR/pii-mask.py" "$SECURITY_DIR/pii-rules.yaml")" fi SAFE_CONTENT="$(printf '%s' "$SAFE_CONTENT" | sed -E "s/$EMAIL_REGEX/***MASKED_EMAIL***/g")" SAFE_CONTENT="$(printf '%s' "$SAFE_CONTENT" | sed -E "s/$PHONE_REGEX/***MASKED_PHONE***/g")" @@ -333,7 +388,7 @@ fi INPUT_HASH="$(printf '%s' "$CONTENT" | hash_text)" OUTPUT_HASH="$(printf '%s' "$SAFE_CONTENT" | hash_text)" -RULES_JSON="$(printf '%s\n' "${MATCHED_RULES[@]:-}" | python -c 'import json,sys; print(json.dumps([x for x in sys.stdin.read().splitlines() if x]))')" +RULES_JSON="$(printf '%s\n' "${MATCHED_RULES[@]:-}" | "$PYTHON_BIN" -c 'import json,sys; print(json.dumps([x for x in sys.stdin.read().splitlines() if x]))')" TRACE_FILE="$TRACE_DIR/security-$TRACE_ID.json" cat > "$TRACE_FILE" <