local ctx = _CTX
local axis = ctx.axis
local applied = ctx.applied or {}
local function pass(evidence)
return { Pass = { evidence = evidence } }
end
local function deny(reason)
return { Deny = { reason = reason } }
end
local function walk_step_refs(node, fn)
if type(node) ~= "table" then return end
local kind = node.kind
if kind == "step" then
fn(node["ref"])
elseif kind == "seq" then
for _, child in ipairs(node.children or {}) do
walk_step_refs(child, fn)
end
elseif kind == "branch" then
walk_step_refs(node["then"], fn)
walk_step_refs(node["else"], fn)
elseif kind == "fanout" then
walk_step_refs(node.body, fn)
elseif kind == "loop" then
walk_step_refs(node.body, fn)
elseif kind == "try" then
walk_step_refs(node.body, fn)
walk_step_refs(node.catch, fn)
end
end
local function verify_des(applied)
local bp = applied.new_bp_json
if type(bp) ~= "table" then
return deny("new_bp_json missing")
end
if type(bp.id) ~= "string" or bp.id == "" then
return deny("blueprint.id missing or not string")
end
if type(bp.flow) ~= "table" then
return deny("blueprint.flow missing or not table")
end
if type(bp.agents) ~= "table" then
return deny("blueprint.agents missing or not array")
end
return pass("blueprint shape ok (id/flow/agents present)")
end
local function verify_canonical(applied)
local canon_yaml = host.canonical_yaml(applied.new_bp_json)
local canon_hash = host.content_hash(canon_yaml)
if canon_hash == applied.new_hash then
return pass("canonical hash matches input hash")
else
return deny(string.format(
"canonical hash mismatch: input=%s canonical=%s",
tostring(applied.new_hash), tostring(canon_hash)
))
end
end
local function verify_noop(applied)
if applied.new_hash == applied.prev_hash then
return deny("patch is no-op (new_hash == prev_hash)")
else
return pass("patch produces new hash")
end
end
local function verify_agent_ref(applied)
local bp = applied.new_bp_json
if type(bp) ~= "table" or type(bp.agents) ~= "table" or type(bp.flow) ~= "table" then
return deny("blueprint shape invalid for agent-ref verify")
end
local agent_names = {}
local agent_count = 0
for _, a in ipairs(bp.agents) do
if type(a) == "table" and type(a.name) == "string" then
agent_names[a.name] = true
agent_count = agent_count + 1
end
end
local unresolved = {}
walk_step_refs(bp.flow, function(r)
if not agent_names[r] then
table.insert(unresolved, r)
end
end)
if #unresolved == 0 then
return pass(string.format("all %d step refs resolved", agent_count))
else
return deny("unresolved step refs: " .. table.concat(unresolved, ", "))
end
end
local outcome
if axis == "des" then
outcome = verify_des(applied)
elseif axis == "canonical" then
outcome = verify_canonical(applied)
elseif axis == "noop" then
outcome = verify_noop(applied)
elseif axis == "agent-ref" then
outcome = verify_agent_ref(applied)
else
outcome = deny("unknown verifier axis: " .. tostring(axis))
end
return { axis = axis, outcome = outcome }