Class: Insika::Commands::GateHarvest
- Inherits:
-
Object
- Object
- Insika::Commands::GateHarvest
- Defined in:
- lib/insika/commands/gate_harvest.rb
Overview
— the double gate on ONE candidate, in order:
eval first (expensive — a full golden replay), conversion second (cheap
— a fold read). Synchronous control command (the Studio dispatches it;
the gate is minutes of real replay, exactly like gate_refinement).
It records both reports on the candidate and moves it: both passed ->
awaiting_approval; either failed -> rejected with the reason (a failed
candidate is terminal — the same finding must re-surface with new
evidence, no silent retry).
Instance Method Summary collapse
-
#call(command) ⇒ Object
payload: { candidate_id: } (tenant from command.meta — WS1) -> Candidate (awaiting_approval | rejected | gated-parked; both reports attached).
-
#initialize(harvest_store:, gate:, conversion_gate:, criterion: nil, event_stream:) ⇒ GateHarvest
constructor
A new instance of GateHarvest.
Constructor Details
#initialize(harvest_store:, gate:, conversion_gate:, criterion: nil, event_stream:) ⇒ GateHarvest
Returns a new instance of GateHarvest.
16 17 18 19 20 21 22 |
# File 'lib/insika/commands/gate_harvest.rb', line 16 def initialize(harvest_store:, gate:, conversion_gate:, criterion: nil, event_stream:) @harvest_store = harvest_store @gate = gate @conversion_gate = conversion_gate @criterion = criterion @event_stream = event_stream end |
Instance Method Details
#call(command) ⇒ Object
payload: { candidate_id: } (tenant from command.meta — WS1) -> Candidate (awaiting_approval | rejected | gated-parked; both reports attached)
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 |
# File 'lib/insika/commands/gate_harvest.rb', line 27 def call(command) p = AgentPayload.symbolize(command.payload) candidate_id = AgentPayload.presence(p[:candidate_id]) raise Insika::ValidationError, "candidate_id is required" if candidate_id.nil? candidate = @harvest_store.find_candidate(candidate_id) || (raise Insika::NotFoundError, "harvest candidate not found: #{candidate_id}") # The base graph wires NO gates (the deployment owns the eval surface): # the candidate parks at gated with the named reason — refuse-with-a- # named-reason, never pass, never crash. if @gate.nil? || @conversion_gate.nil? parked = @harvest_store.attach_gate( candidate.id, eval_gate: { "passed" => false, "reason" => "no_eval_gate_wired" }, conversion_gate: { "passed" => false, "reason" => "no_conversion_gate_wired" }, criterion_sha: nil ) return parked end # The mining budget is read HERE too (the review fix): once the mining # pass spent the pack's cap, the expensive golden replay is refused # with the named reason — the Refinement::Budget discipline ("not # gated — the budget was spent"), applied before the provider bill. run = @harvest_store.find_run(candidate.run_id) cap = run && run.budget && run.budget["tokens"].to_i spent = run && run.cost && run.cost["spent"].to_i if cap&.positive? && spent.to_i >= cap parked = @harvest_store.attach_gate( candidate.id, eval_gate: { "passed" => false, "reason" => "budget_exceeded", "spent" => spent, "tokens" => cap }, conversion_gate: { "passed" => false, "reason" => "budget_exceeded" }, criterion_sha: nil ) return parked end skill = { "name" => candidate.name, "description" => candidate.description, "body" => candidate.body, "triggers" => candidate.triggers } eval_report = @gate.score(agent_id: candidate.agent, skill: skill, run_id: candidate.run_id) conversion_report = @conversion_gate.call(tenant: command.[:tenant], agent: candidate.agent) criterion_sha = @criterion ? @criterion.sha : nil # A conversion REFUSAL (missing data — :no_frozen_baseline etc.) is NOT # a candidate failure: it parks the candidate at gated with both # reports and the named reason — the page shows the ruler's hole. # Only a conversion FAILURE (current worse than baseline) rejects. if refused?(conversion_report) gated = @harvest_store.attach_gate(candidate.id, eval_gate: eval_report.to_h, conversion_gate: conversion_report.to_h, criterion_sha: criterion_sha) emit(:harvest_gated, candidate, gated, eval_report, conversion_report) return gated end passed = eval_report.passed && conversion_report.passed @harvest_store.attach_gate(candidate.id, eval_gate: eval_report.to_h, conversion_gate: conversion_report.to_h, criterion_sha: criterion_sha) result = if passed @harvest_store.mark_awaiting(candidate.id) else reason = eval_report.passed ? conversion_report.reason : eval_report.reason @harvest_store.mark_rejected(candidate.id, operator: "gate", note: reason) end emit(:harvest_gated, candidate, result, eval_report, conversion_report) result end |