reconcile.py reads any input a person brings and grades a supplied statement part by part - #289
Open
sandeep-agami wants to merge 4 commits into
Open
Conversation
sandeep-agami
force-pushed
the
ACE-115-reconcile-intake-ledger
branch
3 times, most recently
from
September 12, 2026 05:22
ea58118 to
e2060f0
Compare
This was referenced Sep 12, 2026
Open
…atement part by part
Three verbs beside parse, diff and band, which are unchanged and still pinned.
- `intake` reads the four shapes the reconcile skill will accept into one row
shape {question?, statement?, expected?} plus provenance: a list of
questions, questions with the SQL the person trusts, labels with numbers,
labels with numbers and the SQL behind each tile. Detection is by content,
never by asking. A CSV whose third column is SQL yields a statement instead
of a label with SQL glued onto it; a third column that is not SQL is still
context, as parse always read it. Several files merge by label under a case
and whitespace fold. Exit 2 for a file it cannot open, 4 for an input with
no question, statement or number anywhere.
- `ledger` grades every part of a supplied statement from fixed filenames in
its row directory: run.json, the sm prepare and sm receipt outputs, the
join-probes and filter-values judgements, and the probe CSVs the tier
returned. Four grades, and only measurement earns model_gap. The dependency
rule is applied: a join that could not be graded leaves the fan-out check on
its aggregate unresolved. A scope refusal is a finding about the semantic
model, not a crash. A zero-byte probe CSV is a probe that failed. The weakest
part decides the verdict and the counts travel with it.
- `findings` reads rows.jsonl and every row directory and writes findings.json
(one entry per problem, the same missing join seen from two statements in
either order counted once), query_defects.json (the person's own defects,
apart, so nothing about the semantic model is proposed from them), and
ledger.json. A clean statement the AI answered differently is a finding of
kind example.
Three shared references carry the procedure: evidence-row.md, part-ledger.md
and statement-check.md, the last saying how a supplied statement runs the
road the AI's SQL runs, by reference to agami-query, never with weaker guards.
Spec: ACE-115
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…lean Second review round on intake, ledger and findings. Each change is one more place an absent or unreadable input, or a part left open, used to read as a statement that held. - ledger: after a run that succeeded, a prepare, receipt, join-probes or judge file that is absent, zero bytes, an error line, or the wrong shape is one open part (fan_out:*, receipt:*, join:*, literal:*) naming the file and the problem; a verb that could not read the statement opens the same way. An output column the receipt marks undetermined is unresolved, not a gap; the matched metric's evidence reads the receipt's `name`. CSV headers match without regard to case, and the one-column fall-back never reads a wider row. One overlap probe's empty file beside a zero is unresolved, not "keys never meet". Two joins between the same tables are two parts. A join without a status stays open instead of being re-derived from flags. The run evidence carries the classifier's kind and remediation, never stderr. - findings: an example is offered only when every part is confirmed and the row was graded at all. Filter keys drop the alias so one declared filter seen through `o.` and `orders.` is one finding. - intake: a `.sql` file's chunks get the statement test a CSV cell gets, so a DROP never becomes a row; exit 4 still prints the skipped lines and their reasons. - statement-check.md: the profile root on every verb; statements and probes passed by file, never inline in a shell string; run.json without raw stderr; a run record beside every probe, so every execution and refusal in the phase is written down. part-ledger.md and evidence-row.md say the same, and name the `words` and `merged_from` keys. Spec: ACE-115 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…oin dropped, and names an undeclared value list Third round, from a test run on a real profile. - fan_out: an aggregate the pre-flight left undetermined is confirmed when every join the statement writes is listed, confirmed, and brings in one row at most (its right side is the one side of the declared relationship the statement wrote, or its written column is unique by the semantic model). The rule ranges over every join part, because the pre-flight's own joins list is empty for exactly this aggregate. Otherwise the open note repeats the pre-flight's reason instead of blaming a join. - A fifth word, `noted`: a fact the run states and never judges. Ranked below confirmed, so it never decides the row's verdict and never blocks an example. First use: `dropped_rows:<a>-<b>`, the left table's rows with no partner on the right, from the probe join-probes now plans. - values_declared:<table>.<column>, one per filtered column from the judge's `columns` map: a model_gap of kind description when the column holds a short list of values and the semantic model lists none of them (or an undecoded empty list); noted for a wide column, an empty one, or a sensitive one; unresolved when the distinct probe did not run. The same finding family as a stale list. - metric: a match to a metric whose source tables the statement never reads is unresolved, the match being by shape alone. - part-ledger.md and statement-check.md say the same. Spec: ACE-115 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ropped-rows count names the side it left out Fourth review round on the ledger. - `unique_by_model` is keyed as the semantic model spells a column; the written pair carries the statement's lowercased spelling. The one-row stamp now folds both sides, so an uppercase- introspected model still confirms a count through its declared joins. The fallback's live shape (a matched edge whose one side is the left, and a unique right column) is tested; the old test used a shape the verb cannot emit. - The dropped-rows part reads `total` and `dropped` by header only, so a one-column result is a probe that did not answer and never N of N dropped; the note names the side the probe did not count, because an inner join drops from both. - Tests for the judge's `empty` and `failed` states and a sensitive column that is also listed, and for a metric over several tables when the statement reads one of them. Spec: ACE-115 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
sandeep-agami
force-pushed
the
ACE-115-reconcile-intake-ledger
branch
from
September 13, 2026 02:16
e2060f0 to
b5d95b9
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Spec: ACE-115 (feature
reconcile-evidence, brief F10)Stacked on #288. Base is
ACE-114-reconcile-sm-verbs, because the ledger reads what those four verbs emit. Merge #288 first, then retarget this tomain. Do not merge without manual review.Summary
agami-reconcileis learning to take the evidence a person brings, in any of four shapes, and to grade a supplied SQL statement part by part instead of trusting it. #288 added the verbs that describe a statement; this PR adds the deterministic half that reads the input and grades the parts. Nothing here runs SQL, and nothing writes to the semantic model. The skill change that wires it all together is the next PR.Three verbs in
plugins/agami/scripts/reconcile.py, besideparse,diffandband, which are unchanged and still pinned bytests/test_reconcile.py:intake --file <path> [--file …] [--source …]reads a list of questions, questions with the SQL the person trusts, labels with numbers, or labels with numbers and the SQL behind each tile, into one row shape{question?, statement?, expected?}plus provenance. Detection is by content, never by asking. A CSV whose third column is SQL yields a statement rather than a label with SQL glued onto it; a third column that is not SQL is still context, asparsealways read it. Several files merge by label under a case and whitespace fold. Exit 2 for a file it cannot open, 4 for an input with nothing usable.ledger --row-dir <dir> [--with-claims]grades every part of a supplied statement from fixed filenames in its row directory: what happened when it ran, whatsm prepareandsm receiptsaid, whatsm join-probesandsm filter-values judgereported, and the probe CSVs the execution tier returned. Four grades (confirmed,model_gap,query_defect,unresolved), and only measurement earnsmodel_gap. The dependency rule is applied: a join that could not be graded leaves the fan-out check on its aggregateunresolved, said out loud. A scope refusal is a finding about the semantic model, not a crash. A zero-byte probe CSV is a probe that failed. The weakest part decides the verdict; the counts travel with it.findings --run-dir <dir>writesfindings.json(one entry per problem, the same missing join seen from two statements in either order counted once, and a filter gap never colliding with a metric gap),query_defects.json(the person's own defects, listed apart so nothing about the semantic model is proposed from them), andledger.json. A clean statement the AI answered differently is a finding of kindexample.Three shared references carry the procedure the skill and
agami-save-correctionwill both follow:shared/evidence-row.md,shared/part-ledger.md, andshared/statement-check.md, the last saying how a supplied statement runs the road the AI's own SQL runs, by reference toagami-query, never with weaker guards.shared/file-layout.mdgains the run directory.Verification
tests/test_reconcile_intake.py: 15 tests, the four shapes, the pinned three-column label case, JSON input, header recognition by name, merging by label, provenance, skips and exit codes.tests/test_reconcile_ledger.py: 30 tests, one per grading rule (the run, joins in every status, the probe rows, aggregates and the dependency rule, filters, metrics with the bare-count exemption, typed values, claims), precedence, idempotence, and the findings' keys, kinds and defects list.tests/test_reconcile.pypasses untouched (parse,diff,bandbyte-identical).uv run dev.py check: ruff and gitleaks clean, no vendored-lib drift, 5642 passed, 12 skipped, 1 pre-existing failure:tests/test_golden_run.py::test_a_client_that_cannot_be_found_still_fails_as_a_generation, which fails identically on untouchedorigin/mainon this machine because a client binary exists at/opt/homebrew/bin/claude. Not touched by this change.Review round 2
A second panel pass, fixed in the second commit with regression tests (ledger 45, intake 17):
fan_out:*/receipt:*/join:*/literal:*gradedunresolved, naming the file and the problem. Grading only what was there made a crashed verb read as a clean statement.undeterminedoutput columns areunresolved, notmodel_gap; a failure to read is never a gap. The matched metric's evidence reads the receipt'snamekey.unresolved, not "keys never meet". Two joins between the same tables are two parts (#2). A join without a status stays open.confirmedand a row that was graded at all. Filter finding keys drop the alias, so one declared filter is one finding..sqlfile's chunks get the statement check, so aDROPnever becomes a row. Exit 4 still prints what was skipped and why.kindandremediation, never stderr.statement-check.md: the profile root on every verb (three commands were missing it), statements and probes passed by file path rather than inline in a shell string, and a.run.jsonrecord beside every probe so every execution and refusal in the phase is written down.Review round 3
From a test run on a real profile. Third commit, ledger tests 54 passing:
confirmed. The pre-flight leavesCOUNT(*)undeterminedbecause it names no column; when every join the statement writes is listed, confirmed, and brings in one row at most (its right side is the declared relationship's one side, or its written column is unique by the semantic model), nothing can multiply the count. The rule reads every join part, because the pre-flight's ownjoinslist is empty for exactly this aggregate.sm preparenow carries, instead of blaming a join where nothing was wrong.noted, for a fact the run states and never judges. It never decides the row's verdict and never blocks an example. First use:dropped_rows:<a>-<b>, the left table's rows with no partner on the right, from the probe Four sm verbs grade a statement a person supplied, part by part #288 now plans.values_declared:<table>.<column>, one per filtered column:model_gapwhen the column holds a short list of values the semantic model lists none of,notedfor a wide, empty or sensitive column,unresolvedwhen the distinct probe did not run. Same finding family as a stale list, so it reachesfindings.json.unresolved, using thesource_tablesthe receipt now carries.Review round 4
Fourth panel pass on the third commit. Fixed in the fourth, with regression tests (ledger tests 68 passing):
emptyandfaileddistinct states, a sensitive column that is also listed, and a metric over several tables read through one of them.🤖 Generated with Claude Code