rubik 0.1.0

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 (523) hide show
  1. checksums.yaml +7 -0
  2. data/ATTRIBUTION.md +139 -0
  3. data/CHANGELOG.md +236 -0
  4. data/LICENSE +21 -0
  5. data/README.md +269 -0
  6. data/bin/rubik +82 -0
  7. data/lib/rubik/adapter/base.rb +195 -0
  8. data/lib/rubik/adapter/c.rb +36 -0
  9. data/lib/rubik/adapter/crystal.rb +78 -0
  10. data/lib/rubik/adapter/generic.rb +86 -0
  11. data/lib/rubik/adapter/go.rb +36 -0
  12. data/lib/rubik/adapter/node.rb +146 -0
  13. data/lib/rubik/adapter/process.rb +78 -0
  14. data/lib/rubik/adapter/python.rb +30 -0
  15. data/lib/rubik/adapter/registry.rb +81 -0
  16. data/lib/rubik/adapter/ruby.rb +30 -0
  17. data/lib/rubik/adapter/rust.rb +36 -0
  18. data/lib/rubik/adapter/zig.rb +36 -0
  19. data/lib/rubik/adapter.rb +18 -0
  20. data/lib/rubik/ast/body_loader.rb +19 -0
  21. data/lib/rubik/ast/c_extractor.rb +114 -0
  22. data/lib/rubik/ast/cache_store.rb +191 -0
  23. data/lib/rubik/ast/extractor.rb +59 -0
  24. data/lib/rubik/ast/fallback_cleaner.rb +145 -0
  25. data/lib/rubik/ast/go_extractor.rb +148 -0
  26. data/lib/rubik/ast/graph.rb +272 -0
  27. data/lib/rubik/ast/python_extractor.rb +120 -0
  28. data/lib/rubik/ast/ruby_extractor.rb +260 -0
  29. data/lib/rubik/ast/rust_extractor.rb +127 -0
  30. data/lib/rubik/ast/symbol.rb +103 -0
  31. data/lib/rubik/ast/treesitter/base_walker.rb +109 -0
  32. data/lib/rubik/ast/treesitter/c_walker.rb +122 -0
  33. data/lib/rubik/ast/treesitter/go_walker.rb +120 -0
  34. data/lib/rubik/ast/treesitter/python_walker.rb +107 -0
  35. data/lib/rubik/ast/treesitter/rust_walker.rb +130 -0
  36. data/lib/rubik/ast/treesitter/typescript_walker.rb +182 -0
  37. data/lib/rubik/ast/treesitter/zig_walker.rb +109 -0
  38. data/lib/rubik/ast/treesitter_extractor.rb +93 -0
  39. data/lib/rubik/ast/typescript_extractor.rb +283 -0
  40. data/lib/rubik/ast/zig_extractor.rb +116 -0
  41. data/lib/rubik/ast.rb +13 -0
  42. data/lib/rubik/benchmark/runner.rb +224 -0
  43. data/lib/rubik/benchmark/synth_repo.rb +59 -0
  44. data/lib/rubik/benchmark.rb +9 -0
  45. data/lib/rubik/buffer/diff.rb +43 -0
  46. data/lib/rubik/buffer/manager.rb +427 -0
  47. data/lib/rubik/buffer/path_validator.rb +60 -0
  48. data/lib/rubik/buffer/snapshot.rb +99 -0
  49. data/lib/rubik/buffer.rb +10 -0
  50. data/lib/rubik/coeffects.rb +129 -0
  51. data/lib/rubik/concurrency/boundary.rb +74 -0
  52. data/lib/rubik/concurrency/dns_pool.rb +68 -0
  53. data/lib/rubik/concurrency/errors.rb +17 -0
  54. data/lib/rubik/concurrency/event_loop.rb +441 -0
  55. data/lib/rubik/concurrency/join.rb +55 -0
  56. data/lib/rubik/concurrency/process_waiter.rb +55 -0
  57. data/lib/rubik/concurrency/scheduler.rb +84 -0
  58. data/lib/rubik/concurrency/timers.rb +109 -0
  59. data/lib/rubik/concurrency.rb +132 -0
  60. data/lib/rubik/config/deep_ops.rb +80 -0
  61. data/lib/rubik/config/defaults.rb +248 -0
  62. data/lib/rubik/config/model_catalog.rb +93 -0
  63. data/lib/rubik/config/store.rb +155 -0
  64. data/lib/rubik/config.rb +216 -0
  65. data/lib/rubik/context/git_info.rb +84 -0
  66. data/lib/rubik/context/git_lister.rb +37 -0
  67. data/lib/rubik/context/index.rb +134 -0
  68. data/lib/rubik/context/repo_context_reader.rb +121 -0
  69. data/lib/rubik/context/scanner.rb +439 -0
  70. data/lib/rubik/context.rb +11 -0
  71. data/lib/rubik/diagnostics/collector.rb +200 -0
  72. data/lib/rubik/diagnostics/entry.rb +60 -0
  73. data/lib/rubik/diagnostics/parser.rb +107 -0
  74. data/lib/rubik/diagnostics.rb +10 -0
  75. data/lib/rubik/event_bus.rb +56 -0
  76. data/lib/rubik/kernel/dispatcher.rb +171 -0
  77. data/lib/rubik/kernel/dmesg.rb +116 -0
  78. data/lib/rubik/kernel/init.rb +177 -0
  79. data/lib/rubik/kernel/record.rb +85 -0
  80. data/lib/rubik/kernel/unit.rb +160 -0
  81. data/lib/rubik/llm/anthropic.rb +202 -0
  82. data/lib/rubik/llm/chunk_assembler.rb +285 -0
  83. data/lib/rubik/llm/client.rb +526 -0
  84. data/lib/rubik/llm/formatter.rb +450 -0
  85. data/lib/rubik/llm/message_sanitizer.rb +87 -0
  86. data/lib/rubik/llm/ollama.rb +167 -0
  87. data/lib/rubik/llm/openai.rb +21 -0
  88. data/lib/rubik/llm/openai_compatible.rb +216 -0
  89. data/lib/rubik/llm/provider.rb +125 -0
  90. data/lib/rubik/llm/registry.rb +336 -0
  91. data/lib/rubik/llm/router.rb +100 -0
  92. data/lib/rubik/llm.rb +18 -0
  93. data/lib/rubik/log.rb +38 -0
  94. data/lib/rubik/lsp/client.rb +475 -0
  95. data/lib/rubik/lsp/manager.rb +366 -0
  96. data/lib/rubik/lsp.rb +9 -0
  97. data/lib/rubik/mcp/boot_lifecycle.rb +114 -0
  98. data/lib/rubik/mcp/client.rb +169 -0
  99. data/lib/rubik/mcp/errors.rb +11 -0
  100. data/lib/rubik/mcp/manager.rb +619 -0
  101. data/lib/rubik/mcp/proxy_tool.rb +104 -0
  102. data/lib/rubik/mcp/transport/stdio.rb +255 -0
  103. data/lib/rubik/mcp.rb +12 -0
  104. data/lib/rubik/orchestrator/activity_log.rb +75 -0
  105. data/lib/rubik/orchestrator/auto_apply.rb +166 -0
  106. data/lib/rubik/orchestrator/budget_manager.rb +97 -0
  107. data/lib/rubik/orchestrator/continuation_manager.rb +161 -0
  108. data/lib/rubik/orchestrator/finish_message_selector.rb +55 -0
  109. data/lib/rubik/orchestrator/goal_ledger/drift.rb +71 -0
  110. data/lib/rubik/orchestrator/goal_ledger/ledger.rb +399 -0
  111. data/lib/rubik/orchestrator/goal_ledger/observation_projection.rb +179 -0
  112. data/lib/rubik/orchestrator/goal_ledger/patch.rb +268 -0
  113. data/lib/rubik/orchestrator/goal_ledger/schema.rb +123 -0
  114. data/lib/rubik/orchestrator/goal_ledger/validator.rb +361 -0
  115. data/lib/rubik/orchestrator/goal_ledger/verifier.rb +129 -0
  116. data/lib/rubik/orchestrator/goal_ledger/verify_spec.rb +50 -0
  117. data/lib/rubik/orchestrator/goal_ledger.rb +65 -0
  118. data/lib/rubik/orchestrator/goal_state.rb +184 -0
  119. data/lib/rubik/orchestrator/goal_wait.rb +125 -0
  120. data/lib/rubik/orchestrator/loop.rb +388 -0
  121. data/lib/rubik/orchestrator/lsp_feedback.rb +55 -0
  122. data/lib/rubik/orchestrator/maestro_gate.rb +65 -0
  123. data/lib/rubik/orchestrator/provider_recovery.rb +90 -0
  124. data/lib/rubik/orchestrator/provider_resolver.rb +69 -0
  125. data/lib/rubik/orchestrator/shell_deps.rb +85 -0
  126. data/lib/rubik/orchestrator/step_executor.rb +328 -0
  127. data/lib/rubik/orchestrator/stream_manager.rb +118 -0
  128. data/lib/rubik/orchestrator/subagent/activation.rb +65 -0
  129. data/lib/rubik/orchestrator/subagent/budget.rb +108 -0
  130. data/lib/rubik/orchestrator/subagent/completion.rb +61 -0
  131. data/lib/rubik/orchestrator/subagent/concurrency_gate.rb +37 -0
  132. data/lib/rubik/orchestrator/subagent/conversation_id.rb +67 -0
  133. data/lib/rubik/orchestrator/subagent/dependency.rb +398 -0
  134. data/lib/rubik/orchestrator/subagent/instance.rb +384 -0
  135. data/lib/rubik/orchestrator/subagent/lifecycle.rb +118 -0
  136. data/lib/rubik/orchestrator/subagent/messaging.rb +61 -0
  137. data/lib/rubik/orchestrator/subagent/narration.rb +125 -0
  138. data/lib/rubik/orchestrator/subagent/notices.rb +209 -0
  139. data/lib/rubik/orchestrator/subagent/questioner.rb +80 -0
  140. data/lib/rubik/orchestrator/subagent/reaper.rb +98 -0
  141. data/lib/rubik/orchestrator/subagent/resilience.rb +70 -0
  142. data/lib/rubik/orchestrator/subagent/reuse.rb +127 -0
  143. data/lib/rubik/orchestrator/subagent/scoped_registry.rb +79 -0
  144. data/lib/rubik/orchestrator/subagent/step_runner.rb +240 -0
  145. data/lib/rubik/orchestrator/subagent/storage_id.rb +26 -0
  146. data/lib/rubik/orchestrator/subagent/supervisor.rb +327 -0
  147. data/lib/rubik/orchestrator/subagent/transcript_formatter.rb +141 -0
  148. data/lib/rubik/orchestrator/subagent/type_registry.rb +199 -0
  149. data/lib/rubik/orchestrator/subagent_notice_inbox.rb +80 -0
  150. data/lib/rubik/orchestrator/tool_call_id.rb +47 -0
  151. data/lib/rubik/orchestrator/tool_circuit_breaker.rb +91 -0
  152. data/lib/rubik/orchestrator/tool_history.rb +39 -0
  153. data/lib/rubik/orchestrator/turn_executor/subagent_channel.rb +102 -0
  154. data/lib/rubik/orchestrator/turn_executor/user_channel.rb +172 -0
  155. data/lib/rubik/orchestrator/turn_executor.rb +376 -0
  156. data/lib/rubik/orchestrator.rb +11 -0
  157. data/lib/rubik/platform/filesystem.rb +64 -0
  158. data/lib/rubik/platform/io_drain.rb +53 -0
  159. data/lib/rubik/platform/path.rb +210 -0
  160. data/lib/rubik/platform/process.rb +38 -0
  161. data/lib/rubik/platform/sanitize.rb +157 -0
  162. data/lib/rubik/platform.rb +113 -0
  163. data/lib/rubik/plugin/dsl.rb +51 -0
  164. data/lib/rubik/plugin/manager.rb +247 -0
  165. data/lib/rubik/plugin/storage.rb +195 -0
  166. data/lib/rubik/plugin.rb +10 -0
  167. data/lib/rubik/prompt/context_builder.rb +377 -0
  168. data/lib/rubik/prompt/context_compactor.rb +326 -0
  169. data/lib/rubik/prompt/conversation_rows.rb +74 -0
  170. data/lib/rubik/prompt/doctrine.rb +160 -0
  171. data/lib/rubik/prompt/emergency_compactor.rb +81 -0
  172. data/lib/rubik/prompt/services.rb +60 -0
  173. data/lib/rubik/prompt/token_estimator.rb +63 -0
  174. data/lib/rubik/prompt/tool_teachings.rb +78 -0
  175. data/lib/rubik/prompt.rb +13 -0
  176. data/lib/rubik/repair/loop.rb +95 -0
  177. data/lib/rubik/repair.rb +8 -0
  178. data/lib/rubik/repl/ast_warmup.rb +129 -0
  179. data/lib/rubik/repl/command_context.rb +214 -0
  180. data/lib/rubik/repl/command_parser.rb +48 -0
  181. data/lib/rubik/repl/commands/agent.rb +30 -0
  182. data/lib/rubik/repl/commands/agent_context.rb +289 -0
  183. data/lib/rubik/repl/commands/agent_execution.rb +342 -0
  184. data/lib/rubik/repl/commands/agent_models.rb +212 -0
  185. data/lib/rubik/repl/commands/agent_providers.rb +232 -0
  186. data/lib/rubik/repl/commands/base.rb +55 -0
  187. data/lib/rubik/repl/commands/buffer.rb +124 -0
  188. data/lib/rubik/repl/commands/dmesg.rb +87 -0
  189. data/lib/rubik/repl/commands/lsp.rb +235 -0
  190. data/lib/rubik/repl/commands/mcp.rb +194 -0
  191. data/lib/rubik/repl/commands/plugins.rb +208 -0
  192. data/lib/rubik/repl/commands/pruner.rb +71 -0
  193. data/lib/rubik/repl/commands/session.rb +183 -0
  194. data/lib/rubik/repl/commands/skills.rb +111 -0
  195. data/lib/rubik/repl/commands/storage.rb +85 -0
  196. data/lib/rubik/repl/commands/subagents.rb +195 -0
  197. data/lib/rubik/repl/commands/system.rb +85 -0
  198. data/lib/rubik/repl/commands/tasks.rb +120 -0
  199. data/lib/rubik/repl/commands/workspace.rb +443 -0
  200. data/lib/rubik/repl/commands.rb +44 -0
  201. data/lib/rubik/repl/composition.rb +32 -0
  202. data/lib/rubik/repl/config_wiring.rb +142 -0
  203. data/lib/rubik/repl/confirmations.rb +56 -0
  204. data/lib/rubik/repl/hooks.rb +47 -0
  205. data/lib/rubik/repl/input_execution.rb +323 -0
  206. data/lib/rubik/repl/input_loop.rb +213 -0
  207. data/lib/rubik/repl/interrupt_handler.rb +217 -0
  208. data/lib/rubik/repl/lifecycle.rb +410 -0
  209. data/lib/rubik/repl/paste_support.rb +88 -0
  210. data/lib/rubik/repl/pending_notice_queue.rb +60 -0
  211. data/lib/rubik/repl/presentation.rb +132 -0
  212. data/lib/rubik/repl/pruner/brief.rb +101 -0
  213. data/lib/rubik/repl/pruner/gate.rb +279 -0
  214. data/lib/rubik/repl/pruner/harness.rb +236 -0
  215. data/lib/rubik/repl/pruner/rules.rb +316 -0
  216. data/lib/rubik/repl/pruner/service.rb +82 -0
  217. data/lib/rubik/repl/pruner.rb +39 -0
  218. data/lib/rubik/repl/service_catalog.rb +374 -0
  219. data/lib/rubik/repl/setup_factories.rb +66 -0
  220. data/lib/rubik/repl/shell.rb +231 -0
  221. data/lib/rubik/repl/step_formatter.rb +324 -0
  222. data/lib/rubik/repl/tee_io.rb +105 -0
  223. data/lib/rubik/repl/terminal_guard.rb +157 -0
  224. data/lib/rubik/repl/turn_recorder.rb +234 -0
  225. data/lib/rubik/repl.rb +14 -0
  226. data/lib/rubik/runtime/ast_service.rb +78 -0
  227. data/lib/rubik/runtime/context.rb +335 -0
  228. data/lib/rubik/runtime/diagnostic_engine.rb +188 -0
  229. data/lib/rubik/runtime/discovery.rb +342 -0
  230. data/lib/rubik/runtime/fiber.rb +142 -0
  231. data/lib/rubik/runtime/fiber_ordering.rb +52 -0
  232. data/lib/rubik/runtime/fiber_retry_gate.rb +84 -0
  233. data/lib/rubik/runtime/index_service.rb +58 -0
  234. data/lib/rubik/runtime/loop_service.rb +73 -0
  235. data/lib/rubik/runtime/manager.rb +241 -0
  236. data/lib/rubik/runtime/overlay_storage.rb +481 -0
  237. data/lib/rubik/runtime/service_definition.rb +54 -0
  238. data/lib/rubik/runtime/service_registration.rb +33 -0
  239. data/lib/rubik/runtime/task_manager.rb +302 -0
  240. data/lib/rubik/runtime.rb +19 -0
  241. data/lib/rubik/sandbox/driver/base.rb +30 -0
  242. data/lib/rubik/sandbox/driver/bubblewrap.rb +111 -0
  243. data/lib/rubik/sandbox/driver/docker.rb +113 -0
  244. data/lib/rubik/sandbox/driver/podman.rb +98 -0
  245. data/lib/rubik/sandbox/driver/process.rb +190 -0
  246. data/lib/rubik/sandbox/driver.rb +91 -0
  247. data/lib/rubik/sandbox/runner.rb +218 -0
  248. data/lib/rubik/sandbox/shell_resolver.rb +178 -0
  249. data/lib/rubik/sandbox.rb +8 -0
  250. data/lib/rubik/security/blocked_guidance.rb +187 -0
  251. data/lib/rubik/security/classifier/classifier.rb +440 -0
  252. data/lib/rubik/security/classifier/commands/agent-browser.yaml +9 -0
  253. data/lib/rubik/security/classifier/commands/apt.yaml +25 -0
  254. data/lib/rubik/security/classifier/commands/awk.yaml +10 -0
  255. data/lib/rubik/security/classifier/commands/basename.yaml +7 -0
  256. data/lib/rubik/security/classifier/commands/bash-classify.yaml +8 -0
  257. data/lib/rubik/security/classifier/commands/bash.yaml +20 -0
  258. data/lib/rubik/security/classifier/commands/black.yaml +12 -0
  259. data/lib/rubik/security/classifier/commands/brew.yaml +31 -0
  260. data/lib/rubik/security/classifier/commands/builtin.yaml +8 -0
  261. data/lib/rubik/security/classifier/commands/cargo.yaml +114 -0
  262. data/lib/rubik/security/classifier/commands/cat.yaml +7 -0
  263. data/lib/rubik/security/classifier/commands/chgrp.yaml +10 -0
  264. data/lib/rubik/security/classifier/commands/chmod.yaml +10 -0
  265. data/lib/rubik/security/classifier/commands/chown.yaml +10 -0
  266. data/lib/rubik/security/classifier/commands/column.yaml +7 -0
  267. data/lib/rubik/security/classifier/commands/comm.yaml +7 -0
  268. data/lib/rubik/security/classifier/commands/command.yaml +12 -0
  269. data/lib/rubik/security/classifier/commands/cp.yaml +7 -0
  270. data/lib/rubik/security/classifier/commands/crystal.yaml +63 -0
  271. data/lib/rubik/security/classifier/commands/curl.yaml +30 -0
  272. data/lib/rubik/security/classifier/commands/cut.yaml +7 -0
  273. data/lib/rubik/security/classifier/commands/date.yaml +10 -0
  274. data/lib/rubik/security/classifier/commands/df.yaml +7 -0
  275. data/lib/rubik/security/classifier/commands/diff.yaml +7 -0
  276. data/lib/rubik/security/classifier/commands/dig.yaml +7 -0
  277. data/lib/rubik/security/classifier/commands/dirname.yaml +7 -0
  278. data/lib/rubik/security/classifier/commands/docker.yaml +177 -0
  279. data/lib/rubik/security/classifier/commands/du.yaml +7 -0
  280. data/lib/rubik/security/classifier/commands/echo.yaml +7 -0
  281. data/lib/rubik/security/classifier/commands/env.yaml +15 -0
  282. data/lib/rubik/security/classifier/commands/eslint.yaml +12 -0
  283. data/lib/rubik/security/classifier/commands/eval.yaml +8 -0
  284. data/lib/rubik/security/classifier/commands/exec.yaml +12 -0
  285. data/lib/rubik/security/classifier/commands/expand.yaml +7 -0
  286. data/lib/rubik/security/classifier/commands/false.yaml +7 -0
  287. data/lib/rubik/security/classifier/commands/file.yaml +7 -0
  288. data/lib/rubik/security/classifier/commands/find.yaml +25 -0
  289. data/lib/rubik/security/classifier/commands/fmt.yaml +7 -0
  290. data/lib/rubik/security/classifier/commands/fold.yaml +7 -0
  291. data/lib/rubik/security/classifier/commands/free.yaml +7 -0
  292. data/lib/rubik/security/classifier/commands/gawk.yaml +10 -0
  293. data/lib/rubik/security/classifier/commands/gcloud.yaml +979 -0
  294. data/lib/rubik/security/classifier/commands/gh.yaml +615 -0
  295. data/lib/rubik/security/classifier/commands/git.yaml +524 -0
  296. data/lib/rubik/security/classifier/commands/glab-discussion.yaml +21 -0
  297. data/lib/rubik/security/classifier/commands/glab.yaml +807 -0
  298. data/lib/rubik/security/classifier/commands/go.yaml +44 -0
  299. data/lib/rubik/security/classifier/commands/gog.yaml +983 -0
  300. data/lib/rubik/security/classifier/commands/gradle.yaml +92 -0
  301. data/lib/rubik/security/classifier/commands/gradlew.yaml +92 -0
  302. data/lib/rubik/security/classifier/commands/grep.yaml +7 -0
  303. data/lib/rubik/security/classifier/commands/groups.yaml +7 -0
  304. data/lib/rubik/security/classifier/commands/gunzip.yaml +8 -0
  305. data/lib/rubik/security/classifier/commands/gws.yaml +211 -0
  306. data/lib/rubik/security/classifier/commands/gzip.yaml +7 -0
  307. data/lib/rubik/security/classifier/commands/hadolint.yaml +9 -0
  308. data/lib/rubik/security/classifier/commands/halt.yaml +8 -0
  309. data/lib/rubik/security/classifier/commands/head.yaml +7 -0
  310. data/lib/rubik/security/classifier/commands/helm.yaml +129 -0
  311. data/lib/rubik/security/classifier/commands/hostname.yaml +7 -0
  312. data/lib/rubik/security/classifier/commands/id.yaml +7 -0
  313. data/lib/rubik/security/classifier/commands/install.yaml +7 -0
  314. data/lib/rubik/security/classifier/commands/isort.yaml +14 -0
  315. data/lib/rubik/security/classifier/commands/java.yaml +35 -0
  316. data/lib/rubik/security/classifier/commands/javac.yaml +39 -0
  317. data/lib/rubik/security/classifier/commands/join.yaml +7 -0
  318. data/lib/rubik/security/classifier/commands/journalctl.yaml +14 -0
  319. data/lib/rubik/security/classifier/commands/jq.yaml +7 -0
  320. data/lib/rubik/security/classifier/commands/kill.yaml +7 -0
  321. data/lib/rubik/security/classifier/commands/killall.yaml +7 -0
  322. data/lib/rubik/security/classifier/commands/kubectl.yaml +211 -0
  323. data/lib/rubik/security/classifier/commands/less.yaml +7 -0
  324. data/lib/rubik/security/classifier/commands/ln.yaml +7 -0
  325. data/lib/rubik/security/classifier/commands/ls.yaml +7 -0
  326. data/lib/rubik/security/classifier/commands/make.yaml +21 -0
  327. data/lib/rubik/security/classifier/commands/md5sum.yaml +7 -0
  328. data/lib/rubik/security/classifier/commands/mise.yaml +579 -0
  329. data/lib/rubik/security/classifier/commands/mkdir.yaml +8 -0
  330. data/lib/rubik/security/classifier/commands/more.yaml +7 -0
  331. data/lib/rubik/security/classifier/commands/mv.yaml +7 -0
  332. data/lib/rubik/security/classifier/commands/mvn.yaml +83 -0
  333. data/lib/rubik/security/classifier/commands/mvnw.yaml +83 -0
  334. data/lib/rubik/security/classifier/commands/mypy.yaml +9 -0
  335. data/lib/rubik/security/classifier/commands/netstat.yaml +7 -0
  336. data/lib/rubik/security/classifier/commands/nice.yaml +11 -0
  337. data/lib/rubik/security/classifier/commands/nl.yaml +7 -0
  338. data/lib/rubik/security/classifier/commands/node.yaml +20 -0
  339. data/lib/rubik/security/classifier/commands/nohup.yaml +9 -0
  340. data/lib/rubik/security/classifier/commands/npm.yaml +71 -0
  341. data/lib/rubik/security/classifier/commands/npx.yaml +42 -0
  342. data/lib/rubik/security/classifier/commands/nslookup.yaml +7 -0
  343. data/lib/rubik/security/classifier/commands/paste.yaml +7 -0
  344. data/lib/rubik/security/classifier/commands/ping.yaml +7 -0
  345. data/lib/rubik/security/classifier/commands/pip.yaml +33 -0
  346. data/lib/rubik/security/classifier/commands/pip3.yaml +33 -0
  347. data/lib/rubik/security/classifier/commands/pipx.yaml +33 -0
  348. data/lib/rubik/security/classifier/commands/pkill.yaml +7 -0
  349. data/lib/rubik/security/classifier/commands/pnpm.yaml +346 -0
  350. data/lib/rubik/security/classifier/commands/poetry.yaml +97 -0
  351. data/lib/rubik/security/classifier/commands/poweroff.yaml +8 -0
  352. data/lib/rubik/security/classifier/commands/prettier.yaml +13 -0
  353. data/lib/rubik/security/classifier/commands/printenv.yaml +7 -0
  354. data/lib/rubik/security/classifier/commands/printf.yaml +7 -0
  355. data/lib/rubik/security/classifier/commands/ps.yaml +7 -0
  356. data/lib/rubik/security/classifier/commands/pup.yaml +882 -0
  357. data/lib/rubik/security/classifier/commands/pwd.yaml +7 -0
  358. data/lib/rubik/security/classifier/commands/pytest.yaml +7 -0
  359. data/lib/rubik/security/classifier/commands/python.yaml +9 -0
  360. data/lib/rubik/security/classifier/commands/python3.yaml +9 -0
  361. data/lib/rubik/security/classifier/commands/read.yaml +7 -0
  362. data/lib/rubik/security/classifier/commands/readlink.yaml +7 -0
  363. data/lib/rubik/security/classifier/commands/realpath.yaml +7 -0
  364. data/lib/rubik/security/classifier/commands/reboot.yaml +8 -0
  365. data/lib/rubik/security/classifier/commands/rev.yaml +7 -0
  366. data/lib/rubik/security/classifier/commands/rg.yaml +7 -0
  367. data/lib/rubik/security/classifier/commands/rm.yaml +8 -0
  368. data/lib/rubik/security/classifier/commands/rsync.yaml +12 -0
  369. data/lib/rubik/security/classifier/commands/rtk.yaml +140 -0
  370. data/lib/rubik/security/classifier/commands/ruff.yaml +29 -0
  371. data/lib/rubik/security/classifier/commands/rustfmt.yaml +14 -0
  372. data/lib/rubik/security/classifier/commands/scp.yaml +7 -0
  373. data/lib/rubik/security/classifier/commands/sed.yaml +14 -0
  374. data/lib/rubik/security/classifier/commands/seq.yaml +7 -0
  375. data/lib/rubik/security/classifier/commands/sh.yaml +14 -0
  376. data/lib/rubik/security/classifier/commands/sha1sum.yaml +7 -0
  377. data/lib/rubik/security/classifier/commands/sha256sum.yaml +7 -0
  378. data/lib/rubik/security/classifier/commands/shellcheck.yaml +10 -0
  379. data/lib/rubik/security/classifier/commands/shutdown.yaml +8 -0
  380. data/lib/rubik/security/classifier/commands/slack.yaml +113 -0
  381. data/lib/rubik/security/classifier/commands/sleep.yaml +8 -0
  382. data/lib/rubik/security/classifier/commands/snip.yaml +79 -0
  383. data/lib/rubik/security/classifier/commands/sort.yaml +10 -0
  384. data/lib/rubik/security/classifier/commands/ss.yaml +7 -0
  385. data/lib/rubik/security/classifier/commands/ssh.yaml +41 -0
  386. data/lib/rubik/security/classifier/commands/stat.yaml +7 -0
  387. data/lib/rubik/security/classifier/commands/stdbuf.yaml +15 -0
  388. data/lib/rubik/security/classifier/commands/strace.yaml +20 -0
  389. data/lib/rubik/security/classifier/commands/strings.yaml +11 -0
  390. data/lib/rubik/security/classifier/commands/sudo.yaml +24 -0
  391. data/lib/rubik/security/classifier/commands/systemctl.yaml +58 -0
  392. data/lib/rubik/security/classifier/commands/systemd-run.yaml +30 -0
  393. data/lib/rubik/security/classifier/commands/tac.yaml +7 -0
  394. data/lib/rubik/security/classifier/commands/tail.yaml +7 -0
  395. data/lib/rubik/security/classifier/commands/tar.yaml +25 -0
  396. data/lib/rubik/security/classifier/commands/tee.yaml +8 -0
  397. data/lib/rubik/security/classifier/commands/terraform.yaml +70 -0
  398. data/lib/rubik/security/classifier/commands/test.yaml +7 -0
  399. data/lib/rubik/security/classifier/commands/time.yaml +8 -0
  400. data/lib/rubik/security/classifier/commands/timeout.yaml +18 -0
  401. data/lib/rubik/security/classifier/commands/top.yaml +7 -0
  402. data/lib/rubik/security/classifier/commands/touch.yaml +8 -0
  403. data/lib/rubik/security/classifier/commands/tr.yaml +7 -0
  404. data/lib/rubik/security/classifier/commands/traceroute.yaml +7 -0
  405. data/lib/rubik/security/classifier/commands/true.yaml +7 -0
  406. data/lib/rubik/security/classifier/commands/tsc.yaml +16 -0
  407. data/lib/rubik/security/classifier/commands/type.yaml +7 -0
  408. data/lib/rubik/security/classifier/commands/uname.yaml +7 -0
  409. data/lib/rubik/security/classifier/commands/unexpand.yaml +7 -0
  410. data/lib/rubik/security/classifier/commands/uniq.yaml +7 -0
  411. data/lib/rubik/security/classifier/commands/unzip.yaml +10 -0
  412. data/lib/rubik/security/classifier/commands/uptime.yaml +7 -0
  413. data/lib/rubik/security/classifier/commands/uv.yaml +156 -0
  414. data/lib/rubik/security/classifier/commands/wait.yaml +11 -0
  415. data/lib/rubik/security/classifier/commands/wc.yaml +7 -0
  416. data/lib/rubik/security/classifier/commands/wget.yaml +7 -0
  417. data/lib/rubik/security/classifier/commands/whereis.yaml +7 -0
  418. data/lib/rubik/security/classifier/commands/which.yaml +7 -0
  419. data/lib/rubik/security/classifier/commands/whoami.yaml +7 -0
  420. data/lib/rubik/security/classifier/commands/xargs.yaml +26 -0
  421. data/lib/rubik/security/classifier/commands/xmllint.yaml +7 -0
  422. data/lib/rubik/security/classifier/commands/yarn.yaml +72 -0
  423. data/lib/rubik/security/classifier/commands/yes.yaml +7 -0
  424. data/lib/rubik/security/classifier/commands/yq.yaml +10 -0
  425. data/lib/rubik/security/classifier/commands/zip.yaml +8 -0
  426. data/lib/rubik/security/classifier/commands/zsh.yaml +14 -0
  427. data/lib/rubik/security/classifier/database.rb +216 -0
  428. data/lib/rubik/security/classifier/matcher/delegation.rb +190 -0
  429. data/lib/rubik/security/classifier/matcher/options.rb +245 -0
  430. data/lib/rubik/security/classifier/matcher/subcommands.rb +103 -0
  431. data/lib/rubik/security/classifier/matcher.rb +249 -0
  432. data/lib/rubik/security/classifier/models.rb +170 -0
  433. data/lib/rubik/security/classifier/parser.rb +23 -0
  434. data/lib/rubik/security/classifier/parsers/bash.rb +280 -0
  435. data/lib/rubik/security/classifier/parsers/bash_extractors.rb +221 -0
  436. data/lib/rubik/security/classifier/parsers/powershell.rb +185 -0
  437. data/lib/rubik/security/classifier/pwsh_rules.rb +224 -0
  438. data/lib/rubik/security/classifier.rb +25 -0
  439. data/lib/rubik/security/errors.rb +39 -0
  440. data/lib/rubik/security/guard.rb +954 -0
  441. data/lib/rubik/security/redactor.rb +89 -0
  442. data/lib/rubik/security.rb +11 -0
  443. data/lib/rubik/session/manager.rb +331 -0
  444. data/lib/rubik/session/recorder.rb +149 -0
  445. data/lib/rubik/session/replayer.rb +65 -0
  446. data/lib/rubik/session.rb +10 -0
  447. data/lib/rubik/skills/frontmatter.rb +41 -0
  448. data/lib/rubik/skills/manager.rb +223 -0
  449. data/lib/rubik/skills/registry.rb +64 -0
  450. data/lib/rubik/skills/skill.rb +33 -0
  451. data/lib/rubik/skills/storage.rb +77 -0
  452. data/lib/rubik/skills/validator.rb +116 -0
  453. data/lib/rubik/skills.rb +20 -0
  454. data/lib/rubik/storage/folding.rb +23 -0
  455. data/lib/rubik/storage/layout.rb +145 -0
  456. data/lib/rubik/storage/render.rb +97 -0
  457. data/lib/rubik/storage/service.rb +42 -0
  458. data/lib/rubik/storage/store.rb +514 -0
  459. data/lib/rubik/storage/transcript_indexer.rb +116 -0
  460. data/lib/rubik/storage/uri_reader.rb +168 -0
  461. data/lib/rubik/storage/vakanuvis/annal_writer.rb +92 -0
  462. data/lib/rubik/storage/vakanuvis/distill_gate.rb +323 -0
  463. data/lib/rubik/storage/vakanuvis/gate.rb +250 -0
  464. data/lib/rubik/storage/vakanuvis/harness.rb +421 -0
  465. data/lib/rubik/storage/vakanuvis/memory_steward.rb +163 -0
  466. data/lib/rubik/storage/vakanuvis/service.rb +69 -0
  467. data/lib/rubik/storage/vakanuvis/vocabulary.rb +99 -0
  468. data/lib/rubik/storage/vakanuvis/wiki_skeleton.rb +67 -0
  469. data/lib/rubik/storage/vakanuvis.rb +77 -0
  470. data/lib/rubik/storage.rb +29 -0
  471. data/lib/rubik/tools/base.rb +107 -0
  472. data/lib/rubik/tools/builtins/dmesg.rb +74 -0
  473. data/lib/rubik/tools/builtins/exec.rb +293 -0
  474. data/lib/rubik/tools/builtins/files.rb +354 -0
  475. data/lib/rubik/tools/builtins/goal_ledger.rb +89 -0
  476. data/lib/rubik/tools/builtins/grep.rb +281 -0
  477. data/lib/rubik/tools/builtins/interactive.rb +175 -0
  478. data/lib/rubik/tools/builtins/lifecycle.rb +359 -0
  479. data/lib/rubik/tools/builtins/lsp.rb +283 -0
  480. data/lib/rubik/tools/builtins/pruner.rb +88 -0
  481. data/lib/rubik/tools/builtins/runtime.rb +120 -0
  482. data/lib/rubik/tools/builtins/send_message.rb +109 -0
  483. data/lib/rubik/tools/builtins/show_file.rb +84 -0
  484. data/lib/rubik/tools/builtins/skill.rb +72 -0
  485. data/lib/rubik/tools/builtins/storage.rb +232 -0
  486. data/lib/rubik/tools/builtins/subagent.rb +356 -0
  487. data/lib/rubik/tools/builtins/subagent_lifecycle.rb +72 -0
  488. data/lib/rubik/tools/builtins/symbols.rb +209 -0
  489. data/lib/rubik/tools/builtins/synthesis.rb +314 -0
  490. data/lib/rubik/tools/builtins/task.rb +168 -0
  491. data/lib/rubik/tools/builtins/unsynthesize.rb +111 -0
  492. data/lib/rubik/tools/builtins/vakanuvis.rb +161 -0
  493. data/lib/rubik/tools/builtins.rb +30 -0
  494. data/lib/rubik/tools/catalog.rb +70 -0
  495. data/lib/rubik/tools/declaration.rb +90 -0
  496. data/lib/rubik/tools/diagnostic_reporter.rb +283 -0
  497. data/lib/rubik/tools/fuzzy_matcher.rb +172 -0
  498. data/lib/rubik/tools/native_search.rb +180 -0
  499. data/lib/rubik/tools/output.rb +54 -0
  500. data/lib/rubik/tools/preflight_runner.rb +209 -0
  501. data/lib/rubik/tools/registry.rb +247 -0
  502. data/lib/rubik/tools/result.rb +71 -0
  503. data/lib/rubik/tools/services.rb +96 -0
  504. data/lib/rubik/tools/truncator.rb +78 -0
  505. data/lib/rubik/tools/uri_resolver.rb +195 -0
  506. data/lib/rubik/tools.rb +14 -0
  507. data/lib/rubik/ui/approvals.rb +52 -0
  508. data/lib/rubik/ui/banner.rb +43 -0
  509. data/lib/rubik/ui/capability.rb +122 -0
  510. data/lib/rubik/ui/dialog.rb +106 -0
  511. data/lib/rubik/ui/diff_viewer.rb +78 -0
  512. data/lib/rubik/ui/frame.rb +95 -0
  513. data/lib/rubik/ui/markdown_stream.rb +107 -0
  514. data/lib/rubik/ui/panel.rb +98 -0
  515. data/lib/rubik/ui/renderer.rb +206 -0
  516. data/lib/rubik/ui/spinner.rb +120 -0
  517. data/lib/rubik/ui/statusline.rb +134 -0
  518. data/lib/rubik/ui/theme.rb +118 -0
  519. data/lib/rubik/ui.rb +57 -0
  520. data/lib/rubik/uris.rb +101 -0
  521. data/lib/rubik/version.rb +5 -0
  522. data/lib/rubik.rb +65 -0
  523. metadata +770 -0
