ClaimPackSource ingests Grok-4 companion bundles (axiomsg4-v2.json +
theoremsg4-v2.json) at the right grain — one Document per axiom or
theorem record. Each record carries Δ (LaTeX symbolic) + ∇verbose
prose, explicit source citation (Mendelson, Enderton, Hilbert,
Newton, Kolmogorov, Łukasiewicz), foundational-group taxonomy, and a
runicLabel that rides as soft metadata only (runtime mints its own
pointer IDs per CTI architecture). Pillar-level
provenance.references arrays become outbound pillar_reference edges.
Lenient JSON parser strips ```json fences and double-escapes lone
LaTeX backslashes (\Theta, \heart, \vec) without corrupting
already-correct \\to pairs — walks left-to-right and pass-throughs
legal escape sequences. Malformed bundles raise rather than return
empty; silent zero-doc would be a footgun.
CLI surface: --source claim_pack with a repeatable --bundle FILE
flag mirroring html source's --url action=append. Single --path
also accepted for one-bundle ingest.
Drive-by: removed a function-local `from arborist.store import
connect` inside _cmd_ingest's providence branch that was shadowing
the module-level binding via Python's "any local assignment makes
the name local for the entire function" rule, breaking every
non-providence ingest with UnboundLocalError. Comment left in
place explaining why not to re-add it.
Smoke-tested on /home/fox/Downloads/{axiomsg4,theoremsg4}-v2.json
end-to-end: 78 docs (55 axioms + 23 theorems across 7 pillars),
14 deduped pillar-reference edges, 78 audit events, 10/10 sampled
Merkle proofs verify, FTS5 search returns Modus Tollens for
"modus tollens".
Honest ceiling: kind=surface for every record. The pack is
pre-distilled but its provenance is asserted not proven — until
Mendelson/Enderton/Hilbert texts are themselves ingested as
surfaces, the verifier has no derivations.proof_blob to compute
and claim-pack records max out at ANCHOR-WARRANTED on the
four-rung ladder. That's a follow-up ticket, not this one.
Hard constraints honored: no new audit ledger (audit_events
remains the only chained-sha256 ledger; bundle's self-validation
fields ride as metadata only); no kind=core without surface
ancestor; cache_key invariants untouched.
15 unit tests cover lenient parser, slug stability, ref
resolution, doc grain, URI stability, content layout, extra
metadata, edge emission, error paths. All 1280 tests in
make test pass.
12 KiB
Ticket #000029 — Claim-pack source (axiom/theorem JSON bundles)
Status: closed · landed 2026-05-09
Opened: 2026-05-09
Scope: A new Source subclass that ingests Grok-4-style
companion JSON bundles (axiomsg4-v2.json + theoremsg4-v2.json)
into arborist as first-class documents. Each axiom and theorem
record becomes one ingested Document with rich metadata; intra-bundle
cross-references (axiom ↔ theorem pillars) become derived_from
edges between documents.
Audience: fox + future blackops shifts; anyone landing a
pre-distilled-claim corpus into a shard.
Hard constraint: no new audit ledger. The JSON bundle's
self-validation fields (additivityCheck, noDuplicates,
derivationConsistency, deterministic_seed) ride as metadata
only — audit_events (sha256-chained) remains the single source
of truth for state-changing ops. Two ledgers ⇒ one is fiction.
1. Problem statement
Two artifacts dropped on 2026-05-09:
/home/fox/Downloads/axiomsg4-v2.json (~46 axioms across 7 pillars)
and /home/fox/Downloads/theoremsg4-v2.json (~23 theorems same
shape). They are Grok-4-generated companion bundles, dual-threaded
(delta LaTeX symbolic + nablaVerbose prose expansion) per item,
with cross-references between bundles
(theoremg4.json:pillar.I.logic.excludedMiddle ↔
axiomsg4.json:pillar.I). They sit shaped exactly like CORE-layer
documents per the whitepaper's surface→core model: distilled atomic
claims with explicit source citation (Mendelson 1997, Enderton 2001,
Hilbert 1899, Newton, Kolmogorov, Łukasiewicz 1921, …).
Today arborist has:
arborist/sources/grok.py— Grok account-export source (whole conversations).arborist/sources/wikipedia*.py— bulk dump ingest.arborist/sources/html_page.py— single HTTP page.arborist/distill/*— produces CORE docs from SURFACE docs by TF-IDF or first-sentence summarization.
There is no path that ingests a pre-structured claim bundle where the JSON's atomic units are already the right grain for one Document each. Running the existing distillation pipeline over these JSONs would re-distill an already-distilled corpus — wrong.
A claim-pack source closes that gap with one new file under
arborist/sources/, mirroring the contract every other source
already follows (Source.iter_documents() yields Documents,
ingest.py handles Merkle commitment + dedup + audit-chain extension).
1.1 Why these bundles fit
- Δ / ∇ pairing matches three verifier strategies in
arborist/qa/verify.py:quote(verbatim Δ),paraphrase(∇verbose token-coverage, prose-shaped only),entity(terms fromformal_language). - Cross-references match
Edgesemantics.theoremg4.json: pillar.I.logic.excludedMiddleresolves to a sibling document URI inside the bundle; anEdge(edge_type='derived_from')ties the axiom-pillar provenance row to the theorem document. source_referencestrings are surface-doc anchors. Today they ride as metadata. When Mendelson / Enderton / Hilbert texts are themselves ingested as surface docs (out of scope for this ticket), they become realderivations.proof_blob-bound edges.
1.2 What stays out of the ticket
- Cited-textbook ingestion. Mendelson, Enderton, Hilbert,
Newton's Principia, Kolmogorov 1933 — none of these are
ingested by this ticket. Without them, every claim-pack record
lands at best at
ANCHOR-WARRANTEDper the four-rung ladder (warrant present in citation form; no surface span to verify). That is the honest ceiling and it is not this ticket's job to raise it. - Promoting ∇concise to a separate CORE document with
derived_fromedge to a SURFACE record. Architecturally cleaner (would mirror whatarborist/distill/runner.pydoes); defer until the "are pre-distilled corpora a thing we have multiple of" question is answered. For one corpus, surface-only is the right amount of structure. - Importing the bundle's self-validation fields into
audit_events. See hard constraint above.
2. Design choices
2.1 Document grain — one per record
A. One Document per axiom / theorem (RECOMMENDED). Each record
is already an atomic claim with stable identity (runicLabel +
name + pillar position). Document URI follows
claim-pack://<bundle_id>/pillar/<P>/<axioms|theorems>/<idx>/<slug>.
Idempotent re-ingest works as designed (same content → same root).
B. One Document per pillar. Coarser; chunker would split into per-record chunks. Loses per-record edge granularity (cross-pillar references can't anchor to a chunk).
C. One Document per bundle. Throws away every benefit of the JSON's structure. Rejected.
→ A.
2.2 Document content layout
Each axiom record holds: name, runicLabel, delta,
nablaVerbose, nablaConcise, formal_language, role, status,
source_reference, date_of_introduction, foundational_group,
category, subfield. We need to project this into
Document.content (canonicalized text the chunker sees) plus
Document.extra (metadata sidecar, never enters chunk hashes).
Layout (one record → one Document.content):
<name>
<delta>
<nablaConcise>
<nablaVerbose>
Role: <role>
Source: <source_reference>
Group: <foundational_group> · Category: <category> · Subfield: <subfield>
runicLabel, date_of_introduction, formal_language,
intra-bundle cross-references → Document.extra. Rationale:
- The Δ formula and the ∇verbose prose are what the verifier needs
to find. They go into
contentsochunks_ftsindexes them. runicLabelis a soft pointer per the CTI architecture — runtime mints its own pointer IDs (E1, E2, …). The runic label rides as metadata so a future operator can audit "did anyone collide on ᚴᚵ across two pillars" without it leaking intocache_key.formal_languageis a free-text type signature, useful for per-domain filtering but not for retrieval matching.
2.3 Cross-bundle edges
Both bundles reference each other:
axiomsg4.jsonpillar.I.provenance.references =["theoremg4.json:pillar.I.logic.excludedMiddle", …]theoremsg4.jsonpillar.I.provenance.references =["axiomsg4.json:pillar.I"]
Strategy: emit Edge(edge_type='derived_from', dst_uri=<sibling URI>, dst_root=None). dst_root is None until the sibling is also
ingested into the same shard, at which point arborist's existing
edge-resolution pass fills it in. (If the sibling never lands, the
edge stays as a URI pointer — same lossless behavior as wikilinks.)
Pillar-level references strings translate to:
theoremg4.json:pillar.I.logic.excludedMiddle→claim-pack://theoremsg4-v2/pillar/I/theorems/0/law-of-excluded-middle(where0is the index inside the pillar's theorems list, derived by name match — the JSON's reference grammar is by name slug).
2.4 Lenient JSON parsing
Both files arrive wrapped in markdown json ``` fences and contain
unescaped LaTeX backslashes (\\Theta_1 \\♥ \\Sigma_{48} is fine,
but bare \Theta in some fields breaks json.loads). The parser
strips fences and runs one regex pass to escape lone backslashes
before delegating to json.loads. Do not use eval. A small
dedicated parser keeps the failure mode loud (raise on parse error)
without giving the JSON shell capability over our process.
2.5 Document kind
Default ingest writes kind='surface' (see arborist/ingest.py:340).
Cores currently come only from arborist/distill/* runners and
carry a derived_from edge to a real surface root. Promoting the
claim-pack to kind='core' would create cores with no surface
ancestry — a contract break.
→ kind='surface' for this ticket. The corpus is pre-distilled
content but its arborist-internal status is "ingested as-is, not
distilled by us." If later we ingest Mendelson/Enderton/Hilbert as
surfaces and run a distill pass that produces these JSON records as
its output, those will land as cores. Until then, surface is
honest.
2.6 Source identity + extras
source_type='claim_pack'. Document.extra carries:
{
"bundle": "axiomsg4-v2" | "theoremsg4-v2",
"bundle_id": "<artifact_id from JSON metadata>",
"version": "<JSON metadata.version>",
"pillar": "I" | "II" | … | "IX",
"kind": "axiom" | "theorem",
"name": "Axiom of Implication Introduction",
"runic": "ᚴᚵ",
"category": "Foundations of Logic",
"subfield": "Propositional Logic",
"source_ref": "Introduction to Mathematical Logic by Elliott Mendelson",
"date_intro": "1997 (standardized form, based on earlier systems from 1920s)",
"formal_lang": "First-order logic with propositional variables, …",
}
These fields are query-time metadata, never folded into
document_root, cache_key, or governance_policy_hash.
3. Implementation sketch
3.1 New file: arborist/sources/claim_pack.py
class ClaimPackSource(Source):
source_type = "claim_pack"
def __init__(self, paths: list[Path]):
self.paths = [Path(p) for p in paths]
def iter_documents(self) -> Iterator[Document]:
for p in self.paths:
bundle = _parse_lenient(p.read_text())
yield from _iter_bundle(bundle, bundle_path=p)
# helpers: _parse_lenient (fence + lone-backslash escape), _iter_bundle
# (walks pillars[*].axioms[*] and pillars[*].theorems[*]), _slug, _ref_to_uri.
3.2 New test: tests/test_claim_pack.py
- A small synthetic bundle (one pillar, one axiom, one theorem,
one cross-ref) hand-built as a string with the same shape as the
real JSON (markdown fence + unescaped
\Theta). - Assertions:
- Lenient parser strips the fence and survives unescaped backslash.
iter_documents()yields exactly two Documents.- URIs are stable & deterministic.
- Both Documents carry
source_type='claim_pack'. - Cross-ref produces an
Edge(edge_type='derived_from', dst_uri=…)on the axiom Document pointing at the theorem Document URI. extracarries pillar + name + source_ref.
- No live JSON file from
~/Downloads/is read by the test — fixtures are in-test strings so the test runs anywhere.
3.3 CLI surface
arborist ingest --source claim_pack --path FILE [--path FILE2 …].
One --path repeats; same convention as other sources that take
local files. No remote fetching; bundles ship as user-provided
files only.
3.4 Out-of-scope (follow-up tickets, not this one)
- Bench: how does claim-pack ingest perform on the existing QA-quality sweep when present in a shard alongside Wikipedia surfaces? Likely needs a new bench fixture set ("propositional logic", "Bayes' theorem proof sketch") to measure the lift.
- A
claim_packdistill adapter that takes Mendelson/Enderton/Hilbert surfaces and emits something shaped like these JSONs askind='core'output. That's a real distill pipeline contribution, not a source. - Importing the JSON's
pillars[*].provenance.referencesas intra-shardsupportedges before the full graph has resolved (today edges resolve lazily; see ingest.py edge-fill).
4. Hard constraints (re-stated)
- No new audit ledger.
audit_eventsstays the only chained- sha256 ledger. Bundle's self-validation fields ride as metadata. - No
kind='core'without a surface ancestor. Honors the existing distill contract (arborist/distill/runner.py). - No re-ingestion of the cited textbooks under this ticket. That is a separate, larger effort and a separate ticket.
- No mutation of
cache_keyinvariants. Schema_version, chunking_version, canonicalization_version stay as-is. The claim-pack contributes documents, not policy changes. - Lenient parser must
raise, neverreturn None. A bundle that fails to parse is a loud failure — not silently zero docs.
5. Status
- 2026-05-09 — opened, design above.
- 2026-05-09 — implementation landed alongside the ticket file:
arborist/sources/claim_pack.py(one Document per record; lenient JSON parser handling fence + lone LaTeX backslashes; cross-bundlepillar_referenceedges);arborist/cli.py(--source claim_pack, repeatable--bundle FILE); 15 unit tests intests/test_claim_pack.py. Drive-by fix: removed a function-localfrom arborist.store import connectinside_cmd_ingest's providence branch that was shadowing the module-level binding and breaking every non-providence ingest withUnboundLocalError. Smoke test on the real downloads ingests 78 documents (55 axioms + 23 theorems across 7 pillars) with 14 deduped pillar-reference edges;arborist verifypasses 10/10 sampled Merkle proofs.