loopx 0.4.8__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (811) hide show
  1. loopx/__init__.py +5 -0
  2. loopx/agent_onboarding.py +654 -0
  3. loopx/agent_registry.py +112 -0
  4. loopx/ark_managed_agent_host.py +59 -0
  5. loopx/authority.py +805 -0
  6. loopx/benchmark.py +2875 -0
  7. loopx/benchmark_adapters/__init__.py +1 -0
  8. loopx/benchmark_adapters/agentissue.py +2644 -0
  9. loopx/benchmark_adapters/agents_last_exam.py +3998 -0
  10. loopx/benchmark_adapters/edgebench.py +322 -0
  11. loopx/benchmark_adapters/skillsbench.py +5978 -0
  12. loopx/benchmark_adapters/skillsbench_acp_failure_policy.py +143 -0
  13. loopx/benchmark_adapters/skillsbench_acp_process.py +31 -0
  14. loopx/benchmark_adapters/skillsbench_acp_relay.py +4832 -0
  15. loopx/benchmark_adapters/skillsbench_batch.py +124 -0
  16. loopx/benchmark_adapters/skillsbench_bridge_guard.py +209 -0
  17. loopx/benchmark_adapters/skillsbench_bridge_summary.py +203 -0
  18. loopx/benchmark_adapters/skillsbench_codex_goal_recovery.py +271 -0
  19. loopx/benchmark_adapters/skillsbench_codex_goal_trace.py +81 -0
  20. loopx/benchmark_adapters/skillsbench_codex_runtime.py +339 -0
  21. loopx/benchmark_adapters/skillsbench_dockerfile_runtime.py +467 -0
  22. loopx/benchmark_adapters/skillsbench_failure_signals.py +652 -0
  23. loopx/benchmark_adapters/skillsbench_proxy_runtime.py +327 -0
  24. loopx/benchmark_adapters/skillsbench_remote_bridge.py +402 -0
  25. loopx/benchmark_adapters/skillsbench_result_discovery.py +143 -0
  26. loopx/benchmark_adapters/skillsbench_runner_profile.py +436 -0
  27. loopx/benchmark_adapters/skillsbench_runner_source.py +99 -0
  28. loopx/benchmark_adapters/skillsbench_setup_preflight.py +771 -0
  29. loopx/benchmark_adapters/skillsbench_signals.py +15 -0
  30. loopx/benchmark_adapters/skillsbench_task_source.py +141 -0
  31. loopx/benchmark_adapters/skillsbench_turn_route.py +723 -0
  32. loopx/benchmark_adapters/skillsbench_turn_runtime.py +1069 -0
  33. loopx/benchmark_adapters/skillsbench_typed_repair.py +689 -0
  34. loopx/benchmark_adapters/skillsbench_uv_cache.py +111 -0
  35. loopx/benchmark_adapters/skillsbench_verifier_bootstrap.py +227 -0
  36. loopx/benchmark_adapters/skillsbench_verifier_cache.py +138 -0
  37. loopx/benchmark_adapters/terminal_bench.py +10078 -0
  38. loopx/benchmark_case_analysis.py +1276 -0
  39. loopx/benchmark_case_state.py +1079 -0
  40. loopx/benchmark_core/__init__.py +239 -0
  41. loopx/benchmark_core/adapter.py +84 -0
  42. loopx/benchmark_core/artifacts.py +517 -0
  43. loopx/benchmark_core/attempts.py +199 -0
  44. loopx/benchmark_core/container_exec.py +216 -0
  45. loopx/benchmark_core/io.py +68 -0
  46. loopx/benchmark_core/lifecycle.py +211 -0
  47. loopx/benchmark_core/loop_protocol.py +689 -0
  48. loopx/benchmark_core/observable_handles.py +348 -0
  49. loopx/benchmark_core/parity.py +256 -0
  50. loopx/benchmark_core/remote_closeout.py +482 -0
  51. loopx/benchmark_core/rounds.py +215 -0
  52. loopx/benchmark_core/route_profile.py +509 -0
  53. loopx/benchmark_core/run_permissions.py +206 -0
  54. loopx/benchmark_core/split_control.py +925 -0
  55. loopx/benchmark_core/turn_fidelity.py +326 -0
  56. loopx/benchmark_ledger.py +3793 -0
  57. loopx/benchmark_ledger_countability.py +372 -0
  58. loopx/benchmark_ledger_current.py +724 -0
  59. loopx/benchmark_trajectory.py +405 -0
  60. loopx/benchmarks/__init__.py +1 -0
  61. loopx/benchmarks/qualification/__init__.py +1 -0
  62. loopx/benchmarks/qualification/release_outcome_baseline.py +360 -0
  63. loopx/benchmarks/read_models/__init__.py +1 -0
  64. loopx/benchmarks/read_models/benchmark_attempt_accounting.py +53 -0
  65. loopx/benchmarks/read_models/benchmark_comparison.py +414 -0
  66. loopx/benchmarks/read_models/benchmark_event_timeline.py +113 -0
  67. loopx/benchmarks/read_models/benchmark_experiment_report.py +475 -0
  68. loopx/benchmarks/read_models/benchmark_learning_ledger.py +137 -0
  69. loopx/benchmarks/read_models/benchmark_lifecycle_contracts.py +228 -0
  70. loopx/benchmarks/read_models/benchmark_projection.py +723 -0
  71. loopx/benchmarks/read_models/benchmark_result.py +146 -0
  72. loopx/benchmarks/read_models/benchmark_run_execution_contract.py +116 -0
  73. loopx/benchmarks/read_models/benchmark_run_failure.py +157 -0
  74. loopx/benchmarks/read_models/benchmark_run_metrics.py +213 -0
  75. loopx/benchmarks/read_models/benchmark_run_post_execution.py +635 -0
  76. loopx/benchmarks/read_models/benchmark_run_pre_execution.py +541 -0
  77. loopx/benchmarks/read_models/benchmark_status_compaction.py +1255 -0
  78. loopx/benchmarks/read_models/benchmark_status_runner.py +780 -0
  79. loopx/benchmarks/read_models/goal_start_control_score.py +857 -0
  80. loopx/benchmarks/read_models/skillsbench_post_run_debug.py +746 -0
  81. loopx/benchmarks/read_models/skillsbench_verifier_attribution.py +269 -0
  82. loopx/bootstrap.py +1116 -0
  83. loopx/bootstrap_command_pack.py +2167 -0
  84. loopx/boundary_authority.py +199 -0
  85. loopx/canary/__init__.py +1 -0
  86. loopx/canary/maintainability_ratchet.py +800 -0
  87. loopx/canary/planner.py +1984 -0
  88. loopx/canary/premerge.py +1130 -0
  89. loopx/canary/qualification_profiles.py +309 -0
  90. loopx/canary/quality_surface_catalog.py +838 -0
  91. loopx/canary/release_profiles.py +51 -0
  92. loopx/canary/runner.py +1107 -0
  93. loopx/canary/smoke_health.py +581 -0
  94. loopx/canary/smoke_profiles.py +212 -0
  95. loopx/capabilities/__init__.py +0 -0
  96. loopx/capabilities/agent_turn_recall/__init__.py +17 -0
  97. loopx/capabilities/agent_turn_recall/cli.py +369 -0
  98. loopx/capabilities/agent_turn_recall/core.py +296 -0
  99. loopx/capabilities/auto_research/__init__.py +16 -0
  100. loopx/capabilities/auto_research/bootstrap_contract.py +157 -0
  101. loopx/capabilities/auto_research/cli.py +1468 -0
  102. loopx/capabilities/auto_research/core.py +11 -0
  103. loopx/capabilities/auto_research/defaults.py +79 -0
  104. loopx/capabilities/auto_research/demo_e2e.py +1848 -0
  105. loopx/capabilities/auto_research/demo_supervisor.py +186 -0
  106. loopx/capabilities/auto_research/evidence_packet.py +767 -0
  107. loopx/capabilities/auto_research/human_view.py +794 -0
  108. loopx/capabilities/auto_research/kernel.py +191 -0
  109. loopx/capabilities/auto_research/knn_demo_workspace.py +322 -0
  110. loopx/capabilities/auto_research/live_evidence.py +248 -0
  111. loopx/capabilities/auto_research/preset.py +176 -0
  112. loopx/capabilities/auto_research/research_state.py +1085 -0
  113. loopx/capabilities/auto_research/role_profiles.py +394 -0
  114. loopx/capabilities/auto_research/rollout_append.py +97 -0
  115. loopx/capabilities/auto_research/terminal_result_contract.py +422 -0
  116. loopx/capabilities/auto_research/terminal_result_projection.py +171 -0
  117. loopx/capabilities/auto_research/terminal_result_query.py +233 -0
  118. loopx/capabilities/auto_research/terminal_results.py +349 -0
  119. loopx/capabilities/auto_research/user_contract.py +190 -0
  120. loopx/capabilities/auto_research/worker_loop.py +163 -0
  121. loopx/capabilities/auto_research/worker_runtime.py +777 -0
  122. loopx/capabilities/auto_research/worker_skill/SKILL.md +343 -0
  123. loopx/capabilities/benchmark_toolkit/__init__.py +19 -0
  124. loopx/capabilities/benchmark_toolkit/integrity.py +387 -0
  125. loopx/capabilities/catalog.py +1875 -0
  126. loopx/capabilities/change_quality/__init__.py +19 -0
  127. loopx/capabilities/change_quality/cli.py +171 -0
  128. loopx/capabilities/change_quality/context.py +156 -0
  129. loopx/capabilities/change_quality/oracles.py +269 -0
  130. loopx/capabilities/change_quality/policy.py +34 -0
  131. loopx/capabilities/change_quality/receipt.py +482 -0
  132. loopx/capabilities/change_quality/result.py +493 -0
  133. loopx/capabilities/change_quality/scope.py +171 -0
  134. loopx/capabilities/change_quality/shadow.py +680 -0
  135. loopx/capabilities/content_ops/__init__.py +0 -0
  136. loopx/capabilities/content_ops/cli.py +649 -0
  137. loopx/capabilities/content_ops/connector_packets.py +164 -0
  138. loopx/capabilities/content_ops/item_lifecycle.py +1000 -0
  139. loopx/capabilities/content_ops/layout.py +451 -0
  140. loopx/capabilities/content_ops/markdown.py +456 -0
  141. loopx/capabilities/content_ops/schemas.py +51 -0
  142. loopx/capabilities/content_ops/social_browser_x.py +107 -0
  143. loopx/capabilities/content_ops/surface.py +1956 -0
  144. loopx/capabilities/content_ops/templates/layout-catalog-v0.json +72 -0
  145. loopx/capabilities/context_providers/__init__.py +36 -0
  146. loopx/capabilities/context_providers/base.py +189 -0
  147. loopx/capabilities/context_providers/factory.py +32 -0
  148. loopx/capabilities/context_providers/openviking.py +702 -0
  149. loopx/capabilities/context_providers/service_ownership.py +185 -0
  150. loopx/capabilities/decision_context/__init__.py +129 -0
  151. loopx/capabilities/decision_context/architecture.py +83 -0
  152. loopx/capabilities/decision_context/assembler.py +849 -0
  153. loopx/capabilities/decision_context/catalog_entry.py +195 -0
  154. loopx/capabilities/decision_context/cli.py +310 -0
  155. loopx/capabilities/decision_context/cursor_commit.py +535 -0
  156. loopx/capabilities/decision_context/outcome_feedback.py +352 -0
  157. loopx/capabilities/decision_context/packets.py +654 -0
  158. loopx/capabilities/decision_context/private_state.py +189 -0
  159. loopx/capabilities/decision_context/profile.py +453 -0
  160. loopx/capabilities/decision_context/providers.py +228 -0
  161. loopx/capabilities/decision_context/review_settlement.py +136 -0
  162. loopx/capabilities/decision_context/runtime.py +273 -0
  163. loopx/capabilities/decision_context/sources.py +415 -0
  164. loopx/capabilities/explore/__init__.py +1 -0
  165. loopx/capabilities/explore/activation.py +198 -0
  166. loopx/capabilities/explore/adaptive_replay_planner.py +221 -0
  167. loopx/capabilities/explore/child_replay_runtime.py +463 -0
  168. loopx/capabilities/explore/composition_frontier.py +291 -0
  169. loopx/capabilities/explore/counterfactual_runtime.py +578 -0
  170. loopx/capabilities/explore/episode_runtime.py +647 -0
  171. loopx/capabilities/explore/harness_checkpoint.py +171 -0
  172. loopx/capabilities/explore/harness_gate.py +115 -0
  173. loopx/capabilities/explore/harness_runtime.py +1124 -0
  174. loopx/capabilities/explore/replay_metrics.py +206 -0
  175. loopx/capabilities/explore/replay_runtime.py +1271 -0
  176. loopx/capabilities/explore/resource_portfolio.py +173 -0
  177. loopx/capabilities/explore/result_log.py +974 -0
  178. loopx/capabilities/explore/router_state.py +432 -0
  179. loopx/capabilities/explore/source_history_reconcile.py +255 -0
  180. loopx/capabilities/explore/speculative_scheduler.py +498 -0
  181. loopx/capabilities/explore/todo_branch_plan.py +650 -0
  182. loopx/capabilities/explore/todo_evidence.py +141 -0
  183. loopx/capabilities/explore/trace_runtime.py +284 -0
  184. loopx/capabilities/explore/worker_branch_plan.py +1257 -0
  185. loopx/capabilities/integration_branch/__init__.py +13 -0
  186. loopx/capabilities/integration_branch/cli.py +148 -0
  187. loopx/capabilities/integration_branch/core.py +916 -0
  188. loopx/capabilities/issue_fix/__init__.py +19 -0
  189. loopx/capabilities/issue_fix/acceptance_loop.py +1050 -0
  190. loopx/capabilities/issue_fix/candidate_evidence.py +503 -0
  191. loopx/capabilities/issue_fix/candidate_preflight.py +676 -0
  192. loopx/capabilities/issue_fix/cli.py +1822 -0
  193. loopx/capabilities/issue_fix/cli_input.py +87 -0
  194. loopx/capabilities/issue_fix/content_ops_cli.py +148 -0
  195. loopx/capabilities/issue_fix/discovered_issue_promotion.py +947 -0
  196. loopx/capabilities/issue_fix/explore_projection.py +710 -0
  197. loopx/capabilities/issue_fix/feasibility.py +542 -0
  198. loopx/capabilities/issue_fix/github_public.py +661 -0
  199. loopx/capabilities/issue_fix/intake_surface.py +832 -0
  200. loopx/capabilities/issue_fix/metadata_preview.py +218 -0
  201. loopx/capabilities/issue_fix/metrics_projection.py +1340 -0
  202. loopx/capabilities/issue_fix/metrics_supplement.py +634 -0
  203. loopx/capabilities/issue_fix/metrics_supplement_cli.py +127 -0
  204. loopx/capabilities/issue_fix/outcome_projection.py +1235 -0
  205. loopx/capabilities/issue_fix/periodic_report.py +189 -0
  206. loopx/capabilities/issue_fix/pr_description.py +418 -0
  207. loopx/capabilities/issue_fix/pr_gate_reconcile.py +496 -0
  208. loopx/capabilities/issue_fix/pr_gate_reconcile_cli.py +464 -0
  209. loopx/capabilities/issue_fix/pr_lifecycle.py +1327 -0
  210. loopx/capabilities/issue_fix/pr_lifecycle_rollout.py +85 -0
  211. loopx/capabilities/issue_fix/pr_monitor_materialization.py +257 -0
  212. loopx/capabilities/issue_fix/pr_review_ack.py +439 -0
  213. loopx/capabilities/issue_fix/provider_hooks.py +24 -0
  214. loopx/capabilities/issue_fix/repository_commit_evidence.py +186 -0
  215. loopx/capabilities/issue_fix/repository_context.py +457 -0
  216. loopx/capabilities/issue_fix/repository_memory.py +459 -0
  217. loopx/capabilities/issue_fix/repository_memory_provider.py +1454 -0
  218. loopx/capabilities/issue_fix/repository_snapshot.py +454 -0
  219. loopx/capabilities/issue_fix/reviewer_cli.py +917 -0
  220. loopx/capabilities/issue_fix/reviewer_notification.py +882 -0
  221. loopx/capabilities/issue_fix/reviewer_notification_drain.py +942 -0
  222. loopx/capabilities/issue_fix/reviewer_recommendation.py +1057 -0
  223. loopx/capabilities/issue_fix/reviewer_request.py +1282 -0
  224. loopx/capabilities/issue_fix/reward_memory.py +879 -0
  225. loopx/capabilities/issue_fix/workflow_plan.py +1286 -0
  226. loopx/capabilities/material_lifecycle/__init__.py +161 -0
  227. loopx/capabilities/material_lifecycle/_validation.py +183 -0
  228. loopx/capabilities/material_lifecycle/apply.py +672 -0
  229. loopx/capabilities/material_lifecycle/architecture.py +122 -0
  230. loopx/capabilities/material_lifecycle/cli.py +161 -0
  231. loopx/capabilities/material_lifecycle/decision_planning.py +470 -0
  232. loopx/capabilities/material_lifecycle/explore_execution.py +306 -0
  233. loopx/capabilities/material_lifecycle/intake.py +869 -0
  234. loopx/capabilities/material_lifecycle/inventory.py +147 -0
  235. loopx/capabilities/material_lifecycle/lifecycle.py +98 -0
  236. loopx/capabilities/material_lifecycle/preparation.py +147 -0
  237. loopx/capabilities/material_lifecycle/project_skill.py +83 -0
  238. loopx/capabilities/material_lifecycle/ranking.py +267 -0
  239. loopx/capabilities/material_lifecycle/readable_projection.py +500 -0
  240. loopx/capabilities/material_lifecycle/rebuild.py +480 -0
  241. loopx/capabilities/material_lifecycle/settlement.py +238 -0
  242. loopx/capabilities/periodic_report/__init__.py +71 -0
  243. loopx/capabilities/periodic_report/adapters.py +939 -0
  244. loopx/capabilities/periodic_report/archive.py +422 -0
  245. loopx/capabilities/periodic_report/bindings.py +705 -0
  246. loopx/capabilities/periodic_report/cli.py +277 -0
  247. loopx/capabilities/periodic_report/core.py +691 -0
  248. loopx/capabilities/periodic_report/extension_envelope.py +66 -0
  249. loopx/capabilities/periodic_report/presets.py +103 -0
  250. loopx/capabilities/periodic_report/profile.py +235 -0
  251. loopx/capabilities/periodic_report/project_progress.py +179 -0
  252. loopx/capabilities/periodic_report/triggers.py +452 -0
  253. loopx/capabilities/pr_review_queue/__init__.py +17 -0
  254. loopx/capabilities/pr_review_queue/core.py +506 -0
  255. loopx/capabilities/pr_review_queue/review_contract.py +506 -0
  256. loopx/capabilities/registry.py +192 -0
  257. loopx/capabilities/reward_memory/__init__.py +75 -0
  258. loopx/capabilities/reward_memory/application.py +819 -0
  259. loopx/capabilities/reward_memory/architecture.py +572 -0
  260. loopx/capabilities/reward_memory/candidate_review.py +511 -0
  261. loopx/capabilities/reward_memory/cli.py +469 -0
  262. loopx/capabilities/reward_memory/dogfood.py +574 -0
  263. loopx/capabilities/reward_memory/evaluation.py +296 -0
  264. loopx/capabilities/reward_memory/evaluation_fixtures.py +362 -0
  265. loopx/capabilities/reward_memory/experiment.py +567 -0
  266. loopx/capabilities/reward_memory/health.py +222 -0
  267. loopx/capabilities/reward_memory/ingestion.py +519 -0
  268. loopx/capabilities/reward_memory/registry.py +600 -0
  269. loopx/capabilities/reward_memory/runtime_hooks.py +312 -0
  270. loopx/capabilities/reward_memory/scoped_feedback.py +173 -0
  271. loopx/capabilities/semantic_preference/__init__.py +12 -0
  272. loopx/capabilities/semantic_preference/cli.py +189 -0
  273. loopx/capabilities/semantic_preference/contract.py +592 -0
  274. loopx/capabilities/semantic_preference/reward_memory.py +62 -0
  275. loopx/capabilities/value_connectors/__init__.py +1 -0
  276. loopx/capabilities/value_connectors/cli.py +401 -0
  277. loopx/capabilities/value_connectors/finance_extension_migration.py +108 -0
  278. loopx/capabilities/value_connectors/install_check.py +147 -0
  279. loopx/capabilities/value_connectors/planner.py +733 -0
  280. loopx/capabilities/value_connectors/source_map.py +446 -0
  281. loopx/claude_goal_baseline.py +138 -0
  282. loopx/claude_goal_mode/__init__.py +23 -0
  283. loopx/claude_goal_mode/hooks/goal_policy.py +212 -0
  284. loopx/claude_goal_mode/hooks/goal_state.py +139 -0
  285. loopx/claude_goal_mode/mcp/loopx_mcp.py +167 -0
  286. loopx/claude_goal_mode/scripts/connect.py +103 -0
  287. loopx/claude_goal_mode/scripts/goalmode_cmd.py +241 -0
  288. loopx/claude_goal_mode/scripts/install.py +328 -0
  289. loopx/claude_goal_mode/statusline/goal_status.py +97 -0
  290. loopx/cli.py +836 -0
  291. loopx/cli_commands/__init__.py +334 -0
  292. loopx/cli_commands/_host_thread.py +13 -0
  293. loopx/cli_commands/agentissue_runner_flow.py +447 -0
  294. loopx/cli_commands/agents_last_exam.py +160 -0
  295. loopx/cli_commands/agents_last_exam_baked_input.py +302 -0
  296. loopx/cli_commands/agents_last_exam_host_codex.py +374 -0
  297. loopx/cli_commands/agents_last_exam_launch_dry_run.py +372 -0
  298. loopx/cli_commands/agents_last_exam_local_plan.py +322 -0
  299. loopx/cli_commands/agents_last_exam_runner_source.py +352 -0
  300. loopx/cli_commands/agents_last_exam_task_material.py +335 -0
  301. loopx/cli_commands/agents_last_exam_validation_gate.py +236 -0
  302. loopx/cli_commands/benchmark_boundary.py +499 -0
  303. loopx/cli_commands/benchmark_dispatch.py +161 -0
  304. loopx/cli_commands/benchmark_release_outcome.py +123 -0
  305. loopx/cli_commands/benchmark_review_lifecycle.py +1275 -0
  306. loopx/cli_commands/benchmark_run_ledger.py +763 -0
  307. loopx/cli_commands/benchmark_run_ledger_case_analysis.py +249 -0
  308. loopx/cli_commands/benchmark_run_ledger_classification.py +45 -0
  309. loopx/cli_commands/benchmark_run_ledger_maintenance.py +486 -0
  310. loopx/cli_commands/benchmark_run_ledger_maintenance_registration.py +342 -0
  311. loopx/cli_commands/benchmark_run_ledger_maintenance_rendering.py +233 -0
  312. loopx/cli_commands/benchmark_run_ledger_parity.py +92 -0
  313. loopx/cli_commands/bootstrap_connect.py +238 -0
  314. loopx/cli_commands/canary.py +707 -0
  315. loopx/cli_commands/canary_release_qualification.py +79 -0
  316. loopx/cli_commands/capability.py +96 -0
  317. loopx/cli_commands/doctor.py +43 -0
  318. loopx/cli_commands/dreaming.py +143 -0
  319. loopx/cli_commands/edgebench.py +205 -0
  320. loopx/cli_commands/evidence_log.py +275 -0
  321. loopx/cli_commands/explore.py +989 -0
  322. loopx/cli_commands/explore_planning_commands.py +157 -0
  323. loopx/cli_commands/extension.py +271 -0
  324. loopx/cli_commands/first_run_report.py +73 -0
  325. loopx/cli_commands/goal_channel.py +656 -0
  326. loopx/cli_commands/handoff_mode.py +158 -0
  327. loopx/cli_commands/history.py +622 -0
  328. loopx/cli_commands/host_mode_plan.py +113 -0
  329. loopx/cli_commands/lark_inbox.py +431 -0
  330. loopx/cli_commands/lark_kanban.py +629 -0
  331. loopx/cli_commands/ml_experiment.py +321 -0
  332. loopx/cli_commands/multi_agent.py +211 -0
  333. loopx/cli_commands/opencode2_goal_worker.py +217 -0
  334. loopx/cli_commands/pr_review.py +167 -0
  335. loopx/cli_commands/presentation.py +218 -0
  336. loopx/cli_commands/preset.py +96 -0
  337. loopx/cli_commands/project.py +150 -0
  338. loopx/cli_commands/project_lifecycle.py +915 -0
  339. loopx/cli_commands/quota.py +859 -0
  340. loopx/cli_commands/quota_registration.py +241 -0
  341. loopx/cli_commands/quota_request.py +113 -0
  342. loopx/cli_commands/ready_score.py +110 -0
  343. loopx/cli_commands/registry_admin.py +975 -0
  344. loopx/cli_commands/registry_admin_configure.py +344 -0
  345. loopx/cli_commands/registry_admin_peer.py +84 -0
  346. loopx/cli_commands/registry_authority.py +218 -0
  347. loopx/cli_commands/review_batch.py +146 -0
  348. loopx/cli_commands/slash_commands.py +145 -0
  349. loopx/cli_commands/start_goal.py +251 -0
  350. loopx/cli_commands/starter.py +175 -0
  351. loopx/cli_commands/starter_bootstrap.py +179 -0
  352. loopx/cli_commands/starter_bootstrap_registration.py +198 -0
  353. loopx/cli_commands/starter_runtime_idle.py +107 -0
  354. loopx/cli_commands/starter_scheduler.py +207 -0
  355. loopx/cli_commands/starter_session_runtime.py +152 -0
  356. loopx/cli_commands/starter_visible_common.py +54 -0
  357. loopx/cli_commands/starter_visible_driver.py +161 -0
  358. loopx/cli_commands/starter_visible_pilot.py +278 -0
  359. loopx/cli_commands/status.py +867 -0
  360. loopx/cli_commands/status_registration.py +239 -0
  361. loopx/cli_commands/summary_all.py +222 -0
  362. loopx/cli_commands/support_control.py +809 -0
  363. loopx/cli_commands/support_control_registry.py +68 -0
  364. loopx/cli_commands/support_control_supervisor.py +289 -0
  365. loopx/cli_commands/task_lease.py +306 -0
  366. loopx/cli_commands/terminal_bench_adapter.py +717 -0
  367. loopx/cli_commands/terminal_bench_environment_result.py +1246 -0
  368. loopx/cli_commands/todo.py +940 -0
  369. loopx/cli_commands/todo_argument_validation.py +572 -0
  370. loopx/cli_commands/todo_event.py +114 -0
  371. loopx/cli_commands/turn.py +804 -0
  372. loopx/cli_commands/version.py +46 -0
  373. loopx/cli_commands/worker_bridge.py +659 -0
  374. loopx/cli_rollout.py +314 -0
  375. loopx/codex_cli_goal_tui.py +672 -0
  376. loopx/codex_cli_probe.py +1530 -0
  377. loopx/codex_cli_probe_markdown.py +935 -0
  378. loopx/codex_cli_runtime_probe.py +733 -0
  379. loopx/codex_cli_scheduler.py +564 -0
  380. loopx/codex_goal_baseline.py +620 -0
  381. loopx/configuration_catalog.py +617 -0
  382. loopx/configure_goal.py +1375 -0
  383. loopx/contract.py +996 -0
  384. loopx/control_plane/__init__.py +71 -0
  385. loopx/control_plane/agents/__init__.py +1 -0
  386. loopx/control_plane/agents/agent_lane_recommendation.py +516 -0
  387. loopx/control_plane/agents/agent_scope.py +1578 -0
  388. loopx/control_plane/agents/agent_scope_frontier.py +60 -0
  389. loopx/control_plane/agents/capability_gate.py +531 -0
  390. loopx/control_plane/agents/identity.py +140 -0
  391. loopx/control_plane/agents/legacy_migration.py +169 -0
  392. loopx/control_plane/agents/management_projection.py +658 -0
  393. loopx/control_plane/agents/material_frontier.py +608 -0
  394. loopx/control_plane/agents/material_handoff.py +156 -0
  395. loopx/control_plane/agents/multi_agent/__init__.py +1 -0
  396. loopx/control_plane/agents/multi_agent/codex_executable.py +207 -0
  397. loopx/control_plane/agents/multi_agent/collective_round_ledger.py +387 -0
  398. loopx/control_plane/agents/multi_agent/contract.py +474 -0
  399. loopx/control_plane/agents/multi_agent/recipe.py +110 -0
  400. loopx/control_plane/agents/multi_agent/role_successor.py +297 -0
  401. loopx/control_plane/agents/multi_agent/runtime_scripts.py +426 -0
  402. loopx/control_plane/agents/multi_agent/visible_launch_policy.py +149 -0
  403. loopx/control_plane/agents/multi_agent/visible_wake_scheduler.py +392 -0
  404. loopx/control_plane/agents/profile.py +216 -0
  405. loopx/control_plane/agents/runtime_model.py +73 -0
  406. loopx/control_plane/agents/subagent_activity.py +164 -0
  407. loopx/control_plane/agents/supervisor.py +544 -0
  408. loopx/control_plane/agents/supervisor_events.py +462 -0
  409. loopx/control_plane/agents/supervisor_inject.py +204 -0
  410. loopx/control_plane/agents/work_mode.py +56 -0
  411. loopx/control_plane/agents/workspace_guard.py +364 -0
  412. loopx/control_plane/effect_program.py +644 -0
  413. loopx/control_plane/goals/__init__.py +1 -0
  414. loopx/control_plane/goals/active_state_event_projection.py +103 -0
  415. loopx/control_plane/goals/active_state_metadata.py +47 -0
  416. loopx/control_plane/goals/active_state_sections.py +58 -0
  417. loopx/control_plane/goals/configure_goal_service.py +354 -0
  418. loopx/control_plane/goals/contract_health.py +132 -0
  419. loopx/control_plane/goals/dreaming.py +152 -0
  420. loopx/control_plane/goals/global_registry_health.py +199 -0
  421. loopx/control_plane/goals/global_registry_shadow.py +33 -0
  422. loopx/control_plane/goals/goal_channel.py +34 -0
  423. loopx/control_plane/goals/goal_channel_projection.py +560 -0
  424. loopx/control_plane/goals/goal_frontier/__init__.py +1917 -0
  425. loopx/control_plane/goals/goal_frontier/ack_policy.py +149 -0
  426. loopx/control_plane/goals/goal_frontier/outcome_continuity.py +437 -0
  427. loopx/control_plane/goals/goal_frontier/replan_rules.py +210 -0
  428. loopx/control_plane/goals/goal_frontier/semantic_history.py +314 -0
  429. loopx/control_plane/goals/goal_frontier/terminal.py +180 -0
  430. loopx/control_plane/goals/goal_vision.py +443 -0
  431. loopx/control_plane/goals/goal_vision_policy.py +36 -0
  432. loopx/control_plane/goals/goal_vision_state.py +62 -0
  433. loopx/control_plane/goals/goal_vision_wait.py +290 -0
  434. loopx/control_plane/goals/path_resolution.py +20 -0
  435. loopx/control_plane/goals/start_contract.py +206 -0
  436. loopx/control_plane/goals/vision_checkpoint.py +92 -0
  437. loopx/control_plane/handoff/__init__.py +1 -0
  438. loopx/control_plane/handoff/cross_runtime_impl_review.py +311 -0
  439. loopx/control_plane/handoff/delivery_contract.py +161 -0
  440. loopx/control_plane/handoff/handoff_runs.py +71 -0
  441. loopx/control_plane/handoff/project_handoff.py +155 -0
  442. loopx/control_plane/handoff/review_batch.py +463 -0
  443. loopx/control_plane/handoff/review_packet_context.py +216 -0
  444. loopx/control_plane/heartbeat/agent.py +173 -0
  445. loopx/control_plane/heartbeat/budget.py +66 -0
  446. loopx/control_plane/heartbeat/builder.py +501 -0
  447. loopx/control_plane/heartbeat/host.py +64 -0
  448. loopx/control_plane/heartbeat/rules.py +68 -0
  449. loopx/control_plane/heartbeat/task_body.py +759 -0
  450. loopx/control_plane/heartbeat/visible_goal.py +86 -0
  451. loopx/control_plane/projects/__init__.py +1 -0
  452. loopx/control_plane/projects/contract.py +25 -0
  453. loopx/control_plane/projects/registry.py +663 -0
  454. loopx/control_plane/quota/__init__.py +1 -0
  455. loopx/control_plane/quota/cli_projection.py +704 -0
  456. loopx/control_plane/quota/decision_summary.py +431 -0
  457. loopx/control_plane/quota/effect_program.py +152 -0
  458. loopx/control_plane/quota/error_codes.py +19 -0
  459. loopx/control_plane/quota/goal_boundary.py +464 -0
  460. loopx/control_plane/quota/heartbeat_receipt.py +277 -0
  461. loopx/control_plane/quota/heartbeat_recommendation.py +718 -0
  462. loopx/control_plane/quota/host_poll_receipts.py +162 -0
  463. loopx/control_plane/quota/live_decision.py +142 -0
  464. loopx/control_plane/quota/monitor_poll.py +786 -0
  465. loopx/control_plane/quota/policy_constants.py +40 -0
  466. loopx/control_plane/quota/projection_repair.py +262 -0
  467. loopx/control_plane/quota/recent_runs.py +210 -0
  468. loopx/control_plane/quota/scheduler_ack.py +490 -0
  469. loopx/control_plane/quota/selected_todo_projection.py +139 -0
  470. loopx/control_plane/quota/settlement.py +437 -0
  471. loopx/control_plane/quota/settlement_cli.py +246 -0
  472. loopx/control_plane/quota/settlement_validation.py +64 -0
  473. loopx/control_plane/quota/settlement_workspace_causality.py +180 -0
  474. loopx/control_plane/quota/should_run.py +249 -0
  475. loopx/control_plane/quota/should_run_packet.py +1165 -0
  476. loopx/control_plane/quota/should_run_prepare.py +675 -0
  477. loopx/control_plane/quota/slot_accounting.py +1123 -0
  478. loopx/control_plane/quota/spend_sources.py +11 -0
  479. loopx/control_plane/quota/stall_repair.py +397 -0
  480. loopx/control_plane/quota/states.py +29 -0
  481. loopx/control_plane/quota/task_orchestration.py +448 -0
  482. loopx/control_plane/quota/task_orchestration_admission.py +497 -0
  483. loopx/control_plane/quota/turn_envelope.py +889 -0
  484. loopx/control_plane/quota/usage_summary.py +140 -0
  485. loopx/control_plane/reward_memory.py +43 -0
  486. loopx/control_plane/runtime/__init__.py +2 -0
  487. loopx/control_plane/runtime/active_user_assisted_pilot.py +275 -0
  488. loopx/control_plane/runtime/agent_scoped_evidence_log.py +435 -0
  489. loopx/control_plane/runtime/decision_freshness.py +203 -0
  490. loopx/control_plane/runtime/event_ledger.py +197 -0
  491. loopx/control_plane/runtime/event_store_migration_bridge.py +196 -0
  492. loopx/control_plane/runtime/goal_project_route.py +70 -0
  493. loopx/control_plane/runtime/local_state_write_correctness.py +242 -0
  494. loopx/control_plane/runtime/promotion_readiness.py +152 -0
  495. loopx/control_plane/runtime/public_safety.py +120 -0
  496. loopx/control_plane/runtime/run_artifacts.py +78 -0
  497. loopx/control_plane/runtime/run_compaction.py +397 -0
  498. loopx/control_plane/runtime/run_context_retention.py +241 -0
  499. loopx/control_plane/runtime/run_history.py +132 -0
  500. loopx/control_plane/runtime/run_index_duplicates.py +205 -0
  501. loopx/control_plane/runtime/run_index_rebuild.py +263 -0
  502. loopx/control_plane/runtime/run_ingest_health.py +336 -0
  503. loopx/control_plane/runtime/runtime_projection_route.py +624 -0
  504. loopx/control_plane/runtime/runtime_projection_writer.py +98 -0
  505. loopx/control_plane/runtime/session_runtime.py +339 -0
  506. loopx/control_plane/runtime/shared_runtime_material_projection.py +332 -0
  507. loopx/control_plane/runtime/shared_runtime_refresh_projection.py +183 -0
  508. loopx/control_plane/runtime/stale_latest_run.py +90 -0
  509. loopx/control_plane/runtime/status_classifications.py +49 -0
  510. loopx/control_plane/runtime/status_projection_cache.py +235 -0
  511. loopx/control_plane/runtime/stride_observation.py +144 -0
  512. loopx/control_plane/runtime/time.py +39 -0
  513. loopx/control_plane/runtime/trajectory_hygiene.py +149 -0
  514. loopx/control_plane/runtime/validation_command.py +69 -0
  515. loopx/control_plane/scheduler/__init__.py +1 -0
  516. loopx/control_plane/scheduler/ack.py +329 -0
  517. loopx/control_plane/scheduler/arbitration.py +188 -0
  518. loopx/control_plane/scheduler/automation_liveness.py +183 -0
  519. loopx/control_plane/scheduler/execution_context.py +555 -0
  520. loopx/control_plane/scheduler/external_evidence_observation.py +428 -0
  521. loopx/control_plane/scheduler/monitor_display.py +143 -0
  522. loopx/control_plane/scheduler/monitor_poll_policy.py +161 -0
  523. loopx/control_plane/scheduler/monitor_poll_writeback.py +351 -0
  524. loopx/control_plane/scheduler/monitor_target.py +64 -0
  525. loopx/control_plane/scheduler/monitor_todo.py +146 -0
  526. loopx/control_plane/scheduler/monitor_wait.py +237 -0
  527. loopx/control_plane/scheduler/scheduler_hint.py +1284 -0
  528. loopx/control_plane/scheduler/state.py +354 -0
  529. loopx/control_plane/scheduler/state_transition_rules.py +179 -0
  530. loopx/control_plane/scheduler/time.py +10 -0
  531. loopx/control_plane/settlement_driver.py +293 -0
  532. loopx/control_plane/status/__init__.py +6 -0
  533. loopx/control_plane/status/active_state_projection.py +105 -0
  534. loopx/control_plane/status/agent_lane_projection.py +375 -0
  535. loopx/control_plane/status/attention_projection.py +74 -0
  536. loopx/control_plane/status/autonomous_replan_projection.py +103 -0
  537. loopx/control_plane/status/collection.py +140 -0
  538. loopx/control_plane/status/contract_projection.py +31 -0
  539. loopx/control_plane/status/dreaming_projection.py +52 -0
  540. loopx/control_plane/status/goal_attention_projection.py +157 -0
  541. loopx/control_plane/status/lifecycle_projection.py +110 -0
  542. loopx/control_plane/status/monitor_display_projection.py +69 -0
  543. loopx/control_plane/status/registry_health_projection.py +75 -0
  544. loopx/control_plane/status/run_projection.py +70 -0
  545. loopx/control_plane/status/runtime_summaries.py +161 -0
  546. loopx/control_plane/testing/__init__.py +1 -0
  547. loopx/control_plane/testing/actual_default_model_behavior_portfolio.py +1371 -0
  548. loopx/control_plane/testing/canary_harness.py +182 -0
  549. loopx/control_plane/testing/capability_monitor_repair_tool_behavior.py +674 -0
  550. loopx/control_plane/testing/cli_output_budget.py +807 -0
  551. loopx/control_plane/testing/cli_output_differential.py +250 -0
  552. loopx/control_plane/testing/cli_output_semantics.py +87 -0
  553. loopx/control_plane/testing/control_plane_composition_scenarios.py +225 -0
  554. loopx/control_plane/testing/decision_replay.py +268 -0
  555. loopx/control_plane/testing/doubao_model_behavior_actor.py +559 -0
  556. loopx/control_plane/testing/model_behavior_corpus.py +344 -0
  557. loopx/control_plane/testing/model_behavior_qualification.py +769 -0
  558. loopx/control_plane/testing/model_behavior_retained_cases.py +235 -0
  559. loopx/control_plane/testing/model_tool_behavior.py +536 -0
  560. loopx/control_plane/testing/onboarding_model_behavior_qualification.py +642 -0
  561. loopx/control_plane/testing/quota_fixtures.py +208 -0
  562. loopx/control_plane/testing/quota_should_run_parity.py +57 -0
  563. loopx/control_plane/testing/release_commit_qualification.py +671 -0
  564. loopx/control_plane/testing/replan_semantic_action_behavior.py +1302 -0
  565. loopx/control_plane/testing/scoped_gate_successor_tool_behavior.py +527 -0
  566. loopx/control_plane/testing/selected_todo_tool_behavior.py +1002 -0
  567. loopx/control_plane/testing/terminal_settlement_tool_behavior.py +656 -0
  568. loopx/control_plane/todos/__init__.py +1 -0
  569. loopx/control_plane/todos/active_state_editing.py +296 -0
  570. loopx/control_plane/todos/active_state_todo_parser.py +138 -0
  571. loopx/control_plane/todos/active_state_todos.py +175 -0
  572. loopx/control_plane/todos/addition.py +103 -0
  573. loopx/control_plane/todos/claim_visibility.py +253 -0
  574. loopx/control_plane/todos/completed_archive.py +139 -0
  575. loopx/control_plane/todos/completion_fence.py +49 -0
  576. loopx/control_plane/todos/completion_policy.py +153 -0
  577. loopx/control_plane/todos/completion_validation.py +248 -0
  578. loopx/control_plane/todos/completion_validation_accountability.py +27 -0
  579. loopx/control_plane/todos/completion_validation_projection.py +57 -0
  580. loopx/control_plane/todos/contract.py +1476 -0
  581. loopx/control_plane/todos/decision_scope.py +554 -0
  582. loopx/control_plane/todos/deferred_resume.py +546 -0
  583. loopx/control_plane/todos/durable_completion.py +201 -0
  584. loopx/control_plane/todos/event_writeback.py +484 -0
  585. loopx/control_plane/todos/frontier_deadline.py +132 -0
  586. loopx/control_plane/todos/handoff_gate.py +283 -0
  587. loopx/control_plane/todos/handoff_mode.py +444 -0
  588. loopx/control_plane/todos/handoff_note.py +202 -0
  589. loopx/control_plane/todos/line_update.py +361 -0
  590. loopx/control_plane/todos/list_projection.py +205 -0
  591. loopx/control_plane/todos/markdown.py +199 -0
  592. loopx/control_plane/todos/monitor_metadata.py +88 -0
  593. loopx/control_plane/todos/mutation_authority.py +299 -0
  594. loopx/control_plane/todos/projection.py +655 -0
  595. loopx/control_plane/todos/quota_summary.py +1138 -0
  596. loopx/control_plane/todos/route_continuation.py +267 -0
  597. loopx/control_plane/todos/succession_warning.py +174 -0
  598. loopx/control_plane/todos/summary_item.py +223 -0
  599. loopx/control_plane/todos/text.py +30 -0
  600. loopx/control_plane/todos/todo_index.py +226 -0
  601. loopx/control_plane/todos/todo_summary.py +1458 -0
  602. loopx/control_plane/todos/unblock_resume.py +326 -0
  603. loopx/control_plane/todos/user_gate.py +263 -0
  604. loopx/control_plane/todos/write_hint.py +63 -0
  605. loopx/control_plane/todos/write_policy.py +135 -0
  606. loopx/control_plane/turn_driver/__init__.py +85 -0
  607. loopx/control_plane/turn_driver/codex_cli.py +502 -0
  608. loopx/control_plane/turn_driver/driver.py +355 -0
  609. loopx/control_plane/turn_driver/executor.py +1468 -0
  610. loopx/control_plane/turn_driver/loop_controller.py +669 -0
  611. loopx/control_plane/turn_driver/settlement.py +318 -0
  612. loopx/control_plane/turn_driver/transaction.py +375 -0
  613. loopx/control_plane/work_items/__init__.py +1 -0
  614. loopx/control_plane/work_items/attention_fields.py +56 -0
  615. loopx/control_plane/work_items/attention_item.py +77 -0
  616. loopx/control_plane/work_items/attention_queue.py +322 -0
  617. loopx/control_plane/work_items/attention_routing.py +213 -0
  618. loopx/control_plane/work_items/autonomous_candidates.py +135 -0
  619. loopx/control_plane/work_items/autonomous_replan_ack.py +276 -0
  620. loopx/control_plane/work_items/autonomous_replan_obligation.py +786 -0
  621. loopx/control_plane/work_items/backlog_hygiene.py +59 -0
  622. loopx/control_plane/work_items/capability_monitor_fallback.py +221 -0
  623. loopx/control_plane/work_items/delivery_batch_scale.py +66 -0
  624. loopx/control_plane/work_items/delivery_outcome.py +152 -0
  625. loopx/control_plane/work_items/delivery_signals.py +113 -0
  626. loopx/control_plane/work_items/execution_obligation.py +235 -0
  627. loopx/control_plane/work_items/goal_route_hint.py +320 -0
  628. loopx/control_plane/work_items/interaction_contract.py +1540 -0
  629. loopx/control_plane/work_items/issue_meta_surface.py +159 -0
  630. loopx/control_plane/work_items/lifecycle.py +139 -0
  631. loopx/control_plane/work_items/operator_inbox.py +266 -0
  632. loopx/control_plane/work_items/outcome_followthrough.py +69 -0
  633. loopx/control_plane/work_items/primary_action.py +326 -0
  634. loopx/control_plane/work_items/progress_observation.py +630 -0
  635. loopx/control_plane/work_items/project_asset.py +675 -0
  636. loopx/control_plane/work_items/repair_delta.py +693 -0
  637. loopx/control_plane/work_items/runtime_capability_reentry.py +168 -0
  638. loopx/control_plane/work_items/semantic_replan_writeback.py +177 -0
  639. loopx/control_plane/work_items/status_contract.py +49 -0
  640. loopx/control_plane/work_items/task_graph.py +1046 -0
  641. loopx/control_plane/work_items/task_lease.py +1254 -0
  642. loopx/control_plane/work_items/task_lease_settlement.py +422 -0
  643. loopx/control_plane/work_items/work_lane.py +510 -0
  644. loopx/control_plane/work_items/work_lane_context.py +161 -0
  645. loopx/demo.py +247 -0
  646. loopx/diagnose.py +633 -0
  647. loopx/doctor.py +1251 -0
  648. loopx/domain_packs/__init__.py +1 -0
  649. loopx/domain_packs/issue_fix.py +571 -0
  650. loopx/domain_packs/ml_experiment.py +854 -0
  651. loopx/domain_state.py +137 -0
  652. loopx/dreaming.py +706 -0
  653. loopx/entrypoint.py +16 -0
  654. loopx/event_sourced_state.py +981 -0
  655. loopx/execution_profile.py +286 -0
  656. loopx/experiments/__init__.py +1 -0
  657. loopx/experiments/planner_worker/__init__.py +1 -0
  658. loopx/experiments/planner_worker/contract.py +523 -0
  659. loopx/experiments/planner_worker/runtime.py +391 -0
  660. loopx/experiments/planner_worker/traex.py +461 -0
  661. loopx/explore_graph.py +11 -0
  662. loopx/extensions/__init__.py +1 -0
  663. loopx/extensions/bundled.py +28 -0
  664. loopx/extensions/execution_envelope.py +126 -0
  665. loopx/extensions/lark/__init__.py +11 -0
  666. loopx/extensions/lark/event_collector.py +478 -0
  667. loopx/extensions/lark/event_collector_runtime.py +506 -0
  668. loopx/extensions/lark/event_inbox.py +454 -0
  669. loopx/extensions/lark/extension.toml +88 -0
  670. loopx/extensions/lark/goal_channel.py +44 -0
  671. loopx/extensions/lark/goal_channel_contracts.py +388 -0
  672. loopx/extensions/lark/goal_channel_lifecycle.py +218 -0
  673. loopx/extensions/lark/goal_channel_runtime.py +792 -0
  674. loopx/extensions/lark/goal_channel_setup.py +805 -0
  675. loopx/extensions/lark/goal_channel_targets.py +215 -0
  676. loopx/extensions/lark/goal_channel_transport.py +281 -0
  677. loopx/extensions/lark/inbox_reactions.py +650 -0
  678. loopx/extensions/lark/inbox_reply.py +430 -0
  679. loopx/extensions/lark/presentation/__init__.py +11 -0
  680. loopx/extensions/lark/presentation/explore_results.py +2276 -0
  681. loopx/extensions/lark/presentation/explore_singleflight.py +127 -0
  682. loopx/extensions/lark/presentation/explore_source_guard.py +121 -0
  683. loopx/extensions/lark/presentation/explore_stage_document.py +703 -0
  684. loopx/extensions/lark/presentation/explore_visual_integrity.py +122 -0
  685. loopx/extensions/lark/presentation/explore_visual_readback.py +452 -0
  686. loopx/extensions/lark/presentation/explore_visual_styles.py +156 -0
  687. loopx/extensions/lark/presentation/issue_fix_surface.py +612 -0
  688. loopx/extensions/lark/presentation/kanban.py +2791 -0
  689. loopx/extensions/lark/presentation/message_card.py +112 -0
  690. loopx/extensions/lark/presentation/periodic_report.py +261 -0
  691. loopx/extensions/lark/presentation/projection_rows.py +600 -0
  692. loopx/extensions/lark/presentation/record_io.py +95 -0
  693. loopx/extensions/lark/presentation/sync_receipt.py +145 -0
  694. loopx/extensions/lark/private_json.py +40 -0
  695. loopx/extensions/lark/provider.py +86 -0
  696. loopx/extensions/lark/reviewer_notification.py +604 -0
  697. loopx/extensions/manifest.py +385 -0
  698. loopx/extensions/openviking_periodic_report/__init__.py +17 -0
  699. loopx/extensions/openviking_periodic_report/activation.py +173 -0
  700. loopx/extensions/openviking_periodic_report/extension.toml +17 -0
  701. loopx/extensions/openviking_periodic_report/provider.py +355 -0
  702. loopx/extensions/openviking_periodic_report/sink.py +117 -0
  703. loopx/extensions/openviking_semantic_preference/__init__.py +5 -0
  704. loopx/extensions/openviking_semantic_preference/extension.toml +16 -0
  705. loopx/extensions/openviking_semantic_preference/history_export.py +484 -0
  706. loopx/extensions/openviking_semantic_preference/project_peer.py +68 -0
  707. loopx/extensions/openviking_semantic_preference/provider.py +312 -0
  708. loopx/extensions/presentation.py +979 -0
  709. loopx/extensions/process_runtime.py +204 -0
  710. loopx/extensions/readiness.py +168 -0
  711. loopx/extensions/runtime.py +931 -0
  712. loopx/extensions/scaffold.py +335 -0
  713. loopx/feedback.py +581 -0
  714. loopx/file_lock.py +382 -0
  715. loopx/global_registry.py +842 -0
  716. loopx/global_risks.py +970 -0
  717. loopx/global_todos.py +568 -0
  718. loopx/handoff_budget.py +28 -0
  719. loopx/heartbeat_prequota.py +80 -0
  720. loopx/heartbeat_prompt.py +159 -0
  721. loopx/help_surface.py +516 -0
  722. loopx/history.py +1507 -0
  723. loopx/host_loop_activation.py +1311 -0
  724. loopx/host_mode_planner.py +991 -0
  725. loopx/install_contract.py +1 -0
  726. loopx/interface_budget.py +196 -0
  727. loopx/long_task_cadence.py +208 -0
  728. loopx/materials.py +185 -0
  729. loopx/ml_experiment.py +3 -0
  730. loopx/onboarding.py +214 -0
  731. loopx/opencode2_goal_mode/README.md +81 -0
  732. loopx/opencode2_goal_mode/__init__.py +9 -0
  733. loopx/opencode2_goal_mode/opencode2-goal-worker.mjs +1018 -0
  734. loopx/opencode_goal_mode/README.md +99 -0
  735. loopx/opencode_goal_mode/__init__.py +13 -0
  736. loopx/opencode_goal_mode/goal-bridge-runtime.mjs +858 -0
  737. loopx/opencode_goal_mode/loopx-goal.js +8 -0
  738. loopx/operator_gate.py +420 -0
  739. loopx/orchestration.py +127 -0
  740. loopx/paths.py +59 -0
  741. loopx/pi_goal_mode/README.md +67 -0
  742. loopx/pi_goal_mode/__init__.py +13 -0
  743. loopx/pi_goal_mode/loopx-goal.ts +254 -0
  744. loopx/pi_goal_mode/pi-goal-loop-runtime.mjs +574 -0
  745. loopx/pr_review.py +1206 -0
  746. loopx/presentation/__init__.py +1 -0
  747. loopx/presentation/explore_views.py +1334 -0
  748. loopx/presentation/markdown.py +61 -0
  749. loopx/presentation/projection_source_reconcile.py +140 -0
  750. loopx/presentation/public_safety.py +42 -0
  751. loopx/presentation/renderers/__init__.py +17 -0
  752. loopx/presentation/renderers/goal_channel_html.py +269 -0
  753. loopx/presentation/renderers/periodic_report_html.py +786 -0
  754. loopx/presentation/renderers/periodic_report_markdown.py +184 -0
  755. loopx/presentation/renderers/quota_event_markdown.py +116 -0
  756. loopx/presentation/renderers/quota_markdown.py +1112 -0
  757. loopx/presentation/renderers/status_markdown.py +1570 -0
  758. loopx/presentation/renderers/trajectory_hygiene_markdown.py +39 -0
  759. loopx/presentation/renderers/turn_envelope_markdown.py +33 -0
  760. loopx/presentation/sinks/__init__.py +5 -0
  761. loopx/presentation/sinks/openviking_periodic_report.py +7 -0
  762. loopx/presentation/static_site.py +691 -0
  763. loopx/presets.py +369 -0
  764. loopx/project_alias.py +217 -0
  765. loopx/project_map.py +589 -0
  766. loopx/project_prompt.py +1153 -0
  767. loopx/project_skill_cli.py +125 -0
  768. loopx/project_skill_delivery.py +470 -0
  769. loopx/project_uninstall.py +462 -0
  770. loopx/promotion_gate.py +197 -0
  771. loopx/quota.py +1197 -0
  772. loopx/ready_score.py +413 -0
  773. loopx/registry.py +621 -0
  774. loopx/registry_writability.py +64 -0
  775. loopx/release_candidate.py +148 -0
  776. loopx/release_manifest.py +316 -0
  777. loopx/repository_identity.py +100 -0
  778. loopx/review_packet.py +1024 -0
  779. loopx/rollout_event_log.py +505 -0
  780. loopx/runtime.py +112 -0
  781. loopx/self_update.py +750 -0
  782. loopx/session_runtime.py +418 -0
  783. loopx/skill_install_readback.py +500 -0
  784. loopx/slash_command_install.py +1393 -0
  785. loopx/slash_commands.py +264 -0
  786. loopx/state_backup.py +573 -0
  787. loopx/state_migration.py +350 -0
  788. loopx/state_projection.py +809 -0
  789. loopx/state_refresh.py +1416 -0
  790. loopx/status.py +1383 -0
  791. loopx/status_server.py +935 -0
  792. loopx/summary_all.py +725 -0
  793. loopx/terminal_bench_agent.py +2056 -0
  794. loopx/thread_agent_binding.py +408 -0
  795. loopx/todo_followups.py +168 -0
  796. loopx/todo_suggestion_prompt.py +204 -0
  797. loopx/todos.py +2229 -0
  798. loopx/turn_identity.py +17 -0
  799. loopx/upgrade.py +1083 -0
  800. loopx/visible_governance.py +667 -0
  801. loopx/visible_multi_agent_launcher.py +1253 -0
  802. loopx/visible_multi_agent_tmux.py +429 -0
  803. loopx/worker_bridge.py +1574 -0
  804. loopx-0.4.8.dist-info/METADATA +708 -0
  805. loopx-0.4.8.dist-info/RECORD +811 -0
  806. loopx-0.4.8.dist-info/WHEEL +5 -0
  807. loopx-0.4.8.dist-info/entry_points.txt +5 -0
  808. loopx-0.4.8.dist-info/licenses/LICENSE +202 -0
  809. loopx-0.4.8.dist-info/licenses/LICENSE-MIT +21 -0
  810. loopx-0.4.8.dist-info/licenses/NOTICE +6 -0
  811. loopx-0.4.8.dist-info/top_level.txt +1 -0