@@ -0,0 +1,361 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rubik
4
+ module Orchestrator
5
+ module GoalLedger
6
+ # Atomic validate-then-apply. Op'lar sırayla, ÖNCEKİ op'ların
7
+ # uygulanmış olduğu working copy üzerinde valide edilir (RFC 6902
8
+ # semantiği); fail-fast YOK — tüm op'lar teşhis edilir ki tek retry
9
+ # her şeyi düzeltsin (bounded-retry bütçesi: DEFAULT_MAX_RETRIES;
10
+ # retry loop'unu feature katmanı yazar). Herhangi bir reason düşerse
11
+ # working copy ÇÖPE gider ve current_doc HİÇ dokunulmadan döner —
12
+ # §1.A "atomic failure rollback"in veri-düzlemi ikizi. Kabul edilirse
13
+ # rev tam olarak bir artar ve sonuç deep-frozen döner (doküman
14
+ # cap'lidir; validator'u dökümanı sınırsız büyütecek şekilde çağırmak
15
+ # sözleşme dışıdır).
16
+ module Validator
17
+ module_function
18
+
19
+ # patch_or_text: String (önce parse edilir) | GoalLedger::Patch
20
+ # -> [:ok, new_doc, new_rev] | [:rejected, [reason, ...]]
21
+ # Model girdisinde ASLA raise etmez (her başarısızlık veridir);
22
+ # yalnız programcı hatası ArgumentError'a düşer.
23
+ def validate(patch_or_text, schema:, current_doc:)
24
+ raise ArgumentError, "schema must be a GoalLedger::Schema" unless schema.is_a?(Schema)
25
+
26
+ verdict = patch_or_text.is_a?(Patch) ? [:ok, patch_or_text] : Patch.parse(patch_or_text)
27
+ return verdict unless verdict[0] == :ok
28
+
29
+ patch = verdict[1]
30
+ current_rev = current_doc.is_a?(Hash) ? current_doc["rev"] : nil
31
+ unless current_rev.is_a?(Integer)
32
+ return [:rejected, [rejection(:conflict, nil, "", "the current ledger has no integer rev",
33
+ "re-read the ledger — it may not be initialized yet")]]
34
+ end
35
+
36
+ # :conflict — küçük-model taksonomisinin %68'i (premature state
37
+ # overwrite) burada deterministik olarak durur; ESKİ doküman
38
+ # korunur, model güncel rev'e karşı yeniden yazmak zorundadır.
39
+ if patch.rev != current_rev
40
+ return [:rejected, [rejection(:conflict, nil, "",
41
+ "patch rev #{patch.rev} does not match ledger rev #{current_rev}",
42
+ "re-read the ledger (rev #{current_rev}) and re-emit the patch " \
43
+ "against it — your view is stale")]]
44
+ end
45
+
46
+ working = deep_dup(current_doc)
47
+ reasons = []
48
+ patch.ops.each do |op|
49
+ # Patch.parse op değerlerini deep-frozen taşır (VO sözleşmesi);
50
+ # working copy'ye SICAK kopyası yerleşir — yoksa sonraki op'lar
51
+ # frozen Hash'lere yazmaya çalışır (sequential-op semantiği).
52
+ op = { **op, value: deep_dup(op[:value]) } if op[:value]
53
+ reasons.concat(apply_op(op, schema, working))
54
+ end
55
+ return [:rejected, reasons] unless reasons.empty?
56
+
57
+ working["rev"] = current_rev + 1
58
+ [:ok, deep_freeze(working), working["rev"]]
59
+ end
60
+
61
+ # ---- op dispatch ----
62
+
63
+ def apply_op(op, schema, doc)
64
+ head = op[:tokens][0]
65
+ section = schema.sections[head]
66
+ return section_op(op, head, section, doc) if section
67
+
68
+ field = schema.root_scalars[head]
69
+ return root_scalar_op(op, head, field, doc) if field
70
+
71
+ patchable = schema.root_scalars.keys.reject { |key| schema.root_scalars[key].managed }
72
+ [rejection(:schema, op[:index], op[:path], "unknown ledger section '#{head}'",
73
+ "known sections: #{schema.sections.keys.join(', ')}; root fields: #{patchable.join(', ')}")]
74
+ end
75
+
76
+ def root_scalar_op(op, name, spec, doc)
77
+ if spec.managed
78
+ return [rejection(:schema, op[:index], op[:path], "'#{name}' is runtime-managed",
79
+ "send the current rev in the patch header; the runtime writes it")]
80
+ end
81
+
82
+ if op[:op] == :remove
83
+ return [rejection(:schema, op[:index], op[:path], "the '#{name}' field is required",
84
+ "replace it instead of removing it (#{op[:path]} with a new value)")]
85
+ end
86
+
87
+ err = check_value(spec, op[:value], name, op)
88
+ return [err] if err
89
+
90
+ doc[name] = op[:value] # add == replace, RFC 6902 üye semantiği
91
+ []
92
+ end
93
+
94
+ def section_op(op, name, section, doc)
95
+ coll = doc[name]
96
+ unless coll.is_a?(Array)
97
+ return [rejection(:schema, op[:index], op[:path], "section '#{name}' is not a list in the current ledger",
98
+ "the ledger may be corrupted — re-read it before patching")]
99
+ end
100
+
101
+ return [section_head_rejection(op, name)] if op[:tokens].size == 1
102
+
103
+ return element_op(op, name, section, coll, op[:tokens][1]) if section.scalar
104
+
105
+ item_op(op, name, section.item, coll)
106
+ end
107
+
108
+ # Derinlik 1 (bütünsel section yazımı) — üç fiil de yapısal red.
109
+ def section_head_rejection(op, name)
110
+ lead = case op[:op]
111
+ when :replace then "wholesale section rewrite is not allowed"
112
+ when :add then "cannot add the section '#{name}' itself"
113
+ else "sections are structural"
114
+ end
115
+ rejection(:schema, op[:index], op[:path], lead,
116
+ "address items individually (/#{name}/3, /#{name}/-) — one op per item")
117
+ end
118
+
119
+ # ---- skalar liste (open_questions) ----
120
+
121
+ def element_op(op, name, section, coll, idx_tok)
122
+ if op[:tokens].size > 2
123
+ return [rejection(:schema, op[:index], op[:path],
124
+ "paths deeper than two levels are not supported in string lists like #{name}",
125
+ "address elements directly (/#{name}/0, /#{name}/-)")]
126
+ end
127
+
128
+ spec = FieldSpec.new(type: section.scalar, max_len: section.max_len)
129
+ if idx_tok == "-"
130
+ return [append_only_rejection(op, name)] unless op[:op] == :add
131
+
132
+ err = check_value(spec, op[:value], name, op)
133
+ return [err] if err
134
+
135
+ coll << op[:value]
136
+ return []
137
+ end
138
+
139
+ idx = index_token(idx_tok, name, coll, op, insertable: op[:op] == :add)
140
+ return [idx] unless idx.is_a?(Integer)
141
+
142
+ err = op[:op] == :remove ? nil : check_value(spec, op[:value], name, op)
143
+ return [err] if err
144
+
145
+ if op[:op] == :add
146
+ coll.insert(idx, op[:value])
147
+ elsif op[:op] == :replace
148
+ coll[idx] = op[:value]
149
+ else
150
+ coll.delete_at(idx)
151
+ end
152
+ []
153
+ end
154
+
155
+ # ---- item listesi (tasks/findings/...) ----
156
+
157
+ def item_op(op, name, spec, coll)
158
+ return whole_item_op(op, name, spec, coll, op[:tokens][1]) if op[:tokens].size == 2
159
+ if op[:tokens].size > 3
160
+ return [rejection(:schema, op[:index], op[:path],
161
+ "paths deeper than three levels are not supported in item lists like #{name}",
162
+ "address items and their fields directly (e.g. /tasks/3/status)")]
163
+ end
164
+
165
+ field_op(op, name, spec, coll, op[:tokens][1])
166
+ end
167
+
168
+ def whole_item_op(op, name, spec, coll, idx_tok)
169
+ if idx_tok == "-"
170
+ return [append_only_rejection(op, name)] unless op[:op] == :add
171
+
172
+ errs = validate_item_object(spec, op[:value], name, op)
173
+ return errs unless errs.empty?
174
+
175
+ coll << op[:value]
176
+ return dup_id_rejections(coll, spec, name, op)
177
+ end
178
+
179
+ idx = index_token(idx_tok, name, coll, op, insertable: op[:op] == :add)
180
+ return [idx] unless idx.is_a?(Integer)
181
+
182
+ if op[:op] == :remove
183
+ coll.delete_at(idx)
184
+ return []
185
+ end
186
+
187
+ errs = validate_item_object(spec, op[:value], name, op)
188
+ return errs unless errs.empty?
189
+
190
+ op[:op] == :add ? coll.insert(idx, op[:value]) : coll[idx] = op[:value]
191
+ dup_id_rejections(coll, spec, name, op)
192
+ end
193
+
194
+ def field_op(op, name, spec, coll, idx_tok)
195
+ idx = index_token(idx_tok, name, coll, op, insertable: false)
196
+ return [idx] unless idx.is_a?(Integer)
197
+
198
+ field_name = op[:tokens][2]
199
+ field = spec.fields[field_name]
200
+ unless field
201
+ return [rejection(:schema, op[:index], op[:path], "unknown field '#{field_name}' on #{name} items",
202
+ "allowed fields: #{spec.fields.keys.join(', ')}")]
203
+ end
204
+
205
+ if op[:op] == :remove
206
+ nullable = spec.fields.select { |_, item_field| item_field.allow_null }.keys.join(", ")
207
+ return [rejection(:schema, op[:index], op[:path], "fields are not removable",
208
+ "replace with null where allowed (#{nullable}) or remove the item (/#{name}/#{idx})")]
209
+ end
210
+
211
+ err = check_value(field, op[:value], "#{name}.#{field_name}", op)
212
+ return [err] if err
213
+
214
+ coll[idx][field_name] = op[:value]
215
+ spec.id_field == field_name ? dup_id_rejections(coll, spec, name, op) : []
216
+ end
217
+
218
+ # ---- değer kontrolü ----
219
+
220
+ def check_value(spec, value, where, op)
221
+ if value.nil? && !spec.allow_null
222
+ parent = op[:tokens].size >= 3 ? "/#{op[:tokens][0..-2].join('/')}" : op[:path]
223
+ return rejection(:schema, op[:index], op[:path], "#{where} does not accept null",
224
+ "null does not delete; use remove on the item (#{parent})")
225
+ end
226
+ return nil if value.nil?
227
+
228
+ type_err = check_type(spec, value, where, op)
229
+ return type_err if type_err
230
+
231
+ return nil unless spec.enum && !spec.enum.include?(value)
232
+
233
+ rejection(:schema, op[:index], op[:path],
234
+ "#{where} must be one of #{spec.enum.join(', ')} (got '#{value}')",
235
+ "pick from the allowed vocabulary")
236
+ end
237
+
238
+ def check_type(spec, value, where, op)
239
+ case spec.type
240
+ when :string
241
+ unless value.is_a?(String)
242
+ return rejection(:schema, op[:index], op[:path],
243
+ "#{where}: expected string, got #{GoalLedger.json_type_name(value)}",
244
+ "use JSON types — strings are quoted, integers bare")
245
+ end
246
+ if spec.max_len && value.length > spec.max_len
247
+ return rejection(:schema, op[:index], op[:path],
248
+ "#{where} is #{value.length} chars; the cap is #{spec.max_len}",
249
+ "shorten the value — caps are deterministic (no stripping)")
250
+ end
251
+ when :integer
252
+ unless value.is_a?(Integer)
253
+ detail = value.is_a?(Float) ? "floats are not integers — send a whole number" : "expected integer"
254
+ return rejection(:schema, op[:index], op[:path],
255
+ "#{where}: #{detail}, got #{GoalLedger.json_type_name(value)}",
256
+ "integers are bare numbers without a decimal point")
257
+ end
258
+ when :boolean
259
+ unless value.is_a?(TrueClass) || value.is_a?(FalseClass)
260
+ return rejection(:schema, op[:index], op[:path],
261
+ "#{where}: expected boolean, got #{GoalLedger.json_type_name(value)}",
262
+ "booleans are the bare literals true and false")
263
+ end
264
+ when :object
265
+ unless value.is_a?(Hash)
266
+ return rejection(:schema, op[:index], op[:path],
267
+ "#{where}: expected object, got #{GoalLedger.json_type_name(value)}",
268
+ "objects are brace-delimited key-value maps")
269
+ end
270
+ end
271
+ nil
272
+ end
273
+
274
+ def validate_item_object(spec, value, name, op)
275
+ unless value.is_a?(Hash)
276
+ return [rejection(:schema, op[:index], op[:path],
277
+ "supply the full #{name} item as a JSON object (got #{GoalLedger.json_type_name(value)})",
278
+ "or address the field directly (#{op[:path]}/<field>, e.g. #{op[:path]}/status)")]
279
+ end
280
+
281
+ reasons = []
282
+ unknown = value.keys - spec.fields.keys
283
+ unless unknown.empty?
284
+ reasons << rejection(:schema, op[:index], op[:path],
285
+ "unknown field(s) #{unknown.join(', ')} on #{name} items",
286
+ "allowed fields: #{spec.fields.keys.join(', ')}")
287
+ end
288
+ missing = spec.fields.reject { |fname, item_field| item_field.optional || value.key?(fname) }.keys
289
+ unless missing.empty?
290
+ reasons << rejection(:schema, op[:index], op[:path],
291
+ "missing required field(s) #{missing.join(', ')}",
292
+ "supply the full item object or address the field directly (/#{name}/3/<field>)")
293
+ end
294
+ value.each do |fname, item_value|
295
+ field = spec.fields[fname]
296
+ next unless field
297
+
298
+ err = check_value(field, item_value, "#{name}.#{fname}", op)
299
+ reasons << err if err
300
+ end
301
+ reasons
302
+ end
303
+
304
+ # ---- ortak yardımcılar ----
305
+
306
+ def index_token(tok, name, coll, op, insertable:)
307
+ unless tok.is_a?(String) && tok.match?(/\A\d+\z/)
308
+ return rejection(:schema, op[:index], op[:path], "expected an item index in #{name}, got '#{tok}'",
309
+ "numeric indices address existing items; '-' appends")
310
+ end
311
+ idx = tok.to_i
312
+ upper = insertable ? coll.size : coll.size - 1
313
+ return idx if idx <= upper
314
+
315
+ message = coll.empty? ? "#{name} is empty — nothing to address yet" : "#{name} has #{coll.size} item(s); valid indices 0-#{coll.size - 1}"
316
+ rejection(:schema, op[:index], op[:path], message, "append via /#{name}/-")
317
+ end
318
+
319
+ def append_only_rejection(op, name)
320
+ rejection(:schema, op[:index], op[:path], "'-' appends; it cannot address an existing #{name} item",
321
+ "use a numeric index for replace/remove")
322
+ end
323
+
324
+ def dup_id_rejections(coll, spec, name, op)
325
+ return [] unless spec.id_field
326
+
327
+ ids = coll.filter_map { |item| item.is_a?(Hash) ? item[spec.id_field] : nil }
328
+ dups = ids.tally.select { |_, count| count > 1 }.keys
329
+ return [] if dups.empty?
330
+
331
+ [rejection(:schema, op[:index], op[:path], "duplicate #{name} id '#{dups.first}'; ids must be unique within #{name}",
332
+ "pick a fresh id (e.g. a letter prefix plus the step number)")]
333
+ end
334
+
335
+ def rejection(kind, op_index, path, message, hint)
336
+ GoalLedger.reason(kind, op_index: op_index, path: path, message: message, hint: hint)
337
+ end
338
+
339
+ def deep_dup(value)
340
+ case value
341
+ when Hash then value.to_a.to_h { |key, val| [key.dup, deep_dup(val)] }
342
+ when Array then value.map { |val| deep_dup(val) }
343
+ else value
344
+ end
345
+ end
346
+
347
+ def deep_freeze(value)
348
+ case value
349
+ when Hash then value.each do |key, val|
350
+ deep_freeze(key)
351
+ deep_freeze(val)
352
+ end.freeze
353
+ when Array then value.each { |val| deep_freeze(val) }.freeze
354
+ when String then value.dup.freeze
355
+ else value
356
+ end
357
+ end
358
+ end
359
+ end
360
+ end
361
+ end
@@ -0,0 +1,129 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "verify_spec"
4
+
5
+ module Rubik
6
+ module Orchestrator
7
+ module GoalLedger
8
+ # Tri-state FS doğrulaması (goal-ledger planı B). SAF değerlendirme:
9
+ # ledger'ın Monitor'lu SNAPSHOT'ı üzerinde çalışır, tüm FS IO kilit
10
+ # DIŞINDADır; flip kararı Drift/apply_internal_drift!'e aittir.
11
+ #
12
+ # Tri-state sözleşmesi (exhaustive honesty): ok = containment kanıtlı
13
+ # + predicate doğru; failed = containment kanıtlı + predicate yanlış
14
+ # (task → stale, env note → annotate); unknown = KANITLANAMAZ
15
+ # (workspace dışı, EACCES/EPERM, kesilmiş okumada needle yokluğu,
16
+ # malformed spec) — status flip YAPMAZ, yalnızca raporlanır.
17
+ module Verifier
18
+ module_function
19
+
20
+ # Adım sınırı girişi (loop.rb execute_steps hook'u): knob FIRST —
21
+ # kapalıysa hiç dokunma. goal_mode? guard'ı çağıran taraftadır.
22
+ def at_step_boundary!(loop_inst)
23
+ ledger = Rubik::Coeffects.get(loop_inst.runtime_manager&.context, Rubik::Coeffects::GOAL_LEDGER)
24
+ return nil unless ledger
25
+
26
+ verify!(ledger, trigger: :step, root_dir: loop_inst.buffer_manager&.root_dir || Dir.pwd,
27
+ config: loop_inst.config)
28
+ end
29
+
30
+ # -> nil (knob off / boş) | {rev:, flipped:, unknowns:, note:}
31
+ def verify!(ledger, trigger:, root_dir:, config: nil)
32
+ return nil unless Ledger.verify_enabled?(config)
33
+
34
+ snap = ledger.snapshot_doc
35
+ flips, unknowns = evaluate(snap, root_dir)
36
+ return nil if flips.empty? && unknowns.empty?
37
+
38
+ outcome = flips.empty? ? nil : ledger.apply_internal_drift!(flips)
39
+ Drift.emit(trigger: trigger, ledger: ledger, flips: flips, unknowns: unknowns, rev: outcome&.dig(:rev))
40
+ { rev: outcome&.dig(:rev), flipped: flips.size, unknowns: unknowns,
41
+ note: flips.empty? ? nil : DriftNote.render(snap: ledger.snapshot_doc) }
42
+ end
43
+
44
+ # Kilit dışı: snapshot frozen Hash'lerdir; yalnız disk okunur.
45
+ def evaluate(snap, root_dir)
46
+ flips = []
47
+ unknowns = []
48
+ collect(snap, "tasks", root_dir, flips, unknowns)
49
+ collect(snap, "environment_notes", root_dir, flips, unknowns)
50
+ [flips, unknowns]
51
+ end
52
+
53
+ def collect(snap, collection, root_dir, flips, unknowns)
54
+ items = snap[collection]
55
+ return unless items.is_a?(Array)
56
+
57
+ if items.size > VerifySpec::MAX_SPECS_PER_COLLECTION * 4
58
+ # Flood guardı artık sessiz değil (review 2026-10-10): atlanan
59
+ # inceleme toplu unknown olarak görünür — asla sessiz spec düşme.
60
+ unknowns << { collection: collection, index: nil, id: "",
61
+ reason: "collection exceeds #{VerifySpec::MAX_SPECS_PER_COLLECTION * 4} items — re-verification skipped" }
62
+ return
63
+ end
64
+
65
+ items.each_with_index do |item, idx|
66
+ spec = item.is_a?(Hash) ? item["verify"] : nil
67
+ next if spec.nil?
68
+
69
+ verdict, detail = run_spec(spec, root_dir)
70
+ if verdict == :ok
71
+ # ok hiçbir şey yapmaz (sessiz) — stale temizliği yalnızca
72
+ # modelin normal ΔΣ patch'idir; harness asla iyileştirmez.
73
+ elsif verdict == :failed
74
+ flips << entry(collection, idx, item, "failed", detail)
75
+ else
76
+ unknowns << { collection: collection, index: idx, id: item_id(item), reason: detail }
77
+ end
78
+ end
79
+ end
80
+
81
+ # -> [:ok|:failed|:unknown, reason]
82
+ def run_spec(raw, root_dir)
83
+ verdict, spec_or_reason = VerifySpec.normalize(raw)
84
+ return [:unknown, spec_or_reason] unless verdict == :ok
85
+
86
+ expanded = File.expand_path(spec_or_reason[:path], root_dir)
87
+ return [:unknown, "path escapes the workspace (containment failed closed)"] unless Rubik::Platform.resolved_path_under?(expanded, root_dir)
88
+
89
+ if spec_or_reason[:type] == "file_exists"
90
+ File.exist?(expanded) ? [:ok, "file_exists"] : [:failed, "file_exists #{spec_or_reason[:path]}: missing"]
91
+ else
92
+ needle_read(spec_or_reason, expanded)
93
+ end
94
+ rescue Errno::EACCES, Errno::EPERM => e
95
+ [:unknown, "#{e.class.name.delete_prefix('Errno::')}: not readable"]
96
+ rescue StandardError => e
97
+ [:unknown, "#{e.class}: #{e.message}"]
98
+ end
99
+
100
+ # Kesilmiş okumada needle yokluğu KANITLANAMAZ → unknown (tri-state
101
+ # dürüstlüğünün kalbi); tam okumada yokluk gerçek yokluktur → failed.
102
+ def needle_read(spec, expanded)
103
+ size = File.size(expanded)
104
+ return [:failed, "file_contains #{spec[:path]}: path is a directory"] if File.directory?(expanded)
105
+
106
+ body = File.open(expanded, "rb") { |f| f.read(VerifySpec::READ_CAP_BYTES) }.to_s
107
+ body = Rubik::Platform.safe_utf8(body)
108
+ return [:ok, "file_contains"] if body.include?(spec[:needle])
109
+ return [:unknown, "read truncated at #{VerifySpec::READ_CAP_BYTES} bytes — absence unprovable"] if size > body.bytesize
110
+
111
+ [:failed, "file_contains #{spec[:path]}: needle absent"]
112
+ end
113
+
114
+ def stale?(item)
115
+ item["status"] == "stale"
116
+ end
117
+
118
+ def entry(collection, idx, item, state, reason)
119
+ { collection: collection, index: idx, id: item_id(item), state: state, reason: reason,
120
+ from: item["status"], at: Time.now.utc.iso8601 }
121
+ end
122
+
123
+ def item_id(item)
124
+ item.is_a?(Hash) ? item["id"].to_s : ""
125
+ end
126
+ end
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rubik
4
+ module Orchestrator
5
+ module GoalLedger
6
+ # Verify-spec grammar SSOT'u (goal-ledger planı B): tasks[] ve
7
+ # environment_notes[] girdilerinin opsiyonel `verify` alanının kapalı
8
+ # tip seti. SAF — FS yok; lexical ön kontroller burada, containment
9
+ # kanıtı Verifier'dadır. "marker", file_contains'in okunabilir
10
+ # alias'ıdır (aynı semantiğin sentinel dili) — implementasyon tek
11
+ # predicate'e iner. Sınırlar sabiteedir (knoba knob eklemeyiz).
12
+ module VerifySpec
13
+ TYPES = ["file_exists", "file_contains", "marker"].freeze
14
+ NEEDLE_FIELD = { "file_contains" => "pattern", "marker" => "marker" }.freeze
15
+ MAX_SPECS_PER_COLLECTION = 16
16
+ PATH_MAX_CHARS = 512
17
+ NEEDLE_MAX_CHARS = 256
18
+ READ_CAP_BYTES = 262_144 # 256 KiB — kesilmiş okumada yokluk KANITLANAMAZ (unknown)
19
+
20
+ module_function
21
+
22
+ # -> [:ok, {type:, path:, needle:}] | [:invalid, neden]
23
+ # Hash girdi; tip kapalı sette; path workspace-relative (absolute
24
+ # ve ".." segmenti reddedilir — lexical ön kontrol, containment
25
+ # kanıtı Verifier'ın işi); uzunluk cap'leri.
26
+ def normalize(raw)
27
+ return [:invalid, "verify must be a JSON object"] unless raw.is_a?(Hash)
28
+
29
+ type = raw["type"]
30
+ return [:invalid, "unknown verify type #{GoalLedger.json_type_name(type)}"] unless TYPES.include?(type)
31
+
32
+ path = raw["path"]
33
+ return [:invalid, "verify path is required"] unless path.is_a?(String) && !path.empty?
34
+ return [:invalid, "verify path exceeds #{PATH_MAX_CHARS} chars"] if path.length > PATH_MAX_CHARS
35
+ return [:invalid, "verify path must be workspace-relative"] if path.start_with?("/", "\\\\")
36
+ return [:invalid, "verify path must not escape the workspace"] if path.split(%r{[\\/]}).include?("..")
37
+
38
+ needle_field = NEEDLE_FIELD[type]
39
+ if needle_field
40
+ needle = raw[needle_field]
41
+ return [:invalid, "verify #{type} requires '#{needle_field}'"] unless needle.is_a?(String) && !needle.empty?
42
+ return [:invalid, "verify #{needle_field} exceeds #{NEEDLE_MAX_CHARS} chars"] if needle.length > NEEDLE_MAX_CHARS
43
+ end
44
+
45
+ [:ok, { type: type, path: path, needle: needle_field ? raw[needle_field] : nil }.freeze]
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../platform"
4
+
5
+ module Rubik
6
+ module Orchestrator
7
+ # GoalLedger — SKILL.state (arxiv 2608.26263v2) türevi yapılandırılmış
8
+ # goal durumu (Σ) için patch seam'i. SAF KÜTÜPHANE: I/O yok, thread yok,
9
+ # config yok, prompt/tool yüzeyi yok — bağlayan katman (goal-ledger
10
+ # feature planı) erişimi sahiplenir; bu dosya require zincirinde
11
+ # olmadığından yüklenmeyen kütüphane = davranış farkı yapısal olarak sıfır.
12
+ #
13
+ # GoalState'in aksine immutable value object'lerdir: Monitor'suzdur çünkü
14
+ # paylaşılan mutable durum yoktur; Validator atomic'tir (working-copy-
15
+ # then-discard) ve kabul edilen doküman deep-frozen çıkar.
16
+ module GoalLedger
17
+ # Ret reason'ının sözleşme şekli (beş anahtar HER ZAMAN mevcuttur —
18
+ # tüketici nil-dalı yazmaz); message/hint model-okunur İngilizce'dir ve
19
+ # retry feedback'inde birebir yankılanır. Hash bilinçli tercihtir:
20
+ # `class` üye adı Struct/Data'da yasaklıdır.
21
+ def reason(kind, op_index:, path:, message:, hint:)
22
+ { class: kind, op_index: op_index, path: path, message: message, hint: hint }.freeze
23
+ end
24
+
25
+ # JSON tip adları (reason mesajları modelin gösterdiği değeri JSON
26
+ # dünyasında adlandırır: "expected integer, got float").
27
+ def json_type_name(value)
28
+ case value
29
+ when nil then "null"
30
+ when true, false then "boolean"
31
+ when Integer then "integer"
32
+ when Float then "float"
33
+ when String then "string"
34
+ when Array then "array"
35
+ when Hash then "object"
36
+ else value.class.name
37
+ end
38
+ end
39
+
40
+ # Patch body flood guardı: gerçekçi dolu bir ledger ~2-6 KB; 32 KB
41
+ # ~5x headroom ama dosya-dump flood'unu keser (context_limit 32_768
42
+ # defaultuyla sayısal simetri — loop.rb).
43
+ MAX_PATCH_BYTES = 32_768
44
+ # Tek patch'te op tavanı: gerçek bir adım 3-8 op churn'lar; 64 ~10x
45
+ # headroom, yine de O(ops×doc) validasyon ve retry-echo boyutunu sınırlar.
46
+ MAX_OPS = 64
47
+ # Bounded-retry default'u: ret reason'ları tool-result feedback olarak
48
+ # yankılanır; bütçe tükenirse ESKİ Σ korunur (dmesg warn; goal loop
49
+ # asla brick olmaz). Config knob'u (goal.ledger.max_patch_retries)
50
+ # feature planına aittir.
51
+ DEFAULT_MAX_RETRIES = 2
52
+
53
+ module_function :reason, :json_type_name
54
+ end
55
+ end
56
+ end
57
+
58
+ require_relative "goal_ledger/patch"
59
+ require_relative "goal_ledger/schema"
60
+ require_relative "goal_ledger/validator"
61
+ require_relative "goal_ledger/ledger"
62
+ require_relative "goal_ledger/verify_spec"
63
+ require_relative "goal_ledger/verifier"
64
+ require_relative "goal_ledger/drift"
65
+ require_relative "goal_ledger/observation_projection"