Compare commits

..
Author SHA1 Message Date
m d19fcceff3 docs: record authenticated mobile acceptance
CI / test (push) Successful in 57s
CI / test (pull_request) Successful in 54s
2026-08-24 14:02:47 +03:00
m 8c6f23dc49 Merge pull request 'DEV-000290 Fix mobile chat header overflow' (#268) from codex/dev-000248-self-development-runtime-status into main
CI / test (push) Successful in 1m5s
2026-08-24 10:59:52 +00:00
m 19c58b6d25 fix: constrain mobile chat header
CI / test (push) Successful in 54s
CI / test (pull_request) Successful in 51s
2026-08-24 13:57:39 +03:00
m e9d5b1ca8a Merge pull request 'DEV-000289 Fix authenticated chat mobile overflow' (#267) from codex/dev-000248-self-development-runtime-status into main
CI / test (push) Successful in 50s
2026-08-24 10:52:31 +00:00
m 444d9d825a fix: constrain chat layout on mobile
CI / test (push) Successful in 55s
CI / test (pull_request) Successful in 54s
2026-08-24 13:50:16 +03:00
m a2ff01050e Merge pull request 'DEV-000288 Refresh production evidence revision' (#266) from codex/dev-000248-self-development-runtime-status into main
CI / test (push) Successful in 57s
2026-08-24 10:40:57 +00:00
m 443e0ceb2e docs: refresh production evidence revision
CI / test (push) Successful in 58s
CI / test (pull_request) Successful in 52s
2026-08-24 13:38:42 +03:00
m 3e91ae503c Merge pull request 'DEV-000287 Reconcile live audit evidence' (#265) from codex/dev-000248-self-development-runtime-status into main
CI / test (push) Successful in 54s
2026-08-24 10:33:25 +00:00
m bf6f2365d4 Merge remote-tracking branch 'origin/main' into codex/dev-000248-self-development-runtime-status
CI / test (push) Successful in 1m2s
CI / test (pull_request) Successful in 53s
2026-08-24 13:30:52 +03:00
m 9b7091bbe9 docs: reconcile live audit evidence
CI / test (push) Successful in 59s
CI / test (pull_request) Successful in 55s
2026-08-24 13:28:26 +03:00
m 12725511c0 Merge pull request 'DEV-000286 Reject irrelevant research excerpts' (#264) from codex/dev-000248-self-development-runtime-status into main
CI / test (push) Successful in 1m8s
2026-08-24 08:51:04 +00:00
m 3c46a5f07f fix: reject irrelevant research excerpts
CI / test (push) Successful in 51s
CI / test (pull_request) Successful in 53s
2026-08-24 06:40:31 +03:00
m b0a8b3e5a7 Merge pull request 'DEV-000285 Route source-dependent questions to research' (#263) from codex/dev-000248-self-development-runtime-status into main
CI / test (push) Successful in 50s
2026-08-24 03:32:34 +00:00
m f6f68ffb04 fix: route source-dependent questions to research
CI / test (push) Successful in 54s
CI / test (pull_request) Successful in 53s
2026-08-24 06:29:56 +03:00
m 5c98bc3568 Merge pull request 'DEV-000284 Record live generic graph synthesis evidence' (#262) from codex/dev-000248-self-development-runtime-status into main
CI / test (push) Successful in 57s
2026-08-24 01:59:32 +00:00
m de1d2f5107 docs: record live generic graph synthesis evidence
CI / test (push) Successful in 52s
CI / test (pull_request) Successful in 52s
2026-08-24 04:56:50 +03:00
m bcbd41f217 Merge pull request 'DEV-000283 Verify generic Work Graph candidate envelopes' (#261) from codex/dev-000248-self-development-runtime-status into main
CI / test (push) Successful in 52s
2026-08-24 01:41:09 +00:00
m f5616fe3ea feat: verify generic work graph candidate envelopes
CI / test (push) Successful in 53s
CI / test (pull_request) Successful in 52s
2026-08-24 04:38:40 +03:00
m 94baf98060 Merge pull request 'DEV-000282 Record final Portainer auto-deploy evidence' (#260) from codex/dev-000248-self-development-runtime-status into main
CI / test (push) Successful in 53s
2026-08-24 01:32:19 +00:00
m c499ae090e docs: record final Portainer auto-deploy evidence
CI / test (push) Successful in 55s
CI / test (pull_request) Successful in 55s
2026-08-24 04:29:20 +03:00
m e132ec6ba9 Merge pull request 'DEV-000281 Reconcile live generic execution evidence' (#259) from codex/dev-000248-self-development-runtime-status into main
CI / test (push) Successful in 54s
2026-08-24 00:38:54 +00:00
m 344f8ad106 docs: reconcile live generic execution evidence
CI / test (push) Successful in 54s
CI / test (pull_request) Successful in 54s
2026-08-24 03:36:06 +03:00
m 080bd44021 Merge pull request 'DEV-000280 Add local generic Agent Execution' (#258) from codex/dev-000248-self-development-runtime-status into main
CI / test (push) Successful in 1m16s
2026-08-24 00:01:15 +00:00
m 318a68c3dd chore: activate generic agent execution task
CI / test (push) Successful in 50s
CI / test (pull_request) Successful in 50s
2026-08-24 02:58:40 +03:00
m 73d7ffd012 feat: execute generic local work graph candidates
CI / test (push) Failing after 37s
CI / test (pull_request) Failing after 35s
2026-08-24 02:56:19 +03:00
m 51db6377a4 Merge pull request 'DEV-000279 Reconcile current audit production evidence' (#257) from codex/dev-000248-self-development-runtime-status into main
CI / test (push) Successful in 51s
2026-08-23 23:46:18 +00:00
m c3b4283101 docs: reconcile current audit production evidence
CI / test (push) Successful in 55s
CI / test (pull_request) Successful in 50s
2026-08-24 02:42:58 +03:00
m ffd7ddcce5 Merge pull request 'DEV-000278 Reconcile live research evidence' (#256) from codex/dev-000248-self-development-runtime-status into main
CI / test (push) Successful in 51s
2026-08-23 23:38:24 +00:00
m dd83442ff9 docs: reconcile live research evidence
CI / test (push) Successful in 53s
CI / test (pull_request) Successful in 53s
2026-08-24 02:36:08 +03:00
m f1c3abb3b7 Merge pull request 'DEV-000277 Parse current research provider markup' (#255) from codex/dev-000248-self-development-runtime-status into main
CI / test (push) Successful in 52s
2026-08-23 23:25:30 +00:00
m a56957138d fix: parse current research provider markup
CI / test (push) Successful in 51s
CI / test (pull_request) Successful in 53s
2026-08-24 02:23:07 +03:00
m 5058d5ac0e Merge pull request 'DEV-000276 Use available bounded research search provider' (#254) from codex/dev-000248-self-development-runtime-status into main
CI / test (push) Successful in 53s
2026-08-23 23:17:23 +00:00
m 00d7f70ffb fix: use available bounded research search provider
CI / test (push) Successful in 51s
CI / test (pull_request) Successful in 53s
2026-08-24 02:15:03 +03:00
m c09be4c9b5 Merge pull request 'DEV-000275 Semantic web research routing' (#253) from codex/dev-000248-self-development-runtime-status into main
CI / test (push) Successful in 53s
2026-08-23 23:07:13 +00:00
m 976edccfd8 feat: route factual questions through bounded research
CI / test (push) Successful in 55s
CI / test (pull_request) Successful in 53s
2026-08-24 02:04:57 +03:00
m 1eb3e39286 Merge pull request 'DEV-000274 Truth-first chat and intent-aware model routing' (#252) from codex/dev-000248-self-development-runtime-status into main
CI / test (push) Successful in 53s
2026-08-23 22:51:07 +00:00
m 15088d1d7d Merge remote-tracking branch 'origin/main' into codex/dev-000248-self-development-runtime-status
CI / test (push) Successful in 49s
CI / test (pull_request) Successful in 52s
2026-08-24 01:48:49 +03:00
m 4cdd1a5276 fix: route model mentions by intent
CI / test (push) Successful in 50s
CI / test (pull_request) Successful in 49s
2026-08-24 01:46:34 +03:00
m e93f810946 fix: require verification before factual chat answers
CI / test (push) Successful in 47s
2026-08-24 01:42:41 +03:00
m 459d836468 Merge pull request 'DEV-000273 reconcile live audit evidence' (#251) from codex/dev-000248-self-development-runtime-status into main
CI / test (push) Successful in 59s
2026-08-23 22:39:40 +00:00
m 52058459f6 Merge remote-tracking branch 'origin/main' into codex/dev-000248-self-development-runtime-status
CI / test (push) Successful in 48s
CI / test (pull_request) Successful in 48s
2026-08-24 01:36:20 +03:00
m 9b91be0764 docs: reconcile live audit evidence
CI / test (push) Successful in 51s
CI / test (pull_request) Successful in 50s
2026-08-24 01:31:24 +03:00
m e4f8d6fa70 Merge pull request 'DEV-000270 local-first model scope' (#250) from codex/dev-000248-self-development-runtime-status into main
CI / test (push) Successful in 1m3s
2026-08-23 22:19:43 +00:00
m d7151f1d78 Merge remote-tracking branch 'origin/main' into codex/dev-000248-self-development-runtime-status
CI / test (push) Successful in 51s
CI / test (pull_request) Successful in 49s
2026-08-24 01:17:31 +03:00
m d357e7714a docs: define local-first model scope
CI / test (push) Successful in 54s
CI / test (pull_request) Successful in 52s
2026-08-24 01:15:00 +03:00
m 9160654a07 Merge pull request 'DEV-000268 fresh GPU timeout evidence' (#249) from codex/dev-000248-self-development-runtime-status into main
CI / test (push) Successful in 55s
2026-08-23 22:06:28 +00:00
m 29dad403ec docs: record gpu timeout after bounded retry
CI / test (push) Successful in 52s
CI / test (pull_request) Successful in 1m1s
2026-08-24 01:03:52 +03:00
m 041c1ce849 Merge pull request 'DEV-000268 configurable GPU provider timeout' (#248) from codex/dev-000248-self-development-runtime-status into main
CI / test (push) Successful in 1m0s
2026-08-23 21:59:45 +00:00
m ba162f6433 fix: make gpu provider timeout configurable
CI / test (push) Successful in 48s
CI / test (pull_request) Successful in 53s
2026-08-24 00:57:24 +03:00
m 3ca7ffab16 Merge pull request 'DEV-000267 refresh GPU and UI gap evidence' (#247) from codex/dev-000248-self-development-runtime-status into main
CI / test (push) Successful in 50s
2026-08-23 21:49:18 +00:00
m 615a3e4765 docs: refresh gpu and ui gap evidence
CI / test (push) Successful in 51s
CI / test (pull_request) Successful in 49s
2026-08-24 00:47:02 +03:00
m c459cd563c Merge pull request 'DEV-000266 live Agent Execution bridge evidence' (#246) from codex/dev-000248-self-development-runtime-status into main
CI / test (push) Successful in 50s
2026-08-23 21:40:02 +00:00
m b2753a64c4 Merge remote-tracking branch 'origin/main' into codex/dev-000248-self-development-runtime-status
CI / test (push) Successful in 48s
CI / test (pull_request) Successful in 49s
2026-08-24 00:37:49 +03:00
m 649dd602dd docs: record live agent execution bridge
CI / test (push) Successful in 58s
CI / test (pull_request) Successful in 51s
2026-08-24 00:34:58 +03:00
m 7a004e524a Merge pull request 'DEV-000266 local Agent Execution bridge' (#245) from codex/dev-000248-self-development-runtime-status into main
CI / test (push) Successful in 1m6s
2026-08-23 21:32:10 +00:00
m cb78c35f24 Merge remote-tracking branch 'origin/main' into codex/dev-000248-self-development-runtime-status
CI / test (push) Successful in 49s
CI / test (pull_request) Successful in 49s
2026-08-24 00:29:32 +03:00
m b1a79ede91 feat: add local agent execution bridge
CI / test (push) Successful in 51s
CI / test (pull_request) Successful in 49s
2026-08-24 00:27:10 +03:00
m e012694108 Merge pull request 'DEV-000265 local agent readiness evidence' (#244) from codex/dev-000248-self-development-runtime-status into main
CI / test (push) Successful in 51s
2026-08-23 21:16:09 +00:00
m c4f3f02933 docs(evidence): record local agent readiness
CI / test (push) Successful in 51s
CI / test (pull_request) Successful in 1m0s
2026-08-24 00:13:37 +03:00
m 102a57aaff Merge pull request 'DEV-000264 capability-aware agent worker readiness' (#243) from codex/dev-000248-self-development-runtime-status into main
CI / test (push) Successful in 51s
2026-08-23 21:11:33 +00:00
m 381e4d52e8 fix(agent-worker): report local capability readiness
CI / test (push) Successful in 51s
CI / test (pull_request) Successful in 1m18s
2026-08-24 00:08:18 +03:00
m c8dc9edbdc Merge pull request 'DEV-000263 record GPU qualification timeout' (#242) from codex/dev-000248-self-development-runtime-status into main
CI / test (push) Successful in 51s
2026-08-23 21:05:54 +00:00
m a0b0b32f0a docs(evidence): record gpu qualification timeout
CI / test (push) Successful in 50s
CI / test (pull_request) Successful in 51s
2026-08-24 00:03:29 +03:00
m 1f2068512e Merge pull request 'DEV-000262 additional evaluation runtime live evidence' (#241) from codex/dev-000248-self-development-runtime-status into main
CI / test (push) Successful in 1m16s
2026-08-23 20:57:34 +00:00
m b97c910434 docs(evidence): record additional evaluation runtime
CI / test (push) Successful in 59s
CI / test (pull_request) Successful in 50s
2026-08-23 23:55:09 +03:00
m b789878d01 Merge pull request 'DEV-000261 expose additional evaluation scheduling endpoint' (#240) from codex/dev-000248-self-development-runtime-status into main
CI / test (push) Successful in 50s
2026-08-23 20:52:29 +00:00
m 705e4a8ed6 Merge remote-tracking branch 'origin/main' into codex/dev-000248-self-development-runtime-status
CI / test (push) Successful in 52s
CI / test (pull_request) Successful in 49s
2026-08-23 23:49:33 +03:00
m 531dfa85ad feat(work): expose additional evaluation scheduling
CI / test (push) Successful in 54s
CI / test (pull_request) Successful in 49s
2026-08-23 23:45:45 +03:00
m 6e215b5b57 Merge pull request 'DEV-000261 durable additional task evaluation scheduling' (#239) from codex/dev-000248-self-development-runtime-status into main
CI / test (push) Successful in 54s
2026-08-23 20:42:20 +00:00
m d46c0cf719 chore(governance): align additional evaluation task base
CI / test (push) Successful in 59s
CI / test (pull_request) Successful in 49s
2026-08-23 23:38:32 +03:00
m 3eecc710ed Merge remote-tracking branch 'origin/main' into codex/dev-000248-self-development-runtime-status 2026-08-23 23:37:57 +03:00
m 1b4284592b feat(work): schedule bounded additional evaluations 2026-08-23 23:37:56 +03:00
m d2dd38b9ae Merge pull request 'DEV-000260 deep authenticated acceptance evidence' (#238) from codex/dev-000248-self-development-runtime-status into main
CI / test (push) Successful in 49s
2026-08-23 19:45:54 +00:00
m fb6b5e9677 Merge remote-tracking branch 'origin/main' into codex/dev-000248-self-development-runtime-status
CI / test (push) Successful in 53s
CI / test (pull_request) Successful in 58s
2026-08-23 22:43:57 +03:00
m 40735ce9c1 docs(acceptance): record deep authenticated evidence
CI / test (push) Successful in 47s
CI / test (pull_request) Successful in 49s
2026-08-23 22:41:26 +03:00
m 9f40c6a793 Merge pull request 'docs(governance): refresh post-responsive evidence' (#237) from codex/dev-000248-self-development-runtime-status into main
CI / test (push) Successful in 1m0s
2026-08-23 19:18:35 +00:00
m a9dbc3b624 docs(governance): refresh post-responsive evidence
CI / test (push) Successful in 52s
CI / test (pull_request) Successful in 49s
2026-08-23 22:16:20 +03:00
m d4b5416126 Merge pull request 'test(creator-web): guard responsive console layout' (#236) from codex/dev-000248-self-development-runtime-status into main
CI / test (push) Successful in 59s
2026-08-23 19:12:44 +00:00
m 7c4981ea09 test(creator-web): guard responsive console layout
CI / test (push) Successful in 53s
CI / test (pull_request) Successful in 50s
2026-08-23 22:10:31 +03:00
m 5e8286695f Merge pull request 'docs(governance): record operations acceptance smoke' (#235) from codex/dev-000248-self-development-runtime-status into main
CI / test (push) Successful in 1m0s
2026-08-23 19:05:31 +00:00
m 2b11baa170 docs(governance): record operations acceptance smoke
CI / test (push) Successful in 54s
CI / test (pull_request) Successful in 51s
2026-08-23 22:03:12 +03:00
m 8868258afd Merge pull request 'feat(capability): expose reachability rating evidence' (#234) from codex/dev-000248-self-development-runtime-status into main
CI / test (push) Successful in 57s
2026-08-23 18:57:40 +00:00
m 90cb2b1590 feat(capability): expose reachability rating evidence
CI / test (push) Successful in 50s
CI / test (pull_request) Successful in 52s
2026-08-23 21:55:13 +03:00
m 3d97b348f7 Merge pull request 'docs(governance): refresh live audit status' (#233) from codex/dev-000248-self-development-runtime-status into main
CI / test (push) Successful in 58s
2026-08-23 18:50:58 +00:00
m 3eea9e58cd docs(governance): refresh live audit status
CI / test (push) Successful in 49s
CI / test (pull_request) Successful in 49s
2026-08-23 21:48:43 +03:00
m 12ab124411 Merge pull request 'feat(governance): add development gate evidence transitions' (#232) from codex/dev-000248-self-development-runtime-status into main
CI / test (push) Successful in 1m0s
2026-08-23 18:40:44 +00:00
m 0886342031 feat(governance): add development gate evidence transitions
CI / test (push) Successful in 47s
CI / test (pull_request) Successful in 47s
2026-08-23 21:38:46 +03:00
m 3bfe76278b Merge pull request 'feat(governance): connect verifier to DevelopmentGate' (#231) from codex/dev-000248-self-development-runtime-status into main
CI / test (push) Successful in 56s
2026-08-23 18:32:28 +00:00
m 067facdfa4 feat(governance): connect verifier to development gate
CI / test (push) Successful in 47s
CI / test (pull_request) Successful in 52s
2026-08-23 21:29:53 +03:00
m f2e520e31e Merge pull request 'feat(governance): add local bounded verification executor' (#230) from codex/dev-000248-self-development-runtime-status into main
CI / test (push) Successful in 1m1s
2026-08-23 18:23:18 +00:00
m cf91807fab feat(governance): add local bounded verification executor
CI / test (push) Successful in 48s
CI / test (pull_request) Successful in 50s
2026-08-23 21:21:06 +03:00
m c925089fd2 Merge pull request 'fix(reflection): preserve cognition-required candidates' (#229) from codex/dev-000248-self-development-runtime-status into main
CI / test (push) Successful in 57s
2026-08-23 18:09:09 +00:00
m a2d0a3bf19 fix(reflection): preserve cognition-required candidates
CI / test (push) Successful in 51s
CI / test (pull_request) Successful in 48s
2026-08-23 21:07:02 +03:00
m c1cde67bcf Merge pull request 'fix(work-graph): preserve scheduled task evaluation revision' (#228) from codex/dev-000248-self-development-runtime-status into main
CI / test (push) Successful in 56s
2026-08-23 18:01:18 +00:00
m aeafc6e0ac fix(work-graph): preserve scheduled task evaluation revision
CI / test (push) Successful in 50s
CI / test (pull_request) Successful in 49s
2026-08-23 20:58:50 +03:00
m 1f5569db15 chore(governance): activate task evaluation callback repair 2026-08-23 20:57:52 +03:00
m 2b8cea5aa8 Merge pull request 'fix(cognitive): fail over task evaluation candidates' (#227) from codex/dev-000248-self-development-runtime-status into main
CI / test (push) Successful in 55s
2026-08-23 17:54:12 +00:00
m 7b09b3b2ec fix(cognitive): fail over task evaluation candidates
CI / test (push) Successful in 45s
CI / test (pull_request) Successful in 46s
2026-08-23 20:52:09 +03:00
m 9cc5101a30 Merge pull request 'test(work-graph): lock task evaluation wire contract' (#226) from codex/dev-000248-self-development-runtime-status into main
CI / test (push) Successful in 1m6s
2026-08-23 17:37:45 +00:00
m f90ed7af35 test(work-graph): lock task evaluation wire contract
CI / test (push) Successful in 50s
CI / test (pull_request) Successful in 50s
2026-08-23 20:35:27 +03:00
m 6f61601d77 Merge pull request 'fix(work-graph): diagnose task evaluation scheduling' (#225) from codex/dev-000248-self-development-runtime-status into main
CI / test (push) Successful in 54s
2026-08-23 17:19:14 +00:00
m 259e7343e3 fix(work-graph): log task evaluation scheduling rejection
CI / test (push) Successful in 50s
CI / test (pull_request) Successful in 51s
2026-08-23 20:17:08 +03:00
m cdc72e17be Merge pull request 'feat(research): expose truth runtime status' (#224) from codex/dev-000247-research-runtime-status into main
CI / test (push) Successful in 58s
2026-08-23 17:05:32 +00:00
m 931a0b0f0c feat(research): expose truth runtime status
CI / test (push) Successful in 1m5s
CI / test (pull_request) Successful in 47s
2026-08-23 20:03:14 +03:00
m e70be0103a Merge pull request 'feat(work-graph): expose runtime status' (#223) from codex/dev-000246-work-graph-runtime-status into main
CI / test (push) Successful in 59s
2026-08-23 16:59:37 +00:00
m 2353f1f3bb feat(work-graph): expose runtime status
CI / test (push) Successful in 48s
CI / test (pull_request) Successful in 46s
2026-08-23 19:57:32 +03:00
m 355ba7db56 Merge pull request 'feat(agent-worker): expose recovery cycle evidence' (#222) from codex/dev-000245-agent-worker-recovery-status into main
CI / test (push) Successful in 56s
2026-08-23 16:52:17 +00:00
m 4839f139e8 feat(agent-worker): expose recovery cycle evidence
CI / test (push) Successful in 47s
CI / test (pull_request) Successful in 46s
2026-08-23 19:50:19 +03:00
m ea90bf0fab Merge pull request 'feat(reflection): expose worker runtime status' (#221) from codex/dev-000244-reflection-worker-observability into main
CI / test (push) Successful in 58s
2026-08-23 16:44:09 +00:00
m 7978b495d1 feat(reflection): expose worker runtime status
CI / test (push) Successful in 48s
CI / test (pull_request) Successful in 46s
2026-08-23 19:42:07 +03:00
m d73d4fc118 Merge pull request 'feat(capability): consume qualification evidence in selection' (#220) from codex/dev-000243-qualification-selection-evidence into main
CI / test (push) Successful in 1m8s
feat(capability): consume qualification evidence in selection
2026-08-23 16:30:23 +00:00
m c81e2269e1 feat(capability): consume qualification evidence in selection
CI / test (push) Successful in 1m16s
CI / test (pull_request) Successful in 1m26s
2026-08-23 19:28:13 +03:00
m 47327cc6d8 Merge pull request 'feat(capability): persist model qualification observations' (#219) from codex/dev-000242-durable-qualification-observations into main
CI / test (push) Successful in 1m16s
feat(capability): persist model qualification observations
2026-08-23 16:22:58 +00:00
m d0ede46352 feat(capability): persist model qualification observations
CI / test (push) Successful in 1m14s
CI / test (pull_request) Successful in 1m15s
2026-08-23 19:21:04 +03:00
m e4ba1cb5fa Merge pull request 'docs(fleet): record medium and gpu qualification' (#218) from codex/dev-000241-fleet-qualification-evidence into main
CI / test (push) Successful in 1m15s
docs(fleet): record medium and gpu qualification
2026-08-23 16:13:58 +00:00
m 90104d2553 docs(fleet): record medium and gpu qualification
CI / test (push) Successful in 1m12s
CI / test (pull_request) Successful in 1m7s
2026-08-23 19:11:41 +03:00
m ca0d002073 Merge pull request 'docs(fleet): record CPU to lifeboat failover' (#217) from codex/dev-000240-cpu-lifeboat-failover-evidence into main
CI / test (push) Successful in 52s
docs(fleet): record CPU to lifeboat failover
2026-08-23 16:07:31 +00:00
m 20db36f52e docs(fleet): record CPU to lifeboat failover
CI / test (push) Successful in 51s
CI / test (pull_request) Successful in 1m11s
2026-08-23 19:05:49 +03:00
m 383d4c6e4f Merge pull request 'fix(cognitive): resolve providers by stable instance identity' (#216) from codex/dev-000239-stable-provider-resolution into main
CI / test (push) Successful in 50s
fix(cognitive): resolve providers by stable instance identity
2026-08-23 16:03:07 +00:00
m f3ddb45fe5 fix(cognitive): resolve providers by stable instance identity
CI / test (push) Successful in 1m12s
CI / test (pull_request) Successful in 1m12s
2026-08-23 19:01:18 +03:00
m 6061eb5c13 Merge pull request 'docs(runtime): record local fallback gateway evidence' (#215) from codex/dev-000238-local-fallback-evidence into main
CI / test (push) Successful in 1m16s
docs(runtime): record local fallback gateway evidence
2026-08-23 15:54:47 +00:00
m 0e46a64702 docs(runtime): record local fallback gateway evidence
CI / test (push) Successful in 1m15s
CI / test (pull_request) Successful in 47s
2026-08-23 18:52:27 +03:00
m 0609561dfe Merge pull request 'docs(ui): record authenticated console section smoke' (#214) from codex/dev-000237-ui-authenticated-section-smoke into main
CI / test (push) Successful in 1m0s
docs(ui): record authenticated console section smoke
2026-08-23 15:46:29 +00:00
m 1b88a35b82 docs(ui): record authenticated console section smoke
CI / test (push) Successful in 1m13s
CI / test (pull_request) Successful in 1m14s
2026-08-23 18:44:36 +03:00
m be01b6b883 Merge pull request 'fix(cognitive): satisfy qualification advisory contract' (#213) from codex/dev-000236-qualification-locale-fix into main
CI / test (push) Successful in 1m13s
fix(cognitive): satisfy qualification advisory contract
2026-08-23 15:37:10 +00:00
m deaddbca12 fix(cognitive): satisfy qualification advisory contract
CI / test (push) Successful in 1m19s
CI / test (pull_request) Successful in 1m8s
2026-08-23 18:34:45 +03:00
m b20611590a Merge pull request 'feat(cognitive): add bounded model qualification probe' (#212) from codex/dev-000235-model-qualification-probe into main
CI / test (push) Successful in 51s
feat(cognitive): add bounded model qualification probe
2026-08-23 15:30:02 +00:00
m d4972afd11 feat(cognitive): add bounded model qualification probe
CI / test (push) Successful in 58s
CI / test (pull_request) Successful in 45s
2026-08-23 18:27:22 +03:00
m 60e8292c1d Merge pull request 'feat(work): persist orchestration snapshot for replan' (#211) from codex/dev-000234-orchestration-snapshot-replan into main
CI / test (push) Successful in 1m15s
feat(work): persist orchestration snapshot for replan
2026-08-23 15:16:43 +00:00
m da251e1aa8 feat(work): persist orchestration snapshot for replan
CI / test (push) Successful in 1m8s
CI / test (pull_request) Successful in 1m11s
2026-08-23 18:13:07 +03:00
m eac39a3fd4 Merge pull request 'feat(work): require verified graph result fan-in' (#210) from codex/dev-000233-graph-result-fanin into main
CI / test (push) Successful in 1m0s
feat(work): require verified graph result fan-in
2026-08-23 15:07:44 +00:00
m 1f8af98f31 feat(work): require verified graph result fan-in
CI / test (push) Successful in 50s
CI / test (pull_request) Successful in 44s
2026-08-23 18:05:23 +03:00
m ee16103e03 Merge pull request 'feat(work): materialize durable solution waves' (#209) from codex/dev-000231-solution-orchestrator into main
CI / test (push) Successful in 1m11s
feat(work): materialize durable solution waves
2026-08-23 13:59:42 +00:00
m 09d53adfd1 feat(work): materialize durable solution waves
CI / test (push) Successful in 1m7s
CI / test (pull_request) Successful in 44s
2026-08-23 16:56:58 +03:00
m 89694047b1 Merge pull request 'feat(work): add persistent solution search orchestration policy' (#208) from codex/dev-000231-solution-orchestrator into main
CI / test (push) Successful in 1m20s
feat(work): add persistent solution search orchestration policy
2026-08-23 13:48:58 +00:00
m 23e0525c08 feat(work): add persistent solution search orchestration policy
CI / test (push) Successful in 1m9s
CI / test (pull_request) Successful in 58s
2026-08-23 16:45:58 +03:00
m 45874f2186 Merge pull request 'feat: expose governed development gate controller' (#207) from codex/dev-000230-development-gate-controller into main
CI / test (push) Successful in 1m16s
feat: expose governed development gate controller
2026-08-23 13:40:04 +00:00
m ae5848d799 feat: expose governed development gate controller
CI / test (push) Successful in 1m10s
CI / test (pull_request) Successful in 1m10s
2026-08-23 16:36:48 +03:00
m ec79f19a79 Merge pull request 'feat: connect creator research to truth runtime' (#206) from codex/dev-000229-research-bridge into main
CI / test (push) Successful in 1m53s
feat: connect creator research to truth runtime
2026-08-23 13:30:35 +00:00
m f63c4bb40c feat: connect creator research to truth runtime
CI / test (push) Successful in 1m9s
CI / test (pull_request) Successful in 1m8s
2026-08-23 16:27:00 +03:00
m 22f839df99 Merge pull request 'feat: expose controlled provider discovery status' (#205) from codex/dev-000228-controlled-discovery into main
CI / test (push) Successful in 1m9s
feat: expose controlled provider discovery status
2026-08-23 12:57:48 +00:00
m c41634a065 feat: expose controlled provider discovery status
CI / test (push) Successful in 1m5s
CI / test (pull_request) Successful in 1m2s
2026-08-23 15:54:30 +03:00
m 2609f31e59 Merge pull request 'feat: expose bounded local agent executor' (#204) from codex/dev-000227-local-executor into main
CI / test (push) Successful in 1m10s
feat: expose bounded local agent executor
2026-08-23 12:48:40 +00:00
m 7aa41b3be2 feat: expose bounded local agent executor
CI / test (push) Successful in 1m8s
CI / test (pull_request) Successful in 1m6s
2026-08-23 15:45:00 +03:00
m 1f6ca363ee docs: record authenticated UI acceptance evidence
CI / test (push) Successful in 1m14s
Merge DEV-000226 authenticated UI acceptance evidence
2026-08-23 12:31:16 +00:00
m 02471990ef docs: record authenticated UI acceptance
CI / test (push) Successful in 1m11s
CI / test (pull_request) Successful in 1m13s
2026-08-23 15:29:04 +03:00
m 40b56798e2 fix: serve login shell at direct route
CI / test (push) Successful in 1m9s
Merge DEV-000225 login route fix
2026-08-23 12:21:44 +00:00
m c53d7ded06 fix: serve login shell at direct route
CI / test (push) Successful in 1m13s
CI / test (pull_request) Successful in 1m13s
2026-08-23 15:19:32 +03:00
m e080c34a77 docs: record live fleet routing evidence
CI / test (push) Successful in 1m23s
Merge DEV-000224 live fleet evidence
2026-08-23 11:56:15 +00:00
m 306f53d484 docs: record live fleet routing evidence
CI / test (push) Successful in 1m10s
CI / test (pull_request) Successful in 57s
2026-08-23 14:53:50 +03:00
m c4411afaaf docs: reconcile current audit status
CI / test (push) Successful in 1m4s
Merge DEV-000223 status reconciliation
2026-08-23 11:42:59 +00:00
m c508941a8a docs: reconcile current audit status
CI / test (push) Successful in 58s
CI / test (pull_request) Successful in 59s
2026-08-23 14:41:14 +03:00
m ee68e2207e feat: extend task verifier and unresolved case builder
CI / test (push) Successful in 56s
Merge DEV-000222 verifier modes and case builder
2026-08-23 11:32:03 +00:00
m 3d9a5bccba feat: extend task verifier and unresolved case builder
CI / test (push) Successful in 47s
CI / test (pull_request) Successful in 51s
2026-08-23 14:30:45 +03:00
m d736500d98 feat(work): add independent task verifier runtime
CI / test (push) Successful in 1m10s
feat(work): add independent task verifier runtime (#198)
2026-08-23 11:21:10 +00:00
182 changed files with 5859 additions and 140 deletions
+197
View File
@@ -0,0 +1,197 @@
// Package developmentexecutor contains the local, read-only verification
// adapter for governed development candidates. It is deliberately not a
// source mutator, promotion authority, secret resolver, or deployment tool.
package developmentexecutor
import (
"context"
"crypto/sha256"
"encoding/hex"
"errors"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
)
type CheckKind string
const (
CheckGoTest CheckKind = "GO_TEST"
CheckGoVet CheckKind = "GO_VET"
CheckGitDiffCheck CheckKind = "GIT_DIFF_CHECK"
)
type Check struct {
Kind CheckKind
Packages []string
}
type Plan struct {
Worktree string
WorktreeID string
BaseRepoRevision string
ProtectedMainAccess string
ProductionAccess string
SecretAccess string
Checks []Check
}
type CheckResult struct {
Kind CheckKind `json:"kind"`
Passed bool `json:"passed"`
ExitCode int `json:"exit_code"`
DurationMillis int64 `json:"duration_millis"`
}
type Result struct {
WorktreeID string `json:"worktree_id"`
BaseRepoRevision string `json:"base_repo_revision"`
Passed bool `json:"passed"`
Checks []CheckResult `json:"checks"`
EvidenceRef string `json:"evidence_ref"`
}
var (
ErrInvalidPlan = errors.New("invalid governed verification plan")
ErrUnsupportedCheck = errors.New("unsupported governed verification check")
ErrProtectedWorktree = errors.New("protected worktree is not executable")
)
type CommandRunner interface {
Run(context.Context, string, string, []string) int
}
type OSCommandRunner struct{}
func (OSCommandRunner) Run(ctx context.Context, dir, name string, args []string) int {
command := exec.CommandContext(ctx, name, args...)
command.Dir = dir
command.Stdin = nil
command.Stdout = io.Discard
command.Stderr = io.Discard
command.Env = safeEnvironment()
if err := command.Run(); err != nil {
if exitErr, ok := err.(*exec.ExitError); ok {
return exitErr.ExitCode()
}
return -1
}
return 0
}
func ValidatePlan(plan Plan) error {
if strings.TrimSpace(plan.Worktree) == "" || strings.TrimSpace(plan.WorktreeID) == "" || strings.TrimSpace(plan.BaseRepoRevision) == "" || len(plan.Checks) == 0 {
return ErrInvalidPlan
}
if !filepath.IsAbs(plan.Worktree) {
return ErrInvalidPlan
}
if plan.ProtectedMainAccess != "DENIED" || plan.ProductionAccess != "DENIED" || plan.SecretAccess != "DENIED" {
return ErrProtectedWorktree
}
switch strings.ToLower(filepath.Base(filepath.Clean(plan.WorktreeID))) {
case "main", "protected-main", "production":
return ErrProtectedWorktree
}
for _, check := range plan.Checks {
if err := validateCheck(check); err != nil {
return err
}
}
return nil
}
func validateCheck(check Check) error {
switch check.Kind {
case CheckGoTest, CheckGoVet:
if len(check.Packages) == 0 {
return ErrInvalidPlan
}
for _, packagePath := range check.Packages {
if strings.TrimSpace(packagePath) == "" || strings.ContainsAny(packagePath, ";&|`$()<>\r\n") || unsafePackagePath(packagePath) {
return ErrInvalidPlan
}
}
case CheckGitDiffCheck:
if len(check.Packages) != 0 {
return ErrInvalidPlan
}
default:
return ErrUnsupportedCheck
}
return nil
}
func unsafePackagePath(value string) bool {
value = strings.ReplaceAll(value, "\\", "/")
for _, part := range strings.Split(value, "/") {
if part == ".." {
return true
}
}
return strings.HasPrefix(value, "../") || strings.HasPrefix(value, "/") || (len(value) > 1 && value[1] == ':')
}
func Execute(ctx context.Context, plan Plan, runner CommandRunner) (Result, error) {
if err := ValidatePlan(plan); err != nil {
return Result{}, err
}
if runner == nil {
runner = OSCommandRunner{}
}
if _, err := os.Stat(plan.Worktree); err != nil {
return Result{}, fmt.Errorf("worktree unavailable: %w", err)
}
result := Result{WorktreeID: plan.WorktreeID, BaseRepoRevision: plan.BaseRepoRevision, Passed: true, Checks: make([]CheckResult, 0, len(plan.Checks))}
for _, check := range plan.Checks {
started := time.Now()
name, args := commandFor(check)
exitCode := runner.Run(ctx, plan.Worktree, name, args)
passed := exitCode == 0
result.Checks = append(result.Checks, CheckResult{Kind: check.Kind, Passed: passed, ExitCode: exitCode, DurationMillis: time.Since(started).Milliseconds()})
if !passed {
result.Passed = false
}
if ctx.Err() != nil {
result.Passed = false
break
}
}
result.EvidenceRef = evidenceRef(result)
return result, nil
}
func commandFor(check Check) (string, []string) {
switch check.Kind {
case CheckGoTest:
return "go", append([]string{"test"}, check.Packages...)
case CheckGoVet:
return "go", append([]string{"vet"}, check.Packages...)
default:
return "git", []string{"diff", "--check"}
}
}
func evidenceRef(result Result) string {
hash := sha256.New()
_, _ = fmt.Fprintf(hash, "%s|%s|%t|", result.WorktreeID, result.BaseRepoRevision, result.Passed)
for _, check := range result.Checks {
_, _ = fmt.Fprintf(hash, "%s|%t|%d|", check.Kind, check.Passed, check.ExitCode)
}
return "verification:" + hex.EncodeToString(hash.Sum(nil))
}
func safeEnvironment() []string {
allowed := []string{"PATH", "SystemRoot", "TEMP", "TMP", "GOROOT", "GOPATH", "GOMODCACHE"}
result := make([]string, 0, len(allowed))
for _, name := range allowed {
if value, ok := os.LookupEnv(name); ok && value != "" {
result = append(result, name+"="+value)
}
}
return result
}
@@ -0,0 +1,70 @@
package developmentexecutor
import (
"context"
"errors"
"path/filepath"
"testing"
)
type fakeRunner struct {
calls []string
exits []int
}
func (r *fakeRunner) Run(_ context.Context, _ string, name string, args []string) int {
call := name
for _, arg := range args {
call += " " + arg
}
r.calls = append(r.calls, call)
if len(r.exits) == 0 {
return 0
}
exit := r.exits[0]
r.exits = r.exits[1:]
return exit
}
func validPlan(t *testing.T) Plan {
t.Helper()
worktree, err := filepath.Abs(".")
if err != nil {
t.Fatal(err)
}
return Plan{Worktree: worktree, WorktreeID: "sandbox-1", BaseRepoRevision: "revision-1", ProtectedMainAccess: "DENIED", ProductionAccess: "DENIED", SecretAccess: "DENIED", Checks: []Check{{Kind: CheckGoTest, Packages: []string{"./modules/work/..."}}, {Kind: CheckGoVet, Packages: []string{"./modules/work/..."}}, {Kind: CheckGitDiffCheck}}}
}
func TestExecuteRunsOnlyTypedChecksAndReturnsEvidence(t *testing.T) {
runner := &fakeRunner{}
result, err := Execute(context.Background(), validPlan(t), runner)
if err != nil || !result.Passed || len(result.Checks) != 3 || result.EvidenceRef == "" {
t.Fatalf("result=%+v err=%v", result, err)
}
if len(runner.calls) != 3 || runner.calls[0] != "go test ./modules/work/..." || runner.calls[2] != "git diff --check" {
t.Fatalf("calls=%v", runner.calls)
}
}
func TestExecuteFailureCannotBecomeVerificationSuccess(t *testing.T) {
runner := &fakeRunner{exits: []int{1, 0}}
plan := validPlan(t)
plan.Checks = []Check{{Kind: CheckGoTest, Packages: []string{"./..."}}, {Kind: CheckGitDiffCheck}}
result, err := Execute(context.Background(), plan, runner)
if err != nil || result.Passed || result.Checks[0].Passed || result.EvidenceRef == "" {
t.Fatalf("failed result was overclaimed: %+v err=%v", result, err)
}
}
func TestValidatePlanRejectsProtectedAndShellLikeInputs(t *testing.T) {
protected := validPlan(t)
protected.WorktreeID = "production"
if !errors.Is(ValidatePlan(protected), ErrProtectedWorktree) {
t.Fatal("protected worktree accepted")
}
injection := validPlan(t)
injection.Checks = []Check{{Kind: CheckGoTest, Packages: []string{"./...; echo leaked"}}}
if !errors.Is(ValidatePlan(injection), ErrInvalidPlan) {
t.Fatal("shell-like package accepted")
}
}
@@ -505,6 +505,14 @@ func (r *CapabilityRepository) loadCognitiveEvidence(ctx context.Context, value
if err == nil {
value.Performance = &performance
}
var qualification capability.ModelQualificationObservation
err = r.pool.QueryRow(ctx, `SELECT observation_id,provider_instance_id,provider_model_id,status,finish_reason,latency_millis,output_observed,quality_assessed,category,observed_at_unix_millis FROM capability.cognitive_model_qualification_observations WHERE provider_instance_id=$1 AND provider_model_id=$2 ORDER BY observed_at_unix_millis DESC LIMIT 1`, value.Provider.ID, value.Model.ProviderModelID).Scan(&qualification.ID, &qualification.ProviderInstanceID, &qualification.ProviderModelID, &qualification.Status, &qualification.FinishReason, &qualification.LatencyMillis, &qualification.OutputObserved, &qualification.QualityAssessed, &qualification.Category, &qualification.ObservedAtUnixMillis)
if err != nil && !errors.Is(err, pgx.ErrNoRows) {
return err
}
if err == nil {
value.Qualification = &qualification
}
if profileID != "" {
var taskEvaluation capability.TaskEvaluation
err = r.pool.QueryRow(ctx, `SELECT evaluation_id,task_profile_id,binding_id,binding_revision,capability_id,result,quality_score_millis,reliability_score_millis,latency_score_millis,overall_score_millis,critical_failure_millis,benchmark_version,artifact_id,observed_at_unix_millis FROM capability.cognitive_task_evaluations WHERE task_profile_id=$1 AND binding_id=$2 AND binding_revision=$3 ORDER BY observed_at_unix_millis DESC LIMIT 1`, profileID, value.Binding.ID, value.Binding.Revision).Scan(&taskEvaluation.ID, &taskEvaluation.ProfileID, &taskEvaluation.BindingID, &taskEvaluation.BindingRevision, &taskEvaluation.CapabilityID, &taskEvaluation.Result, &taskEvaluation.QualityScoreMillis, &taskEvaluation.ReliabilityScoreMillis, &taskEvaluation.LatencyScoreMillis, &taskEvaluation.OverallScoreMillis, &taskEvaluation.CriticalFailureMillis, &taskEvaluation.BenchmarkVersion, &taskEvaluation.ArtifactID, &taskEvaluation.ObservedAtUnixMillis)
@@ -633,3 +641,8 @@ func (r *CapabilityRepository) SaveTaskEvaluation(ctx context.Context, value cap
_, err := r.pool.Exec(ctx, `INSERT INTO capability.cognitive_task_evaluations (evaluation_id,task_profile_id,binding_id,binding_revision,capability_id,result,quality_score_millis,reliability_score_millis,latency_score_millis,overall_score_millis,critical_failure_millis,benchmark_version,artifact_id,observed_at_unix_millis) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14) ON CONFLICT DO NOTHING`, value.ID, value.ProfileID, value.BindingID, value.BindingRevision, value.CapabilityID, value.Result, value.QualityScoreMillis, value.ReliabilityScoreMillis, value.LatencyScoreMillis, value.OverallScoreMillis, value.CriticalFailureMillis, value.BenchmarkVersion, value.ArtifactID, value.ObservedAtUnixMillis)
return err
}
func (r *CapabilityRepository) SaveModelQualificationObservation(ctx context.Context, value capability.ModelQualificationObservation) error {
_, err := r.pool.Exec(ctx, `INSERT INTO capability.cognitive_model_qualification_observations (observation_id,provider_instance_id,provider_model_id,status,finish_reason,latency_millis,output_observed,quality_assessed,category,observed_at_unix_millis) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10) ON CONFLICT DO NOTHING`, value.ID, value.ProviderInstanceID, value.ProviderModelID, value.Status, value.FinishReason, value.LatencyMillis, value.OutputObserved, value.QualityAssessed, value.Category, value.ObservedAtUnixMillis)
return err
}
+52 -1
View File
@@ -11,6 +11,14 @@ import (
type EpistemicsRepository struct{ pool *pgxpool.Pool }
type TruthRuntimeStatus struct {
BundleCount int64 `json:"bundle_count"`
ClaimsByVerification map[string]int64 `json:"claims_by_verification"`
BeliefsByState map[string]int64 `json:"beliefs_by_state"`
SourcesByFreshness map[string]int64 `json:"sources_by_freshness"`
ObservedAtUnixMillis int64 `json:"observed_at_unix_millis"`
}
func OpenEpistemics(ctx context.Context, config ContinuityConfig) (*EpistemicsRepository, error) {
pool, err := openPool(ctx, config)
if err != nil {
@@ -25,6 +33,49 @@ func (r *EpistemicsRepository) Ping(ctx context.Context) error {
return r.pool.Ping(ctx)
}
// TruthRuntimeStatus is a read-only projection of durable truth bundles.
// It does not promote claims, revise beliefs, or mutate epistemic state.
func (r *EpistemicsRepository) TruthRuntimeStatus(ctx context.Context) (TruthRuntimeStatus, error) {
status := TruthRuntimeStatus{
ClaimsByVerification: map[string]int64{},
BeliefsByState: map[string]int64{},
SourcesByFreshness: map[string]int64{},
ObservedAtUnixMillis: time.Now().UnixMilli(),
}
if err := r.pool.QueryRow(ctx, `SELECT count(*) FROM epistemics.truth_bundles`).Scan(&status.BundleCount); err != nil {
return TruthRuntimeStatus{}, err
}
queries := []struct {
query string
destination map[string]int64
}{
{`SELECT claim_verification, count(*) FROM epistemics.truth_bundles GROUP BY claim_verification`, status.ClaimsByVerification},
{`SELECT belief_state, count(*) FROM epistemics.truth_bundles GROUP BY belief_state`, status.BeliefsByState},
{`SELECT snapshot_freshness, count(*) FROM epistemics.truth_bundles GROUP BY snapshot_freshness`, status.SourcesByFreshness},
}
for _, item := range queries {
rows, err := r.pool.Query(ctx, item.query)
if err != nil {
return TruthRuntimeStatus{}, err
}
for rows.Next() {
var state string
var count int64
if err := rows.Scan(&state, &count); err != nil {
rows.Close()
return TruthRuntimeStatus{}, err
}
item.destination[state] = count
}
if err := rows.Err(); err != nil {
rows.Close()
return TruthRuntimeStatus{}, err
}
rows.Close()
}
return status, nil
}
func (r *EpistemicsRepository) SaveSystemExpectation(ctx context.Context, value epistemics.SystemExpectation) error {
_, err := r.pool.Exec(ctx, `INSERT INTO epistemics.system_expectations (
expectation_id, original_intent_id, source_utterance_id, expected_outcome, expected_effect,
@@ -99,7 +150,7 @@ func (r *EpistemicsRepository) ClaimReflectionCandidate(ctx context.Context, wor
var value epistemics.ReflectionCandidate
err = tx.QueryRow(ctx, `SELECT reflection_candidate_id,trigger,state,source_experience_id,original_intent_id,source_utterance_id,summary,evidence_refs,requires_cognition,recorded_at_unix_millis,revision,claimed_by,claim_expires_at_unix_millis
FROM epistemics.reflection_candidates
WHERE state='OPEN' AND (claim_expires_at_unix_millis=0 OR claim_expires_at_unix_millis < $1)
WHERE state='OPEN' AND requires_cognition=false AND (claim_expires_at_unix_millis=0 OR claim_expires_at_unix_millis < $1)
ORDER BY recorded_at_unix_millis,reflection_candidate_id
FOR UPDATE SKIP LOCKED LIMIT 1`, nowUnixMillis).Scan(&value.ID, &value.Trigger, &value.State, &value.SourceExperienceID, &value.OriginalIntentID, &value.SourceUtteranceID, &value.Summary, &value.EvidenceRefs, &value.RequiresCognition, &value.RecordedAtUnixMillis, &value.Revision, &value.ClaimedBy, &value.ClaimExpiresAtUnixMillis)
if err != nil {
+58 -2
View File
@@ -32,6 +32,54 @@ func OpenWork(ctx context.Context, config ContinuityConfig) (*WorkRepository, er
}
return &WorkRepository{pool}, nil
}
// WorkGraphRuntimeStatus is a read-only operational projection owned by Work.
// It is not canonical graph state and cannot advance any lifecycle.
type WorkGraphRuntimeStatus struct {
GraphsByState map[string]int64 `json:"graphs_by_state"`
NodesByState map[string]int64 `json:"nodes_by_state"`
QueueByState map[string]int64 `json:"queue_by_state"`
ObservedAtUnixMillis int64 `json:"observed_at_unix_millis"`
}
func (r *WorkRepository) RuntimeStatus(ctx context.Context) (WorkGraphRuntimeStatus, error) {
status := WorkGraphRuntimeStatus{
GraphsByState: map[string]int64{},
NodesByState: map[string]int64{},
QueueByState: map[string]int64{},
ObservedAtUnixMillis: time.Now().UnixMilli(),
}
queries := []struct {
query string
destination map[string]int64
}{
{`SELECT state, count(*) FROM work.graphs GROUP BY state`, status.GraphsByState},
{`SELECT state, count(*) FROM work.graph_nodes GROUP BY state`, status.NodesByState},
{`SELECT state, count(*) FROM work.agent_dispatch_queue GROUP BY state`, status.QueueByState},
}
for _, item := range queries {
rows, err := r.pool.Query(ctx, item.query)
if err != nil {
return WorkGraphRuntimeStatus{}, err
}
for rows.Next() {
var state string
var count int64
if err := rows.Scan(&state, &count); err != nil {
rows.Close()
return WorkGraphRuntimeStatus{}, err
}
item.destination[state] = count
}
if err := rows.Err(); err != nil {
rows.Close()
return WorkGraphRuntimeStatus{}, err
}
rows.Close()
}
return status, nil
}
func (r *WorkRepository) Close() { r.pool.Close() }
func (r *WorkRepository) Ping(ctx context.Context) error { return r.pool.Ping(ctx) }
func (r *WorkRepository) Create(ctx context.Context, target work.TaskTarget) error {
@@ -263,7 +311,7 @@ func (r *WorkRepository) SaveWorkGraph(ctx context.Context, graph work.WorkGraph
return err
}
defer tx.Rollback(ctx)
if _, err := tx.Exec(ctx, `INSERT INTO work.graphs(graph_id,revision,state,max_parallel,max_attempts,parent_graph_id,parent_graph_revision,trigger_evidence_ref) VALUES($1,$2,$3,$4,$5,$6,$7,$8) ON CONFLICT(graph_id,revision) DO NOTHING`, graph.ID, graph.Revision, graph.State, graph.Budget.MaxParallel, graph.Budget.MaxAttempts, graph.ParentGraphID, graph.ParentGraphRevision, graph.TriggerEvidenceRef); err != nil {
if _, err := tx.Exec(ctx, `INSERT INTO work.graphs(graph_id,revision,state,max_parallel,max_attempts,parent_graph_id,parent_graph_revision,trigger_evidence_ref,orchestration_bound,orchestration_goal_id,orchestration_goal_state,orchestration_search_round,orchestration_max_round,orchestration_cancel_requested,orchestration_verification_ready,orchestration_unresolved) VALUES($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16) ON CONFLICT(graph_id,revision) DO NOTHING`, graph.ID, graph.Revision, graph.State, graph.Budget.MaxParallel, graph.Budget.MaxAttempts, graph.ParentGraphID, graph.ParentGraphRevision, graph.TriggerEvidenceRef, graph.SearchInputBound, graph.SearchInput.GoalID, graph.SearchInput.GoalState, graph.SearchInput.SearchRound, graph.SearchInput.MaxRound, graph.SearchInput.CancelRequested, graph.SearchInput.VerificationReady, graph.SearchInput.Unresolved); err != nil {
return err
}
for _, node := range graph.Nodes {
@@ -285,7 +333,7 @@ func (r *WorkRepository) SaveWorkGraph(ctx context.Context, graph work.WorkGraph
func (r *WorkRepository) LoadWorkGraph(ctx context.Context, graphID string, revision int64) (work.WorkGraph, error) {
var graph work.WorkGraph
err := r.pool.QueryRow(ctx, `SELECT graph_id,revision,state,max_parallel,max_attempts,parent_graph_id,parent_graph_revision,trigger_evidence_ref FROM work.graphs WHERE graph_id=$1 AND revision=$2`, graphID, revision).Scan(&graph.ID, &graph.Revision, &graph.State, &graph.Budget.MaxParallel, &graph.Budget.MaxAttempts, &graph.ParentGraphID, &graph.ParentGraphRevision, &graph.TriggerEvidenceRef)
err := r.pool.QueryRow(ctx, `SELECT graph_id,revision,state,max_parallel,max_attempts,parent_graph_id,parent_graph_revision,trigger_evidence_ref,orchestration_bound,orchestration_goal_id,orchestration_goal_state,orchestration_search_round,orchestration_max_round,orchestration_cancel_requested,orchestration_verification_ready,orchestration_unresolved FROM work.graphs WHERE graph_id=$1 AND revision=$2`, graphID, revision).Scan(&graph.ID, &graph.Revision, &graph.State, &graph.Budget.MaxParallel, &graph.Budget.MaxAttempts, &graph.ParentGraphID, &graph.ParentGraphRevision, &graph.TriggerEvidenceRef, &graph.SearchInputBound, &graph.SearchInput.GoalID, &graph.SearchInput.GoalState, &graph.SearchInput.SearchRound, &graph.SearchInput.MaxRound, &graph.SearchInput.CancelRequested, &graph.SearchInput.VerificationReady, &graph.SearchInput.Unresolved)
if err != nil {
return work.WorkGraph{}, fmt.Errorf("load graph: %w", err)
}
@@ -351,6 +399,14 @@ func (r *WorkRepository) SaveGraphSynthesis(ctx context.Context, conflict work.C
return tx.Commit(ctx)
}
func (r *WorkRepository) SaveGraphResultCandidate(ctx context.Context, candidate work.ResultCandidate) error {
if candidate.ID == "" || candidate.GraphID == "" || candidate.GraphRevision < 1 || candidate.NodeID == "" || candidate.AuthorRunID == "" || candidate.EvidenceSummary == "" || candidate.Summary == "" {
return errors.New("invalid graph result candidate")
}
_, err := r.pool.Exec(ctx, `INSERT INTO work.graph_result_candidates(result_candidate_id,graph_id,graph_revision,node_id,author_run_id,artifact_refs,evidence_summary,summary) VALUES($1,$2,$3,$4,$5,$6,$7,$8) ON CONFLICT(result_candidate_id) DO NOTHING`, candidate.ID, candidate.GraphID, candidate.GraphRevision, candidate.NodeID, candidate.AuthorRunID, candidate.ArtifactRefs, candidate.EvidenceSummary, candidate.Summary)
return err
}
func (r *WorkRepository) RecoverStaleClaims(ctx context.Context, nowUnixMillis, staleAfterMillis int64) (int64, error) {
tx, err := r.pool.Begin(ctx)
if err != nil {
+150
View File
@@ -0,0 +1,150 @@
package research
import (
"context"
"crypto/sha256"
"encoding/hex"
"errors"
"html"
"io"
"net/http"
"net/url"
"regexp"
"strconv"
"strings"
"git.cin.su/m/Assistant/contracts/epistemics"
)
var ErrSearchRejected = errors.New("web search request rejected")
var (
searchLinkPattern = regexp.MustCompile(`(?is)<a[^>]+class=["'][^"']*result__a[^"']*["'][^>]*>(.*?)</a>`)
searchSnippetPattern = regexp.MustCompile(`(?is)<a[^>]+class=["'][^"']*result__snippet[^"']*["'][^>]*>(.*?)</a>`)
bingLinkPattern = regexp.MustCompile(`(?is)<li[^>]+class=["'][^"']*b_algo[^"']*["'][^>]*>.*?<h2[^>]*><a[^>]*>(.*?)</a>`)
bingSnippetPattern = regexp.MustCompile(`(?is)<li[^>]+class=["'][^"']*b_algo[^"']*["'][^>]*>.*?<p[^>]*>(.*?)</p>`)
tagPattern = regexp.MustCompile(`(?s)<[^>]*>`)
termPattern = regexp.MustCompile(`(?i)[\p{L}\p{N}]{2,}`)
)
var searchStopTerms = map[string]struct{}{
"какая": {}, "какое": {}, "какие": {}, "какой": {}, "что": {}, "как": {}, "где": {}, "когда": {}, "почему": {}, "мне": {}, "пожалуйста": {},
"what": {}, "which": {}, "where": {}, "when": {}, "why": {}, "how": {}, "please": {}, "the": {}, "this": {},
}
// SearchConnector uses a configured HTTPS search endpoint. It does not know
// cities, weather terms, entities or model names: the complete semantic query
// comes from the interpretation layer.
type SearchConnector struct {
Client *http.Client
EndpointTemplate string
AllowedHosts map[string]struct{}
MaxBytes int64
MaxResults int
}
func (c SearchConnector) Collect(ctx context.Context, request epistemics.WebSearchRequest, nowUnixMillis int64) ([]epistemics.ResearchCandidate, error) {
if request.Plan.Validate() != nil || strings.TrimSpace(request.Query) == "" || nowUnixMillis < 1 || !strings.Contains(c.EndpointTemplate, "%s") {
return nil, ErrSearchRejected
}
endpoint, err := url.Parse(strings.Replace(c.EndpointTemplate, "%s", url.QueryEscape(strings.TrimSpace(request.Query)), 1))
if err != nil || endpoint.Scheme != "https" || endpoint.Host == "" || !c.allowedHost(strings.ToLower(endpoint.Hostname())) {
return nil, ErrSearchRejected
}
client := c.Client
if client == nil {
client = &http.Client{}
}
maxBytes := c.MaxBytes
if maxBytes <= 0 || maxBytes > 4<<20 {
maxBytes = 512 << 10
}
req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint.String(), nil)
if err != nil {
return nil, ErrSearchRejected
}
response, err := client.Do(req)
if err != nil {
return nil, err
}
defer response.Body.Close()
if response.StatusCode < 200 || response.StatusCode >= 300 {
return nil, ErrSearchRejected
}
body, err := io.ReadAll(io.LimitReader(response.Body, maxBytes+1))
if err != nil || len(body) == 0 || int64(len(body)) > maxBytes {
return nil, ErrSearchRejected
}
links := searchLinkPattern.FindAllSubmatch(body, c.limit())
snippets := searchSnippetPattern.FindAllSubmatch(body, c.limit())
if len(links) == 0 {
links = bingLinkPattern.FindAllSubmatch(body, c.limit())
snippets = bingSnippetPattern.FindAllSubmatch(body, c.limit())
}
if len(links) == 0 {
return nil, nil
}
digest := sha256.Sum256(body)
digestText := "sha256:" + hex.EncodeToString(digest[:])
base := hex.EncodeToString(digest[:])
sourceID := epistemics.SourceID("source:search:" + base)
snapshotID := epistemics.SourceSnapshotID("snapshot:search:" + base)
results := make([]epistemics.ResearchCandidate, 0, len(links))
for index, link := range links {
title := cleanHTML(string(link[1]))
snippet := ""
if index < len(snippets) {
snippet = cleanHTML(string(snippets[index][1]))
}
statement := strings.TrimSpace(strings.Trim(title+" — "+snippet, " —"))
if statement == "" || !searchStatementRelatesToQuery(request.Query, statement) {
continue
}
ordinal := strconv.Itoa(index)
excerptID := epistemics.SourceExcerptID("excerpt:search:" + base + ":" + ordinal)
results = append(results, epistemics.ResearchCandidate{
PropositionKey: request.PropositionKey + ":result:" + ordinal,
Statement: statement,
Source: epistemics.Source{ID: sourceID, Kind: "web-search", Locator: endpoint.String(), Title: title, Publisher: endpoint.Hostname()},
Snapshot: epistemics.SourceSnapshot{ID: snapshotID, SourceID: sourceID, ContentDigest: digestText, CapturedAtUnixMillis: nowUnixMillis, Freshness: epistemics.FreshnessCurrent},
Excerpt: epistemics.SourceExcerpt{ID: excerptID, SnapshotID: snapshotID, Text: statement, Start: 0, End: int64(len(statement))},
Evidence: epistemics.EvidenceObserved, Coverage: epistemics.CoverageComplete, Conflict: epistemics.ConflictNone,
})
}
return results, nil
}
// searchStatementRelatesToQuery is a bounded relevance gate, not a truth
// check. It prevents generic search pages from becoming apparent evidence
// when their content has no substantive relation to the requested query.
func searchStatementRelatesToQuery(query, statement string) bool {
terms := termPattern.FindAllString(strings.ToLower(query), -1)
content := strings.ToLower(statement)
usable := 0
for _, term := range terms {
if _, stop := searchStopTerms[term]; stop || len([]rune(term)) < 4 {
continue
}
usable++
runes := []rune(term)
prefixLen := 5
if len(runes) < prefixLen {
prefixLen = len(runes)
}
if strings.Contains(content, string(runes[:prefixLen])) {
return true
}
}
return usable == 0
}
func (c SearchConnector) allowedHost(host string) bool { _, ok := c.AllowedHosts[host]; return ok }
func (c SearchConnector) limit() int {
if c.MaxResults <= 0 || c.MaxResults > 8 {
return 3
}
return c.MaxResults
}
func cleanHTML(value string) string {
return strings.Join(strings.Fields(html.UnescapeString(tagPattern.ReplaceAllString(value, ""))), " ")
}
+211
View File
@@ -0,0 +1,211 @@
package main
import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/http"
"os"
"strings"
"time"
"git.cin.su/m/Assistant/contracts/artifact"
"git.cin.su/m/Assistant/contracts/capability"
"git.cin.su/m/Assistant/contracts/cognitive"
"git.cin.su/m/Assistant/contracts/work"
capabilitydomain "git.cin.su/m/Assistant/modules/capability/domain"
)
type bridge struct {
cognitiveURL string
callbackURL string
verifierURL string
client *http.Client
}
func main() {
b := bridge{
cognitiveURL: strings.TrimRight(envOr("COGNITIVE_WORKER_URL", "http://cognitive-worker:8083"), "/"),
callbackURL: strings.TrimRight(envOr("WORK_GRAPH_RUNTIME_URL", "http://work-graph-runtime:8088"), "/"),
verifierURL: strings.TrimRight(envOr("TASK_VERIFIER_URL", "http://task-verifier:8091"), "/"),
client: &http.Client{Timeout: 10 * time.Minute},
}
mux := http.NewServeMux()
mux.HandleFunc("/healthz", healthHandler)
mux.HandleFunc("/readyz", healthHandler)
mux.HandleFunc("/v1/execute", b.executeHandler)
addr := envOr("LISTEN_ADDR", ":8092")
if err := http.ListenAndServe(addr, mux); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
func (b bridge) executeHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
var request work.AgentExecutionRequest
if err := json.NewDecoder(http.MaxBytesReader(w, r.Body, 1<<20)).Decode(&request); err != nil {
http.Error(w, "invalid execution request", http.StatusBadRequest)
return
}
result, err := b.execute(r.Context(), request)
if err != nil {
http.Error(w, err.Error(), http.StatusBadGateway)
return
}
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(result)
}
func (b bridge) execute(ctx context.Context, request work.AgentExecutionRequest) (work.AgentExecutionResult, error) {
now := time.Now().UnixMilli()
intent := request.Intent
if intent.TaskEvaluationExecution != nil && intent.TaskEvaluationExecution.GenericMessage != "" {
return b.executeGenericCognitiveWork(ctx, request, *intent.TaskEvaluationExecution)
}
if intent.TaskEvaluationExecution == nil || intent.TaskEvaluationProfile == nil || len(intent.TaskEvaluationCases) == 0 {
return work.AgentExecutionResult{State: work.AgentExecutionWaiting, Summary: "локальный bridge не поддерживает это намерение; работа сохранена и ожидает подходящего исполнителя", ObservedAtUnixMillis: now}, nil
}
execution := intent.TaskEvaluationExecution
if execution.GraphID == "" || execution.NodeID == "" || execution.BindingRevision < 1 || execution.ActorID == "" || execution.PrincipalID == "" || execution.ScopeID == "" {
return work.AgentExecutionResult{State: work.AgentExecutionWaiting, Summary: "контекст task-evaluation неполон; работа сохранена для повторной маршрутизации", ObservedAtUnixMillis: now}, nil
}
payload := capability.TaskEvaluationExecutionRequest{Profile: *intent.TaskEvaluationProfile, CapabilityID: intent.TaskEvaluationProfile.CapabilityID, BindingID: intent.TaskEvaluationBindingID, BindingRevision: execution.BindingRevision, ActorID: execution.ActorID, PrincipalID: execution.PrincipalID, ScopeID: execution.ScopeID, Cases: intent.TaskEvaluationCases, MaxOutputBytes: 4096}
response, err := postJSON[capability.TaskEvaluationExecutionResponse](ctx, b.client, b.cognitiveURL+"/v1/task-evaluations/execute", payload)
if err != nil {
return work.AgentExecutionResult{}, err
}
if len(response.Responses) == 0 || response.ObservedAtUnixMillis < 1 {
return work.AgentExecutionResult{}, fmt.Errorf("task evaluation returned no observations")
}
caseByID := make(map[string]capability.TaskEvaluationCase, len(intent.TaskEvaluationCases))
for _, item := range intent.TaskEvaluationCases {
caseByID[item.CaseID] = item
}
observations := response.Observations
if !hasCriticalFailure(observations) {
observations = make([]capability.TaskCaseObservation, 0, len(response.Responses))
for _, item := range response.Responses {
caseValue, ok := caseByID[item.CaseID]
if !ok || caseValue.Verification.Mode == "" {
return work.AgentExecutionResult{State: work.AgentExecutionWaiting, Summary: "task-evaluation case has no bounded verifier; work remains waiting", ObservedAtUnixMillis: time.Now().UnixMilli()}, nil
}
verified, verifyErr := postJSON[capability.TaskCaseVerificationResponse](ctx, b.client, b.verifierURL+"/v1/task-evaluations/verify", capability.TaskCaseVerificationRequest{CaseID: item.CaseID, ResponseText: item.ResponseText, LatencyMicros: item.LatencyMicros, EvidenceArtifactRef: item.EvidenceArtifactRef, Verification: caseValue.Verification})
if verifyErr != nil {
return work.AgentExecutionResult{}, verifyErr
}
observations = append(observations, capability.TaskCaseObservation{CaseID: verified.CaseID, Result: verified.Result, QualityScoreMillis: verified.QualityScoreMillis, LatencyMicros: verified.LatencyMicros, CriticalFailure: verified.CriticalFailure, EvidenceArtifactRef: verified.EvidenceArtifactRef, VerificationEvidenceRef: verified.VerificationEvidenceRef, VerifierIdentity: verified.VerifierIdentity})
}
}
evaluation, err := capabilitydomain.BuildTaskEvaluation(*intent.TaskEvaluationProfile, capability.TaskEvaluationID("evaluation:"+intent.DispatchID), response.BindingID, response.BindingRevision, intent.TaskEvaluationProfile.CapabilityID, observations, artifact.ArtifactID(intent.TaskEvaluationCases[0].EvidenceArtifactRef), response.ObservedAtUnixMillis)
if err != nil {
return work.AgentExecutionResult{}, err
}
callback := work.TaskEvaluationEvidenceCallback{GraphID: execution.GraphID, Revision: execution.GraphRevision, NodeID: execution.NodeID, Evaluation: evaluation, Succeeded: true}
if _, err := postJSON[json.RawMessage](ctx, b.client, b.callbackURL+"/v1/work-graphs/task-evaluations/callback", callback); err != nil {
return work.AgentExecutionResult{}, err
}
return work.AgentExecutionResult{State: work.AgentExecutionCompleted, Summary: "local bridge executed task evaluation and persisted verifier callback", OutcomeRef: string(evaluation.ID), ArtifactRefs: []string{string(evaluation.ArtifactID)}, ObservedAtUnixMillis: time.Now().UnixMilli()}, nil
}
func (b bridge) executeGenericCognitiveWork(ctx context.Context, request work.AgentExecutionRequest, execution work.TaskEvaluationDispatchContext) (work.AgentExecutionResult, error) {
if execution.GraphID == "" || execution.NodeID == "" || execution.ActorID == "" || execution.PrincipalID == "" || execution.ScopeID == "" {
return work.AgentExecutionResult{State: work.AgentExecutionWaiting, Summary: "generic cognitive work context is incomplete; durable work remains waiting for rerouting", ObservedAtUnixMillis: time.Now().UnixMilli()}, nil
}
maxOutputBytes := execution.GenericMaxOutputBytes
if maxOutputBytes < 1 {
maxOutputBytes = 4096
}
dataPolicy := capability.DataPolicy(execution.GenericDataPolicy)
if dataPolicy == "" {
dataPolicy = capability.DataPolicyLocalOnly
}
response, err := postJSON[cognitive.GatewayAdvisoryResponse](ctx, b.client, b.cognitiveURL+"/v1/advisories", cognitive.GatewayAdvisoryRequest{
Version: cognitive.CreatorAdvisoryV1, RequestID: cognitive.RequestID("agent-work:" + request.Intent.DispatchID), ActorID: execution.ActorID,
PrincipalID: execution.PrincipalID, ScopeID: execution.ScopeID, Message: execution.GenericMessage, Locale: execution.GenericLocale,
MaxOutputBytes: maxOutputBytes, DataPolicy: dataPolicy, Meaning: cognitive.TaskMeaning{SpeechAct: "DIRECTIVE", RequestedOutcome: execution.GenericPurpose},
})
if err != nil {
return work.AgentExecutionResult{}, err
}
if response.FinishReason != cognitive.FinishCompleted || strings.TrimSpace(response.AdvisoryText) == "" {
return work.AgentExecutionResult{}, fmt.Errorf("generic cognitive work did not complete: %s", response.FinishReason)
}
candidateID := "candidate:" + request.Intent.DispatchID
artifactRef := work.EvidenceRef("agent-output:" + request.Intent.DispatchID)
verification, err := postJSON[work.GenericCandidateVerificationResponse](ctx, b.client, b.verifierURL+"/v1/work-graphs/candidates/verify", work.GenericCandidateVerificationRequest{
GraphID: execution.GraphID, GraphRevision: execution.GraphRevision, NodeID: execution.NodeID, AuthorRunID: request.Intent.RunID, CandidateID: candidateID, Summary: response.AdvisoryText, EvidenceRefs: []work.EvidenceRef{artifactRef},
})
if err != nil {
return work.AgentExecutionResult{}, err
}
if !verification.Verified || verification.VerificationEvidenceRef == "" || verification.VerifierIdentity == "" {
return work.AgentExecutionResult{State: work.AgentExecutionWaiting, Summary: "generic candidate envelope was not independently verified; work remains waiting", ObservedAtUnixMillis: time.Now().UnixMilli()}, nil
}
candidate := work.GraphResultCallback{GraphID: execution.GraphID, Revision: execution.GraphRevision, Candidate: work.ResultCandidate{
ID: candidateID, GraphID: execution.GraphID, GraphRevision: execution.GraphRevision, NodeID: execution.NodeID,
AuthorRunID: request.Intent.RunID, ArtifactRefs: []work.EvidenceRef{artifactRef, work.EvidenceRef(verification.VerificationEvidenceRef)}, EvidenceSummary: "candidate attribution and evidence envelope independently verified; semantic content remains unverified",
Summary: response.AdvisoryText,
}}
if _, err := postJSON[json.RawMessage](ctx, b.client, b.callbackURL+"/v1/work-graphs/result-callback", candidate); err != nil {
return work.AgentExecutionResult{}, err
}
return work.AgentExecutionResult{State: work.AgentExecutionCompleted, Summary: "local cognitive specialist produced a candidate for independent verification", OutcomeRef: "candidate:" + request.Intent.DispatchID, ArtifactRefs: []string{"agent-output:" + request.Intent.DispatchID}, ObservedAtUnixMillis: time.Now().UnixMilli()}, nil
}
func hasCriticalFailure(observations []capability.TaskCaseObservation) bool {
for _, observation := range observations {
if observation.CriticalFailure {
return true
}
}
return false
}
func postJSON[T any](ctx context.Context, client *http.Client, url string, payload any) (T, error) {
var result T
body, err := json.Marshal(payload)
if err != nil {
return result, err
}
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(body))
if err != nil {
return result, err
}
req.Header.Set("Content-Type", "application/json")
resp, err := client.Do(req)
if err != nil {
return result, err
}
defer resp.Body.Close()
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return result, fmt.Errorf("POST %s: status %s", url, resp.Status)
}
if resp.StatusCode == http.StatusNoContent {
return result, nil
}
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return result, err
}
return result, nil
}
func healthHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"runtime":"agent-execution-bridge","status":"ok","capabilities":["task-evaluation","generic-cognitive-work"]}`))
}
func envOr(name, fallback string) string {
if value := strings.TrimSpace(os.Getenv(name)); value != "" {
return value
}
return fallback
}
+88
View File
@@ -0,0 +1,88 @@
package main
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"git.cin.su/m/Assistant/contracts/capability"
"git.cin.su/m/Assistant/contracts/cognitive"
"git.cin.su/m/Assistant/contracts/work"
)
func TestBridgeWaitsForUnsupportedIntent(t *testing.T) {
b := bridge{client: http.DefaultClient}
result, err := b.execute(context.Background(), work.AgentExecutionRequest{Intent: work.AgentDispatchIntent{DispatchID: "dispatch-unsupported"}})
if err != nil {
t.Fatal(err)
}
if result.State != work.AgentExecutionWaiting || result.ObservedAtUnixMillis < 1 {
t.Fatalf("result=%+v", result)
}
}
func TestHealthHandlerExposesBoundedCapability(t *testing.T) {
recorder := httptest.NewRecorder()
request := httptest.NewRequest(http.MethodGet, "/healthz", nil)
healthHandler(recorder, request)
if recorder.Code != http.StatusOK || recorder.Body.String() == "" {
t.Fatalf("status=%d body=%q", recorder.Code, recorder.Body.String())
}
}
func TestBridgeRunsTaskEvaluationThroughVerifierAndCallback(t *testing.T) {
profile := capability.TaskEvaluationProfile{ID: "profile-1", Revision: 1, CapabilityID: "capability-1", Stage: capability.TaskEvaluationGeneral, BenchmarkVersion: "general-v1", RequiredCaseIDs: []string{"case-1"}, QualityWeightMillis: 600, ReliabilityWeightMillis: 300, LatencyWeightMillis: 100}
verification := capability.TaskCaseVerification{Mode: capability.TaskCaseVerificationExactText, ExpectedText: "answer", MinimumQualityMillis: 900}
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/v1/task-evaluations/execute":
_ = json.NewEncoder(w).Encode(capability.TaskEvaluationExecutionResponse{ProfileID: profile.ID, BindingID: "binding-1", BindingRevision: 3, Responses: []capability.TaskCaseResponse{{CaseID: "case-1", ResponseText: "answer", LatencyMicros: 2, EvidenceArtifactRef: "artifact-1"}}, ObservedAtUnixMillis: 10})
case "/v1/task-evaluations/verify":
_ = json.NewEncoder(w).Encode(capability.TaskCaseVerificationResponse{CaseID: "case-1", Result: capability.EvaluationSupported, QualityScoreMillis: 900, LatencyMicros: 2, EvidenceArtifactRef: "artifact-1", VerificationEvidenceRef: "verify-1", VerifierIdentity: "verifier-1"})
case "/v1/work-graphs/task-evaluations/callback":
w.WriteHeader(http.StatusNoContent)
default:
http.NotFound(w, r)
}
}))
defer server.Close()
b := bridge{cognitiveURL: server.URL, callbackURL: server.URL, verifierURL: server.URL, client: server.Client()}
result, err := b.execute(context.Background(), work.AgentExecutionRequest{Intent: work.AgentDispatchIntent{DispatchID: "dispatch-1", TaskEvaluationBindingID: "binding-1", TaskEvaluationProfile: &profile, TaskEvaluationCases: []capability.TaskEvaluationCase{{CaseID: "case-1", Prompt: "answer", EvidenceArtifactRef: "artifact-1", Verification: verification}}, TaskEvaluationExecution: &work.TaskEvaluationDispatchContext{GraphID: "graph-1", GraphRevision: 1, NodeID: "node-1", BindingRevision: 3, ActorID: "actor-1", PrincipalID: "principal-1", ScopeID: "scope-1"}}})
if err != nil || result.State != work.AgentExecutionCompleted || result.OutcomeRef == "" {
t.Fatalf("result=%+v err=%v", result, err)
}
}
func TestBridgeRunsGenericCognitiveWorkAsUnverifiedGraphCandidate(t *testing.T) {
var callback work.GraphResultCallback
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/v1/advisories":
var request cognitive.GatewayAdvisoryRequest
if err := json.NewDecoder(r.Body).Decode(&request); err != nil || request.Message != "find the bounded solution" {
t.Fatalf("unexpected advisory request: %+v err=%v", request, err)
}
_ = json.NewEncoder(w).Encode(cognitive.GatewayAdvisoryResponse{Version: cognitive.CreatorAdvisoryV1, RequestID: request.RequestID, AdvisoryText: "candidate answer", FinishReason: cognitive.FinishCompleted})
case "/v1/work-graphs/result-callback":
if err := json.NewDecoder(r.Body).Decode(&callback); err != nil {
t.Fatal(err)
}
w.WriteHeader(http.StatusNoContent)
case "/v1/work-graphs/candidates/verify":
_ = json.NewEncoder(w).Encode(work.GenericCandidateVerificationResponse{Verified: true, VerificationEvidenceRef: "verification:generic-1", VerifierIdentity: "deterministic-generic-envelope-verifier-v1", Summary: "envelope verified"})
default:
http.NotFound(w, r)
}
}))
defer server.Close()
b := bridge{cognitiveURL: server.URL, callbackURL: server.URL, verifierURL: server.URL, client: server.Client()}
result, err := b.execute(context.Background(), work.AgentExecutionRequest{Intent: work.AgentDispatchIntent{
DispatchID: "dispatch-generic", RunID: "run-generic",
TaskEvaluationExecution: &work.TaskEvaluationDispatchContext{GraphID: "graph-1", GraphRevision: 2, NodeID: "node-1", ActorID: "actor-1", PrincipalID: "principal-1", ScopeID: "scope-1", GenericMessage: "find the bounded solution", GenericPurpose: "investigate", GenericDataPolicy: string(capability.DataPolicyLocalOnly)},
}})
if err != nil || result.State != work.AgentExecutionCompleted || callback.Candidate.Summary != "candidate answer" || callback.Candidate.AuthorRunID != "run-generic" || len(callback.Candidate.ArtifactRefs) != 2 {
t.Fatalf("result=%+v callback=%+v err=%v", result, callback, err)
}
}
+8 -8
View File
@@ -7,26 +7,26 @@ import (
"git.cin.su/m/Assistant/contracts/work"
)
// localFallbackExecutor keeps the durable work path alive when no external
// executor is reachable. It never claims that an external action happened.
// The work owner records the WAITING callback and can retry after a bridge is
// localExecutor provides the bounded local execution capability. It never
// claims that an unsupported or external action happened. The work owner
// records the WAITING callback and can retry after a suitable executor is
// configured or a human chooses the next step.
type localFallbackExecutor struct {
type localExecutor struct {
now func() time.Time
}
func newLocalFallbackExecutor() localFallbackExecutor {
return localFallbackExecutor{now: time.Now}
func newLocalExecutor() localExecutor {
return localExecutor{now: time.Now}
}
func (e localFallbackExecutor) Execute(_ context.Context, request work.AgentExecutionRequest) (work.AgentExecutionResult, error) {
func (e localExecutor) Execute(_ context.Context, request work.AgentExecutionRequest) (work.AgentExecutionResult, error) {
now := time.Now()
if e.now != nil {
now = e.now()
}
return work.AgentExecutionResult{
State: work.AgentExecutionWaiting,
Summary: "Внешний Agent Execution Bridge недоступен; локальный fallback сохранил durable работу и ожидает исполнителя.",
Summary: "Локальный исполнитель не поддерживает эту операцию; durable работа сохранена и ожидает подходящего исполнителя.",
ObservedAtUnixMillis: now.UnixMilli(),
}, nil
}
+3 -3
View File
@@ -8,9 +8,9 @@ import (
"git.cin.su/m/Assistant/contracts/work"
)
func TestLocalFallbackNeverClaimsExternalExecution(t *testing.T) {
func TestLocalExecutorNeverClaimsUnsupportedExecution(t *testing.T) {
when := time.UnixMilli(1_700_000_000_000)
executor := localFallbackExecutor{now: func() time.Time { return when }}
executor := localExecutor{now: func() time.Time { return when }}
result, err := executor.Execute(context.Background(), work.AgentExecutionRequest{Intent: work.AgentDispatchIntent{DispatchID: "dispatch-1"}})
if err != nil {
t.Fatal(err)
@@ -19,6 +19,6 @@ func TestLocalFallbackNeverClaimsExternalExecution(t *testing.T) {
t.Fatalf("unexpected fallback result: %+v", result)
}
if result.OutcomeRef != "" || result.Summary == "" {
t.Fatalf("fallback must not claim an outcome: %+v", result)
t.Fatalf("local executor must not claim an outcome: %+v", result)
}
}
+28 -4
View File
@@ -42,26 +42,29 @@ func main() {
defer repository.Close()
var executor ports.AgentExecutor
localFallback := false
if bridgeURL, bridgeConfigured := loadProtectedBridgeURL(); bridgeConfigured {
if bridgeURL, bridgeConfigured := loadConfiguredBridgeURL(); bridgeConfigured {
executor, err = agentbridge.NewHTTPExecutor(bridgeURL, &http.Client{Timeout: *leaseTTL})
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
} else {
// The local evaluator keeps task-qualification work alive, but it is not
// an external Agent Execution Bridge and must never be reported as one.
// The local executor keeps supported task-qualification work alive, but
// it is not an external Agent Execution Bridge and must never be reported
// as one. Unsupported work remains durably WAITING.
localFallback = true
executor = newTaskEvaluationExecutor(envOr("COGNITIVE_WORKER_URL", "http://cognitive-worker:8083"), envOr("WORK_GRAPH_RUNTIME_URL", "http://work-graph-runtime:8088"), envOr("TASK_VERIFIER_URL", "http://task-verifier:8091"), &http.Client{Timeout: *leaseTTL})
fmt.Fprintln(os.Stderr, "protected agent execution bridge URL is not configured; task evaluations use the local durable evaluator, other work remains in fallback")
}
runtimeStatus := newAgentWorkerRuntimeStatus()
go func() {
if serveErr := http.ListenAndServe(*statusAddr, workerStatusHandler(executor != nil && !localFallback, localFallback)); serveErr != nil {
if serveErr := http.ListenAndServe(*statusAddr, workerStatusHandler(executor != nil && !localFallback, localFallback, runtimeStatus)); serveErr != nil {
fmt.Fprintln(os.Stderr, "agent worker status server:", serveErr)
}
}()
ctx := context.Background()
for {
runtimeStatus.cycleStarted(time.Now())
var result application.WorkerCycleResult
var cycleErr error
if executor == nil {
@@ -70,11 +73,19 @@ func main() {
result, cycleErr = application.ExecuteWorkerCycleWithExecutor(ctx, repository, executor, *workerID, work.AgentFencingToken(1), time.Now().UTC(), *staleAfter, *leaseTTL)
}
if cycleErr != nil {
runtimeStatus.cycleFailed(time.Now(), cycleErr)
fmt.Fprintln(os.Stderr, cycleErr)
if *once {
os.Exit(1)
}
}
cycleResult := "idle"
if result.Completed {
cycleResult = "completed"
} else if result.RecoveredClaims > 0 {
cycleResult = "recovered"
}
runtimeStatus.cycleFinished(time.Now(), result.RecoveredClaims, result.DispatchID, cycleResult)
if cycleErr == nil && (result.Completed || result.RecoveredClaims > 0) {
_ = json.NewEncoder(os.Stdout).Encode(result)
}
@@ -88,6 +99,19 @@ func main() {
}
}
func loadConfiguredBridgeURL() (string, bool) {
if bridgeURL, configured := loadProtectedBridgeURL(); configured {
return bridgeURL, true
}
// The URL is routing configuration, not a credential. A protected file
// remains preferred when an independently operated bridge is supplied.
value := strings.TrimSpace(os.Getenv("AGENT_EXECUTION_BRIDGE_URL"))
if value == "" || !strings.HasPrefix(value, "http://") && !strings.HasPrefix(value, "https://") {
return "", false
}
return value, true
}
func loadProtectedBridgeURL() (string, bool) {
return loadProtectedBridgeURLFrom("/run/secrets", "/run/secrets/agent_execution_bridge_url")
}
+98 -16
View File
@@ -3,35 +3,106 @@ package main
import (
"encoding/json"
"net/http"
"sync"
"time"
)
type workerStatusResponse struct {
Runtime string `json:"runtime"`
ExecutorConfigured bool `json:"executor_configured"`
Mode string `json:"mode"`
Ready bool `json:"ready"`
Reason string `json:"reason"`
LocalFallback bool `json:"local_fallback"`
Runtime string `json:"runtime"`
ExecutorConfigured bool `json:"executor_configured"`
Mode string `json:"mode"`
Ready bool `json:"ready"`
Reason string `json:"reason"`
LocalFallback bool `json:"local_fallback"`
LocalExecutor bool `json:"local_executor"`
Capabilities []string `json:"capabilities,omitempty"`
StartedAt time.Time `json:"started_at,omitempty"`
LastCycleAt time.Time `json:"last_cycle_at,omitempty"`
LastCompletedAt time.Time `json:"last_completed_at,omitempty"`
LastRecoveredClaims int64 `json:"last_recovered_claims"`
TotalRecoveredClaims int64 `json:"total_recovered_claims"`
LastDispatchID string `json:"last_dispatch_id,omitempty"`
LastResult string `json:"last_result,omitempty"`
LastError string `json:"last_error,omitempty"`
}
func workerStatusHandler(executorConfigured bool, localFallback ...bool) http.Handler {
local := len(localFallback) > 0 && localFallback[0]
status := workerStatusResponse{
type agentWorkerRuntimeStatus struct {
mu sync.RWMutex
startedAt time.Time
lastCycleAt time.Time
lastCompletedAt time.Time
lastRecoveredClaims int64
totalRecoveredClaims int64
lastDispatchID string
lastResult string
lastError string
}
func newAgentWorkerRuntimeStatus() *agentWorkerRuntimeStatus {
return &agentWorkerRuntimeStatus{startedAt: time.Now().UTC()}
}
func (s *agentWorkerRuntimeStatus) cycleStarted(at time.Time) {
s.mu.Lock()
defer s.mu.Unlock()
s.lastCycleAt = at.UTC()
s.lastResult = "running"
s.lastError = ""
}
func (s *agentWorkerRuntimeStatus) cycleFinished(at time.Time, recoveredClaims int64, dispatchID string, result string) {
s.mu.Lock()
defer s.mu.Unlock()
s.lastCompletedAt = at.UTC()
s.lastRecoveredClaims = recoveredClaims
s.totalRecoveredClaims += recoveredClaims
s.lastDispatchID = dispatchID
s.lastResult = result
s.lastError = ""
}
func (s *agentWorkerRuntimeStatus) cycleFailed(at time.Time, err error) {
s.mu.Lock()
defer s.mu.Unlock()
s.lastCompletedAt = at.UTC()
s.lastResult = "failed"
s.lastError = err.Error()
}
func workerStatusHandler(executorConfigured bool, localFallback ...interface{}) http.Handler {
local := false
var runtime *agentWorkerRuntimeStatus
for _, argument := range localFallback {
switch value := argument.(type) {
case bool:
local = value
case *agentWorkerRuntimeStatus:
runtime = value
}
}
if runtime == nil {
runtime = newAgentWorkerRuntimeStatus()
}
baseStatus := workerStatusResponse{
Runtime: "agent-worker",
ExecutorConfigured: executorConfigured,
Mode: "recovery-only",
Ready: false,
Reason: "protected agent execution bridge URL is not configured",
LocalFallback: local,
LocalExecutor: local,
}
if local {
status.Mode = "local-fallback"
status.Reason = "external bridge unavailable; durable work is retained without external mutation"
baseStatus.Mode = "local-executor"
baseStatus.Ready = true
baseStatus.Reason = "bounded local task-evaluation capability is ready; unsupported work remains durably waiting"
baseStatus.Capabilities = []string{"task-evaluation"}
}
if executorConfigured {
status.Mode = "execution"
status.Ready = true
status.Reason = "typed agent execution bridge configured"
baseStatus.Mode = "execution"
baseStatus.Ready = true
baseStatus.Reason = "typed agent execution bridge configured"
baseStatus.Capabilities = []string{"task-evaluation", "generic-cognitive-work"}
}
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet || (r.URL.Path != "/healthz" && r.URL.Path != "/readyz" && r.URL.Path != "/status") {
@@ -39,9 +110,20 @@ func workerStatusHandler(executorConfigured bool, localFallback ...bool) http.Ha
return
}
w.Header().Set("Content-Type", "application/json")
if r.URL.Path == "/readyz" && !status.Ready {
if r.URL.Path == "/readyz" && !baseStatus.Ready {
w.WriteHeader(http.StatusServiceUnavailable)
}
_ = json.NewEncoder(w).Encode(status)
response := baseStatus
runtime.mu.RLock()
response.StartedAt = runtime.startedAt
response.LastCycleAt = runtime.lastCycleAt
response.LastCompletedAt = runtime.lastCompletedAt
response.LastRecoveredClaims = runtime.lastRecoveredClaims
response.TotalRecoveredClaims = runtime.totalRecoveredClaims
response.LastDispatchID = runtime.lastDispatchID
response.LastResult = runtime.lastResult
response.LastError = runtime.lastError
runtime.mu.RUnlock()
_ = json.NewEncoder(w).Encode(response)
})
}
+42 -4
View File
@@ -7,6 +7,7 @@ import (
"os"
"path/filepath"
"testing"
"time"
)
func TestLoadProtectedBridgeURL(t *testing.T) {
@@ -38,16 +39,19 @@ func TestWorkerStatusReportsRecoveryOnlyWithoutExecutor(t *testing.T) {
}
}
func TestWorkerStatusReportsLocalFallback(t *testing.T) {
func TestWorkerStatusReportsLocalExecutorCapabilityReady(t *testing.T) {
response := httptest.NewRecorder()
workerStatusHandler(false, true).ServeHTTP(response, httptest.NewRequest(http.MethodGet, "/status", nil))
var value workerStatusResponse
if err := json.NewDecoder(response.Body).Decode(&value); err != nil {
t.Fatal(err)
}
if response.Code != http.StatusOK || value.Ready || value.Mode != "local-fallback" || !value.LocalFallback {
if response.Code != http.StatusOK || !value.Ready || value.Mode != "local-executor" || !value.LocalFallback || !value.LocalExecutor {
t.Fatalf("unexpected status: code=%d value=%+v", response.Code, value)
}
if len(value.Capabilities) != 1 || value.Capabilities[0] != "task-evaluation" {
t.Fatalf("unexpected local capabilities: %+v", value.Capabilities)
}
}
func TestWorkerStatusDoesNotCallLocalFallbackAnExternalExecutor(t *testing.T) {
@@ -57,20 +61,54 @@ func TestWorkerStatusDoesNotCallLocalFallbackAnExternalExecutor(t *testing.T) {
if err := json.NewDecoder(response.Body).Decode(&value); err != nil {
t.Fatal(err)
}
if value.ExecutorConfigured || value.Ready || !value.LocalFallback || value.Mode != "local-fallback" {
if value.ExecutorConfigured || !value.Ready || !value.LocalFallback || !value.LocalExecutor || value.Mode != "local-executor" {
t.Fatalf("local fallback was presented as external execution: %+v", value)
}
}
func TestWorkerReadyzRequiresExecutor(t *testing.T) {
func TestWorkerReadyzRequiresAnAdvertisedCapability(t *testing.T) {
response := httptest.NewRecorder()
workerStatusHandler(false).ServeHTTP(response, httptest.NewRequest(http.MethodGet, "/readyz", nil))
if response.Code != http.StatusServiceUnavailable {
t.Fatalf("readyz status=%d", response.Code)
}
response = httptest.NewRecorder()
workerStatusHandler(false, true).ServeHTTP(response, httptest.NewRequest(http.MethodGet, "/readyz", nil))
if response.Code != http.StatusOK {
t.Fatalf("local capability readyz status=%d", response.Code)
}
response = httptest.NewRecorder()
workerStatusHandler(true).ServeHTTP(response, httptest.NewRequest(http.MethodGet, "/readyz", nil))
if response.Code != http.StatusOK {
t.Fatalf("configured readyz status=%d", response.Code)
}
}
func TestWorkerStatusReportsRecoveryCycle(t *testing.T) {
runtime := newAgentWorkerRuntimeStatus()
runtime.cycleStarted(time.Date(2026, 8, 23, 10, 0, 0, 0, time.UTC))
runtime.cycleFinished(time.Date(2026, 8, 23, 10, 0, 1, 0, time.UTC), 3, "dispatch-1", "recovered")
response := httptest.NewRecorder()
workerStatusHandler(false, true, runtime).ServeHTTP(response, httptest.NewRequest(http.MethodGet, "/status", nil))
var value workerStatusResponse
if err := json.NewDecoder(response.Body).Decode(&value); err != nil {
t.Fatal(err)
}
if value.LastRecoveredClaims != 3 || value.TotalRecoveredClaims != 3 || value.LastDispatchID != "dispatch-1" || value.LastResult != "recovered" {
t.Fatalf("recovery evidence missing: %+v", value)
}
}
func TestWorkerStatusReportsCycleError(t *testing.T) {
runtime := newAgentWorkerRuntimeStatus()
runtime.cycleFailed(time.Date(2026, 8, 23, 10, 0, 1, 0, time.UTC), os.ErrClosed)
response := httptest.NewRecorder()
workerStatusHandler(false, runtime).ServeHTTP(response, httptest.NewRequest(http.MethodGet, "/status", nil))
var value workerStatusResponse
if err := json.NewDecoder(response.Body).Decode(&value); err != nil {
t.Fatal(err)
}
if value.LastResult != "failed" || value.LastError == "" {
t.Fatalf("cycle error missing: %+v", value)
}
}
+22 -2
View File
@@ -20,14 +20,14 @@ type taskEvaluationExecutor struct {
callbackURL string
verifierURL string
client *http.Client
fallback localFallbackExecutor
fallback localExecutor
}
func newTaskEvaluationExecutor(cognitiveURL, callbackURL, verifierURL string, client *http.Client) taskEvaluationExecutor {
if client == nil {
client = http.DefaultClient
}
return taskEvaluationExecutor{cognitiveURL: strings.TrimRight(cognitiveURL, "/"), callbackURL: strings.TrimRight(callbackURL, "/"), verifierURL: strings.TrimRight(verifierURL, "/"), client: client, fallback: newLocalFallbackExecutor()}
return taskEvaluationExecutor{cognitiveURL: strings.TrimRight(cognitiveURL, "/"), callbackURL: strings.TrimRight(callbackURL, "/"), verifierURL: strings.TrimRight(verifierURL, "/"), client: client, fallback: newLocalExecutor()}
}
func (e taskEvaluationExecutor) Execute(ctx context.Context, request work.AgentExecutionRequest) (work.AgentExecutionResult, error) {
@@ -50,6 +50,17 @@ func (e taskEvaluationExecutor) Execute(ctx context.Context, request work.AgentE
if len(response.Responses) == 0 || response.ObservedAtUnixMillis < 1 {
return work.AgentExecutionResult{}, fmt.Errorf("task evaluation returned no observations")
}
if len(response.Observations) == len(intent.TaskEvaluationCases) && taskEvaluationHasCriticalFailure(response.Observations) {
evaluation, err := capabilitydomain.BuildTaskEvaluation(*intent.TaskEvaluationProfile, capability.TaskEvaluationID("evaluation:"+intent.DispatchID), response.BindingID, response.BindingRevision, intent.TaskEvaluationProfile.CapabilityID, response.Observations, artifact.ArtifactID(intent.TaskEvaluationCases[0].EvidenceArtifactRef), response.ObservedAtUnixMillis)
if err != nil {
return work.AgentExecutionResult{}, err
}
callback := work.TaskEvaluationEvidenceCallback{GraphID: execution.GraphID, Revision: execution.GraphRevision, NodeID: execution.NodeID, Evaluation: evaluation, Succeeded: true}
if _, err := postJSON[json.RawMessage](ctx, e.client, e.callbackURL+"/v1/work-graphs/task-evaluations/callback", callback); err != nil {
return work.AgentExecutionResult{}, err
}
return work.AgentExecutionResult{State: work.AgentExecutionCompleted, Summary: "task evaluation recorded as UNKNOWN after bounded provider recovery", OutcomeRef: string(evaluation.ID), ArtifactRefs: []string{string(evaluation.ArtifactID)}, ObservedAtUnixMillis: time.Now().UnixMilli()}, nil
}
if len(intent.TaskEvaluationCases) == 0 {
return work.AgentExecutionResult{}, fmt.Errorf("task evaluation cases are missing")
}
@@ -80,6 +91,15 @@ func (e taskEvaluationExecutor) Execute(ctx context.Context, request work.AgentE
return work.AgentExecutionResult{State: work.AgentExecutionCompleted, Summary: "task evaluation executed, aggregated and accepted by Work Graph callback", OutcomeRef: string(evaluation.ID), ArtifactRefs: []string{string(evaluation.ArtifactID)}, ObservedAtUnixMillis: time.Now().UnixMilli()}, nil
}
func taskEvaluationHasCriticalFailure(observations []capability.TaskCaseObservation) bool {
for _, observation := range observations {
if observation.CriticalFailure {
return true
}
}
return false
}
func postJSON[T any](ctx context.Context, client *http.Client, url string, payload any) (T, error) {
var result T
body, err := json.Marshal(payload)
@@ -41,3 +41,32 @@ func TestTaskEvaluationExecutorInvokesAggregatesAndCallbacks(t *testing.T) {
t.Fatalf("result=%+v err=%v", result, err)
}
}
func TestTaskEvaluationExecutorPersistsUnknownAfterProviderFailure(t *testing.T) {
profile := capability.TaskEvaluationProfile{ID: "profile-unknown", Revision: 1, CapabilityID: "capability-1", Stage: capability.TaskEvaluationGeneral, BenchmarkVersion: "unknown-v1", RequiredCaseIDs: []string{"case-1"}, QualityWeightMillis: 600, ReliabilityWeightMillis: 300, LatencyWeightMillis: 100}
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/v1/task-evaluations/execute" {
_ = json.NewEncoder(w).Encode(capability.TaskEvaluationExecutionResponse{ProfileID: profile.ID, BindingID: "binding-1", BindingRevision: 3, Observations: []capability.TaskCaseObservation{{CaseID: "case-1", Result: capability.EvaluationUnknown, CriticalFailure: true, EvidenceArtifactRef: "artifact-1"}}, Responses: []capability.TaskCaseResponse{{CaseID: "case-1", EvidenceArtifactRef: "artifact-1"}}, ObservedAtUnixMillis: 10})
return
}
if r.URL.Path == "/v1/work-graphs/task-evaluations/callback" {
var callback work.TaskEvaluationEvidenceCallback
if err := json.NewDecoder(r.Body).Decode(&callback); err != nil || callback.Evaluation.Result != capability.EvaluationUnknown || !callback.Succeeded {
http.Error(w, "invalid unknown callback", http.StatusBadRequest)
return
}
w.WriteHeader(http.StatusNoContent)
return
}
if r.URL.Path == "/v1/task-evaluations/verify" {
t.Errorf("verifier must not be called for a provider failure")
}
http.NotFound(w, r)
}))
defer server.Close()
executor := newTaskEvaluationExecutor(server.URL, server.URL, server.URL, server.Client())
result, err := executor.Execute(context.Background(), work.AgentExecutionRequest{Intent: work.AgentDispatchIntent{DispatchID: "dispatch-unknown", TaskEvaluationBindingID: "binding-1", TaskEvaluationProfile: &profile, TaskEvaluationCases: []capability.TaskEvaluationCase{{CaseID: "case-1", Prompt: "answer", EvidenceArtifactRef: "artifact-1"}}, TaskEvaluationExecution: &work.TaskEvaluationDispatchContext{GraphID: "graph-1", GraphRevision: 1, NodeID: "node-1", BindingRevision: 3, ActorID: "actor-1", PrincipalID: "principal-1", ScopeID: "scope-1"}}})
if err != nil || result.State != work.AgentExecutionCompleted || result.OutcomeRef == "" {
t.Fatalf("result=%+v err=%v", result, err)
}
}
+44
View File
@@ -3,6 +3,7 @@ package main
import (
"encoding/json"
"net/http"
"sort"
"time"
"git.cin.su/m/Assistant/contracts/capability"
@@ -17,6 +18,10 @@ type discoveryService struct {
}
func (s discoveryService) handler(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodGet && r.URL.Path == "/v1/provider-discovery/status" {
s.statusHandler(w, r)
return
}
registration, ok := s.providers[r.PathValue("provider")]
if !ok || registration.Discovery == nil {
http.Error(w, "provider discovery not found", http.StatusNotFound)
@@ -35,3 +40,42 @@ func (s discoveryService) handler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(result)
}
type discoveryStatus struct {
Provider string `json:"provider"`
ProviderKind capability.ProviderKind `json:"provider_kind"`
Health capability.HealthState `json:"health"`
FailureClass capability.CognitiveFailureType `json:"failure_class,omitempty"`
ObservedAtUnixMillis int64 `json:"observed_at_unix_millis"`
Models []discoveredModelStatus `json:"models"`
}
type discoveredModelStatus struct {
ProviderModelID capability.ProviderModelID `json:"provider_model_id"`
Fingerprint capability.ModelFingerprint `json:"fingerprint,omitempty"`
Capabilities []capability.ModelCapability `json:"capabilities,omitempty"`
}
func (s discoveryService) statusHandler(w http.ResponseWriter, r *http.Request) {
providers := make([]string, 0, len(s.providers))
for name := range s.providers {
providers = append(providers, name)
}
sort.Strings(providers)
result := make([]discoveryStatus, 0, len(providers))
for _, name := range providers {
registration := s.providers[name]
status := discoveryStatus{Provider: name, ProviderKind: registration.Kind, Health: capability.HealthUnknown, ObservedAtUnixMillis: s.now().UnixMilli(), Models: []discoveredModelStatus{}}
if registration.Discovery != nil {
models, probe := registration.Discovery.Discover(r.Context())
status.Health = probe.Health
status.FailureClass = capability.CognitiveFailureType(probe.Failure)
for _, model := range models {
status.Models = append(status.Models, discoveredModelStatus{ProviderModelID: model.ProviderModelID, Fingerprint: model.Fingerprint, Capabilities: model.Capabilities})
}
}
result = append(result, status)
}
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(result)
}
+52
View File
@@ -0,0 +1,52 @@
package main
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
"git.cin.su/m/Assistant/adapters/models"
"git.cin.su/m/Assistant/contracts/capability"
"git.cin.su/m/Assistant/contracts/cognitive"
"git.cin.su/m/Assistant/contracts/identity"
)
type discoveryTestProvider struct{}
func (discoveryTestProvider) Probe(context.Context) models.ProviderProbe {
return models.ProviderProbe{Health: capability.HealthAvailable}
}
func (discoveryTestProvider) Discover(context.Context) ([]models.DiscoveredModel, models.ProviderProbe) {
return []models.DiscoveredModel{{ProviderModelID: "local-model", Fingerprint: "fingerprint-1", Capabilities: []capability.ModelCapability{capability.CapabilityAdvisory}}}, models.ProviderProbe{Health: capability.HealthAvailable}
}
func (discoveryTestProvider) Advise(context.Context, cognitive.AdvisoryRequest) (cognitive.AdvisoryResponse, error) {
return cognitive.AdvisoryResponse{}, nil
}
func TestDiscoveryStatusIsReadOnlyAndDoesNotExposeCredentialMetadata(t *testing.T) {
service := discoveryService{
providers: map[string]providerRegistration{"cpu": {ProviderInstanceID: "provider-1", Kind: "openai-compatible-chat-v1", CredentialRef: "credential://must-not-be-returned", Discovery: discoveryTestProvider{}}},
owner: identity.PrincipalID("owner-1"),
now: func() time.Time { return time.UnixMilli(1234) },
}
response := httptest.NewRecorder()
service.handler(response, httptest.NewRequest(http.MethodGet, "/v1/provider-discovery/status", nil))
if response.Code != http.StatusOK {
t.Fatalf("status=%d", response.Code)
}
body := response.Body.String()
var result []discoveryStatus
if err := json.NewDecoder(strings.NewReader(body)).Decode(&result); err != nil {
t.Fatal(err)
}
if len(result) != 1 || result[0].Provider != "cpu" || result[0].Health != capability.HealthAvailable || len(result[0].Models) != 1 || result[0].ObservedAtUnixMillis != 1234 {
t.Fatalf("unexpected discovery status: %+v", result)
}
if strings.Contains(body, "must-not-be-returned") || strings.Contains(body, "credential://") {
t.Fatalf("credential metadata leaked in discovery status: %s", body)
}
}
+10 -1
View File
@@ -298,12 +298,21 @@ func fallbackEligible(reason cognitive.FinishReason) bool {
}
func (g cognitiveGateway) provider(selected capability.ResolvedCognitiveBinding) advisoryProvider {
var byStableID advisoryProvider
for _, registration := range g.providers[selected.Provider.Kind] {
if registration.EndpointRef == selected.Provider.EndpointRef && registration.CredentialRef == selected.Provider.CredentialRef {
return registration.Provider
}
// Capability revisions can legitimately retain an older credential
// reference while the protected runtime has rotated or reloaded the
// current credential. ProviderInstanceID is the stable owner identity;
// use it after exact metadata matching so stale non-secret metadata does
// not force an unnecessary builtin fallback.
if byStableID == nil && registration.ProviderInstanceID == selected.Provider.ID && registration.Kind == selected.Provider.Kind {
byStableID = registration.Provider
}
}
return nil
return byStableID
}
func writeGatewayResponse(w http.ResponseWriter, response cognitive.GatewayAdvisoryResponse) {
@@ -88,6 +88,19 @@ func TestActiveRegistryBindingReachesProviderAdapter(t *testing.T) {
}
}
func TestProviderUsesStableInstanceWhenCredentialMetadataIsStale(t *testing.T) {
provider := &stubAdvisoryProvider{}
gateway := cognitiveGateway{providers: map[capability.ProviderKind][]providerRegistration{
"provider-kind-1": {{ProviderInstanceID: "provider-1", Kind: "provider-kind-1", EndpointRef: "current-endpoint", CredentialRef: "current-credential", Provider: provider}},
}}
selected := gatewaySelection()
selected.Provider.EndpointRef = "old-endpoint"
selected.Provider.CredentialRef = ""
if got := gateway.provider(selected); got != provider {
t.Fatalf("stable provider identity was not used for stale metadata")
}
}
func TestCanaryFlagIsForwardedToCapabilityResolver(t *testing.T) {
registry := &registryStub{err: errors.New("stop after resolve")}
gateway := cognitiveGateway{resolver: registry, providers: map[capability.ProviderKind][]providerRegistration{}, now: time.Now, allowCanary: true}
+2 -2
View File
@@ -163,8 +163,8 @@ func interpretationPrompt(request cognitive.GatewayInterpretationRequest) string
Allowed speech acts: DIRECTIVE, ASSERTION, DECISION, PREFERENCE, QUESTION, HYPOTHESIS, CORRECTION, REQUEST_TO_INVESTIGATE, CONFIRMATION, REJECTION, CANCELLATION, CONVERSATIONAL, UNKNOWN.
Allowed applicability: APPLICABLE, LOW_CONFIDENCE, MIXED_INTENT, NEEDS_BROADER_INTERPRETATION, CLARIFICATION_REQUIRED.
Allowed clarification: NOT_REQUIRED or REQUIRED.
Capability hints may only contain known capability kinds like COGNITIVE_REASONING, MODIFY_CODE, RUN_TESTS, SEND_EMAIL, SCHEDULE_REMINDER, VERIFY_FACT, INSPECT_PROJECT, READ_DATABASE, QUERY_LOGS.
Interpret the human utterance semantically. Do not claim action execution. Do not treat punctuation alone as decisive. If uncertain, set applicability accordingly.
Capability hints may only contain known capability kinds like COGNITIVE_REASONING, WEB_RESEARCH, MODIFY_CODE, RUN_TESTS, SEND_EMAIL, SCHEDULE_REMINDER, VERIFY_FACT, INSPECT_PROJECT, READ_DATABASE, QUERY_LOGS.
Interpret the human utterance semantically. Do not claim action execution. A question asking for a current, external or otherwise source-dependent fact should receive WEB_RESEARCH; do not answer that fact from model memory. A request to discuss hypotheses or options is different and may receive COGNITIVE_REASONING. Do not treat punctuation alone as decisive. If uncertain, set applicability accordingly.
Message: %q
Deterministic primary: %s
Deterministic secondary: %s
+4 -1
View File
@@ -97,7 +97,8 @@ func main() {
if gpuErr != nil {
log.Printf("optional GPU provider disabled: %v", gpuErr)
} else if gpuConfigured {
gpuAdapter, adapterErr := models.NewOpenAICompatibleLocalAdapterWithAPIKey(gpuURL, gpuKey, nil)
gpuTimeout := durationEnv("LOCAL_GPU_PROVIDER_TIMEOUT", 60*time.Second)
gpuAdapter, adapterErr := models.NewOpenAICompatibleLocalAdapterWithAPIKeyAndTimeout(gpuURL, gpuKey, nil, gpuTimeout)
if adapterErr != nil {
log.Printf("optional GPU provider disabled: %v", adapterErr)
} else {
@@ -169,6 +170,8 @@ func main() {
mux.HandleFunc("POST /v1/task-evaluations/execute", gateway.taskEvaluationHandler)
discovery := discoveryService{resolver: resolver, providers: discoveries, owner: identity.PrincipalID(envOr("COGNITIVE_OWNER_PRINCIPAL_ID", "domovoy-creator-principal")), now: time.Now}
mux.HandleFunc("POST /v1/provider-discovery/{provider}", discovery.handler)
mux.Handle("POST /v1/provider-discovery/{provider}/qualify", modelQualificationHandler(discoveries, string(envOr("COGNITIVE_OWNER_PRINCIPAL_ID", "domovoy-creator-principal")), httpQualificationObserver{resolver: resolver}))
mux.HandleFunc("GET /v1/provider-discovery/status", discovery.statusHandler)
refreshProviderDiscoveries(resolver, discovery)
log.Printf("cognitive-worker listening on %s", addr)
if err := http.ListenAndServe(addr, mux); err != nil {
+16
View File
@@ -4,6 +4,7 @@ import (
"os"
"path/filepath"
"testing"
"time"
)
func TestLifeboatProviderIsOptionalAndLocalAdapterBacked(t *testing.T) {
@@ -19,6 +20,21 @@ func TestLifeboatProviderIsOptionalAndLocalAdapterBacked(t *testing.T) {
}
}
func TestGPUProviderTimeoutUsesBoundedConfigurableDefault(t *testing.T) {
t.Setenv("LOCAL_GPU_PROVIDER_TIMEOUT", "")
if got := durationEnv("LOCAL_GPU_PROVIDER_TIMEOUT", 60*time.Second); got != 60*time.Second {
t.Fatalf("default GPU timeout=%s", got)
}
t.Setenv("LOCAL_GPU_PROVIDER_TIMEOUT", "31s")
if got := durationEnv("LOCAL_GPU_PROVIDER_TIMEOUT", 60*time.Second); got != 31*time.Second {
t.Fatalf("configured GPU timeout=%s", got)
}
t.Setenv("LOCAL_GPU_PROVIDER_TIMEOUT", "invalid")
if got := durationEnv("LOCAL_GPU_PROVIDER_TIMEOUT", 60*time.Second); got != 60*time.Second {
t.Fatalf("invalid GPU timeout=%s", got)
}
}
func TestLoadLocalCPUProviderAPIKeyUsesProtectedFileReference(t *testing.T) {
t.Setenv("LOCAL_CPU_PROVIDER_API_KEY", "")
_ = os.Unsetenv("LOCAL_CPU_PROVIDER_API_KEY")
+167
View File
@@ -0,0 +1,167 @@
package main
import (
"context"
"encoding/json"
"fmt"
"net/http"
"slices"
"sync"
"time"
"git.cin.su/m/Assistant/adapters/models"
"git.cin.su/m/Assistant/contracts/authority"
"git.cin.su/m/Assistant/contracts/capability"
"git.cin.su/m/Assistant/contracts/cognitive"
"git.cin.su/m/Assistant/contracts/identity"
"git.cin.su/m/Assistant/platform/ids"
)
type qualificationObserver interface {
ObserveQualification(context.Context, capability.ModelQualificationObservation) error
}
type httpQualificationObserver struct{ resolver *httpBindingResolver }
func (o httpQualificationObserver) ObserveQualification(ctx context.Context, value capability.ModelQualificationObservation) error {
if o.resolver == nil {
return fmt.Errorf("qualification observer resolver is nil")
}
return o.resolver.post(ctx, "/v1/cognitive-registry/model-qualification-observations", value, nil)
}
type modelQualificationRequest struct {
Prompt string `json:"prompt"`
MaxOutputBytes int `json:"max_output_bytes"`
ModelIDs []capability.ProviderModelID `json:"model_ids,omitempty"`
}
type modelQualificationResult struct {
ProviderModelID capability.ProviderModelID `json:"provider_model_id"`
Status string `json:"status"`
FinishReason cognitive.FinishReason `json:"finish_reason,omitempty"`
LatencyMillis int64 `json:"latency_millis"`
OutputObserved bool `json:"output_observed"`
QualityAssessed bool `json:"quality_assessed"`
Category capability.ModelRatingCategory `json:"category"`
Error string `json:"error,omitempty"`
}
type modelQualificationResponse struct {
Provider capability.ProviderInstanceID `json:"provider"`
ObservedAtUnixMillis int64 `json:"observed_at_unix_millis"`
Results []modelQualificationResult `json:"results"`
}
func modelQualificationHandler(providers map[string]providerRegistration, owner string, observers ...qualificationObserver) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
name := r.PathValue("provider")
registration, ok := providers[name]
if !ok || registration.Discovery == nil || registration.Provider == nil {
http.Error(w, "provider qualification not found", http.StatusNotFound)
return
}
var request modelQualificationRequest
if !decodeJSON(w, r, 16<<10, &request) || request.Prompt == "" {
return
}
if request.MaxOutputBytes < 1 {
request.MaxOutputBytes = 512
}
if request.MaxOutputBytes > 4096 {
http.Error(w, "max_output_bytes exceeds qualification bound", http.StatusBadRequest)
return
}
modelsFound, probe := registration.Discovery.Discover(r.Context())
if probe.Health != capability.HealthAvailable || len(modelsFound) == 0 {
http.Error(w, "provider unavailable for qualification", http.StatusServiceUnavailable)
return
}
if len(request.ModelIDs) > 0 {
modelsFound = slices.DeleteFunc(modelsFound, func(model models.DiscoveredModel) bool {
return !slices.Contains(request.ModelIDs, model.ProviderModelID)
})
}
if len(modelsFound) == 0 {
http.Error(w, "requested model not discovered", http.StatusNotFound)
return
}
if len(modelsFound) > 16 {
modelsFound = modelsFound[:16]
}
results := qualifyModels(r.Context(), registration, owner, request.Prompt, request.MaxOutputBytes, modelsFound)
if len(observers) > 0 && observers[0] != nil {
if err := persistQualificationResults(r.Context(), observers[0], registration, results); err != nil {
http.Error(w, "persist model qualification observation", http.StatusBadGateway)
return
}
}
w.Header().Set("Content-Type", "application/json")
_ = jsonEncode(w, modelQualificationResponse{Provider: registration.ProviderInstanceID, ObservedAtUnixMillis: time.Now().UnixMilli(), Results: results})
})
}
func qualifyModels(ctx context.Context, registration providerRegistration, owner, prompt string, maxOutputBytes int, discovered []models.DiscoveredModel) []modelQualificationResult {
results := make([]modelQualificationResult, len(discovered))
var wg sync.WaitGroup
for index, model := range discovered {
wg.Add(1)
go func(index int, model models.DiscoveredModel) {
defer wg.Done()
started := time.Now()
request := cognitive.AdvisoryRequest{Version: cognitive.CreatorAdvisoryV1, RequestID: cognitive.RequestID(fmt.Sprintf("qualification:%s:%d", model.ProviderModelID, started.UnixNano())), ActorID: identity.ActorID("qualification-runtime"), PrincipalID: identity.PrincipalID(owner), ScopeID: authority.ScopeID("qualification-scope"), Message: prompt, Locale: "en-US", MaxOutputBytes: maxOutputBytes, ProviderBindingID: "qualification-binding-v1", ProviderBindingRevision: 1, ProviderInstanceID: registration.ProviderInstanceID, ModelDescriptorID: capability.ModelDescriptorID("qualification-" + string(model.ProviderModelID)), ProviderModelID: model.ProviderModelID}
response, err := registration.Provider.Advise(ctx, request)
result := modelQualificationResult{ProviderModelID: model.ProviderModelID, LatencyMillis: time.Since(started).Milliseconds(), QualityAssessed: false, Category: capability.ModelRatingUnrated}
if err != nil {
result.Status = "UNAVAILABLE"
result.FinishReason = cognitive.FinishProviderUnavailable
result.Error = err.Error()
results[index] = result
return
}
result.FinishReason = response.FinishReason
result.OutputObserved = response.AdvisoryText != ""
if response.FinishReason == cognitive.FinishCompleted && result.OutputObserved {
result.Status = "REACHABLE"
} else {
result.Status = "RESPONDED_WITHOUT_QUALIFICATION"
}
results[index] = result
}(index, model)
}
wg.Wait()
return results
}
func persistQualificationResults(ctx context.Context, observer qualificationObserver, registration providerRegistration, results []modelQualificationResult) error {
for _, result := range results {
observationID, err := ids.New()
if err != nil {
return err
}
status := capability.ModelQualificationRespondedWithoutRating
switch result.Status {
case "REACHABLE":
status = capability.ModelQualificationReachable
case "UNAVAILABLE":
status = capability.ModelQualificationUnavailable
}
observation := capability.ModelQualificationObservation{
ID: capability.ModelQualificationObservationID(observationID), ProviderInstanceID: registration.ProviderInstanceID, ProviderModelID: result.ProviderModelID,
Status: status, FinishReason: string(result.FinishReason), LatencyMillis: result.LatencyMillis,
OutputObserved: result.OutputObserved, QualityAssessed: false, Category: capability.ModelRatingUnrated,
ObservedAtUnixMillis: time.Now().UnixMilli(),
}
if err := observation.Validate(); err != nil {
return err
}
if err := observer.ObserveQualification(ctx, observation); err != nil {
return err
}
}
return nil
}
func jsonEncode(w http.ResponseWriter, value any) error {
return json.NewEncoder(w).Encode(value)
}
@@ -0,0 +1,85 @@
package main
import (
"context"
"net/http"
"net/http/httptest"
"strings"
"testing"
"git.cin.su/m/Assistant/adapters/models"
"git.cin.su/m/Assistant/contracts/capability"
"git.cin.su/m/Assistant/contracts/cognitive"
)
type qualificationProvider struct{}
func (qualificationProvider) Probe(context.Context) models.ProviderProbe {
return models.ProviderProbe{Health: capability.HealthAvailable}
}
func (qualificationProvider) Discover(context.Context) ([]models.DiscoveredModel, models.ProviderProbe) {
return []models.DiscoveredModel{{ProviderModelID: "model-a"}, {ProviderModelID: "model-b"}}, models.ProviderProbe{Health: capability.HealthAvailable}
}
func (qualificationProvider) Advise(_ context.Context, request cognitive.AdvisoryRequest) (cognitive.AdvisoryResponse, error) {
if err := models.ValidateAdvisoryRequest(request); err != nil {
return cognitive.AdvisoryResponse{}, err
}
return cognitive.AdvisoryResponse{Version: request.Version, RequestID: request.RequestID, AdvisoryText: "reachable", FinishReason: cognitive.FinishCompleted}, nil
}
func TestModelQualificationUsesDiscoveredModelsAndLeavesQualityUnrated(t *testing.T) {
provider := qualificationProvider{}
mux := http.NewServeMux()
mux.Handle("POST /v1/provider-discovery/{provider}/qualify", modelQualificationHandler(map[string]providerRegistration{"gpu": {ProviderInstanceID: "gpu", Provider: provider, Discovery: provider}}, "owner"))
server := httptest.NewServer(mux)
defer server.Close()
request := httptest.NewRequest(http.MethodPost, server.URL+"/v1/provider-discovery/gpu/qualify", strings.NewReader(`{"prompt":"ready","max_output_bytes":128}`))
request.Header.Set("Content-Type", "application/json")
response := httptest.NewRecorder()
mux.ServeHTTP(response, request)
if response.Code != http.StatusOK {
t.Fatalf("qualification status=%d body=%s", response.Code, response.Body.String())
}
if !strings.Contains(response.Body.String(), `"status":"REACHABLE"`) || !strings.Contains(response.Body.String(), `"category":"UNRATED"`) || strings.Contains(response.Body.String(), `"quality_assessed":true`) {
t.Fatalf("qualification overclaimed quality: %s", response.Body.String())
}
}
func TestModelQualificationRejectsUnboundedOutput(t *testing.T) {
provider := qualificationProvider{}
mux := http.NewServeMux()
mux.Handle("POST /v1/provider-discovery/{provider}/qualify", modelQualificationHandler(map[string]providerRegistration{"cpu": {ProviderInstanceID: "cpu", Provider: provider, Discovery: provider}}, "owner"))
request := httptest.NewRequest(http.MethodPost, "/v1/provider-discovery/cpu/qualify", strings.NewReader(`{"prompt":"ready","max_output_bytes":8192}`))
request.Header.Set("Content-Type", "application/json")
response := httptest.NewRecorder()
mux.ServeHTTP(response, request)
if response.Code != http.StatusBadRequest {
t.Fatalf("unbounded qualification status=%d", response.Code)
}
}
type qualificationObserverStub struct {
observations []capability.ModelQualificationObservation
}
func (s *qualificationObserverStub) ObserveQualification(_ context.Context, value capability.ModelQualificationObservation) error {
s.observations = append(s.observations, value)
return nil
}
func TestModelQualificationPersistsReachabilityWithoutQuality(t *testing.T) {
provider := qualificationProvider{}
observer := &qualificationObserverStub{}
mux := http.NewServeMux()
mux.Handle("POST /v1/provider-discovery/{provider}/qualify", modelQualificationHandler(map[string]providerRegistration{"cpu": {ProviderInstanceID: "cpu", Provider: provider, Discovery: provider}}, "owner", observer))
request := httptest.NewRequest(http.MethodPost, "/v1/provider-discovery/cpu/qualify", strings.NewReader(`{"prompt":"ready","max_output_bytes":128,"model_ids":["model-a"]}`))
request.Header.Set("Content-Type", "application/json")
response := httptest.NewRecorder()
mux.ServeHTTP(response, request)
if response.Code != http.StatusOK || len(observer.observations) != 1 {
t.Fatalf("qualification persistence status=%d observations=%d body=%s", response.Code, len(observer.observations), response.Body.String())
}
if err := observer.observations[0].Validate(); err != nil || observer.observations[0].Status != capability.ModelQualificationReachable {
t.Fatalf("invalid qualification observation: %+v err=%v", observer.observations[0], err)
}
}
+74 -31
View File
@@ -1,9 +1,11 @@
package main
import (
"context"
"encoding/json"
"fmt"
"net/http"
"strings"
"sync"
"time"
@@ -34,20 +36,13 @@ func (g cognitiveGateway) taskEvaluationHandler(w http.ResponseWriter, r *http.R
http.Error(w, "external egress acknowledgement required", http.StatusBadRequest)
return
}
provider := g.provider(selected)
if provider == nil {
http.Error(w, "cognitive provider unavailable", http.StatusServiceUnavailable)
return
}
caseSet := make(map[string]struct{}, len(request.Profile.RequiredCaseIDs))
seenCases := make(map[string]struct{}, len(request.Cases))
for _, id := range request.Profile.RequiredCaseIDs {
caseSet[id] = struct{}{}
}
observations := make([]capability.TaskCaseObservation, len(request.Cases))
responses := make([]capability.TaskCaseResponse, len(request.Cases))
var wg sync.WaitGroup
seenCases := make(map[string]struct{}, len(request.Cases))
for i, item := range request.Cases {
for _, item := range request.Cases {
if item.CaseID == "" || item.Prompt == "" || item.EvidenceArtifactRef == "" {
http.Error(w, "invalid task evaluation case", http.StatusBadRequest)
return
@@ -61,35 +56,83 @@ func (g cognitiveGateway) taskEvaluationHandler(w http.ResponseWriter, r *http.R
return
}
seenCases[item.CaseID] = struct{}{}
wg.Add(1)
go func(index int, item capability.TaskEvaluationCase) {
defer wg.Done()
started := time.Now()
invocation := cognitive.AdvisoryRequest{Version: cognitive.CreatorAdvisoryV1, RequestID: cognitive.RequestID(fmt.Sprintf("task-evaluation:%s:%s", request.Profile.ID, item.CaseID)), ActorID: request.ActorID, PrincipalID: request.PrincipalID, ScopeID: request.ScopeID, Message: item.Prompt, MaxOutputBytes: int(request.MaxOutputBytes), ProviderBindingID: selected.Binding.ID, ProviderBindingRevision: selected.Binding.Revision, ProviderInstanceID: selected.Provider.ID, ModelDescriptorID: selected.Model.ID, ProviderModelID: selected.Model.ProviderModelID}
response, invokeErr := g.invokeAdmitted(r.Context(), string(invocation.RequestID), provider, invocation, int(request.MaxOutputBytes))
observation := capability.TaskCaseObservation{CaseID: item.CaseID, LatencyMicros: time.Since(started).Microseconds(), EvidenceArtifactRef: item.EvidenceArtifactRef}
responses[index] = capability.TaskCaseResponse{CaseID: item.CaseID, ResponseText: response.AdvisoryText, LatencyMicros: observation.LatencyMicros, EvidenceArtifactRef: item.EvidenceArtifactRef}
if invokeErr == nil && response.FinishReason == cognitive.FinishCompleted && response.AdvisoryText != "" {
// Invocation success proves reachability only. Without an
// independent verifier and answer evidence, semantic support and
// quality must remain UNKNOWN rather than becoming false evidence.
observation.Result = capability.EvaluationUnknown
observation.QualityScoreMillis = 0
} else {
observation.Result = capability.EvaluationUnknown
observation.CriticalFailure = invokeErr != nil
}
observations[index] = observation
}(i, item)
}
if len(seenCases) != len(caseSet) {
http.Error(w, "task evaluation case set is incomplete", http.StatusBadRequest)
return
}
wg.Wait()
// One bounded fan-out wave uses one binding snapshot. If that candidate is
// unavailable, only this binding is excluded and the existing resolver
// chooses the next eligible candidate. Provider output remains untrusted
// until the independent verifier accepts it downstream.
requirement := capability.CognitiveRequirement{CapabilityID: request.CapabilityID, OwnerPrincipalID: request.PrincipalID, DataPolicy: selected.Binding.DataPolicy, RequiredModalities: []capability.ModelModality{capability.ModalityText}, RequiredCapabilities: []capability.ModelCapability{capability.CapabilityAdvisory}, AllowCanary: g.allowCanary, MaxOutputBytes: request.MaxOutputBytes}
var observations []capability.TaskCaseObservation
var responses []capability.TaskCaseResponse
for attempt := 0; attempt < 3; attempt++ {
provider := g.provider(selected)
if provider == nil {
observations, responses = taskEvaluationUnavailable(request.Cases)
} else {
observations, responses = g.executeTaskEvaluationCases(r.Context(), request, selected, provider)
}
if taskEvaluationResponsesUsable(responses) {
break
}
requirement.ExcludedBindingIDs = append(requirement.ExcludedBindingIDs, selected.Binding.ID)
fallback, resolveErr := g.resolver.Resolve(r.Context(), requirement)
fallbackProvider := g.provider(fallback)
if resolveErr != nil || fallbackProvider == nil || fallback.Binding.ID == selected.Binding.ID || (fallback.Binding.DataPolicy != capability.DataPolicyLocalOnly && !request.EgressAcknowledged) {
break
}
selected = fallback
}
writeTaskEvaluationJSON(w, capability.TaskEvaluationExecutionResponse{ProfileID: request.Profile.ID, BindingID: selected.Binding.ID, BindingRevision: selected.Binding.Revision, Observations: observations, Responses: responses, ObservedAtUnixMillis: time.Now().UnixMilli()})
}
func (g cognitiveGateway) executeTaskEvaluationCases(ctx context.Context, request capability.TaskEvaluationExecutionRequest, selected capability.ResolvedCognitiveBinding, provider advisoryProvider) ([]capability.TaskCaseObservation, []capability.TaskCaseResponse) {
observations := make([]capability.TaskCaseObservation, len(request.Cases))
responses := make([]capability.TaskCaseResponse, len(request.Cases))
var wg sync.WaitGroup
for i, item := range request.Cases {
wg.Add(1)
go func(index int, item capability.TaskEvaluationCase) {
defer wg.Done()
started := time.Now()
invocation := cognitive.AdvisoryRequest{Version: cognitive.CreatorAdvisoryV1, RequestID: cognitive.RequestID(fmt.Sprintf("task-evaluation:%s:%s", request.Profile.ID, item.CaseID)), ActorID: request.ActorID, PrincipalID: request.PrincipalID, ScopeID: request.ScopeID, Message: item.Prompt, MaxOutputBytes: int(request.MaxOutputBytes), ProviderBindingID: selected.Binding.ID, ProviderBindingRevision: selected.Binding.Revision, ProviderInstanceID: selected.Provider.ID, ModelDescriptorID: selected.Model.ID, ProviderModelID: selected.Model.ProviderModelID}
response, invokeErr := g.invokeAdmitted(ctx, string(invocation.RequestID), provider, invocation, int(request.MaxOutputBytes))
latency := time.Since(started).Microseconds()
observations[index] = capability.TaskCaseObservation{CaseID: item.CaseID, Result: capability.EvaluationUnknown, CriticalFailure: invokeErr != nil || response.FinishReason != cognitive.FinishCompleted || strings.TrimSpace(response.AdvisoryText) == "", EvidenceArtifactRef: item.EvidenceArtifactRef, LatencyMicros: latency}
responses[index] = capability.TaskCaseResponse{CaseID: item.CaseID, ResponseText: response.AdvisoryText, LatencyMicros: latency, EvidenceArtifactRef: item.EvidenceArtifactRef}
}(i, item)
}
wg.Wait()
return observations, responses
}
func taskEvaluationUnavailable(cases []capability.TaskEvaluationCase) ([]capability.TaskCaseObservation, []capability.TaskCaseResponse) {
observations := make([]capability.TaskCaseObservation, len(cases))
responses := make([]capability.TaskCaseResponse, len(cases))
for i, item := range cases {
observations[i] = capability.TaskCaseObservation{CaseID: item.CaseID, Result: capability.EvaluationUnknown, CriticalFailure: true, EvidenceArtifactRef: item.EvidenceArtifactRef}
responses[i] = capability.TaskCaseResponse{CaseID: item.CaseID, EvidenceArtifactRef: item.EvidenceArtifactRef}
}
return observations, responses
}
func taskEvaluationResponsesUsable(responses []capability.TaskCaseResponse) bool {
if len(responses) == 0 {
return false
}
for _, response := range responses {
if strings.TrimSpace(response.ResponseText) == "" {
return false
}
}
return true
}
func writeTaskEvaluationJSON(w http.ResponseWriter, value any) {
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(value)
@@ -56,3 +56,90 @@ func TestTaskEvaluationHandlerInvokesEveryBoundedCase(t *testing.T) {
}
}
}
type taskEvaluationFailoverRegistry struct {
primary, fallback capability.ResolvedCognitiveBinding
resolveCalls int
}
func (s *taskEvaluationFailoverRegistry) ResolveCandidate(context.Context, capability.CapabilityBindingID, int64) (capability.ResolvedCognitiveBinding, error) {
return s.primary, nil
}
func (s *taskEvaluationFailoverRegistry) Resolve(_ context.Context, requirement capability.CognitiveRequirement) (capability.ResolvedCognitiveBinding, error) {
s.resolveCalls++
if len(requirement.ExcludedBindingIDs) == 0 {
return s.primary, nil
}
return s.fallback, nil
}
func (s *taskEvaluationFailoverRegistry) Observe(context.Context, capability.CognitiveUsageObservation) error {
return nil
}
func (s *taskEvaluationFailoverRegistry) RegisterDiscovery(context.Context, capability.CognitiveDiscoverySnapshot) (capability.CognitiveDiscoveryRegistration, error) {
return capability.CognitiveDiscoveryRegistration{}, nil
}
func TestTaskEvaluationExcludesUnavailableCandidateAndUsesFallback(t *testing.T) {
primary := gatewaySelection()
fallback := primary
fallback.Binding.ID = "binding-fallback"
fallback.Binding.Revision = 7
fallback.Binding.ProviderInstanceID = "provider-fallback"
fallback.Provider.ID = "provider-fallback"
fallback.Provider.Kind = "provider-fallback-kind"
fallback.Provider.EndpointRef = "fallback-endpoint"
fallback.Provider.CredentialRef = "fallback-credential"
fallback.Model.ProviderInstanceID = "provider-fallback"
registry := &taskEvaluationFailoverRegistry{primary: primary, fallback: fallback}
primaryProvider := &stubAdvisoryProvider{response: cognitive.AdvisoryResponse{Version: cognitive.CreatorAdvisoryV1, FinishReason: cognitive.FinishProviderUnavailable}}
fallbackProvider := &stubAdvisoryProvider{response: cognitive.AdvisoryResponse{Version: cognitive.CreatorAdvisoryV1, FinishReason: cognitive.FinishCompleted, AdvisoryText: "fallback response"}}
gateway := cognitiveGateway{resolver: registry, providers: map[capability.ProviderKind][]providerRegistration{
primary.Provider.Kind: {{ProviderInstanceID: primary.Provider.ID, Kind: primary.Provider.Kind, EndpointRef: primary.Provider.EndpointRef, CredentialRef: primary.Provider.CredentialRef, Provider: primaryProvider}},
fallback.Provider.Kind: {{ProviderInstanceID: fallback.Provider.ID, Kind: fallback.Provider.Kind, EndpointRef: fallback.Provider.EndpointRef, CredentialRef: fallback.Provider.CredentialRef, Provider: fallbackProvider}},
}, now: func() time.Time { return time.UnixMilli(1000) }}
profile := capability.TaskEvaluationProfile{ID: "profile-failover", Revision: 1, CapabilityID: primary.Binding.CapabilityID, Stage: capability.TaskEvaluationGeneral, BenchmarkVersion: "failover-v1", RequiredCaseIDs: []string{"case-a"}, QualityWeightMillis: 600, ReliabilityWeightMillis: 300, LatencyWeightMillis: 100}
request := capability.TaskEvaluationExecutionRequest{Profile: profile, CapabilityID: profile.CapabilityID, BindingID: primary.Binding.ID, BindingRevision: primary.Binding.Revision, ActorID: identity.ActorID("actor-1"), PrincipalID: primary.Binding.OwnerPrincipalID, ScopeID: authority.ScopeID("scope-1"), MaxOutputBytes: 512, EgressAcknowledged: true, Cases: []capability.TaskEvaluationCase{{CaseID: "case-a", Prompt: "one", EvidenceArtifactRef: "artifact:a"}}}
payload, _ := json.Marshal(request)
recorder := httptest.NewRecorder()
gateway.taskEvaluationHandler(recorder, httptest.NewRequest(http.MethodPost, "/v1/task-evaluations/execute", bytes.NewReader(payload)))
if recorder.Code != http.StatusOK {
t.Fatalf("status=%d body=%s", recorder.Code, recorder.Body.String())
}
var response capability.TaskEvaluationExecutionResponse
if err := json.Unmarshal(recorder.Body.Bytes(), &response); err != nil {
t.Fatal(err)
}
if response.BindingID != fallback.Binding.ID || !primaryProvider.called || !fallbackProvider.called || registry.resolveCalls != 1 {
t.Fatalf("failover binding=%s primary_called=%t fallback_called=%t resolve_calls=%d", response.BindingID, primaryProvider.called, fallbackProvider.called, registry.resolveCalls)
}
if response.Observations[0].CriticalFailure || response.Responses[0].ResponseText != "fallback response" {
t.Fatalf("fallback response was not usable: %+v %+v", response.Observations, response.Responses)
}
}
func TestTaskEvaluationUnavailableResponseIsMarkedCriticalUnknown(t *testing.T) {
selected := gatewaySelection()
registry := &taskEvaluationFailoverRegistry{primary: selected, fallback: selected}
provider := &stubAdvisoryProvider{response: cognitive.AdvisoryResponse{Version: cognitive.CreatorAdvisoryV1, FinishReason: cognitive.FinishTimeout}}
gateway := cognitiveGateway{resolver: registry, providers: map[capability.ProviderKind][]providerRegistration{
selected.Provider.Kind: {{ProviderInstanceID: selected.Provider.ID, Kind: selected.Provider.Kind, EndpointRef: selected.Provider.EndpointRef, CredentialRef: selected.Provider.CredentialRef, Provider: provider}},
}, now: func() time.Time { return time.UnixMilli(1000) }}
profile := capability.TaskEvaluationProfile{ID: "profile-unknown", Revision: 1, CapabilityID: selected.Binding.CapabilityID, Stage: capability.TaskEvaluationGeneral, BenchmarkVersion: "unknown-v1", RequiredCaseIDs: []string{"case-a"}, QualityWeightMillis: 600, ReliabilityWeightMillis: 300, LatencyWeightMillis: 100}
request := capability.TaskEvaluationExecutionRequest{Profile: profile, CapabilityID: profile.CapabilityID, BindingID: selected.Binding.ID, BindingRevision: selected.Binding.Revision, ActorID: identity.ActorID("actor-1"), PrincipalID: selected.Binding.OwnerPrincipalID, ScopeID: authority.ScopeID("scope-1"), MaxOutputBytes: 512, EgressAcknowledged: true, Cases: []capability.TaskEvaluationCase{{CaseID: "case-a", Prompt: "one", EvidenceArtifactRef: "artifact:a"}}}
payload, _ := json.Marshal(request)
recorder := httptest.NewRecorder()
gateway.taskEvaluationHandler(recorder, httptest.NewRequest(http.MethodPost, "/v1/task-evaluations/execute", bytes.NewReader(payload)))
if recorder.Code != http.StatusOK {
t.Fatalf("status=%d body=%s", recorder.Code, recorder.Body.String())
}
var response capability.TaskEvaluationExecutionResponse
if err := json.Unmarshal(recorder.Body.Bytes(), &response); err != nil {
t.Fatal(err)
}
if len(response.Observations) != 1 || response.Observations[0].Result != capability.EvaluationUnknown || !response.Observations[0].CriticalFailure {
t.Fatalf("unavailable response was overclaimed: %+v", response.Observations)
}
}
@@ -61,13 +61,6 @@ func (c cognitiveInterpretationClient) Interpret(ctx context.Context, request co
}
func shouldRequestBroaderInterpretation(result interactiondomain.SemanticResult) bool {
// The built-in advisory channel is the default conversational path. A
// simple question must not first wait for the slower, strict JSON
// interpretation route: that route exists for ambiguity and work routing,
// not as a prerequisite for talking to Domovoy.
if isSimpleLocalQuestion(result) {
return false
}
switch result.Interpretation.Applicability {
case interaction.SemanticNeedsBroaderInterpret, interaction.SemanticLowConfidence, interaction.SemanticMixedIntent:
return true
@@ -7,7 +7,7 @@ import (
interactiondomain "git.cin.su/m/Assistant/modules/interaction/domain"
)
func TestSimpleQuestionUsesBuiltInAdvisoryWithoutStrictInterpretation(t *testing.T) {
func TestSimpleQuestionUsesStrictInterpretationForSemanticRouting(t *testing.T) {
result := interactiondomain.SemanticResult{
Interpretation: interaction.UtteranceInterpretation{
SpeechAct: interaction.SpeechActQuestion,
@@ -16,8 +16,8 @@ func TestSimpleQuestionUsesBuiltInAdvisoryWithoutStrictInterpretation(t *testing
},
CapabilityHints: []string{"COGNITIVE_REASONING", "VERIFY_FACT"},
}
if shouldRequestBroaderInterpretation(result) {
t.Fatal("simple question must go directly to the built-in advisory path")
if !shouldRequestBroaderInterpretation(result) {
t.Fatal("simple question must reach semantic interpretation before any answer")
}
}
+129
View File
@@ -0,0 +1,129 @@
package main
import (
"context"
"errors"
"strings"
"time"
"git.cin.su/m/Assistant/adapters/developmentexecutor"
"git.cin.su/m/Assistant/contracts/work"
workdomain "git.cin.su/m/Assistant/modules/work/domain"
"git.cin.su/m/Assistant/modules/work/ports"
)
var errDevelopmentGateCreate = errors.New("development gate must start at evidence collected with revision one")
var errDevelopmentGateNotIsolated = errors.New("development gate is not isolated")
var errDevelopmentVerifierNotIndependent = errors.New("development verifier is not independent")
var errDevelopmentWorktreeMismatch = errors.New("verification worktree does not match gate")
var errDevelopmentChecksFailed = errors.New("development verification checks failed")
var errDevelopmentEvidenceRequired = errors.New("development transition evidence is required")
var errDevelopmentEvidenceStage = errors.New("development transition evidence does not match stage")
type developmentGateVerificationRequest struct {
GateID string
ExpectedRevision int64
VerifierRunID work.AgentRunID
Plan developmentexecutor.Plan
}
type developmentGateEvidenceRequest struct {
GateID string
ExpectedRevision int64
NextStage work.DevelopmentStage
EvidenceRef string
}
func recordDevelopmentGateEvidence(ctx context.Context, store ports.DevelopmentGateStore, request developmentGateEvidenceRequest, now time.Time) (work.DevelopmentGate, error) {
gate, err := store.LoadDevelopmentGate(ctx, request.GateID)
if err != nil {
return work.DevelopmentGate{}, err
}
if request.ExpectedRevision < 1 || gate.Revision != request.ExpectedRevision {
return gate, errDevelopmentGateNotIsolated
}
if strings.TrimSpace(request.EvidenceRef) == "" {
return gate, errDevelopmentEvidenceRequired
}
switch request.NextStage {
case work.DevelopmentCanary:
gate.CanaryEvidence = strings.TrimSpace(request.EvidenceRef)
case work.DevelopmentPromoted:
gate.PromotionRef = strings.TrimSpace(request.EvidenceRef)
case work.DevelopmentRolledBack:
gate.RollbackRef = strings.TrimSpace(request.EvidenceRef)
default:
return gate, errDevelopmentEvidenceStage
}
advanced, err := advanceDevelopmentGate(gate, request.NextStage, now)
if err != nil {
return gate, err
}
if err := store.SaveDevelopmentGate(ctx, advanced, request.ExpectedRevision); err != nil {
return gate, err
}
return advanced, nil
}
func verifyDevelopmentGate(ctx context.Context, store ports.DevelopmentGateStore, request developmentGateVerificationRequest, now time.Time, runner developmentexecutor.CommandRunner) (work.DevelopmentGate, developmentexecutor.Result, error) {
gate, err := store.LoadDevelopmentGate(ctx, request.GateID)
if err != nil {
return work.DevelopmentGate{}, developmentexecutor.Result{}, err
}
if request.ExpectedRevision < 1 || gate.Revision != request.ExpectedRevision || gate.Stage != work.DevelopmentIsolated {
return gate, developmentexecutor.Result{}, errDevelopmentGateNotIsolated
}
if request.VerifierRunID == "" || sameRun(request.VerifierRunID, gate.ProducerRunRefs) {
return gate, developmentexecutor.Result{}, errDevelopmentVerifierNotIndependent
}
if request.Plan.WorktreeID != gate.WorktreeRef {
return gate, developmentexecutor.Result{}, errDevelopmentWorktreeMismatch
}
result, err := developmentexecutor.Execute(ctx, request.Plan, runner)
if err != nil {
return gate, result, err
}
if !result.Passed {
return gate, result, errDevelopmentChecksFailed
}
gate.VerifierRunRefs = append(append([]work.AgentRunID(nil), gate.VerifierRunRefs...), request.VerifierRunID)
gate.VerificationEvidence = result.EvidenceRef
advanced, err := advanceDevelopmentGate(gate, work.DevelopmentVerified, now)
if err != nil {
return gate, result, err
}
if err := store.SaveDevelopmentGate(ctx, advanced, request.ExpectedRevision); err != nil {
return gate, result, err
}
return advanced, result, nil
}
func sameRun(value work.AgentRunID, runs []work.AgentRunID) bool {
for _, run := range runs {
if run == value {
return true
}
}
return false
}
func prepareDevelopmentGate(value work.DevelopmentGate, now time.Time) (work.DevelopmentGate, error) {
if value.Stage != work.DevelopmentEvidenceCollected || value.Revision != 1 {
return work.DevelopmentGate{}, errDevelopmentGateCreate
}
value.UpdatedAtUnixMillis = now.UnixMilli()
if err := workdomain.ValidateDevelopmentGate(value); err != nil {
return work.DevelopmentGate{}, err
}
return value, nil
}
func advanceDevelopmentGate(value work.DevelopmentGate, next work.DevelopmentStage, now time.Time) (work.DevelopmentGate, error) {
advanced, err := workdomain.AdvanceDevelopmentGate(value, next)
if err != nil {
return work.DevelopmentGate{}, err
}
advanced.Revision = value.Revision + 1
advanced.UpdatedAtUnixMillis = now.UnixMilli()
return advanced, nil
}
+182
View File
@@ -0,0 +1,182 @@
package main
import (
"context"
"path/filepath"
"testing"
"time"
"git.cin.su/m/Assistant/adapters/developmentexecutor"
"git.cin.su/m/Assistant/contracts/work"
)
type developmentGateStoreStub struct {
gate work.DevelopmentGate
saved bool
expected int64
}
func (s *developmentGateStoreStub) LoadDevelopmentGate(context.Context, string) (work.DevelopmentGate, error) {
return s.gate, nil
}
func (s *developmentGateStoreStub) SaveDevelopmentGate(_ context.Context, gate work.DevelopmentGate, expected int64) error {
s.gate = gate
s.saved = true
s.expected = expected
return nil
}
type developmentCommandRunnerStub struct {
exitCodes []int
commands int
}
func (r *developmentCommandRunnerStub) Run(context.Context, string, string, []string) int {
code := 0
if r.commands < len(r.exitCodes) {
code = r.exitCodes[r.commands]
}
r.commands++
return code
}
func validDevelopmentVerificationPlan(t *testing.T, worktreeID string) developmentexecutor.Plan {
t.Helper()
worktree, err := filepath.Abs(".")
if err != nil {
t.Fatal(err)
}
return developmentexecutor.Plan{
Worktree: worktree,
WorktreeID: worktreeID,
BaseRepoRevision: "base-revision",
ProtectedMainAccess: "DENIED",
ProductionAccess: "DENIED",
SecretAccess: "DENIED",
Checks: []developmentexecutor.Check{{Kind: developmentexecutor.CheckGitDiffCheck}},
}
}
func isolatedDevelopmentGate() work.DevelopmentGate {
return work.DevelopmentGate{
ID: "gate-1",
Revision: 4,
Stage: work.DevelopmentIsolated,
EvidenceRefs: []string{"evidence-1"},
ProposalRef: "proposal-1",
TaskPacketRef: "task-1",
WorktreeRef: "sandbox-1",
ProducerRunRefs: []work.AgentRunID{"producer-1"},
VerifierRunRefs: nil,
VerificationEvidence: "",
}
}
func TestVerifyDevelopmentGatePersistsVerifiedWithIndependentEvidence(t *testing.T) {
store := &developmentGateStoreStub{gate: isolatedDevelopmentGate()}
runner := &developmentCommandRunnerStub{exitCodes: []int{0}}
gate, result, err := verifyDevelopmentGate(context.Background(), store, developmentGateVerificationRequest{
GateID: "gate-1",
ExpectedRevision: 4,
VerifierRunID: "verifier-1",
Plan: validDevelopmentVerificationPlan(t, "sandbox-1"),
}, time.UnixMilli(20), runner)
if err != nil {
t.Fatal(err)
}
if !result.Passed || gate.Stage != work.DevelopmentVerified || gate.Revision != 5 {
t.Fatalf("verification did not advance gate: gate=%#v result=%#v", gate, result)
}
if !store.saved || store.expected != 4 || gate.VerificationEvidence == "" {
t.Fatalf("verification was not durably persisted: store=%#v gate=%#v", store, gate)
}
}
func TestVerifyDevelopmentGateLeavesIsolatedOnFailedChecks(t *testing.T) {
store := &developmentGateStoreStub{gate: isolatedDevelopmentGate()}
gate, result, err := verifyDevelopmentGate(context.Background(), store, developmentGateVerificationRequest{
GateID: "gate-1",
ExpectedRevision: 4,
VerifierRunID: "verifier-1",
Plan: validDevelopmentVerificationPlan(t, "sandbox-1"),
}, time.UnixMilli(20), &developmentCommandRunnerStub{exitCodes: []int{1}})
if err != errDevelopmentChecksFailed || result.Passed || gate.Stage != work.DevelopmentIsolated || store.saved {
t.Fatalf("failed checks changed gate unexpectedly: gate=%#v result=%#v err=%v store=%#v", gate, result, err, store)
}
}
func TestVerifyDevelopmentGateRejectsProducerAsVerifier(t *testing.T) {
store := &developmentGateStoreStub{gate: isolatedDevelopmentGate()}
_, _, err := verifyDevelopmentGate(context.Background(), store, developmentGateVerificationRequest{
GateID: "gate-1",
ExpectedRevision: 4,
VerifierRunID: "producer-1",
Plan: validDevelopmentVerificationPlan(t, "sandbox-1"),
}, time.UnixMilli(20), &developmentCommandRunnerStub{exitCodes: []int{0}})
if err != errDevelopmentVerifierNotIndependent {
t.Fatalf("producer was accepted as verifier: %v", err)
}
}
func TestRecordDevelopmentGateEvidenceAdvancesCanaryAndPromotion(t *testing.T) {
store := &developmentGateStoreStub{gate: work.DevelopmentGate{
ID: "gate-1", Revision: 6, Stage: work.DevelopmentVerified,
EvidenceRefs: []string{"evidence-1"}, ProposalRef: "proposal-1", TaskPacketRef: "task-1", WorktreeRef: "sandbox-1",
ProducerRunRefs: []work.AgentRunID{"producer-1"}, VerifierRunRefs: []work.AgentRunID{"verifier-1"}, VerificationEvidence: "verification-1",
}}
gate, err := recordDevelopmentGateEvidence(context.Background(), store, developmentGateEvidenceRequest{GateID: "gate-1", ExpectedRevision: 6, NextStage: work.DevelopmentCanary, EvidenceRef: "canary-1"}, time.UnixMilli(30))
if err != nil || gate.Stage != work.DevelopmentCanary || gate.Revision != 7 || gate.CanaryEvidence != "canary-1" {
t.Fatalf("canary transition failed: gate=%#v err=%v", gate, err)
}
gate, err = recordDevelopmentGateEvidence(context.Background(), store, developmentGateEvidenceRequest{GateID: "gate-1", ExpectedRevision: 7, NextStage: work.DevelopmentPromoted, EvidenceRef: "promotion-1"}, time.UnixMilli(31))
if err != nil || gate.Stage != work.DevelopmentPromoted || gate.Revision != 8 || gate.PromotionRef != "promotion-1" {
t.Fatalf("promotion transition failed: gate=%#v err=%v", gate, err)
}
}
func TestRecordDevelopmentGateEvidenceSupportsCanaryRollbackAndRejectsMissingEvidence(t *testing.T) {
store := &developmentGateStoreStub{gate: work.DevelopmentGate{ID: "gate-1", Revision: 2, Stage: work.DevelopmentCanary, EvidenceRefs: []string{"evidence-1"}, CanaryEvidence: "canary-1"}}
gate, err := recordDevelopmentGateEvidence(context.Background(), store, developmentGateEvidenceRequest{GateID: "gate-1", ExpectedRevision: 2, NextStage: work.DevelopmentRolledBack, EvidenceRef: "rollback-1"}, time.UnixMilli(40))
if err != nil || gate.Stage != work.DevelopmentRolledBack || gate.RollbackRef != "rollback-1" {
t.Fatalf("rollback transition failed: gate=%#v err=%v", gate, err)
}
store = &developmentGateStoreStub{gate: work.DevelopmentGate{ID: "gate-2", Revision: 2, Stage: work.DevelopmentCanary, EvidenceRefs: []string{"evidence-1"}, CanaryEvidence: "canary-1"}}
gate, err = recordDevelopmentGateEvidence(context.Background(), store, developmentGateEvidenceRequest{GateID: "gate-2", ExpectedRevision: 2, NextStage: work.DevelopmentRolledBack}, time.UnixMilli(40))
if err != errDevelopmentEvidenceRequired || gate.Stage != work.DevelopmentCanary || store.saved {
t.Fatalf("missing rollback evidence changed gate: gate=%#v err=%v store=%#v", gate, err, store)
}
}
func TestDevelopmentGateControllerRequiresSequentialVerifiedEvidence(t *testing.T) {
now := time.UnixMilli(10)
gate, err := prepareDevelopmentGate(work.DevelopmentGate{ID: "gate-1", Revision: 1, Stage: work.DevelopmentEvidenceCollected, EvidenceRefs: []string{"evidence-1"}}, now)
if err != nil {
t.Fatal(err)
}
gate.ProposalRef = "proposal-1"
gate, err = advanceDevelopmentGate(gate, work.DevelopmentProposalCreated, now)
if err != nil || gate.Revision != 2 {
t.Fatalf("proposal transition failed: %#v %v", gate, err)
}
gate.TaskPacketRef = "task-1"
gate, err = advanceDevelopmentGate(gate, work.DevelopmentTaskAdmitted, now)
if err != nil {
t.Fatal(err)
}
gate.WorktreeRef = "isolated-worktree"
gate, err = advanceDevelopmentGate(gate, work.DevelopmentIsolated, now)
if err != nil {
t.Fatal(err)
}
gate.ProducerRunRefs = []work.AgentRunID{"producer"}
gate.VerifierRunRefs = []work.AgentRunID{"verifier"}
gate.VerificationEvidence = "verification-1"
gate, err = advanceDevelopmentGate(gate, work.DevelopmentVerified, now)
if err != nil {
t.Fatal(err)
}
if _, err := advanceDevelopmentGate(gate, work.DevelopmentPromoted, now); err == nil {
t.Fatal("promotion bypassed canary")
}
}
+182
View File
@@ -5,6 +5,7 @@ import (
"encoding/json"
"errors"
"fmt"
"git.cin.su/m/Assistant/adapters/developmentexecutor"
"git.cin.su/m/Assistant/adapters/postgres"
"git.cin.su/m/Assistant/contracts/authority"
"git.cin.su/m/Assistant/contracts/capability"
@@ -21,6 +22,7 @@ import (
interactiondomain "git.cin.su/m/Assistant/modules/interaction/domain"
situationdomain "git.cin.su/m/Assistant/modules/situation/domain"
workdomain "git.cin.su/m/Assistant/modules/work/domain"
"git.cin.su/m/Assistant/platform/ids"
platformsecrets "git.cin.su/m/Assistant/platform/secrets"
"git.cin.su/m/Assistant/runtime/health"
"github.com/jackc/pgx/v5"
@@ -116,6 +118,10 @@ func main() {
if err != nil {
log.Fatal(err)
}
researchClient, err := newResearchRuntimeClient(envOr("RESEARCH_RUNTIME_URL", "http://research-runtime:8087"), &http.Client{Timeout: 12 * time.Second})
if err != nil {
log.Fatal(err)
}
learning := learningRecorder{epistemics: epistemicsRepository, capability: capabilityRepository}
mux := http.NewServeMux()
authenticateCreator := func(w http.ResponseWriter, r *http.Request) (identity.CreatorSession, bool) {
@@ -178,6 +184,25 @@ func main() {
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(session)
})
mux.HandleFunc("POST /v1/creator/research/web", func(w http.ResponseWriter, r *http.Request) {
if _, ok := authenticateCreator(w, r); !ok {
return
}
var request epistemics.WebResearchRequest
decoder := json.NewDecoder(http.MaxBytesReader(w, r.Body, 128<<10))
decoder.DisallowUnknownFields()
if err := decoder.Decode(&request); err != nil {
http.Error(w, "invalid web research request", http.StatusBadRequest)
return
}
result, err := researchClient.EvaluateWeb(r.Context(), request)
if err != nil {
http.Error(w, "web research unavailable", http.StatusBadGateway)
return
}
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(result)
})
mux.HandleFunc("POST /v1/creator/development/proposals", func(w http.ResponseWriter, r *http.Request) {
session, ok := authenticateCreator(w, r)
if !ok {
@@ -233,6 +258,111 @@ func main() {
w.WriteHeader(http.StatusCreated)
_ = json.NewEncoder(w).Encode(scaffold)
})
mux.HandleFunc("POST /v1/creator/development/gates", func(w http.ResponseWriter, r *http.Request) {
if _, ok := authenticateCreator(w, r); !ok {
return
}
var gate work.DevelopmentGate
decoder := json.NewDecoder(http.MaxBytesReader(w, r.Body, 64<<10))
decoder.DisallowUnknownFields()
if err := decoder.Decode(&gate); err != nil {
http.Error(w, "invalid development gate", http.StatusBadRequest)
return
}
gate, err := prepareDevelopmentGate(gate, time.Now())
if err != nil {
http.Error(w, "development gate rejected", http.StatusUnprocessableEntity)
return
}
if err := workRepository.SaveDevelopmentGate(r.Context(), gate, 0); err != nil {
http.Error(w, "development gate conflict", http.StatusConflict)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
_ = json.NewEncoder(w).Encode(gate)
})
mux.HandleFunc("POST /v1/creator/development/gates/{id}/advance", func(w http.ResponseWriter, r *http.Request) {
if _, ok := authenticateCreator(w, r); !ok {
return
}
var request struct {
ExpectedRevision int64 `json:"expected_revision"`
NextStage work.DevelopmentStage `json:"next_stage"`
}
decoder := json.NewDecoder(http.MaxBytesReader(w, r.Body, 16<<10))
decoder.DisallowUnknownFields()
if err := decoder.Decode(&request); err != nil || request.ExpectedRevision < 1 || request.NextStage == "" {
http.Error(w, "invalid development gate transition", http.StatusBadRequest)
return
}
gate, err := workRepository.LoadDevelopmentGate(r.Context(), r.PathValue("id"))
if err != nil || gate.Revision != request.ExpectedRevision {
http.Error(w, "development gate revision conflict", http.StatusConflict)
return
}
gate, err = advanceDevelopmentGate(gate, request.NextStage, time.Now())
if err != nil {
http.Error(w, "development gate transition rejected", http.StatusUnprocessableEntity)
return
}
if err := workRepository.SaveDevelopmentGate(r.Context(), gate, request.ExpectedRevision); err != nil {
http.Error(w, "development gate conflict", http.StatusConflict)
return
}
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(gate)
})
mux.HandleFunc("POST /v1/creator/development/gates/{id}/verify", func(w http.ResponseWriter, r *http.Request) {
if _, ok := authenticateCreator(w, r); !ok {
return
}
var request struct {
ExpectedRevision int64 `json:"expected_revision"`
VerifierRunID work.AgentRunID `json:"verifier_run_id"`
Plan developmentexecutor.Plan `json:"plan"`
}
decoder := json.NewDecoder(http.MaxBytesReader(w, r.Body, 64<<10))
decoder.DisallowUnknownFields()
if err := decoder.Decode(&request); err != nil || request.ExpectedRevision < 1 || request.VerifierRunID == "" {
http.Error(w, "invalid development verification request", http.StatusBadRequest)
return
}
gate, result, err := verifyDevelopmentGate(r.Context(), workRepository, developmentGateVerificationRequest{GateID: r.PathValue("id"), ExpectedRevision: request.ExpectedRevision, VerifierRunID: request.VerifierRunID, Plan: request.Plan}, time.Now(), developmentexecutor.OSCommandRunner{})
if err != nil {
http.Error(w, err.Error(), http.StatusUnprocessableEntity)
return
}
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(map[string]any{"gate": gate, "verification": result})
})
registerDevelopmentGateEvidenceRoute := func(path string, next work.DevelopmentStage, fieldName string) {
mux.HandleFunc("POST /v1/creator/development/gates/{id}/"+path, func(w http.ResponseWriter, r *http.Request) {
if _, ok := authenticateCreator(w, r); !ok {
return
}
var request struct {
ExpectedRevision int64 `json:"expected_revision"`
EvidenceRef string `json:"evidence_ref"`
}
decoder := json.NewDecoder(http.MaxBytesReader(w, r.Body, 16<<10))
decoder.DisallowUnknownFields()
if err := decoder.Decode(&request); err != nil || request.ExpectedRevision < 1 || request.EvidenceRef == "" {
http.Error(w, "invalid development transition evidence", http.StatusBadRequest)
return
}
gate, err := recordDevelopmentGateEvidence(r.Context(), workRepository, developmentGateEvidenceRequest{GateID: r.PathValue("id"), ExpectedRevision: request.ExpectedRevision, NextStage: next, EvidenceRef: request.EvidenceRef}, time.Now())
if err != nil {
http.Error(w, "development "+fieldName+" transition rejected", http.StatusUnprocessableEntity)
return
}
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(gate)
})
}
registerDevelopmentGateEvidenceRoute("canary", work.DevelopmentCanary, "canary")
registerDevelopmentGateEvidenceRoute("promote", work.DevelopmentPromoted, "promotion")
registerDevelopmentGateEvidenceRoute("rollback", work.DevelopmentRolledBack, "rollback")
mux.HandleFunc("GET /v1/creator/focus", func(w http.ResponseWriter, r *http.Request) {
session, ok := authenticateCreator(w, r)
if !ok {
@@ -492,6 +622,17 @@ func main() {
planningReply = humanCognitiveChannelReply(semantic.InformationRequestKind, semantic.Interpretation.SpeechAct, health, healthErr)
needsCognition = false
}
researchBacked := false
if hasCapabilityHint(semantic.CapabilityHints, "WEB_RESEARCH") && semantic.Interpretation.Clarification != interaction.ClarificationRequired {
if planID, idErr := ids.New(); idErr == nil {
result, researchErr := researchClient.SearchWeb(r.Context(), epistemics.WebSearchRequest{
Plan: epistemics.ResearchPlan{ID: epistemics.ResearchPlanID("plan:" + planID), Question: utterance.Content, SourceClasses: []string{"web-search"}, FreshnessPolicy: "current"},
PropositionKey: "question:" + string(utterance.ID), Query: utterance.Content,
})
planningReply, researchBacked = renderResearchReply(result, researchErr)
needsCognition = false
}
}
if helpPath.Reply != "" {
planningReply = helpPath.Reply
needsCognition = false
@@ -589,6 +730,7 @@ func main() {
Clarification: semantic.Interpretation.Clarification,
WorkPlanned: workPlanned,
PlanningReply: planningReply,
ResearchBacked: researchBacked,
CapabilityStatus: capabilityStatus,
CapabilityKind: capabilityKind,
CapabilityHints: toCapabilityKinds(semantic.CapabilityHints),
@@ -980,6 +1122,20 @@ func main() {
}
w.WriteHeader(http.StatusCreated)
})
mux.HandleFunc("POST /v1/cognitive-registry/model-qualification-observations", func(w http.ResponseWriter, r *http.Request) {
var value capability.ModelQualificationObservation
decoder := json.NewDecoder(io.LimitReader(r.Body, 16<<10))
decoder.DisallowUnknownFields()
if err := decoder.Decode(&value); err != nil || value.Validate() != nil {
http.Error(w, "invalid model qualification observation", http.StatusBadRequest)
return
}
if err := capabilityRepository.SaveModelQualificationObservation(r.Context(), value); err != nil {
http.Error(w, "save model qualification observation", http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusCreated)
})
mux.HandleFunc("POST /v1/creator/cognitive-canary", func(w http.ResponseWriter, r *http.Request) {
session, ok := authenticateCreator(w, r)
if !ok {
@@ -1284,6 +1440,32 @@ func envInt(name string, fallback int) int {
return value
}
func hasCapabilityHint(hints []string, wanted string) bool {
for _, hint := range hints {
if hint == wanted {
return true
}
}
return false
}
func renderResearchReply(result epistemics.ResearchResult, researchErr error) (string, bool) {
if researchErr != nil {
return "Я не смог получить проверяемые данные из research-источника, поэтому не буду придумывать ответ.", false
}
lines := make([]string, 0, len(result.Findings))
for _, finding := range result.Findings {
if finding.State != epistemics.FindingSupported || strings.TrimSpace(finding.Excerpt.Text) == "" || strings.TrimSpace(finding.Source.Locator) == "" {
continue
}
lines = append(lines, "- "+finding.Excerpt.Text+"\n Источник: "+finding.Source.Locator)
}
if len(lines) == 0 {
return "Я не получил достаточно согласованных данных из проверяемых источников, поэтому не буду выдавать предположение как факт.", false
}
return "Нашёл точные выдержки из подключённого источника. Я не заменяю их догадкой:\n" + strings.Join(lines, "\n"), true
}
func humanCognitiveModelInventory(candidates []capability.ResolvedCognitiveBinding, inventoryErr error) string {
if inventoryErr != nil {
return "Я не смогла прочитать реестр подключённых моделей. Встроенный канал Домового остаётся доступен; настройки можно проверить повторно после восстановления реестра. Ключи и пароли не выводятся в чат."
+78
View File
@@ -0,0 +1,78 @@
package main
import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/http"
"strings"
"git.cin.su/m/Assistant/contracts/epistemics"
)
type researchRuntimeClient struct {
baseURL string
client *http.Client
}
func newResearchRuntimeClient(rawURL string, client *http.Client) (researchRuntimeClient, error) {
base := strings.TrimRight(strings.TrimSpace(rawURL), "/")
if base == "" {
return researchRuntimeClient{}, fmt.Errorf("research runtime URL is empty")
}
if client == nil {
client = http.DefaultClient
}
return researchRuntimeClient{baseURL: base, client: client}, nil
}
func (c researchRuntimeClient) EvaluateWeb(ctx context.Context, request epistemics.WebResearchRequest) (epistemics.ResearchResult, error) {
payload, err := json.Marshal(request)
if err != nil {
return epistemics.ResearchResult{}, err
}
httpRequest, err := http.NewRequestWithContext(ctx, http.MethodPost, c.baseURL+"/v1/research/web/evaluate", bytes.NewReader(payload))
if err != nil {
return epistemics.ResearchResult{}, err
}
httpRequest.Header.Set("Content-Type", "application/json")
response, err := c.client.Do(httpRequest)
if err != nil {
return epistemics.ResearchResult{}, err
}
defer response.Body.Close()
if response.StatusCode < 200 || response.StatusCode >= 300 {
return epistemics.ResearchResult{}, fmt.Errorf("research runtime status %d", response.StatusCode)
}
var result epistemics.ResearchResult
if err := json.NewDecoder(response.Body).Decode(&result); err != nil {
return epistemics.ResearchResult{}, err
}
return result, nil
}
func (c researchRuntimeClient) SearchWeb(ctx context.Context, request epistemics.WebSearchRequest) (epistemics.ResearchResult, error) {
payload, err := json.Marshal(request)
if err != nil {
return epistemics.ResearchResult{}, err
}
httpRequest, err := http.NewRequestWithContext(ctx, http.MethodPost, c.baseURL+"/v1/research/web/search", bytes.NewReader(payload))
if err != nil {
return epistemics.ResearchResult{}, err
}
httpRequest.Header.Set("Content-Type", "application/json")
response, err := c.client.Do(httpRequest)
if err != nil {
return epistemics.ResearchResult{}, err
}
defer response.Body.Close()
if response.StatusCode < 200 || response.StatusCode >= 300 {
return epistemics.ResearchResult{}, fmt.Errorf("research search status %d", response.StatusCode)
}
var result epistemics.ResearchResult
if err := json.NewDecoder(response.Body).Decode(&result); err != nil {
return epistemics.ResearchResult{}, err
}
return result, nil
}
+33
View File
@@ -0,0 +1,33 @@
package main
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"git.cin.su/m/Assistant/contracts/epistemics"
)
func TestResearchRuntimeClientForwardsBoundedWebRequest(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost || r.URL.Path != "/v1/research/web/evaluate" || r.Header.Get("Content-Type") != "application/json" {
t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
}
var request epistemics.WebResearchRequest
if err := json.NewDecoder(r.Body).Decode(&request); err != nil || request.URL != "https://assistant.cin.su/healthz" {
t.Fatalf("unexpected request body: %#v %v", request, err)
}
_ = json.NewEncoder(w).Encode(epistemics.ResearchResult{PlanID: request.Plan.ID, Findings: []epistemics.ResearchFinding{{PropositionKey: request.PropositionKey, State: epistemics.FindingSupported}}})
}))
defer server.Close()
client, err := newResearchRuntimeClient(server.URL, server.Client())
if err != nil {
t.Fatal(err)
}
result, err := client.EvaluateWeb(context.Background(), epistemics.WebResearchRequest{Plan: epistemics.ResearchPlan{ID: "plan-1"}, PropositionKey: "health", URL: "https://assistant.cin.su/healthz"})
if err != nil || result.PlanID != "plan-1" || len(result.Findings) != 1 || result.Findings[0].State != epistemics.FindingSupported {
t.Fatalf("unexpected result: %#v %v", result, err)
}
}
+38
View File
@@ -151,6 +151,44 @@ func TestChatMessageUsesCoreAndLocalOnlyCognitiveGateway(t *testing.T) {
}
}
func TestChatMessageDoesNotExposeUnverifiedQuestionAsFact(t *testing.T) {
core := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch {
case r.Method == http.MethodGet && r.URL.Path == "/v1/creator/sessions/current":
_ = json.NewEncoder(w).Encode(identity.CreatorSession{ActorID: "actor-1", PrincipalID: "principal-1"})
case r.Method == http.MethodPost && r.URL.Path == "/v1/creator/chat/messages":
w.WriteHeader(http.StatusCreated)
_ = json.NewEncoder(w).Encode(interaction.ChatMessageResponse{ConversationID: "conversation-1", UtteranceID: "utterance-1", SpeechAct: interaction.SpeechActQuestion, NeedsCognition: true, FallbackText: "небезопасная модельная догадка"})
default:
t.Fatalf("unexpected core route %s %s", r.Method, r.URL.Path)
}
}))
defer core.Close()
cognitiveRuntime := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
t.Fatalf("unverified question must not reach advisory: %s %s", r.Method, r.URL.Path)
}))
defer cognitiveRuntime.Close()
handler, err := newHandler(core.URL, "http://continuity", "http://commit", core.Client(), cognitiveRuntime.URL)
if err != nil {
t.Fatal(err)
}
request := httptest.NewRequest(http.MethodPost, "/api/chat/messages", strings.NewReader(`{"message":"Какая погода?"}`))
request.Header.Set("Authorization", "Bearer session-token")
request.Header.Set("Content-Type", "application/json")
recorder := httptest.NewRecorder()
handler.ServeHTTP(recorder, request)
if recorder.Code != http.StatusOK {
t.Fatalf("expected status 200, got %d body=%s", recorder.Code, recorder.Body.String())
}
var response browserChatResponse
if err := json.NewDecoder(recorder.Body).Decode(&response); err != nil {
t.Fatal(err)
}
if response.Cognition != "verification_required" || strings.Contains(response.ReplyText, "небезопасная") || !strings.Contains(response.ReplyText, "не буду придумывать") {
t.Fatalf("unexpected truth-first response: %#v", response)
}
}
func TestChatMessageReturnsHumanCapabilityMetadata(t *testing.T) {
core := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch {
+53
View File
@@ -0,0 +1,53 @@
package main
import (
"strings"
"testing"
)
func TestCreatorConsoleResponsiveGuardrails(t *testing.T) {
html, err := staticFiles.ReadFile("static/index.html")
if err != nil {
t.Fatal(err)
}
if !strings.Contains(string(html), `<meta name="viewport" content="width=device-width, initial-scale=1">`) {
t.Fatal("creator console is missing responsive viewport metadata")
}
css, err := staticFiles.ReadFile("static/assets/console.css")
if err != nil {
t.Fatal(err)
}
stylesheet := string(css)
for _, guard := range []string{
"@media (max-width: 680px)",
".console { display: block; }",
".diagnostic-list { grid-template-columns: 1fr; }",
".agent-details { grid-template-columns: 1fr; gap: 14px; }",
".fleet-list { grid-template-columns: repeat(2, minmax(0, 1fr)); }",
".section-nav { display: flex;",
"overflow-x: auto",
} {
if !strings.Contains(stylesheet, guard) {
t.Fatalf("responsive guardrail missing: %s", guard)
}
}
}
func TestChatResponsiveGuardrailsConstrainMobileGridItem(t *testing.T) {
css, err := staticFiles.ReadFile("static/assets/chat.css")
if err != nil {
t.Fatal(err)
}
stylesheet := string(css)
for _, guard := range []string{
".chat-view { width: 100%; min-width: 0;",
"@media (max-width: 640px)",
".chat-layout { height: auto; display: block; }",
".chat-header > .operator-link, .build-meta { display: none; }",
".chat-header .cognitive-console-link { flex: 0 1 110px;",
} {
if !strings.Contains(stylesheet, guard) {
t.Fatalf("chat responsive guardrail missing: %s", guard)
}
}
}
+12 -2
View File
@@ -150,7 +150,7 @@ func (g *gateway) ServeHTTP(w http.ResponseWriter, r *http.Request) {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
if r.URL.Path == "/" || r.URL.Path == "/chat" || r.URL.Path == "/chat.html" {
if r.URL.Path == "/" || r.URL.Path == "/login" || r.URL.Path == "/chat" || r.URL.Path == "/chat.html" {
g.serveEmbeddedHTML(w, "chat.html")
return
}
@@ -209,6 +209,7 @@ type browserChatResponse struct {
CapabilityKind string `json:"capability_kind"`
CapabilityHints []capability.CapabilityKind `json:"capability_hints,omitempty"`
RequestedOutcome string `json:"requested_outcome,omitempty"`
ResearchBacked bool `json:"research_backed"`
InterpretationConfidenceMillis int64 `json:"interpretation_confidence_millis,omitempty"`
DelegationID string `json:"delegation_id"`
DelegationState string `json:"delegation_state"`
@@ -467,7 +468,11 @@ func (g *gateway) handleChatMessage(w http.ResponseWriter, r *http.Request) {
saved.NeedsCognition = false
}
}
if saved.NeedsCognition && !saved.WorkPlanned && saved.Clarification != interaction.ClarificationRequired {
if saved.SpeechAct == interaction.SpeechActQuestion && saved.InformationRequestKind == "" && !saved.ResearchBacked && !saved.WorkPlanned && saved.Clarification != interaction.ClarificationRequired && !asksAttention(browserRequest.Message) {
reply = truthfulVerificationRequiredReply()
cognition = "verification_required"
saved.NeedsCognition = false
} else if saved.NeedsCognition && !saved.WorkPlanned && saved.Clarification != interaction.ClarificationRequired {
cognition = "unavailable"
requestID, idErr := ids.New()
if idErr == nil {
@@ -501,6 +506,7 @@ func (g *gateway) handleChatMessage(w http.ResponseWriter, r *http.Request) {
CapabilityKind: string(saved.CapabilityKind),
CapabilityHints: saved.CapabilityHints,
RequestedOutcome: saved.RequestedOutcome,
ResearchBacked: saved.ResearchBacked,
InterpretationConfidenceMillis: saved.InterpretationConfidenceMillis,
DelegationID: saved.DelegationID,
DelegationState: saved.DelegationState,
@@ -508,6 +514,10 @@ func (g *gateway) handleChatMessage(w http.ResponseWriter, r *http.Request) {
})
}
func truthfulVerificationRequiredReply() string {
return "Я не могу сейчас подтвердить этот факт по проверенному источнику, поэтому не буду придумывать ответ. Для проверки используйте официальный сайт соответствующей службы или надёжный сервис с текущими данными; когда подключён research-маршрут, Домовой сможет проверить это сам."
}
func localChatContext(saved interaction.ChatMessageResponse) []cognitive.ContextExcerpt {
if strings.TrimSpace(saved.ActiveProjectName) == "" {
return []cognitive.ContextExcerpt{}
+12
View File
@@ -354,6 +354,18 @@ func TestConsoleAndSecurityHeaders(t *testing.T) {
}
}
func TestLoginRouteServesChatShell(t *testing.T) {
handler, err := newHandler("http://core-runtime:8080", "http://continuity-runtime:8081", "http://commit-runtime:8082", http.DefaultClient)
if err != nil {
t.Fatal(err)
}
response := httptest.NewRecorder()
handler.ServeHTTP(response, httptest.NewRequest(http.MethodGet, "/login", nil))
if response.Code != http.StatusOK || !strings.Contains(response.Body.String(), "id=\"username\"") || !strings.Contains(response.Body.String(), "id=\"password\"") {
t.Fatalf("login response = %d", response.Code)
}
}
func TestAdvisoryModeRejectsInvalidConfiguration(t *testing.T) {
_, err := newHandlerWithAdvisoryMode("http://core.invalid", "http://continuity.invalid", "http://commit.invalid", http.DefaultClient, "browser-selected")
if err == nil || !strings.Contains(err.Error(), "invalid advisory binding mode") {
+5 -4
View File
@@ -29,7 +29,7 @@ textarea { resize: vertical; min-height: 84px; max-height: 240px; padding: 14px;
.login-form button, .composer button { border: 0; color: #11150f; background: var(--signal); font-weight: 700; }
.login-form button { width: 100%; height: 50px; margin-top: 18px; }
#login-error { min-height: 20px; color: var(--bad); font-size: 13px; }
.chat-view { height: 100vh; min-height: 0; display: grid; grid-template-rows: 64px minmax(0, 1fr); overflow: hidden; }
.chat-view { width: 100%; min-width: 0; height: 100vh; min-height: 0; display: grid; grid-template-rows: 64px minmax(0, 1fr); overflow: hidden; }
.chat-header { min-width: 0; display: flex; align-items: center; gap: 14px; padding: 0 22px; border-bottom: 1px solid var(--line); background: rgba(16, 23, 20, .98); }
.chat-header a { color: var(--ink); text-decoration: none; font: 400 24px/1 var(--serif); }
#active-focus { color: var(--good); }
@@ -107,9 +107,10 @@ textarea { resize: vertical; min-height: 84px; max-height: 240px; padding: 14px;
.conversation-starters { align-items: stretch; }
.conversation-starters span { width: 100%; }
.chat-header { gap: 10px; padding: 0 12px; }
.chat-header .cognitive-status { display: inline-flex; max-width: 150px; overflow: hidden; text-overflow: ellipsis; }
.build-meta { font-size: 8px; letter-spacing: .04em; }
.build-meta time { font-size: 7px; }
.chat-header .cognitive-status { display: inline-flex; flex: 1 1 110px; min-width: 0; max-width: 110px; overflow: hidden; text-overflow: ellipsis; }
.chat-header .cognitive-console-link { flex: 0 1 110px; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.chat-header > .operator-link, .build-meta { display: none; }
.chat-header > #logout { flex: 0 0 38px; }
.chat-layout { height: auto; display: block; }
.chat-side, .chat-main { min-height: auto; max-height: none; overflow: visible; }
.chat-left, .chat-right { border: 0; border-bottom: 1px solid var(--line); }
+17
View File
@@ -4,6 +4,8 @@ import (
"context"
"flag"
"fmt"
"log"
"net/http"
"os"
"time"
@@ -29,9 +31,21 @@ func main() {
panic(err)
}
defer repository.Close()
status := newWorkerStatus(*workerID, *once)
listenAddr := os.Getenv("REFLECTION_LISTEN_ADDR")
if listenAddr == "" {
listenAddr = ":8092"
}
go func() {
if err := http.ListenAndServe(listenAddr, status.handler()); err != nil {
log.Printf("reflection status server stopped: %v", err)
}
}()
for {
now := time.Now().UnixMilli()
status.cycleStarted(time.Now())
if _, err := repository.RecoverReflectionClaims(ctx, now); err != nil {
status.cycleFailed(time.Now(), err)
panic(err)
}
candidate, claimErr := repository.ClaimReflectionCandidate(ctx, *workerID, now, lease.Milliseconds())
@@ -39,16 +53,19 @@ func main() {
// This worker only closes the durable evaluation loop. Any cognitive
// proposal remains a candidate and must be admitted by a separate governed flow.
if err := repository.ResolveReflectionCandidate(ctx, candidate.ID, *workerID, time.Now().UnixMilli()); err != nil {
status.cycleFailed(time.Now(), err)
panic(err)
}
fmt.Printf("resolved reflection candidate %s\n", candidate.ID)
} else if *once {
fmt.Println("no reflection candidate")
}
status.cycleFinished(time.Now(), map[bool]string{true: "candidate-resolved", false: "idle"}[claimErr == nil])
if *once {
return
}
if err := repository.WaitForReflectionCandidate(ctx, *pollInterval); err != nil {
status.cycleFailed(time.Now(), err)
panic(err)
}
}
+74
View File
@@ -0,0 +1,74 @@
package main
import (
"encoding/json"
"net/http"
"sync"
"time"
"git.cin.su/m/Assistant/runtime/health"
)
type workerStatus struct {
mu sync.RWMutex
workerID string
once bool
startedAt time.Time
lastCycleAt time.Time
lastCompleted time.Time
lastResult string
lastError string
}
func newWorkerStatus(workerID string, once bool) *workerStatus {
return &workerStatus{workerID: workerID, once: once, startedAt: time.Now().UTC()}
}
func (s *workerStatus) cycleStarted(at time.Time) {
s.mu.Lock()
defer s.mu.Unlock()
s.lastCycleAt = at.UTC()
s.lastResult = "running"
s.lastError = ""
}
func (s *workerStatus) cycleFinished(at time.Time, result string) {
s.mu.Lock()
defer s.mu.Unlock()
s.lastCompleted = at.UTC()
s.lastResult = result
s.lastError = ""
}
func (s *workerStatus) cycleFailed(at time.Time, err error) {
s.mu.Lock()
defer s.mu.Unlock()
s.lastCompleted = at.UTC()
s.lastResult = "failed"
s.lastError = err.Error()
}
func (s *workerStatus) handler() http.Handler {
mux := http.NewServeMux()
mux.Handle("/healthz", health.Handler{Runtime: "reflection-worker", SemanticGeneration: 1, ArchitectureGeneration: 1})
mux.HandleFunc("/status", func(w http.ResponseWriter, _ *http.Request) {
s.mu.RLock()
response := struct {
Runtime string `json:"runtime"`
WorkerID string `json:"worker_id"`
Mode string `json:"mode"`
StartedAt time.Time `json:"started_at"`
LastCycleAt time.Time `json:"last_cycle_at,omitempty"`
LastCompleted time.Time `json:"last_completed_at,omitempty"`
LastResult string `json:"last_result"`
LastError string `json:"last_error,omitempty"`
}{Runtime: "reflection-worker", WorkerID: s.workerID, Mode: "durable-reflection-loop", StartedAt: s.startedAt, LastCycleAt: s.lastCycleAt, LastCompleted: s.lastCompleted, LastResult: s.lastResult, LastError: s.lastError}
if s.once {
response.Mode = "durable-reflection-once"
}
s.mu.RUnlock()
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(response)
})
return mux
}
+35
View File
@@ -0,0 +1,35 @@
package main
import (
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
)
func TestWorkerStatusReportsCycle(t *testing.T) {
status := newWorkerStatus("test-worker", false)
status.cycleStarted(time.Date(2026, 8, 23, 10, 0, 0, 0, time.UTC))
status.cycleFinished(time.Date(2026, 8, 23, 10, 0, 1, 0, time.UTC), "idle")
recorder := httptest.NewRecorder()
status.handler().ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, "/status", nil))
if recorder.Code != http.StatusOK {
t.Fatalf("status code = %d, want 200", recorder.Code)
}
body := recorder.Body.String()
for _, expected := range []string{"\"runtime\":\"reflection-worker\"", "\"worker_id\":\"test-worker\"", "\"last_result\":\"idle\""} {
if !strings.Contains(body, expected) {
t.Fatalf("status body missing %q: %s", expected, body)
}
}
}
func TestWorkerHealthIsAvailable(t *testing.T) {
recorder := httptest.NewRecorder()
newWorkerStatus("test-worker", true).handler().ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, "/healthz", nil))
if recorder.Code != http.StatusOK {
t.Fatalf("health status code = %d, want 200", recorder.Code)
}
}
+21
View File
@@ -34,6 +34,7 @@ func main() {
mux := http.NewServeMux()
mux.Handle("/healthz", health.Handler{Runtime: "research-runtime", SemanticGeneration: 1, ArchitectureGeneration: 1})
mux.Handle("/readyz", health.ReadinessHandler{Runtime: "research-runtime", Probes: []health.Probe{{Name: "epistemics-postgres", Check: func(ctx context.Context) error { return repository.Ping(ctx) }}}})
mux.Handle("/v1/research/status", truthRuntimeStatusHandler(repository))
mux.HandleFunc("POST /v1/research/evaluate", func(w http.ResponseWriter, r *http.Request) {
var request epistemics.ResearchEvaluationRequest
if err := json.NewDecoder(http.MaxBytesReader(w, r.Body, 2<<20)).Decode(&request); err != nil {
@@ -88,6 +89,26 @@ func main() {
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(result)
})
searchConnector := researchadapter.SearchConnector{EndpointTemplate: os.Getenv("RESEARCH_SEARCH_URL_TEMPLATE"), AllowedHosts: allowedHosts(os.Getenv("RESEARCH_SEARCH_ALLOWED_HOSTS")), Client: &http.Client{Timeout: 8 * time.Second}, MaxResults: 3}
mux.HandleFunc("POST /v1/research/web/search", func(w http.ResponseWriter, r *http.Request) {
var request epistemics.WebSearchRequest
if err := json.NewDecoder(http.MaxBytesReader(w, r.Body, 64<<10)).Decode(&request); err != nil {
http.Error(w, "invalid web search request", http.StatusBadRequest)
return
}
candidates, err := searchConnector.Collect(r.Context(), request, time.Now().UnixMilli())
if err != nil {
http.Error(w, err.Error(), http.StatusUnprocessableEntity)
return
}
result, err := application.EvaluateAndPersist(r.Context(), epistemics.ResearchEvaluationRequest{Plan: request.Plan, Candidates: candidates}, repository, time.Now().UnixMilli())
if err != nil {
http.Error(w, err.Error(), http.StatusUnprocessableEntity)
return
}
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(result)
})
addr := os.Getenv("LISTEN_ADDR")
if addr == "" {
addr = ":8087"
+47
View File
@@ -0,0 +1,47 @@
package main
import (
"context"
"encoding/json"
"net/http"
"git.cin.su/m/Assistant/adapters/postgres"
)
type truthRuntimeStatusReader interface {
TruthRuntimeStatus(context.Context) (postgres.TruthRuntimeStatus, error)
}
type truthRuntimeStatusResponse struct {
Runtime string `json:"runtime"`
Status string `json:"status"`
BundleCount int64 `json:"bundle_count"`
ClaimsByVerification map[string]int64 `json:"claims_by_verification"`
BeliefsByState map[string]int64 `json:"beliefs_by_state"`
SourcesByFreshness map[string]int64 `json:"sources_by_freshness"`
ObservedAtUnixMillis int64 `json:"observed_at_unix_millis"`
}
func truthRuntimeStatusHandler(reader truthRuntimeStatusReader) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet || r.URL.Path != "/v1/research/status" {
http.NotFound(w, r)
return
}
status, err := reader.TruthRuntimeStatus(r.Context())
if err != nil {
http.Error(w, "research status unavailable", http.StatusServiceUnavailable)
return
}
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(truthRuntimeStatusResponse{
Runtime: "research-runtime",
Status: "ready",
BundleCount: status.BundleCount,
ClaimsByVerification: status.ClaimsByVerification,
BeliefsByState: status.BeliefsByState,
SourcesByFreshness: status.SourcesByFreshness,
ObservedAtUnixMillis: status.ObservedAtUnixMillis,
})
})
}
+48
View File
@@ -0,0 +1,48 @@
package main
import (
"context"
"errors"
"net/http"
"net/http/httptest"
"strings"
"testing"
"git.cin.su/m/Assistant/adapters/postgres"
)
type fakeTruthRuntimeStatusReader struct {
status postgres.TruthRuntimeStatus
err error
}
func (f fakeTruthRuntimeStatusReader) TruthRuntimeStatus(context.Context) (postgres.TruthRuntimeStatus, error) {
return f.status, f.err
}
func TestTruthRuntimeStatusReportsEvidenceCounts(t *testing.T) {
response := httptest.NewRecorder()
truthRuntimeStatusHandler(fakeTruthRuntimeStatusReader{status: postgres.TruthRuntimeStatus{
BundleCount: 4,
ClaimsByVerification: map[string]int64{"UNVERIFIED": 3, "VERIFIED": 1},
BeliefsByState: map[string]int64{"PROVISIONAL": 4},
SourcesByFreshness: map[string]int64{"FRESH": 4},
ObservedAtUnixMillis: 1700000000000,
}}).ServeHTTP(response, httptest.NewRequest(http.MethodGet, "/v1/research/status", nil))
if response.Code != http.StatusOK {
t.Fatalf("status code = %d", response.Code)
}
for _, expected := range []string{"\"status\":\"ready\"", "\"bundle_count\":4", "\"VERIFIED\":1", "\"PROVISIONAL\":4", "1700000000000"} {
if !strings.Contains(response.Body.String(), expected) {
t.Fatalf("response missing %q: %s", expected, response.Body.String())
}
}
}
func TestTruthRuntimeStatusFailsClosedOnDatabaseError(t *testing.T) {
response := httptest.NewRecorder()
truthRuntimeStatusHandler(fakeTruthRuntimeStatusReader{err: errors.New("database unavailable")}).ServeHTTP(response, httptest.NewRequest(http.MethodGet, "/v1/research/status", nil))
if response.Code != http.StatusServiceUnavailable {
t.Fatalf("status code = %d", response.Code)
}
}
+39 -3
View File
@@ -10,9 +10,11 @@ import (
"strings"
"git.cin.su/m/Assistant/contracts/capability"
"git.cin.su/m/Assistant/contracts/work"
)
const verifierIdentity = "deterministic-task-verifier-v1"
const genericVerifierIdentity = "deterministic-generic-envelope-verifier-v1"
func main() {
mux := http.NewServeMux()
@@ -21,6 +23,7 @@ func main() {
_, _ = w.Write([]byte(`{"runtime":"task-verifier","status":"ok"}`))
})
mux.HandleFunc("POST /v1/task-evaluations/verify", verifyHandler)
mux.HandleFunc("POST /v1/work-graphs/candidates/verify", genericCandidateVerifyHandler)
addr := ":8091"
if value := strings.TrimSpace(os.Getenv("LISTEN_ADDR")); value != "" {
addr = value
@@ -30,15 +33,36 @@ func main() {
}
}
func genericCandidateVerifyHandler(w http.ResponseWriter, r *http.Request) {
var request work.GenericCandidateVerificationRequest
decoder := json.NewDecoder(http.MaxBytesReader(w, r.Body, 64<<10))
decoder.DisallowUnknownFields()
if err := decoder.Decode(&request); err != nil || request.GraphID == "" || request.GraphRevision < 1 || request.NodeID == "" || request.AuthorRunID == "" || request.CandidateID == "" || strings.TrimSpace(request.Summary) == "" || len(request.EvidenceRefs) == 0 {
http.Error(w, "invalid generic candidate envelope", http.StatusBadRequest)
return
}
payload := fmt.Sprintf("%s|%s|%d|%s|%s|%s|%s", genericVerifierIdentity, request.GraphID, request.GraphRevision, request.NodeID, request.AuthorRunID, request.CandidateID, strings.Join(evidenceRefStrings(request.EvidenceRefs), "|"))
digest := sha256.Sum256([]byte(payload))
writeJSON(w, work.GenericCandidateVerificationResponse{Verified: true, VerificationEvidenceRef: "verification:" + hex.EncodeToString(digest[:]), VerifierIdentity: genericVerifierIdentity, Summary: "candidate attribution and evidence envelope verified; semantic content remains unverified"})
}
func evidenceRefStrings(values []work.EvidenceRef) []string {
result := make([]string, len(values))
for i, value := range values {
result[i] = string(value)
}
return result
}
func verifyHandler(w http.ResponseWriter, r *http.Request) {
var request capability.TaskCaseVerificationRequest
decoder := json.NewDecoder(http.MaxBytesReader(w, r.Body, 64<<10))
decoder.DisallowUnknownFields()
if err := decoder.Decode(&request); err != nil || request.CaseID == "" || request.ResponseText == "" || request.EvidenceArtifactRef == "" || request.LatencyMicros < 0 || request.Verification.Mode != capability.TaskCaseVerificationExactText || request.Verification.ExpectedText == "" || request.Verification.MinimumQualityMillis < 0 || request.Verification.MinimumQualityMillis > 1000 {
if err := decoder.Decode(&request); err != nil || request.CaseID == "" || request.ResponseText == "" || request.EvidenceArtifactRef == "" || request.LatencyMicros < 0 || (request.Verification.Mode != capability.TaskCaseVerificationExactText && request.Verification.Mode != capability.TaskCaseVerificationJSONFieldEquals) || (request.Verification.Mode == capability.TaskCaseVerificationJSONFieldEquals && request.Verification.FieldPath == "") || request.Verification.ExpectedText == "" || request.Verification.MinimumQualityMillis < 0 || request.Verification.MinimumQualityMillis > 1000 {
http.Error(w, "invalid task verification request", http.StatusBadRequest)
return
}
matched := strings.TrimSpace(request.ResponseText) == strings.TrimSpace(request.Verification.ExpectedText)
matched := matches(request)
quality := int64(0)
result := capability.EvaluationUnsupported
if matched {
@@ -53,11 +77,23 @@ func verifyHandler(w http.ResponseWriter, r *http.Request) {
}
func verificationEvidence(request capability.TaskCaseVerificationRequest, matched bool) string {
payload := fmt.Sprintf("%s|%s|%s|%t", verifierIdentity, request.CaseID, request.EvidenceArtifactRef, matched)
payload := fmt.Sprintf("%s|%s|%s|%s|%s|%t", verifierIdentity, request.CaseID, request.EvidenceArtifactRef, request.Verification.Mode, request.Verification.FieldPath, matched)
digest := sha256.Sum256([]byte(payload))
return "verification:" + hex.EncodeToString(digest[:])
}
func matches(request capability.TaskCaseVerificationRequest) bool {
if request.Verification.Mode == capability.TaskCaseVerificationExactText {
return strings.TrimSpace(request.ResponseText) == strings.TrimSpace(request.Verification.ExpectedText)
}
var document map[string]json.RawMessage
if json.Unmarshal([]byte(request.ResponseText), &document) != nil {
return false
}
value, ok := document[request.Verification.FieldPath]
return ok && strings.TrimSpace(string(value)) == strings.TrimSpace(request.Verification.ExpectedText)
}
func writeJSON(w http.ResponseWriter, value any) {
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(value)
+22
View File
@@ -5,9 +5,11 @@ import (
"encoding/json"
"net/http"
"net/http/httptest"
"strings"
"testing"
"git.cin.su/m/Assistant/contracts/capability"
"git.cin.su/m/Assistant/contracts/work"
)
func TestVerifyExactTextProducesIndependentEvidence(t *testing.T) {
@@ -29,3 +31,23 @@ func TestVerifyMismatchIsUnsupportedWithEvidence(t *testing.T) {
t.Fatalf("unexpected mismatch response: code=%d body=%s value=%+v", recorder.Code, recorder.Body.String(), response)
}
}
func TestVerifyJSONFieldEquals(t *testing.T) {
payload, _ := json.Marshal(capability.TaskCaseVerificationRequest{CaseID: "json-1", ResponseText: `{"answer":42}`, EvidenceArtifactRef: "case-artifact", Verification: capability.TaskCaseVerification{Mode: capability.TaskCaseVerificationJSONFieldEquals, FieldPath: "answer", ExpectedText: "42", MinimumQualityMillis: 850}})
recorder := httptest.NewRecorder()
verifyHandler(recorder, httptest.NewRequest(http.MethodPost, "/v1/task-evaluations/verify", bytes.NewReader(payload)))
var response capability.TaskCaseVerificationResponse
if recorder.Code != http.StatusOK || json.NewDecoder(recorder.Body).Decode(&response) != nil || response.Result != capability.EvaluationSupported || response.QualityScoreMillis != 850 {
t.Fatalf("unexpected JSON verification response: code=%d body=%s value=%+v", recorder.Code, recorder.Body.String(), response)
}
}
func TestVerifyGenericCandidateEnvelopeDoesNotClaimTruth(t *testing.T) {
payload, _ := json.Marshal(work.GenericCandidateVerificationRequest{GraphID: "graph-1", GraphRevision: 2, NodeID: "node-1", AuthorRunID: "run-1", CandidateID: "candidate-1", Summary: "model proposal", EvidenceRefs: []work.EvidenceRef{"agent-output:1"}})
recorder := httptest.NewRecorder()
genericCandidateVerifyHandler(recorder, httptest.NewRequest(http.MethodPost, "/v1/work-graphs/candidates/verify", bytes.NewReader(payload)))
var response work.GenericCandidateVerificationResponse
if recorder.Code != http.StatusOK || json.NewDecoder(recorder.Body).Decode(&response) != nil || !response.Verified || response.VerifierIdentity == "" || response.VerificationEvidenceRef == "" || !strings.Contains(response.Summary, "semantic content remains unverified") {
t.Fatalf("unexpected generic verification response: code=%d body=%s value=%+v", recorder.Code, recorder.Body.String(), response)
}
}
+60 -4
View File
@@ -35,6 +35,7 @@ func main() {
mux := http.NewServeMux()
mux.Handle("/healthz", health.Handler{Runtime: "work-graph-runtime", SemanticGeneration: 1, ArchitectureGeneration: 1})
mux.Handle("/readyz", health.ReadinessHandler{Runtime: "work-graph-runtime", Probes: []health.Probe{{Name: "work-postgres", Check: func(ctx context.Context) error { return repository.Ping(ctx) }}}})
mux.Handle("/v1/work-graphs/status", workGraphStatusHandler(repository))
mux.HandleFunc("POST /v1/work-graphs/schedule", func(w http.ResponseWriter, r *http.Request) {
var request application.GraphScheduleRequest
if err := json.NewDecoder(http.MaxBytesReader(w, r.Body, 4<<20)).Decode(&request); err != nil {
@@ -43,6 +44,7 @@ func main() {
}
graph, ready, err := application.AdmitAndSchedule(r.Context(), repository, request)
if err != nil {
log.Printf("work graph schedule rejected: %v", err)
http.Error(w, err.Error(), http.StatusUnprocessableEntity)
return
}
@@ -56,23 +58,77 @@ func main() {
}
graph, ready, err := application.AdmitAndScheduleTaskEvaluation(r.Context(), repository, request)
if err != nil {
log.Printf("task evaluation graph schedule rejected: %v", err)
http.Error(w, err.Error(), http.StatusUnprocessableEntity)
return
}
writeJSON(w, map[string]any{"graph": graph, "scheduled_nodes": ready})
})
mux.HandleFunc("POST /v1/work-graphs/task-evaluations/additional/schedule", func(w http.ResponseWriter, r *http.Request) {
var request application.TaskEvaluationExpansionScheduleRequest
if err := json.NewDecoder(http.MaxBytesReader(w, r.Body, 4<<20)).Decode(&request); err != nil {
http.Error(w, "invalid additional task evaluation request", http.StatusBadRequest)
return
}
graph, ready, err := application.ScheduleAdditionalTaskEvaluation(r.Context(), repository, request)
if err != nil {
log.Printf("additional task evaluation schedule rejected: %v", err)
http.Error(w, err.Error(), http.StatusUnprocessableEntity)
return
}
writeJSON(w, map[string]any{"graph": graph, "scheduled_nodes": ready})
})
mux.HandleFunc("POST /v1/work-graphs/orchestration/next", func(w http.ResponseWriter, r *http.Request) {
var request work.SolutionSearchInput
if err := json.NewDecoder(http.MaxBytesReader(w, r.Body, 32<<10)).Decode(&request); err != nil {
http.Error(w, "invalid orchestration input", http.StatusBadRequest)
return
}
decision, err := application.DecideSolutionSearch(request)
if err != nil {
http.Error(w, "invalid orchestration input", http.StatusUnprocessableEntity)
return
}
writeJSON(w, decision)
})
mux.HandleFunc("POST /v1/work-graphs/orchestration/wave", func(w http.ResponseWriter, r *http.Request) {
var request work.SolutionSearchWaveRequest
if err := json.NewDecoder(http.MaxBytesReader(w, r.Body, 1<<20)).Decode(&request); err != nil {
http.Error(w, "invalid orchestration wave request", http.StatusBadRequest)
return
}
result, err := application.OrchestrateSolutionSearch(r.Context(), repository, request)
if err != nil {
http.Error(w, "invalid orchestration wave request", http.StatusUnprocessableEntity)
return
}
writeJSON(w, result)
})
mux.HandleFunc("POST /v1/work-graphs/result-callback", func(w http.ResponseWriter, r *http.Request) {
var request work.GraphResultCallback
if err := json.NewDecoder(http.MaxBytesReader(w, r.Body, 1<<20)).Decode(&request); err != nil {
http.Error(w, "invalid graph result callback", http.StatusBadRequest)
return
}
graph, err := application.RecordGraphResultCandidate(r.Context(), repository, repository, request)
if err != nil {
http.Error(w, "invalid graph result callback", http.StatusUnprocessableEntity)
return
}
writeJSON(w, graph)
})
mux.HandleFunc("POST /v1/work-graphs/complete", func(w http.ResponseWriter, r *http.Request) {
var request application.GraphCompleteRequest
if err := json.NewDecoder(http.MaxBytesReader(w, r.Body, 64<<10)).Decode(&request); err != nil {
http.Error(w, "invalid graph completion request", http.StatusBadRequest)
return
}
graph, err := application.CompleteGraphNode(r.Context(), repository, request.GraphID, request.Revision, request.NodeID, request.Succeeded)
result, err := application.CompleteGraphNodeWithReplan(r.Context(), repository, request)
if err != nil {
http.Error(w, err.Error(), http.StatusUnprocessableEntity)
return
}
writeJSON(w, graph)
writeJSON(w, result)
})
mux.HandleFunc("POST /v1/work-graphs/task-evaluations/callback", func(w http.ResponseWriter, r *http.Request) {
var request work.TaskEvaluationEvidenceCallback
@@ -93,12 +149,12 @@ func main() {
http.Error(w, "invalid graph synthesis request", http.StatusBadRequest)
return
}
conflict, synthesis, err := application.SynthesizeGraph(r.Context(), repository, request)
conflict, synthesis, graph, err := application.SynthesizeAndCompleteGraph(r.Context(), repository, repository, request)
if err != nil {
http.Error(w, err.Error(), http.StatusUnprocessableEntity)
return
}
writeJSON(w, map[string]any{"conflict": conflict, "synthesis": synthesis})
writeJSON(w, map[string]any{"conflict": conflict, "synthesis": synthesis, "graph": graph})
})
addr := os.Getenv("LISTEN_ADDR")
if addr == "" {
+45
View File
@@ -0,0 +1,45 @@
package main
import (
"context"
"encoding/json"
"net/http"
"git.cin.su/m/Assistant/adapters/postgres"
)
type workGraphStatusReader interface {
RuntimeStatus(context.Context) (postgres.WorkGraphRuntimeStatus, error)
}
type workGraphStatusResponse struct {
Runtime string `json:"runtime"`
Status string `json:"status"`
GraphsByState map[string]int64 `json:"graphs_by_state"`
NodesByState map[string]int64 `json:"nodes_by_state"`
QueueByState map[string]int64 `json:"queue_by_state"`
ObservedAtUnixMillis int64 `json:"observed_at_unix_millis"`
}
func workGraphStatusHandler(reader workGraphStatusReader) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet || r.URL.Path != "/v1/work-graphs/status" {
http.NotFound(w, r)
return
}
status, err := reader.RuntimeStatus(r.Context())
if err != nil {
http.Error(w, "work graph status unavailable", http.StatusServiceUnavailable)
return
}
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(workGraphStatusResponse{
Runtime: "work-graph-runtime",
Status: "ready",
GraphsByState: status.GraphsByState,
NodesByState: status.NodesByState,
QueueByState: status.QueueByState,
ObservedAtUnixMillis: status.ObservedAtUnixMillis,
})
})
}
+56
View File
@@ -0,0 +1,56 @@
package main
import (
"context"
"errors"
"net/http"
"net/http/httptest"
"testing"
"git.cin.su/m/Assistant/adapters/postgres"
)
type fakeWorkGraphStatusReader struct {
status postgres.WorkGraphRuntimeStatus
err error
}
func (f fakeWorkGraphStatusReader) RuntimeStatus(context.Context) (postgres.WorkGraphRuntimeStatus, error) {
return f.status, f.err
}
func TestWorkGraphStatusReportsDurableCounts(t *testing.T) {
response := httptest.NewRecorder()
workGraphStatusHandler(fakeWorkGraphStatusReader{status: postgres.WorkGraphRuntimeStatus{
GraphsByState: map[string]int64{"RUNNING": 2},
NodesByState: map[string]int64{"LEASED": 3, "SUCCEEDED": 1},
QueueByState: map[string]int64{"QUEUED": 3},
ObservedAtUnixMillis: 1700000000000,
}}).ServeHTTP(response, httptest.NewRequest(http.MethodGet, "/v1/work-graphs/status", nil))
if response.Code != http.StatusOK {
t.Fatalf("status code = %d", response.Code)
}
body := response.Body.String()
for _, expected := range []string{"\"status\":\"ready\"", "\"RUNNING\":2", "\"LEASED\":3", "\"QUEUED\":3", "1700000000000"} {
if !contains(body, expected) {
t.Fatalf("response missing %q: %s", expected, body)
}
}
}
func TestWorkGraphStatusFailsClosedOnDatabaseError(t *testing.T) {
response := httptest.NewRecorder()
workGraphStatusHandler(fakeWorkGraphStatusReader{err: errors.New("database unavailable")}).ServeHTTP(response, httptest.NewRequest(http.MethodGet, "/v1/work-graphs/status", nil))
if response.Code != http.StatusServiceUnavailable {
t.Fatalf("status code = %d", response.Code)
}
}
func contains(value, expected string) bool {
for i := 0; i+len(expected) <= len(value); i++ {
if value[i:i+len(expected)] == expected {
return true
}
}
return false
}
@@ -172,6 +172,7 @@ type ResolvedCognitiveBinding struct {
Evaluation *EvaluatedCapability
Performance *PerformanceEvidence
TaskEvaluation *TaskEvaluation
Qualification *ModelQualificationObservation
Rating *ModelRating
}
@@ -0,0 +1,47 @@
package capability
import (
"errors"
"git.cin.su/m/Assistant/platform/ids"
)
type ModelQualificationObservationID ids.ID
type ModelQualificationStatus string
const (
ModelQualificationReachable ModelQualificationStatus = "REACHABLE"
ModelQualificationRespondedWithoutRating ModelQualificationStatus = "RESPONDED_WITHOUT_QUALIFICATION"
ModelQualificationUnavailable ModelQualificationStatus = "UNAVAILABLE"
)
// ModelQualificationObservation is operational evidence only. It records
// reachability and latency, never quality, activation, routing priority or
// provider credentials.
type ModelQualificationObservation struct {
ID ModelQualificationObservationID
ProviderInstanceID ProviderInstanceID
ProviderModelID ProviderModelID
Status ModelQualificationStatus
FinishReason string
LatencyMillis int64
OutputObserved bool
QualityAssessed bool
Category ModelRatingCategory
ObservedAtUnixMillis int64
}
func (o ModelQualificationObservation) Validate() error {
if o.ID == "" || o.ProviderInstanceID == "" || o.ProviderModelID == "" || o.FinishReason == "" || o.LatencyMillis < 0 || o.ObservedAtUnixMillis < 1 {
return errors.New("model qualification observation identity and timing are incomplete")
}
switch o.Status {
case ModelQualificationReachable, ModelQualificationRespondedWithoutRating, ModelQualificationUnavailable:
default:
return errors.New("model qualification observation status is unsupported")
}
if o.Category != ModelRatingUnrated || o.QualityAssessed {
return errors.New("model qualification observation cannot assert quality")
}
return nil
}
+5 -1
View File
@@ -72,12 +72,16 @@ type TaskEvaluationExpansionRequest struct {
type TaskCaseVerificationMode string
const TaskCaseVerificationExactText TaskCaseVerificationMode = "EXACT_TEXT"
const (
TaskCaseVerificationExactText TaskCaseVerificationMode = "EXACT_TEXT"
TaskCaseVerificationJSONFieldEquals TaskCaseVerificationMode = "JSON_FIELD_EQUALS"
)
// TaskCaseVerification is a bounded, task-owned rubric. The expected text is
// test data, not model output, and is never sent to the cognitive provider.
type TaskCaseVerification struct {
Mode TaskCaseVerificationMode
FieldPath string
ExpectedText string
MinimumQualityMillis int64
}
+11
View File
@@ -96,6 +96,8 @@ type ResearchFinding struct {
Statement string
Claim Claim
Citations []Citation
Source Source
Excerpt SourceExcerpt
State ResearchFindingState
Reason string
}
@@ -131,6 +133,15 @@ type WebResearchRequest struct {
Statement string
}
// WebSearchRequest asks the research runtime to discover bounded source
// evidence for a human question. The provider is configured by the runtime;
// the caller supplies meaning, not a provider URL or model choice.
type WebSearchRequest struct {
Plan ResearchPlan
PropositionKey string
Query string
}
// ResolveCitation creates evidence only when all identifiers form one exact
// source chain. It deliberately does not fetch, infer or rewrite source text.
func ResolveCitation(source Source, snapshot SourceSnapshot, excerpt SourceExcerpt, evidence EvidenceKind, coverage CoverageState, conflict ConflictState, resolvedAtUnixMillis int64) (Citation, error) {
+1
View File
@@ -173,6 +173,7 @@ type ChatMessageResponse struct {
Clarification ClarificationState
WorkPlanned bool
PlanningReply string
ResearchBacked bool
CapabilityStatus capability.CapabilityResolutionStatus
CapabilityKind capability.CapabilityKind
CapabilityHints []capability.CapabilityKind
+49 -17
View File
@@ -21,14 +21,15 @@ const (
GraphFailed GraphState = "FAILED"
GraphUnknown GraphState = "UNKNOWN"
GraphNodeBlocked GraphNodeState = "BLOCKED_PREREQUISITE"
GraphNodeReady GraphNodeState = "READY"
GraphNodeLeased GraphNodeState = "LEASED"
GraphNodeRunning GraphNodeState = "RUNNING"
GraphNodeSuccess GraphNodeState = "SUCCEEDED"
GraphNodeFailed GraphNodeState = "FAILED"
GraphNodeCancel GraphNodeState = "CANCELLED"
GraphNodeUnknown GraphNodeState = "UNKNOWN"
GraphNodeBlocked GraphNodeState = "BLOCKED_PREREQUISITE"
GraphNodeReady GraphNodeState = "READY"
GraphNodeLeased GraphNodeState = "LEASED"
GraphNodeRunning GraphNodeState = "RUNNING"
GraphNodeVerifying GraphNodeState = "VERIFYING"
GraphNodeSuccess GraphNodeState = "SUCCEEDED"
GraphNodeFailed GraphNodeState = "FAILED"
GraphNodeCancel GraphNodeState = "CANCELLED"
GraphNodeUnknown GraphNodeState = "UNKNOWN"
)
type GraphBudget struct {
@@ -56,15 +57,17 @@ type GraphEdge struct {
}
type WorkGraph struct {
ID string `json:"id"`
Revision int64 `json:"revision"`
State GraphState `json:"state"`
Nodes []GraphNode `json:"nodes"`
Edges []GraphEdge `json:"edges"`
Budget GraphBudget `json:"budget"`
ParentGraphID string `json:"parent_graph_id,omitempty"`
ParentGraphRevision int64 `json:"parent_graph_revision,omitempty"`
TriggerEvidenceRef string `json:"trigger_evidence_ref,omitempty"`
ID string `json:"id"`
Revision int64 `json:"revision"`
State GraphState `json:"state"`
Nodes []GraphNode `json:"nodes"`
Edges []GraphEdge `json:"edges"`
Budget GraphBudget `json:"budget"`
ParentGraphID string `json:"parent_graph_id,omitempty"`
ParentGraphRevision int64 `json:"parent_graph_revision,omitempty"`
TriggerEvidenceRef string `json:"trigger_evidence_ref,omitempty"`
SearchInputBound bool `json:"search_input_bound,omitempty"`
SearchInput SolutionSearchInput `json:"search_input,omitempty"`
}
// TaskEvaluationGraphRequest describes a durable fan-out. The graph contains
@@ -110,6 +113,35 @@ type ResultCandidate struct {
Summary string `json:"summary"`
}
// GenericCandidateVerificationRequest asks the independent deterministic
// verifier to validate attribution and evidence-envelope integrity. It does
// not assert that the candidate's semantic content is true.
type GenericCandidateVerificationRequest struct {
GraphID string `json:"graph_id"`
GraphRevision int64 `json:"graph_revision"`
NodeID GraphNodeID `json:"node_id"`
AuthorRunID AgentRunID `json:"author_run_id"`
CandidateID string `json:"candidate_id"`
Summary string `json:"summary"`
EvidenceRefs []EvidenceRef `json:"evidence_refs"`
}
type GenericCandidateVerificationResponse struct {
Verified bool `json:"verified"`
VerificationEvidenceRef string `json:"verification_evidence_ref"`
VerifierIdentity string `json:"verifier_identity"`
Summary string `json:"summary"`
}
// GraphResultCallback carries one agent's attributable candidate to Work.
// Recording a candidate never makes the node successful; the node enters
// VERIFYING until an independent synthesis/verifier accepts it.
type GraphResultCallback struct {
GraphID string `json:"graph_id"`
Revision int64 `json:"revision"`
Candidate ResultCandidate `json:"candidate"`
}
type ConflictSet struct {
ID string `json:"id"`
GraphID string `json:"graph_id"`
+8
View File
@@ -83,4 +83,12 @@ type TaskEvaluationDispatchContext struct {
ActorID identity.ActorID `json:"actor_id"`
PrincipalID identity.PrincipalID `json:"principal_id"`
ScopeID authority.ScopeID `json:"scope_id"`
// GenericMessage turns the existing durable dispatch context into a
// discriminated local cognitive-work payload without adding a second
// persistence column. It is ignored by task-evaluation executors.
GenericMessage string `json:"generic_message,omitempty"`
GenericPurpose string `json:"generic_purpose,omitempty"`
GenericLocale string `json:"generic_locale,omitempty"`
GenericDataPolicy string `json:"generic_data_policy,omitempty"`
GenericMaxOutputBytes int `json:"generic_max_output_bytes,omitempty"`
}
+66
View File
@@ -0,0 +1,66 @@
package work
type OrchestrationState string
type SearchOptionKind string
const (
OrchestrationSearching OrchestrationState = "SEARCHING"
OrchestrationExecuting OrchestrationState = "EXECUTING"
OrchestrationVerifying OrchestrationState = "VERIFYING"
OrchestrationWaiting OrchestrationState = "WAITING"
OrchestrationCompleted OrchestrationState = "COMPLETED"
OrchestrationCancelled OrchestrationState = "CANCELLED"
SearchOptionAgents SearchOptionKind = "AGENT_TEAM"
SearchOptionCapability SearchOptionKind = "CAPABILITY_DISCOVERY"
SearchOptionResearch SearchOptionKind = "TRUTH_RESEARCH"
SearchOptionReflection SearchOptionKind = "REFLECTION_MEMORY"
SearchOptionHuman SearchOptionKind = "HUMAN_ATTENTION"
)
// SolutionSearchInput is the durable orchestration snapshot. The scheduler
// stores the graph/goal separately; this value only decides the next bounded
// search wave and never grants authority by itself.
type SolutionSearchInput struct {
GoalID GoalID
GoalState GoalState
SearchRound int
MaxRound int
CancelRequested bool
ActiveOptionCount int
VerificationReady bool
Unresolved bool
}
type SolutionSearchDecision struct {
GoalID GoalID
State OrchestrationState
ContinueSearching bool
NextSearchRound int
Options []SearchOptionKind
Reason string
}
type SolutionSearchWaveRequest struct {
Input SolutionSearchInput `json:"input"`
GraphID string `json:"graph_id"`
IdempotencyPrefix string `json:"idempotency_prefix"`
Dispatches []SolutionSearchDispatch `json:"dispatches"`
Budget GraphBudget `json:"budget"`
ParentGraphID string `json:"parent_graph_id,omitempty"`
ParentGraphRevision int64 `json:"parent_graph_revision,omitempty"`
TriggerEvidenceRef string `json:"trigger_evidence_ref,omitempty"`
NowUnixMillis int64 `json:"now_unix_millis"`
}
type SolutionSearchDispatch struct {
Option SearchOptionKind `json:"option"`
Intent AgentDispatchIntent `json:"intent"`
}
type SolutionSearchWaveResult struct {
Decision SolutionSearchDecision `json:"decision"`
Graph WorkGraph `json:"graph"`
ScheduledNodeIDs []GraphNodeID `json:"scheduled_node_ids,omitempty"`
GraphMaterialized bool `json:"graph_materialized"`
}
@@ -0,0 +1,21 @@
BEGIN;
SET ROLE work_role;
ALTER TABLE work.graphs ADD COLUMN IF NOT EXISTS orchestration_bound BOOLEAN NOT NULL DEFAULT FALSE;
ALTER TABLE work.graphs ADD COLUMN IF NOT EXISTS orchestration_goal_id TEXT NOT NULL DEFAULT '';
ALTER TABLE work.graphs ADD COLUMN IF NOT EXISTS orchestration_goal_state TEXT NOT NULL DEFAULT '';
ALTER TABLE work.graphs ADD COLUMN IF NOT EXISTS orchestration_search_round INTEGER NOT NULL DEFAULT 0;
ALTER TABLE work.graphs ADD COLUMN IF NOT EXISTS orchestration_max_round INTEGER NOT NULL DEFAULT 0;
ALTER TABLE work.graphs ADD COLUMN IF NOT EXISTS orchestration_cancel_requested BOOLEAN NOT NULL DEFAULT FALSE;
ALTER TABLE work.graphs ADD COLUMN IF NOT EXISTS orchestration_verification_ready BOOLEAN NOT NULL DEFAULT FALSE;
ALTER TABLE work.graphs ADD COLUMN IF NOT EXISTS orchestration_unresolved BOOLEAN NOT NULL DEFAULT FALSE;
ALTER TABLE work.graphs DROP CONSTRAINT IF EXISTS work_graphs_orchestration_snapshot_check;
ALTER TABLE work.graphs ADD CONSTRAINT work_graphs_orchestration_snapshot_check CHECK (
(NOT orchestration_bound AND orchestration_goal_id = '' AND orchestration_search_round = 0 AND orchestration_max_round = 0)
OR (orchestration_bound AND orchestration_goal_id <> '' AND orchestration_search_round >= 1 AND orchestration_max_round >= 1)
);
RESET ROLE;
COMMIT;
@@ -0,0 +1,24 @@
BEGIN;
SET ROLE capability_role;
CREATE TABLE IF NOT EXISTS capability.cognitive_model_qualification_observations (
observation_id TEXT PRIMARY KEY,
provider_instance_id TEXT NOT NULL,
provider_model_id TEXT NOT NULL,
status TEXT NOT NULL CHECK (status IN ('REACHABLE','RESPONDED_WITHOUT_QUALIFICATION','UNAVAILABLE')),
finish_reason TEXT NOT NULL,
latency_millis BIGINT NOT NULL CHECK (latency_millis >= 0),
output_observed BOOLEAN NOT NULL,
quality_assessed BOOLEAN NOT NULL DEFAULT FALSE CHECK (quality_assessed = FALSE),
category TEXT NOT NULL CHECK (category = 'UNRATED'),
observed_at_unix_millis BIGINT NOT NULL CHECK (observed_at_unix_millis > 0)
);
CREATE INDEX IF NOT EXISTS cognitive_model_qualification_lookup
ON capability.cognitive_model_qualification_observations(provider_instance_id, provider_model_id, observed_at_unix_millis DESC);
GRANT SELECT, INSERT ON capability.cognitive_model_qualification_observations TO capability_role;
RESET ROLE;
COMMIT;
+23
View File
@@ -119,12 +119,32 @@ services:
WORK_POSTGRES_PASSWORD_FILE: /run/secrets/work_db_password
SECRET_BACKEND_ROOT: /run/secrets
TASK_VERIFIER_URL: http://task-verifier:8091
AGENT_EXECUTION_BRIDGE_URL: http://agent-execution-bridge:8092
depends_on:
postgres: { condition: service_healthy }
task-verifier: { condition: service_healthy }
agent-execution-bridge: { condition: service_healthy }
volumes:
- assistant_core_secrets:/run/secrets:ro
agent-execution-bridge:
build: { context: .., dockerfile: deploy/images/runtime.Dockerfile, args: { TARGET: agent-execution-bridge, SOURCE_REVISION: "${ASSISTANT_SOURCE_REVISION:-unknown}" } }
restart: unless-stopped
environment:
LISTEN_ADDR: :8092
COGNITIVE_WORKER_URL: http://cognitive-worker:8083
WORK_GRAPH_RUNTIME_URL: http://work-graph-runtime:8088
TASK_VERIFIER_URL: http://task-verifier:8091
depends_on:
cognitive-worker: { condition: service_started }
task-verifier: { condition: service_healthy }
work-graph-runtime: { condition: service_started }
healthcheck:
test: ["CMD-SHELL", "wget -qO- http://localhost:8092/healthz >/dev/null"]
interval: 10s
timeout: 3s
retries: 10
task-verifier:
build: { context: .., dockerfile: deploy/images/runtime.Dockerfile, args: { TARGET: task-verifier, SOURCE_REVISION: "${ASSISTANT_SOURCE_REVISION:-unknown}" } }
restart: unless-stopped
@@ -175,6 +195,8 @@ services:
LISTEN_ADDR: :8087
RESEARCH_SOURCE_ROOT: /var/lib/research-sources
RESEARCH_HTTP_ALLOWED_HOSTS: assistant.cin.su,git.cin.su
RESEARCH_SEARCH_URL_TEMPLATE: ${RESEARCH_SEARCH_URL_TEMPLATE:-https://www.bing.com/search?q=%s}
RESEARCH_SEARCH_ALLOWED_HOSTS: ${RESEARCH_SEARCH_ALLOWED_HOSTS:-www.bing.com}
POSTGRES_HOST: postgres
POSTGRES_PORT: "5432"
POSTGRES_DATABASE: ${POSTGRES_DB:-assistant}
@@ -234,6 +256,7 @@ services:
LOCAL_MEDIUM_PROVIDER_URL: ${LOCAL_MEDIUM_PROVIDER_URL:-http://llm-local-api:8080/v1}
LOCAL_MEDIUM_PROVIDER_API_KEY_FILE: ${LOCAL_MEDIUM_PROVIDER_API_KEY_FILE:-}
LOCAL_MEDIUM_PROVIDER_CREDENTIAL_REF: ${LOCAL_MEDIUM_PROVIDER_CREDENTIAL_REF:-credential://domovoy/local-medium/provider-api-key/v1}
LOCAL_GPU_PROVIDER_TIMEOUT: ${LOCAL_GPU_PROVIDER_TIMEOUT:-60s}
LOCAL_LIFEBOAT_PROVIDER_URL: http://cognitive-lifeboat:8086/v1
COGNITIVE_OWNER_PRINCIPAL_ID: domovoy-creator-principal
COGNITIVE_ALLOW_CANARY: "true"
+2 -2
View File
@@ -1,5 +1,5 @@
{
"task_file": "dev/tasks/DEV-000221-task-verifier-runtime.json",
"task_id": "DEV-000221",
"task_file": "dev/tasks/DEV-000291-mobile-acceptance-evidence.json",
"task_id": "DEV-000291",
"task_revision": 1
}
@@ -0,0 +1,23 @@
{
"task_id": "DEV-000222",
"task_revision": 1,
"architecture_revision": "ADR-0018",
"base_repo_revision": "d736500d9818a7748c3715c442edb695c5aed1100",
"risk_tier": "E",
"intent": "Extend the independent task verifier with a structured JSON field mode and add a bounded case builder driven by unresolved evidence.",
"requirement_ids": ["task-aware verification", "additional cases during lifetime", "general-first bounded expansion"],
"allowed_change_scope": {"modules": ["capability", "task-verifier"], "paths": ["contracts/capability/task_evaluation.go", "cmd/task-verifier/main.go", "cmd/task-verifier/main_test.go", "modules/capability/domain/task_evaluation.go", "modules/capability/domain/task_evaluation_test.go", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000222-verifier-modes-and-case-builder.json"]},
"forbidden_change_scope": {"paths": ["architecture/**", "security/**", "database/**", "deploy/**", "cmd/core-runtime/**", "cmd/creator-web/**", "cmd/agent-worker/**", "cmd/cognitive-worker/**", "modules/identity/**", "LLM-local/**"]},
"allowed_architecture_delta": {"new_cross_module_dependencies": 0, "new_canonical_owners": 0, "new_privileges": 0, "new_third_party_dependencies": 0, "new_database_owner_roles": 0},
"contracts": {"consume": ["TaskCaseVerification", "TaskEvaluationCase", "unresolved outcome evidence"], "may_change": ["verification modes and bounded case construction" ]},
"semantic_owner": "capability owns evaluation policy; verifier remains stateless adapter",
"canonical_state": ["none; case builder returns candidates and does not persist or activate them"],
"state_transitions": ["unresolved evidence -> two bounded additional case candidates", "structured response -> verifier evidence -> supported/unsupported"],
"side_effects_and_privileges": ["no model calls, no external mutation, no secrets"],
"persistence_and_migrations": ["none"],
"failure_retry_restart": ["invalid unresolved evidence fails closed; verifier remains stateless and repeatable"],
"assumptions": ["generated cases still require an explicit verification spec before quality can be claimed"],
"acceptance_scenarios": ["JSON field match verifies with evidence", "JSON mismatch is unsupported", "unresolved evidence yields exactly two bounded probes", "generated cases do not bypass verifier or promotion gate"],
"required_tests": ["go test", "go vet", "preflight", "check-diff", "check-architecture", "independent", "git diff --check", "CI", "production public health check"],
"terminal_gap_states": ["BLOCKED_BY_MISSING_CAPABILITY", "BLOCKED_BY_SPEC_GAP"]
}
@@ -0,0 +1,23 @@
{
"task_id": "DEV-000223",
"task_revision": 1,
"architecture_revision": "ADR-0018",
"base_repo_revision": "ee68e2207eff5535c79cc594637a971cea03d71c",
"risk_tier": "E",
"intent": "Reconcile the status projection with the merged DEV-000222 verifier stage and preserve explicit remaining runtime and UI gaps.",
"requirement_ids": ["current audit evidence", "task lineage accuracy", "negative-state transparency"],
"allowed_change_scope": {"modules": ["operations-evidence"], "paths": ["docs/PROJECT_STATUS.md", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000223-status-reconciliation.json"]},
"forbidden_change_scope": {"paths": ["architecture/**", "security/**", "cmd/**", "modules/**", "contracts/**", "database/**", "deploy/**", "LLM-local/**"]},
"allowed_architecture_delta": {"new_cross_module_dependencies": 0, "new_canonical_owners": 0, "new_privileges": 0, "new_third_party_dependencies": 0, "new_database_owner_roles": 0},
"contracts": {"consume": ["merged task lineage", "existing redacted live evidence"], "may_change": ["operations status projection"]},
"semantic_owner": "operations-evidence projection",
"canonical_state": ["none; docs are rebuildable projections"],
"state_transitions": ["stale task pointer -> merged verifier stage pointer"],
"side_effects_and_privileges": ["none; documentation only"],
"persistence_and_migrations": ["none"],
"failure_retry_restart": ["historical evidence remains immutable"],
"assumptions": ["DEV-000222 main revision ee68e22 is the current deployed baseline"],
"acceptance_scenarios": ["status names DEV-000222 as last completed", "remaining bridge and current authenticated UI gaps remain explicit", "no secret value is added"],
"required_tests": ["go test", "go vet", "preflight", "check-diff", "check-architecture", "independent", "git diff --check", "CI", "production public health check"],
"terminal_gap_states": ["BLOCKED_BY_MISSING_CAPABILITY", "BLOCKED_BY_SPEC_GAP"]
}
@@ -0,0 +1,23 @@
{
"task_id": "DEV-000224",
"task_revision": 1,
"architecture_revision": "ADR-0018",
"base_repo_revision": "c4411afaaf21886cba9b5ead586afb26d697a355",
"risk_tier": "E",
"intent": "Record the current production model-fleet readiness and dynamic tier selection without claiming quality or promotion.",
"requirement_ids": ["model fleet/failover", "GPU is not intelligence priority", "truthful operational evidence"],
"allowed_change_scope": {"modules": ["operations-evidence"], "paths": ["docs/PROJECT_STATUS.md", "docs/evidence/fleet-readiness-live-2026-08-23-v2.json", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000224-live-fleet-evidence.json"]},
"forbidden_change_scope": {"paths": ["architecture/**", "security/**", "cmd/**", "modules/**", "contracts/**", "database/**", "deploy/**", "LLM-local/**"]},
"allowed_architecture_delta": {"new_cross_module_dependencies": 0, "new_canonical_owners": 0, "new_privileges": 0, "new_third_party_dependencies": 0, "new_database_owner_roles": 0},
"contracts": {"consume": ["live cognitive-worker fleet readiness response"], "may_change": ["redacted evidence and status projection"]},
"semantic_owner": "operations-evidence projection",
"canonical_state": ["none; evidence is a rebuildable projection"],
"state_transitions": ["live fleet readiness -> redacted evidence record"],
"side_effects_and_privileges": ["read-only production inspection"],
"persistence_and_migrations": ["none"],
"failure_retry_restart": ["unavailable providers remain explicit; no missing data is promoted to ready"],
"assumptions": ["fleet endpoint response is authoritative for this observation window"],
"acceptance_scenarios": ["FAST unavailable selects available MEDIUM", "HEAVY availability does not imply priority", "LIFEBOAT remains fallback", "secret values are absent"],
"required_tests": ["go test", "go vet", "preflight", "check-diff", "check-architecture", "independent", "git diff --check", "CI", "production public health and fleet live check"],
"terminal_gap_states": ["BLOCKED_BY_MISSING_CAPABILITY", "BLOCKED_BY_SPEC_GAP"]
}
+23
View File
@@ -0,0 +1,23 @@
{
"task_id": "DEV-000225",
"task_revision": 1,
"architecture_revision": "ADR-0018",
"base_repo_revision": "e080c34a772caa5d256ab9f4ae0d2568bb8a7fb9",
"risk_tier": "E",
"intent": "Serve the existing chat/login shell at the direct /login route so users can reach authentication without a 404.",
"requirement_ids": ["UI login direct route", "authenticated acceptance entrypoint"],
"allowed_change_scope": {"modules": ["creator-web"], "paths": ["cmd/creator-web/server.go", "cmd/creator-web/server_test.go", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000225-login-route-fix.json"]},
"forbidden_change_scope": {"paths": ["architecture/**", "security/**", "database/**", "deploy/**", "contracts/**", "modules/**", "LLM-local/**"]},
"allowed_architecture_delta": {"new_cross_module_dependencies": 0, "new_canonical_owners": 0, "new_privileges": 0, "new_third_party_dependencies": 0, "new_database_owner_roles": 0},
"contracts": {"consume": ["existing embedded chat/login shell"], "may_change": ["HTTP route mapping only"]},
"semantic_owner": "creator-web route projection",
"canonical_state": ["none"],
"state_transitions": ["GET /login -> existing chat shell"],
"side_effects_and_privileges": ["none; GET only"],
"persistence_and_migrations": ["none"],
"failure_retry_restart": ["route remains deterministic and stateless"],
"assumptions": ["authentication behavior remains unchanged and is handled by the existing chat shell"],
"acceptance_scenarios": ["GET /login returns 200", "login inputs are present", "root route remains unchanged", "no credential values are added"],
"required_tests": ["go test", "go vet", "preflight", "check-diff", "check-architecture", "independent", "git diff --check", "CI", "production public health and /login check"],
"terminal_gap_states": ["BLOCKED_BY_SPEC_GAP"]
}
@@ -0,0 +1,23 @@
{
"task_id": "DEV-000226",
"task_revision": 1,
"architecture_revision": "ADR-0018",
"base_repo_revision": "40b56798e23d852d26156b0bc2abbc518fd6b9b6",
"risk_tier": "E",
"intent": "Record current authenticated desktop/mobile UI and operations acceptance evidence without mutating production state.",
"requirement_ids": ["UI operations acceptance", "authenticated browser evidence", "responsive acceptance"],
"allowed_change_scope": {"modules": ["operations-evidence"], "paths": ["docs/PROJECT_STATUS.md", "docs/evidence/authenticated-ui-acceptance-2026-08-23-v2.json", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000226-ui-acceptance-evidence.json"]},
"forbidden_change_scope": {"paths": ["architecture/**", "security/**", "cmd/**", "modules/**", "contracts/**", "database/**", "deploy/**", "LLM-local/**"]},
"allowed_architecture_delta": {"new_cross_module_dependencies": 0, "new_canonical_owners": 0, "new_privileges": 0, "new_third_party_dependencies": 0, "new_database_owner_roles": 0},
"contracts": {"consume": ["current authenticated browser DOM and visual state", "live production UI responses"], "may_change": ["redacted UI acceptance evidence and status projection"]},
"semantic_owner": "operations-evidence projection",
"canonical_state": ["none; evidence is rebuildable and redacted"],
"state_transitions": ["current browser acceptance -> evidence record"],
"side_effects_and_privileges": ["read-only navigation and section inspection; no production mutation"],
"persistence_and_migrations": ["none"],
"failure_retry_restart": ["missing authenticated session or unavailable section must remain explicit"],
"assumptions": ["browser evidence reflects the deployed revision shown in the page metadata"],
"acceptance_scenarios": ["root chat authenticated", "diagnostics 7/7", "fleet 4/4 with fallback visible", "learning/reflection visible", "390x844 layout readable", "no secret values retained"],
"required_tests": ["go test", "go vet", "preflight", "check-diff", "check-architecture", "independent", "git diff --check", "CI", "production public health and authenticated browser evidence"],
"terminal_gap_states": ["BLOCKED_BY_MISSING_CAPABILITY", "BLOCKED_BY_SPEC_GAP"]
}
+47
View File
@@ -0,0 +1,47 @@
{
"task_id": "DEV-000227",
"task_revision": 1,
"architecture_revision": "ADR-0018",
"base_repo_revision": "1f6ca363ee241b41a89bff1f1b603f15e9873b00",
"risk_tier": "E",
"intent": "Make the existing bounded local task-evaluation path an explicit local executor capability while preserving truthful durable waiting for unsupported work and keeping the external bridge optional.",
"requirement_ids": ["local executor", "truthful degraded execution", "optional external bridge"],
"allowed_change_scope": {
"modules": ["agent-worker", "operations-status"],
"paths": [
"cmd/agent-worker/local_fallback.go",
"cmd/agent-worker/local_fallback_test.go",
"cmd/agent-worker/task_evaluation_executor.go",
"cmd/agent-worker/status.go",
"cmd/agent-worker/status_test.go",
"cmd/agent-worker/main.go",
"dev/ACTIVE_TASK.json",
"dev/tasks/DEV-000227-local-executor.json",
"docs/PROJECT_STATUS.md"
]
},
"forbidden_change_scope": {
"paths": ["architecture/**", "security/**", "contracts/**", "database/**", "deploy/**", "LLM-local/**"]
},
"allowed_architecture_delta": {
"new_cross_module_dependencies": 0,
"new_canonical_owners": 0,
"new_privileges": 0,
"new_third_party_dependencies": 0,
"new_database_owner_roles": 0
},
"contracts": {
"consume": ["work.AgentExecutor", "typed task-evaluation runtime endpoints"],
"may_change": ["agent-worker status projection adds local executor capability metadata"]
},
"semantic_owner": "work agent-worker runtime",
"canonical_state": ["existing durable Work dispatch, lease and callback state"],
"state_transitions": ["unsupported local request -> WAITING", "supported task evaluation -> verified callback"],
"side_effects_and_privileges": ["existing bounded internal task-evaluation calls and Work callback only; no external mutation authority"],
"persistence_and_migrations": ["none"],
"failure_retry_restart": ["unsupported work remains durable WAITING; existing lease recovery remains authoritative"],
"assumptions": ["task-evaluation execution and verification endpoints are already deployed internal capabilities", "external bridge may remain absent"],
"acceptance_scenarios": ["local status names bounded local executor and capability", "unsupported work never claims completion", "task evaluation still executes, verifies and callbacks", "external bridge status remains separate", "no secret or new privilege is introduced"],
"required_tests": ["go test ./cmd/agent-worker/...", "go vet ./cmd/agent-worker/...", "preflight", "check-diff", "check-architecture", "independent", "git diff --check", "CI", "production status smoke"],
"terminal_gap_states": ["BLOCKED_BY_MISSING_CAPABILITY", "BLOCKED_BY_SPEC_GAP"]
}
@@ -0,0 +1,23 @@
{
"task_id": "DEV-000228",
"task_revision": 1,
"architecture_revision": "ADR-0018",
"base_repo_revision": "2609f31e59d3914cdf4adcf23ec618705277d12a",
"risk_tier": "E",
"intent": "Expose a read-only, provider-neutral discovery status view so Domovoy can inspect available optional providers and models before requesting qualification, without exposing credential metadata or activating anything.",
"requirement_ids": ["controlled discovery", "provider qualification visibility", "optional external tools"],
"allowed_change_scope": {"modules": ["cognitive-worker", "operations-status"], "paths": ["cmd/cognitive-worker/discovery.go", "cmd/cognitive-worker/discovery_test.go", "cmd/cognitive-worker/main.go", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000228-controlled-discovery.json", "docs/PROJECT_STATUS.md"]},
"forbidden_change_scope": {"paths": ["architecture/**", "security/**", "contracts/**", "database/**", "deploy/**", "LLM-local/**"]},
"allowed_architecture_delta": {"new_cross_module_dependencies": 0, "new_canonical_owners": 0, "new_privileges": 0, "new_third_party_dependencies": 0, "new_database_owner_roles": 0},
"contracts": {"consume": ["existing LocalProvider discovery probes", "existing capability model metadata"], "may_change": ["read-only cognitive-worker discovery status projection"]},
"semantic_owner": "cognitive-worker observation projection",
"canonical_state": ["existing Capability discovery registration remains the canonical persisted state"],
"state_transitions": ["provider probe -> observed discovery status", "discovery registration remains DISCOVERED/INACTIVE until qualification and promotion"],
"side_effects_and_privileges": ["read-only provider probe on explicit status request; no activation, mutation or credential disclosure"],
"persistence_and_migrations": ["none"],
"failure_retry_restart": ["unavailable provider is reported with health/failure state; persisted discovery remains available through existing owner"],
"assumptions": ["provider discovery adapters already enforce bounded requests", "qualification remains a separate task-evaluation and promotion step"],
"acceptance_scenarios": ["status lists registered providers deterministically", "model inventory and probe health are visible", "credential references are not returned", "status does not activate or persist a provider", "unknown provider remains absent or unavailable"],
"required_tests": ["go test ./cmd/cognitive-worker/...", "go vet ./cmd/cognitive-worker/...", "preflight", "check-diff", "check-architecture", "independent", "git diff --check", "CI", "production status smoke"],
"terminal_gap_states": ["BLOCKED_BY_MISSING_CAPABILITY", "BLOCKED_BY_SPEC_GAP"]
}
@@ -0,0 +1,23 @@
{
"task_id": "DEV-000229",
"task_revision": 1,
"architecture_revision": "ADR-0018",
"base_repo_revision": "22f839df99d8f6abd95f00cf99d34b4b45ace692",
"risk_tier": "E",
"intent": "Connect authenticated Domovoy/Core requests to the existing bounded Truth/Research web runtime while preserving source freshness, exact citations, conflicts and read-only connector policy.",
"requirement_ids": ["Truth/Research runtime", "authenticated internet research", "source evidence"],
"allowed_change_scope": {"modules": ["core-runtime", "research-runtime-bridge"], "paths": ["cmd/core-runtime/main.go", "cmd/core-runtime/research_client.go", "cmd/core-runtime/research_client_test.go", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000229-research-runtime-bridge.json", "docs/PROJECT_STATUS.md"]},
"forbidden_change_scope": {"paths": ["architecture/**", "security/**", "contracts/**", "database/**", "deploy/**", "LLM-local/**"]},
"allowed_architecture_delta": {"new_cross_module_dependencies": 0, "new_canonical_owners": 0, "new_privileges": 0, "new_third_party_dependencies": 0, "new_database_owner_roles": 0},
"contracts": {"consume": ["existing epistemics.WebResearchRequest and ResearchResult", "existing authenticated Creator session"], "may_change": ["authenticated Core-to-research runtime adapter route"]},
"semantic_owner": "epistemics research runtime; Core is an authenticated transport boundary",
"canonical_state": ["epistemics runtime remains sole owner of persisted truth bundles, claims, beliefs and citations"],
"state_transitions": ["authenticated request -> bounded web connector -> evaluated persisted finding"],
"side_effects_and_privileges": ["read-only outbound source fetches to preconfigured allowlisted hosts; authenticated user request persists evidence in epistemics owner"],
"persistence_and_migrations": ["none"],
"failure_retry_restart": ["research runtime unavailable returns explicit gateway failure; no claim is emitted by Core"],
"assumptions": ["research runtime allowlist is configured outside code", "web connector retains exact source snapshot and does not invent citations"],
"acceptance_scenarios": ["unauthenticated research request is denied", "authenticated request reaches research runtime", "source evidence and finding are returned without fabricated claims", "research runtime remains bounded by allowlist", "no secret is forwarded or returned"],
"required_tests": ["go test ./cmd/core-runtime/...", "go vet ./cmd/core-runtime/...", "preflight", "check-diff", "check-architecture", "independent", "git diff --check", "CI", "production health and research-runtime readiness smoke"],
"terminal_gap_states": ["BLOCKED_BY_MISSING_CAPABILITY", "BLOCKED_BY_SPEC_GAP"]
}
@@ -0,0 +1,23 @@
{
"task_id": "DEV-000230",
"task_revision": 1,
"architecture_revision": "ADR-0018",
"base_repo_revision": "ec79f19a7935e1eb8f0f59c4c9ce2940b97c4855",
"risk_tier": "E",
"intent": "Expose the existing Work-owned governed development state machine through authenticated optimistic-concurrency routes without executing code, changing protected source or bypassing promotion gates.",
"requirement_ids": ["governed self-development", "durable gate progression", "canary rollback control"],
"allowed_change_scope": {"modules": ["core-runtime", "governed-development"], "paths": ["cmd/core-runtime/main.go", "cmd/core-runtime/development_gate.go", "cmd/core-runtime/development_gate_test.go", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000230-development-gate-controller.json", "docs/PROJECT_STATUS.md"]},
"forbidden_change_scope": {"paths": ["architecture/**", "security/**", "contracts/**", "database/**", "deploy/**", "LLM-local/**"]},
"allowed_architecture_delta": {"new_cross_module_dependencies": 0, "new_canonical_owners": 0, "new_privileges": 0, "new_third_party_dependencies": 0, "new_database_owner_roles": 0},
"contracts": {"consume": ["Work DevelopmentGate state machine", "authenticated Creator session", "optimistic revision persistence"], "may_change": ["authenticated Core gate controller routes"]},
"semantic_owner": "Work governed development gate",
"canonical_state": ["Work-owned development gate and existing proposal/scaffold records"],
"state_transitions": ["EVIDENCE_COLLECTED -> PROPOSAL_CREATED -> TASK_ADMITTED -> ISOLATED -> VERIFIED -> CANARY -> PROMOTED", "CANARY -> ROLLED_BACK"],
"side_effects_and_privileges": ["authenticated durable gate creation/transition only; no checkout, code execution, secret access, production mutation or automatic promotion"],
"persistence_and_migrations": ["none; existing development_gates table and repository are reused"],
"failure_retry_restart": ["optimistic revision conflict is explicit; invalid or skipped transitions are rejected; rollback remains available only from canary"],
"assumptions": ["Creator authentication is the explicit governance boundary for these operator routes", "actual worktree execution and deployment remain separate controlled systems"],
"acceptance_scenarios": ["gate creation requires evidence-collected revision one", "sequential transitions require their typed references", "verified cannot jump directly to promoted", "stale revision is rejected", "canary rollback is allowed", "unauthenticated requests are denied"],
"required_tests": ["go test ./cmd/core-runtime/...", "go vet ./cmd/core-runtime/...", "preflight", "check-diff", "check-architecture", "independent", "git diff --check", "CI", "production health smoke"],
"terminal_gap_states": ["BLOCKED_BY_MISSING_CAPABILITY", "BLOCKED_BY_SPEC_GAP"]
}
@@ -0,0 +1,23 @@
{
"task_id": "DEV-000231",
"task_revision": 1,
"architecture_revision": "ADR-0018",
"base_repo_revision": "45874f218646d7879dfcbc814782fe56bf0aa06a",
"risk_tier": "E",
"intent": "Provide a durable, parallel-first solution-search decision policy for Work Graph goals so unresolved work continues through independent agent, capability, research and reflection avenues instead of dead-ending after one bounded wave.",
"requirement_ids": ["solution orchestration", "parallel agent team", "durable retry waves", "independent verification"],
"allowed_change_scope": {"modules": ["work-graph", "work-domain"], "paths": ["contracts/work/orchestration.go", "modules/work/domain/orchestration.go", "modules/work/domain/orchestration_test.go", "modules/work/application/orchestration.go", "cmd/work-graph-runtime/main.go", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000231-solution-orchestrator.json", "docs/PROJECT_STATUS.md"]},
"forbidden_change_scope": {"paths": ["architecture/**", "security/**", "database/**", "deploy/**", "LLM-local/**", "modules/work/secrets/**"]},
"allowed_architecture_delta": {"new_cross_module_dependencies": 0, "new_canonical_owners": 0, "new_privileges": 0, "new_third_party_dependencies": 0, "new_database_owner_roles": 0},
"contracts": {"consume": ["Work Goal state", "durable Work Graph scheduling", "independent verification result"], "may_change": ["typed solution-search decision contract", "Work Graph orchestration decision endpoint"]},
"semantic_owner": "Work solution orchestration policy",
"canonical_state": ["existing Work Goal and Work Graph records; caller persists the returned wave decision"],
"state_transitions": ["SEARCHING -> EXECUTING -> VERIFYING -> COMPLETED", "SEARCHING -> WAITING -> SEARCHING", "any unresolved state -> next durable search wave", "any active state -> CANCELLED"],
"side_effects_and_privileges": ["pure policy decision and unauthenticated internal runtime route only; no provider call, secret access, code mutation, promotion or deployment"],
"persistence_and_migrations": ["none; this stage returns a typed decision for the existing durable scheduler"],
"failure_retry_restart": ["bounded wave exhaustion becomes WAITING with a new wave number and human-attention avenue; it is never represented as failure; cancellation and independently verified completion are terminal"],
"assumptions": ["the existing Work Graph dispatcher or next integration stage will persist the decision and fan out options", "model quality and provider routing remain separate capability decisions"],
"acceptance_scenarios": ["first unresolved goal selects all independent search avenues", "active wave remains EXECUTING", "exhausted wave continues with a higher durable round and human attention", "only cancellation or independently verified completion stops search", "invalid input is rejected", "the decision endpoint does not mutate source, secrets or production"],
"required_tests": ["go test ./modules/work/domain ./modules/work/application ./cmd/work-graph-runtime/...", "go vet ./modules/work/domain ./modules/work/application ./cmd/work-graph-runtime/...", "preflight", "check-diff", "check-architecture", "independent", "git diff --check", "CI", "production health smoke"],
"terminal_gap_states": ["BLOCKED_BY_MISSING_CAPABILITY", "BLOCKED_BY_SPEC_GAP"]
}
@@ -0,0 +1,23 @@
{
"task_id": "DEV-000232",
"task_revision": 1,
"architecture_revision": "ADR-0018",
"base_repo_revision": "89694047b1e6c7729b00c20a52acb5a2487e6dd6",
"risk_tier": "E",
"intent": "Connect the solution-search policy to the existing durable Work Graph scheduler so each unresolved wave is materialized as parallel dispatch intents and queued nodes.",
"requirement_ids": ["durable solution waves", "parallel fan-out", "agent/reflex orchestration", "restartable scheduling"],
"allowed_change_scope": {"modules": ["work-graph", "work-domain"], "paths": ["contracts/work/orchestration.go", "modules/work/domain/orchestration.go", "modules/work/domain/orchestration_test.go", "modules/work/application/orchestration.go", "cmd/work-graph-runtime/main.go", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000232-durable-solution-wave-dispatch.json", "docs/PROJECT_STATUS.md"]},
"forbidden_change_scope": {"paths": ["architecture/**", "security/**", "database/**", "deploy/**", "LLM-local/**", "modules/work/secrets/**"]},
"allowed_architecture_delta": {"new_cross_module_dependencies": 0, "new_canonical_owners": 0, "new_privileges": 0, "new_third_party_dependencies": 0, "new_database_owner_roles": 0},
"contracts": {"consume": ["typed solution-search decision", "existing Work Graph and dispatch queue ports"], "may_change": ["solution-search wave materialization request/result", "Work Graph orchestration wave route"]},
"semantic_owner": "Work solution orchestration and Work Graph",
"canonical_state": ["existing work.graphs, graph_nodes, dispatch_intents and dispatch_queue stores"],
"state_transitions": ["policy SEARCHING/WAITING -> graph ADMITTED/RUNNING", "each option -> one queued dispatch node", "human attention remains decision metadata"],
"side_effects_and_privileges": ["durable Work-owned graph and queue writes only; no provider call, secret access, external mutation, promotion or deployment"],
"persistence_and_migrations": ["none; existing graph and queue tables are reused"],
"failure_retry_restart": ["existing queue idempotency and graph revision persistence govern restart; invalid wave shape is rejected; no unresolved goal is marked failed by wave exhaustion"],
"assumptions": ["dispatch intents are supplied by the upstream authority-aware planner", "provider/model selection and execution remain worker responsibilities"],
"acceptance_scenarios": ["four independent solution avenues materialize as four graph nodes", "all ready nodes are queued up to graph MaxParallel", "human attention is not silently executed as an agent", "terminal policy decisions do not create a graph", "invalid or duplicate options are rejected"],
"required_tests": ["go test ./modules/work/domain ./modules/work/application ./cmd/work-graph-runtime/...", "go vet ./modules/work/domain ./modules/work/application ./cmd/work-graph-runtime/...", "preflight", "check-diff", "check-architecture", "independent", "git diff --check", "CI", "production health smoke"],
"terminal_gap_states": ["BLOCKED_BY_MISSING_CAPABILITY", "BLOCKED_BY_SPEC_GAP"]
}
@@ -0,0 +1,23 @@
{
"task_id": "DEV-000233",
"task_revision": 1,
"architecture_revision": "ADR-0018",
"base_repo_revision": "ee16103e03a5fd911119e2bc45e5904255ab7752",
"risk_tier": "E",
"intent": "Close the multi-agent Work Graph result lifecycle: persist attributable agent candidates, prevent candidate text from completing a node, and complete only after independently verified synthesis.",
"requirement_ids": ["fan-in", "independent verifier", "result provenance", "no false completion"],
"allowed_change_scope": {"modules": ["work-graph", "agent-execution", "postgres-work-adapter"], "paths": ["contracts/work/graph.go", "modules/work/ports/graph.go", "modules/work/application/graph_runtime.go", "adapters/postgres/work_repository.go", "cmd/work-graph-runtime/main.go", "tests/agent_execution/graph_runtime_test.go", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000233-graph-result-fanin-verification.json", "docs/PROJECT_STATUS.md"]},
"forbidden_change_scope": {"paths": ["architecture/**", "security/**", "database/**", "deploy/**", "LLM-local/**", "modules/work/secrets/**"]},
"allowed_architecture_delta": {"new_cross_module_dependencies": 0, "new_canonical_owners": 0, "new_privileges": 0, "new_third_party_dependencies": 0, "new_database_owner_roles": 0},
"contracts": {"consume": ["existing Work Graph revision store", "existing graph result candidate/synthesis tables", "agent callback evidence"], "may_change": ["typed graph result callback", "Work result callback route", "verified synthesis completion path"]},
"semantic_owner": "Work Graph result and verification lifecycle",
"canonical_state": ["work.graphs, work.graph_nodes, work.graph_result_candidates, work.graph_conflicts and work.graph_synthesis_candidates"],
"state_transitions": ["agent result -> durable ResultCandidate", "candidate -> no completion", "independent verified synthesis -> graph node SUCCEEDED", "all verified nodes -> graph COMPLETED"],
"side_effects_and_privileges": ["Work-owned candidate/graph persistence only; no provider call, secret access, external mutation, promotion or deployment"],
"persistence_and_migrations": ["none; existing result candidate and synthesis tables are reused"],
"failure_retry_restart": ["immutable graph revisions remain attributable; duplicate candidate IDs are idempotent at storage; missing or unverified synthesis cannot mark success"],
"assumptions": ["the verifier supplies all competing candidates and evidence in the synthesis request", "automatic replan selection remains the next stage because a graph does not yet own a persisted goal-search snapshot"],
"acceptance_scenarios": ["result callback persists candidate", "result callback leaves graph running and not completed", "verified independent synthesis advances the node", "verifier cannot be a producer", "fan-in completion requires all graph nodes successful"],
"required_tests": ["go test ./modules/work/domain ./modules/work/application ./tests/agent_execution ./cmd/work-graph-runtime/...", "go vet ./modules/work/domain ./modules/work/application ./tests/agent_execution ./cmd/work-graph-runtime/...", "preflight", "check-diff", "check-architecture", "independent", "git diff --check", "CI", "production health smoke"],
"terminal_gap_states": ["BLOCKED_BY_MISSING_CAPABILITY", "BLOCKED_BY_SPEC_GAP"]
}
@@ -0,0 +1,23 @@
{
"task_id": "DEV-000234",
"task_revision": 1,
"architecture_revision": "ADR-0018",
"base_repo_revision": "eac39a3fd40e9f8d0752af6f9e7893cb0b03055b",
"risk_tier": "E",
"intent": "Persist the solution-search snapshot with every orchestration Work Graph revision and return the next durable search decision after verified graph completion.",
"requirement_ids": ["durable goal continuity", "automatic replan signal", "restart recovery", "no invented execution authority"],
"allowed_change_scope": {"modules": ["work-graph", "postgres-work-adapter"], "paths": ["contracts/work/graph.go", "modules/work/domain/orchestration.go", "modules/work/application/graph_runtime.go", "adapters/postgres/work_repository.go", "database/migrations/044_work_graph_orchestration_snapshot.sql", "cmd/work-graph-runtime/main.go", "tests/agent_execution/graph_runtime_test.go", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000234-orchestration-snapshot-replan.json", "docs/PROJECT_STATUS.md"]},
"forbidden_change_scope": {"paths": ["architecture/**", "security/**", "deploy/**", "LLM-local/**", "modules/work/secrets/**"]},
"allowed_architecture_delta": {"new_cross_module_dependencies": 0, "new_canonical_owners": 0, "new_privileges": 0, "new_third_party_dependencies": 0, "new_database_owner_roles": 1},
"contracts": {"consume": ["Work Graph immutable revision store", "SolutionSearchDecision policy"], "may_change": ["orchestration snapshot fields on WorkGraph", "graph completion result with next search decision"]},
"semantic_owner": "Work Graph orchestration continuity",
"canonical_state": ["work.graphs revision snapshot including GoalID and search round"],
"state_transitions": ["wave materialization persists search snapshot", "verified graph completion -> next search decision", "next decision remains nonterminal when unresolved"],
"side_effects_and_privileges": ["Work-owned graph persistence and queue orchestration only; no model/provider call, secret access, external mutation or promotion"],
"persistence_and_migrations": ["migration 044 adds nullable-by-contract orchestration snapshot columns with backward-compatible defaults"],
"failure_retry_restart": ["restarting from any immutable graph revision reconstructs the same search input; missing snapshot does not invent a replan"],
"assumptions": ["dispatch-intent discovery/materialization is supplied by the next wave planner; this stage returns the durable hand-off rather than fabricating agent authority"],
"acceptance_scenarios": ["new solution wave persists goal and round", "verified completion returns next round decision", "cancelled or verified terminal goal does not replan", "legacy graph without snapshot completes without invented replan"],
"required_tests": ["go test ./modules/work/domain ./modules/work/application ./tests/agent_execution ./cmd/work-graph-runtime/...", "go vet ./modules/work/domain ./modules/work/application ./tests/agent_execution ./cmd/work-graph-runtime/...", "preflight", "check-diff", "check-architecture", "independent", "git diff --check", "CI", "production health smoke"],
"terminal_gap_states": ["BLOCKED_BY_MISSING_CAPABILITY", "BLOCKED_BY_SPEC_GAP", "REQUIRES_MIGRATION_DECISION"]
}
@@ -0,0 +1,23 @@
{
"task_id": "DEV-000235",
"task_revision": 1,
"architecture_revision": "ADR-0018",
"base_repo_revision": "60e8292c1df947b887079413b3b35ce8b4212086",
"risk_tier": "E",
"intent": "Add a bounded provider-neutral model qualification probe that discovers actual model IDs, invokes them in parallel, records reachability and latency, and never overclaims quality or hardware superiority.",
"requirement_ids": ["model fleet/failover", "dynamic model discovery", "quality separated from reachability", "parallel qualification"],
"allowed_change_scope": {"modules": ["cognitive-worker", "capability-observation"], "paths": ["cmd/cognitive-worker/main.go", "cmd/cognitive-worker/model_qualification.go", "cmd/cognitive-worker/model_qualification_test.go", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000235-model-qualification-probe.json", "docs/PROJECT_STATUS.md"]},
"forbidden_change_scope": {"paths": ["architecture/**", "security/**", "database/**", "deploy/**", "LLM-local/**", "modules/work/secrets/**"]},
"allowed_architecture_delta": {"new_cross_module_dependencies": 0, "new_canonical_owners": 0, "new_privileges": 0, "new_third_party_dependencies": 0, "new_database_owner_roles": 0},
"contracts": {"consume": ["OpenAI-compatible LocalProvider discovery/advisory adapter", "Capability model rating categories"], "may_change": ["internal bounded provider qualification route"]},
"semantic_owner": "Capability cognitive runtime observation",
"canonical_state": ["none; probe response is an operational observation and does not promote a binding"],
"state_transitions": ["DISCOVERED model -> bounded probe -> REACHABLE/UNAVAILABLE/RESPONDED_WITHOUT_QUALIFICATION", "quality remains UNRATED until independent task tests"],
"side_effects_and_privileges": ["read-only provider model discovery and bounded chat invocation; no external mutation, secret exposure, activation, promotion or routing priority"],
"persistence_and_migrations": ["none; existing discovery/observation and promotion flows remain unchanged"],
"failure_retry_restart": ["each probe has provider adapter timeout; one model failure does not cancel sibling probes; rerun is bounded and idempotent in effect"],
"assumptions": ["provider credentials are resolved inside cognitive-worker protected runtime", "additional task-specific tests can be added later without changing the primary probe"],
"acceptance_scenarios": ["actual discovered model IDs are used instead of static placeholders", "models are probed in parallel", "successful reachability reports latency", "quality is explicitly UNRATED", "GPU presence does not alter category or promotion", "output bound and model selection are constrained"],
"required_tests": ["go test ./cmd/cognitive-worker/... ./modules/capability/domain ./adapters/models/...", "go vet ./cmd/cognitive-worker/... ./modules/capability/domain ./adapters/models/...", "preflight", "check-diff", "check-architecture", "independent", "git diff --check", "CI", "production health smoke"],
"terminal_gap_states": ["BLOCKED_BY_MISSING_CAPABILITY", "BLOCKED_BY_SPEC_GAP"]
}
@@ -0,0 +1,23 @@
{
"task_id": "DEV-000236",
"task_revision": 1,
"architecture_revision": "ADR-0018",
"risk_tier": "E",
"base_repo_revision": "b20611590a075e3574efddbdba6e1ace312d11a8",
"intent": "Make the bounded model qualification invocation satisfy the canonical advisory contract and keep a regression test for the required locale.",
"requirement_ids": ["model qualification smoke", "advisory contract"],
"allowed_change_scope": {"modules": ["cognitive-worker"], "paths": ["cmd/cognitive-worker/model_qualification.go", "cmd/cognitive-worker/model_qualification_test.go", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000236-qualification-locale-fix.json"]},
"forbidden_change_scope": {"paths": ["architecture/**", "security/**", "database/**", "deploy/**", "LLM-local/**", "modules/work/secrets/**", "contracts/**"]},
"allowed_architecture_delta": {"new_cross_module_dependencies": 0, "new_canonical_owners": 0, "new_privileges": 0, "new_third_party_dependencies": 0, "new_database_owner_roles": 0},
"contracts": {"consume": ["Creator advisory request validation"], "may_change": []},
"semantic_owner": "Cognitive worker qualification observation",
"canonical_state": ["none; qualification remains an operational observation"],
"state_transitions": ["invalid advisory invocation -> valid bounded invocation"],
"side_effects_and_privileges": ["read-only bounded provider invocation; no promotion or routing mutation"],
"persistence_and_migrations": ["none"],
"failure_retry_restart": ["one bounded probe failure remains isolated"],
"assumptions": ["provider credentials remain protected inside cognitive-worker"],
"acceptance_scenarios": ["live qualification invocation passes advisory validation", "quality remains UNRATED"],
"required_tests": ["go test ./cmd/cognitive-worker/...", "go vet ./cmd/cognitive-worker/...", "preflight", "check-diff", "check-architecture", "independent", "git diff --check", "CI", "production health smoke"],
"terminal_gap_states": ["BLOCKED_BY_MISSING_CAPABILITY", "BLOCKED_BY_SPEC_GAP"]
}
@@ -0,0 +1,23 @@
{
"task_id": "DEV-000237",
"task_revision": 1,
"architecture_revision": "ADR-0018",
"base_repo_revision": "be01b6b883c2ce6e9bb47bc851505fe222c46d14",
"risk_tier": "E",
"intent": "Record current authenticated quick acceptance for the five creator-console sections without mutating production state.",
"requirement_ids": ["WP-800", "WP-804", "authenticated UI quick acceptance"],
"allowed_change_scope": {"modules": ["creator-web", "evidence"], "paths": ["docs/evidence/authenticated-ui-section-smoke-2026-08-23-v1.json", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000237-ui-authenticated-section-smoke.json"]},
"forbidden_change_scope": {"paths": ["architecture/**", "security/**", "database/**", "deploy/**", "LLM-local/**", "modules/work/secrets/**", "contracts/**"]},
"allowed_architecture_delta": {"new_cross_module_dependencies": 0, "new_canonical_owners": 0, "new_privileges": 0, "new_third_party_dependencies": 0, "new_database_owner_roles": 0},
"contracts": {"consume": ["authenticated creator-console UI"], "may_change": []},
"semantic_owner": "Creator-web acceptance evidence",
"canonical_state": ["evidence only; no runtime state mutation"],
"state_transitions": ["unverified current section -> observed authenticated quick acceptance"],
"side_effects_and_privileges": ["read-only browser inspection; no mutation"],
"persistence_and_migrations": ["evidence artifact only"],
"failure_retry_restart": ["recheck is idempotent and does not change product state"],
"assumptions": ["the authenticated browser session belongs to the authorized creator"],
"acceptance_scenarios": ["five admin sections load authenticated", "no visible section error is present", "full mobile/accessibility/chaos acceptance remains explicitly unclaimed"],
"required_tests": ["go test ./cmd/creator-web/...", "go vet ./cmd/creator-web/...", "go run ./cmd/devctl preflight -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-diff -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-architecture", "git diff --check", "independent", "CI", "production health smoke"],
"terminal_gap_states": ["BLOCKED_BY_MISSING_CAPABILITY", "BLOCKED_BY_SPEC_GAP"]
}
@@ -0,0 +1,23 @@
{
"task_id": "DEV-000238",
"task_revision": 1,
"architecture_revision": "ADR-0018",
"base_repo_revision": "0609561dfefb6dce302f56d50bb56bd5a222fb64",
"risk_tier": "E",
"intent": "Record a bounded live proof that the advisory gateway keeps a local lifeboat path when the normal model route cannot be used.",
"requirement_ids": ["Agent Execution local recovery", "model fleet fallback", "false completion prevention"],
"allowed_change_scope": {"modules": ["evidence"], "paths": ["docs/evidence/local-fallback-gateway-live-2026-08-23-v1.json", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000238-local-fallback-evidence.json"]},
"forbidden_change_scope": {"paths": ["architecture/**", "security/**", "database/**", "deploy/**", "LLM-local/**", "modules/work/secrets/**", "contracts/**"]},
"allowed_architecture_delta": {"new_cross_module_dependencies": 0, "new_canonical_owners": 0, "new_privileges": 0, "new_third_party_dependencies": 0, "new_database_owner_roles": 0},
"contracts": {"consume": ["creator advisory gateway", "durable cognitive usage observation"], "may_change": []},
"semantic_owner": "Evidence only",
"canonical_state": ["no runtime state mutation; read-only evidence capture"],
"state_transitions": ["normal route unavailable or not eligible -> builtin local lifeboat response"],
"side_effects_and_privileges": ["bounded local advisory request; no external egress and no secret exposure"],
"persistence_and_migrations": ["existing cognitive usage observation only"],
"failure_retry_restart": ["live request is bounded and idempotent in effect"],
"assumptions": ["the production database observation is the authoritative route attribution"],
"acceptance_scenarios": ["local response is COMPLETED", "usage evidence identifies builtin-local-lifeboat-v1", "no claim of external bridge or multi-model failover"],
"required_tests": ["go test ./cmd/core-runtime/...", "go vet ./cmd/core-runtime/...", "go run ./cmd/devctl preflight -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-diff -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-architecture", "git diff --check", "independent", "CI", "production health smoke"],
"terminal_gap_states": ["BLOCKED_BY_MISSING_CAPABILITY", "BLOCKED_BY_SPEC_GAP"]
}
@@ -0,0 +1,23 @@
{
"task_id": "DEV-000239",
"task_revision": 1,
"architecture_revision": "ADR-0018",
"base_repo_revision": "6061eb5c13e46cec1e60fc104b022394f0d94dee",
"risk_tier": "E",
"intent": "Resolve a registered provider by its stable instance identity when a persisted Capability revision contains stale non-secret endpoint credential metadata, while preserving exact metadata matching first.",
"requirement_ids": ["model fleet/failover", "credential rotation compatibility", "local fallback attribution"],
"allowed_change_scope": {"modules": ["cognitive-worker"], "paths": ["cmd/cognitive-worker/gateway.go", "cmd/cognitive-worker/gateway_registry_test.go", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000239-stable-provider-resolution.json"]},
"forbidden_change_scope": {"paths": ["architecture/**", "security/**", "database/**", "deploy/**", "LLM-local/**", "modules/work/secrets/**", "contracts/**"]},
"allowed_architecture_delta": {"new_cross_module_dependencies": 0, "new_canonical_owners": 0, "new_privileges": 0, "new_third_party_dependencies": 0, "new_database_owner_roles": 0},
"contracts": {"consume": ["Capability ResolvedCognitiveBinding", "protected provider registration"], "may_change": ["internal provider lookup fallback order"]},
"semantic_owner": "Cognitive worker provider adapter boundary",
"canonical_state": ["no new durable state"],
"state_transitions": ["exact provider metadata match -> stable provider identity match -> no provider"],
"side_effects_and_privileges": ["provider lookup only; secret values remain in protected runtime"],
"persistence_and_migrations": ["none"],
"failure_retry_restart": ["missing provider remains explicit and uses existing builtin fallback"],
"assumptions": ["ProviderInstanceID is the stable Capability-owned provider identity"],
"acceptance_scenarios": ["stale credential metadata still reaches current registered adapter", "exact metadata match retains priority", "no provider remains nil"],
"required_tests": ["go test ./cmd/cognitive-worker/...", "go vet ./cmd/cognitive-worker/...", "go run ./cmd/devctl preflight -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-diff -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-architecture", "git diff --check", "independent", "CI", "production health smoke"],
"terminal_gap_states": ["BLOCKED_BY_MISSING_CAPABILITY", "BLOCKED_BY_SPEC_GAP"]
}
@@ -0,0 +1,23 @@
{
"task_id": "DEV-000240",
"task_revision": 1,
"architecture_revision": "ADR-0018",
"base_repo_revision": "383d4c6e4fd53bb0c34ed358e0d1be6a416da6cc",
"risk_tier": "E",
"intent": "Record live durable evidence of CPU model failure followed by successful local Lifeboat failover after stable provider resolution was deployed.",
"requirement_ids": ["model fleet/failover", "durable usage evidence", "false completion prevention"],
"allowed_change_scope": {"modules": ["evidence"], "paths": ["docs/evidence/cpu-lifeboat-failover-2026-08-23-v1.json", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000240-cpu-lifeboat-failover-evidence.json"]},
"forbidden_change_scope": {"paths": ["architecture/**", "security/**", "database/**", "deploy/**", "LLM-local/**", "modules/work/secrets/**", "contracts/**"]},
"allowed_architecture_delta": {"new_cross_module_dependencies": 0, "new_canonical_owners": 0, "new_privileges": 0, "new_third_party_dependencies": 0, "new_database_owner_roles": 0},
"contracts": {"consume": ["cognitive advisory gateway", "cognitive usage observations"], "may_change": []},
"semantic_owner": "Evidence only",
"canonical_state": ["no runtime state mutation; evidence captures existing durable observations"],
"state_transitions": ["CPU MODEL_UNAVAILABLE -> builtin local Lifeboat COMPLETED"],
"side_effects_and_privileges": ["bounded LOCAL_ONLY advisory request; no external egress"],
"persistence_and_migrations": ["existing usage observation persistence only"],
"failure_retry_restart": ["the fallback is selected within the bounded gateway request"],
"assumptions": ["provider_instance_id and result_class in capability usage observations are authoritative route attribution"],
"acceptance_scenarios": ["primary failure is durably observed", "fallback completion is durably observed", "HTTP gateway returns COMPLETED", "public health remains OK"],
"required_tests": ["go test ./cmd/cognitive-worker/...", "go vet ./cmd/cognitive-worker/...", "go run ./cmd/devctl preflight -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-diff -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-architecture", "git diff --check", "independent", "CI", "production health smoke"],
"terminal_gap_states": ["BLOCKED_BY_MISSING_CAPABILITY", "BLOCKED_BY_SPEC_GAP"]
}
@@ -0,0 +1,23 @@
{
"task_id": "DEV-000241",
"task_revision": 1,
"architecture_revision": "ADR-0018",
"base_repo_revision": "ca0d0020739d8f70e2a3ac04820e32794072ab30",
"risk_tier": "E",
"intent": "Record bounded medium and GPU qualification observations without treating hardware presence or reachability as quality or promotion evidence.",
"requirement_ids": ["model fleet inventory", "provider-neutral qualification", "hardware-neutral routing"],
"allowed_change_scope": {"modules": ["evidence"], "paths": ["docs/evidence/fleet-qualification-2026-08-23-v1.json", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000241-fleet-qualification-evidence.json"]},
"forbidden_change_scope": {"paths": ["architecture/**", "security/**", "database/**", "deploy/**", "LLM-local/**", "modules/work/secrets/**", "contracts/**"]},
"allowed_architecture_delta": {"new_cross_module_dependencies": 0, "new_canonical_owners": 0, "new_privileges": 0, "new_third_party_dependencies": 0, "new_database_owner_roles": 0},
"contracts": {"consume": ["provider discovery status", "bounded qualification response"], "may_change": []},
"semantic_owner": "Evidence only",
"canonical_state": ["no runtime state mutation"],
"state_transitions": ["discovered model -> bounded reachability observation -> UNRATED result"],
"side_effects_and_privileges": ["bounded provider invocation; no promotion, activation or routing mutation"],
"persistence_and_migrations": ["evidence artifact only"],
"failure_retry_restart": ["one model timeout does not cancel or misclassify other tier evidence"],
"assumptions": ["provider discovery IDs are the exact IDs returned by the live worker"],
"acceptance_scenarios": ["medium reachability records latency", "GPU timeout is explicit", "both results remain UNRATED", "GPU does not receive an implicit priority"],
"required_tests": ["go test ./cmd/cognitive-worker/...", "go vet ./cmd/cognitive-worker/...", "go run ./cmd/devctl preflight -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-diff -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-architecture", "git diff --check", "independent", "CI", "production health smoke"],
"terminal_gap_states": ["BLOCKED_BY_MISSING_CAPABILITY", "BLOCKED_BY_SPEC_GAP"]
}
@@ -0,0 +1,23 @@
{
"task_id": "DEV-000242",
"task_revision": 1,
"architecture_revision": "ADR-0018",
"base_repo_revision": "e4ba1cb5fabf930396b2aa9f95d3b4b205e6319d",
"risk_tier": "E",
"intent": "Persist provider-neutral model qualification observations through the Capability owner while keeping quality unrated until independent task evaluation.",
"requirement_ids": ["durable qualification evidence", "model fleet/failover", "quality separated from reachability"],
"allowed_change_scope": {"modules": ["capability", "core-runtime", "cognitive-worker", "database"], "paths": ["adapters/postgres/capability_repository.go", "cmd/cognitive-worker/main.go", "cmd/cognitive-worker/model_qualification.go", "cmd/cognitive-worker/model_qualification_test.go", "cmd/core-runtime/main.go", "contracts/capability/model_qualification.go", "database/migrations/045_capability_model_qualification_observations.sql", "modules/capability/ports/repository.go", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000242-durable-qualification-observations.json"]},
"forbidden_change_scope": {"paths": ["architecture/**", "security/**", "deploy/**", "LLM-local/**", "modules/work/secrets/**", "contracts/work/**"]},
"allowed_architecture_delta": {"new_cross_module_dependencies": 0, "new_canonical_owners": 0, "new_privileges": 0, "new_third_party_dependencies": 0, "new_database_owner_roles": 1},
"contracts": {"consume": ["bounded qualification result", "Capability repository boundary"], "may_change": ["typed model qualification observation contract", "internal core persistence route"]},
"semantic_owner": "Capability owns qualification evidence; cognitive-worker produces observations",
"canonical_state": ["cognitive_model_qualification_observations"],
"state_transitions": ["probe -> REACHABLE/RESPONDED_WITHOUT_QUALIFICATION/UNAVAILABLE; category remains UNRATED"],
"side_effects_and_privileges": ["bounded provider call and capability-owned INSERT; no promotion, activation or secret persistence"],
"persistence_and_migrations": ["migration 045 creates capability-owned observation table and index"],
"failure_retry_restart": ["observation persistence failure returns bounded gateway error; duplicate IDs are idempotent"],
"assumptions": ["independent task evaluation remains the only quality/promotion evidence"],
"acceptance_scenarios": ["qualification route persists one observation per probed model", "invalid quality/category is rejected", "prompt/output/error text is not persisted", "existing qualification tests remain valid"],
"required_tests": ["go test ./cmd/cognitive-worker/... ./cmd/core-runtime/... ./modules/capability/... ./adapters/postgres/...", "go vet ./cmd/cognitive-worker/... ./cmd/core-runtime/... ./modules/capability/... ./adapters/postgres/...", "go run ./cmd/devctl preflight -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-diff -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-architecture", "git diff --check", "independent", "CI", "production health smoke"],
"terminal_gap_states": ["BLOCKED_BY_MISSING_CAPABILITY", "BLOCKED_BY_SPEC_GAP", "REQUIRES_MIGRATION_DECISION"]
}
@@ -0,0 +1,23 @@
{
"task_id": "DEV-000243",
"task_revision": 1,
"architecture_revision": "ADR-0018",
"base_repo_revision": "47327cc6d853da9084c96e3bf48502266706ca57",
"risk_tier": "E",
"intent": "Make Capability selection consume the latest durable qualification observation for fresh unavailability exclusion and transparent latency rating, without granting quality or activation.",
"requirement_ids": ["qualification selection loop", "hardware-neutral routing", "quality separated from reachability"],
"allowed_change_scope": {"modules": ["capability", "adapters/postgres"], "paths": ["adapters/postgres/capability_repository.go", "contracts/capability/cognitive_registry.go", "modules/capability/domain/cognitive_registry.go", "modules/capability/domain/model_rating_test.go", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000243-qualification-selection-evidence.json"]},
"forbidden_change_scope": {"paths": ["architecture/**", "security/**", "database/**", "deploy/**", "LLM-local/**", "modules/work/secrets/**", "contracts/work/**"]},
"allowed_architecture_delta": {"new_cross_module_dependencies": 0, "new_canonical_owners": 0, "new_privileges": 0, "new_third_party_dependencies": 0, "new_database_owner_roles": 0},
"contracts": {"consume": ["durable ModelQualificationObservation"], "may_change": ["resolved binding evidence projection"]},
"semantic_owner": "Capability selection and rating",
"canonical_state": ["qualification observation remains evidence; no activation mutation"],
"state_transitions": ["fresh UNAVAILABLE observation -> candidate excluded", "stale UNAVAILABLE observation -> discovery state remains authoritative", "REACHABLE observation -> latency evidence only"],
"side_effects_and_privileges": ["read-only projection and deterministic selection"],
"persistence_and_migrations": ["uses migration 045 from DEV-000242"],
"failure_retry_restart": ["missing observation preserves existing behavior"],
"assumptions": ["provider/model observed timestamps define freshness boundary"],
"acceptance_scenarios": ["fresh unavailable candidate is not selected", "stale unavailable candidate is not permanently poisoned", "quality remains task-evaluation controlled"],
"required_tests": ["go test ./modules/capability/... ./adapters/postgres/...", "go vet ./modules/capability/... ./adapters/postgres/...", "go run ./cmd/devctl preflight -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-diff -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-architecture", "git diff --check", "independent", "CI", "production health smoke"],
"terminal_gap_states": ["BLOCKED_BY_MISSING_CAPABILITY", "BLOCKED_BY_SPEC_GAP"]
}
@@ -0,0 +1,26 @@
{
"task_id": "DEV-000244",
"task_revision": 1,
"architecture_revision": "ADR-0018",
"base_repo_revision": "c81e2269e157c1a41b567d79d93bbbe49dd4b134",
"risk_tier": "E",
"intent": "Expose bounded health and status evidence for the durable reflection worker without changing reflection semantics or authority.",
"requirement_ids": ["learning reflection loop", "runtime observability", "fast operational verification"],
"allowed_change_scope": {
"modules": ["epistemics"],
"paths": ["cmd/reflection-worker/**", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000244-reflection-worker-observability.json"]
},
"forbidden_change_scope": {"paths": ["architecture/**", "security/**", "database/**", "LLM-local/**", "modules/work/secrets/**", "contracts/**"]},
"allowed_architecture_delta": {"new_cross_module_dependencies": 0, "new_canonical_owners": 0, "new_privileges": 0, "new_third_party_dependencies": 0, "new_database_owner_roles": 0},
"contracts": {"consume": ["existing reflection repository port", "runtime health conventions"], "may_change": ["worker operational HTTP surface"]},
"semantic_owner": "Epistemics reflection worker runtime",
"canonical_state": ["none; status is derived operational evidence"],
"state_transitions": ["cycle start/complete/failure are reported without changing candidate lifecycle rules"],
"side_effects_and_privileges": ["read-only health/status endpoint; existing repository writes unchanged"],
"persistence_and_migrations": ["none"],
"failure_retry_restart": ["status resets on process restart; durable candidate state remains authoritative"],
"assumptions": ["internal production network is sufficient for worker status checks"],
"acceptance_scenarios": ["health endpoint returns 200", "status exposes loop mode and latest cycle", "reflection loop still resolves candidates"],
"required_tests": ["go test ./cmd/reflection-worker", "go vet ./cmd/reflection-worker", "go run ./cmd/devctl preflight -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-diff -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-architecture", "git diff --check", "independent", "CI", "production health smoke"],
"terminal_gap_states": ["BLOCKED_BY_MISSING_CAPABILITY", "BLOCKED_BY_SPEC_GAP"]
}
@@ -0,0 +1,23 @@
{
"task_id": "DEV-000245",
"task_revision": 1,
"architecture_revision": "ADR-0018",
"base_repo_revision": "ea90bf0fabea43b735dcb901a2da2733db216369",
"risk_tier": "E",
"intent": "Expose live durable agent-worker cycle and recovery evidence through the existing internal status surface without changing dispatch, lease, executor, or canonical semantics.",
"requirement_ids": ["Agent Execution runtime", "recovery observability", "fast operational verification"],
"allowed_change_scope": {"modules": ["work"], "paths": ["cmd/agent-worker/**", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000245-agent-worker-recovery-status.json"]},
"forbidden_change_scope": {"paths": ["architecture/**", "security/**", "database/**", "deploy/**", "contracts/**", "LLM-local/**", "modules/work/secrets/**"]},
"allowed_architecture_delta": {"new_cross_module_dependencies": 0, "new_canonical_owners": 0, "new_privileges": 0, "new_third_party_dependencies": 0, "new_database_owner_roles": 0},
"contracts": {"consume": ["existing WorkerCycleResult", "existing worker status HTTP surface"], "may_change": ["derived worker status response fields"]},
"semantic_owner": "Work agent-worker runtime observability",
"canonical_state": ["none; status is derived operational evidence"],
"state_transitions": ["cycle start -> completed/failed status only"],
"side_effects_and_privileges": ["read-only internal status endpoint; no new privilege"],
"persistence_and_migrations": ["none"],
"failure_retry_restart": ["status is process-local evidence and resets on restart; durable recovery remains repository-owned"],
"assumptions": ["existing status address is reachable from creator-web and internal diagnostics"],
"acceptance_scenarios": ["status reports cycle completion", "status reports recovered claims", "cycle error is visible without exposing secrets", "existing executor mode remains accurate"],
"required_tests": ["go test ./cmd/agent-worker", "go vet ./cmd/agent-worker", "go run ./cmd/devctl preflight -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-diff -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-architecture", "git diff --check", "independent", "CI", "production health smoke"],
"terminal_gap_states": ["BLOCKED_BY_MISSING_CAPABILITY", "BLOCKED_BY_SPEC_GAP"]
}
@@ -0,0 +1,23 @@
{
"task_id": "DEV-000246",
"task_revision": 1,
"architecture_revision": "ADR-0018",
"base_repo_revision": "355ba7db5621a11b2c4e0827d9301ab7fdb2d419",
"risk_tier": "E",
"intent": "Expose read-only durable Work Graph runtime counts for operational verification without changing graph scheduling, fan-out/fan-in, verifier, or replan semantics.",
"requirement_ids": ["Multi-Agent Work Graph", "fan-out/fan-in observability", "fast operational verification"],
"allowed_change_scope": {"modules": ["work"], "paths": ["adapters/postgres/work_repository.go", "cmd/work-graph-runtime/**", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000246-work-graph-runtime-status.json"]},
"forbidden_change_scope": {"paths": ["architecture/**", "security/**", "database/**", "deploy/**", "contracts/**", "LLM-local/**", "modules/work/secrets/**"]},
"allowed_architecture_delta": {"new_cross_module_dependencies": 0, "new_canonical_owners": 0, "new_privileges": 0, "new_third_party_dependencies": 0, "new_database_owner_roles": 0},
"contracts": {"consume": ["work-owned graph and dispatch queue tables", "existing runtime health conventions"], "may_change": ["internal read-only status response"]},
"semantic_owner": "Work Graph runtime observability",
"canonical_state": ["none; counts are derived projections"],
"state_transitions": ["none"],
"side_effects_and_privileges": ["read-only aggregate queries only"],
"persistence_and_migrations": ["none"],
"failure_retry_restart": ["status fails closed with 503 when work database is unavailable"],
"assumptions": ["status is consumed by internal diagnostics and can be queried from the production network"],
"acceptance_scenarios": ["readyz remains database-backed", "status returns graph state and queue counts", "status does not mutate canonical state"],
"required_tests": ["go test ./cmd/work-graph-runtime ./adapters/postgres", "go vet ./cmd/work-graph-runtime ./adapters/postgres", "go run ./cmd/devctl preflight -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-diff -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-architecture", "git diff --check", "independent", "CI", "production health smoke"],
"terminal_gap_states": ["BLOCKED_BY_MISSING_CAPABILITY", "BLOCKED_BY_SPEC_GAP"]
}
@@ -0,0 +1,23 @@
{
"task_id": "DEV-000247",
"task_revision": 1,
"architecture_revision": "ADR-0018",
"base_repo_revision": "e70be0103a03c31f7ecea851fd1aeabcb7abcc55",
"risk_tier": "E",
"intent": "Expose read-only Truth/Research evidence counts from durable truth bundles without changing epistemic evaluation or belief semantics.",
"requirement_ids": ["Truth/Research runtime", "Observation-Claim-Belief evidence visibility", "fast operational verification"],
"allowed_change_scope": {"modules": ["epistemics"], "paths": ["adapters/postgres/epistemics_repository.go", "cmd/research-runtime/**", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000247-research-runtime-status.json"]},
"forbidden_change_scope": {"paths": ["architecture/**", "security/**", "database/**", "deploy/**", "contracts/**", "LLM-local/**", "modules/work/secrets/**"]},
"allowed_architecture_delta": {"new_cross_module_dependencies": 0, "new_canonical_owners": 0, "new_privileges": 0, "new_third_party_dependencies": 0, "new_database_owner_roles": 0},
"contracts": {"consume": ["epistemics.truth_bundles", "existing research health conventions"], "may_change": ["internal read-only status response"]},
"semantic_owner": "Epistemics Truth/Research runtime observability",
"canonical_state": ["none; counts are derived projections"],
"state_transitions": ["none"],
"side_effects_and_privileges": ["read-only aggregate queries only"],
"persistence_and_migrations": ["none"],
"failure_retry_restart": ["status fails closed with 503 when epistemics database is unavailable"],
"assumptions": ["truth bundle migration is present in production"],
"acceptance_scenarios": ["readyz remains database-backed", "status reports bundle, claim verification, belief and freshness counts", "status does not mutate epistemic state"],
"required_tests": ["go test ./cmd/research-runtime ./adapters/postgres", "go vet ./cmd/research-runtime ./adapters/postgres", "go run ./cmd/devctl preflight -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-diff -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-architecture", "git diff --check", "independent", "CI", "production health smoke"],
"terminal_gap_states": ["BLOCKED_BY_MISSING_CAPABILITY", "BLOCKED_BY_SPEC_GAP"]
}
@@ -0,0 +1,23 @@
{
"task_id": "DEV-000248",
"task_revision": 1,
"architecture_revision": "ADR-0018",
"base_repo_revision": "cdc72e17be46c843737eabcd654cb69ae32d6268",
"risk_tier": "E",
"intent": "Diagnose and repair the live Work Graph task-evaluation scheduling path exposed by the production canary, preserving durable fan-out and callback semantics.",
"requirement_ids": ["live task evaluation", "model quality qualification", "Work Graph integration"],
"allowed_change_scope": {"modules": ["work", "capability"], "paths": ["cmd/work-graph-runtime/**", "adapters/postgres/work_repository.go", "modules/work/application/**", "modules/work/domain/**", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000248-live-task-evaluation-scheduling.json"]},
"forbidden_change_scope": {"paths": ["architecture/**", "security/**", "database/**", "deploy/**", "contracts/**", "LLM-local/**", "modules/work/secrets/**"]},
"allowed_architecture_delta": {"new_cross_module_dependencies": 0, "new_canonical_owners": 0, "new_privileges": 0, "new_third_party_dependencies": 0, "new_database_owner_roles": 0},
"contracts": {"consume": ["existing TaskEvaluationGraphRequest", "existing AgentDispatchIntent", "existing Work Graph repository"], "may_change": ["diagnostic logging and local integration behavior"]},
"semantic_owner": "Work Graph task-evaluation scheduling",
"canonical_state": ["existing graph, dispatch and evaluation records only"],
"state_transitions": ["schedule -> queue -> evaluator -> verifier -> evidence callback"],
"side_effects_and_privileges": ["existing work/capability owner writes; no new privileges"],
"persistence_and_migrations": ["none unless current schema mismatch is proven"],
"failure_retry_restart": ["failed scheduling leaves no partial graph or dispatch mutation; durable retries remain existing worker behavior"],
"assumptions": ["production canary payload is provider-neutral and contains no secret"],
"acceptance_scenarios": ["live general evaluation graph schedules", "worker executes and verifier accepts one case", "TaskEvaluation persists", "graph callback reaches terminal state"],
"required_tests": ["go test ./cmd/work-graph-runtime ./modules/work/application ./modules/work/domain ./adapters/postgres", "go vet ./cmd/work-graph-runtime ./modules/work/application ./modules/work/domain ./adapters/postgres", "go run ./cmd/devctl preflight -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-diff -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-architecture", "git diff --check", "independent", "CI", "production health smoke", "live task-evaluation canary"],
"terminal_gap_states": ["BLOCKED_BY_MISSING_CAPABILITY", "BLOCKED_BY_SPEC_GAP"]
}
@@ -0,0 +1,23 @@
{
"task_id": "DEV-000249",
"task_revision": 1,
"architecture_revision": "ADR-0018",
"base_repo_revision": "f90ed7af35f9f19c72039812272640ae1d63635b",
"risk_tier": "E",
"intent": "Make task-evaluation execution use the existing cognitive model resolution and local fallback path without recording unsupported output as qualification evidence.",
"requirement_ids": ["model fleet/failover", "live task evaluation", "local recovery"],
"allowed_change_scope": {"modules": ["capability", "work"], "paths": ["cmd/cognitive-worker/**", "cmd/agent-worker/**", "modules/capability/domain/**", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000249-task-evaluation-model-failover.json"]},
"forbidden_change_scope": {"paths": ["architecture/**", "security/**", "database/**", "deploy/**", "contracts/**", "LLM-local/**", "modules/work/**", "modules/work/secrets/**"]},
"allowed_architecture_delta": {"new_cross_module_dependencies": 0, "new_canonical_owners": 0, "new_privileges": 0, "new_third_party_dependencies": 0, "new_database_owner_roles": 0},
"contracts": {"consume": ["existing CognitiveRequirement", "existing ResolvedCognitiveBinding", "existing TaskEvaluationExecutionRequest/Response"], "may_change": []},
"semantic_owner": "capability cognitive execution adapter",
"canonical_state": ["existing cognitive usage observations and task evaluation records only"],
"state_transitions": ["candidate invocation -> bounded fallback candidate -> verified evaluation or UNKNOWN"],
"side_effects_and_privileges": ["existing cognitive provider calls and capability observations; no new privileges"],
"persistence_and_migrations": ["none"],
"failure_retry_restart": ["provider failure excludes the failed binding for the current bounded execution; all candidates unavailable returns UNKNOWN without qualification"],
"assumptions": ["fallback resolution remains governed by the existing capability resolver and data-policy checks"],
"acceptance_scenarios": ["task evaluation invokes active candidate", "provider-unavailable candidate is excluded and local candidate is attempted", "empty/non-completed provider response never becomes supported evidence", "all candidates unavailable produces durable UNKNOWN evaluation and terminal graph callback"],
"required_tests": ["go test ./cmd/cognitive-worker ./modules/capability/domain", "go vet ./cmd/cognitive-worker ./modules/capability/domain", "go run ./cmd/devctl preflight -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-diff -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-architecture", "git diff --check", "independent", "CI", "production health smoke", "live task-evaluation canary"],
"terminal_gap_states": ["BLOCKED_BY_MISSING_CAPABILITY", "BLOCKED_BY_SPEC_GAP"]
}
@@ -0,0 +1,23 @@
{
"task_id": "DEV-000250",
"task_revision": 1,
"architecture_revision": "ADR-0018",
"base_repo_revision": "2b8cea5aa8f43f597b2af7d92c2eaa305d68256e",
"risk_tier": "E",
"intent": "Preserve the scheduled Work Graph revision in task-evaluation dispatch context so verifier callbacks complete the current immutable graph revision and reach terminal state.",
"requirement_ids": ["live task evaluation", "Work Graph integration", "durable callback lineage"],
"allowed_change_scope": {"modules": ["work"], "paths": ["modules/work/application/**", "modules/work/domain/**", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000250-task-evaluation-terminal-callback.json"]},
"forbidden_change_scope": {"paths": ["architecture/**", "security/**", "database/**", "deploy/**", "contracts/**", "LLM-local/**", "cmd/cognitive-worker/**", "cmd/agent-worker/**"]},
"allowed_architecture_delta": {"new_cross_module_dependencies": 0, "new_canonical_owners": 0, "new_privileges": 0, "new_third_party_dependencies": 0, "new_database_owner_roles": 0},
"contracts": {"consume": ["existing WorkGraph scheduler", "existing TaskEvaluationDispatchContext", "existing callback contract"], "may_change": []},
"semantic_owner": "Work Graph task-evaluation scheduling",
"canonical_state": ["existing immutable graph revisions and node states only"],
"state_transitions": ["scheduled revision -> evaluator callback -> completed revision -> terminal graph"],
"side_effects_and_privileges": ["existing work and capability owner writes; no new privileges"],
"persistence_and_migrations": ["none"],
"failure_retry_restart": ["callback uses the revision that contains the leased node; duplicate callback remains protected by existing immutable revision/idempotency behavior"],
"assumptions": ["scheduler increments the draft graph revision exactly once before enqueueing dispatches"],
"acceptance_scenarios": ["live task-evaluation callback updates scheduled revision", "unknown evaluation reaches graph terminal state", "successful verified evaluation reaches graph terminal state", "callback lineage remains serializable"],
"required_tests": ["go test ./modules/work/application ./modules/work/domain", "go vet ./modules/work/application ./modules/work/domain", "go run ./cmd/devctl preflight -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-diff -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-architecture", "git diff --check", "independent", "CI", "production health smoke", "live task-evaluation canary"],
"terminal_gap_states": ["BLOCKED_BY_MISSING_CAPABILITY", "BLOCKED_BY_SPEC_GAP"]
}
@@ -0,0 +1,23 @@
{
"task_id": "DEV-000251",
"task_revision": 1,
"architecture_revision": "ADR-0018",
"base_repo_revision": "c1cde67bcfff8e0e80553ce0562f9841f6c00b30",
"risk_tier": "E",
"intent": "Prevent the reflection worker from resolving cognition-required candidates before a governed reflection path evaluates them.",
"requirement_ids": ["learning reflection loop", "durable candidate routing", "no false resolution"],
"allowed_change_scope": {"modules": ["epistemics"], "paths": ["adapters/postgres/epistemics_repository.go", "cmd/reflection-worker/**", "modules/epistemics/domain/**", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000251-reflection-cognition-routing.json"]},
"forbidden_change_scope": {"paths": ["architecture/**", "security/**", "database/**", "deploy/**", "contracts/**", "LLM-local/**", "modules/work/**", "cmd/cognitive-worker/**"]},
"allowed_architecture_delta": {"new_cross_module_dependencies": 0, "new_canonical_owners": 0, "new_privileges": 0, "new_third_party_dependencies": 0, "new_database_owner_roles": 0},
"contracts": {"consume": ["existing ReflectionCandidate state and requires_cognition field", "existing Epistemics repository"], "may_change": []},
"semantic_owner": "Epistemics reflection candidate lifecycle",
"canonical_state": ["existing epistemics.reflection_candidates rows only"],
"state_transitions": ["OPEN deterministic -> EVALUATING -> RESOLVED", "OPEN cognition-required remains OPEN until governed evaluator"],
"side_effects_and_privileges": ["existing epistemics owner updates only; no cognition call or proposal promotion"],
"persistence_and_migrations": ["none"],
"failure_retry_restart": ["cognition-required candidates remain durable and claimable only by a future cognition evaluator; stale deterministic claims recover"],
"assumptions": ["reflection worker is the deterministic close loop and must not impersonate a cognitive evaluator"],
"acceptance_scenarios": ["worker resolves deterministic candidate", "worker does not claim cognition-required candidate", "OPEN cognition candidate survives restart", "status remains healthy when only cognition candidates are pending"],
"required_tests": ["go test ./cmd/reflection-worker ./adapters/postgres ./modules/epistemics/domain", "go vet ./cmd/reflection-worker ./adapters/postgres ./modules/epistemics/domain", "go run ./cmd/devctl preflight -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-diff -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-architecture", "git diff --check", "independent", "CI", "production health smoke", "live reflection-worker status"],
"terminal_gap_states": ["BLOCKED_BY_MISSING_CAPABILITY", "BLOCKED_BY_SPEC_GAP"]
}
@@ -0,0 +1,23 @@
{
"task_id": "DEV-000252",
"task_revision": 1,
"architecture_revision": "ADR-0018",
"base_repo_revision": "c925089fd2304213758d2721a0592ca73d765047",
"risk_tier": "E",
"intent": "Add a local governed verification executor that runs only typed, bounded checks inside an isolated worktree and returns evidence without source, secret, production or promotion authority.",
"requirement_ids": ["governed self-development", "isolated verification", "independent test evidence"],
"allowed_change_scope": {"modules": ["governed-development"], "paths": ["adapters/developmentexecutor/**", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000252-local-governed-verification-executor.json"]},
"forbidden_change_scope": {"paths": ["architecture/**", "security/**", "database/**", "deploy/**", "contracts/**", "LLM-local/**", "modules/work/**", "cmd/core-runtime/**", "cmd/agent-worker/**"]},
"allowed_architecture_delta": {"new_cross_module_dependencies": 0, "new_canonical_owners": 0, "new_privileges": 0, "new_third_party_dependencies": 0, "new_database_owner_roles": 0},
"contracts": {"consume": ["existing DevelopmentScaffold semantics", "typed local check plan"], "may_change": []},
"semantic_owner": "governed development verification adapter",
"canonical_state": ["none; executor returns evidence to a future gate owner"],
"state_transitions": ["typed plan -> validated sandbox checks -> immutable result summary"],
"side_effects_and_privileges": ["read-only source/test execution in supplied isolated worktree; no secrets, production, protected main or promotion access"],
"persistence_and_migrations": ["none"],
"failure_retry_restart": ["each check has an explicit result; failed or cancelled checks never become verification success; rerun creates a new result"],
"assumptions": ["worktree isolation and gate persistence are supplied by the surrounding governed control plane"],
"acceptance_scenarios": ["allowed go test/vet and git diff check plans validate", "shell injection and unsupported commands reject", "protected or production worktree rejects", "failed check returns non-success evidence", "result contains no command output or secret material"],
"required_tests": ["go test ./adapters/developmentexecutor", "go vet ./adapters/developmentexecutor", "go run ./cmd/devctl preflight -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-diff -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-architecture", "git diff --check", "independent", "CI"],
"terminal_gap_states": ["BLOCKED_BY_MISSING_CAPABILITY", "BLOCKED_BY_SPEC_GAP"]
}
@@ -0,0 +1,23 @@
{
"task_id": "DEV-000253",
"task_revision": 1,
"architecture_revision": "ADR-0018",
"base_repo_revision": "f2e520e31e27563a17f66c73ff68b35bc93963a0",
"risk_tier": "E",
"intent": "Connect the local bounded verification executor to the authenticated durable DevelopmentGate ISOLATED-to-VERIFIED transition with optimistic revision and independent verifier checks.",
"requirement_ids": ["governed self-development", "independent verification", "durable gate progression"],
"allowed_change_scope": {"modules": ["governed-development"], "paths": ["cmd/core-runtime/development_gate.go", "cmd/core-runtime/main.go", "cmd/core-runtime/development_gate_test.go", "adapters/developmentexecutor/**", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000253-development-gate-verifier-integration.json"]},
"forbidden_change_scope": {"paths": ["architecture/**", "security/**", "database/**", "deploy/**", "contracts/**", "LLM-local/**", "modules/work/**", "cmd/agent-worker/**"]},
"allowed_architecture_delta": {"new_cross_module_dependencies": 0, "new_canonical_owners": 0, "new_privileges": 0, "new_third_party_dependencies": 0, "new_database_owner_roles": 0},
"contracts": {"consume": ["existing authenticated Creator session", "existing DevelopmentGate CAS store", "local developmentexecutor"], "may_change": ["internal authenticated verifier route" ]},
"semantic_owner": "Work-owned DevelopmentGate controller",
"canonical_state": ["existing work.development_gates row only"],
"state_transitions": ["ISOLATED -> VERIFIED only after bounded checks pass and verifier run differs from producer runs"],
"side_effects_and_privileges": ["read-only checks in isolated worktree; one existing Work-owner CAS update; no source mutation, secrets, production or promotion authority"],
"persistence_and_migrations": ["none"],
"failure_retry_restart": ["stale gate revision conflicts; failed checks leave gate ISOLATED; rerun uses a new verifier run/evidence reference"],
"assumptions": ["worktree has already been created by the governed isolation step"],
"acceptance_scenarios": ["unauthenticated verifier request denied", "non-ISOLATED gate denied", "same producer/verifier run denied", "failed check leaves gate unchanged", "passing checks persist VERIFIED with evidence and CAS"],
"required_tests": ["go test ./cmd/core-runtime ./adapters/developmentexecutor", "go vet ./cmd/core-runtime ./adapters/developmentexecutor", "go run ./cmd/devctl preflight -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-diff -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-architecture", "git diff --check", "independent", "CI", "production health smoke", "authenticated gate verifier live smoke"],
"terminal_gap_states": ["BLOCKED_BY_MISSING_CAPABILITY", "BLOCKED_BY_SPEC_GAP"]
}
@@ -0,0 +1,23 @@
{
"task_id": "DEV-000254",
"task_revision": 1,
"architecture_revision": "ADR-0018",
"base_repo_revision": "3bfe76278b7e44965cbb0e3b4ac2911ad4a4bbae",
"risk_tier": "E",
"intent": "Expose typed authenticated transitions for durable governed self-development canary, promotion and rollback evidence with optimistic revision checks.",
"requirement_ids": ["governed self-development", "durable canary evidence", "promotion and rollback lineage"],
"allowed_change_scope": {"modules": ["governed-development"], "paths": ["cmd/core-runtime/development_gate.go", "cmd/core-runtime/main.go", "cmd/core-runtime/development_gate_test.go", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000254-development-gate-canary-promotion-rollback.json"]},
"forbidden_change_scope": {"paths": ["architecture/**", "security/**", "database/**", "deploy/**", "contracts/**", "LLM-local/**", "modules/work/**", "cmd/agent-worker/**"]},
"allowed_architecture_delta": {"new_cross_module_dependencies": 0, "new_canonical_owners": 0, "new_privileges": 0, "new_third_party_dependencies": 0, "new_database_owner_roles": 0},
"contracts": {"consume": ["existing authenticated Creator session", "existing DevelopmentGate CAS store", "existing governed stage machine"], "may_change": ["internal authenticated canary/promotion/rollback routes"]},
"semantic_owner": "Work-owned DevelopmentGate controller",
"canonical_state": ["existing work.development_gates row only"],
"state_transitions": ["VERIFIED -> CANARY", "CANARY -> PROMOTED", "CANARY -> ROLLED_BACK"],
"side_effects_and_privileges": ["one existing Work-owner CAS update per transition; no source mutation, secrets, external mutation or deployment authority"],
"persistence_and_migrations": ["none"],
"failure_retry_restart": ["stale revisions conflict; missing evidence is rejected; terminal stages cannot be reopened; retry uses a new reference and current revision"],
"assumptions": ["canary execution and deployment evidence are produced by their owning executor; this task records only typed evidence and advances the durable gate"],
"acceptance_scenarios": ["unauthenticated transition denied", "missing evidence denied", "stale revision denied", "canary evidence advances VERIFIED to CANARY", "promotion evidence advances CANARY to PROMOTED", "rollback evidence advances CANARY to ROLLED_BACK", "terminal gates cannot be reopened"],
"required_tests": ["go test ./cmd/core-runtime", "go vet ./cmd/core-runtime", "go run ./cmd/devctl preflight -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-diff -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-architecture", "git diff --check", "independent verifier", "CI", "production health smoke", "authenticated transition smoke"],
"terminal_gap_states": ["BLOCKED_BY_MISSING_CAPABILITY", "BLOCKED_BY_SPEC_GAP"]
}
@@ -0,0 +1,23 @@
{
"task_id": "DEV-000255",
"task_revision": 1,
"architecture_revision": "ADR-0018",
"base_repo_revision": "12ab124411771fbed16e1f1935b97b1d0700a7b0",
"risk_tier": "E",
"intent": "Refresh the audit status projection and evidence matrix from current production and authenticated UI observations without changing runtime behavior.",
"requirement_ids": ["task lineage and status synchronization", "auditable production evidence"],
"allowed_change_scope": {"modules": ["governance-documentation"], "paths": ["docs/PROJECT_STATUS.md", "docs/evidence/audit-completion-matrix-2026-08-23-v4.json", "docs/evidence/authenticated-ui-acceptance-2026-08-23-v3.json", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000255-refresh-live-audit-status-projection.json"]},
"forbidden_change_scope": {"paths": ["architecture/**", "security/**", "database/**", "deploy/**", "contracts/**", "LLM-local/**", "cmd/**", "modules/**", "adapters/**"]},
"allowed_architecture_delta": {"new_cross_module_dependencies": 0, "new_canonical_owners": 0, "new_privileges": 0, "new_third_party_dependencies": 0, "new_database_owner_roles": 0},
"contracts": {"consume": ["current production health and revision evidence", "current authenticated Creator UI observation"], "may_change": ["documentation evidence projection only"]},
"semantic_owner": "Governance evidence projection",
"canonical_state": ["none; evidence files are derived audit artifacts"],
"state_transitions": ["supersede stale status projection with timestamped current evidence"],
"side_effects_and_privileges": ["documentation-only; no runtime, database, secret or external mutation"],
"persistence_and_migrations": ["none"],
"failure_retry_restart": ["retain prior evidence as historical; newer evidence supersedes it without rewriting history"],
"assumptions": ["mobile viewport cannot be emulated by the current in-app browser control surface, so mobile remains explicitly unverified"],
"acceptance_scenarios": ["status names current deployed revision", "model counts match current authenticated UI", "external bridge and mobile limitations remain explicit", "no secret values are recorded"],
"required_tests": ["go test ./cmd/core-runtime", "go vet ./cmd/core-runtime", "independent evidence review", "go run ./cmd/devctl preflight -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-diff -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-architecture", "git diff --check", "CI", "production health smoke"],
"terminal_gap_states": ["BLOCKED_BY_MISSING_CAPABILITY", "BLOCKED_BY_SPEC_GAP"]
}
@@ -0,0 +1,23 @@
{
"task_id": "DEV-000256",
"task_revision": 1,
"architecture_revision": "ADR-0018",
"base_repo_revision": "3d97b348f74741b9aca2aa8d77f2e66b9f5760c2",
"risk_tier": "E",
"intent": "Expose observed qualification latency and evidence strength in provider-neutral model ratings without inferring quality, activation or authority.",
"requirement_ids": ["model fleet/failover", "provider-neutral rating", "quality must remain unrated without task evaluation"],
"allowed_change_scope": {"modules": ["capability"], "paths": ["modules/capability/domain/cognitive_registry.go", "modules/capability/domain/model_rating_test.go", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000256-model-rating-reachability-evidence.json"]},
"forbidden_change_scope": {"paths": ["architecture/**", "security/**", "database/**", "deploy/**", "contracts/**", "LLM-local/**", "cmd/**", "adapters/**"]},
"allowed_architecture_delta": {"new_cross_module_dependencies": 0, "new_canonical_owners": 0, "new_privileges": 0, "new_third_party_dependencies": 0, "new_database_owner_roles": 0},
"contracts": {"consume": ["existing ModelQualificationObservation", "existing provider-neutral ModelRating"], "may_change": ["derived rating values for reachability-only evidence"]},
"semantic_owner": "Capability rating derivation",
"canonical_state": ["none; ModelRating is derived from existing evidence"],
"state_transitions": ["none"],
"side_effects_and_privileges": ["pure deterministic derivation only"],
"persistence_and_migrations": ["none"],
"failure_retry_restart": ["missing or unavailable qualification remains UNRATED with no quality claim"],
"assumptions": ["reachability and latency are operational evidence, not task quality"],
"acceptance_scenarios": ["reachable qualification exposes latency/evidence scores", "quality category remains UNRATED without supported task evaluation", "provider kind and hardware do not affect rating", "unavailable qualification does not create positive rating"],
"required_tests": ["go test ./modules/capability/domain", "go vet ./modules/capability/domain", "independent evidence review", "go run ./cmd/devctl preflight -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-diff -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-architecture", "git diff --check", "CI", "production health smoke"],
"terminal_gap_states": ["BLOCKED_BY_MISSING_CAPABILITY", "BLOCKED_BY_SPEC_GAP"]
}
@@ -0,0 +1,23 @@
{
"task_id": "DEV-000257",
"task_revision": 1,
"architecture_revision": "ADR-0018",
"base_repo_revision": "8868258afd4e58edfa9c71e3fbf79793cd32397b",
"risk_tier": "E",
"intent": "Record a bounded operations acceptance smoke for authenticated fleet and agent-worker status surfaces and unauthenticated protection.",
"requirement_ids": ["UI/operations acceptance", "truthful degraded state", "authenticated read-only status"],
"allowed_change_scope": {"modules": ["governance-documentation"], "paths": ["docs/evidence/operations-acceptance-2026-08-23-v1.json", "docs/PROJECT_STATUS.md", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000257-operations-acceptance-fast-smoke.json"]},
"forbidden_change_scope": {"paths": ["architecture/**", "security/**", "database/**", "deploy/**", "contracts/**", "LLM-local/**", "cmd/**", "modules/**", "adapters/**"]},
"allowed_architecture_delta": {"new_cross_module_dependencies": 0, "new_canonical_owners": 0, "new_privileges": 0, "new_third_party_dependencies": 0, "new_database_owner_roles": 0},
"contracts": {"consume": ["authenticated Creator UI", "fleet readiness read API", "agent-worker status read API"], "may_change": ["evidence projection only"]},
"semantic_owner": "Governance evidence projection",
"canonical_state": ["none"],
"state_transitions": ["none"],
"side_effects_and_privileges": ["read-only API/UI checks; no mutation"],
"persistence_and_migrations": ["none"],
"failure_retry_restart": ["failed checks remain explicit and do not alter runtime state"],
"assumptions": ["deep UI, long-running delegation and mobile viewport acceptance remain separate tests"],
"acceptance_scenarios": ["unauthenticated fleet status returns 401", "unauthenticated agent status returns 401", "authenticated fleet refresh displays selected tier and degraded reason", "authenticated agent refresh displays local fallback and bridge-unavailable reason"],
"required_tests": ["go test ./cmd/creator-web", "go vet ./cmd/creator-web", "independent evidence review", "go run ./cmd/devctl preflight -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-diff -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-architecture", "git diff --check", "CI", "production health smoke", "authenticated operations smoke"],
"terminal_gap_states": ["BLOCKED_BY_MISSING_CAPABILITY", "BLOCKED_BY_SPEC_GAP"]
}
@@ -0,0 +1,23 @@
{
"task_id": "DEV-000258",
"task_revision": 1,
"architecture_revision": "ADR-0018",
"base_repo_revision": "5e8286695f0da3f3819cdc17ced6525237512e6e",
"risk_tier": "E",
"intent": "Add a fast static responsive regression guard for the authenticated Creator console while retaining the requirement for real mobile visual acceptance.",
"requirement_ids": ["UI responsive safety", "authenticated console acceptance"],
"allowed_change_scope": {"modules": ["creator-web"], "paths": ["cmd/creator-web/responsive_test.go", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000258-responsive-console-regression-guard.json"]},
"forbidden_change_scope": {"paths": ["architecture/**", "security/**", "database/**", "deploy/**", "contracts/**", "LLM-local/**", "cmd/core-runtime/**", "modules/**", "adapters/**"]},
"allowed_architecture_delta": {"new_cross_module_dependencies": 0, "new_canonical_owners": 0, "new_privileges": 0, "new_third_party_dependencies": 0, "new_database_owner_roles": 0},
"contracts": {"consume": ["embedded Creator console HTML/CSS"], "may_change": ["tests only"]},
"semantic_owner": "Creator Web presentation tests",
"canonical_state": ["none"],
"state_transitions": ["none"],
"side_effects_and_privileges": ["test-only static inspection"],
"persistence_and_migrations": ["none"],
"failure_retry_restart": ["test fails when responsive guardrails disappear"],
"assumptions": ["static guard is not a substitute for browser visual acceptance; real mobile viewport evidence remains required when browser capability exists"],
"acceptance_scenarios": ["viewport metadata exists", "mobile media block exists", "key panels collapse to safe single-column layouts", "only section navigation owns horizontal scrolling"],
"required_tests": ["go test ./cmd/creator-web", "go vet ./cmd/creator-web", "independent evidence review", "go run ./cmd/devctl preflight -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-diff -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-architecture", "git diff --check", "CI", "production health smoke"],
"terminal_gap_states": ["BLOCKED_BY_MISSING_CAPABILITY", "BLOCKED_BY_SPEC_GAP"]
}
@@ -0,0 +1,23 @@
{
"task_id": "DEV-000259",
"task_revision": 1,
"architecture_revision": "ADR-0018",
"base_repo_revision": "d4b541612647acd826477c8cbea2bc9976049f03",
"risk_tier": "E",
"intent": "Refresh the audit status projection after the responsive regression guard and current production deployment.",
"requirement_ids": ["task lineage and status synchronization", "auditable UI acceptance state"],
"allowed_change_scope": {"modules": ["governance-documentation"], "paths": ["docs/PROJECT_STATUS.md", "docs/evidence/audit-completion-matrix-2026-08-23-v5.json", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000259-refresh-post-responsive-evidence.json"]},
"forbidden_change_scope": {"paths": ["architecture/**", "security/**", "database/**", "deploy/**", "contracts/**", "LLM-local/**", "cmd/**", "modules/**", "adapters/**"]},
"allowed_architecture_delta": {"new_cross_module_dependencies": 0, "new_canonical_owners": 0, "new_privileges": 0, "new_third_party_dependencies": 0, "new_database_owner_roles": 0},
"contracts": {"consume": ["current production health/revision", "responsive regression test evidence", "authenticated operations smoke"], "may_change": ["status projection only"]},
"semantic_owner": "Governance evidence projection",
"canonical_state": ["none"],
"state_transitions": ["supersede stale projection without rewriting historical evidence"],
"side_effects_and_privileges": ["documentation-only"],
"persistence_and_migrations": ["none"],
"failure_retry_restart": ["historical evidence remains immutable; current projection can be refreshed"],
"assumptions": ["real mobile viewport visual evidence remains unavailable"],
"acceptance_scenarios": ["status names DEV-000258 as latest completed", "matrix names d4b54161 production revision", "responsive static guard is recorded", "mobile visual limitation remains explicit"],
"required_tests": ["go test ./cmd/creator-web", "go vet ./cmd/creator-web", "independent evidence review", "go run ./cmd/devctl preflight -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-diff -active dev/ACTIVE_TASK.json", "go run ./cmd/devctl check-architecture", "git diff --check", "CI", "production health smoke"],
"terminal_gap_states": ["BLOCKED_BY_MISSING_CAPABILITY", "BLOCKED_BY_SPEC_GAP"]
}
@@ -0,0 +1,23 @@
{
"task_id": "DEV-000260",
"task_revision": 1,
"architecture_revision": "ADR-0018",
"base_repo_revision": "a9dbc3b",
"risk_tier": "E",
"intent": "Record deep authenticated UI and operations acceptance without changing runtime behavior.",
"requirement_ids": ["authenticated deep acceptance evidence", "truthful operational status"],
"allowed_change_scope": {"modules": ["governance-documentation"], "paths": ["docs/evidence/deep-authenticated-acceptance-2026-08-23-v1.json", "docs/PROJECT_STATUS.md", "docs/evidence/audit-completion-matrix-2026-08-23-v5.json", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000260-deep-authenticated-acceptance.json"]},
"forbidden_change_scope": {"paths": ["architecture/**", "security/**", "database/**", "deploy/**", "contracts/**", "LLM-local/**", "cmd/**", "modules/**", "adapters/**"]},
"allowed_architecture_delta": {"new_cross_module_dependencies": 0, "new_canonical_owners": 0, "new_privileges": 0, "new_third_party_dependencies": 0, "new_database_owner_roles": 0},
"contracts": {"consume": ["authenticated browser UI", "current production fleet/agent/learning status"], "may_change": ["evidence projection only"]},
"semantic_owner": "Governance evidence projection",
"canonical_state": ["none"],
"state_transitions": ["none"],
"side_effects_and_privileges": ["documentation-only; browser acceptance used no mutation"],
"persistence_and_migrations": ["none"],
"failure_retry_restart": ["evidence remains immutable; status projection can be refreshed"],
"assumptions": ["mobile viewport capability remains unavailable", "no delegation ID was present in the authenticated session"],
"acceptance_scenarios": ["authenticated chat reply received", "diagnostics 7/7 after refresh", "fleet and agent refresh completed", "learning refresh completed", "action history visible", "no secrets exposed"],
"required_tests": ["go test ./cmd/creator-web ./cmd/devctl", "go vet ./cmd/creator-web", "go run ./cmd/devctl preflight -task dev/tasks/DEV-000260-deep-authenticated-acceptance.json", "go run ./cmd/devctl check-diff -task dev/tasks/DEV-000260-deep-authenticated-acceptance.json", "independent evidence review", "git diff --check", "CI", "production health smoke"],
"terminal_gap_states": ["BLOCKED_BY_MISSING_CAPABILITY", "BLOCKED_BY_SPEC_GAP"]
}

Some files were not shown because too many files have changed in this diff Show More