docs/tickets: open #000041 — citation-aliases table

Design ticket for the alias mechanism proposed in #000038
§3.2 option (b). When a cited textbook is proprietary or
unavailable, fox decides on a PD substitute (e.g.,
Hilbert-Ackermann 1928 instead of Mendelson 1997), and the
warrant resolver looks up the aliased citation alongside the
original.

Schema (`arborist citation_aliases`) carries
decision_at + decision_by + decision_rationale per row so
the substitution is auditable. Opt-in via `--use-aliases` on
warrant-resolve; alias-resolved chains carry distinct
process_id ("warrant-resolver-v1+alias") so audit can tell
substituted chains from original ones.

Recommend parking until first PD-substitute decision lands
in #000038. Today the only candidate is Hilbert-Ackermann
1928 → Mendelson + Enderton (~13 records), which is
substantial. Goldstein → Newton's Principia for ~5 pillar VI
records is also viable.

Sibling design to #000042 term-aliases (the other vocabulary-
mismatch follow-up surfaced by the Hilbert "incidence" vs
"connection" gap from #000040 §6).

Implementation deferred until trigger fires.
This commit is contained in:
russell@unturf.com 2026-05-09 18:27:10 -04:00
parent b9e5bbdb13
commit 2487b1c05c
No known key found for this signature in database
2 changed files with 216 additions and 1 deletions

View file

@ -61,6 +61,7 @@ Newest first. Update on every open/close.
| ID | Title | Status | Opened | Directive |
|----------|------------------------------------------------|-----------------------|------------|-----------|
| #000041 | Citation-aliases table (PD substitutes for proprietary cites) | open · awaiting first PD-substitute decision from #000038 | 2026-05-09 | — |
| #000040 | Phase 5 resolver fix — phrase + content-token cascade (Hilbert terminology mismatch surfaced) | closed · cascade landed 2026-05-09; lift blocked by 1902-vs-modern vocab; follow-up #000042 | 2026-05-09 | — |
| #000039 | Optional `sqlite-vec` retrieval backend (A/B vs FTS5, hybrid not replacement) | open · awaiting go/no-go (doc-only Phase 0) | 2026-05-09 | — |
| #000038 | Phase 4 content acquisition — proprietary textbook license decisions for warrant coverage | open · awaiting go/no-go | 2026-05-09 | — |
@ -104,4 +105,4 @@ Newest first. Update on every open/close.
## Next ID
`000041`
`000042`

View file