@@ -0,0 +1,1956 @@
1
+ from __future__ import annotations
2
+
3
+ import ipaddress
4
+ from collections import Counter
5
+ from collections.abc import Mapping, Sequence
6
+ from typing import Any
7
+ from urllib.error import HTTPError, URLError
8
+ from urllib.parse import urljoin, urlsplit, urlunsplit
9
+ from urllib.request import HTTPRedirectHandler, Request, build_opener
10
+
11
+
12
+ from .connector_packets import (
13
+ source_item_and_trial_from_private_gate_packet,
14
+ source_item_and_trial_from_public_packet,
15
+ )
16
+ from .social_browser_x import build_social_browser_x_provider_packet
17
+ from .markdown import (
18
+ render_content_ops_chatview_report_markdown as render_content_ops_chatview_report_markdown,
19
+ render_content_ops_exploration_plan_markdown as render_content_ops_exploration_plan_markdown,
20
+ render_content_ops_packet_aggregation_markdown as render_content_ops_packet_aggregation_markdown,
21
+ render_content_ops_preview_markdown as render_content_ops_preview_markdown,
22
+ render_content_ops_private_connector_gate_markdown as render_content_ops_private_connector_gate_markdown,
23
+ render_content_ops_public_handle_observation_markdown as render_content_ops_public_handle_observation_markdown,
24
+ render_content_ops_walkthrough_artifact_markdown as render_content_ops_walkthrough_artifact_markdown,
25
+ )
26
+ from .schemas import (
27
+ ANGLE_CANDIDATE_SCHEMA_VERSION,
28
+ CONNECTOR_TRIAL_SCHEMA_VERSION,
29
+ CONTENT_OPS_CHATVIEW_CONNECTOR_REPORT_SCHEMA_VERSION,
30
+ CONTENT_OPS_CONNECTOR_RUNTIME_POLICY_SCHEMA_VERSION,
31
+ CONTENT_OPS_EXPLORATION_PLAN_PACKET_SCHEMA_VERSION,
32
+ CONTENT_OPS_PACKET_AGGREGATION_SCHEMA_VERSION,
33
+ CONTENT_OPS_PREVIEW_PACKET_SCHEMA_VERSION,
34
+ CONTENT_OPS_PRIVATE_CONNECTOR_GATE_PACKET_SCHEMA_VERSION,
35
+ CONTENT_OPS_PRIVATE_CONNECTOR_OWNER_GATE_SCHEMA_VERSION,
36
+ CONTENT_OPS_PUBLIC_HANDLE_OBSERVATION_PACKET_SCHEMA_VERSION,
37
+ CONTENT_OPS_PUBLIC_HANDLE_OBSERVATION_SCHEMA_VERSION,
38
+ CONTENT_OPS_SURFACE_PROJECTION_SCHEMA_VERSION,
39
+ CONTENT_OPS_SURFACE_SCHEMA_VERSION,
40
+ CONTENT_OPS_VALIDATION_SCHEMA_VERSION,
41
+ CONTENT_OPS_WALKTHROUGH_ARTIFACT_SCHEMA_VERSION,
42
+ DRAFT_ITEM_SCHEMA_VERSION,
43
+ EXPLORATION_PLAN_SCHEMA_VERSION,
44
+ FEEDBACK_SIGNAL_SCHEMA_VERSION,
45
+ MATERIAL_MEMORY_SCHEMA_VERSION,
46
+ PUBLISH_GATE_SCHEMA_VERSION,
47
+ SOURCE_ITEM_SCHEMA_VERSION,
48
+ )
49
+
50
+ RAW_MATERIAL_KEY_HINTS = (
51
+ "body",
52
+ "chat",
53
+ "credential",
54
+ "dm",
55
+ "local_path",
56
+ "log",
57
+ "message",
58
+ "raw",
59
+ "secret",
60
+ "token",
61
+ "transcript",
62
+ )
63
+
64
+ ALLOWED_SOURCE_STATUSES = {
65
+ "public",
66
+ "private_needs_review",
67
+ "synthetic_public_safe",
68
+ "unpublished",
69
+ "forbidden_for_public_surface",
70
+ }
71
+ ALLOWED_FRESHNESS = {"fresh", "stale", "unknown"}
72
+ ALLOWED_USE_POLICIES = {
73
+ "summarize_and_transform",
74
+ "metadata_only",
75
+ "do_not_quote",
76
+ "forbidden",
77
+ }
78
+ ALLOWED_ANGLE_DECISIONS = {"draft", "reject", "hold", "needs_review"}
79
+ ALLOWED_DRAFT_STATES = {"outline", "draft", "rewrite", "blocked", "ready_for_review"}
80
+ ALLOWED_FEEDBACK_EFFECTS = {
81
+ "preference_hint",
82
+ "source_boundary_correction",
83
+ "rewrite_todo",
84
+ "publish_decision",
85
+ }
86
+ ALLOWED_PUBLISH_GATE_STATUSES = {
87
+ "blocked_until_user_approval",
88
+ "approved",
89
+ "denied",
90
+ "needs_revision",
91
+ }
92
+ ALLOWED_CONNECTOR_TRIAL_STATES = {
93
+ "candidate",
94
+ "metadata_packet_collected",
95
+ "ready_for_metadata_trial",
96
+ "needs_owner_gate",
97
+ "blocked",
98
+ }
99
+ ALLOWED_CONNECTOR_ACCESS_MODES = {
100
+ "public_metadata_only",
101
+ "private_metadata_only",
102
+ "synthetic_fixture_only",
103
+ }
104
+ ALLOWED_EXPLORATION_READ_STATUSES = {
105
+ "metadata_ready",
106
+ "not_read",
107
+ "blocked_until_owner_gate",
108
+ "compact_result_ready",
109
+ }
110
+ ALLOWED_EXPLORATION_EVIDENCE_QUALITIES = {
111
+ "primary_source_metadata",
112
+ "metadata_only",
113
+ "not_evidence_until_approved",
114
+ "compact_result_metadata",
115
+ }
116
+
117
+
118
+ def _as_mappings(values: Sequence[Mapping[str, Any]] | None) -> list[dict[str, Any]]:
119
+ if not values:
120
+ return []
121
+ return [dict(item) for item in values if isinstance(item, Mapping)]
122
+
123
+
124
+ def _text(value: Any, *, limit: int = 160) -> str | None:
125
+ if value is None:
126
+ return None
127
+ text = " ".join(str(value).split())
128
+ if not text:
129
+ return None
130
+ if len(text) <= limit:
131
+ return text
132
+ return text[: max(0, limit - 1)].rstrip() + "..."
133
+
134
+
135
+ def _ids(items: Sequence[Mapping[str, Any]], key: str) -> set[str]:
136
+ return {
137
+ str(item.get(key))
138
+ for item in items
139
+ if item.get(key) is not None and str(item.get(key)).strip()
140
+ }
141
+
142
+
143
+ def _counter(values: Sequence[Any]) -> dict[str, int]:
144
+ return dict(sorted(Counter(str(value) for value in values if value).items()))
145
+
146
+
147
+ def _normalise_exploration_label(value: Any, label: str) -> str:
148
+ text = _text(value, limit=80)
149
+ if not text:
150
+ raise ValueError(f"{label} is required")
151
+ lowered = text.lower()
152
+ if (
153
+ "http://" in lowered
154
+ or "https://" in lowered
155
+ or "/users/" in lowered
156
+ or "/private/" in lowered
157
+ or "bearer " in lowered
158
+ or "credential" in lowered
159
+ or "secret" in lowered
160
+ ):
161
+ raise ValueError(f"{label} must be a compact public-safe label")
162
+ return text
163
+
164
+
165
+ def _normalise_public_https_url(url: str) -> tuple[str, Any]:
166
+ text = str(url or "").strip()
167
+ parsed = urlsplit(text)
168
+ if parsed.scheme != "https":
169
+ raise ValueError("public handle observation requires an https URL")
170
+ if parsed.username or parsed.password:
171
+ raise ValueError("public handle URL must not include credentials")
172
+ if parsed.query or parsed.fragment:
173
+ raise ValueError("public handle URL must not include query or fragment data")
174
+
175
+ host = parsed.hostname
176
+ if not host:
177
+ raise ValueError("public handle URL must include a host")
178
+ lowered_host = host.lower().rstrip(".")
179
+ if lowered_host in {"localhost"} or lowered_host.endswith((".localhost", ".local")):
180
+ raise ValueError("public handle URL must not target localhost or local hosts")
181
+ if parsed.port not in (None, 443):
182
+ raise ValueError("public handle URL must use the default https port")
183
+
184
+ try:
185
+ address = ipaddress.ip_address(lowered_host.strip("[]"))
186
+ except ValueError:
187
+ address = None
188
+ if address is not None and (
189
+ address.is_private
190
+ or address.is_loopback
191
+ or address.is_link_local
192
+ or address.is_multicast
193
+ or address.is_reserved
194
+ or address.is_unspecified
195
+ ):
196
+ raise ValueError("public handle URL must not target private or local addresses")
197
+
198
+ path = parsed.path or "/"
199
+ normalised = urlunsplit(("https", parsed.netloc, path, "", ""))
200
+ return normalised, parsed._replace(path=path, query="", fragment="")
201
+
202
+
203
+ class _NoFollowPublicHandleRedirect(HTTPRedirectHandler):
204
+ def redirect_request(self, req, fp, code, msg, headers, newurl): # type: ignore[override]
205
+ _normalise_public_https_url(newurl)
206
+ return None
207
+
208
+
209
+ def _public_handle_attribution(parsed: Any) -> str:
210
+ path = parsed.path.rstrip("/") or "/"
211
+ return f"{parsed.hostname}{path}"
212
+
213
+
214
+ def _source_item_from_public_handle_observation(
215
+ *,
216
+ source_item_id: str,
217
+ source_kind: str,
218
+ freshness: str,
219
+ terms_note: str,
220
+ parsed_url: Any,
221
+ observation: Mapping[str, Any],
222
+ ) -> dict[str, Any]:
223
+ return {
224
+ "schema_version": SOURCE_ITEM_SCHEMA_VERSION,
225
+ "source_item_id": source_item_id,
226
+ "source_kind": source_kind,
227
+ "source_status": "public",
228
+ "freshness": freshness,
229
+ "terms_note": terms_note,
230
+ "allowed_use": "metadata_only",
231
+ "attribution": _public_handle_attribution(parsed_url),
232
+ "summary": (
233
+ "Public handle observed as metadata-only; no source content was "
234
+ "captured and no external write was attempted."
235
+ ),
236
+ "observation": dict(observation),
237
+ }
238
+
239
+
240
+ def _source_item_from_private_connector_gate(
241
+ *,
242
+ source_item_id: str,
243
+ source_kind: str,
244
+ freshness: str,
245
+ connector_name: str,
246
+ owner_label: str,
247
+ gate: Mapping[str, Any],
248
+ ) -> dict[str, Any]:
249
+ return {
250
+ "schema_version": SOURCE_ITEM_SCHEMA_VERSION,
251
+ "source_item_id": source_item_id,
252
+ "source_kind": source_kind,
253
+ "source_status": "private_needs_review",
254
+ "freshness": freshness,
255
+ "terms_note": (
256
+ "private connector metadata-only placeholder; owner approval is "
257
+ "required before any source content read, quote, summary, or publication"
258
+ ),
259
+ "allowed_use": "metadata_only",
260
+ "attribution": f"{owner_label} via {connector_name}",
261
+ "summary": (
262
+ "Private connector is represented only as an owner-gated metadata "
263
+ "signal; no source content was read or copied."
264
+ ),
265
+ "owner_gate": dict(gate),
266
+ }
267
+
268
+
269
+ def build_content_ops_connector_runtime_policy(
270
+ *,
271
+ connector_id: str,
272
+ connector_name: str,
273
+ access_mode: str,
274
+ connector_url: str | None = None,
275
+ ) -> dict[str, Any]:
276
+ """Build the runtime policy that keeps connector runs inside source bounds."""
277
+
278
+ if access_mode not in ALLOWED_CONNECTOR_ACCESS_MODES:
279
+ allowed_modes = sorted(ALLOWED_CONNECTOR_ACCESS_MODES)
280
+ raise ValueError(f"access_mode must be one of {allowed_modes}")
281
+
282
+ if access_mode == "public_metadata_only":
283
+ return {
284
+ "schema_version": CONTENT_OPS_CONNECTOR_RUNTIME_POLICY_SCHEMA_VERSION,
285
+ "connector_id": connector_id,
286
+ "connector_name": connector_name,
287
+ "access_mode": access_mode,
288
+ "safe_default": "head_only_metadata_probe",
289
+ "browser_open_allowed_before_gate": False,
290
+ "browser_open_risk": (
291
+ "public browser pages can autoload timelines, post text, media, "
292
+ "video streams, analytics, and engagement counters"
293
+ ),
294
+ "allowed_probe_methods": ["HEAD"],
295
+ "allowed_before_approval": [
296
+ "verify URL and host",
297
+ "read response status and content-type headers",
298
+ "record attribution and freshness metadata",
299
+ ],
300
+ "forbidden_before_approval": [
301
+ "timeline body capture",
302
+ "media download",
303
+ "login-gated reads",
304
+ "posting or engagement actions",
305
+ ],
306
+ }
307
+
308
+ if access_mode == "private_metadata_only":
309
+ return {
310
+ "schema_version": CONTENT_OPS_CONNECTOR_RUNTIME_POLICY_SCHEMA_VERSION,
311
+ "connector_id": connector_id,
312
+ "connector_name": connector_name,
313
+ "access_mode": access_mode,
314
+ "connector_url": connector_url,
315
+ "safe_default": "gate_projection_only",
316
+ "browser_open_allowed_before_gate": False,
317
+ "browser_open_risk": (
318
+ "the default web app route may autoload private-source message "
319
+ "lists or message-detail APIs before an agent can intervene"
320
+ ),
321
+ "allowed_probe_methods": [],
322
+ "allowed_before_approval": [
323
+ "store this compact gate packet",
324
+ "display the owner question",
325
+ "prepare fixture-only smoke coverage",
326
+ ],
327
+ "forbidden_url_path_prefixes_before_approval": [
328
+ "/api/messages",
329
+ "/api/reports",
330
+ "/api/channel-state",
331
+ ],
332
+ "forbidden_before_approval": [
333
+ "browser-opening the default private connector route",
334
+ "private source content read",
335
+ "message-list API calls",
336
+ "message-detail API calls",
337
+ "derived report ingestion",
338
+ "source quote",
339
+ "source summary",
340
+ "external posting",
341
+ "autopublish",
342
+ ],
343
+ }
344
+
345
+ return {
346
+ "schema_version": CONTENT_OPS_CONNECTOR_RUNTIME_POLICY_SCHEMA_VERSION,
347
+ "connector_id": connector_id,
348
+ "connector_name": connector_name,
349
+ "access_mode": access_mode,
350
+ "safe_default": "fixture_only",
351
+ "browser_open_allowed_before_gate": False,
352
+ "allowed_before_approval": ["fixture-only validation"],
353
+ "forbidden_before_approval": ["external reads", "external writes"],
354
+ }
355
+
356
+
357
+ def _raw_material_key_names(*groups: Sequence[Mapping[str, Any]]) -> list[str]:
358
+ names: set[str] = set()
359
+ for group in groups:
360
+ for item in group:
361
+ for key in item:
362
+ lowered = str(key).lower()
363
+ if any(hint in lowered for hint in RAW_MATERIAL_KEY_HINTS):
364
+ names.add(str(key))
365
+ return sorted(names)
366
+
367
+
368
+ def build_content_ops_surface_fixture(
369
+ *, generated_at: str | None = "2026-06-23T00:00:00Z"
370
+ ) -> dict[str, Any]:
371
+ """Build a synthetic public-safe content-ops state surface fixture.
372
+
373
+ This fixture demonstrates the shape of a creator/self-media loop without
374
+ copying raw platform posts, chat messages, draft bodies, credentials, or
375
+ local paths into LoopX state.
376
+ """
377
+
378
+ source_items = [
379
+ {
380
+ "schema_version": SOURCE_ITEM_SCHEMA_VERSION,
381
+ "source_item_id": "source_demo_public_feed_001",
382
+ "source_kind": "synthetic_demo_feed",
383
+ "source_status": "synthetic_public_safe",
384
+ "freshness": "fresh",
385
+ "terms_note": "synthetic demo only; no platform scraping claim",
386
+ "allowed_use": "summarize_and_transform",
387
+ "attribution": "LoopX synthetic creator-ops demo",
388
+ "summary": (
389
+ "A public-safe trend summary suggests creator operators need "
390
+ "source-aware drafting queues."
391
+ ),
392
+ },
393
+ {
394
+ "schema_version": SOURCE_ITEM_SCHEMA_VERSION,
395
+ "source_item_id": "source_demo_private_note_001",
396
+ "source_kind": "synthetic_private_note",
397
+ "source_status": "private_needs_review",
398
+ "freshness": "fresh",
399
+ "terms_note": "metadata-only placeholder for private material",
400
+ "allowed_use": "metadata_only",
401
+ "attribution": "operator-owned private source placeholder",
402
+ "summary": "Private source is represented only as a compact review-needed signal.",
403
+ },
404
+ ]
405
+ angle_candidates = [
406
+ {
407
+ "schema_version": ANGLE_CANDIDATE_SCHEMA_VERSION,
408
+ "angle_id": "angle_source_aware_loop",
409
+ "source_item_ids": ["source_demo_public_feed_001"],
410
+ "audience": "maintainers evaluating creator-ops automation",
411
+ "topic": "source-aware drafting loop",
412
+ "novelty": "connects connector observations to explicit publish gates",
413
+ "preference_fit": "high",
414
+ "evidence_quality": "synthetic_demo",
415
+ "decision": "draft",
416
+ },
417
+ {
418
+ "schema_version": ANGLE_CANDIDATE_SCHEMA_VERSION,
419
+ "angle_id": "angle_private_material_quote",
420
+ "source_item_ids": ["source_demo_private_note_001"],
421
+ "audience": "same",
422
+ "topic": "private source quote",
423
+ "novelty": "blocked by source boundary",
424
+ "preference_fit": "unknown",
425
+ "evidence_quality": "needs_owner_review",
426
+ "decision": "reject",
427
+ "rejection_reason": "private material cannot be quoted or promoted without review",
428
+ },
429
+ ]
430
+ draft_items = [
431
+ {
432
+ "schema_version": DRAFT_ITEM_SCHEMA_VERSION,
433
+ "draft_id": "draft_source_aware_loop_outline",
434
+ "angle_id": "angle_source_aware_loop",
435
+ "state": "outline",
436
+ "source_map": [
437
+ {
438
+ "source_item_id": "source_demo_public_feed_001",
439
+ "use": "summarized premise",
440
+ }
441
+ ],
442
+ "preference_hints": [
443
+ "explain value as quality and feedback, not raw publish count",
444
+ "keep publish decision human-gated",
445
+ ],
446
+ "publish_gate_id": "publish_gate_source_aware_loop",
447
+ "validation_surface": (
448
+ "source map present; no raw private material; publish gate visible"
449
+ ),
450
+ }
451
+ ]
452
+ feedback_signals = [
453
+ {
454
+ "schema_version": FEEDBACK_SIGNAL_SCHEMA_VERSION,
455
+ "feedback_id": "feedback_demo_style_001",
456
+ "target_id": "draft_source_aware_loop_outline",
457
+ "signal": "useful_but_less_salesy",
458
+ "effect": "preference_hint",
459
+ "writes_todo": False,
460
+ "summary": "Favor operator-quality framing over content volume claims.",
461
+ },
462
+ {
463
+ "schema_version": FEEDBACK_SIGNAL_SCHEMA_VERSION,
464
+ "feedback_id": "feedback_private_source_boundary_001",
465
+ "target_id": "source_demo_private_note_001",
466
+ "signal": "do_not_use_source_body",
467
+ "effect": "source_boundary_correction",
468
+ "writes_todo": False,
469
+ "summary": "Private source stays metadata-only until an explicit review approves use.",
470
+ },
471
+ ]
472
+ publish_gates = [
473
+ {
474
+ "schema_version": PUBLISH_GATE_SCHEMA_VERSION,
475
+ "gate_id": "publish_gate_source_aware_loop",
476
+ "draft_id": "draft_source_aware_loop_outline",
477
+ "status": "blocked_until_user_approval",
478
+ "approval_required": True,
479
+ "autopublish_allowed": False,
480
+ "required_review": [
481
+ "source attribution",
482
+ "tone/style",
483
+ "platform policy",
484
+ "final publish destination",
485
+ ],
486
+ }
487
+ ]
488
+ material_memory = [
489
+ {
490
+ "schema_version": MATERIAL_MEMORY_SCHEMA_VERSION,
491
+ "memory_id": "memory_source_aware_loop",
492
+ "source_item_id": "source_demo_public_feed_001",
493
+ "attribution": "LoopX synthetic creator-ops demo",
494
+ "reuse_boundary": "demo_only",
495
+ "rejected_angles": ["angle_private_material_quote"],
496
+ "preference_hints": ["quality and feedback beat raw article count"],
497
+ }
498
+ ]
499
+ connector_trials = [
500
+ build_social_browser_x_provider_packet()["connector_trial"],
501
+ {
502
+ "schema_version": CONNECTOR_TRIAL_SCHEMA_VERSION,
503
+ "trial_id": "trial_wechat_chatlog_alpha",
504
+ "surface": "wechat_private_archive",
505
+ "tool_hint": "chatlog-alpha/chatview",
506
+ "access_mode": "private_metadata_only",
507
+ "source_status": "private_needs_review",
508
+ "freshness": "unknown",
509
+ "allowed_use": "metadata_only",
510
+ "trial_state": "needs_owner_gate",
511
+ "proposed_source_item_id": "source_wechat_metadata_signal_001",
512
+ "terms_note": "private material intake stays metadata-only until owner review approves any use",
513
+ "promotion_target": "source_item_v0",
514
+ "requires_user_gate": True,
515
+ "external_write_allowed": False,
516
+ },
517
+ ]
518
+ return {
519
+ "schema_version": CONTENT_OPS_SURFACE_SCHEMA_VERSION,
520
+ "surface_id": "creator_ops_public_safe_demo",
521
+ "generated_at": generated_at,
522
+ "mode": "compact_state_surface",
523
+ "source_items": source_items,
524
+ "angle_candidates": angle_candidates,
525
+ "draft_items": draft_items,
526
+ "feedback_signals": feedback_signals,
527
+ "publish_gates": publish_gates,
528
+ "material_memory": material_memory,
529
+ "connector_trials": connector_trials,
530
+ "operator_states": [
531
+ "waiting_for_source_review",
532
+ "ready_to_draft",
533
+ "waiting_for_feedback",
534
+ "ready_for_publish_decision",
535
+ "safe_side_work_available",
536
+ ],
537
+ "boundary": {
538
+ "public_safe": True,
539
+ "raw_private_material_recorded": False,
540
+ "raw_platform_data_recorded": False,
541
+ "credentials_recorded": False,
542
+ "autopublish_allowed": False,
543
+ "publish_requires_user_gate": True,
544
+ "connector_bodies_are_source_of_truth": False,
545
+ },
546
+ }
547
+
548
+
549
+ def validate_content_ops_surface(surface: Mapping[str, Any]) -> dict[str, Any]:
550
+ source_items = _as_mappings(surface.get("source_items")) # type: ignore[arg-type]
551
+ angle_candidates = _as_mappings(surface.get("angle_candidates")) # type: ignore[arg-type]
552
+ draft_items = _as_mappings(surface.get("draft_items")) # type: ignore[arg-type]
553
+ feedback_signals = _as_mappings(surface.get("feedback_signals")) # type: ignore[arg-type]
554
+ publish_gates = _as_mappings(surface.get("publish_gates")) # type: ignore[arg-type]
555
+ material_memory = _as_mappings(surface.get("material_memory")) # type: ignore[arg-type]
556
+ connector_trials = _as_mappings(surface.get("connector_trials")) # type: ignore[arg-type]
557
+
558
+ errors: list[str] = []
559
+ source_ids = _ids(source_items, "source_item_id")
560
+ angle_ids = _ids(angle_candidates, "angle_id")
561
+ draft_ids = _ids(draft_items, "draft_id")
562
+ gate_ids = _ids(publish_gates, "gate_id")
563
+
564
+ if surface.get("schema_version") != CONTENT_OPS_SURFACE_SCHEMA_VERSION:
565
+ errors.append("surface schema_version must be content_ops_surface_v0")
566
+ if not source_items:
567
+ errors.append("at least one source_item_v0 record is required")
568
+ if not angle_candidates:
569
+ errors.append("at least one angle_candidate_v0 record is required")
570
+ if not draft_items:
571
+ errors.append("at least one draft_item_v0 record is required")
572
+ if not feedback_signals:
573
+ errors.append("at least one feedback_signal_v0 record is required")
574
+ if not publish_gates:
575
+ errors.append("at least one publish_gate_v0 record is required")
576
+ if not material_memory:
577
+ errors.append("at least one material_memory_v0 record is required")
578
+ if not connector_trials:
579
+ errors.append("at least one connector_trial_v0 record is required")
580
+
581
+ for item in source_items:
582
+ if item.get("schema_version") != SOURCE_ITEM_SCHEMA_VERSION:
583
+ errors.append(f"source item {item.get('source_item_id')} has wrong schema")
584
+ if item.get("source_status") not in ALLOWED_SOURCE_STATUSES:
585
+ errors.append(
586
+ f"source item {item.get('source_item_id')} has invalid source_status"
587
+ )
588
+ if item.get("freshness") not in ALLOWED_FRESHNESS:
589
+ errors.append(f"source item {item.get('source_item_id')} has invalid freshness")
590
+ if item.get("allowed_use") not in ALLOWED_USE_POLICIES:
591
+ errors.append(f"source item {item.get('source_item_id')} has invalid allowed_use")
592
+
593
+ for item in angle_candidates:
594
+ if item.get("schema_version") != ANGLE_CANDIDATE_SCHEMA_VERSION:
595
+ errors.append(f"angle {item.get('angle_id')} has wrong schema")
596
+ if item.get("decision") not in ALLOWED_ANGLE_DECISIONS:
597
+ errors.append(f"angle {item.get('angle_id')} has invalid decision")
598
+ for source_id in item.get("source_item_ids") or []:
599
+ if str(source_id) not in source_ids:
600
+ errors.append(
601
+ f"angle {item.get('angle_id')} references unknown source {source_id}"
602
+ )
603
+
604
+ for item in draft_items:
605
+ if item.get("schema_version") != DRAFT_ITEM_SCHEMA_VERSION:
606
+ errors.append(f"draft {item.get('draft_id')} has wrong schema")
607
+ if item.get("state") not in ALLOWED_DRAFT_STATES:
608
+ errors.append(f"draft {item.get('draft_id')} has invalid state")
609
+ if str(item.get("angle_id")) not in angle_ids:
610
+ errors.append(f"draft {item.get('draft_id')} references unknown angle")
611
+ if str(item.get("publish_gate_id")) not in gate_ids:
612
+ errors.append(f"draft {item.get('draft_id')} references unknown publish gate")
613
+ source_map = item.get("source_map")
614
+ if not isinstance(source_map, Sequence) or isinstance(source_map, (str, bytes)):
615
+ errors.append(f"draft {item.get('draft_id')} must carry a source_map")
616
+ else:
617
+ for source_ref in source_map:
618
+ if not isinstance(source_ref, Mapping):
619
+ errors.append(f"draft {item.get('draft_id')} has invalid source_map item")
620
+ continue
621
+ source_id = str(source_ref.get("source_item_id") or "")
622
+ if source_id not in source_ids:
623
+ errors.append(
624
+ f"draft {item.get('draft_id')} source_map references unknown source"
625
+ )
626
+
627
+ for item in feedback_signals:
628
+ if item.get("schema_version") != FEEDBACK_SIGNAL_SCHEMA_VERSION:
629
+ errors.append(f"feedback {item.get('feedback_id')} has wrong schema")
630
+ if item.get("effect") not in ALLOWED_FEEDBACK_EFFECTS:
631
+ errors.append(f"feedback {item.get('feedback_id')} has invalid effect")
632
+ target_id = str(item.get("target_id") or "")
633
+ if (
634
+ target_id not in draft_ids
635
+ and target_id not in source_ids
636
+ and target_id not in angle_ids
637
+ ):
638
+ errors.append(f"feedback {item.get('feedback_id')} references unknown target")
639
+
640
+ for item in publish_gates:
641
+ if item.get("schema_version") != PUBLISH_GATE_SCHEMA_VERSION:
642
+ errors.append(f"publish gate {item.get('gate_id')} has wrong schema")
643
+ if item.get("status") not in ALLOWED_PUBLISH_GATE_STATUSES:
644
+ errors.append(f"publish gate {item.get('gate_id')} has invalid status")
645
+ if item.get("autopublish_allowed") is not False:
646
+ errors.append(
647
+ f"publish gate {item.get('gate_id')} must set autopublish_allowed=false"
648
+ )
649
+ if item.get("approval_required") is not True:
650
+ errors.append(f"publish gate {item.get('gate_id')} must require approval")
651
+
652
+ for item in material_memory:
653
+ if item.get("schema_version") != MATERIAL_MEMORY_SCHEMA_VERSION:
654
+ errors.append(f"memory {item.get('memory_id')} has wrong schema")
655
+ source_id = str(item.get("source_item_id") or "")
656
+ if source_id not in source_ids:
657
+ errors.append(f"memory {item.get('memory_id')} references unknown source")
658
+
659
+ for item in connector_trials:
660
+ if item.get("schema_version") != CONNECTOR_TRIAL_SCHEMA_VERSION:
661
+ errors.append(f"connector trial {item.get('trial_id')} has wrong schema")
662
+ if item.get("source_status") not in ALLOWED_SOURCE_STATUSES:
663
+ errors.append(
664
+ f"connector trial {item.get('trial_id')} has invalid source_status"
665
+ )
666
+ if item.get("freshness") not in ALLOWED_FRESHNESS:
667
+ errors.append(
668
+ f"connector trial {item.get('trial_id')} has invalid freshness"
669
+ )
670
+ if item.get("allowed_use") not in ALLOWED_USE_POLICIES:
671
+ errors.append(
672
+ f"connector trial {item.get('trial_id')} has invalid allowed_use"
673
+ )
674
+ if item.get("trial_state") not in ALLOWED_CONNECTOR_TRIAL_STATES:
675
+ errors.append(
676
+ f"connector trial {item.get('trial_id')} has invalid trial_state"
677
+ )
678
+ if item.get("access_mode") not in ALLOWED_CONNECTOR_ACCESS_MODES:
679
+ errors.append(
680
+ f"connector trial {item.get('trial_id')} has invalid access_mode"
681
+ )
682
+ if item.get("external_write_allowed") is not False:
683
+ errors.append(
684
+ f"connector trial {item.get('trial_id')} must keep external_write_allowed=false"
685
+ )
686
+ if item.get("access_mode") == "private_metadata_only" and item.get(
687
+ "requires_user_gate"
688
+ ) is not True:
689
+ errors.append(
690
+ f"connector trial {item.get('trial_id')} must gate private metadata use"
691
+ )
692
+
693
+ boundary = surface.get("boundary") if isinstance(surface.get("boundary"), Mapping) else {}
694
+ if boundary.get("public_safe") is not True:
695
+ errors.append("boundary.public_safe must be true")
696
+ for key in (
697
+ "raw_private_material_recorded",
698
+ "raw_platform_data_recorded",
699
+ "credentials_recorded",
700
+ "autopublish_allowed",
701
+ "connector_bodies_are_source_of_truth",
702
+ ):
703
+ if boundary.get(key) is not False:
704
+ errors.append(f"boundary.{key} must be false")
705
+ if boundary.get("publish_requires_user_gate") is not True:
706
+ errors.append("boundary.publish_requires_user_gate must be true")
707
+
708
+ raw_key_names = _raw_material_key_names(
709
+ source_items,
710
+ angle_candidates,
711
+ draft_items,
712
+ feedback_signals,
713
+ publish_gates,
714
+ material_memory,
715
+ connector_trials,
716
+ )
717
+ if raw_key_names:
718
+ errors.append(
719
+ "raw/private-looking key names must not appear in content-ops records"
720
+ )
721
+
722
+ return {
723
+ "schema_version": CONTENT_OPS_VALIDATION_SCHEMA_VERSION,
724
+ "ok": not errors,
725
+ "errors": errors,
726
+ "record_counts": {
727
+ "source_items": len(source_items),
728
+ "angle_candidates": len(angle_candidates),
729
+ "draft_items": len(draft_items),
730
+ "feedback_signals": len(feedback_signals),
731
+ "publish_gates": len(publish_gates),
732
+ "material_memory": len(material_memory),
733
+ "connector_trials": len(connector_trials),
734
+ },
735
+ "raw_material_key_names": raw_key_names,
736
+ }
737
+
738
+
739
+ def project_content_ops_surface(surface: Mapping[str, Any]) -> dict[str, Any]:
740
+ """Project a content-ops surface into first-screen status fields."""
741
+
742
+ source_items = _as_mappings(surface.get("source_items")) # type: ignore[arg-type]
743
+ angle_candidates = _as_mappings(surface.get("angle_candidates")) # type: ignore[arg-type]
744
+ draft_items = _as_mappings(surface.get("draft_items")) # type: ignore[arg-type]
745
+ feedback_signals = _as_mappings(surface.get("feedback_signals")) # type: ignore[arg-type]
746
+ publish_gates = _as_mappings(surface.get("publish_gates")) # type: ignore[arg-type]
747
+ material_memory = _as_mappings(surface.get("material_memory")) # type: ignore[arg-type]
748
+ connector_trials = _as_mappings(surface.get("connector_trials")) # type: ignore[arg-type]
749
+ validation = validate_content_ops_surface(surface)
750
+
751
+ source_review_required = [
752
+ item
753
+ for item in source_items
754
+ if item.get("source_status") in {"private_needs_review", "unpublished"}
755
+ or item.get("allowed_use") == "metadata_only"
756
+ ]
757
+ ready_angles = [
758
+ item for item in angle_candidates if item.get("decision") == "draft"
759
+ ]
760
+ drafts_waiting_feedback = [
761
+ item
762
+ for item in draft_items
763
+ if item.get("state") in {"outline", "draft", "ready_for_review"}
764
+ ]
765
+ publish_decision_gates = [
766
+ item
767
+ for item in publish_gates
768
+ if item.get("status") == "blocked_until_user_approval"
769
+ ]
770
+ feedback_effects = _counter(item.get("effect") for item in feedback_signals)
771
+ operator_states = [
772
+ str(item)
773
+ for item in surface.get("operator_states", []) or []
774
+ if isinstance(item, str) and item.strip()
775
+ ]
776
+ user_action_required = bool(publish_decision_gates)
777
+ safe_side_work_available = "safe_side_work_available" in operator_states
778
+ ready_to_draft = bool(ready_angles)
779
+
780
+ if user_action_required:
781
+ waiting_on = "user"
782
+ next_safe_action = "review source map and publish gate before external posting"
783
+ elif ready_to_draft:
784
+ waiting_on = "agent"
785
+ next_safe_action = "draft or rewrite from approved source-mapped angle"
786
+ elif source_review_required:
787
+ waiting_on = "operator"
788
+ next_safe_action = "review source status before drafting"
789
+ else:
790
+ waiting_on = "agent"
791
+ next_safe_action = "collect more compact source signals"
792
+
793
+ todo_candidates = []
794
+ if ready_angles:
795
+ todo_candidates.append(
796
+ {
797
+ "role": "agent",
798
+ "action_kind": "content_ops_draft_from_angle",
799
+ "title": "Draft or rewrite the selected source-mapped angle",
800
+ "angle_ids": [str(item.get("angle_id")) for item in ready_angles],
801
+ "validation_surface": "source_map plus publish_gate must remain present",
802
+ "stop_condition": "stop before external posting",
803
+ }
804
+ )
805
+ if source_review_required:
806
+ todo_candidates.append(
807
+ {
808
+ "role": "user",
809
+ "action_kind": "content_ops_source_review",
810
+ "title": "Review private or metadata-only source before use",
811
+ "source_item_ids": [
812
+ str(item.get("source_item_id")) for item in source_review_required
813
+ ],
814
+ "validation_surface": "source_status and allowed_use updated",
815
+ }
816
+ )
817
+ if publish_decision_gates:
818
+ todo_candidates.append(
819
+ {
820
+ "role": "user",
821
+ "action_kind": "content_ops_publish_gate",
822
+ "title": "Approve, deny, or request revision before publication",
823
+ "publish_gate_ids": [
824
+ str(item.get("gate_id")) for item in publish_decision_gates
825
+ ],
826
+ "validation_surface": "publish gate decision recorded",
827
+ }
828
+ )
829
+ runnable_connector_trials = [
830
+ item
831
+ for item in connector_trials
832
+ if item.get("trial_state") == "ready_for_metadata_trial"
833
+ and item.get("external_write_allowed") is False
834
+ ]
835
+ gated_connector_trials = [
836
+ item for item in connector_trials if item.get("requires_user_gate") is True
837
+ ]
838
+ if runnable_connector_trials:
839
+ todo_candidates.append(
840
+ {
841
+ "role": "agent",
842
+ "action_kind": "content_ops_connector_metadata_trial",
843
+ "title": "Run a connector metadata-only observation trial",
844
+ "trial_ids": [
845
+ str(item.get("trial_id")) for item in runnable_connector_trials
846
+ ],
847
+ "validation_surface": (
848
+ "compact source_item_v0 produced; no raw platform or private material"
849
+ ),
850
+ "stop_condition": "stop before login-gated reads, posting, or private source use",
851
+ }
852
+ )
853
+ if gated_connector_trials:
854
+ todo_candidates.append(
855
+ {
856
+ "role": "user",
857
+ "action_kind": "content_ops_connector_owner_gate",
858
+ "title": "Approve or reject private connector metadata intake",
859
+ "trial_ids": [str(item.get("trial_id")) for item in gated_connector_trials],
860
+ "validation_surface": "connector trial gate decision recorded",
861
+ }
862
+ )
863
+
864
+ return {
865
+ "schema_version": CONTENT_OPS_SURFACE_PROJECTION_SCHEMA_VERSION,
866
+ "surface_schema_version": surface.get("schema_version"),
867
+ "surface_id": _text(surface.get("surface_id"), limit=120),
868
+ "mode": "read_only",
869
+ "first_screen": {
870
+ "waiting_on": waiting_on,
871
+ "user_action_required": user_action_required,
872
+ "agent_can_continue": bool(ready_to_draft or safe_side_work_available),
873
+ "safe_side_work_available": safe_side_work_available,
874
+ "source_review_required_count": len(source_review_required),
875
+ "ready_to_draft_count": len(ready_angles),
876
+ "waiting_for_feedback_count": len(drafts_waiting_feedback),
877
+ "publish_decision_count": len(publish_decision_gates),
878
+ "next_safe_action": next_safe_action,
879
+ },
880
+ "record_counts": validation["record_counts"],
881
+ "source_statuses": _counter(item.get("source_status") for item in source_items),
882
+ "draft_states": _counter(item.get("state") for item in draft_items),
883
+ "feedback_effects": feedback_effects,
884
+ "publish_gate_statuses": _counter(item.get("status") for item in publish_gates),
885
+ "connector_trials": {
886
+ "count": len(connector_trials),
887
+ "states": _counter(item.get("trial_state") for item in connector_trials),
888
+ "access_modes": _counter(item.get("access_mode") for item in connector_trials),
889
+ "surfaces": _counter(item.get("surface") for item in connector_trials),
890
+ "ready_for_metadata_trial_count": len(runnable_connector_trials),
891
+ "owner_gate_required_count": len(gated_connector_trials),
892
+ },
893
+ "material_memory": {
894
+ "count": len(material_memory),
895
+ "reuse_boundaries": _counter(
896
+ item.get("reuse_boundary") for item in material_memory
897
+ ),
898
+ },
899
+ "todo_candidates": todo_candidates,
900
+ "validation": validation,
901
+ "truth_contract": {
902
+ "projection_is_writable": False,
903
+ "write_authority": "none",
904
+ "source_surface_is_source_of_truth": True,
905
+ "publish_gate_required": True,
906
+ "autopublish_allowed": False,
907
+ "raw_private_material_copied": False,
908
+ "recompute_rule": (
909
+ "recompute from compact content_ops_surface_v0 records; "
910
+ "do not edit this projection as source state"
911
+ ),
912
+ },
913
+ }
914
+
915
+
916
+ def build_content_ops_preview_packet(
917
+ *, generated_at: str | None = "2026-06-23T00:00:00Z"
918
+ ) -> dict[str, Any]:
919
+ """Build a public-safe content-ops preview packet.
920
+
921
+ The preview uses synthetic/metadata-only connector trial records. It does
922
+ not read platform timelines, private chat archives, credentials, or source
923
+ bodies.
924
+ """
925
+
926
+ surface = build_content_ops_surface_fixture(generated_at=generated_at)
927
+ validation = validate_content_ops_surface(surface)
928
+ projection = project_content_ops_surface(surface)
929
+ return {
930
+ "ok": bool(validation.get("ok")),
931
+ "schema_version": CONTENT_OPS_PREVIEW_PACKET_SCHEMA_VERSION,
932
+ "mode": "content-ops-preview",
933
+ "surface": surface,
934
+ "projection": projection,
935
+ "validation": validation,
936
+ "connector_trials": projection.get("connector_trials"),
937
+ "external_reads_performed": False,
938
+ "external_writes_performed": False,
939
+ "private_source_bodies_read": False,
940
+ "autopublish_allowed": False,
941
+ "next_safe_action": projection.get("first_screen", {}).get("next_safe_action")
942
+ if isinstance(projection.get("first_screen"), Mapping)
943
+ else None,
944
+ }
945
+
946
+
947
+ def build_content_ops_exploration_plan_packet(
948
+ *,
949
+ scenario: str = "mixed_connector_product_workflow",
950
+ generated_at: str | None = "2026-06-23T00:00:00Z",
951
+ ) -> dict[str, Any]:
952
+ """Build a fixture-only exploration plan packet.
953
+
954
+ The plan expresses source lanes, read status, gates, and promotion targets
955
+ before any connector reads source bodies or performs external writes.
956
+ """
957
+
958
+ scenario_label = _normalise_exploration_label(
959
+ scenario,
960
+ "scenario",
961
+ )
962
+ source_lanes = [
963
+ {
964
+ "lane_id": "repo_issue_public_metadata",
965
+ "surface": "repo_issue_fix",
966
+ "source_kind": "github_issue_or_pr",
967
+ "source_status": "public",
968
+ "access_mode": "public_metadata_only",
969
+ "read_status": "metadata_ready",
970
+ "route": "GitHub CLI or issue API metadata read",
971
+ "fallback": "browser permalink metadata or ask for a stable issue URL",
972
+ "evidence_quality": "primary_source_metadata",
973
+ "promotion_target": "loopx_agent_todo_candidate",
974
+ "requires_user_gate": False,
975
+ "source_body_captured": False,
976
+ "response_payload_captured": False,
977
+ "local_path_captured": False,
978
+ "external_write_allowed": False,
979
+ },
980
+ {
981
+ "lane_id": "public_social_signal_metadata",
982
+ "surface": "content_ops_signal_intake",
983
+ "source_kind": "public_social_handle",
984
+ "source_status": "public",
985
+ "access_mode": "public_metadata_only",
986
+ "read_status": "not_read",
987
+ "route": "HEAD-only public handle metadata probe",
988
+ "fallback": "operator-provided public permalink metadata",
989
+ "evidence_quality": "metadata_only",
990
+ "promotion_target": "source_item_v0",
991
+ "requires_user_gate": False,
992
+ "source_body_captured": False,
993
+ "response_payload_captured": False,
994
+ "local_path_captured": False,
995
+ "external_write_allowed": False,
996
+ },
997
+ {
998
+ "lane_id": "private_chat_metadata_gate",
999
+ "surface": "content_ops_private_material",
1000
+ "source_kind": "private_chat_connector",
1001
+ "source_status": "private_needs_review",
1002
+ "access_mode": "private_metadata_only",
1003
+ "read_status": "blocked_until_owner_gate",
1004
+ "route": "owner-gate projection before connector read",
1005
+ "fallback": "owner supplies compact count and topic labels",
1006
+ "evidence_quality": "not_evidence_until_approved",
1007
+ "promotion_target": "source_item_v0_after_owner_gate",
1008
+ "requires_user_gate": True,
1009
+ "user_gate": {
1010
+ "role": "user",
1011
+ "action_kind": "approve_private_metadata_intake",
1012
+ "question": (
1013
+ "Approve metadata-only private connector intake, reject it, "
1014
+ "or narrow the source handle before source content use."
1015
+ ),
1016
+ "blocks": [
1017
+ "source content read",
1018
+ "source quote",
1019
+ "source summary",
1020
+ "external posting",
1021
+ ],
1022
+ },
1023
+ "source_body_captured": False,
1024
+ "response_payload_captured": False,
1025
+ "local_path_captured": False,
1026
+ "external_write_allowed": False,
1027
+ },
1028
+ {
1029
+ "lane_id": "experiment_compact_result_metadata",
1030
+ "surface": "experiment_state_surface",
1031
+ "source_kind": "ml_experiment_run",
1032
+ "source_status": "unpublished",
1033
+ "access_mode": "synthetic_fixture_only",
1034
+ "read_status": "compact_result_ready",
1035
+ "route": "experiment reducer emits compact status counters",
1036
+ "fallback": "operator supplies public-safe result card",
1037
+ "evidence_quality": "compact_result_metadata",
1038
+ "promotion_target": "experiment_state_surface_candidate",
1039
+ "requires_user_gate": True,
1040
+ "user_gate": {
1041
+ "role": "user",
1042
+ "action_kind": "approve_raw_experiment_material_use",
1043
+ "question": (
1044
+ "Approve any raw experiment material read; compact status "
1045
+ "metadata can be used without raw logs."
1046
+ ),
1047
+ "blocks": [
1048
+ "raw run log read",
1049
+ "private dataset sample read",
1050
+ "credential or environment dump",
1051
+ ],
1052
+ },
1053
+ "source_body_captured": False,
1054
+ "response_payload_captured": False,
1055
+ "local_path_captured": False,
1056
+ "external_write_allowed": False,
1057
+ },
1058
+ ]
1059
+ validation = validate_content_ops_exploration_plan_lanes(source_lanes)
1060
+ user_gate_lanes = [
1061
+ lane for lane in source_lanes if bool(lane.get("requires_user_gate"))
1062
+ ]
1063
+ plan = {
1064
+ "schema_version": EXPLORATION_PLAN_SCHEMA_VERSION,
1065
+ "scenario": scenario_label,
1066
+ "generated_at": generated_at,
1067
+ "selected_source_lanes": source_lanes,
1068
+ "lane_counts": {
1069
+ "total": len(source_lanes),
1070
+ "user_gate_required": len(user_gate_lanes),
1071
+ "metadata_ready": sum(
1072
+ 1 for lane in source_lanes if lane.get("read_status") == "metadata_ready"
1073
+ ),
1074
+ "blocked_until_owner_gate": sum(
1075
+ 1
1076
+ for lane in source_lanes
1077
+ if lane.get("read_status") == "blocked_until_owner_gate"
1078
+ ),
1079
+ },
1080
+ "promotion_targets": _counter(
1081
+ [lane.get("promotion_target") for lane in source_lanes]
1082
+ ),
1083
+ "first_screen": {
1084
+ "waiting_on": "user" if user_gate_lanes else "agent",
1085
+ "user_action_required": bool(user_gate_lanes),
1086
+ "agent_can_continue": True,
1087
+ "top_agent_action": (
1088
+ "promote metadata-ready public lanes or ask concrete owner gates "
1089
+ "before private/raw source use"
1090
+ ),
1091
+ "top_user_gate": user_gate_lanes[0].get("user_gate")
1092
+ if user_gate_lanes
1093
+ else None,
1094
+ },
1095
+ "boundary": {
1096
+ "external_reads_performed": False,
1097
+ "external_writes_performed": False,
1098
+ "source_bodies_captured": False,
1099
+ "response_payloads_captured": False,
1100
+ "local_paths_captured": False,
1101
+ "autopublish_allowed": False,
1102
+ },
1103
+ "truth_contract": {
1104
+ "plan_is_writable": False,
1105
+ "source_lanes_are_candidates": True,
1106
+ "promotion_requires_target_surface_validation": True,
1107
+ "private_boundary_crossing_requires_user_gate": True,
1108
+ },
1109
+ }
1110
+ return {
1111
+ "ok": bool(validation.get("ok")),
1112
+ "schema_version": CONTENT_OPS_EXPLORATION_PLAN_PACKET_SCHEMA_VERSION,
1113
+ "mode": "content-ops-exploration-plan",
1114
+ "exploration_plan": plan,
1115
+ "validation": validation,
1116
+ "external_reads_performed": False,
1117
+ "external_writes_performed": False,
1118
+ "private_source_bodies_read": False,
1119
+ "private_source_content_read": False,
1120
+ "local_paths_captured": False,
1121
+ "autopublish_allowed": False,
1122
+ "next_safe_action": plan["first_screen"]["top_agent_action"],
1123
+ }
1124
+
1125
+
1126
+ def validate_content_ops_exploration_plan_lanes(
1127
+ source_lanes: Sequence[Mapping[str, Any]],
1128
+ ) -> dict[str, Any]:
1129
+ errors: list[str] = []
1130
+ if not source_lanes:
1131
+ errors.append("at least one source lane is required")
1132
+ seen_ids: set[str] = set()
1133
+ for index, lane in enumerate(source_lanes):
1134
+ lane_id = str(lane.get("lane_id") or "").strip()
1135
+ if not lane_id:
1136
+ errors.append(f"lane {index + 1}: lane_id is required")
1137
+ elif lane_id in seen_ids:
1138
+ errors.append(f"lane {index + 1}: duplicate lane_id {lane_id}")
1139
+ seen_ids.add(lane_id)
1140
+ if lane.get("source_status") not in ALLOWED_SOURCE_STATUSES:
1141
+ errors.append(
1142
+ f"{lane_id or index + 1}: source_status must be one of "
1143
+ f"{sorted(ALLOWED_SOURCE_STATUSES)}"
1144
+ )
1145
+ if lane.get("access_mode") not in ALLOWED_CONNECTOR_ACCESS_MODES:
1146
+ errors.append(
1147
+ f"{lane_id or index + 1}: access_mode must be one of "
1148
+ f"{sorted(ALLOWED_CONNECTOR_ACCESS_MODES)}"
1149
+ )
1150
+ if lane.get("read_status") not in ALLOWED_EXPLORATION_READ_STATUSES:
1151
+ errors.append(
1152
+ f"{lane_id or index + 1}: read_status must be one of "
1153
+ f"{sorted(ALLOWED_EXPLORATION_READ_STATUSES)}"
1154
+ )
1155
+ if lane.get("evidence_quality") not in ALLOWED_EXPLORATION_EVIDENCE_QUALITIES:
1156
+ errors.append(
1157
+ f"{lane_id or index + 1}: evidence_quality must be one of "
1158
+ f"{sorted(ALLOWED_EXPLORATION_EVIDENCE_QUALITIES)}"
1159
+ )
1160
+ if not str(lane.get("route") or "").strip():
1161
+ errors.append(f"{lane_id or index + 1}: route is required")
1162
+ if not str(lane.get("fallback") or "").strip():
1163
+ errors.append(f"{lane_id or index + 1}: fallback is required")
1164
+ if not str(lane.get("promotion_target") or "").strip():
1165
+ errors.append(f"{lane_id or index + 1}: promotion_target is required")
1166
+ if bool(lane.get("source_body_captured")):
1167
+ errors.append(f"{lane_id or index + 1}: source_body_captured must be false")
1168
+ if bool(lane.get("response_payload_captured")):
1169
+ errors.append(
1170
+ f"{lane_id or index + 1}: response_payload_captured must be false"
1171
+ )
1172
+ if bool(lane.get("local_path_captured")):
1173
+ errors.append(f"{lane_id or index + 1}: local_path_captured must be false")
1174
+ if bool(lane.get("external_write_allowed")):
1175
+ errors.append(f"{lane_id or index + 1}: external_write_allowed must be false")
1176
+ if bool(lane.get("requires_user_gate")) and not isinstance(
1177
+ lane.get("user_gate"), Mapping
1178
+ ):
1179
+ errors.append(f"{lane_id or index + 1}: user_gate is required")
1180
+ return {
1181
+ "schema_version": "content_ops_exploration_plan_validation_v0",
1182
+ "ok": not errors,
1183
+ "errors": errors,
1184
+ "lane_count": len(source_lanes),
1185
+ }
1186
+
1187
+
1188
+ def build_content_ops_public_handle_observation_packet(
1189
+ *,
1190
+ url: str,
1191
+ source_item_id: str,
1192
+ surface: str = "x_public_feed",
1193
+ source_kind: str = "x_public_profile_handle",
1194
+ freshness: str = "fresh",
1195
+ terms_note: str | None = None,
1196
+ timeout_seconds: float = 10.0,
1197
+ fetch: bool = True,
1198
+ ) -> dict[str, Any]:
1199
+ """Observe a public handle as a metadata-only ``source_item_v0`` record.
1200
+
1201
+ The live path issues at most one HEAD request. It never reads response
1202
+ content, sends cookies, logs in, posts, or follows redirects.
1203
+ """
1204
+
1205
+ if not str(source_item_id or "").strip():
1206
+ raise ValueError("source_item_id is required")
1207
+ if freshness not in ALLOWED_FRESHNESS:
1208
+ raise ValueError(f"freshness must be one of {sorted(ALLOWED_FRESHNESS)}")
1209
+ if timeout_seconds <= 0:
1210
+ raise ValueError("timeout_seconds must be positive")
1211
+
1212
+ normalised_url, parsed_url = _normalise_public_https_url(url)
1213
+ effective_terms_note = terms_note or (
1214
+ "public metadata-only handle observation; no login, body capture, "
1215
+ "posting, or private source use"
1216
+ )
1217
+ observation: dict[str, Any] = {
1218
+ "schema_version": CONTENT_OPS_PUBLIC_HANDLE_OBSERVATION_SCHEMA_VERSION,
1219
+ "surface": surface,
1220
+ "input_url": normalised_url,
1221
+ "url_effective": normalised_url,
1222
+ "url_host": parsed_url.hostname,
1223
+ "url_path": parsed_url.path,
1224
+ "http_method": "HEAD" if fetch else "none",
1225
+ "http_status": None,
1226
+ "content_type": None,
1227
+ "redirect_location": None,
1228
+ "content_bytes_read": 0,
1229
+ "external_write_performed": False,
1230
+ "login_used": False,
1231
+ "cookies_sent": False,
1232
+ "private_source_content_read": False,
1233
+ "autopublish_allowed": False,
1234
+ }
1235
+
1236
+ if fetch:
1237
+ request = Request(
1238
+ normalised_url,
1239
+ headers={"User-Agent": "loopx-content-ops-public-handle-metadata/0.1"},
1240
+ method="HEAD",
1241
+ )
1242
+ opener = build_opener(_NoFollowPublicHandleRedirect)
1243
+ response = None
1244
+ try:
1245
+ response = opener.open(request, timeout=timeout_seconds)
1246
+ observation["http_status"] = response.getcode()
1247
+ observation["url_effective"] = response.geturl()
1248
+ observation["content_type"] = response.headers.get("Content-Type")
1249
+ except HTTPError as exc:
1250
+ observation["http_status"] = exc.code
1251
+ observation["url_effective"] = exc.geturl()
1252
+ observation["content_type"] = exc.headers.get("Content-Type")
1253
+ location = exc.headers.get("Location")
1254
+ if location:
1255
+ redirect_url = urljoin(normalised_url, location)
1256
+ _normalise_public_https_url(redirect_url)
1257
+ observation["redirect_location"] = redirect_url
1258
+ except URLError as exc:
1259
+ raise RuntimeError(f"public handle HEAD observation failed: {exc.reason}") from exc
1260
+ finally:
1261
+ if response is not None:
1262
+ response.close()
1263
+
1264
+ _normalise_public_https_url(str(observation["url_effective"]))
1265
+ else:
1266
+ observation["observation_status"] = "not_fetched"
1267
+
1268
+ source_item = _source_item_from_public_handle_observation(
1269
+ source_item_id=str(source_item_id).strip(),
1270
+ source_kind=source_kind,
1271
+ freshness=freshness,
1272
+ terms_note=effective_terms_note,
1273
+ parsed_url=parsed_url,
1274
+ observation=observation,
1275
+ )
1276
+ runtime_policy = build_content_ops_connector_runtime_policy(
1277
+ connector_id=f"{surface}_{source_kind}",
1278
+ connector_name="public handle browser connector",
1279
+ access_mode="public_metadata_only",
1280
+ connector_url=normalised_url,
1281
+ )
1282
+ return {
1283
+ "ok": True,
1284
+ "schema_version": CONTENT_OPS_PUBLIC_HANDLE_OBSERVATION_PACKET_SCHEMA_VERSION,
1285
+ "mode": "content-ops-observe-public-handle",
1286
+ "surface": surface,
1287
+ "source_item": source_item,
1288
+ "source_item_schema_version": SOURCE_ITEM_SCHEMA_VERSION,
1289
+ "observation": observation,
1290
+ "runtime_policy": runtime_policy,
1291
+ "external_reads_performed": bool(fetch),
1292
+ "external_read_kind": "http_head" if fetch else "none",
1293
+ "external_writes_performed": False,
1294
+ "private_source_bodies_read": False,
1295
+ "private_source_content_read": False,
1296
+ "autopublish_allowed": False,
1297
+ "promotion_target": "source_item_v0",
1298
+ "next_safe_action": (
1299
+ "promote the compact source_item_v0 into content_ops_surface_v0 "
1300
+ "only after attribution and allowed_use remain metadata_only"
1301
+ ),
1302
+ }
1303
+
1304
+
1305
+ def build_content_ops_private_connector_gate_packet(
1306
+ *,
1307
+ connector_id: str = "chatlog_alpha_chatview",
1308
+ connector_name: str = "chatlog-alpha/chatview",
1309
+ surface: str = "wechat_private_archive",
1310
+ proposed_source_item_id: str = "source_wechat_metadata_signal_001",
1311
+ source_kind: str = "wechat_private_connector_metadata",
1312
+ owner_label: str = "WeChat archive owner",
1313
+ freshness: str = "unknown",
1314
+ ) -> dict[str, Any]:
1315
+ """Project a concrete owner gate before private connector intake.
1316
+
1317
+ This packet is a routing artifact only. It does not connect to private
1318
+ sources, read source content, quote material, or authorize publication.
1319
+ """
1320
+
1321
+ if not str(connector_id or "").strip():
1322
+ raise ValueError("connector_id is required")
1323
+ if not str(proposed_source_item_id or "").strip():
1324
+ raise ValueError("proposed_source_item_id is required")
1325
+ if freshness not in ALLOWED_FRESHNESS:
1326
+ raise ValueError(f"freshness must be one of {sorted(ALLOWED_FRESHNESS)}")
1327
+
1328
+ gate_id = f"owner_gate_{str(connector_id).strip()}"
1329
+ runtime_policy = build_content_ops_connector_runtime_policy(
1330
+ connector_id=str(connector_id).strip(),
1331
+ connector_name=str(connector_name).strip(),
1332
+ access_mode="private_metadata_only",
1333
+ connector_url="https://chatview.zaynjarvis.com/"
1334
+ if str(connector_id).strip() == "chatlog_alpha_chatview"
1335
+ else None,
1336
+ )
1337
+ gate = {
1338
+ "schema_version": CONTENT_OPS_PRIVATE_CONNECTOR_OWNER_GATE_SCHEMA_VERSION,
1339
+ "gate_id": gate_id,
1340
+ "connector_id": str(connector_id).strip(),
1341
+ "connector_name": str(connector_name).strip(),
1342
+ "surface": surface,
1343
+ "status": "blocked_until_user_approval",
1344
+ "approval_required": True,
1345
+ "owner_label": str(owner_label).strip(),
1346
+ "requested_decision": "approve_metadata_only_intake_or_reject",
1347
+ "approval_options": [
1348
+ "approve metadata-only intake",
1349
+ "reject connector intake",
1350
+ "request a narrower source handle",
1351
+ ],
1352
+ "forbidden_until_approved": [
1353
+ "source content read",
1354
+ "source quote",
1355
+ "source summary",
1356
+ "external posting",
1357
+ "autopublish",
1358
+ ],
1359
+ "allowed_before_approval": [
1360
+ "store this compact gate packet",
1361
+ "display the owner question",
1362
+ "prepare fixture-only smoke coverage",
1363
+ ],
1364
+ "runtime_policy": runtime_policy,
1365
+ }
1366
+ source_item = _source_item_from_private_connector_gate(
1367
+ source_item_id=str(proposed_source_item_id).strip(),
1368
+ source_kind=source_kind,
1369
+ freshness=freshness,
1370
+ connector_name=str(connector_name).strip(),
1371
+ owner_label=str(owner_label).strip(),
1372
+ gate=gate,
1373
+ )
1374
+ user_todo_projection = {
1375
+ "role": "user",
1376
+ "action_kind": "content_ops_private_connector_owner_gate",
1377
+ "title": (
1378
+ f"Approve, reject, or narrow metadata-only intake for {connector_name} "
1379
+ "before LoopX reads any private source content."
1380
+ ),
1381
+ "gate_id": gate_id,
1382
+ "connector_id": str(connector_id).strip(),
1383
+ "source_item_id": source_item["source_item_id"],
1384
+ "validation_surface": "owner decision recorded before private source use",
1385
+ }
1386
+ return {
1387
+ "ok": True,
1388
+ "schema_version": CONTENT_OPS_PRIVATE_CONNECTOR_GATE_PACKET_SCHEMA_VERSION,
1389
+ "mode": "content-ops-project-private-connector-gate",
1390
+ "surface": surface,
1391
+ "connector": {
1392
+ "connector_id": str(connector_id).strip(),
1393
+ "connector_name": str(connector_name).strip(),
1394
+ "access_mode": "private_metadata_only",
1395
+ "source_status": "private_needs_review",
1396
+ "allowed_use": "metadata_only",
1397
+ "promotion_target": "source_item_v0_after_owner_gate",
1398
+ },
1399
+ "owner_gate": gate,
1400
+ "runtime_policy": runtime_policy,
1401
+ "source_item": source_item,
1402
+ "user_todo_projection": user_todo_projection,
1403
+ "external_reads_performed": False,
1404
+ "external_writes_performed": False,
1405
+ "private_source_bodies_read": False,
1406
+ "private_source_content_read": False,
1407
+ "autopublish_allowed": False,
1408
+ "owner_gate_required": True,
1409
+ "next_safe_action": (
1410
+ "surface the projected owner gate; do not read private source content "
1411
+ "until an owner decision updates the gate"
1412
+ ),
1413
+ }
1414
+
1415
+
1416
+ def _non_negative_int(value: int, label: str) -> int:
1417
+ count = int(value)
1418
+ if count < 0:
1419
+ raise ValueError(f"{label} must be non-negative")
1420
+ return count
1421
+
1422
+
1423
+ def _normalise_api_path_counts(values: Mapping[str, Any] | None) -> dict[str, int]:
1424
+ counts: dict[str, int] = {}
1425
+ for raw_path, raw_count in (values or {}).items():
1426
+ path = str(raw_path or "").strip()
1427
+ if not path.startswith("/api/"):
1428
+ raise ValueError("ChatView API path counts must use /api/ path classes")
1429
+ counts[path] = _non_negative_int(int(raw_count), f"api path count for {path}")
1430
+ return dict(sorted(counts.items()))
1431
+
1432
+
1433
+ def _normalise_theme_signals(values: Sequence[str] | None) -> list[str]:
1434
+ signals: list[str] = []
1435
+ for raw_value in values or []:
1436
+ signal = _text(raw_value, limit=80)
1437
+ if not signal:
1438
+ continue
1439
+ lowered = signal.lower()
1440
+ if (
1441
+ "http://" in lowered
1442
+ or "https://" in lowered
1443
+ or "/users/" in lowered
1444
+ or "/private/" in lowered
1445
+ or "bearer " in lowered
1446
+ or "credential" in lowered
1447
+ or "secret" in lowered
1448
+ ):
1449
+ raise ValueError("theme_signal must be a compact public-safe label")
1450
+ signals.append(signal)
1451
+ return signals[:8]
1452
+
1453
+
1454
+ def build_content_ops_chatview_report_packet(
1455
+ *,
1456
+ channel_count: int,
1457
+ recent_record_count: int,
1458
+ report_count: int,
1459
+ api_request_count: int,
1460
+ api_path_counts: Mapping[str, Any] | None = None,
1461
+ connector_url: str = "https://chatview.zaynjarvis.com/",
1462
+ source_item_id: str = "source_chatview_metadata_signal_001",
1463
+ owner_label: str = "ChatView owner",
1464
+ generated_at: str | None = "2026-06-23T00:00:00Z",
1465
+ ) -> dict[str, Any]:
1466
+ """Build a public-safe ChatView report that remains aggregation-compatible."""
1467
+
1468
+ channels = _non_negative_int(channel_count, "channel_count")
1469
+ recent_records = _non_negative_int(recent_record_count, "recent_record_count")
1470
+ reports = _non_negative_int(report_count, "report_count")
1471
+ api_requests = _non_negative_int(api_request_count, "api_request_count")
1472
+ path_counts = _normalise_api_path_counts(api_path_counts)
1473
+ if not str(source_item_id or "").strip():
1474
+ raise ValueError("source_item_id is required")
1475
+ normalised_url, _parsed = _normalise_public_https_url(connector_url)
1476
+
1477
+ operator_card = (
1478
+ f"{channels} channels, {recent_records} recent records, {reports} reports "
1479
+ "detected; private source use remains gated."
1480
+ )
1481
+ packet = build_content_ops_private_connector_gate_packet(
1482
+ connector_id="chatlog_alpha_chatview",
1483
+ connector_name="chatlog-alpha/chatview",
1484
+ surface="wechat_private_archive",
1485
+ proposed_source_item_id=str(source_item_id).strip(),
1486
+ source_kind="wechat_private_connector_metadata",
1487
+ owner_label=owner_label,
1488
+ freshness="fresh",
1489
+ )
1490
+ source_item = packet.get("source_item")
1491
+ if isinstance(source_item, dict):
1492
+ source_item["summary"] = (
1493
+ "ChatView metadata signal: "
1494
+ f"{operator_card} No source body or response payload was saved."
1495
+ )
1496
+ source_item["terms_note"] = (
1497
+ "metadata-only ChatView connector report; owner approval is required "
1498
+ "before any source content read, quote, summary, or publication"
1499
+ )
1500
+ user_todo = packet.get("user_todo_projection")
1501
+ if isinstance(user_todo, dict):
1502
+ user_todo["title"] = (
1503
+ "Approve, reject, or narrow ChatView metadata use before LoopX reads "
1504
+ "any private source content."
1505
+ )
1506
+ user_todo["validation_surface"] = (
1507
+ "owner decision recorded after reviewing the public-safe operator card"
1508
+ )
1509
+
1510
+ report = {
1511
+ "schema_version": CONTENT_OPS_CHATVIEW_CONNECTOR_REPORT_SCHEMA_VERSION,
1512
+ "generated_at": generated_at,
1513
+ "connector_url": normalised_url,
1514
+ "operator_card": operator_card,
1515
+ "observed_shape": {
1516
+ "channel_count": channels,
1517
+ "recent_record_count": recent_records,
1518
+ "report_count": reports,
1519
+ "api_request_count": api_requests,
1520
+ "api_path_counts": path_counts,
1521
+ },
1522
+ "aggregation": {
1523
+ "aggregation_ready": True,
1524
+ "private_gate_packet_schema_version": (
1525
+ CONTENT_OPS_PRIVATE_CONNECTOR_GATE_PACKET_SCHEMA_VERSION
1526
+ ),
1527
+ "source_item_id": str(source_item_id).strip(),
1528
+ },
1529
+ "boundary": {
1530
+ "source_bodies_saved": False,
1531
+ "response_payloads_saved": False,
1532
+ "external_write_performed": False,
1533
+ "autopublish_allowed": False,
1534
+ },
1535
+ }
1536
+ packet.update(
1537
+ {
1538
+ "mode": "content-ops-project-chatview-report",
1539
+ "chatview_report": report,
1540
+ "operator_card": operator_card,
1541
+ "aggregation_ready": True,
1542
+ "external_reads_performed": False,
1543
+ "external_writes_performed": False,
1544
+ "private_source_bodies_read": False,
1545
+ "private_source_content_read": False,
1546
+ "autopublish_allowed": False,
1547
+ "next_safe_action": (
1548
+ "aggregate this private gate packet with public source packets, "
1549
+ "or ask the owner to approve a narrower ChatView source handle"
1550
+ ),
1551
+ }
1552
+ )
1553
+ return packet
1554
+
1555
+
1556
+ def build_content_ops_surface_from_connector_packets(
1557
+ *,
1558
+ public_handle_packets: Sequence[Mapping[str, Any]],
1559
+ private_connector_gate_packets: Sequence[Mapping[str, Any]],
1560
+ surface_id: str = "content_ops_connector_packet_aggregation",
1561
+ generated_at: str | None = "2026-06-23T00:00:00Z",
1562
+ ) -> dict[str, Any]:
1563
+ """Aggregate connector packets into a compact content-ops state surface."""
1564
+
1565
+ public_packets = [dict(packet) for packet in public_handle_packets]
1566
+ private_packets = [dict(packet) for packet in private_connector_gate_packets]
1567
+ if not public_packets:
1568
+ raise ValueError("at least one public handle observation packet is required")
1569
+ if not private_packets:
1570
+ raise ValueError("at least one private connector gate packet is required")
1571
+
1572
+ source_items: list[dict[str, Any]] = []
1573
+ connector_trials: list[dict[str, Any]] = []
1574
+
1575
+ for index, packet in enumerate(public_packets):
1576
+ source_item, connector_trial = source_item_and_trial_from_public_packet(
1577
+ packet,
1578
+ index,
1579
+ )
1580
+ source_items.append(source_item)
1581
+ connector_trials.append(connector_trial)
1582
+
1583
+ for index, packet in enumerate(private_packets):
1584
+ source_item, connector_trial = source_item_and_trial_from_private_gate_packet(
1585
+ packet,
1586
+ index,
1587
+ )
1588
+ source_items.append(source_item)
1589
+ connector_trials.append(connector_trial)
1590
+
1591
+ public_source_ids = [
1592
+ str(item.get("source_item_id"))
1593
+ for item in source_items
1594
+ if item.get("source_status") == "public"
1595
+ ]
1596
+ private_source_ids = [
1597
+ str(item.get("source_item_id"))
1598
+ for item in source_items
1599
+ if item.get("source_status") == "private_needs_review"
1600
+ ]
1601
+ aggregate_angle_id = "angle_connector_packet_aggregation"
1602
+ publish_gate_id = "publish_gate_connector_packet_aggregation"
1603
+ draft_id = "draft_connector_packet_aggregation_outline"
1604
+
1605
+ angle_candidates = [
1606
+ {
1607
+ "schema_version": ANGLE_CANDIDATE_SCHEMA_VERSION,
1608
+ "angle_id": aggregate_angle_id,
1609
+ "source_item_ids": public_source_ids,
1610
+ "audience": "creator operators evaluating bounded automation",
1611
+ "topic": "connector-bounded content operations",
1612
+ "novelty": "combines public metadata packets with explicit private owner gates",
1613
+ "preference_fit": "medium",
1614
+ "evidence_quality": "metadata_only_connector_packet",
1615
+ "decision": "draft",
1616
+ }
1617
+ ]
1618
+ if private_source_ids:
1619
+ angle_candidates.append(
1620
+ {
1621
+ "schema_version": ANGLE_CANDIDATE_SCHEMA_VERSION,
1622
+ "angle_id": "angle_private_connector_source_quote",
1623
+ "source_item_ids": private_source_ids,
1624
+ "audience": "creator operators evaluating bounded automation",
1625
+ "topic": "private connector source quote",
1626
+ "novelty": "blocked by private-source owner gate",
1627
+ "preference_fit": "unknown",
1628
+ "evidence_quality": "needs_owner_review",
1629
+ "decision": "reject",
1630
+ "rejection_reason": (
1631
+ "private connector material cannot be quoted or summarized "
1632
+ "before owner approval"
1633
+ ),
1634
+ }
1635
+ )
1636
+
1637
+ draft_items = [
1638
+ {
1639
+ "schema_version": DRAFT_ITEM_SCHEMA_VERSION,
1640
+ "draft_id": draft_id,
1641
+ "angle_id": aggregate_angle_id,
1642
+ "state": "outline",
1643
+ "source_map": [
1644
+ {"source_item_id": source_id, "use": "metadata-only premise"}
1645
+ for source_id in public_source_ids
1646
+ ],
1647
+ "preference_hints": [
1648
+ "explain connector value as bounded signal intake",
1649
+ "keep private-source use and publishing behind explicit gates",
1650
+ ],
1651
+ "publish_gate_id": publish_gate_id,
1652
+ "validation_surface": (
1653
+ "public source map present; private connector represented only by owner gate"
1654
+ ),
1655
+ }
1656
+ ]
1657
+ feedback_signals = [
1658
+ {
1659
+ "schema_version": FEEDBACK_SIGNAL_SCHEMA_VERSION,
1660
+ "feedback_id": "feedback_connector_packet_boundary",
1661
+ "target_id": private_source_ids[0],
1662
+ "signal": "private_connector_stays_gated",
1663
+ "effect": "source_boundary_correction",
1664
+ "writes_todo": True,
1665
+ "summary": "Private connector packets remain owner-gated before source use.",
1666
+ }
1667
+ ]
1668
+ publish_gates = [
1669
+ {
1670
+ "schema_version": PUBLISH_GATE_SCHEMA_VERSION,
1671
+ "gate_id": publish_gate_id,
1672
+ "draft_id": draft_id,
1673
+ "status": "blocked_until_user_approval",
1674
+ "approval_required": True,
1675
+ "autopublish_allowed": False,
1676
+ "required_review": [
1677
+ "source attribution",
1678
+ "private connector owner gate",
1679
+ "tone/style",
1680
+ "final publish destination",
1681
+ ],
1682
+ }
1683
+ ]
1684
+ material_memory = [
1685
+ {
1686
+ "schema_version": MATERIAL_MEMORY_SCHEMA_VERSION,
1687
+ "memory_id": f"memory_{source_id}",
1688
+ "source_item_id": source_id,
1689
+ "attribution": "content-ops packet aggregation",
1690
+ "reuse_boundary": "metadata_only_public_handle",
1691
+ "rejected_angles": ["angle_private_connector_source_quote"],
1692
+ "preference_hints": ["bounded connector intake before drafting"],
1693
+ }
1694
+ for source_id in public_source_ids
1695
+ ]
1696
+ material_memory.extend(
1697
+ {
1698
+ "schema_version": MATERIAL_MEMORY_SCHEMA_VERSION,
1699
+ "memory_id": f"memory_{source_id}",
1700
+ "source_item_id": source_id,
1701
+ "attribution": "content-ops private connector gate",
1702
+ "reuse_boundary": "private_owner_gate_required",
1703
+ "rejected_angles": ["angle_private_connector_source_quote"],
1704
+ "preference_hints": ["do not quote or summarize before owner approval"],
1705
+ }
1706
+ for source_id in private_source_ids
1707
+ )
1708
+
1709
+ return {
1710
+ "schema_version": CONTENT_OPS_SURFACE_SCHEMA_VERSION,
1711
+ "surface_id": surface_id,
1712
+ "generated_at": generated_at,
1713
+ "mode": "compact_state_surface",
1714
+ "source_items": source_items,
1715
+ "angle_candidates": angle_candidates,
1716
+ "draft_items": draft_items,
1717
+ "feedback_signals": feedback_signals,
1718
+ "publish_gates": publish_gates,
1719
+ "material_memory": material_memory,
1720
+ "connector_trials": connector_trials,
1721
+ "operator_states": [
1722
+ "waiting_for_source_review",
1723
+ "ready_to_draft",
1724
+ "waiting_for_feedback",
1725
+ "ready_for_publish_decision",
1726
+ "safe_side_work_available",
1727
+ ],
1728
+ "boundary": {
1729
+ "public_safe": True,
1730
+ "raw_private_material_recorded": False,
1731
+ "raw_platform_data_recorded": False,
1732
+ "credentials_recorded": False,
1733
+ "autopublish_allowed": False,
1734
+ "publish_requires_user_gate": True,
1735
+ "connector_bodies_are_source_of_truth": False,
1736
+ },
1737
+ }
1738
+
1739
+
1740
+ def build_content_ops_packet_aggregation_packet(
1741
+ *,
1742
+ public_handle_packets: Sequence[Mapping[str, Any]],
1743
+ private_connector_gate_packets: Sequence[Mapping[str, Any]],
1744
+ surface_id: str = "content_ops_connector_packet_aggregation",
1745
+ generated_at: str | None = "2026-06-23T00:00:00Z",
1746
+ ) -> dict[str, Any]:
1747
+ surface = build_content_ops_surface_from_connector_packets(
1748
+ public_handle_packets=public_handle_packets,
1749
+ private_connector_gate_packets=private_connector_gate_packets,
1750
+ surface_id=surface_id,
1751
+ generated_at=generated_at,
1752
+ )
1753
+ validation = validate_content_ops_surface(surface)
1754
+ projection = project_content_ops_surface(surface)
1755
+ return {
1756
+ "ok": bool(validation.get("ok")),
1757
+ "schema_version": CONTENT_OPS_PACKET_AGGREGATION_SCHEMA_VERSION,
1758
+ "mode": "content-ops-aggregate-packets",
1759
+ "surface": surface,
1760
+ "projection": projection,
1761
+ "validation": validation,
1762
+ "input_summary": {
1763
+ "public_handle_packet_count": len(public_handle_packets),
1764
+ "private_connector_gate_packet_count": len(private_connector_gate_packets),
1765
+ "source_item_count": len(surface.get("source_items") or []),
1766
+ "owner_gate_required_count": projection.get("connector_trials", {}).get(
1767
+ "owner_gate_required_count"
1768
+ )
1769
+ if isinstance(projection.get("connector_trials"), Mapping)
1770
+ else None,
1771
+ },
1772
+ "external_reads_performed": False,
1773
+ "external_writes_performed": False,
1774
+ "private_source_bodies_read": False,
1775
+ "private_source_content_read": False,
1776
+ "autopublish_allowed": False,
1777
+ "next_safe_action": projection.get("first_screen", {}).get("next_safe_action")
1778
+ if isinstance(projection.get("first_screen"), Mapping)
1779
+ else None,
1780
+ }
1781
+
1782
+
1783
+ def build_content_ops_walkthrough_artifact_packet(
1784
+ *,
1785
+ public_handle_url: str,
1786
+ public_source_item_id: str = "source_x_public_handle_walkthrough",
1787
+ public_surface: str = "x_public_feed",
1788
+ public_source_kind: str = "x_public_profile_handle",
1789
+ chatview_source_item_id: str = "source_chatview_metadata_signal_walkthrough",
1790
+ channel_count: int,
1791
+ recent_record_count: int,
1792
+ report_count: int,
1793
+ api_request_count: int,
1794
+ api_path_counts: Mapping[str, Any] | None = None,
1795
+ private_preview_item_count: int = 0,
1796
+ theme_signals: Sequence[str] | None = None,
1797
+ generated_at: str | None = "2026-06-23T00:00:00Z",
1798
+ ) -> dict[str, Any]:
1799
+ """Build the public-safe operator artifact for the content-ops chain."""
1800
+
1801
+ preview_count = _non_negative_int(
1802
+ private_preview_item_count,
1803
+ "private_preview_item_count",
1804
+ )
1805
+ themes = _normalise_theme_signals(theme_signals)
1806
+ public_packet = build_content_ops_public_handle_observation_packet(
1807
+ url=public_handle_url,
1808
+ source_item_id=public_source_item_id,
1809
+ surface=public_surface,
1810
+ source_kind=public_source_kind,
1811
+ freshness="fresh",
1812
+ terms_note="metadata-only public handle signal for walkthrough artifact",
1813
+ fetch=False,
1814
+ )
1815
+ chatview_packet = build_content_ops_chatview_report_packet(
1816
+ channel_count=channel_count,
1817
+ recent_record_count=recent_record_count,
1818
+ report_count=report_count,
1819
+ api_request_count=api_request_count,
1820
+ api_path_counts=api_path_counts,
1821
+ source_item_id=chatview_source_item_id,
1822
+ generated_at=generated_at,
1823
+ )
1824
+ aggregate = build_content_ops_packet_aggregation_packet(
1825
+ public_handle_packets=[public_packet],
1826
+ private_connector_gate_packets=[chatview_packet],
1827
+ surface_id="content_ops_walkthrough_artifact_surface",
1828
+ generated_at=generated_at,
1829
+ )
1830
+
1831
+ surface = aggregate.get("surface") if isinstance(aggregate.get("surface"), Mapping) else {}
1832
+ projection = (
1833
+ aggregate.get("projection")
1834
+ if isinstance(aggregate.get("projection"), Mapping)
1835
+ else {}
1836
+ )
1837
+ first_screen = (
1838
+ projection.get("first_screen")
1839
+ if isinstance(projection.get("first_screen"), Mapping)
1840
+ else {}
1841
+ )
1842
+ source_items = _as_mappings(surface.get("source_items")) # type: ignore[arg-type]
1843
+ draft_items = _as_mappings(surface.get("draft_items")) # type: ignore[arg-type]
1844
+ publish_gates = _as_mappings(surface.get("publish_gates")) # type: ignore[arg-type]
1845
+ chatview_report = (
1846
+ chatview_packet.get("chatview_report")
1847
+ if isinstance(chatview_packet.get("chatview_report"), Mapping)
1848
+ else {}
1849
+ )
1850
+ chatview_observed = (
1851
+ chatview_report.get("observed_shape")
1852
+ if isinstance(chatview_report.get("observed_shape"), Mapping)
1853
+ else {}
1854
+ )
1855
+ gate = publish_gates[0] if publish_gates else {}
1856
+ draft = draft_items[0] if draft_items else {}
1857
+
1858
+ operator_artifact = {
1859
+ "headline": (
1860
+ "Public and private connector signals can reach a draft plan, but "
1861
+ "private source use and publication stay behind explicit gates."
1862
+ ),
1863
+ "source_cards": [
1864
+ {
1865
+ "source_item_id": item.get("source_item_id"),
1866
+ "source_status": item.get("source_status"),
1867
+ "allowed_use": item.get("allowed_use"),
1868
+ "summary": item.get("summary"),
1869
+ }
1870
+ for item in source_items
1871
+ ],
1872
+ "private_operator_preview": {
1873
+ "available_in_current_operator_session": preview_count > 0,
1874
+ "sample_record_count": preview_count,
1875
+ "theme_signals": themes,
1876
+ "stored_in_repo": False,
1877
+ "source_content_recorded": False,
1878
+ "response_payload_recorded": False,
1879
+ },
1880
+ "draft_gate": {
1881
+ "draft_id": draft.get("draft_id"),
1882
+ "state": draft.get("state"),
1883
+ "publish_gate_id": gate.get("gate_id"),
1884
+ "publish_status": gate.get("status"),
1885
+ "approval_required": gate.get("approval_required"),
1886
+ "autopublish_allowed": gate.get("autopublish_allowed"),
1887
+ },
1888
+ "next_actions": [
1889
+ "review the private connector owner gate before source use",
1890
+ "draft only from source-mapped metadata until approval changes",
1891
+ "ask for final publish approval before any external posting",
1892
+ ],
1893
+ }
1894
+
1895
+ chain_steps = [
1896
+ {
1897
+ "step": "public_signal_intake",
1898
+ "result": "metadata-only public handle packet",
1899
+ "source_item_id": public_source_item_id,
1900
+ "external_write_performed": False,
1901
+ },
1902
+ {
1903
+ "step": "private_connector_operator_card",
1904
+ "result": chatview_report.get("operator_card"),
1905
+ "observed_shape": chatview_observed,
1906
+ "owner_gate_required": True,
1907
+ },
1908
+ {
1909
+ "step": "aggregate_surface_projection",
1910
+ "result": first_screen.get("next_safe_action"),
1911
+ "waiting_on": first_screen.get("waiting_on"),
1912
+ "user_action_required": first_screen.get("user_action_required"),
1913
+ },
1914
+ {
1915
+ "step": "draft_publish_gate",
1916
+ "result": gate.get("status"),
1917
+ "approval_required": gate.get("approval_required"),
1918
+ "autopublish_allowed": gate.get("autopublish_allowed"),
1919
+ },
1920
+ ]
1921
+
1922
+ return {
1923
+ "ok": bool(aggregate.get("ok")),
1924
+ "schema_version": CONTENT_OPS_WALKTHROUGH_ARTIFACT_SCHEMA_VERSION,
1925
+ "mode": "content-ops-walkthrough-artifact",
1926
+ "generated_at": generated_at,
1927
+ "operator_artifact": operator_artifact,
1928
+ "chain_steps": chain_steps,
1929
+ "aggregation_projection": projection,
1930
+ "validation": aggregate.get("validation"),
1931
+ "packet_summary": {
1932
+ "public_packet_schema_version": public_packet.get("schema_version"),
1933
+ "chatview_packet_schema_version": chatview_packet.get("schema_version"),
1934
+ "aggregation_schema_version": aggregate.get("schema_version"),
1935
+ "source_item_count": aggregate.get("input_summary", {}).get(
1936
+ "source_item_count"
1937
+ )
1938
+ if isinstance(aggregate.get("input_summary"), Mapping)
1939
+ else None,
1940
+ "owner_gate_required_count": aggregate.get("input_summary", {}).get(
1941
+ "owner_gate_required_count"
1942
+ )
1943
+ if isinstance(aggregate.get("input_summary"), Mapping)
1944
+ else None,
1945
+ },
1946
+ "external_reads_performed": False,
1947
+ "external_writes_performed": False,
1948
+ "private_source_bodies_read": False,
1949
+ "private_source_content_read": False,
1950
+ "autopublish_allowed": False,
1951
+ "public_repo_safe": True,
1952
+ "next_safe_action": (
1953
+ "show the operator artifact, then collect owner approval before "
1954
+ "private source use or publication"
1955
+ ),
1956
+ }