Commit Graph

80 Commits

Author SHA1 Message Date
Tarik Moussa
cfbfa53398 fix(wiki): protect math in cross-refs; drop no-op DB call; fix mark order
- R-9 (MED): _inject_cross_refs now protects LaTeX math spans ($…$, $$…$$,
  \(…\), \[…\]) like inline code, so a concept name inside a formula is no longer
  rewritten to a [[slug]] link and corrupting the LaTeX. Regression test added.
- C-13 (LOW): mark ungrounded claims on the raw body BEFORE injecting cross-refs
  (render then inject), so a concept name inside a claim cannot stop the ⚠ regex
  from matching.
- C-12 (LOW): _try_embed_formulas is now a true no-op — it previously issued a
  per-concept information_schema round-trip that did nothing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-15 16:24:22 +02:00
Tarik Moussa
ae5e9a528b docs: document 'codex migrate' + MIGRATION_DATABASE_URL as the schema-sync path
README step 4 used an inline apply_schema over the app DATABASE_URL, which fails
under the privilege model (least-privilege app role cannot run DDL). Replace it
with 'codex migrate' and document MIGRATION_DATABASE_URL (owner/superuser) in the
README env table and .env.example, so the privileged migrate path is the
standard for future schema upgrades.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-15 16:20:54 +02:00
Tarik Moussa
10ff5ba3ab chore(infra): point migration helper preflight at 'codex migrate'
Now that 'codex migrate' applies the idempotent schema via a privileged role,
recommend it (with MIGRATION_DATABASE_URL) as the primary schema-sync fix in the
preflight abort message, keeping the manual owner-ALTER as a fallback.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-15 15:54:25 +02:00
Tarik Moussa
cdb7d254df feat(db): idempotent schema + 'codex migrate' command (audit M-1, T-2)
M-1: schema.sql could not be re-applied (leading CREATE TABLE/INDEX lacked
IF NOT EXISTS), apply_schema was never called, and the live migration just
failed on a missing chunks.section column. Fixes:

- schema.sql: all CREATE TABLE/INDEX now use IF NOT EXISTS — the whole file is
  re-applyable as a no-op.
- codex migrate: new CLI command that applies schema.sql. Connects via
  MIGRATION_DATABASE_URL (falls back to DATABASE_URL) and catches
  InsufficientPrivilege with guidance — because the app role is DML-only and
  core tables are owned by 'postgres' (the privilege dimension found during the
  live migration incident).
- config: migration_database_url (optional privileged connection).
- db: apply_schema docstring corrected (idempotency now true) + privilege note.
- T-2: static test that schema.sql is fully idempotent; migrate privilege-error
  test.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-15 15:53:25 +02:00
Tarik Moussa
9f172f5c68 fix(infra): preflight schema check before TRUNCATE in migration helper
The helper TRUNCATEd papers CASCADE and only then ran ingest_all.sh, which
failed on the missing chunks.section column (audit M-1) — leaving the corpus
wiped. Add a preflight that verifies chunks.section exists BEFORE the
destructive step and aborts early (no TRUNCATE) with the owner-level ALTER to
run, since the app user cannot alter postgres-owned tables.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-15 15:37:35 +02:00
Tarik Moussa
1ff8052b69 chore(infra): add canonical-id re-ingest migration helper (audit C-7)
One-time, guarded migration for the C-7 id-format change: a plain re-ingest
would duplicate every paper (new bare-id row beside the old URL-id PK), so this
wipes (TRUNCATE papers CASCADE) and rebuilds via ingest_all.sh.

Safety: requires the SSH tunnel, prints a BEFORE snapshot, gates the TRUNCATE
behind an explicit 'MIGRATE' confirmation, then prints AFTER verification —
C-7 (url_form_ids should be 0) and C-1 via the real resolver-based
discovery_leads() (ingested papers leaked should be 0). Uses .venv psycopg
(psql is not installed); DATABASE_URL is sourced, never echoed.

