local T = require("alc_shapes.t")
local check = require("alc_shapes.check")
local reflect = require("alc_shapes.reflect")
local luacats = require("alc_shapes.luacats")
local spec_resolver = require("alc_shapes.spec_resolver")
local instrument = require("alc_shapes.instrument")
local M = {}
M.VERSION = "0.25.1"
M.voted = T.shape({
consensus = T.string:describe("LLM-synthesized majority summary"),
answer = T.string:is_optional():describe("Majority answer (nil when no paths converge)"),
answer_norm = T.string:is_optional():describe("Normalized vote key"),
paths = T.array_of(T.shape({
reasoning = T.string,
answer = T.string,
})):describe("Per-path reasoning + extracted answer"),
votes = T.array_of(T.string):describe("Normalized vote per path, 1-indexed"),
vote_counts = T.map_of(T.string, T.number):describe("{ [norm] = count } tally"),
n_sampled = T.number:describe("Number of sampled paths"),
total_llm_calls = T.number,
}, { open = true })
M.paneled = T.shape({
arguments = T.array_of(T.shape({
role = T.string,
text = T.string,
})):describe("Per-role position statements"),
synthesis = T.string:describe("Moderator synthesis"),
}, { open = true })
M.assessed = T.shape({
answer = T.string,
confidence = T.number:describe("Self-assessed confidence 0.0–1.0"),
total_llm_calls = T.number,
}, { open = true })
M.calibrated = T.shape({
answer = T.string,
confidence = T.number:describe("Initial self-assessed confidence"),
escalated = T.boolean:describe("Whether fallback was triggered"),
strategy = T.one_of({ "direct", "retry", "panel", "ensemble" }),
total_llm_calls = T.number,
fallback_detail = T.table:is_optional():describe("Fallback strategy result (voted/paneled)"),
}, { open = true })
M.conformal_decided = T.shape({
action = T.one_of({ "commit", "escalate", "anomaly" })
:describe("Three-way decision per Proposition 3"),
selected = T.string:is_optional()
:describe("Committed label (nil when action != 'commit')"),
prediction_set = T.array_of(T.string)
:describe("Labels y with P_social(y|x) >= tau"),
p_social = T.map_of(T.string, T.number)
:describe("Linear opinion pool output { [label] = prob }"),
coverage_level = T.number:describe("1 - alpha (finite-sample guarantee)"),
q_hat = T.number:describe("Calibration quantile of nonconformity scores"),
tau = T.number:describe("1 - q_hat (prediction-set threshold)"),
card_id = T.string:is_optional()
:describe("Emitted Card id (only when auto_card=true)"),
}, { open = true })
M.deliberated = T.shape({
answer = T.string:describe("Selected option's final answer text"),
decision_packet = T.shape({
selected_option = T.shape({
answer = T.string,
rationale = T.string,
evidence = T.array_of(T.string),
}, { open = true }):describe("Chosen option with rationale and cited evidence"),
residual_objections = T.array_of(T.string)
:describe("Objections not fully resolved (empty array allowed, nil禁止)"),
minority_report = T.array_of(T.shape({
position = T.string,
rationale = T.string,
confidence = T.number,
}, { open = true }))
:describe("Dissenting positions with confidence (empty array allowed, nil禁止)"),
next_actions = T.array_of(T.string)
:describe("Concrete follow-up actions (empty array allowed, nil禁止)"),
reopen_triggers = T.array_of(T.string)
:describe("Conditions to reopen deliberation (empty array allowed, nil禁止)"),
}, { open = true }):describe("5-component decision packet; all 5 fields MUST be non-nil"),
workspace = T.shape({
problem_view = T.string,
key_frames = T.array_of(T.string),
emerging_ideas = T.array_of(T.string),
tensions = T.array_of(T.string),
synthesis_in_progress = T.string,
next_actions = T.array_of(T.string),
}, { open = true }):describe("Shared workspace 6 fields after finalization"),
history = T.array_of(T.table):describe("Per-stage typed-act log (14-act typed)"),
convergence = T.one_of({ "dominance", "no_blocking", "fallback" })
:describe("How the session converged"),
stats = T.shape({
rounds_used = T.number,
total_acts = T.number,
options_count = T.number,
total_llm_calls = T.number,
}, { open = true }):describe("Execution statistics"),
card_id = T.string:is_optional()
:describe("Emitted Card id (only when auto_card=true)"),
}, { open = true })
M.smc_sampled = T.shape({
answer = T.string:describe("Argmax-weight particle's answer text"),
particles = T.array_of(T.shape({
answer = T.string:describe("Particle answer text"),
weight = T.number:describe("Final normalized importance weight"),
reward = T.number:describe("Final reward under caller's reward_fn"),
history = T.array_of(T.table):describe("Per-iteration trace entries (1 slot in v1)"),
}, { open = true }))
:describe("All N particles in their final state"),
weights = T.array_of(T.number)
:describe("Final normalized weights (Σ ≈ 1)"),
iterations = T.number:describe("K SMC rounds actually executed"),
resample_count = T.number:describe("Number of iterations that triggered multinomial resample"),
ess_trace = T.array_of(T.number)
:describe("ESS recorded at the start of each iteration (length K)"),
stats = T.shape({
total_llm_calls = T.number:describe("alc.llm invocations issued by the pkg"),
total_reward_calls = T.number:describe("reward_fn invocations (caller-provided)"),
}, { open = true }):describe("Execution counters (open for diagnostics like mh_rejected)"),
card_id = T.string:is_optional()
:describe("Emitted Card id (only when auto_card=true)"),
}, { open = true })
local ranked_item_3 = T.shape({
rank = T.number,
index = T.number,
text = T.string,
})
local ranked_item_4 = T.shape({
rank = T.number,
index = T.number,
score = T.number,
text = T.string,
})
M.tournament = T.shape({
best = T.string:describe("Winner text"),
best_index = T.number:describe("Winner original index (1-based)"),
total_wins = T.number:describe("Winner's win count"),
candidates = T.array_of(T.string):describe("Input candidate texts"),
matches = T.array_of(T.shape({
a = T.number:describe("Index of first candidate"),
b = T.number:describe("Index of second candidate"),
winner = T.number:describe("Index of the winner"),
reason = T.string:describe("Judge verdict explanation"),
})):describe("Pairwise match log"),
}, { open = true })
M.listwise_ranked = T.shape({
ranked = T.array_of(ranked_item_3):describe("Full ranking"),
top_k = T.array_of(ranked_item_3):describe("Top-k subset"),
killed = T.array_of(ranked_item_3):describe("Eliminated candidates"),
best = T.string:describe("Top-ranked text"),
best_index = T.number:describe("Top-ranked original index (1-based)"),
n_candidates = T.number,
total_llm_calls = T.number,
}, { open = true })
M.pairwise_ranked = T.shape({
ranked = T.array_of(ranked_item_4):describe("Full ranking with scores"),
top_k = T.array_of(ranked_item_4):describe("Top-k subset"),
killed = T.array_of(ranked_item_4):describe("Eliminated candidates"),
best = T.string:describe("Top-ranked text"),
best_index = T.number:describe("Top-ranked original index (1-based)"),
method = T.one_of({ "allpair", "sorting" }):describe("Comparison strategy"),
score_semantics = T.one_of({ "copeland", "rank_inverse" }):describe("Score interpretation"),
n_candidates = T.number,
total_llm_calls = T.number,
position_bias_splits = T.number:describe("Position-bias correction splits"),
both_tie_pairs = T.number:describe("Pairs that tied in both directions"),
}, { open = true })
local funnel_stage = T.discriminated("name", {
listwise_rank = T.shape({
name = T.one_of({ "listwise_rank" }),
input_count = T.number,
output_count = T.number,
llm_calls = T.number,
window_size = T.number,
}),
listwise_skipped = T.shape({
name = T.one_of({ "listwise_skipped" }),
input_count = T.number,
output_count = T.number,
llm_calls = T.number,
reason = T.string,
}),
multi_axis_scoring = T.shape({
name = T.one_of({ "multi_axis_scoring" }),
input_count = T.number,
output_count = T.number,
llm_calls = T.number,
axes_count = T.number,
parse_failures = T.number,
score_range = T.shape({ min = T.number, max = T.number }),
}),
scoring_skipped = T.shape({
name = T.one_of({ "scoring_skipped" }),
input_count = T.number,
output_count = T.number,
llm_calls = T.number,
reason = T.string,
}),
pairwise_rank_allpair = T.shape({
name = T.one_of({ "pairwise_rank_allpair" }),
input_count = T.number,
output_count = T.number,
llm_calls = T.number,
position_bias_splits = T.number,
both_tie_pairs = T.number,
}),
direct_pairwise = T.shape({
name = T.one_of({ "direct_pairwise" }),
input_count = T.number,
output_count = T.number,
llm_calls = T.number,
position_bias_splits = T.number,
both_tie_pairs = T.number,
}),
})
M.funnel_ranked = T.shape({
ranking = T.array_of(T.shape({
rank = T.number,
text = T.string,
original_index = T.number:describe("Pre-funnel candidate index (1-based)"),
pairwise_score = T.number:describe("Copeland score from pairwise stage"),
})):describe("Final ranking"),
best = T.string:describe("Top-ranked text"),
best_index = T.number:describe("Top-ranked original index (1-based)"),
funnel_bypassed = T.boolean:describe("True when N < 6 bypasses funnel stages"),
bypass_reason = T.string:is_optional():describe("Reason for bypass (nil when not bypassed)"),
total_llm_calls = T.number,
naive_baseline_calls = T.number:describe("Hypothetical full-pairwise call count"),
naive_baseline_kind = T.string:describe("Baseline method identifier"),
savings_percent = T.number:is_optional():describe("LLM call savings vs baseline (nil on bypass)"),
warnings = T.array_of(T.shape({
code = T.string:describe("Machine-readable warning identifier"),
severity = T.one_of({ "warn", "critical" }),
data = T.table:describe("Diagnostic payload (structure varies by code)"),
message = T.string:describe("Human-readable summary"),
})):describe("Diagnostic warnings"),
stages = T.array_of(funnel_stage):describe("Per-stage detail (discriminated by name)"),
funnel_shape = T.array_of(T.number):describe("Candidate counts per stage [N, s1_out, s2_out]"),
}, { open = true })
local safe_panel_stage = T.discriminated("name", {
condorcet = T.shape({
name = T.one_of({ "condorcet" }),
p_estimate = T.number,
recommended_n = T.number,
expected_accuracy = T.number,
target_accuracy = T.number,
target_met = T.boolean,
}),
condorcet_anti_jury = T.shape({
name = T.one_of({ "condorcet_anti_jury" }),
p_estimate = T.number,
is_anti_jury = T.boolean,
}),
condorcet_coin_flip = T.shape({
name = T.one_of({ "condorcet_coin_flip" }),
p_estimate = T.number,
is_anti_jury = T.boolean,
}),
sc = T.shape({
name = T.one_of({ "sc" }),
panel_size = T.number,
answer = T.string,
plurality_fraction = T.number,
margin_gap = T.number,
n_distinct_answers = T.number,
unanimous = T.boolean,
llm_calls = T.number,
}),
vote_prefix_stability = T.shape({
name = T.one_of({ "vote_prefix_stability" }),
signal_type = T.string,
series_length = T.number,
is_safe = T.boolean,
peak_idx = T.number,
consecutive_drops = T.number,
}),
vote_prefix_stability_skipped = T.shape({
name = T.one_of({ "vote_prefix_stability_skipped" }),
signal_type = T.string,
reason = T.string,
series_length = T.number,
is_safe = T.boolean,
}),
calibrate = T.shape({
name = T.one_of({ "calibrate" }),
confidence = T.number,
threshold = T.number,
needs_investigation = T.boolean,
escalated = T.boolean,
llm_calls = T.number,
}),
})
M.safe_paneled = T.shape({
answer = T.string:is_optional():describe("Consensus answer (nil on abort)"),
confidence = T.number:describe("Meta-confidence estimate"),
panel_size = T.number:describe("Actual panel size used"),
plurality_fraction = T.number:describe("Top-answer vote fraction"),
margin_gap = T.number:describe("(top - runner_up) / n"),
vote_counts = T.map_of(T.string, T.number):describe("{ [normalized_answer] = count } tally"),
n_distinct_answers = T.number:describe("Count of unique answers"),
expected_accuracy = T.number:describe("Condorcet expected majority accuracy"),
target_met = T.boolean:describe("Whether expected accuracy >= target"),
is_safe = T.boolean:describe("Vote-prefix stability safe flag"),
anti_jury = T.boolean:describe("Condorcet anti-jury detection"),
aborted = T.boolean:describe("True if early-abort triggered"),
needs_investigation = T.boolean:describe("True if meta-confidence below threshold"),
unanimous = T.boolean:describe("All votes identical"),
total_llm_calls = T.number,
abort_reason = T.string:is_optional():describe("Abort reason (nil when not aborted)"),
stages = T.array_of(safe_panel_stage):describe("Per-stage detail (discriminated by name)"),
}, { open = true })
local quick_vote_sample = T.shape({
reasoning = T.string,
answer = T.string,
norm = T.string,
}, { open = true })
local quick_vote_sprt = T.shape({
log_lr = T.number,
n = T.number,
a_bound = T.number,
b_bound = T.number,
}, { open = true })
local quick_vote_params = T.shape({
p0 = T.number,
p1 = T.number,
alpha = T.number,
beta = T.number,
min_n = T.number,
max_n = T.number,
}, { open = true })
M.quick_voted = T.shape({
answer = T.string:describe("Leader answer from sample 1 (cleaned, not normalized)"),
leader_norm = T.string:describe("Normalized leader key used for agreement tests"),
outcome = T.one_of({ "confirmed", "rejected", "truncated" })
:describe("Terminal state: confirmed=H1 accepted, rejected=H0 accepted, truncated=no verdict at max_n"),
verdict = T.one_of({ "accept_h1", "accept_h0", "continue" })
:describe("Underlying SPRT verdict from the final decide()"),
n_samples = T.number:describe("Total samples drawn (1 leader + k agreement observations)"),
vote_counts = T.map_of(T.string, T.number):describe("{ [norm] = count } tally across all samples"),
samples = T.array_of(quick_vote_sample):describe("Per-sample reasoning + extracted answer"),
sprt = quick_vote_sprt:describe("Final SPRT state snapshot"),
params = quick_vote_params:describe("Echoed parameter values"),
total_llm_calls = T.number:describe("2 × n_samples (reasoning + extract per sample)"),
needs_investigation = T.boolean:describe("True only when outcome == 'truncated' (evidence inconclusive at declared α/β). 'rejected' is a conclusive verdict and does NOT set this flag."),
}, { open = true })
M.deep_paneled = T.shape({
answer = T.any:describe("Plurality answer (nil on abort)"),
confidence = T.number:describe("Meta-confidence estimate"),
panel_size = T.number:describe("Requested panel size"),
n_branches_completed = T.number:describe("Branches actually finished"),
plurality_fraction = T.number:describe("Top-answer vote fraction"),
margin_gap = T.number:describe("(top - runner_up) / n"),
vote_counts = T.map_of(T.string, T.number):describe("{ [normalized_answer] = count } tally"),
n_distinct_answers = T.number:describe("Count of unique normalized answers"),
branches = T.table:describe("{ [bkey] = { approach, answer, best_score, tree_stats } }"),
expected_accuracy = T.number:describe("Condorcet expected majority accuracy"),
target_met = T.boolean:describe("Whether expected accuracy >= ctx.target_accuracy"),
anti_jury = T.boolean:describe("Condorcet anti-jury detection at Stage 1"),
aborted = T.boolean:describe("True if early-abort triggered"),
needs_investigation = T.boolean:describe("True if meta-confidence below threshold"),
unanimous = T.boolean:describe("All normalized votes identical"),
diversity = T.table:is_optional():describe("{ n_distinct, distinctness, decomp_status }"),
decomp = T.table:is_optional():describe("ensemble_div.decompose output (nil if Stage 3b skipped)"),
total_llm_calls = T.number,
abort_reason = T.string:is_optional():describe("Abort reason (nil when not aborted)"),
stages = T.array_of(T.table):describe("Per-stage detail (heterogeneous)"),
}, { open = true })
M.check = check.check
M.assert = check.assert
M.assert_dev = check.assert_dev
M.is_dev_mode = check.is_dev_mode
M.fields = reflect.fields
M.walk = reflect.walk
M.is_schema = T._internal.is_schema
M.T = T
M.LuaCats = luacats
M.spec_resolver = spec_resolver
M.instrument = instrument.instrument
local RESERVED_SHAPE_NAMES = {
"any", "check", "assert", "assert_dev", "is_dev_mode",
"fields", "walk", "is_schema", "T", "LuaCats", "spec_resolver",
"instrument", "_internal",
}
local function assert_no_reserved_shapes(mod)
for i = 1, #RESERVED_SHAPE_NAMES do
local name = RESERVED_SHAPE_NAMES[i]
local v = rawget(mod, name)
if type(v) == "table" and rawget(v, "kind") == "shape" then
error(string.format(
"alc_shapes: '%s' is reserved (assert shortcut); cannot register a shape under this name",
name), 2)
end
end
end
assert_no_reserved_shapes(M)
M._internal = {
assert_no_reserved_shapes = assert_no_reserved_shapes,
RESERVED_SHAPE_NAMES = RESERVED_SHAPE_NAMES,
}
return M