@ -0,0 +1,214 @@
# Ticket #000041 — Citation-aliases table
**Status:** open · awaiting go/no-go (depends on #000038 PD-substitute decisions)
**Opened:** 2026-05-09
**Scope:** Add an `arborist citation_aliases` table that maps
a claim-pack record's original `source_reference` string to a
substitute citation (a different textbook with comparable
content), read at warrant-resolve time. Closes the gap surfaced
in `#000038` §3.2: when a cited textbook is proprietary or
unavailable, fox decides on a PD substitute (e.g.,
Hilbert-Ackermann 1928 instead of Mendelson 1997), and the
warrant resolver looks up the aliased citation when matching
against shard textbooks.
**Audience:** future blackops shifts running warrant-promotion
on records whose original cite isn't ingestable.
**Hard constraint:** **alias decisions are auditable.** Each
alias row carries a `decision_at` timestamp + `decision_by`
identifier (free-string, e.g. `"fox 2026-05-12"`); silent
fabrication of substitute citations is not allowed.
---
## 1. Problem statement
`#000038` Phase 4 surfaces 9 cited textbooks not yet in the
shard cluster. Some are proprietary (Mendelson, Enderton,
Stanley, Brualdi, Knuth, Goldstein) and licensing decisions
take time. When a textbook stays out, the records that cite
it stay at ANCHOR-WARRANTED.
Sometimes a comparable PD work exists. `#000038` §3.1
documents:
- **Mendelson + Enderton** — Hilbert-Ackermann 1928
*Principles of Mathematical Logic* covers the same ground;
PD by age.
- **Goldstein** — Newton's *Principia* (already ingested) is
a substitute for the classical mechanics axiom citations
that point to Goldstein for "Newton's first law".
For these, the warrant resolver could light up if it knew
about the substitution. But silently rewriting the claim-pack
record's `source_reference` field would break audit
traceability. The right answer is an explicit alias table.
## 2. Design choices
### 2.1 Schema
```sql
CREATE TABLE IF NOT EXISTS citation_aliases (
original_ref TEXT NOT NULL, -- exact source_reference string
substitute_ref TEXT NOT NULL, -- replacement citation string
substitute_authors TEXT NOT NULL, -- JSON array
substitute_title TEXT NOT NULL,
decision_at INTEGER NOT NULL, -- unix timestamp
decision_by TEXT NOT NULL, -- e.g., "fox 2026-05-12"
decision_rationale TEXT, -- e.g., "Hilbert-Ackermann 1928 covers same propositional-logic axioms"
PRIMARY KEY (original_ref, substitute_ref)
);
```
Stored at the shards-cluster level (one alias table per
shard or one global at `~/.arborist/aliases.db`?). The
**global** path keeps mesh-syncing simpler — peers can share
the alias table without per-shard duplication.
### 2.2 Resolver wiring
The resolver currently:
1. Reads claim-pack record → parses `source_reference`
2. Walks shards, matches title + author against the cited work
3. Writes derivations row binding record → surface chunk
With aliases:
1. Reads claim-pack record → parses original `source_reference`
2. **Looks up** original `source_reference` in `citation_aliases`
3. If alias found, **also tries** the substitute citation alongside
the original (resolver tries both; either may produce a match)
4. When the alias matches, the derivations row's `process_id`
carries `"warrant-resolver-v1+alias"` — distinguishes
alias-resolved chains in the audit trail
5. Existing chain-of-custody render tail stays the same
### 2.3 Alias scope — per-record or per-citation-string?
**A. Per exact `source_reference` string.** Keys on the literal
string. Brittle to whitespace / punctuation changes; safe to
share.
**B. Per (author last name, normalized title)** tuple. Looser
match; could catch "by Mendelson" and "Elliott Mendelson 1997"
as the same entry. Risk: over-matching unrelated records.
**C. Per (citation_id, alias_id)** opaque IDs. Requires
content-addressed citation IDs (we don't have those today).
**A** for now. Strings ARE the data fox writes the alias
against. Key-collision is acceptable — the resolver tries both
the original AND the alias; if the original happens to match
some other shard, both chains land.
### 2.4 CLI surface
```
arborist alias add ORIGINAL --substitute SUB --by FOX [--rationale RAT]
arborist alias list [--filter X]
arborist alias remove ORIGINAL --substitute SUB
arborist warrant-resolve --shards-dir X --use-aliases [--write]
```
The `--use-aliases` flag opt-in is fail-closed: aliases don't
apply unless the operator explicitly turns them on for a
resolve run. Audit trails distinguish alias-resolved derivations.
## 3. Recommendation
Implement when **at least one PD-substitute decision lands
from `#000038`**. Today the only candidate is Hilbert-Ackermann
1928 → Mendelson + Enderton (~13 records), which is
substantial. The Goldstein → Newton substitution is also
viable for ~5 pillar VI records.
Without those substitutions, the table sits empty and the
implementation has no real test cases. **Park until #000038
surfaces the first substitution decision.**
## 4. Implementation sketch
### 4.1 Schema migration
Add to `arborist/store.py`:
```python
def _ensure_citation_aliases_schema(conn):
conn.execute(SCHEMA_CITATION_ALIASES)
conn.execute(IDX_CITATION_ALIASES_BY_AUTHOR)
```
### 4.2 Resolver integration
In `arborist/qa/warrant_resolver.py:warrant_resolve`:
```python
for record_root, record_root_data in claim_pack_records:
citations = parse_citation(source_ref)
if use_aliases:
for alias_row in fetch_aliases(source_ref):
citations.append(parse_alias_substitute(alias_row))
# … existing resolve loop …
```
### 4.3 CLI subcommands
```python
def _cmd_alias_add(args):
conn = connect(args.aliases_db)
conn.execute(
"INSERT INTO citation_aliases (...) VALUES (...)",
(args.original, args.substitute, ...)
)
```
## 5. Hard constraints (re-stated)
1. **No silent fabrication.** Every alias row has
`decision_at` + `decision_by`. The CLI refuses to add
aliases without those fields populated.
2. **Opt-in by default.** `arborist warrant-resolve` does NOT
use aliases unless `--use-aliases` is passed; aliases are a
curator's tool, not a default behavior.
3. **Audit trail preserves origin.** Derivations rows from
alias-resolved chains carry a distinct `process_id`
(`"warrant-resolver-v1+alias"` proposed) so a future audit
can tell which records are evidence-warranted under their
original citation vs. under a fox-approved substitute.
4. **Same fail-closed honesty as #000031.** When NEITHER the
original nor the alias resolves, the record stays at
ANCHOR-WARRANTED. No half-credit.
## 6. Open questions
- **Does the `process_id` discrimination break the existing
PK on `derivations(core_root, src_root, process_id)`?** No —
alias-resolved chains have a different process_id, which
means an alias chain CAN coexist with an original chain
for the same (core_root, src_root) pair. Probably the
desired behavior; either chain validates.
- **Mesh sync for aliases?** Aliases aren't deterministic
(they're fox decisions); shouldn't auto-propagate across
peers without each peer's curator approval. Defer mesh
semantics; treat the alias table as local-only initially.
- **Per-pillar alias tables?** Sometimes a substitution is
per-pillar (Hilbert-Ackermann → Mendelson works for pillar I
logic but not for any other). The schema's loose; operators
can encode pillar-specific aliases via the
`decision_rationale` field for now. Promote to a schema
field if the pattern hardens.
## 7. Status
Open · awaiting fox's first PD-substitute decision under
`#000038` §3.1. Implementation triggered by that decision;
estimated size when triggered:
- Schema migration: ~30 LOC + 1 migration test.
- Resolver integration: ~50 LOC.
- CLI subcommands (add / list / remove): ~80 LOC + 5 unit tests.
- Documentation update.
Total: ~160 LOC + ~6 tests when triggered.