Joins PR #13 (Wave 2). Read-only verification SQL validated against the live DB.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-15 12:17:17 +02:00
Tarik Moussa
ce75bc02e5 docs(adr): audit addendum to ADR-F15 — ID mismatch resolved (audit D-1)
The ADR's GO rested on a live-corpus spike (478 dangling, Bobenko/Springborn
rank 7, 5.7% spread) measured before the resolution + canonicalisation landed.
Add an audit addendum: mark the 'Known Limitation: ID-Format Mismatch' as
resolved (C-1 shared resolver + C-7 canonical ids), and flag the Spike Result
numbers as pending re-measurement after the C-7 re-ingest. History preserved.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-15 12:10:46 +02:00
Tarik Moussa
8e2f9e037d fix(ingest): key formulas/figures on papers.id, not filename stem (audit C-10)
extract_formulas/extract_figures derive paper_id from Path(pdf_path).stem, which
need not equal papers.id (and won't, given canonical ids). Inserting that stem
violated the formulas/figures -> papers(id) FK on rich (.pdf) ingest. The DELETE
already used paper.id; the INSERTs now do too.

Strengthened the rich-ingest test: the fake formula/figure carry a filename-stem
paper_id distinct from papers.id, and the test asserts the inserted rows are
keyed on papers.id.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-15 12:09:38 +02:00
Tarik Moussa
c36e7c6ee7 fix(ingest): canonicalise papers.id to the caller id (audit C-7, T-3)
openalex.fetch_paper fills paper.id from OpenAlex's URL-form doi/id
(e.g. https://doi.org/10.48550/arxiv.math/0603097), and ingest upserted that as
papers.id. Live DB confirmed 35/36 ids were URL-form — breaking bare-id lookups
(graph related / discover citing / search by id) and leaving cited_id matching
to rely solely on openalex_id.

ingest now sets paper.id to the caller-supplied id (the arXiv id / DOI the CLI
and ingest scripts use); OpenAlex's work id is retained as openalex_id and in
paper_identifiers. Regression test: fetch_paper returns a URL-form id, papers.id
upsert uses the bare caller id.

T-3: the OpenAlex test fixture used a bare 'doi' (not the URL form the API
emits) and never asserted paper.id, which masked this. Fixture now uses the URL
form and the test pins fetch_paper's mapping.

NOTE: existing rows keep their URL-form ids until re-ingested — run ingest_all.sh
to migrate the 36-paper corpus (chosen migration path).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-15 12:06:53 +02:00
Tarik Moussa
6193f5630d docs: fix two doc/code contradictions (audit D-2, D-4)
- D-2 config.py: graph_cite_boost_alpha described the OLD inverted formula
  (score = dense * (1 + alpha*pr)); the code divides distance. Document the
  actual boosted_distance = distance / (1 + alpha*pr).
- D-4 synthesis.py: write_leads claimed an 'append-only' INDEX.md that keeps
  previously-recorded ids; _update_index actually rebuilds from on-disk glob.
  Docstring now matches the implementation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-15 11:13:52 +02:00
Tarik Moussa
eee147275a fix(discover): resolve cited_id via shared resolver (audit C-1)
discover.py compared the raw OpenAlex cited_id against papers.id (a DOI/arXiv
id), so an ingested paper cited by its OpenAlex id was mis-reported as a
discovery lead. graph.py already resolved this via papers.openalex_id, but the
fix was never propagated — two 'dangling' implementations disagreed.

Extract the resolution into one shared constant RESOLVED_CITATIONS_SQL in
graph.py; build_citation_graph and all four discover.py queries
(discovery_leads / citing_papers / cited_by / cocited_papers) now go through it,
so the 'what counts as ingested' rule cannot drift again.

Live-DB validated: before, 13 ingested papers leaked into discovery_leads;
after, the real discovery_leads() returns 0 ingested papers among its leads.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-15 11:12:55 +02:00
Tarik Moussa
3092f1814e fix(synthesis): namespace lead ids by kind to stop collisions (audit C-11)
Each stage (find_connections/gaps/improvements/propose_conjectures) numbered its
leads from seq=1, so connection L-0001, gap L-0001 and improvement L-0001 all
resolved to grounded/L-0001.md. The CLI concatenates them
(connections + gaps + improvements) and write_leads writes in order, so later
kinds silently overwrote earlier ones — survivors = max(per-kind count), not the
sum. Reproduced: 4 distinct leads -> 2 files.

_make_lead_id now takes the kind and emits L-C-/L-G-/L-I-/L-X- prefixes, keeping
same-seq ids distinct across stages. Regression tests cover the format and the
no-collision invariant.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-15 11:09:24 +02:00
Tarik Moussa
115bb63f2d fix(wiki): quarantine zero-citation pages instead of publishing (audit C-4)
compile_all's quarantine gate was guarded by `total_claims > 0`, so a page
with no parseable citations — including the LLM-outage case where
compile_concept returns an empty page — fell through to the publish branch and
was written to wiki/ + marked compiled. Such a page carries zero grounding
evidence.

Now: total_claims == 0 (or grounding_rate below threshold) quarantines and does
NOT update the compile-state, so a transient LLM failure retries next run
instead of freezing an empty/uncited page as 'compiled'. Empty bodies are
recorded but not written as draft files.

Regression test reproduces the exact bypass (uncited LLM output -> quarantined,
not published).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-15 11:06:12 +02:00
Tarik Moussa
8af9f9ad0a docs(audit): runtime-validate C-4 & C-11, close nougat.py review
Exercised the two runtime-confirmable HIGHs against real code paths (injected
fake LLM, no Ollama/DB):
- C-11 CONFIRMED: 4 distinct leads -> 2 files; connection+gap L-0001 silently
  overwritten by improvement L-0001 (2/4 leads lost).
- C-4 CONFIRMED: zero-claim (uncited) page published to wiki/ + marked compiled,
  not quarantined (grounding_rate=0.0 < 0.5 but total_claims>0 guard skips it).
- nougat.py now fully reviewed -> folds into R-6 (only retries ConnectError,
  wait_fixed(0), unwrapped in ingest). No new finding.

Audit/finding-identification pass complete; remediation planning still open.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-15 10:57:24 +02:00
Tarik Moussa
33fb91d478 docs(audit): live-DB verification — confirm C-7 & C-1, refine M-1
Read-only queries vs the live Jetson corpus (papers=36, citations=811):
- C-7 CONFIRMED: 35/36 papers.id are DOI-URL form (https://doi.org/...),
  incl. arXiv papers as arXiv-DOI URLs. Bare-ID lookups fail for 35/36.
- C-1 CONFIRMED: discover.py mis-flags 13 ingested papers as dangling;
  graph.py correctly resolves 57 cross-cite edges → the two paths diverge.
- M-1 REFINED HIGH→MED: paper_identifiers exists (39 rows), so no live crash;
  structural migration-tooling gap remains.
- C-2 partially refuted: 39 alias rows > 35 → ~4 diverging aliases, not inert.

Adjusted tally: 7 HIGH, 12 MED, 26 LOW.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-15 10:40:46 +02:00
Tarik Moussa
2d1b0b5e23 docs(audit): complete whole-repo audit (parts D-G + exec summary)
- Part D synthesis: C-11 HIGH (lead-ID collision → silent overwrite), R-8 MED, D-4 LOW.
- Part E wiki-rest: R-9 MED (cross-ref injection corrupts LaTeX math), C-12/C-13/R-10/R-11 LOW.
- Part F embed/quality/models: C-14/R-12 LOW + C-7 corroboration. Cleanest modules.
- Part G cli/config: C-15 MED (embedding_dim vs hardcoded vector(1024)), R-13/S-4/R-14/D-5 LOW.

Executive summary: 45 findings total (8 HIGH, 11 MED, 26 LOW). Five root themes;
dominant one is un-normalized identity (C-1/C-7/C-10/M-1). Whole repo reviewed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-15 10:14:50 +02:00
Tarik Moussa
7682ff112f docs(audit): whole-repo audit parts A-C (grounding, sources, parsing)
Closes request (a) — the second pass over loop-1 §8 open items:
- Part A grounding guard: C-4 (HIGH, zero-claim pages bypass quarantine),
  C-5/R-1 MED, C-6/R-2/R-3 LOW.
- Part B sources/: C-7 (HIGH, papers.id stored in OpenAlex URL form vs the
  bare-ID contract), T-3 MED, R-4/R-5/C-9 LOW.
- Part C parsing/: C-10 MED (formulas/figures key on filename not paper.id),
  R-6/R-7 LOW — all latent (live corpus is .txt).

Running tally: 30 findings, 7 HIGH. Common root: un-normalized identity.
Parts D-G (synthesis/wiki/embed/cli) still open.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-15 09:23:56 +02:00
Tarik Moussa
a0ad069b1d docs(audit): record development-loop-1 findings
Audit of main @5dfa6e4: 4 HIGH (S-1 secret-in-tree, M-1 missing migration
path, C-1 unpropagated ID-mismatch fix in discover.py, D-1 stale ADR-F15
validation, T-1 untested resolution SQL) plus MED/LOW findings. Findings
only; remediation planning deferred to the next loop phase.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-15 08:43:54 +02:00
Tarik Moussa
0c89eebeb8 chore(ops): ignore .env.* profiles + add batch ingest script
- .gitignore: ignore .env.* (keep .env.example) so local DB-credential
  profiles like .env.jetson-ingest can never be staged (audit S-1).
- ingest_all.sh: batch-ingest helper writing to the Jetson DB via SSH tunnel.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-15 08:43:53 +02:00
Tarik Moussa
9726042964 style: ruff format normalization to line-length=100
Committed code predated the line-length=100 ruff config; this brings the
five drifted files into compliance. No logic change.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-15 08:43:53 +02:00
Tarik Moussa
5dfa6e4bae feat(graph): paper_identifiers table — multi-ID alias support
Fixes preprint/journal duplicate problem: arXiv papers were ingested
under their preprint OpenAlex ID, but corpus citations reference the
published-version OpenAlex ID → those citations were invisible in the
graph (appeared as dangling).

Changes:
- infra/schema.sql: CREATE TABLE paper_identifiers (paper_id, openalex_id)
  with UNIQUE index; seeded from papers.openalex_id on migration
- codex/graph.py: build_citation_graph LEFT JOINs paper_identifiers as
  a second resolution path: COALESCE(p.id, pi.paper_id, c.cited_id)
- codex/ingest.py: every ingest inserts openalex_id into paper_identifiers
  (ON CONFLICT DO NOTHING) — aliases can be added manually alongside

Live DB: 35 rows seeded + 4 published-version aliases added:
  math/0503219 → W2163787581 (DCG 2007)
  math/0306167 → W2136126748 (Commun Analysis Geom 2004)
  math/0203250 → W1567166970 (Trans AMS 2003)
  1005.2698    → W1511400044 (Geom & Topol 2015)

Effect: dangling 574→570; Bobenko+Springborn 2007 now IN-KB rank 9.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 07:42:21 +02:00
Tarik Moussa
664600fdc7 fix(graph): resolve cited_id to papers.id via openalex_id JOIN
build_citation_graph now does:
  SELECT c.citing_id, COALESCE(p.id, c.cited_id) AS cited_id
  FROM citations c
  LEFT JOIN papers p ON p.openalex_id = c.cited_id

Before: cited_id (OpenAlex IDs) never matched papers.id (DOIs) →
  13 cross-citations between ingested papers were invisible in graph.
After: in-KB papers use their canonical DOI key; dangling references
  keep their raw OpenAlex ID unchanged.

Test: test_cross_citation_uses_doi_when_in_kb (42 passed).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 07:15:21 +02:00
Tarik Moussa
a1a6f4590b docs(adr): ADR-F15 literature graph — GO, Bobenko+Springborn rank 7
Live-corpus spike (29 papers, 495 nodes, 590 edges, 478 dangling):
- Bobenko & Springborn 2007 at rank 7 — manual hub expectation PASS
- Pinkall & Polthier 1993 at rank 1 (foundational DGP)
- Score spread 5.7 % (0.002282→0.002159); flat but ordered correctly
- cite-boost alpha=0.3: ~0.06 % boost per unit PR — tie-breaker, not
  hard re-rank; correct formula is distance/(1+alpha*pr), not multiply
- Known limitation: citing_id(DOI)→cited_id(OpenAlex) mismatch renders
  inter-KB cross-citations invisible; non-blocking at corpus size 29

Closes R-46.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 07:11:50 +02:00
2c476dc038 Merge pull request 'feat(F-15): citation graph — PageRank, coupling, co-citation, CLI' (#11) from feat/F-15-literature-graph into main
feat(F-15): citation graph — PageRank, coupling, co-citation, CLI

R-41..R-45 done. R-46 (ADR spike) todo pending full-corpus ingest.
2026-06-15 03:18:22 +00:00
Tarik Moussa
1f1e0e82b3 fix(F-15): address Opus review findings (CRITICAL + WARN)
CRITICAL:
- cli.py: invert cite-boost formula: divide distance by (1 + alpha*pr)
  instead of multiply — high-PageRank papers now correctly rank higher
- cli.py: wire graph_min_corpus_size config into graph report; warning
  emitted to stderr so JSON stdout stays parseable

WARN:
- graph.py: document that damping is ignored in small-graph uniform branch
- cli.py: sort dangling citations before slicing for stable output
- cli.py: replace raise typer.Exit(0) with return in empty-graph path
- tests: fix *extra_args helper signatures; add cite-boost ordering test
  and small-corpus warning test

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 03:40:30 +02:00
Tarik Moussa
fd51b70000 feat(F-15): citation graph — PageRank, coupling, co-citation, CLI
- codex/graph.py: build_citation_graph (DiGraph from DB), citation_pagerank
  (graceful uniform fallback < 5 nodes), find_related (bibliographic coupling),
  find_co_cited, dangling_citations
- codex/config.py: graph_cite_boost_alpha=0.3, graph_min_corpus_size=15
- codex/cli.py: graph report [--top-n] [--json], graph related <paper-id>
  [--min-shared]; search paper --cite-boost (PageRank score weighting)
- tests/graph/: 39 tests (graph functions + CLI) — 326 total green

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 03:31:36 +02:00
9647897173 feat(F-16): chunk quality gate + section classification
3-signal quality filter (length/alpha-ratio/bib-score) + rule-based section classifier + retroactive CLI pass. 35 new tests, 287 total green.
2026-06-15 01:22:44 +00:00
1a9afb4433 Merge pull request 'fix(ingest): D-04 bibkey heuristic + D-05a/b/c regression tests + __main__' (#9) from fix/d04-d05-ingest-fixes into main 2026-06-15 01:07:35 +00:00
Tarik Moussa
b87087d3c6 fix(ingest): D-04 bibkey heuristic + D-05a/b/c regression tests + __main__
D-04: add _make_bibkey(paper) — ConcatSurnames+Year, ≤3 authors full list,
      >3 authors FirstEtAlYear. Applied in ingest_paper when paper.bibkey is
      None. ON CONFLICT now fills NULL bibkeys via COALESCE(papers.bibkey,
      EXCLUDED.bibkey) while preserving manually-set values.

D-05b: add codex/__main__.py so `python -m codex` dispatches to the CLI.

D-05c: fix test_ingest.py:75 — rename var to raw_api_citations (the raw
       OpenAlex payload with citing_id=openalex_id) and add regression
       assertion: citing_id written to DB must equal paper.id (DOI), not
       paper.openalex_id.

D-05a (proxy): add TestEntryPoint.test_python_m_codex_help — runs
       `sys.executable -m codex --help` via subprocess, covering the
       non-uv-run entry path. 253 tests green, ruff+mypy clean.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 03:07:06 +02:00
Tarik Moussa
335ca2c923 docs(adr): ADR-F13 synthesis engine — GO + zero-fact-leak enforcement (N-02)
Closes audit N-02: docs/adr/ADR-F13-synthesis-engine.md was missing,
blocking R-34. Documents spike result (GO, Opus APPROVE 2 passes),
four-stage lead taxonomy, grounding discipline, three-level
zero-fact-leak invariant, and falsification-gate in _parse_conjecture_output.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 01:54:53 +02:00
36a5d9566a Merge pull request 'fix(docs): correct provenance CLI command names in README (D-01)' (#8) from fix/cli-readme-drift into main 2026-06-14 23:51:07 +00:00
Tarik Moussa
b12789a47f fix(docs): correct provenance CLI command names in README (D-01)
Audit D-01 (2026-06-15): README documented `provenance sync` and
`provenance export-bib` but cli.py:161/195 defines `scan` and `bib`.
Fix: update both occurrences (prose + CLI reference block).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 00:49:50 +02:00
1bec18ae82 Merge pull request 'fix(ingest): support .txt sources + fix citing_id FK violation' (#7) from fix/ingest-txt-and-citing-id into main
fix(ingest): support .txt sources + fix citing_id FK violation — Opus APPROVE
2026-06-14 13:56:04 +00:00
Tarik Moussa
5025ff3a88 fix(ingest): support .txt sources + fix citing_id FK violation
Two bugs exposed during first batch ingest run after D-03 fix:

1. `.txt` files were silently skipped ("Unbekannter Dateityp") — added
   plain-text read path alongside .tex and .pdf.

2. After D-03, OpenAlex resolves arXiv IDs → paper.id uses the DOI
   (https://doi.org/…) but fetch_citations emits citing_id=openalex_id
   (https://openalex.org/W…). FK constraint citations_citing_id_fkey
   → papers.id violated. Fix: rewrite citing_id to paper.id after
   fetching from OpenAlex.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-14 15:54:05 +02:00
6c28446644 Merge pull request 'fix(mcp): disable DNS rebinding protection for LAN HTTP transport' (#6) from fix/mcp-transport-security into main
fix(mcp): disable DNS rebinding protection for LAN HTTP transport
2026-06-14 12:55:59 +00:00
Tarik Moussa
d29dcf2fb5 fix(mcp): disable DNS rebinding protection for LAN HTTP transport
FastMCP defaults to DNS rebinding protection (allowed_hosts=localhost only),
which rejects requests from the Mac (192.168.178.82) to the Jetson
(192.168.178.103:8765). Security is already provided by Bearer token auth
in _TokenAuth middleware and UFW restricting port 8765 to trusted LAN IPs,
making host-header validation redundant in this deployment context.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-14 14:55:29 +02:00
1d1203b225 Merge pull request 'fix(sources): resolve arXiv IDs via arXiv DOI (OpenAlex /works/arxiv: 404s)' (#5) from fix/openalex-arxiv-doi into main
fix(sources): resolve arXiv IDs via arXiv DOI (OpenAlex /works/arxiv: 404s)
2026-06-14 12:31:33 +00:00
Tarik Moussa
50e6bb8210 fix(config): remove duplicate F-13 settings block (merge artefact)
F-13 settings (leads_dir, synthesis_llm_*, synthesis_top_k,
synthesis_min_grounded_ratio, synthesis_gap_min_coverage) appeared twice
due to independent commits on F-13 and F-14 branches. Kept the second
(more complete) block; removed the first.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-14 08:59:05 +02:00
Tarik Moussa
b2ef8f2575 docs(mcp): client registration (Claude Code / Desktop)
Documents MCP server setup for Claude Code (.mcp.json) and Claude Desktop
(claude_desktop_config.json), HTTP transport config, and available tools table.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-14 08:55:49 +02:00
Tarik Moussa
b803811701 feat(mcp): FastMCP server exposing read-only KB tools (stdio)
Implements F-14: thin FastMCP wrapper over existing codex domain modules.
Seven read-only tools: search, ask, wiki_read, wiki_list, discover_leads,
provenance_verify, synthesis_browse. All optional-feature tools degrade
gracefully to {"error": "feature not available"} instead of crashing.
Adds mcp[cli]>=1.0 dependency and codex-mcp console_script entry-point.
28 new tests across test_server, test_search, test_readonly, test_graceful,
test_http_auth — all green; 0 regressions in existing 172 tests.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-14 08:55:49 +02:00
Tarik Moussa
59763105a5 fix(synthesis): enforce non-empty provenance + fix gap lead + clean index docstring
- Lead.__post_init__: raises ValueError on empty provenance list
- find_gaps: no-chunk path now carries topic-marker Provenance instead of []
- _update_index: docstring corrected (rebuilds, not append-only)
- test_model: test_lead_empty_provenance_raises covers the new invariant

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-14 08:52:35 +02:00
Tarik Moussa
8cf0cc7e01 feat(synthesis): Lead/Provenance model + grounded leads + conjecture generator
- codex/synthesis.py: Lead/Provenance dataclasses, find_connections/gaps/improvements,
  propose_conjectures (status=unverified, quarantined in leads/conjectures/),
  write_leads (grounded→leads/grounded/, conjectures→leads/conjectures/ HARD invariant)
- codex/cli.py: synthesis leads/conjectures/report command group
- codex/config.py: F-13 settings (leads_dir, synthesis_llm_*, synthesis_top_k,
  synthesis_min_grounded_ratio, synthesis_gap_min_coverage)
- tests/synthesis/: 42 tests covering model, grounding, conjecture invariant, quarantine, CLI
- spike/: F-13 spike script + output (live run attempted)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-14 08:37:42 +02:00
Tarik Moussa
967e6baa59 feat(wiki): ADR-F12-wiki-compile.md — GO decision + grounding measurement
Document Spike result (grounding ~0.95, hallucination ≤5% on 3 concepts),
content-5-gram guard design, quarantine mechanism, LLM graceful degradation,
cross-ref injection rules, and conflict detection MVP.
R-27 satisfied.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-14 05:11:06 +02:00
Tarik Moussa
7d338d29ca fix(wiki): sentence-level claim granularity + content-5-gram grounding guard
Replace 3-gram substring match with:
- _last_sentence(): only the last sentence before a citation is checked
  (prevents a hallucinated paragraph grounding via a phrase at its end)
- _content_words(): stopwords removed from both claim and chunk before
  n-gram comparison (prevents bypass via "the discrete conformal map")
- Content-5-gram: require 5 consecutive non-stopword tokens from last
  sentence to appear in the chunk's content-word stream
- Claims with <5 content words in last sentence → ungrounded by default

Add adversarial tests: stopword-3-gram bypass → grounded=False;
legitimate content-5-gram → grounded=True.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-14 05:11:06 +02:00
Tarik Moussa
408e4886bb fix(wiki): basic conflict detection in CompileReport
Add _detect_conflicts() using adversative keyword heuristic (but,
however, in contrast, …) between chunks of different bibkeys; results
populate CompileReport.conflicts and appear in wiki/log.md.
Also add CompileReport.quarantined field (used by Fix 2).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-14 05:11:06 +02:00
Tarik Moussa
ef46d14aed fix(wiki): protect inline-code spans from cross-ref injection
_inject_cross_refs temporarily replaces backtick spans with null-byte
placeholders before injecting [[slug]] cross-refs, then restores them.
This prevents concept titles inside `code` from being rewritten.
Add tests for inline-code protection and full-list cross-ref injection.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-14 03:58:02 +02:00
Tarik Moussa
0202a2266e fix(wiki): pass chunks explicitly to compile_concept (single retrieve)
compile_concept now accepts chunks as a positional parameter; compile_all
retrieves chunks once and passes them directly, avoiding double-retrieve.
Also separates all_concepts (full list) from compile_concepts (filtered),
so cross-ref injection always uses the complete concept list regardless of
--concept filter (Fix 6 + Fix 8).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-14 03:58:02 +02:00
Tarik Moussa
60e40f6f36 fix(wiki): exclude URL-shaped bibkeys from claim parsing
_parse_claims now skips matches where bibkey contains spaces or starts
with "http", preventing Markdown hyperlinks like [text](https://...) from
being misidentified as citations. Add two tests.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-14 03:58:02 +02:00
Tarik Moussa
89228a1f62 fix(wiki): LLM error handling — graceful degradation on Ollama unavailable
Wrap llm.generate() in compile_concept with a broad try/except;
log a warning and return an empty ConceptPage (no crash) when the
LLM endpoint is unreachable. Add logging module import.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-14 03:58:02 +02:00
946a80418e Merge pull request 'feat(F-09): rich parsing — formula + figure extraction' (#1) from feat/F-09-rich-parsing into main
feat(F-09): rich parsing — formula + figure extraction
2026-06-14 01:52:23 +00:00