rubik 0.4.1 → 0.5.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.
- checksums.yaml +4 -4
- data/ATTRIBUTION.md +1 -1
- data/CHANGELOG.md +118 -1
- data/lib/rubik/ast/cache_store.rb +69 -5
- data/lib/rubik/ast/extraction_result.rb +6 -0
- data/lib/rubik/ast/graph.rb +32 -5
- data/lib/rubik/ast/ruby_extractor.rb +8 -2
- data/lib/rubik/buffer/apply_contract.rb +6 -0
- data/lib/rubik/buffer/manager.rb +37 -27
- data/lib/rubik/config/defaults.rb +7 -1
- data/lib/rubik/mcp/client.rb +4 -0
- data/lib/rubik/mcp/manager.rb +51 -35
- data/lib/rubik/mcp/proxy_tool.rb +17 -0
- data/lib/rubik/mcp/transport/stdio.rb +142 -17
- data/lib/rubik/orchestrator/budget_manager.rb +2 -0
- data/lib/rubik/orchestrator/loop.rb +1 -0
- data/lib/rubik/orchestrator/shell_deps.rb +4 -0
- data/lib/rubik/orchestrator/turn_executor/user_channel.rb +2 -0
- data/lib/rubik/plugin/manager.rb +39 -15
- data/lib/rubik/plugin/package_inventory.rb +11 -1
- data/lib/rubik/plugin/storage.rb +5 -4
- data/lib/rubik/prompt/doctrine.rb +10 -15
- data/lib/rubik/prompt/tool_teachings.rb +29 -35
- data/lib/rubik/repair/loop.rb +29 -15
- data/lib/rubik/repl/commands/agent_context.rb +1 -0
- data/lib/rubik/repl/commands/system.rb +2 -0
- data/lib/rubik/repl/turn_recorder.rb +3 -0
- data/lib/rubik/security/redactor.rb +13 -9
- data/lib/rubik/storage/vakanuvis/gate.rb +13 -8
- data/lib/rubik/storage/vakanuvis/harness.rb +115 -112
- data/lib/rubik/storage/vakanuvis/orientation.rb +277 -0
- data/lib/rubik/storage/vakanuvis/service.rb +5 -1
- data/lib/rubik/storage/vakanuvis/vocabulary.rb +33 -2
- data/lib/rubik/storage/vakanuvis.rb +8 -1
- data/lib/rubik/tools/builtins/lifecycle.rb +42 -47
- data/lib/rubik/tools/builtins/vakanuvis.rb +13 -15
- data/lib/rubik/ui/capability.rb +19 -5
- data/lib/rubik/version.rb +1 -1
- metadata +2 -1
data/lib/rubik/plugin/storage.rb
CHANGED
|
@@ -6,6 +6,7 @@ require "time"
|
|
|
6
6
|
# save_manifest atomik yazımı Platform.atomic_write kullanır; dosyanın tek başına
|
|
7
7
|
# require edilmesi durumunda da çözümlenebilsin diye açık yüklenir.
|
|
8
8
|
require_relative "../platform"
|
|
9
|
+
require_relative "../log"
|
|
9
10
|
require_relative "package_inventory"
|
|
10
11
|
|
|
11
12
|
module Rubik
|
|
@@ -31,7 +32,7 @@ module Rubik
|
|
|
31
32
|
[project_plugins_dir, global_plugins_dir].each do |dir|
|
|
32
33
|
FileUtils.mkdir_p(dir)
|
|
33
34
|
rescue SystemCallError => e
|
|
34
|
-
warn "
|
|
35
|
+
Rubik::Log.warn(:plugin, "Failed to create plugin directory #{dir}: #{e.class}: #{e.message}")
|
|
35
36
|
end
|
|
36
37
|
end
|
|
37
38
|
|
|
@@ -67,7 +68,7 @@ module Rubik
|
|
|
67
68
|
save_manifest(dir, manifest)
|
|
68
69
|
end
|
|
69
70
|
|
|
70
|
-
def verify_integrity(path, scope: :project)
|
|
71
|
+
def verify_integrity(path, scope: :project, content: nil)
|
|
71
72
|
return :trusted if ENV["RUBIK_TRUST_OVERLAYS"] == "1"
|
|
72
73
|
|
|
73
74
|
dir = scope.to_sym == :global ? global_plugins_dir : project_plugins_dir
|
|
@@ -78,12 +79,12 @@ module Rubik
|
|
|
78
79
|
basename = File.basename(path)
|
|
79
80
|
manifest = load_manifest(dir)
|
|
80
81
|
|
|
81
|
-
current_content = File.exist?(path) ? File.binread(path) : ""
|
|
82
|
+
current_content = content || (File.exist?(path) ? File.binread(path) : "")
|
|
82
83
|
current_hash = PackageInventory.content_hash(current_content)
|
|
83
84
|
|
|
84
85
|
if manifest.key?(basename)
|
|
85
86
|
expected_hash = manifest[basename]["sha256"]
|
|
86
|
-
if current_hash
|
|
87
|
+
if PackageInventory.secure_compare(current_hash, expected_hash)
|
|
87
88
|
:verified
|
|
88
89
|
else
|
|
89
90
|
warn "[SECURITY] Integrity check failed for plugin '#{basename}': file modified since adding."
|
|
@@ -221,14 +221,15 @@ module Rubik
|
|
|
221
221
|
def rule_nine_body(active_tools: nil, plan_mode: false)
|
|
222
222
|
return "Keep the repository worktree free of agent workfiles, scratch notes, and temporary artifacts." if plan_mode
|
|
223
223
|
|
|
224
|
+
vakanuvis_on = tool_active?("vakanuvis", active_tools)
|
|
224
225
|
storage_part = if tool_active?("storage", active_tools)
|
|
225
|
-
Rubik::Prompt::ToolTeachings.teaching_for("storage")
|
|
226
|
+
Rubik::Prompt::ToolTeachings.teaching_for("storage", vakanuvis_active: vakanuvis_on)
|
|
226
227
|
else
|
|
227
228
|
"Keep the repository worktree free of scattered agent workfiles; read any existing " \
|
|
228
229
|
"`rubik://storage/<type>/<id>` entities via `read_file` when a store is wired."
|
|
229
230
|
end
|
|
230
231
|
parts = [storage_part]
|
|
231
|
-
parts << Rubik::Prompt::ToolTeachings.teaching_for("vakanuvis") if
|
|
232
|
+
parts << Rubik::Prompt::ToolTeachings.teaching_for("vakanuvis") if vakanuvis_on
|
|
232
233
|
parts << Rubik::Prompt::ToolTeachings.teaching_for("show_file") if tool_active?("show_file", active_tools)
|
|
233
234
|
parts << "Subagent transcripts are indexed automatically as `transcript` entities (alias `subagent-<storage-id>`) " \
|
|
234
235
|
"and are readable live or persisted via the canonical `rubik://subagent/<storage-id>/transcript` reference — " \
|
|
@@ -244,14 +245,10 @@ module Rubik
|
|
|
244
245
|
if storage_on && vakanuvis_on
|
|
245
246
|
"Persist pipeline knowledge in the central storage (`storage` tool): the decomposed plan, review findings, " \
|
|
246
247
|
"and the final goal record — link stages with entity ids (`links: {\"goal\": \"goal.N.<hash16>\"}`) so the " \
|
|
247
|
-
"audit trail survives the session.
|
|
248
|
-
"
|
|
249
|
-
"
|
|
250
|
-
"
|
|
251
|
-
"`vakanuvis(action: 'recall', query: '...')` (also after compaction). Keep the repository worktree free of " \
|
|
252
|
-
"agent workfiles. Transcripts of finished " \
|
|
253
|
-
"subagents are indexed automatically (searchable via `storage` action 'search') and persist after the " \
|
|
254
|
-
"subagent is reaped."
|
|
248
|
+
"audit trail survives the session. Route durable memory through the chronicler (`vakanuvis` record/recall; " \
|
|
249
|
+
"see rule 9), never through raw storage puts. Keep the repository worktree free of agent workfiles. " \
|
|
250
|
+
"Transcripts of finished subagents are indexed automatically (searchable via `storage` action 'search') " \
|
|
251
|
+
"and persist after the subagent is reaped."
|
|
255
252
|
elsif storage_on
|
|
256
253
|
"Persist pipeline knowledge in the central storage (`storage` tool): the decomposed plan, review findings, " \
|
|
257
254
|
"and the final goal record — link stages with entity ids (`links: {\"goal\": \"goal.N.<hash16>\"}`) so the " \
|
|
@@ -259,11 +256,9 @@ module Rubik
|
|
|
259
256
|
"finished subagents are indexed automatically (searchable via `storage` action 'search') and persist after " \
|
|
260
257
|
"the subagent is reaped."
|
|
261
258
|
elsif vakanuvis_on
|
|
262
|
-
"
|
|
263
|
-
"
|
|
264
|
-
"`
|
|
265
|
-
"agent workfiles. Transcripts of finished subagents remain readable via `rubik://subagent/<storage-id>/transcript` " \
|
|
266
|
-
"after the subagent is reaped."
|
|
259
|
+
"Route durable memory through the chronicler (`vakanuvis` record/recall; see rule 9). Keep the repository " \
|
|
260
|
+
"worktree free of agent workfiles. Transcripts of finished subagents remain readable via " \
|
|
261
|
+
"`rubik://subagent/<storage-id>/transcript` after the subagent is reaped."
|
|
267
262
|
else
|
|
268
263
|
"Keep the repository worktree free of agent workfiles. Transcripts of finished subagents remain readable via " \
|
|
269
264
|
"`rubik://subagent/<storage-id>/transcript` after the subagent is reaped."
|
|
@@ -2,21 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
module Rubik
|
|
4
4
|
module Prompt
|
|
5
|
-
# Tool teachings keyed by canonical tool name (architecture review
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
# its Tools::Declaration; prose (the presentation) lives here — a prompt
|
|
10
|
-
# layer concern. test_tool_teachings welds the two: every key must
|
|
11
|
-
# resolve to a declared (Catalog) tool, so a rename that forgets the
|
|
12
|
-
# prompt fails the suite.
|
|
13
|
-
#
|
|
14
|
-
# The strings are interpolated into the operational rules HEREDOC at the
|
|
15
|
-
# same positions they originally occupied — the rendered rules are
|
|
16
|
-
# byte-identical (pinned by the substring tests in
|
|
17
|
-
# test_structured_context_builder, test_agent_command_contract,
|
|
18
|
-
# test_buffer_apply_conflict, test_repl_tool_separation_and_guardrails).
|
|
5
|
+
# Tool teachings keyed by canonical tool name (architecture review 2026-10-01, candidate 1):
|
|
6
|
+
# the sentences that teach HOW a specific tool is used live here, one key per tool.
|
|
7
|
+
# Identity lives in its Tools::Declaration; prose lives here — a prompt layer concern.
|
|
8
|
+
# test_tool_teachings welds the two: every key must resolve to a declared Catalog tool.
|
|
19
9
|
module ToolTeachings
|
|
10
|
+
STORAGE_BASE =
|
|
11
|
+
"Persist agent workfiles — goals, plans, reviews, and work notes — in the central " \
|
|
12
|
+
"`storage` database (`storage` tool: put/get/list/search/set_ref/delete), NOT as scattered files in " \
|
|
13
|
+
"the repository worktree. Read, list, and search all entity types (`goal`, `plan`, `memory`, `wiki`, " \
|
|
14
|
+
"`transcript`, `review`, `note`) via `storage` or `read_file(path: 'rubik://storage/<type>/<id>')` " \
|
|
15
|
+
"(browse with `rubik://storage/index` and `rubik://storage/<type>` listings)."
|
|
16
|
+
|
|
17
|
+
STORAGE_FALLBACK =
|
|
18
|
+
"#{STORAGE_BASE[0...-1]}; write durable `memory` cards and compiled `wiki` pages through direct " \
|
|
19
|
+
"`storage` `put` on `memory`/`wiki` (reserving direct `storage` `put` on `memory`/`wiki` for sessions " \
|
|
20
|
+
"where `vakanuvis` is unavailable).".freeze
|
|
21
|
+
|
|
20
22
|
TEACHINGS = {
|
|
21
23
|
"replace_file_content" =>
|
|
22
24
|
"For modifying existing files, prefer `replace_file_content` with `path`, exact verbatim `target_content`, " \
|
|
@@ -35,15 +37,7 @@ module Rubik
|
|
|
35
37
|
"params_schema: { type: 'object', properties: { ... }, required: [...] }, code: '->(args) { ... }')` " \
|
|
36
38
|
"(always provide `params_schema` when the handler reads arguments from `args`; omit it or pass an empty " \
|
|
37
39
|
"`{ type: 'object', properties: {} }` schema only for tools that take no arguments).",
|
|
38
|
-
"storage" =>
|
|
39
|
-
"Persist agent workfiles — goals, plans, reviews, and work notes — in the central " \
|
|
40
|
-
"`storage` database (`storage` tool: put/get/list/search/set_ref/delete), NOT as scattered files in " \
|
|
41
|
-
"the repository worktree. Read, list, and search all entity types (`goal`, `plan`, `memory`, `wiki`, " \
|
|
42
|
-
"`transcript`, `review`, `note`) via `storage` or `read_file(path: 'rubik://storage/<type>/<id>')` " \
|
|
43
|
-
"(browse with `rubik://storage/index` and `rubik://storage/<type>` listings); write durable `memory` " \
|
|
44
|
-
"cards and compiled `wiki` pages through `vakanuvis` (`record` / `digest`) whenever `vakanuvis` is in " \
|
|
45
|
-
"your toolbelt, reserving direct `storage` `put` on `memory`/`wiki` for sessions where `vakanuvis` is " \
|
|
46
|
-
"unavailable.",
|
|
40
|
+
"storage" => STORAGE_BASE,
|
|
47
41
|
"run_command" =>
|
|
48
42
|
"GitHub facts must be verified, not assumed: after `gh pr merge` (which can succeed with EMPTY output) " \
|
|
49
43
|
"confirm the merge with `gh pr view <number> --json state`; `mergeable: UNKNOWN` right after a push means " \
|
|
@@ -57,17 +51,13 @@ module Rubik
|
|
|
57
51
|
"while your context stays lean; only fall back to read_file when YOU must reason about the content.",
|
|
58
52
|
"vakanuvis" =>
|
|
59
53
|
"The background chronicler keeps compiled knowledge (memory cards, wiki pages) in rubik://storage. " \
|
|
60
|
-
"At task start
|
|
61
|
-
"
|
|
62
|
-
"
|
|
63
|
-
"
|
|
64
|
-
"body: '...')` — memory writes go
|
|
65
|
-
"through
|
|
66
|
-
"
|
|
67
|
-
"synonyms in BOTH working languages (e.g. TR + EN) — a Turkish query cannot otherwise find an English page. " \
|
|
68
|
-
"If recall is too shallow, queue `vakanuvis(action: 'digest', query: '...')`: it returns a deterministic " \
|
|
69
|
-
"receipt and compiles the wiki page asynchronously. Make the session close a standard digest point: review " \
|
|
70
|
-
"the session's new cards, queue one digest per open topic, and let the home index refresh.",
|
|
54
|
+
"At task start, ask it with `vakanuvis(action: 'recall', query: '...')`: it returns a deterministic " \
|
|
55
|
+
"digest plus rubik:// source links. Recall is keyword-AND over card/page text: use 2-3 core terms. " \
|
|
56
|
+
"If the result is empty, retry once with other-language synonyms. When dispatching a subagent, paste " \
|
|
57
|
+
"the relevant recall digest (or its rubik:// link) into the task brief. At the end of a task, persist " \
|
|
58
|
+
"durable conclusions with `vakanuvis(action: 'record', topic: '...', body: '...')` — memory writes go " \
|
|
59
|
+
"through record (ref minting, upsert, supersede come free), never through raw storage puts. End the body " \
|
|
60
|
+
"with a `Keywords:` line (TR+EN) if you can; it is auto-extracted otherwise.",
|
|
71
61
|
"prune_context" =>
|
|
72
62
|
"When long sessions leave stale exploration and superseded tool output weighing on the ROOT window, " \
|
|
73
63
|
"check `prune_context(action: 'status')` (window estimate vs ceiling, staged plan state) and force a " \
|
|
@@ -79,7 +69,11 @@ module Rubik
|
|
|
79
69
|
|
|
80
70
|
module_function
|
|
81
71
|
|
|
82
|
-
def teaching_for(tool_name
|
|
72
|
+
def teaching_for(tool_name, vakanuvis_active: true)
|
|
73
|
+
return vakanuvis_active ? STORAGE_BASE : STORAGE_FALLBACK if tool_name.to_s == "storage"
|
|
74
|
+
|
|
75
|
+
TEACHINGS[tool_name.to_s]
|
|
76
|
+
end
|
|
83
77
|
end
|
|
84
78
|
end
|
|
85
79
|
end
|
data/lib/rubik/repair/loop.rb
CHANGED
|
@@ -27,20 +27,34 @@ module Rubik
|
|
|
27
27
|
|
|
28
28
|
@snapshot_store.create_snapshot(@buffer_manager)
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
30
|
+
begin
|
|
31
|
+
prompt = build_repair_prompt(current_diag, target_file, @attempts_made)
|
|
32
|
+
res = @provider.complete([{ role: "user", content: prompt }])
|
|
33
|
+
proposed_code = extract_code(res.text)
|
|
34
|
+
|
|
35
|
+
if proposed_code.empty?
|
|
36
|
+
@snapshot_store.undo(@buffer_manager)
|
|
37
|
+
next
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
@buffer_manager.write(target_file, proposed_code)
|
|
41
|
+
|
|
42
|
+
# Verify syntax against in-memory buffer via tempfile
|
|
43
|
+
check_res = check_buffer_syntax(target_file, lang)
|
|
44
|
+
|
|
45
|
+
return { ok: true, attempts: @attempts_made, code: proposed_code } if check_res[:ok]
|
|
46
|
+
|
|
47
|
+
if check_res[:unverified]
|
|
48
|
+
@snapshot_store.undo(@buffer_manager)
|
|
49
|
+
return { ok: false, attempts: @attempts_made, unverified: true, error: check_res[:error] }
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
current_diag = Rubik::Diagnostics::Parser.parse(check_res[:output], lang)
|
|
53
|
+
@snapshot_store.undo(@buffer_manager)
|
|
54
|
+
rescue StandardError => e
|
|
55
|
+
@snapshot_store.undo(@buffer_manager)
|
|
56
|
+
return { ok: false, attempts: @attempts_made, error: "#{e.class}: #{e.message}" }
|
|
57
|
+
end
|
|
44
58
|
end
|
|
45
59
|
|
|
46
60
|
{ ok: false, attempts: @attempts_made, last_diagnostics: current_diag }
|
|
@@ -50,7 +64,7 @@ module Rubik
|
|
|
50
64
|
|
|
51
65
|
def check_buffer_syntax(target_file, lang)
|
|
52
66
|
adapter = @adapter_registry.get(lang.to_s)
|
|
53
|
-
return { ok: true } unless adapter
|
|
67
|
+
return { ok: false, unverified: true, error: "No adapter registered for #{lang}" } unless adapter
|
|
54
68
|
|
|
55
69
|
content = @buffer_manager.read(target_file)
|
|
56
70
|
ext = File.extname(target_file)
|
|
@@ -97,6 +97,7 @@ module Rubik
|
|
|
97
97
|
config: context_cfg,
|
|
98
98
|
force: true
|
|
99
99
|
)
|
|
100
|
+
Rubik::Storage::Vakanuvis::Orientation.inject_after_compaction!(ctx.conversation_history, ctx.runtime_manager, config: ctx.config)
|
|
100
101
|
after_tokens = Rubik::Prompt::TokenEstimator.estimate_history(ctx.conversation_history)
|
|
101
102
|
delta = before_tokens - after_tokens
|
|
102
103
|
ctx.out.puts "Context compaction completed: #{before_tokens} -> #{after_tokens} tokens (reduced #{delta} tokens)."
|
|
@@ -57,11 +57,13 @@ module Rubik
|
|
|
57
57
|
|
|
58
58
|
shell.register_command("clear", description: "Clear conversation history / dialogue turns") do |_args, ctx|
|
|
59
59
|
ctx.conversation_history.clear
|
|
60
|
+
Rubik::Storage::Vakanuvis::Orientation.reset_session!(ctx.runtime_manager)
|
|
60
61
|
ctx.out.puts "Conversation context cleared. Starting fresh topic."
|
|
61
62
|
end
|
|
62
63
|
|
|
63
64
|
shell.register_command("reset", description: "Reset conversation history and in-memory dialogue turns") do |_args, ctx|
|
|
64
65
|
ctx.conversation_history.clear
|
|
66
|
+
Rubik::Storage::Vakanuvis::Orientation.reset_session!(ctx.runtime_manager)
|
|
65
67
|
ctx.out.puts "Conversation context reset. Starting fresh topic."
|
|
66
68
|
end
|
|
67
69
|
|
|
@@ -191,6 +191,8 @@ module Rubik
|
|
|
191
191
|
def apply_staged_prune_plan!
|
|
192
192
|
return if @replaying
|
|
193
193
|
|
|
194
|
+
Rubik::Storage::Vakanuvis::Orientation.inject_session_start!(@conversation_history, @runtime_manager, config: @config) if @conversation_history.empty?
|
|
195
|
+
|
|
194
196
|
harness = Rubik::Coeffects.get(@runtime_manager&.context, Rubik::Coeffects::PRUNER)
|
|
195
197
|
return unless harness
|
|
196
198
|
|
|
@@ -198,6 +200,7 @@ module Rubik
|
|
|
198
200
|
return unless new_window
|
|
199
201
|
|
|
200
202
|
@conversation_history = new_window
|
|
203
|
+
Rubik::Storage::Vakanuvis::Orientation.inject_after_compaction!(@conversation_history, @runtime_manager, config: @config)
|
|
201
204
|
line = "[Pruner] Applied staged plan: dropped #{receipt[:dropped]} unit(s), " \
|
|
202
205
|
"summarized #{receipt[:summarized]} (#{receipt[:skipped_stale]} stale op(s) skipped) — " \
|
|
203
206
|
"window ~#{receipt[:before_tokens]} -> ~#{receipt[:after_tokens]} tokens."
|
|
@@ -38,16 +38,20 @@ module Rubik
|
|
|
38
38
|
return text unless text.is_a?(String)
|
|
39
39
|
return text if text.empty?
|
|
40
40
|
|
|
41
|
-
#
|
|
42
|
-
#
|
|
43
|
-
#
|
|
44
|
-
|
|
41
|
+
# PERF: Fast-path for valid encoding strings: test QUICK_CHECK_REGEX
|
|
42
|
+
# directly on input text before calling dup or scrub. This short-circuits
|
|
43
|
+
# clean strings without allocating extra intermediate string copies or
|
|
44
|
+
# running scrub/gsub passes (~2.7x speedup, ~50% fewer allocations).
|
|
45
|
+
if text.valid_encoding?
|
|
46
|
+
return text.dup unless text.match?(QUICK_CHECK_REGEX)
|
|
45
47
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
result = text.dup
|
|
49
|
+
else
|
|
50
|
+
# Fall back safely for invalid UTF-8 / binary strings: scrub first so
|
|
51
|
+
# match? and gsub! do not raise ArgumentError.
|
|
52
|
+
result = text.scrub
|
|
53
|
+
return result unless result.match?(QUICK_CHECK_REGEX)
|
|
54
|
+
end
|
|
51
55
|
|
|
52
56
|
# Keep replacement order: masking one credential can expose a word
|
|
53
57
|
# boundary needed to recognize the next (for example, a bearer token).
|
|
@@ -54,8 +54,11 @@ module Rubik
|
|
|
54
54
|
# hemen ardından doğum denemesi AYRI bir @lock penceresinde
|
|
55
55
|
# (iki fazlı — tek büyük kritik bölüm İÇİNE ALMA). Doğum
|
|
56
56
|
# başarısızsa iş kuyrukta kalır: yarım durum birikmez.
|
|
57
|
-
# Dönen makbuz dizgesi bayt-bayt eski Harness#digest çıktısıdır
|
|
58
|
-
|
|
57
|
+
# Dönen makbuz dizgesi bayt-bayt eski Harness#digest çıktısıdır;
|
|
58
|
+
# submit_with_status aynı kabulü [makbuz, durum] ikilisi olarak
|
|
59
|
+
# verir (:accepted|:duplicate|:full) — otomatik damıtım red'i
|
|
60
|
+
# makbuz dizgesinden ayırt edebilsin diye (review #346).
|
|
61
|
+
def submit_with_status(query, spawn: true)
|
|
59
62
|
target = Vocabulary.wiki_ref(query)
|
|
60
63
|
# v2 §4-V5: aynı hedefli iş zaten kuyrukta ya da uçuştaysa makbuz
|
|
61
64
|
# 'already queued' varyantına düşer — çift doğum yok.
|
|
@@ -68,18 +71,20 @@ module Rubik
|
|
|
68
71
|
:accepted
|
|
69
72
|
end
|
|
70
73
|
if admission == :duplicate
|
|
71
|
-
return "Digest already queued for '#{target}' (#{AnnalWriter.one_line(query, 60)}): the chronicler is " \
|
|
72
|
-
|
|
74
|
+
return ["Digest already queued for '#{target}' (#{AnnalWriter.one_line(query, 60)}): the chronicler is " \
|
|
75
|
+
"compiling it — recall it later with vakanuvis(action: 'recall', query: '...').", admission]
|
|
73
76
|
end
|
|
74
77
|
if admission == :full
|
|
75
|
-
return "Digest not accepted for '#{target}': capacity #{@max_digest_jobs} is full. " \
|
|
76
|
-
|
|
78
|
+
return ["Digest not accepted for '#{target}': capacity #{@max_digest_jobs} is full. " \
|
|
79
|
+
"Existing jobs are preserved; retry after a digest finishes.", admission]
|
|
77
80
|
end
|
|
78
81
|
|
|
79
|
-
spawned = spawn_job!(batch: false)
|
|
80
|
-
DistillGate.receipt(query: query, target: target, spawned: !spawned.nil?)
|
|
82
|
+
spawned = spawn ? spawn_job!(batch: false) : nil
|
|
83
|
+
[DistillGate.receipt(query: query, target: target, spawned: !spawned.nil?), admission]
|
|
81
84
|
end
|
|
82
85
|
|
|
86
|
+
def submit(query, spawn: true) = submit_with_status(query, spawn: spawn)[0]
|
|
87
|
+
|
|
83
88
|
# ---- Seam 2: worker thread'i (Harness#handle_subagent çağırır) ---
|
|
84
89
|
|
|
85
90
|
# Ham subagent tamamlanma olayı. İş sicilden @lock altında
|