Project 01 · Technical & scholarly documentation

Palimnote

A complete technical and scholarly reference for Palimnote — a TypeScript monorepo that turns an uploaded scholarly text into an annotated critical edition — and for the in-progress MLA research manuscript that analyzes its actually-built mechanisms against the trust-calibration, cognitive-load, extended-mind, and metaphor-in-theory literatures.

Reading this reference

About This Documentation

A single reference covering two connected bodies of work: the Palimnote software (Part I) and the research manuscript that analyzes it (Part II).

This report is a comprehensive technical and scholarly reference covering two connected bodies of work located on this machine:

  1. The ProjectAutoCriticalEditionProject (working repository name interactive-critical-edition, shipped product name Palimnote), a production TypeScript monorepo implementing a web application that turns an uploaded scholarly text into an annotated “critical edition”: a resolved and classified citation apparatus, a personalized reading roadmap, a 3D knowledge graph, and a retrieval-grounded (“Ask Library”) Socratic chat interface — all governed by an explicit anti-hallucination, provenance-first design discipline.

  2. The Research PaperPalimnote_Research, an in-progress MLA-style academic manuscript (LaTeX/XeLaTeX + biblatex-mla) that analyzes Palimnote’s actually-built mechanisms as a design-level instantiation of constructs drawn from the trust-calibration, cognitive-load, extended-mind, and metaphor-in-theory-construction literatures — together with the elaborate, governed multi-agent research process used to produce that manuscript.

Part I documents the software project: its architecture, every package and application, and — package by package, file by file — the purpose, signature, algorithm, edge cases, and wiring of its significant functions, components, and database tables. It also records the project’s known limitations, testing posture, and development history, all sourced from direct reading of the source code and the project’s own extensive internal documentation (CLAUDE.md / docs/PROJECT-LOG.md, docs/architecture/plan.md, docs/project-status.json).

Part II documents the research paper: its central argument and four (plus one modest fifth) claimed contributions, a section-by-section walkthrough of what each part of the manuscript argues and why, and a full account of the governed research production process — the phase/gate discipline, the source-intake and citability pipeline, the citation/quote verification ledger, and the two-agent development architecture — that produced it.

A note on scope and method. Given the size of the underlying codebase (over 40,000 lines of TypeScript across two applications and seventeen shared packages) and the length of the underlying manuscript’s own development record, this document aims for genuine completeness at the level of every module, every package, and every significant exported function or component — not a curated sample. Where a package or file contains dozens of small helper functions, each is named and explained; where a design choice was deliberate and documented in the project’s own source comments or commit history, that rationale is preserved here rather than only described from the outside. All claims about the software are grounded in direct reading of the source code as it exists in this repository as of July 23, 2026; all claims about the manuscript are grounded in direct reading of the manuscript’s own .tex source and the research project’s governance records as of the same date.

Part I · Software — 01 / 12

Product Overview & Purpose

What Palimnote is, the reading it turns an uploaded text into, and the anti-hallucination discipline that governs every AI-generated claim it produces.

Palimnote (internal repository and package name interactive-critical-edition) is a web application that takes an uploaded scholarly text — a PDF, EPUB, plain-text, or Markdown file — and produces what its own documentation calls an “interactive critical edition”: a processed, annotated reader; a resolved and classified citation apparatus; a personalized, priority-ranked reading roadmap; a knowledge/research graph; a Library of works and sources; a retrieval-grounded (“Ask Library”) chat; and a citation-aware Writer mode for producing new scholarly writing grounded in the reader’s own library.

The product’s animating idea, stated in the project’s own planning documents, is that an expert reader approaches a difficult text with an accumulated relational map of prerequisites, context, and connections that a novice reader lacks, and that this map can be partially externalized by software: automatically discovering a text’s explicit citations and implicit intellectual context, classifying each into one of ten relationship categories, and using that classified graph to compute a dependency-ordered reading plan personalized to what the reader has already read and self-rated as understood.

The system is built around one non-negotiable design discipline, repeated throughout its own internal documentation and enforced at multiple layers of the code: every AI-generated claim carries a confidence score and a record of its own provenance, and no bibliographic fact (a title, author, year, or DOI) is ever invented by a language model — it is accepted only from a real external lookup against Crossref, OpenAlex, Open Library, or Google Books. An unresolved citation stays honestly unresolved rather than being filled in with a plausible guess. Where no AI provider API key is configured at all, the system does not block or silently disable the pipeline; it falls back to a deterministic, rule-based heuristic classifier, and every output of that fallback is labeled heuristic end-to-end, all the way into the reader’s own UI, so a stub verdict is never mistaken for a real model judgment.

The product is explicitly framed, in its own privacy policy and landing copy, as “a research aid, not a substitute for the primary sources” — every generated claim is meant to be checkable, disputable, and reversible by the reader, not treated as settled scholarship.

Part I · Software — 02 / 12

Architecture, Stack & Development History

The three-deployable-unit monorepo, the ten-category relationship vocabulary threading through the whole system, and the phase-by-phase build history from early scaffolding to the current hardening program.

Palimnote is a TypeScript monorepo managed with pnpm workspaces (pnpm-workspace.yaml: apps/*, packages/*), targeting Node 24, with a single pinned package-manager version. It consists of three deployable units sharing one Postgres database as the single system of record — no Redis, no separate vector database, no dedicated graph database:

  • apps/web — a Next.js (App Router) application deployed on Vercel. It serves the public landing page, the authenticated app shell, the Reader, the Roadmap, the Curriculum view, the Library, the 3D Visualization (knowledge graph), Writer mode, the Ask Library chat, and an admin dashboard, and it hosts all CRUD API routes under apps/web/src/app/api/*.
  • apps/worker — a Node.js background service deployed on Render, consuming jobs from a pg-boss queue (itself backed by tables inside the same Postgres database, not a separate service). The worker owns everything AI- or network-latency-bound: document extraction and OCR, citation resolution, AI relationship classification, cross-work research discovery, credibility assessment, and cross-library graph expansion.
  • packages/db — the Drizzle ORM schema (over 60 tables across more than 30 additive migrations), the pg-boss queue helpers, and the shared database client, imported by both apps/web and apps/worker.

External providers — OpenAI, Anthropic, Crossref, OpenAlex, Open Library, Google Books, a self-hosted/Cloud-Run GROBID structural-extraction service, Tavily web search, and several social/video discovery adapters — are called only from the worker, never from the browser or from apps/web’s own server code in a way that would expose a key client-side.

2.1 The Monorepo’s Packages

Beyond packages/db, the monorepo has grown, phase by phase, to include the following shared packages, each documented in full later in this report:

Package Responsibility
packages/ai-adapters A provider-agnostic LLMProvider/EmbeddingProvider interface; fetch-only OpenAI and Anthropic implementations (no vendor SDK); cost-first cheapest-tier-first task routing; the deterministic, no-network heuristic relationship classifier used whenever no AI key is configured.
packages/bibliographic Resolution of a citation’s bibliographic metadata against Crossref, OpenAlex, Open Library, and Google Books, with a shared title-overlap confidence guard and form-aware (book vs. journal) provider ordering.
packages/ingestion PDF (via unpdf/pdf.js), EPUB, and plain-text/Markdown parsing; a GROBID TEI-XML structural-extraction adapter with extensive contamination-guard logic; OCR fallback via Tesseract; deterministic regex-based citation, footnote, and apparatus extraction; upload validation; and shared Supabase Storage access.
packages/research The broader, evidence-first discovery layer (scholarly/web/social adapters, a deterministic relevance gate, two-axis credibility scoring, canonical work/creator identity resolution, and cost-budget primitives), extending far beyond the narrower bibliographic-resolution package above.
packages/roadmap A pure, dependency-free ranking function (rankRoadmap) that turns candidate references, a user’s knowledge profile, and manual overrides into a priority-tiered, sequenced reading plan — unit-tested against the plan’s own worked Heidegger and Vico acceptance cases.
packages/curriculum A deterministic, non-AI mapping from the ten relationship categories to a five-stage pedagogical curriculum, plus deterministic, template-based self-check questions.
packages/consistency Nine deterministic cross-surface data-consistency checks (citation↔︎Library, graph-edge endpoints, RAG-citation ownership, title/author/year agreement, and more) with a transactional, idempotent repair applier.
packages/rag Text chunking, deterministic lexical retrieval and ranking, the Socratic prompt/validation logic for the “Ask Library” chat, and the separate Conversational Competency Designation feature.
packages/deletion A pure state machine governing safe, ordered, crash-resumable permanent deletion of a work (Storage bytes before database rows).
packages/phase-lifecycle Not a product feature at all, but meta-tooling governing the AI-agent development process itself — how a coding agent reads project-memory records and safely closes out a development phase.
packages/config Shared configuration: the ANALYSIS_PIPELINE version parser, per-version processing-stage sequences and labels, and every phase’s independently addressable release-flag helpers.
packages/observability A single, dependency-light error-and-event reporting seam shared by both apps, with an optional Sentry adapter.
2.2 The Ten Relationship Categories

A cornerstone of the whole design is a closed, fixed vocabulary of ten relationship categories that every classified citation, annotation, and graph edge is expressed in terms of: explicit_reference, secondary_scholarly_recommendation, historical_context, prerequisite, conceptual_influence, disagreement_polemical_target, interpretive_aid, parallel_comparison, optional_extension, and ai_inferred. This exact list appears, verbatim and in the same order, in the Postgres relationship_category enum, in the packages/ai-adapters TypeScript types, in packages/roadmap’s tier-mapping table, and in packages/curriculum’s stage-mapping table — the single vocabulary threading through the whole system, from extraction through classification through ranking through pedagogical sequencing.

2.3 A Deliberate Deviation from the Original Plan: No Persisted Roadmap

The project’s original design document specified persisted reading_roadmaps and roadmap_items tables — a stored snapshot of each user’s computed roadmap. The shipped schema deliberately does not have these tables. Instead, the roadmap is computed fresh on every request from the underlying citation graph (graph_edges) plus the durable, user-authored inputs that actually need to persist: reading_records, understanding_ratings, and roadmap_overrides. The documented rationale is that a stored snapshot would drift the instant a re-analysis runs or a user changes a rating, whereas computing on demand makes “recalculation respects overrides” free rather than requiring separate reconciliation logic. The same on-demand philosophy is later extended to the Curriculum view, the Roadmap-annotated 3D graph projection, and the Library’s cross-run “resource role” view.

2.4 The AI-Adapter Design Philosophy

No AI vendor SDK is imported anywhere in the codebase. Both packages/ai-adapters’ OpenAI and Anthropic providers, and packages/bibliographic’s four external bibliographic sources, are implemented over plain fetch calls against each provider’s HTTP API. This was a deliberate choice recorded in the project’s own design-decision log: no SDK dependency to install or build, and less code and less supply-chain surface than pulling in two vendor SDKs — business logic anywhere in the codebase only ever depends on the small LLMProvider/BibliographicSource interfaces, never on a concrete vendor import.

Every AI task defaults to the cheapest viable model tier (gpt-5.4-nano / claude-haiku-4-5), with a pricier tier reserved only for specific, deliberately narrower uses (a research-synthesis route, a competency-designation route) and never silently promoted — a promotion is a documented, explicit configuration change, gated on eval-harness evidence. Every AI call, including a $0-cost heuristic fallback, is logged to an ai_usage_log table, which is what powers the admin cost dashboard.

Project history and development phases

The project’s own internal memory file (docs/PROJECT-LOG.md, mirrored at the repository root as CLAUDE.md) records a long, incrementally delivered phase history, each phase tagged in git and cross-checked against a generated, machine-verified docs/project-status.json tracker. In brief:

  • Phase 0–2 (2026, early): research and planning; local foundation (monorepo scaffold, Postgres + pgvector, Drizzle schema, Auth.js v5 credentials-based authentication with JWT sessions and a sessionVersion revocation counter); Supabase + Vercel deployment; upload and Library with a dedicated packages/ingestion and apps/worker.
  • Phase 3–7: the annotated Reader (quote-anchored highlights, footnotes, notes, bookmarks, split-pane); scholarly analysis (citation extraction, bibliographic resolution, ten-category relationship classification); the reading roadmap, knowledge profile, and 3D graph; the landing page and onboarding flow; hardening (an authorization-bypass test matrix, an AI-reliability eval harness, an admin dashboard, performance indexes, a CI-safe end-to-end test subset).
  • Phase 8 (“Critical Edition Recovery, Autonomous Research & Public-Source Discovery”): page-aware structural extraction via a GROBID service, note-style citation extraction for documents that cite entirely through footnotes with no formal bibliography, a nine-provider evidence-first research pipeline under hard cost caps, a deterministic relevance gate, canonical work identity, and independent, non-conflated credibility scoring.
  • Phase 9–10 (“Interactive Learning Workspace” and its completion): reader levels, concept mastery with an explicit precedence chain (explicit rating beats diagnostic beats inferred), passage-anchored annotations, Library/work grouping, the five-stage Curriculum package, trash with a 30-day restore window, and per-request cost-breakdown UI.
  • Phase 11: a full rebrand to the name “Palimnote,” a credibility-meter UI redesign, an honest byte-level upload progress indicator, and a two-mode Reader (an immutable “Published edition” view and a processed “Interactive reader” view).
  • Phase 12: Library canonical identity and duplicate-edition handling; the version-4 (“v4”) section-aware analysis pipeline; the fully-featured Interactive Reader; the paid, evidence-hashed Cross-Library Graph expansion feature; Writer mode (private ProseMirror projects with MLA citation formatting and DOCX/PDF export); and a hardening pass (shared rate limits, idempotency, upload safeguards, cost/error telemetry).
  • Phase 13–17: a project-record/checklist audit; Library focus and multi-file batch upload; the Visualization reframed as “a connected research web” across sources, people, and concepts with licensed source-text retrieval; and a citation-completeness/Library-integrity repair pass.
  • Phase 18: the Library-grounded Socratic RAG chat (“Ask Library”), with owner-scoped retrieval, SHA-256-hashed chunks, and citation-required answering.
  • Phase 19–24 (the most recent, still-active “completion program” as of the project’s latest recorded state): a governed, owner-approved plan covering a full product-wide baseline audit and defect register; Library integrity and file-lifecycle repair; Visualization repair; Reader/Annotations/Roadmap/RAG-sidebar parity; comprehensive accessibility, performance, and hardening work; and a final end-to-end verification and program sign-off. As of the most recent entries examined for this report, Phases 0–18 are recorded complete in every source; Phase 20 (Library integrity/reliability) and Phase 22 (parity/RAG-sidebar/motion) are substantially complete; Phase 23 (comprehensive hardening) is the active phase, with numeric explicit-citation-recall floors and accessibility sweeps underway.

Two independent, machine-generated status files in the repository — docs/phase-lifecycle-state.json and docs/project-status.json — disagreed at one point about which phase was current (18 versus 22); the project’s own documentation records this discrepancy candidly rather than silently resolving it, attributing it to the lifecycle-controller file having gone stale after the Phase 17→18 handoff while the newer, schema-versioned status tracker was kept current. This kind of self-reported inconsistency is characteristic of the project’s overall documentation discipline: known problems, defects, and even governance gaps are recorded in the project’s own log rather than hidden, and a running defect register (docs/project-status.json’s defects block) tracks every finding by priority (P0–P3) with fixed/open/partial status.

Part I · Software — 03 / 12

Data Model

The 2,100-line Drizzle schema, table by table — over sixty tables, roughly forty enums, and the CHECK constraints that make several invariants a database-level guarantee rather than an application convention.

The Drizzle ORM schema is the largest single file in the shared packages (over 2,100 lines) and defines the complete relational model, built incrementally across every phase of the project. It uses uuid primary keys (defaultRandom()), timestamp audit columns throughout, and — notably — a heavy, deliberate use of Postgres CHECK constraints to enforce polymorphic-target invariants (such as “exactly one of these three nullable foreign keys is set”) at the database level rather than by application convention alone.

3.1 Enumerated Types

The schema declares roughly forty Postgres enums, each governing one column’s closed vocabulary. The most consequential are: relationshipCategoryEnum (the ten required categories described in §2.2 above); edgeTypeEnum (fourteen values — cites, quotes, influences, criticizes, responds_to, presupposes, provides_context_for, interprets, disagrees_with, translates, is_edition_of, is_prerequisite_for, is_comparable_to, is_recommended_by — a strict superset of the ten annotation categories, since the graph needs structural edges the annotation layer does not surface); verificationStatusEnum (unreviewed, user_verified, source_verified, disputed, rejected); accessStatusEnum (open, subscription, metadata_only, user_uploaded, unavailable); readerLevelEnum (beginner, undergraduate, advanced, research); priorityTierEnum (the seven roadmap tiers, from essential to optional); masterySourceEnum (explicit, diagnostic, inferred — the precedence chain governing which competency signal is allowed to overwrite which); deletionCleanupStatusEnum (in_progress, storage_failed, completed); and queryLaneEnum (the twelve discovery “lanes” the research pipeline searches under: explicit_citation, primary_prerequisite, historical_background, concept_doctrine, scholarly_debate, author_corpus, reception_citation, parallel_literature, lecture_course, video_podcast, blog_newsletter, public_discussion).

3.2 Auth Tables

users — the account record: id, name, email (unique), emailVerified, passwordHash, sessionVersion (an integer, default 0, whose increment invalidates every outstanding JWT for the account — the mechanism that gives session revocability despite Auth.js v5’s Credentials provider requiring JWT rather than database sessions), preferences (jsonb — onboarding state and a nested workspace sub-object for theme/font/reading-width/focus-mode), and readerLevel (the four-level enum, nullable — null means “not chosen,” with every caller applying its own default rather than the schema inventing one). Cascade-deleted from users are nearly every user-owned table in the system: works, documents, highlights, notes, bookmarks, annotations, graph edges, reading records, understanding ratings, roadmap overrides, RAG conversations and chunks, Writer projects, API rate-limit counters, deletion-cleanup records, concept mastery, competency signals, cross-work relationship judgments, and graph-expansion requests.

accounts, sessions, verificationTokens follow Auth.js’s Drizzle-adapter conventions for OAuth-provider linkage, email verification, and (unused in practice, since the app uses JWT sessions) database sessions. passwordResetTokens carries a composite (identifier, token) key, an expiry, and a used flag.

3.3 Library Core

works — the top-level entity a user uploads (one intellectual work, e.g. “one edition of the Nicomachean Ethics”): id, userId, title, authorName, workType (primary/secondary), workIdentityId (a nullable link to the canonical cross-work identity a Phase-9+ analysis run derives), and deletedAt (the soft-delete/trash marker — non-null means trashed as of that timestamp, eligible for hard purge after thirty days).

editions models the real-world fact that one work can have multiple physical/print editions (translator, publisher, ISBN, DOI, year).

documents — one uploaded file belonging to a work: storagePath, mimeType, fileSize, contentHash (a SHA-256 of the verified stored bytes, used only for duplicate detection), processingStatus (uploadedprocessingneeds_reviewready, or failed), extractedText/extractedTitle/extractedAuthor, lastPosition (a jsonb reading-position-resume payload), analysisStatus (an independent lifecycle from processingStatus), and confirmedAt — a durable, Phase-20.8-era fact recording the real moment the user confirmed the document’s metadata, deliberately kept separate from and more trustworthy than the mutable processingStatus, since it can never be silently produced as a side effect of a later, unrelated reprocess.

processingJobs is the database-side bookkeeping mirror of a pg-boss job (separate from pg-boss’s own internal queue tables), tracking job type, status, error, and attempt count.

3.4 Reader Tables

footnotes — heuristically-detected footnotes, plain-text/Markdown documents only (a documented, deliberate scope limit; PDF footnote detection needs layout awareness this heuristic doesn’t have and is handled instead, for structurally-extracted PDFs, via the GROBID/apparatus path described in §4).

highlights — a user’s text selection, anchored by anchor (a jsonb quote-plus-prefix-plus-suffix text fingerprint, deliberately not raw page/pixel coordinates, so an anchor survives re-render, reflow, or re-extraction — directly implementing the project’s documented mitigation for its own identified “annotation position drift” risk). This same anchoring shape is reused by annotations.anchor, docFootnotes.pageAnchor, and the whole passage-annotation subsystem.

notes — a user’s free-text annotation, either standalone or attached to a highlight via the legacy single highlightId pointer or (Phase 12.4) the many-to-many noteHighlights join table, letting one note explain several selected passages and one passage be discussed by several notes.

termVariants / termOccurrences — transliteration/translation pairs for non-Latin-script terms (original script, transliteration, language, direction), each carrying a verificationStatus (suggested/verified) so a suggested pair never changes what the reader sees until a person verifies it; termOccurrences records the exact processed-text offsets where a verified pair appears.

bookmarks — a saved reading position (jsonb, PDF-page or text-paragraph shaped).

3.5 Scholarly Analysis (Legacy v1 Pipeline)

bibliographicRecords — the shared, deliberately un-owned, append-only catalog of external scholarly works (an accepted trade-off documented in the project’s own design-decision log: a record can be orphaned when the works citing it are deleted, since there is no per-user foreign key to cascade; a periodic orphan sweep is recorded as future, unbuilt work). An “unresolved” citation still gets its own record rather than being dropped or guessed.

citations — one raw citation mention extracted from a document, carrying sourceType (bibliography/footnote/endnote/inline), parserConfidence, a sourceAnchor (page/block/marker/offset), a nullable resolvedBibId, and resolutionState (pending/resolved/unresolved).

annotations — a relationship-classified annotation, the legacy v1 output: relationshipCategory, targetBibId (nullable) paired with a always-populated targetLabel (so a citation is never dropped from the UI even when unresolved), anchor, extractedSourceText (the verbatim triggering passage, never paraphrased), explanation, confidence (always shown to the user), modelUsed/promptVersion (provenance for AI annotations, null for user-created ones), createdBy (system/user/editor), and verificationStatus.

graphEdges — the generic, per-user, polymorphic edge table backing the recursive-CTE roadmap and graph traversal, a deliberate substitute for a dedicated graph database: sourceType/sourceId and targetType/targetId are plain text-plus-uuid discriminators, not real foreign keys, since the graph must connect works, bibliographic records, and concepts interchangeably. Because these edges carry no real FK, Postgres’s cascade delete cannot clean them up automatically when a work is deleted — this is one of the few places the codebase must perform an explicit, manual cleanup step (see packages/deletion in §8).

aiUsageLogs — every AI provider call, including the zero-cost heuristic fallback (logged as model: "heuristic-fallback"), feeding the admin cost dashboard.

3.6 Roadmap and Knowledge Profile

readingRecords and understandingRatings both carry a three-way polymorphic target (workId / bibId / learningResourceId, exactly one non-null, enforced by a CHECK constraint computed via a summed case expression) — a user’s reading-status entry and 0–100 self-rated understanding, respectively, for any of the three kinds of thing a roadmap or curriculum item might point at. A score of 60 or above (KNOWN_THRESHOLD, defined once in packages/roadmap and referenced everywhere else) means “known,” which demotes an item to review-only. understandingRatings.source (Phase 22.9b) distinguishes an explicit user rating from a diagnostic self-assessment answer or an inferred chat-derived signal, guarding a strict precedence chain.

roadmapOverrides — a user’s manual adjustment layered on top of the computed roadmap for one root work: hidden, manualTier, manualPosition, and addedManually (a target the automatic traversal never reached but the user searched for and added by hand).

As already noted in §2.3, there is deliberately no persisted roadmap table — the roadmap is recomputed from graphEdges plus these three durable, user-authored tables on every request.

3.7 Versioned Processing Pipeline (Phase 8+)

processingRuns — one (re)processing attempt for a document: version (monotonic per document), pipelineVersion, status (pending/running/complete/failed), structureState (full/limited — whether GROBID-quality structure was available, orthogonal to success/failure), stage (the live human-readable progress label), isPublished, aiCostUsd, degraded (true only when the run’s cost crossed its soft cap, never merely because a resource cap or discovery-saturation limit was reached — the project’s own commentary notes an earlier version that flagged every saturation-limited run as “degraded” was “crying wolf” against real load-test evidence), and saturationNote. A partial unique index (WHERE is_published) makes “exactly one live edition per document” a hard database invariant rather than an application-logic-only guarantee.

pages and textBlocks carry the page-by-page, block-by-block structural transcript (title/header/body/footer/footnote/endnote/caption/bibliography/reference block kinds, each with optional page-coordinate bbox), the real structural anchor consumed by citations, term occurrences, apparatus entries, passage annotations, RAG chunks, and work claims alike.

3.8 Retrieval-Augmented Generation (Phase 18)

ragChunks — owner-scoped retrieval chunks, deliberately narrow: only uploaded-document blocks or explicitly-licensed open-access content are eligible, enforced by a CHECK constraint (rag_chunk_exactly_one_eligible_source) that requires an uploaded chunk to carry a textBlockId and no researchResourceContentId, and an open_access chunk the reverse — the two kinds are mutually exclusive at the database level. Every chunk carries a not-null anchor (jsonb — a real reader block/page location or a real licensed source URL, “never a fabricated locator”) and a contentHash.

ragConversations, ragMessages, ragMessageCitations — a persistent, owner-scoped conversation history, with every substantive answer’s citations linked to the exact chunk(s) it drew from; the cascade chain (work → run → chunk → citation) means deleting a work or a licensed source automatically removes its answer citations rather than leaving a dangling locator.

3.9 Structural Apparatus and the Research Pipeline (Phase 8+)

docFootnotes (legacy, page-anchored) and documentApparatus (Phase 12.3, typed footnote/endnote/bibliography_entry/citation_block, block-anchored) both hold structurally-extracted authorial apparatus, explicitly distinct from AI-generated notes and never to be replaced by them. docMetadata records auto-resolved title/author metadata for a run with a winning source (embedded/grobid/title-page/bibliographic/ai) and a confidence score that drives whether a document can skip straight past manual metadata confirmation.

researchResources — one discovered scholarly/web/social resource for a specific processing run (run-scoped, so a reprocess can publish atomically without deleting the last good edition’s own resource set), carrying accessStatus, inspectionDepth, dedup identity fields (DOI, ISBN, canonical URL, normalizedKey, in priority order), and — the honest first cut at cross-record grouping, later promoted into a proper shared table — its own run-scoped workKey/workRole/workCanonicalTitle/workAuthorSurname/workEvidence columns.

researchResourceContents keeps actual retrieved full text in its own table, deliberately separate from researchResources.raw, so provenance/deletion/retrieval reasoning never conflates metadata with copied content; status distinguishes metadata_only from open_access_available (known-retrievable but not yet fetched) from open_access_indexed (actually fetched and chunked) from retrieval_failed.

providerAttempts logs, per run and per provider, exactly what was queried and what happened — queried/unavailable/rate_limited/failed/disabled — the audit trail proving which sources were and were not actually consulted, never silently omitted.

credibilityAssessments deliberately keeps credibility as several independent, separately-labeled real-valued dimensions (publicationRigor, creatorExpertise, hostProvenance, pedagogicalValue, an authority band AE, and a peerReviewed boolean whose null means genuinely “unknown,” never “no”) rather than one collapsed number — directly implementing one of the project’s central design commitments — with popularity recorded and displayed but never scored as a credibility input.

generatedNotes/generatedClaims/claimEvidence — an AI-generated critical note about how a discovered external resource relates to the primary work (distinct from passageAnnotations, which explain the primary text itself), each claim carrying an agreement state (strong/contested/mixed/insufficient) and linked to the exact evidenceSpans (verbatim quotes) that support or contradict it.

researchCandidates — deliberately retains every candidate a discovery lane surfaced, including rejected and quarantined ones, specifically because “deleting them would make the pipeline unfalsifiable” — the project’s own precision/recall evaluation gates are measured against exactly these rows, verdict included.

3.10 Interactive Learning Workspace (Phase 9+)

concepts — a shared, global, append-only vocabulary unifying concepts, doctrines, people, traditions, and debates into one typed table (rather than five near-identical ones), so two readers studying the same doctrine converge on the same graph node.

conceptMastery — shaped identically to understandingRatings (0–100, ≥60 known), with the same masterySourceEnum precedence discipline.

competencySignals (Phase 22.9b) — the audit ledger for every chat-inferred competency write or precedence-skip: a not-null basis field holding a verbatim quote (never a paraphrase, mirroring annotations.extractedSourceText’s grounding discipline), a detector field distinguishing the always-on deterministic self-report pattern-matcher from a gated model call, and a status (applied/undone/superseded/skipped_precedence).

workIdentities — the canonical, cross-run, cross-work identity table (Phase 9, promoted out of the earlier run-scoped columns) so the Library can recognize “you already have this” across separate uploads and separate runs; verified identifiers (DOI, ISBN, an external provider id, an uploaded document’s content hash) are accepted only from a primary-role record and only ever backfilled when null, never overwritten — “a review’s own DOI must never become the reviewed work’s DOI.”

workIdentityMerges (Phase 20.6) — a fully reversible record of one applied canonical-identity merge: a reversal jsonb payload capturing exactly what was displaced, so the merge can be undone precisely; the merged-away (“loser”) identity row is never deleted, only unreferenced, which is what makes the reversal possible at all.

learningResources/resourceRoles — the Library’s durable system of record and the actual join the Library, curriculum, and graph all read: what role one resource plays for one work, at a given reader level (since the same commentary can be a prerequisite for a beginner and a parallel reading for a specialist), with a NULLS NOT DISTINCT unique constraint specifically so the “applies at every level” row (a null reader level) is still correctly deduplicated — the schema’s own comment notes this was deliberately chosen because Postgres’s default null-handling would have silently let that one row type violate uniqueness.

passageAnnotations (Phase 9.3) — an explanatory note about the primary text itself, with two CHECK constraints making two invalid states structurally impossible: an annotation that is anchored but points nowhere real, and an annotation that claims to be whole-work but still carries a fake anchor.

workClaims, workEmbeddings, workRelationshipCandidates, workRelationshipJudgments, graphExpansionRequests (Phase 12.5) — the machinery behind the paid, evidence-hashed Cross-Library Graph feature: a grounded claim (never a bare model assertion) tied to its own supporting excerpt and (where available) a real text-block foreign key; a compact per-work embedding vector (stored as jsonb rather than a dedicated pgvector column, a deliberate scope decision to keep the phase additive and rollback-safe); a cheap, unjudged retrieval-candidate cache; and the durable, judged relationship record itself, uniquely keyed by a SHA-256 hash of the exact claim evidence supplied — so unchanged evidence between two works is never re-billed to a model twice.

3.11 Writer Mode (Phase 12.6)

writerProjects, writerDocuments (storing canonical ProseMirror JSON, never rendered HTML), writerDocumentRevisions (immutable content snapshots for autosave recovery and deliberate restore), and writerCitations (project-scoped CSL-JSON sources) — all nested beneath their owning project specifically so no API route can grant access by a bare document UUID alone; every access path must traverse the owned project first.

3.12 Hardening Tables

apiRateLimits — the shared, database-backed, cross-instance fixed-window rate-limit counter keyed by (userId, scope), used by every cost-bearing or abuse-prone authenticated route.

deletionCleanups (Phase 20.3) — the durable persistence layer behind the @ice/deletion pure state machine described in full in §8.1: workId is deliberately not a foreign key (since the work row is itself hard-deleted mid-flow and this record must outlive it), workTitle is retained so an admin cleanup queue can still name the work after its row is gone, and pendingStoragePaths/stageLog (both jsonb, the latter bounded to a fixed length) provide a crash-resumable audit trail of exactly which Storage objects remain to be removed.

3.13 Overall Relational Shape

The schema’s dominant shape runs: usersworks → (editions, documents) → (footnotes/highlights/notes/bookmarks/termVariants/citations/annotations/processingJobs); a document also spawns a chain of processingRunspagestextBlocks, from which nearly every Phase-8-and-later table hangs. Cross-work, cross-run canonical identity is layered on top via workIdentities/workIdentityMerges/learningResources/resourceRoles, which the Library, roadmap, curriculum, and graph all read against instead of touching run-scoped data directly. bibliographicRecords is the one deliberately un-owned, append-only shared catalog several tables reference but that nothing cascades from a user delete — an accepted, documented trade-off at the project’s current single-user-scale cost posture. graphEdges is the schema’s one genuinely polymorphic (non-foreign-key) relation, existing purely to support the recursive-CTE roadmap and graph traversal without standing up a dedicated graph database.

Part I · Software — 04 / 12

The Ingestion Pipeline

How a raw PDF, EPUB, or text file becomes a page- and block-structured transcript — pdf.js extraction, GROBID structural enrichment with its contamination guards, OCR fallback, and deterministic citation extraction.

@ice/ingestion is the shared document-parsing package used by both apps/web (upload validation and Storage access) and apps/worker (the job that turns an uploaded file into a structured document). Its job is to take the raw bytes of a PDF, EPUB, or plain-text/Markdown file and produce a page- and block-structured transcript, plus best-effort metadata, footnote/endnote/bibliography apparatus, and heuristic citation candidates — all without ever inventing text that is not actually present in the source. Its dependencies are chosen carefully: unpdf (a pdf.js wrapper, chosen over pdf-parse specifically because pdf-parse has a known footgun where a bare require can trigger test-fixture loading) for PDF text-layer extraction and page rendering; epub2 for DRM-free EPUB parsing, imported only lazily so it is not a hard startup dependency; fast-xml-parser for GROBID’s TEI XML, using its order-preserving node shape so mixed text/element content and reading order survive; tesseract.js and @napi-rs/canvas for OCR, both loaded dynamically so a missing optional dependency never breaks the main application; and @supabase/supabase-js for Storage operations via the service-role key.

4.1 src/index.ts — the entry point

parseDocument(buffer, mimeType) dispatches by MIME type to parsePdf, parseText, or parseEpub, then passes the result through an internal sanitizeParsedDocument step before returning. This sanitization step recursively applies sanitizeExtractedText (see §4.9) to every string field of the parsed document — the merged text, the detected title/author, and every page’s and block’s text — so that every parser’s output passes through the same defensive choke point before it can ever reach the database or a job payload. This exists specifically because of a real production incident: a malformed-Unicode fragment from a damaged or OCR’d source once crashed the entire worker process with “unsupported Unicode escape sequence,” rather than failing just the one job.

4.2 src/parsers/pdf.ts — the PDF pipeline

This is the structurally central file of the package. It defines the shared ParsedDocument/ParsedPage/ParsedBlock type contract the whole package is built around, and orchestrates pdf.js extraction, OCR fallback, and optional GROBID enrichment.

processedTextFromPages(pages) builds the reader/analysis transcript by joining only prose-bearing block kinds (title, header, body, caption) — footnotes, endnotes, and bibliography blocks stay persisted and individually addressable but are deliberately never folded into the body text, preventing the same source material from being shown twice, once as apparatus and once as prose.

mergePageTexts(pageTexts) trims, drops empty pages, and joins the rest — rebuilt deliberately from the final per-page texts (including any OCR results), fixing a prior bug where a scanned document whose text layer started empty stayed empty in the merged document text even after OCR successfully recovered content.

**metadataConfidenceFor(titleSource, hasFallbackTitle)** is a small decision table converting a title's provenance into a numeric confidence consumed downstream by the worker's auto-ready gate: a GROBID header-sourced title scores0.95(near-certain); a *recovered* title (the header title was distrusted and a body heading was substituted instead) scores only0.7, deliberately kept below the auto-ready threshold so the document routes to manual review rather than asserting a second guess with the same false certainty that caused the original defect; a plain PDF-metadata or first-line fallback scores0.65if present, else0`.

parsePdf(buffer) runs, in order: (1) pdf.js text-layer extraction, once merged and once per-page; (2) PDF metadata reading, tolerating failure as “no metadata available” rather than a parse failure; (3) per-page block construction, flagging any page whose text is under 40 characters as low-confidence (possibly scanned); (4) OCR fallback for sparse pages, folding results into both the per-page record and the merged document text; (5) a title fallback from the first non-empty short line of the merged text if no PDF-metadata title exists; (6) optional GROBID enrichment.

Where GROBID runs and returns at least one body block, its entire structured block set replaces — never appends to — each page’s block array; the code’s own comment is explicit that appending would be “a structural lie,” since the pdf.js fallback body already contains footnote and bibliography glyphs inline, and appending structured notes on top would show the same material twice. The raw per-page pdf.js/OCR text is left untouched even after replacement, which is what lets a later recovery pass (§4.3) still see the original text. structureState is set to "full" only if GROBID actually ran, produced non-empty text, and produced at least one genuinely body-kind block — a syntactically valid but content-empty TEI response is explicitly not enough to claim structural fidelity.

4.3 src/parsers/grobid.ts — the GROBID adapter

The most intricate file in the package, holding extensive fixture-driven “contamination guard” logic addressing a specific, measured GROBID model failure: its citation-segmentation model (trained on itemized numbered reference lists, not the continuous-prose, multi-citation-per-footnote style common in humanities scholarship) sometimes “bleeds” one bibliography entry’s author into a structurally nearby sibling that has no author of its own. A fixed adjacency window (three positions in document order) and a whole-document pre-pass mapping every confidently-owned surname to its true owning entry together decide whether a bare surname on a different entry is contamination or a genuinely different person who happens to share a name — a document-wide (unbounded) version of this check was proven, by an adversarial two-distinct-people-same-surname test, to be too broad.

walkBody(node, ctx) recursively walks the TEI <text> subtree in document order, emitting one block per structurally meaningful element: headings become header blocks; <note> elements become footnote or endnote blocks (carrying their n attribute as the marker, emitted separately from body text so a paragraph’s footnotes are never duplicated into its own prose); paragraphs become body blocks with their nested notes excluded from the paragraph’s own text but still recursed into as their own blocks; bibliography structures become reference blocks using the contamination-guarded text-builder; figures and tables become caption blocks.

headerAuthorsMissing(header) and recoverTitleFromHeadings(blocks) implement a Phase-20.67 fix for a real, observed defect: on at least one real fixture, GROBID’s header-segmentation model locked onto the wrong page region entirely (extracting a journal’s copublisher line as the “title,” with zero author names also extracted). Because there is no reliable text blocklist for every possible publisher string, the fix instead uses “the extracted header region contains zero person names at all” as the structural tell, and recovers a replacement title from GROBID’s own separate body-segmentation pass — finding the earliest page carrying any heading with bounding-box coordinates and picking the heading with the largest bounding-box height, reasoning that a running venue line prints smaller than the real title on the same page.

processWithGrobid(buffer) reads the configured GROBID service URL from the environment and returns null immediately if it is unset — a missing configuration means “disabled,” never a silent transfer of a private document to an unintended third-party service. A per-process concurrency semaphore (default limit of 1) serializes calls to the GROBID service, since the underlying model service is memory-bound and can be OOM-killed under parallel full-text requests. A companion module, gcpIdToken.ts, mints a self-signed-JWT-bearer Google ID token so the worker (which runs outside GCP and has no metadata server available to it) can call an authenticated, private Cloud Run GROBID instance — falling back to null (unauthenticated) if no service-account key is configured, which then naturally degrades to the standard “GROBID unavailable” fallback path.

4.4 src/parsers/ocr.ts — page-bounded OCR fallback

ocrLowTextPages(pdf, pageTexts) selects pages whose text is under 40 characters, capped at a configurable maximum (default 12 pages), creates one Tesseract worker for the whole batch to bound memory, and processes pages sequentially rather than in parallel — an explicit memory-management choice. Any failure (a missing native renderer, a missing language model) is caught and the function returns whatever results were already collected, since OCR is explicitly treated as an enhancement that must never discard page boundaries or degrade an otherwise-readable PDF.

4.5 src/parsers/endnoteRecovery.ts — recovering endnotes GROBID’s structural pass missed entirely

This addresses a separate, confirmed GROBID gap, documented as “D-20-89”: re-running the same fixture with and without requesting positional coordinates produced byte-identical missing-endnote counts, ruling out a request-parameter fix. recoverTruncatedEndnotes({pageTexts, structuredMarkers}) scans raw per-page text for a “Notes”/“Endnotes” heading, then expects a strictly sequential run of numbered entries (1, 2, 3, …), filtering out running-header/footer boilerplate lines (detected by their repetition rate across the document) along the way. A trust gate requires at least three sequential entries to be found before any are accepted at all — “not a real endnotes list” is the safe default otherwise — and any recovered marker already present in GROBID’s own structural output is dropped, so recovery can only add coverage, never duplicate it. Recovered entries are tagged recovered: true and, because they are endnote-kind, are automatically excluded from the reader’s body transcript by processedTextFromPages — recovery can add addressable apparatus, but can never leak into prose.

4.6 src/parsers/citations.ts — the deterministic citation extractor

Explicitly documented as “not a parser” — a pattern-matching, always-on, zero-cost baseline stage that runs ahead of any AI-assisted extraction. Handles reference/bibliography-section splitting, inline author-year forms ((Kant 1781), Kant (1781)), a deliberately narrow allowlist of direct classical-work mentions (so it never over-matches generic title-cased phrases), and — the module’s most extensively justified addition — two “note-style” citation patterns added after a concrete production observation: on a real 2001 philosophy article that cites entirely via numbered footnotes with no bibliography section at all, both the reference-section pass and the author-year pass extracted zero citations, and GROBID itself only recovered four of roughly forty notes since it had no bibliography section of its own to anchor against. Without these two added patterns, the pipeline would have seen none of that article’s citations at all.

splitNoteEntries(text) un-bundles a single footnote block that actually contains two independent citations joined by connective prose with no semicolon at all — but only commits to a split when every resulting segment independently looks like a real citation, an explicitly conservative “when in doubt, do not split” discipline recorded as a direct fix for an earlier over-eager split that had mis-divided ordinary scholarly-discourse sentences.

extractCitationMentions(sources, max) is the provenance-preserving sibling of the simpler discovery-oriented extractCitations: a bibliography-type source is trusted verbatim as a single citation (specifically avoiding the case where the extractor misreads a title’s own words, such as one containing the phrase “Nicomachean Ethics,” as a second spurious inline citation); a footnote or endnote source is first split via splitNoteEntries, then each resulting segment is independently re-run through the extractor, and even a segment that yields zero regex matches is still preserved verbatim as a low-confidence lookup candidate rather than silently dropped — “a structural footnote or endnote entry is itself evidence even when it lacks a year or uses an unrecognized catalog style.”

4.7 src/parsers/footnotes.ts, text.ts, epub.ts — the remaining format handlers

detectFootnotes(text) is a much simpler, legacy-scoped heuristic for plain-text/Markdown documents only: it finds a trailing run of consecutive numbered lines and cross-checks each candidate number against an in-body [N]/(N) marker earlier in the document, which is the real false-positive guard (not the length of the run) — this is what filters out an unrelated numbered list, like a table of contents, sitting near the end of a document.

parseText(buffer, mimeType) produces a single-page document with a conservative, line-pattern-based structural split (a Markdown heading, a recognized “notes”/“bibliography” section heading, a caption line), explicitly documented as still “structure-limited,” since line patterns cannot assert real page-layout truth the way GROBID’s structural pass can.

parseEpub(buffer) parses via epub2, producing one page per non-empty chapter (no finer within-chapter structural split), converting each chapter’s HTML to plain text. Its own documentation is explicit that an encrypted or malformed EPUB produces a normal processing failure, never a DRM bypass attempt.

4.8 src/parsers/apparatus.ts — deriving reader-facing apparatus

extractAuthorApparatus({blocks, text, maxCitationBlocks}) runs a structural pass over already-classified blocks (tracking an “in endnotes” state that flips on at a recognized notes heading and off at the next section heading, catching the common case where a structural parser classified a note-section paragraph as ordinary body text) plus a heuristic citation pass over the merged text, de-duplicating by normalized text across both.

4.9 src/sanitizeText.ts, src/storage.ts, src/validation.ts

sanitizeExtractedText(text) replaces every unpaired UTF-16 surrogate with the Unicode replacement character and strips NUL bytes — the ingestion-boundary fix for the production crash described in §4.1, applied universally to every parser’s output.

storage.ts is the shared Supabase Storage access layer (server-only, service-role key), offering uploadDocumentFile (never silently overwrites, upsert: false), createSignedUploadUrl (lets a large file be PUT directly to Storage from the browser, bypassing Vercel’s request-body size limit), downloadDocumentFile, deleteDocumentFile, getDocumentFileSize (the authoritative Storage-reported byte count, used specifically to close a signed-upload quota-bypass path where a client-declared size could otherwise be trusted without verification), and getSignedDocumentUrl (a short-lived signed URL for the reader UI, whose caller is responsible for checking ownership before ever calling it, since the returned URL itself grants access to anyone holding it for its TTL window).

validation.ts performs format- and archive-level validation — explicitly not a malware-detection claim, as the module’s own comment states directly — checking PDF magic bytes, EPUB ZIP/manifest structure with a zip-bomb-style entry-count guard, and (for plain text) the absence of embedded binary data, plus an optional private ClamAV-compatible scan that is genuinely a no-op (returning “valid” without pretending to scan) whenever no scanner endpoint is configured.

Part I · Software — 05 / 12

AI Adapters & Bibliographic Resolution

The provider-agnostic, fetch-only LLM layer and its deterministic heuristic fallback, paired with the four-source bibliographic resolver that is the software’s core anti-hallucination guarantee.

5.1 packages/ai-adapters — the provider-agnostic LLM layer

@ice/ai-adapters implements the provider-agnostic interface described in §2.4. Its package.json declares no runtime dependencies at all, only development dependencies — a direct consequence of the fetch-only design.

types.ts defines the shared vocabulary: TaskType (relationship_classification, metadata_extraction, citation_parse); LLMCompletionParams/LLMCompletionResult; the single-method LLMProvider interface ({name, model, complete()}) that is the entire contract downstream classification logic depends on; the frozen RELATIONSHIP_CATEGORIES ten-value tuple; SUSTAINED_CITATION_THRESHOLD = 5 (the mention-count floor at which a candidate’s sheer repetition becomes evidence of a prerequisite relationship); and ClassificationResult, whose heuristic boolean is the honesty flag surfaced all the way to the UI.

routing.ts implements cost-first task routing: TASK_ROUTES maps every current task type to an identical {preferred: OpenAI cheap tier, alternate: Anthropic cheap tier} pair, while a separate RESEARCH_ROUTE (a pricier tier) exists only for note synthesis and is never used by mechanical extraction or classification work. estimateCostUsd(model, promptTokens, completionTokens) looks up a small hardcoded USD-per-million-token price table, falling back to a conservative default for any unrecognized model — explicitly documented as an approximation for the admin dashboard, not a billing-grade calculation.

providers/openai.ts (OpenAIProvider) and providers/anthropic.ts (AnthropicProvider) both implement LLMProvider over raw fetch. The OpenAI provider detects “reasoning” model families (GPT-5/o*) and switches request-body shape accordingly, since those models reject the classic max_tokens/non-default-temperature parameters and instead require max_completion_tokens, deliberately floored at 1024 tokens “to leave headroom for reasoning tokens so the JSON output isn’t truncated.” It always requests OpenAI’s native JSON response mode. The Anthropic provider, lacking an equivalent native JSON-mode flag, compensates by appending an explicit “respond with a single JSON object and nothing else” instruction to the system prompt — the caller still parses defensively regardless. Both providers normalize their differently-shaped token-usage fields into the shared LLMCompletionResult contract, and both throw a descriptive error on any non-OK HTTP response, which the caller (classify.ts, index.ts) catches and degrades to the heuristic fallback rather than propagating.

providers/heuristic.tsheuristicClassify(input) is the deterministic, no-network fallback classifier used whenever neither an OpenAI nor an Anthropic key is configured, and also as the degrade path when a configured provider’s response cannot be parsed. It tests the triggering passage against five ordered regex rules (disagreement, influence, prerequisite, historical-context, comparison); if none match, it checks the candidate’s own title for secondary-literature markers (“introduction,” “commentary,” “companion,” etc.); if that also fails, it checks whether the candidate’s total mention count crosses the SUSTAINED_CITATION_THRESHOLD; and only as a last resort defaults to explicit_reference (if resolved) or ai_inferred (if not). Every branch’s confidence is deliberately capped below what a real model call would report — a resolved match through a genuine regex rule scores at most 0.7, while an unresolved pure-fallback result scores only 0.3 — and the generated explanation text itself states plainly that it was produced without a model and should be verified. The result always carries provider: "heuristic", model: "heuristic-fallback", and heuristic: true.

classify.tsclassifyWithProvider(provider, input) builds a fixed, hardcoded system prompt listing all ten categories with one-sentence definitions and an explicit instruction never to invent bibliographic facts, reasoning only from the passage and titles given. buildPrompt truncates the triggering passage to 1,200 characters and strips any literal triple-quote sequence so the passage cannot prematurely close its own delimiter fence and smuggle in fake instructions — the uploaded document’s own text is always treated strictly as untrusted data, never as instructions, which is the anti-prompt-injection anchor point for the whole classification stage. The parsed response’s category field is validated against the closed RELATIONSHIP_CATEGORIES set via coerceCategory; an invalid or out-of-vocabulary value, or an unparseable response, degrades to the heuristic classifier — but crucially, the real token counts from the failed call are still preserved onto the degraded result, so cost accounting stays accurate even when the classification itself falls back.

embeddings.ts (OpenAIEmbeddingsClient) and responses.ts (OpenAIResponsesClient) are two further, narrower OpenAI clients: the embeddings client validates that a returned vector is genuinely non-empty and entirely numeric before accepting it, throwing rather than propagating a garbage vector into the database; the Responses client wraps OpenAI’s structured-output (JSON-Schema, strict: true) endpoint with a bounded retry loop (at most three attempts total), distinguishing a definitive 4xx failure (retried never — “won’t get better on retry”) from a soft failure like empty output, invalid JSON, or a caller-supplied validate() rejection (retried up to the bound). safetyIdentifierFor(userId) derives a stable, non-reversible per-user identifier via SHA-256, so the raw user id is never sent to the vendor.

index.tsgetProviderForTask(task) is the single place “which provider is actually available” is decided: it checks the preferred, then the alternate, environment key in order and constructs the corresponding concrete provider, returning null if neither key is present. classifyRelationship(input) is the package’s real public entrypoint: if no provider is available at all, it returns the heuristic result immediately with zero network calls and zero latency; otherwise it calls the real provider inside a try/catch, logging and degrading to the heuristic on any thrown error — meaning a transient provider outage degrades exactly one candidate classification, never the whole analysis job.

5.2 packages/bibliographic — resolving citations against real catalogs

@ice/bibliographic implements the project’s core anti-hallucination guarantee: a citation’s title, author, year, or DOI can only ever originate from a real match against Crossref, OpenAlex, Open Library, or Google Books, never from language-model generation. Like ai-adapters, it has zero runtime dependencies — every source uses plain global fetch.

types.tstitleOverlap(query, title) is the confidence guard’s core primitive: it normalizes both strings (lowercase, strip punctuation, drop words of four characters or fewer as noise) into significant-word sets, then returns the fraction of the query’s significant vocabulary that the candidate title covers — a recall-oriented, not symmetric, measure, so a title carrying extra words beyond the query is never penalized. bestTitleMatch(query, items, titleOf, threshold=0.34) scans up to five candidate results from a provider (not just its top-ranked hit, per a fix for a documented “single-hit fragility” defect where a provider’s own #1 result was sometimes wrong for a noisy, OCR-garbled citation string) and returns the highest-scoring candidate that still clears the 0.34 threshold, or null if none does — widening the scan window never loosens the actual confidence bar.

classifyCitationForm(query) is a pure, deterministic classifier distinguishing a book-form citation (publisher/imprint/edition markers, an editor abbreviation) from a journal-form one (a quoted title, or venue-name words like “Journal”/“Review”/“Quarterly”) from “unknown” — this classification exists specifically to fix a measured defect where book-form citations (older monographs, critical text editions, lexicons) resolved far worse against the article/DOI-centric default provider order than against book-first catalogs.

crossref.ts, openalex.ts, openlibrary.ts, googlebooks.ts each implement the shared BibliographicSource interface, applying bestTitleMatch at the same 0.34 threshold against up to five results. Notably, openlibrary.ts contains a documented, live-verified fix (D-20-81): Open Library’s own search endpoint reliably returns zero results for an author-plus-bare-year query (exactly the shape every citation query already has, since an upstream cleanup step collapses a publisher parenthetical down to just its year), so the module strips the trailing year specifically from the outbound search parameter while still scoring every candidate against the citation’s original, unmodified query — the fix changes only what is sent over the wire, never the confidence guard itself. googlebooks.ts was added at the same time, specifically to cover book-form fixture misses that neither Crossref nor OpenAlex nor Open Library could resolve.

index.tsresolveCitation(query, opts) is the package’s orchestration entrypoint. It refuses queries under six characters outright (too short and noisy to be worth a network round trip); classifies the citation’s form and reorders the source list accordingly (BOOK_ORDER = [openlibrary, googlebooks, openalex, crossref] for book-form citations, DEFAULT_ORDER/JOURNAL_ORDER = [crossref, openalex, openlibrary, googlebooks] for everything else, always preserving every provider — only ever reordering, never dropping one); and then tries each source in that order with an independent per-source timeout (default eight seconds) and error isolation, returning the first confident match and never trying further sources once one succeeds. A single flaky provider is caught, logged, and skipped rather than aborting resolution. If every source is exhausted with no match, the function returns null, and the calling worker code keeps the citation honestly unresolved rather than guessing.

A particularly load-bearing detail, documented in the code itself: the form-classification step reads the citation’s raw, unmodified text when the caller supplies it, even though the actual network queries sent to each provider always use the already-cleaned lookup string — because the upstream cleanup step that strips publisher-parenthetical noise (to improve match rate) also strips exactly the book-form signal words (Press, Clarendon, Lexicon) that the classifier needs to see. Only the decision of which provider order to use looks at the raw text; the query actually sent over the wire is unaffected.

Part I · Software — 06 / 12

Roadmap, Curriculum & Consistency Checking

The pure ranking function behind the personalized reading plan, the deterministic five-stage pedagogical sequencer built on top of it, and the nine cross-surface data-integrity checks that keep them honest.

6.1 packages/roadmap — the pure ranking core

@ice/roadmap is a single, dependency-free module implementing the pure ranking function behind the personalized reading roadmap. It performs no I/O, no database access, and no randomness — which is exactly what lets its own test suite assert the project’s plan-derived Heidegger and Vico acceptance cases deterministically.

CATEGORY_TIER is a total, hardcoded mapping from each of the ten relationship categories to one of seven priority tiers (essential, high, strongly_recommended, contextual, interpretive_aid, comparative, optional) — prerequisite maps to essential; conceptual_influence and disagreement_polemical_target map to high; and so on. KNOWN_THRESHOLD = 60 is defined once here and imported everywhere else in the system that needs the “known” cutoff.

matchesReaderLevel(materialLevel, selectedLevel, mode) decides whether one piece of material belongs in a given reader-level view: universal (untagged) material and the "all" selection are always shown; a "cumulative" mode shows everything at or below the selected level (a superset accumulation); an "exact" mode shows only material tagged at exactly that level (plus universal material, deliberately still included).

collapseDuplicateCandidates(candidates) implements duplicate-edition collapse: because the graph traversal can reach several distinct bibliographic records for the same real-world work (the book itself, a review of it, a second edition), this function groups candidates by a normalized title and merges each group into one deterministic “primary” survivor — chosen first by whether the item is already in the reader’s library, then by shallowest graph depth, then by highest centrality, then by a stable id tiebreak — while unioning categories, taking the maximum confidence, and recording every folded-away id so a later graph-node lookup can still try any of them.

rankRoadmap(candidates, profile, overrides, options) is the package’s central export. For each candidate it resolves the strongest category present (by tier rank), applies any manual tier override, filters by the requested depth mode (concise keeps only high-tier-and-above) and reader level (a manual override always bypasses the level filter, so a pinned item is never silently hidden by a level change), determines “known” status from the profile, and generates a deterministic, template-based “why this, here” explanation via reasonFor(category, centrality, known) — never a model call. The final sort chains: any manual position pin first; then known items sink below unknown ones; then priority tier; then descending centrality; then descending confidence; then an alphabetical title tiebreak. If a time budget is supplied, items are never dropped for exceeding it, only flagged overBudget: true.

mergeRoadmapsAcrossRoots(perRoot, profile, options) (Phase 22.7) merges several selected root works’ roadmaps into one sequence, reusing — never re-implementing — the same collapseDuplicateCandidates and rankRoadmap pipeline over the unioned candidate set, with a documented composition rule for multi-root overrides: an item is hidden in the merged view only if every root that reaches it individually marks it hidden.

6.2 packages/curriculum — deterministic pedagogical sequencing

@ice/curriculum’s only dependency is @ice/roadmap itself. stageForRelationship(category) is a total, compile-time-exhaustive lookup mapping each of the ten relationship categories into one of five fixed pedagogical stages, in order: prerequisites, formative_context, core_engagement, interpretation_context, extension. checkpointFor(category) is likewise a total, hardcoded lookup of deterministic, template-based self-check questions keyed by category — the module’s own documentation frames this explicitly as matching the project’s anti-hallucination posture, since a self-check question “is cheap to fabricate plausibly and expensive to verify,” so it is generated the same deterministic way reasonFor() is, with no model call at all.

hasCycle(items) is a general-purpose, textbook three-color depth-first cycle detector, deliberately more general than the fixed five-stage structure strictly needs — and assertAcyclicStages(items) uses it as a genuine runtime proof, not merely an assumption, that the fixed stage ordering can never produce a cyclic dependency graph, functioning as the pure-function equivalent of a database CHECK constraint.

6.3 packages/consistency — nine deterministic cross-surface checks

@ice/consistency implements Phase 20.7’s data-integrity layer: nine deterministic checks, each taking an already-fetched, flat, whole-database snapshot and returning a list of typed mismatches, plus a matching repair action where one can be safely derived. Its central anti-hallucination discipline, stated in its own type definitions, is that a repair is only ever emitted when the correct value is derivable from an already-existing canonical fact — never guessed — and every emitted repair’s reason field must name the canonical source it was derived from.

The nine checks are: citation↔︎Library-item consistency (a resolved citation with no matching Library link, or one pointing at the wrong link); Library-item↔︎canonical-work consistency (a stale pointer into an identity that has since been merged away, correctable to the resolved winner via the merge-chain resolver); graph-node↔︎canonical-entity consistency (report-only — deliberately never repairs the same fact the prior check already owns, to avoid two code paths applying the same patch twice); graph-edge endpoint validity (a dangling edge, since graph_edges has no real foreign key — repaired by deletion, since there is no canonical replacement endpoint to guess); annotation↔︎related-work consistency (a stale cached title-label resynced from its own resolved foreign-key target); roadmap-item target canonicality (a roadmap override, reading record, or understanding rating still pointing at a non-canonical duplicate record); reader-source↔︎citation consistency (a citation’s cached processing-run id resynced from its own real foreign-key chain); RAG-citation anchor integrity (the one security-sensitive check — verifying a cited chunk’s owner matches the citing conversation’s owner, with no repair path other than deletion, since substituting any other chunk for a privacy-boundary violation would itself be a guess); and title/author/year agreement (a broad, mostly report-only check between a work and its canonical identity, deliberately never auto-repaired in the work-title direction, since a work’s own uploaded-document title is legitimately allowed to differ from an aggregated canonical title — for instance across translations — and overwriting either side “would risk destroying real, user-supplied data to chase an aggregate that isn’t necessarily more correct for this row”).

runAllConsistencyChecks(snapshot) runs all nine checks in a fixed order and concatenates their findings, and this exact function is what both a plain report run and the actual repair-mode runner both call — guaranteeing “what would be reported” and “what a repair pass acts on” can never diverge into two separately-maintained code paths. The worker-side runner (apps/worker/src/consistency/run.ts, described in §9) applies any repairs inside a single database transaction, so a partial failure mid-batch can never leave the database in a state worse than either fully pre- or fully post-repair, and it re-runs the full check pass after applying repairs to print a genuine, empirically verified before/after comparison rather than merely asserting success.

Part I · Software — 07 / 12

Ask Library — Retrieval-Augmented Generation

Deterministic chunking and lexical retrieval, the Socratic prompt and its grounded-citation validation gate, and the separate conversational-competency-inference feature layered on the same discipline.

@ice/rag is the pure-package home of Palimnote’s “Ask Library” retrieval-augmented chat logic and, as of sub-phase 22.9, the separate Conversational Competency Designation feature. It keeps its pure text/prompt/validation utilities free of any database dependency, importing @ice/db and Drizzle only lazily, inside the specific functions that actually need one, so the rest of the module stays usable and unit-testable without a live Postgres connection.

7.1 Chunking and lexical retrieval

chunkText(text, maxChars=1400) is a deterministic, paragraph/sentence-boundary-aware splitter. It walks forward in fixed-size windows, but where a window doesn’t reach the string’s natural end, it looks inside the window for the last occurrence of a paragraph break, a sentence-ending period, question mark, or exclamation mark, and honors that boundary only if it falls at least 45% of the way through the window — preventing an over-early boundary from producing a tiny sliver chunk. Deliberately, chunks never overlap: the module’s own documentation states this choice keeps the source location clear and retrieval cheap at the project’s current single-user scale, and it means a chunk boundary always corresponds to a real place in the source text, never a model-invented passage boundary — so downstream citation offsets are always trustworthy. ragContentHash(text) is a SHA-256 hex digest used both as the persisted contentHash column and as a dedupe key associating a chunk with its (optional) embedding result.

lexicalScore(query, content) is the deterministic retrieval-ranking baseline: no embeddings or vector math, a hand-built term-frequency-like score (each query word’s occurrence count capped per-word at three, so one dominant repeated term cannot swamp the score), a flat phrase-match bonus for a long query appearing verbatim in the content, and a square-root length-normalization denominator so long chunks are not unfairly favored merely for containing more incidental term occurrences. An empty or stop-word-only query returns a score of zero for every row. rankLexically(query, rows, limit) filters out any row scoring zero or below before ranking — the module’s own documentation states plainly that “a zero-score row is never smuggled into a response simply to make an answer.”

7.2 Indexing and retrieval against the database

indexEligibleRagSources(input) is the top-level “rebuild one owner’s RAG index for one newly published processing run” operation, called by the worker at the end of edition extraction. It fetches eligible uploaded-document text blocks (explicitly excluding footnote/endnote/bibliography block kinds from RAG indexing — those are apparatus, not indexable prose) and eligible open-access research content (requiring both non-blank text and a non-blank license before considering a source at all — a second, code-level enforcement of the eligibility rule beyond the database’s own status filter). Chunks are capped globally at 256 per document, with only the first 32 selected chunks sent to an embedding provider if one is configured; an embedding failure on any individual chunk is swallowed and never blocks or corrupts the index — “lexical retrieval remains valid and honest if an embedding provider is unavailable. Do not fail source indexing or fabricate a vector.” The database write is transactional and deliberately deletes the document’s prior chunks first, then re-inserts — a full wipe-and-rebuild, not an incremental diff — because, in the module’s own words, “deleting the old document rows first makes reprocessing fail closed: it can leave fewer retrievable chunks, but can never answer from a superseded private run.”

retrieveOwnerRagChunks(userId, query, limit) is the single entry point turning a reader’s question into a ranked, owner-scoped, citation-ready chunk set. Owner scoping is enforced as a first-class SQL predicate in the query itself, not a post-fetch filter — the module’s documentation states this explicitly, since it is what makes cross-user leakage structurally impossible for this query rather than merely policy-enforced. A trashed work’s chunks remain physically present (for restore) but are excluded from retrieval via the same query’s deletedAt IS NULL predicate. Ranking is delegated entirely to rankLexically — despite rag_chunk.embedding existing in the schema, this function performs no vector-similarity step at all; the project’s own log records this as an explicit, stated design boundary (“RAG answer ranking intentionally retains its evidence-required lexical baseline”), not an unreleased feature.

canonicalWorkDisplayTitles(userId, workIds) resolves, for a set of works, the single canonical display title each should present under, so two separate uploads of the same underlying work — sharing one canonical work identity — always cite under one name rather than confusingly presenting as two different sources; the representative is chosen deterministically by earliest creation timestamp with an id tiebreak.

7.3 The Socratic prompt, grounding, and validation

SOCRATIC_SYSTEM_PROMPT instructs the model that it must answer only from supplied retrieved passages; that it must treat both the reader’s question and every retrieved passage as untrusted data, never as instructions, and must never follow requests embedded inside a passage; that it must use a concise Socratic method — state what the cited evidence supports, then ask one useful question that helps the reader inspect it; and that when the passages do not support an answer, it must return an explicit not-found response rather than guess.

buildSocraticInput(input) wraps every retrieved passage in an XML-like <passage id=... source=... title=...> tag explicitly labeled as untrusted quoted source material, and formats the last six turns of conversation history as “untrusted conversation text” — this is the prompt-injection defense layer, since a retrieved passage is, in the general case, text drawn from a user’s own uploaded document, which is itself untrusted content from the model’s point of view.

validateSocraticAnswer(parsed, allowedChunkIds) is the grounded-evidence gate: it throws on any malformed shape, but its most consequential check is that every cited chunk id must be a member of the chunks actually retrieved and shown to the model this turn — a citation can never reference a chunk that was not part of this turn’s real, database-fetched, owner-scoped evidence set. A further rule requires that any answer not flagged notFound must carry at least one citation — a substantive answer can never be presented as grounded without a real source.

fallbackSocraticAnswer(question, chunks) is the deterministic, zero-cost, non-AI fallback: with no chunks retrieved, it returns a fixed not-found message; with chunks available, it quotes the single top-ranked chunk verbatim (truncated to 460 characters) inside a templated Socratic-style prompt-back — guaranteed available, guaranteed non-hallucinating, since it only ever quotes text that was actually retrieved.

7.4 Conversational Competency Designation (competency.ts, sub-phase 22.9b)

This is an architecturally separate feature, inferring a reader’s self-reported familiarity with specific works or concepts from what they say in an Ask Library chat message, so the app can adjust roadmap and mastery signals without an explicit quiz — but mirroring the same purity, closed-candidate-set, and grounded-quote discipline as the Socratic chat primitives above.

detectSelfReportedCompetency(message, candidates) is the deterministic, always-on, zero-cost half of the feature. It splits a message into clauses (skipping any clause that ends in a question mark outright, since “never infer from the mere fact that a question was asked” is an explicit design rule), and tests each clause against five ordered, carefully-prioritized pattern groups — unfamiliar patterns (“never read,” “no idea,” “new to”) checked first, specifically so a negated statement like “never really understood” is never mis-caught by a later, more generic “understood” pattern; then struggling; then strong; then partial; then, last and most generic, familiar (“I’ve read,” “I understand”). A clause is only credited to a candidate if the clause also textually mentions that candidate’s own label or alias, and at most one signal is recorded per candidate per message, capped at three signals total.

validateCompetencySignals(parsed, candidates, userMessage) is the model-path validation gate, described in its own documentation as “the chat equivalent of validateSocraticAnswer’s citations-are-a-subset-of-retrieved check.” It requires every signal’s target id to be a member of the server-supplied closed candidate set (never an invented target), its quote to be a genuine, whitespace-normalized substring of the reader’s own message (rejecting paraphrase, not merely exact-byte mismatch), and — a later, sub-phase-22.9b addition motivated by a real reproduced failure — a cross-target confusion check: if a grounded, genuinely-substring quote mentions a different candidate’s own label by name but was bound to this target, the whole response is rejected, since a valid target id plus a genuinely grounded quote is not, on its own, sufficient to rule out the model having bound the right quote to the wrong target. The module’s own documentation candidly records the one residual gap this check cannot close — a grounded quote that names no candidate by label at all (e.g., “I don’t really get any of this”) cannot be cross-checked this way — and states plainly that this gap is intentionally left to the user-facing notice-and-undo affordance as a backstop, not something the validator is asked to catch exhaustively.

COMPETENCY_LEVEL_SCORES maps the five levels (unfamiliar, struggling, partial, familiar, strong) to fixed server-side numeric scores — the model or detector never emits a raw number itself — capped at a COMPETENCY_SCORE_CEILING of 75, deliberately kept below both the existing self-assessment diagnostic’s top tier (85) and an explicit user-set rating’s maximum (100), since chat-derived evidence should never mint expert-grade scores on its own.

Part I · Software — 08 / 12

Shared Infrastructure Packages

The pure deletion state machine, the meta-tooling that governs the AI-agent development process itself, shared release-flag configuration, and the observability seam shared by both applications.

8.1 packages/deletion — a pure permanent-deletion state machine

@ice/deletion was built in Phase 20.3 to replace an earlier “fire-and-forget” purge implementation that could report success while Storage bytes were, in fact, still present. The package contains no I/O at all — every side effect (database reads/writes, Storage deletion, job cancellation) is injected via an effects interface, which is what makes its ordering, failure, and idempotency guarantees testable as deterministic unit tests without a real database or Storage bucket.

The core design invariant, stated in the module’s own header comment, is that deletion is deliberately ordered Storage first, database last: a durable cleanup record is persisted before any destructive step; queued jobs are cancelled so no worker can re-touch the work mid-delete; every private Storage object is deleted; and only then is the work row hard-deleted, letting Postgres cascade the rest. This ordering is what guarantees the specific failure mode the project’s honesty requirement targets — “database rows gone but bytes left behind with no record of it” — cannot happen silently: bytes are only ever removed before the rows that track them, and any Storage failure halts the run in a persisted, retryable storage_failed state rather than being reported as success.

executeWorkDeletion(effects, input) short-circuits immediately if a cleanup record already shows completed (idempotent on repeat calls); otherwise it collects the work’s Storage paths (unioning with any already-recorded pending paths from a prior partial attempt) and persists the collected state before any destructive step, which is the crash-safety anchor — if everything below crashes, the pending paths already survive on disk. It cancels queued jobs; deletes every pending Storage path (attempting every path even after an early failure, retaining only the first error message, and removing successfully-deleted paths from the pending set as it goes, so a retry only re-attempts what’s still outstanding); and only once every path is confirmed gone does it call the database-delete effect. A crash between the database delete and the final “completed” write converges cleanly on retry, since the machine re-runs against an empty pending set and the work-row delete becomes a safe no-op.

8.2 packages/phase-lifecycle — meta-tooling for the development process itself

@ice/phase-lifecycle is not consumed by the product at runtime at all; it is meta-tooling governing the AI-agent development process that built the project. It was built specifically because a prior agent session had, in the project’s own words, “failed closed” without a real way to safely and provably terminate its active model context and hand off to a fresh one at a phase boundary — the package’s header comment states its rationale precisely: “a shell command cannot prove that an agent’s active context was destroyed,” so the package refuses to fake compaction and instead requires a host-supplied adapter that can atomically replace the active session.

PhaseLifecycleController.closePhase(input) enforces, in order: that a phase may only dispatch its immediate successor (never skip or reorder phases); that the handoff prompt seeding the next phase is non-blank; that the phase’s own verification actually passed (the tracker is never advanced on a failed test); and, critically, a host preflight check — if the host cannot prove it can replace the active session, the controller throws before claiming any dishonest success, and it writes its durable bookkeeping (the compact handoff record and the status tracker) before attempting the risky, one-shot context-replacement call, mirroring the same “durable write before destructive step” discipline seen in @ice/deletion.

8.3 packages/config — shared configuration and release flags

@ice/config’s pipeline.ts centralizes which analysis pipeline version a deployment runs, replacing what its own header comment describes as a real production incident: two call sites once independently compared the pipeline-version environment variable for exact string equality, so any unrecognized value — including a genuinely new, valid version — silently fell back to the oldest pipeline and silently disabled edition reprocessing, with no diagnostic anywhere. isEditionPipeline(version) reframes the question from an equality check to an ordered one — “is this pipeline at least the edition pipeline?” — which is the question the code actually needs answered, and parsePipelineVersion logs a one-time warning (not a silent fallback) whenever it encounters a genuinely unrecognized value.

stages.ts is the single source of truth for the ordered, real processing-stage sequences of each pipeline version, shared between the worker (which sets these exact string labels on a processing run’s stage column as it executes) and the web application (which renders them as an honest, step-by-step progress indicator) — the module’s own comment stresses that neither pipeline version is credited with a stage it doesn’t actually set.

phase12.ts, phase18.ts, and phase22.ts each define independently addressable release flags, all following the identical pattern and all defaulting off except a Phase-12 “foundation” flag — and all sharing one explicit design principle stated in each module’s own comment: these are release controls, not authorization controls. Every protected route still performs its own auth and ownership check regardless of flag state; a flag only controls whether a feature is exposed at all, deliberately so a feature’s mere presence in a deployed build can never itself become a security boundary.

8.4 packages/observability — error reporting and structured events

@ice/observability is the single, dependency-light error-and-event reporting seam shared by both applications, following the same “adapter plus fallback” pattern used elsewhere in the codebase (the mail provider, the AI classifier): if a real monitoring backend is configured, errors are forwarded there, but regardless, every error is always logged locally in a structured, parseable form — nothing is ever silently swallowed. reportError(error, context) normalizes any thrown value to a real error, logs a structured JSON record, and forwards to an optionally-registered external reporter. reportEvent(event, context) logs an operational metric as a structured, content-free record — explicitly the safe channel for cost, usage, and audit events, distinct from the failure channel, and specifically documented as standing in for a dedicated audit table in places (such as the consistency-repair applier) where building new schema for an audit trail was judged unnecessary given this existing structured-event log.

The package has no dependency on any monitoring vendor’s SDK itself — the actual Sentry wiring lives in the consuming application (apps/worker/src/sentry.ts), which registers itself as the external reporter only if a DSN is actually configured, keeping the worker (and any future consumer) free of that dependency otherwise.

8.5 packages/db — the queue and connection layer (beyond the schema itself)

Beyond the schema described in §3, packages/db/src/index.ts establishes the single shared Postgres connection with {prepare: false} — a deliberate choice tied directly to production infrastructure, since Supabase’s transaction-pooling connection mode (used by the Render-deployed worker) does not support prepared statements. It also bridges Drizzle-managed tables with pg-boss’s separate, Drizzle-unaware internal schema: cancelQueuedJobsForDocuments(documentIds) deletes any non-terminal queued job whose payload references one of the given documents, catching and treating a “relation does not exist” Postgres error as “nothing was queued” rather than propagating it — because pg-boss lazily creates its own internal schema only on first start, so a fresh test database genuinely has no such schema to query yet. This function exists because Postgres’s own cascading delete on a work/document has no visibility into pg-boss’s separate job table, and without it a job left non-terminal at delete time would survive forever and eventually fire a spurious “document not found” error when a worker later dequeued it — a real batch of hours-old stale jobs was discovered draining on local worker boot during a Phase-19 audit before this fix.

queue.ts defines the four named pg-boss queues (extract-text, analyze-work, resolve-citation-metadata, expand-cross-library-graph) and, as of Phase 20.5, the pure decision core for the single idempotent reprocess command. buildPgBossConfig(connectionString) parses the database URL into discrete host/port/user/password fields rather than passing a connection string directly to pg-boss’s underlying driver — a documented fix for a real production incident where the driver’s own connection-parameter merging logic silently overwrote an explicitly-set TLS option whenever a connection string was also supplied, breaking worker startup against the database’s pooler after a credential rotation. planReprocess(input) is a pure function (no database access) that classifies the current queue/run state into one of four actions: reuse an already-queued identical attempt (a double-click protection), recover an orphaned active job left by a dead worker (only when the job is stale and there is no fresher heartbeat proving the run is still genuinely alive), enqueue fresh when nothing is pending, or report a conflict when a live attempt is genuinely running and starting another would duplicate paid AI work.

Part I · Software — 09 / 12

The Background Worker

The pg-boss job consumer that owns everything AI- or network-latency-bound — extraction, the 2,300-line scholarly-analysis engine, and the paid Cross-Library Graph expansion handler.

apps/worker is the pg-boss job consumer deployed on Render. It listens on the four queues defined in packages/db, and the dominant pipeline — the “edition pipeline” (v2/v3/v4) — runs almost entirely inside one extract-text job: extraction, structural parsing, the whole research/classification pass, publication, and (optionally) RAG indexing and cross-library graph expansion. A legacy v1 path still exists for documents that predate the edition pipeline, explicitly guarded off for any document that already owns a processing run.

9.1 Process entrypoint and crash observability (index.ts, sentry.ts)

At startup, the worker sweeps expired research-cache rows, fails any processing run left running by a crashed prior instance, and registers handlers for all four queues — deliberately logging the resolved pipeline version (not the raw environment variable) at startup, since an earlier incident cost three lost production canary runs to a deployment silently running a different pipeline than was assumed. The analyze-work handler is deliberately not gated on the pipeline-version check itself; the guard instead lives inside analyzeWork() (the legacy handler), because a stale job enqueued before a fix, or a worker whose environment disagrees with the web application’s, must still be handled safely by the same code path — moving the guard into the dispatcher would silently reintroduce the exact class of bug the guard exists to prevent. installCrashObservability() registers process-level uncaughtException/unhandledRejection handlers that report structurally and then exit, so Render restarts the instance — added after a real crash whose only trace, before this fix, was a raw stack in a log stream with no structured record.

9.2 Run lifecycle primitives (runLifecycle.ts)

allocateEditionRun(documentId, pipeline) takes a per-document Postgres advisory lock inside one transaction before computing the next monotonic run version — the lock is the entire concurrency guarantee: two concurrent reprocess requests for the same document can never allocate the same version number. publishEditionRun(params) atomically unpublishes every other run for the document and marks the target run published, only overwriting the work’s title/author from newly-detected metadata when autoReady and a detected title are both present — reasoning that user-approved metadata is stronger evidence than a lower-confidence re-extraction. sweepAbandonedRuns(staleMinutes) finds every run stuck running past a staleness threshold and atomically fails it, the affected document, and any stale queued job — deliberately set well above the run’s own one-minute heartbeat interval, so a genuinely live run sitting in one long research stage is never mistaken for a dead one; only a worker whose heartbeat has genuinely stopped is ever swept.

9.3 extraction.ts — the main edition pipeline handler

startRunHeartbeat(runId) updates the run’s updatedAt timestamp every sixty seconds, but only while the run’s status is still running — a raw SQL guard ensuring the heartbeat becomes a safe no-op the instant the run reaches a terminal state, even before the interval timer is actually cleared. This is precisely what makes sweepAbandonedRuns’s staleness detection safe against false positives on a slow but live run.

handleEditionExtraction(documentId) is the largest orchestration function outside analyze.ts. In order: it allocates a run, downloads and validates the file, parses it, and (for text/markdown documents) runs footnote detection against the per-page joined text rather than the already body-only parsed.text, since a documented Phase-16 structural separation means the merged text alone would never see a trailing notes section. It inserts one pages row and its textBlocks per page (tagging any text-layer-recovered block with a lower confidence score, an honest distinction between GROBID-native structure and this pipeline’s own fallback recovery), inserts structural footnotes, and inserts one metadata row recording the winning title/author source and confidence.

It computes a document’s autoReady eligibility via a Phase-20.8 fix documented at length in the code: the prior version used “a published run already exists” as a proxy for “the user confirmed this document’s metadata,” but that proxy over-fires for a never-confirmed document that fails a first processing attempt and then succeeds on a retry, since it would auto-ready off its own accidental prior published run. The document’s own confirmedAt timestamp, set once and only once, by the confirm API route, is the precise underlying fact instead of a proxy for it.

It calls analyzeEditionRun (the actual research pipeline, described below) synchronously, then publishes the run. RAG indexing after publication is independently flag-gated and enforces a hard-coded per-run cost cap on embedding generation via a running reserved-cost tally, throwing before any call that would exceed it — a failed or unavailable embedding provider can never alter the reader pipeline, since durable lexical chunks remain valid regardless. Cross-library graph expansion (v4 only, independently flag-gated) inserts exactly one automatic expansion request per run via a conflict-safe insert keyed on the run id, guaranteeing uploading a document can never silently fan out into an unbounded paid graph operation.

On any failure, the function fails the run and document with the real error message and rethrows (unlike the legacy handler, which swallows its own failures) so pg-boss retries per its configured policy; a finally block unconditionally stops the heartbeat regardless of outcome.

9.4 analyze.ts — the scholarly-analysis engine

This is the single largest file in the codebase (over 2,300 lines) and the heart of the whole scholarly pipeline. A representative sample of its documented design disciplines:

Citations are never silently dropped. createCitationLibraryProjection deliberately runs before any catalogue lookup or AI call at all: it upserts a visible Library-item stub for the exact raw citation text immediately, so that a later provider failure can enrich that stub but can never erase it — “the exact source citation becomes a visible Library item immediately.”

Benign concurrency races are recovered from, never allowed to crash the whole run. ensureCitationRole documents, at length, a real 2026-07-23 production incident where two concurrently-running pg-boss jobs for the same document raced a canonical-identity merge against a citation-role insert, producing a genuine Postgres foreign-key violation on an id that a legitimate concurrent operation had already repointed and deleted. The fix specifically inspects the driver’s nested error code, and on a confirmed benign race, re-resolves the citation’s now-current target and retries once before giving up gracefully with a structured event log rather than an unhandled exception. A separate, similarly-documented incident (research_resourcesonConflictDoNothing insert) addresses two independently-accepted candidates in the same run legitimately resolving to the identical dedup key with different role/evidence data — “no codebase precedent” exists for merging such a conflict, so first-in wins and the collision is logged and skipped rather than crashing the job (a fix that reportedly resolved a defect that had previously crashed the same production run six times in a row).

Relevance is decided before authority, deliberately: the code’s own comment states that “authority answers ‘how trustworthy is this source?’, which is meaningless until ‘is this source about the right thing?’ is settled” — and every discovered candidate is persisted to a permanent audit table, including rejected and quarantined ones, since “deleting them would make the pipeline unfalsifiable” against the project’s own precision/recall evaluation gates.

Cost accounting survives a crash. Because pg-boss retries a failed extraction job up to three times, and each retry re-runs the entire paid pipeline under a fresh run id, an in-memory budget that started fresh at $0 on every attempt would let a crash-looping document silently re-spend up to the hard cost cap on every single attempt. The function seeds its in-memory budget from the sum of prior failed runs’ logged AI spend (bounded to runs after the document’s most recent complete publish, so a successful publish correctly “closes out” a prior crash episode) — and flushes accumulated usage-log rows incrementally, every five new entries, plus unconditionally in a finally block, so a mid-run crash can no longer discard real spend with no ledger row to show for it.

Classification is deferred, not eager, in the modern pipeline. v3/v4 defer relationship classification to the very end of the per-resource loop, after a resource has already earned relevance, creator-evidence, citation-expansion, and credibility signals — and even then, the final classification is run through conservativeInfluenceClassification (from v3.ts), which downgrades a “conceptual influence” verdict to the more cautious “ai inferred” unless the underlying evidence text actually contains an influence-signaling word, specifically preventing an LLM from turning a mere topical resemblance into an unsupported influence claim.

Degraded is a narrow, specific signal, not a catch-all. The run is marked degraded only when it genuinely exceeded its soft cost cap — explicitly not merely because a resource cap was hit or discovery saturated early, both of which the code documents as healthy, intentional stopping conditions. An earlier version that flagged every saturation-limited run as degraded is recorded as having been “crying wolf” against real load-test evidence.

v4.ts supplies the v4-only cross-work signal generation: persistV4WorkSignals embeds a compact per-work summary (title, author, up to sixteen concepts, up to twelve claim/excerpt pairs) only if it has not already been embedded under an identical content hash, checks the budget before any network call, and — on any embedding-provider failure — degrades honestly to “no cross-work vector produced this run” rather than throwing or fabricating a vector; surviving embeddings are compared via plain cosine similarity against every other work the same user owns, and candidate pairs above a 0.25 similarity threshold are persisted (capped at twenty per work) for the later, separately-paid Cross-Library Graph judgment step to consider.

9.5 crossLibraryGraph.ts — the paid Cross-Library Graph expansion handler

expandCrossLibraryGraph(expansionRequestId) re-derives its own cost estimate server-side rather than trusting any client-supplied figure, and fails the request outright if the requested candidate count, hard cap, or cost estimate exceeds any of several independent guardrails — including, for manual-mode requests estimated above one dollar, an explicit requirement that the user has separately confirmed the cost. For each candidate pair, it computes a SHA-256 hash of the exact claim evidence supplied and checks for an already-cached judgment under that exact hash before calling any model — “only unjudged basis hashes can call a provider; cached evidence is returned with no new model work” — and reserves the per-pair cost against the budget before, not after, making the call, since the reservation (not the after-the-fact metering) is what actually prevents a long sequence of calls from ever starting past the documented hard limit.

9.6 Consistency and canonical-identity maintenance tooling

apps/worker/src/consistency/ (snapshot.ts, apply.ts, run.ts) and apps/worker/src/identity/ (merge.ts, dryRun.ts) are standalone maintenance CLI scripts, not queue consumers. identity/merge.ts’s mergeWorkIdentities and revertWorkIdentityMerge implement the fully reversible canonical-identity merge described in §3.10 — the module’s own comment states the crucial invariant plainly: “the loser work-identity row is NEVER deleted,” which is precisely what makes an exact reversal possible at all. auditWorkIdentityDuplicates performs the background duplicate-detection audit itself, explicitly documented as “fetch, plan, decide nothing” — it never mutates the database on its own, only computing and returning what a human-authorized merge pass could later apply. identity/dryRun.ts is a standalone script that seeds a fixture of deliberately duplicate-rich data, runs the real audit against it, and cleans the fixture up unconditionally in a finally block, explicitly performing no merges and no production reads or writes of any kind — a pure demonstration/verification tool for the audit logic, not an execution path.

Part I · Software — 10 / 12

Web App — Business Logic & API Routes

The Next.js application’s server-side tier — graph construction, roadmap computation, the Library, Writer mode, authentication — and the API conventions (ownership-as-404, rate limiting, feature flags) repeated across every route.

The business-logic layer (apps/web/src/lib)

This directory is the Next.js application’s server-side business-logic tier, sitting between the API routes/Server Components and the shared workspace packages. It comprises roughly forty files, grouped here by function.

10.1 The knowledge graph — graph.ts

buildGraph(userId, rootWorkId?) is the single canonical graph-construction function behind both the global and work-scoped Visualization. It assembles works, cited bibliographic records and concepts (with missing/read/unread/reading node state derived from the user’s own reading records and understanding ratings), discovered research sources with provenance, durable resource-role recommendations, and structurally-anchored passage annotations into one typed node/link payload — recomputed fresh on every request, never persisted.

A central, carefully-documented step is the Phase 20.6 canonical collapse: research_resource rows sharing a derived work key (restricted to primary/edition roles) are grouped, and all but one representative are aliased to it, so a cited book that happened to resolve to two separate catalogue records renders as one visual node rather than two — deliberately not applied to reviews, which are instead attached via relation edges rather than collapsed, since a review is not the same work as the thing it reviews. A separate, explicit mergeExternal() upsert function unions provider/provenance arrays across data sources, keeps the better of two conflicting authority scores, and is written specifically to never let a source-type label overwrite a properly-typed bibliographic-record node.

Edges are assembled from six independent sources (explicit citation edges, concept-presupposition edges, synthetic never-persisted document-outline edges for the work-scoped view, discovered-source edges, source-to-source relation edges, and — for the global graph only — the paid, evidence-hashed Cross-Library Graph judgments, filtered to keep only the single latest judgment per directed pair) and then deduplicated by a stable source|edgeType|target key, merging evidence and provenance arrays and keeping the maximum confidence across duplicates. A final pass computes each node’s real in-app destination link, each link’s stable id and directed-vs-undirected flag, and propagates an associatedWorkIds set outward one hop from every work node, so the accessible table and the 3D scene can filter consistently by “which of my own works does this relate to.”

10.2 The reading roadmap and its graph projection — roadmap.ts, roadmapGraph.ts

computeRoadmapCandidates(userId, rootWorkId) runs a single recursive Common Table Expression over the citation graph, rooted at the primary work: the base case is direct work-to-bibliographic-record edges, and the recursive case re-enters the graph by matching a reached record’s normalized title against another owned, non-deleted work’s title, then continues from that work’s own edges — capped at a traversal depth of four and cycle-guarded via an accumulated work-id path. This is the literal implementation of the project’s documented, honest limitation that transitivity (“Kant leads to Hume”) only surfaces when the intermediate work is itself in the reader’s own uploaded library, never a claim about the wider field’s citation graph.

roadmapGraph.ts’s buildRoadmapGraph(userId, rootWorkIds, options) composes the canonical buildGraph() payload with per-root roadmap candidates and the pure, package-level mergeRoadmapsAcrossRoots, then joins the resulting ranked items back onto graph node ids via a documented precedence chain: an exact bibliographic-record match first, then any record collapsed into that item during duplicate-merging, then the item’s own matched owned work, and only as a last, explicitly-flagged fallback, a normalized-title match against a precomputed label index — the roadmap’s own “why this, here” explanation surfaces this fallback basis so a rare homonym mis-merge stays inspectable rather than silent. An item matching no graph node at all is simply skipped from the annotated graph (it remains in the plain roadmap list) — an accepted, honest gap rather than a forced, potentially wrong match.

10.3 The Library — library.ts, librarySearch.ts

getLibrary(userId, options) reads from the durable, cross-run learning_resource/resource_role tables (populated only once a modern-pipeline analysis run has established a work’s canonical identity), returning an honestly empty shelf — not an error — for a user whose works have no identity yet. Credibility for a (work, resource) pair is joined by matching a resource’s normalized key scoped specifically to the user’s own work ids, deliberately avoiding a raw string comparison that could compare against an unrelated identity. The same Phase-20.6 canonical-collapse logic seen in graph.ts is applied again here, at the Library-list level, so a book, its review, and its second edition present as one entry with the others attached beneath it. librarySearch.ts exists as an entirely separate, dependency-free module purely so a client component can safely import real (not merely typed) search-matching logic without dragging the database driver into the browser bundle — a bundle-boundary problem documented explicitly in the module’s own header comment. Its hasReaderLevelSignal(items) function encodes a candid, dated observation: as of the writing of that module, essentially every resource_role row in production carries a null reader level, so a level filter over entirely-null data would be a mathematically correct no-op, not a defect — the Library UI accordingly only renders the level filter when this function returns true, replacing it with an honest inline note otherwise, and the filter will reappear automatically the moment any future write path actually sets a real level, with no code change required.

10.4 The edition payload — edition.ts

getPublishedEdition(documentId) assembles the entire published-edition reader payload in one batched, largely Promise.all-parallelized read: pages and blocks, authorial and AI-generated notes with claim-level evidence, research resources with credibility, canonically-grouped work display, resource relations, the three passage-annotation buckets (anchored, whole-work, and reader-hidden), term variants, and apparatus. A documented de-duplication step computes a footprint of every modern, block-anchored apparatus entry and filters the legacy footnote table down to only entries with no modern equivalent, so a v4-run document never shows the same authorial footnote twice under two different representations.

10.5 Retrieval-augmented chat and competency inference — ragData.ts, competencyData.ts

answerRagConversation(input) loads the owned conversation, persists the user’s question, retrieves owner-scoped chunks, generates an answer (gated behind an explicit provider-enabled flag, a per-response cost cap, a shared daily spend cap, and a latency cap, degrading to the deterministic fallback on any failure), persists the assistant message and its citations, and — deliberately after the answer has already been generated, so the shared cost pool reflects the Socratic answer’s own spend first — kicks off (without awaiting) the competency-inference pipeline as a background promise the caller races against a short timeout. currentRagSpend(userId) is exported specifically so competencyData.ts can gate its own spend against the same combined daily total — the two chat sub-features deliberately share one pool rather than each getting an independent budget.

processCompetencySignals(input) in competencyData.ts wraps everything in a try/catch that always resolves rather than throws (a competency-inference failure must never fail the underlying chat answer); merges the always-on deterministic detector’s findings with the gated model’s findings (the detector’s result for a given target always wins over the model’s, since it is free and deterministic — the model only fills gaps the detector didn’t find); suppresses any signal the user has already explicitly undone earlier in the same conversation; enforces a daily cap on applied writes, not raw detections, silently dropping signals beyond the remaining allowance rather than queuing them; and, for each surviving signal, applies it transactionally alongside its own audit-ledger row, checking the mastery-source precedence chain first and recording a skipped_precedence ledger entry (never overwriting a stronger existing signal) when it should not apply.

10.6 Trash and permanent deletion — trash.ts

deletionEffects(userId) supplies the real, database- and Storage-backed implementation of the pure @ice/deletion state machine’s injected effects interface described in §8.1 — including the explicit deletion of polymorphic graph_edges referencing the work, since Postgres cannot cascade a relation that carries no real foreign key. retryPendingCleanups(userId) defends against a subtle correctness trap: before resuming a persisted, retryable cleanup record, it checks whether the underlying work has, in the meantime, actually been restored by the user — if the work row still exists and is no longer trashed, the stale cleanup record is deleted rather than resumed, so a restored work is never accidentally re-deleted by a lingering retry. purgeExpiredTrash(userId) runs opportunistically at the top of the trash page’s own load, rather than as a scheduled job — the project’s own documentation states this was a deliberate choice, since trash is pure web CRUD with no worker or AI involvement that would otherwise justify standing up new scheduling infrastructure.

10.7 Writer mode — writer.ts, writerData.ts, writerExport.ts

writer.ts is pure, framework-free domain logic with no database import at all: a strict Zod schema for the ProseMirror document shape (bounded to 10,000 blocks and one million total characters); CSL-JSON normalization tolerant of many real-world field-name aliases; MLA in-text and Works Cited formatting; and hand-rolled BibTeX and RIS parsers.

writerExport.ts builds real binary exports without any external PDF or DOCX library dependency: createWriterPdf hand-assembles a minimal, spec-valid PDF from scratch (escaping special characters, wrapping long lines, building the content-stream operators, and manually writing the numbered objects, cross-reference table, and trailer with correct byte offsets); createWriterDocx builds a minimal, valid Office Open XML document by hand-implementing an uncompressed ZIP container — including its own from-scratch CRC-32 implementation — the module’s own comment noting that “uncompressed ZIP is enough for a portable, standards-compliant DOCX.”

10.8 Authentication — auth.ts, auth-service.ts, actions.ts, auth.d.ts

auth.ts configures Auth.js v5 with a Credentials provider and JWT sessions, a choice forced by the fact that Auth.js only auto-wires database sessions for OAuth-adapter flows, not Credentials — session revocability is instead achieved through the users.sessionVersion counter checked on every request inside the jwt callback: if the currently-stored session version no longer matches what the token was stamped with (for instance, after a password reset incremented it), the callback returns null, invalidating that JWT immediately despite JWT sessions having no natural server-side revocation mechanism of their own. auth.d.ts is a pure type-only file fixing a genuinely subtle TypeScript footgun: augmenting the wrong re-exported module (rather than the module that actually declares the Session interface) silently creates an unrelated interface that never merges with the real one, leaving session.user.id typed as possibly undefined everywhere with no compiler error to catch it.

auth-service.ts implements registration (silently no-op on an already-registered email, so registration can never be used to enumerate existing accounts), email verification, and password reset — the reset path is where sessionVersion is actually incremented, in the same database update as the new password hash, which is exactly the mechanism auth.ts’s jwt callback checks.

actions.ts implements the corresponding Next.js Server Actions, each applying the same pre-authentication rate limits as their sibling API routes and redirecting to a deliberately generic, indistinguishable error state on either a validation failure or a rate-limit rejection — anti-enumeration by construction, not merely by convention.

10.9 Rate limiting — apiRateLimit.ts, preAuthRateLimit.ts

apiRateLimit.ts’s enforceUserRateLimit is a database-backed, cluster-wide, fixed-window counter for authenticated routes, with a deliberate fallback: if the upsert throws an error matching a “relation does not exist” pattern (the additive rate-limit migration has not yet reached this particular database), it degrades to an in-process, per-instance counter rather than turning every rate-limited route into a server error during a rolling deployment — logging a one-time warning per scope so the fallback state is still visible without spamming.

preAuthRateLimit.ts is a separate, purely in-memory limiter for the handful of routes that have no authenticated user id to key a limit by (registration, password-reset request, password reset itself), keyed instead by a platform-set client IP header that cannot be spoofed by the requester, with an explicit, documented eviction strategy once its tracked-key count crosses ten thousand — first evicting anything provably stale (older than two hours), and only if that alone is insufficient, evicting the oldest entries down to ninety percent of the cap, an accepted best-effort trade-off the module’s own comment records candidly as something a real, durable, cluster-wide table would eventually need to replace.

10.10 Smaller, single-purpose utilities

citationResolver.ts provides a lighter, Writer-mode-specific DOI/ISBN/title lookup against Crossref and Open Library, independent of the worker’s own bibliographic pipeline. annotations.ts loads a legacy document’s annotations joined to their resolved bibliographic target, computing an isHeuristic flag from the stored prompt version so the UI can honestly distinguish a real model verdict from the deterministic fallback. graphEdgeCategory.ts’s deriveEdgeCategory fills in a missing relationship category for exactly two, and only two, edge-writing code paths that are provably unambiguous by construction — the module’s own comment stresses this is an exhaustively verified mapping, not a keyword heuristic, and warns explicitly against extending it by guessing. graphExpansion.ts computes the same cost-estimation function the worker’s execution path independently re-derives, so a client-shown preview and the server’s actual guardrail can never silently disagree. ragConversationClient.ts’s loadOrCreateRagConversation fixes a real, documented production defect where a localStorage-persisted conversation pointer that stopped resolving (for instance, because the owning test account was deleted during routine cleanup) permanently disabled the chat panel for that browser; the fix treats a 404 on a stored id exactly like “no id was ever stored,” transparently creating a fresh conversation, while still allowing any other kind of error (a genuine network failure, an authentication failure) to propagate untouched rather than being silently treated as “gone.” v4Backfill.ts computes a deliberately read-only, dry-run cost forecast for backfilling existing documents through the newer v4 pipeline, explicitly never enqueuing any real work itself. cost.ts groups a run’s existing AI-usage log rows by stage and task for the reader’s itemized cost breakdown, adding no new tracking of its own. mail.ts, like the AI-provider layer, follows the adapter-plus-honest-fallback pattern — falling back to console logging, never silent failure, when no real email provider key is configured. works.ts’s two ownership-resolution helpers, getOwnedDocument and getOwnedWork, are the single most repeated pattern in the entire API layer, discussed further in §12.

API routes (apps/web/src/app/api)

The API layer follows a small number of consistent, repeated conventions rather than a different pattern per route, which is itself worth documenting before the routes themselves.

11.1 Shared conventions

Ownership and IDOR posture. Almost every protected route resolves ownership through getOwnedDocument(workId, userId) or getOwnedWork(workId, userId) (§10.10), both of which return null — never a distinguishable “forbidden” value — for a resource that exists but belongs to someone else. Every caller uniformly turns a null into a 404, so a request can never distinguish “this does not exist” from “this exists but is not yours.” The same posture governs the admin area (requireAdmin() returns a plain 404 for a non-admin, not a 403, so the very existence of the admin surface is not revealed) and every phase-gated feature (a disabled feature 404s rather than 403s, so its existence is not revealed either).

Rate limiting. Two independent mechanisms are used depending on whether a route has an authenticated user to key on: enforceUserRateLimit() (database-backed, cluster-wide) for authenticated, cost-bearing, or abuse-prone routes, and preAuthRateLimit() (in-memory, IP- or email-keyed) for the handful of pre-session routes. Composed guards — requireWriterApiUser(), requireRagApiUser() — bundle a feature-flag check, an authentication check, and a rate-limit check into one reusable call.

Feature flags from @ice/config gate whole route families (Writer, RAG, cross-library graph expansion, conversational competency) to a 404, not a 401 or 403, when the corresponding release flag is off.

11.2 Authentication routes (api/auth/*)

The Auth.js catch-all route re-exports the framework’s own handlers directly. register, request-reset, and reset-password each apply the pre-authentication rate limits described in §10.9, and each is deliberately engineered so its response shape is identical regardless of whether the underlying account exists — registration and reset-request both always return a generic success acknowledgment, closing the account-enumeration channel that a differing response would otherwise open. verify-email is a browser-navigated redirect endpoint, not a JSON API — it always redirects, either to a login-success state or a verification-error state, never returning a raw JSON error to a clicked email link.

11.3 Work lifecycle and processing (api/works/*)

GET /api/works lists the caller’s own ready works. DELETE /api/works/[workId] soft-deletes (sets deletedAt, no cascade, no Storage touch — a reversible, idempotent action). GET .../status is the reader’s polling endpoint, computing an honest stalled flag on read (never via a scheduled sweep) by comparing the latest run’s heartbeat age against a documented threshold. POST .../analyze and POST .../confirm gate concurrent double-runs and, in confirm’s case, contain a documented D-23-6 fix: it checks whether a processing_runs row already exists for the document (a data-driven, not environment-variable-driven, signal that the modern pipeline already ran its own analysis) before deciding whether to enqueue the legacy analyzer at all — enqueuing it unconditionally would silently wipe an edition document’s richer, run-scoped citation set. POST .../reprocess is gated to edition-pipeline documents only and delegates its entire concurrency/idempotency decision to the pure planReprocess() function described in §8.5, translating its four possible outcomes (reuse, recover, enqueue, conflict) directly into the HTTP response. POST .../purge and POST .../restore implement the honest, retryable permanent-deletion contract from §8.1/§10.6 — a purge response is only ever reported successful when every Storage byte is actually confirmed gone.

The reader/* sub-routes (highlights, bookmarks, notes, annotations, passage-annotations, position, terms) implement the full CRUD surface for the annotated reading experience, each independently ownership-scoped; passage-annotations in particular resolves ownership through a run-to-document-to-work chain rather than a direct owner column, since passage annotations belong to one document, not one user, by their own schema shape. The upload/* sub-routes (init, complete, proxy, and a legacy single-request upload) implement the multi-phase, security-hardened upload pipeline: init reserves quota and creates a signed direct-to-Storage upload URL; complete re-verifies the actual Storage-reported byte size rather than trusting the client’s own declared size — closing a documented, real quota-bypass path — before atomically flipping the document into the processing queue, guarded so a duplicate completion call cannot double-enqueue; proxy is a same-origin fallback for browser environments that block the direct signed-URL PUT, itself bounded to a small byte cap consistent with the platform’s own request-body limit.

11.4 Roadmap, curriculum, diagnostic, and graph (api/works/[workId]/{roadmap,curriculum,diagnostic,graph}, api/graph)

The roadmap and curriculum routes are thin wrappers around the pure computation packages described in §6 and §10.2 — nothing here persists anything beyond the durable user-authored profile inputs. POST .../roadmap/item performs a partial, field-by-field upsert (understanding score, reading status, and/or a manual override are each independently optional) with no underlying database unique constraint, resolved instead at the application level as a find-then-update-or-insert. GET .../diagnostic opportunistically infers a weak mastery score from other already-completed prerequisite works, but only ever fills a genuinely empty slot — an existing rating, of any source, is never overwritten by this inference. Both api/graph and api/works/[workId]/graph support an identical ?layout=roadmap opt-in that switches the response to the roadmap-annotated projection from §10.2 without changing the default, unannotated response shape at all — a deliberate backward-compatibility guarantee for existing clients and tests.

11.5 Library (api/library/*)

GET /api/library performs the server-authoritative search described in §10.3 — search is never merely a client-side filter over an already-downloaded list. POST /api/library/[resourceId]/status upserts reading state against the shared, un-owned resource catalog, checking only that the resource genuinely exists (not that the caller “owns” it, since the catalog itself has no owner column).

11.6 Ask Library (api/rag/*)

POST /api/rag/conversations/[id] streams its response as Server-Sent Events: a user event echoing the persisted question, tokenized delta events building the answer incrementally, citation events per resolved source, up to several seconds’ worth of competency events (raced against a timeout so a slow or failing competency pass can never delay or break the underlying chat answer), and a final done event. POST /api/rag/competency-signals/[id]/undo is engineered to be genuinely idempotent (undoing an already-undone signal is a safe no-op) and fails closed with a conflict response, rather than silently overwriting, if the signal’s current state is neither “applied” nor “already undone” — restoring a stale prior value over a newer one would itself be a data-integrity bug.

11.7 Writer mode (api/writer/*)

Every route resolves ownership through project- and document-scoped helpers that require both a project and (where relevant) a document id to match the caller, so no route ever grants access by a bare document identifier alone. The export route streams a genuine binary DOCX or PDF produced by the hand-rolled generators in §10.7, with Cache-Control: no-store so a generated export is never inadvertently cached. Citation import supports four distinct input shapes (an owned Library resource, a DOI/ISBN/title lookup, or pasted BibTeX/RIS text) behind one discriminated-union request body.

11.8 Admin (api/admin/*)

The one route here, pipeline-v4/backfill-forecast, is deliberately read-only: it returns a cost/scope projection for a hypothetical v4 backfill and never itself enqueues any real reprocessing — a genuine dry run, not a trigger with a misleading name.

Part I · Software — 11 / 12

Web App — Pages & Components

The Reader’s dual immutable/interactive views and quote-anchored highlight system, and the 3D knowledge-graph visualizer built so its WebGL scene and accessible table can never disagree about what is visible.

Pages — Reader, Roadmap, Curriculum, Graph, Library, Admin, Auth

12.1 The app shell and work lifecycle

The authenticated area’s root layout is the single centralized authentication gate (superseding an earlier, more error-prone per-page pattern): it requires a session, resolves admin status via an environment-variable email allowlist, and loads the caller’s workspace preferences before rendering the shared app shell. works/[workId]/page.tsx and its client-side companion, WorkStatusPanel, together implement the work’s full lifecycle as a five-state machine driven by the document’s own status and its deletedAt field: a trashed panel with an undo action; a polling panel showing an honest, discrete step list built from the pipeline’s own real stage sequence (@ice/config, §8.3) rather than a fabricated percentage, with a documented stalled-processing sub-state surfaced only when the server itself reports it; a failed panel explaining that the original file and any previously published edition remain safe; a metadata-confirmation form; and a ready panel with reprocess, trash, and navigation controls. The polling mechanism deliberately uses a repeating interval timer rather than a one-shot timeout specifically because the effect’s own dependencies do not change while a document remains stuck processing, so a one-shot timer would never re-arm itself.

12.2 The Reader

The Reader is the largest and most feature-dense single client component in the application (ReaderShell.tsx), orchestrating: the toggle between the immutable “Published edition” view and the processed “Interactive reader” view (with the interactive view as the Phase-16 default whenever an edition exists); highlight, note, bookmark, and annotation CRUD, each optimistically updated locally before the server round-trip resolves; an analysis-status polling loop absorbing the brief window between metadata confirmation and worker pickup; a fully independent second reader pane for split-view reading of two works at once, deliberately disallowed from nesting a third; debounced reading-position persistence; and a documented set of narrow-viewport behaviors (closing all sticky side rails the instant the window crosses into narrow mode, so a previously-open column never snaps into an unrequested full-screen drawer).

Two distinct text-rendering surfaces exist beneath the shell: TextReader/OriginalTextReader for the immutable plain-text/Markdown source, and PdfReader for the immutable original PDF (rendered client-side via pdfjs-dist, with its worker script vendored as a static file specifically because the project’s bundler could not reliably resolve it dynamically). EditionReader is the substantially larger processed/interactive surface, rendering one continuous transcript from the modern edition payload, with margin notes that reveal on hover or focus, inline verified-term rendering (swapping between original script and transliteration per the user’s own display preference), a selection-driven toolbar offering highlight/new-note/link-to-existing-note actions, and a documented, shared computeOutline(blocks) function reused verbatim by both the persistent outline sidebar and this component’s own jump-to-section navigation, specifically so the two can never disagree about a document’s own section structure.

The highlight and annotation DOM layer (highlightDom.ts) is the concrete client-side implementation of the project’s text-fingerprint anchoring discipline (§3.4): findQuoteOffset tries an exact prefix-plus-quote-plus-suffix match first, and only falls back to scoring every raw occurrence of the bare quote by how well its actual surrounding context matches the stored prefix and suffix when the quote appears more than once in the document. Highlights and annotation markers are applied as two deliberately independent DOM layers — highlights wrap ranges of text, while annotation markers are single-point insertions — specifically so the two can never collide over the same span of overlapping text. A companion module, matchNoteToBlock, recomputes a conservative anchor for an AI-generated critical note (which carries only an evidence quote, never a database-enforced block id) at render time, and accepts a match only when it is exactly one — zero or multiple candidate matches both leave the note sidebar-only rather than forcing an anchor onto a passage it may not genuinely belong to.

The reader’s Ask Library panel (RagChatPanel.tsx) implements the client half of the retrieval-grounded chat described in §7 and §10.5, including a documented self-healing fix for the stale-conversation-pointer defect described in §10.10, and renders competency notices as quiet, collapsible, fully undoable lines carrying no “AI”/“model”/“detected” language at all, per the owner’s explicit display-language directive recorded in the project’s own log.

12.3 Roadmap and Curriculum

RoadmapView.tsx renders the priority-tiered plan described in §6.1, grouped by tier, with every reader-level and depth-mode filter framed explicitly as a page-local view preference — the component’s own documentation states plainly that “browsing alone never silently changes a level,” meaning none of these filters ever writes back to the user’s saved global reader-level preference. A collapsed “add a reference” control lets the reader search the shared bibliographic catalog and add an item the automatic traversal never reached; every mutation to an item’s rating, status, tier pin, or position pin is followed by a full reload of the roadmap from the server, rather than a local patch, specifically so the displayed ranking always reflects the server’s real, authoritative recomputation. CurriculumView.tsx follows the identical mutate-then-reload pattern for its own route (minimal/university/graduate) and reader-level filters.

12.4 Visualization

Both the global (/graph) and the per-work (/works/[workId]/graph) Visualization routes are thin server-component shells that mount the shared GraphView component (documented in full in §13) against the appropriate API endpoint — no independent graph logic lives in either page itself.

12.5 Library, Trash, Admin, Upload, Dashboard, Welcome, Writer

LibraryView.tsx implements server-authoritative, debounced search; reading-status tabs with live counts; a dismissible, purely-inferred “suggested reader level” nudge that only ever changes the saved profile on an explicit user click, never silently; and per-item related-record display for canonically-collapsed entries. TrashView.tsx and PermanentDeleteDialog.tsx implement the honest, retryable deletion contract described in §8.1 at the UI layer — a permanent-delete response is only ever treated as successful when the server explicitly confirms it, and any other outcome (including a caught network exception) is surfaced as “could not finish, recorded and will be retried,” never papered over. High-value works (multiple editions, or an already-ready document) require the reader to type the exact title before the destructive action enables — the one dialog in this entire subtree implementing a full manual keyboard focus-trap loop, appropriate given the severity of the action it gates.

The admin dashboard is entirely read-only: platform counts, per-model AI cost and usage, processing-job failure history, the same dry-run v4 backfill forecast the API route exposes, per-pipeline research-run statistics, a per-provider availability pivot table, the deletion-cleanup queue (explicitly never reported to the owner as a completed deletion until it genuinely is one), and the Phase-8 relevance gate’s own review breakdown — including a “displayed precision” figure that is shown as an explicit dash, not a fabricated 100%, whenever nothing has actually been judged yet.

The multi-file upload page processes files sequentially, not concurrently, as independent works, pausing the whole batch on any individual duplicate-file detection for an explicit user decision (open the existing work, skip this file, or add it as another edition) before continuing. The Welcome/onboarding page is explicitly, fully skippable — its own copy states this directly — and only ever changes what opens by default, never what is reachable.

12.6 Auth pages and static policy pages

The login, signup, and password-reset pages each mirror their API-route siblings’ exact rate-limiting behavior and, in every case where account existence could otherwise be inferred from a differing response, present a deliberately identical, generic outcome regardless of whether the underlying account exists, was rate-limited, or the input was simply invalid. The Privacy and Terms pages are static, unauthenticated content stating the project’s own privacy and copyright posture directly: uploaded content is isolated per user; bibliographic facts are asserted to come only from real lookups, never invented; content is never used for model training without a separate, explicit, default-off opt-in; and the product is framed throughout as a research aid whose every generated claim can be corrected, disputed, or hidden by the reader, not a source of settled scholarship.

Shared UI components and the 3D knowledge-graph visualizer

13.1 The app shell (components/app)

AppShell.tsx is the authenticated application’s outer chrome: primary navigation, a mobile drawer, the workspace-preferences popover, and a focus mode that marks the page header inert and moves keyboard focus explicitly to a visible exit control on entry and back to the preferences trigger on exit — a deliberate, repeated focus-management pattern used throughout the codebase’s dismissible surfaces. CommandPalette.tsx implements the global search dialog (opened by a keyboard shortcut or a dispatched custom browser event), lazily fetching the user’s own works only once per session and implementing a manual keyboard focus trap. WorkspacePreferencesProvider.tsx maintains the cross-page theme/font/reading-width/focus-mode state with dual persistence — to local browser storage immediately, and to the server via a keepalive fetch specifically chosen so an immediate page navigation right after a preference change cannot abort the in-flight save before it reaches the database. PreferenceBootstrap.tsx emits a small inline script that runs before React hydrates, reading the stored preferences and setting the corresponding attributes directly on the document root, eliminating a flash of un-themed content.

13.2 The 3D graph visualization (components/graph) — the largest and most architecturally significant component family

types.ts is the single typed data contract every other file in this family imports from — not a component itself, but the load-bearing shared source of truth. It defines node and link shapes, a fixed color/label table for every node state and edge-relationship family (all resolved through CSS custom properties for theme support, and always paired with a text label, never color alone — an explicit accessibility discipline), and, most importantly, filterGraphData(data, filters, pinnedWorkIds) — the one filtering implementation that both the 3D scene and the accessible table both consume, so the two views can never disagree about what is currently visible. It filters nodes first and then drops any link whose endpoint was filtered away, guaranteeing the resulting graph never contains a dangling edge, and it deliberately exempts the reader’s own uploaded works from most attribute filters (only the “associated work” filter or explicit pinning can scope them out), so a reader’s own library never silently vanishes from view.

GraphView.tsx is the page-level orchestrator (over 1,300 lines): it owns data fetching, URL-synced filter and selection state, roadmap-versus-explore layout mode, and renders the 3D scene, the accessible table, and a shared inspector panel side by side. A single derived displayed value, computed once via filterGraphData (and, in roadmap mode, further narrowed via roadmapSubset), is passed identically to both the 3D component and the accessible fallback — the component’s own documentation is explicit that this is what guarantees the two views can never show a different set of nodes.

KnowledgeGraph3D.tsx is the actual WebGL scene, built on react-force-graph-3d, dynamically imported with server-side rendering disabled. Its central architectural constraint, documented at length in its own header comment, is that changing the identity of the library’s own node/link rendering callbacks forces a full rebuild of every Three.js object in the scene — so selection, pinning, hover, and “next up” visual accents are deliberately applied by mutating already-created scene objects’ scale and visibility in a post-creation pass, never by changing the callback identities themselves. Node and edge colors are resolved from CSS custom properties at runtime (re-resolved whenever the page’s theme attribute changes, via a mutation observer) rather than hardcoded, so the 3D scene stays correctly themed across light and dark mode without any special-casing. Motion is capped by the browser’s own reduced-motion preference and additionally throttled for large graphs regardless of that preference; the component’s own camera-framing logic replaces the underlying library’s own “fit to view” function with a from-scratch, unit-tested trigonometric replacement, after the library’s version was found — via a real reproduced blank-canvas bug — to be measurably wrong (roughly 2.5 times over-zoomed) and always aimed at the world origin rather than the actual content’s center.

GraphAccessibleFallback.tsx is the mandatory, non-optional accessible table — never an afterthought or an alternate mode, but, per the project’s own stated design principle, the default-equal view. It is a fully sortable, keyboard-operable table over the identical filtered data the 3D scene renders, with roving keyboard focus, aria-sort on every sortable column, and arrow-key navigation between connected nodes bound directly on each row so a keyboard user never needs to leave the table to explore graph structure.

graphFocus.ts and graphSceneScaling.ts are pure, DB- and DOM-free modules holding, respectively, the focus/emphasis/dimming logic shared between the 3D scene’s fade behavior and the table’s data-emphasis attribute, and the camera-distance-to-scale math (including the from-scratch camera-fit replacement mentioned above) — both independently unit-tested without any WebGL context at all.

roadmapLayout.ts computes the fixed, deterministic stage-column layout used in roadmap mode: nodes are placed into columns by pedagogical stage and rows by priority tier and reading sequence within a column, with an explicit horizontal centering offset added specifically to fix a real, reproduced bug where the scene’s camera — which always aims at the world origin — rendered a blank canvas for an off-center graph.

13.3 Shared annotation, roadmap, and typography primitives (components/shared)

These modules exist, per the project’s own documented Phase-22.1 decision, specifically so the public landing page’s frozen visual showcase and the authenticated Reader, Annotations, and Roadmap surfaces render from one shared vocabulary rather than two copies that could drift apart. annotationMeta.ts’s CATEGORY_META table pairs a color, a glyph, and a label for each of the ten relationship categories — color is never the sole signal. roadmapPrimitives.tsx and annotationPrimitives.tsx supply the actual shared, server-renderable presentation components (badges, dots, evidence lines) that both the landing page’s static showcase and the real, live Roadmap and Reader panels render through.

13.4 Writer mode components (components/writer)

WriterEditor.tsx implements the document-switcher, debounced autosave, citation import/insertion, and revision-recovery UI, including a resizable citation sidebar supporting both pointer drag and full keyboard resizing (arrow keys stepping the width, Home/End jumping to bounds) — the same value-bearing, keyboard-operable resizable-separator pattern reused identically by the global Ask Library sidebar.

13.5 Custom hooks

useNarrowViewport.ts returns the correct narrow/wide state synchronously on the very first client render (via a lazily-initialized state value, not a post-mount effect), specifically avoiding any server/client rendering mismatch. useScrollReveal.ts implements the site’s one-time scroll-entrance animation, and its own effect deliberately does nothing at all — no observer is even created — when the browser’s reduced-motion preference is set or IntersectionObserver is unavailable, so reduced-motion compliance is structural rather than a fallback bolted on after the fact.

Part I · Software — 12 / 12

Limitations, Testing Posture & Development Discipline

The project’s own candidly documented defects and unfinished work, its layered automated and manual testing surface, and the recurring diagnose-then-patch-then-record discipline visible across its whole history.

The project keeps its own operational log unusually candid about its defects, incidents, and unfinished work — most of the limitations recorded below are the project’s own documented admissions, cross-checked here against the code that implements (or fails to fully implement) the behavior described, rather than externally inferred criticisms.

14.1 Documented, code-grounded limitations

Structural PDF extraction degrades silently to a flatter fallback when GROBID is unavailable. The GROBID adapter returns a bare null — no error, no distinguishing signal beyond a downstream structure-limited label — on an unset service URL, an authentication failure, a timeout, or any non-success HTTP response, and no code path retries a failed call, alerts an operator, or surfaces the degradation to the reader beyond that one internal flag. The labeling discipline itself is genuine and honest — a structure-limited block is never presented as a page-anchored GROBID block — but the practical consequence is that citation and footnote anchoring quality for any given document depends on the availability of one external service at the moment of ingestion.

Roadmap “transitivity” is capped at the reader’s own uploaded library, not the wider scholarly graph — already discussed in §6.1/§10.2, and worth restating plainly as a limitation: the roadmap is a materially smaller object than “the citation graph of the field,” and any claim about the system surfacing a reader’s implicit intellectual context inherits this cap directly.

A discrepancy exists between the application’s privacy-page copy and its actual code. The privacy page states that deleting an account removes all uploaded files, extracted text, annotations, and derived data, and that a user can export their notes, roadmap, and bibliography. A targeted search of the codebase for a self-service, account-wide deletion route or a bulk personal-data export endpoint found no matching implementation — what is actually implemented is per-work soft-delete/restore and a permanent-delete state machine scoped to one work at a time, described in §8.1 and §10.6, not an account-wide action, and no export feature exists outside Writer’s own per-project document export. This is a genuine documentation-versus-implementation mismatch in user-facing legal copy, worth naming directly rather than silently assuming the copy is accurate.

The automated CI test suite runs against an older pipeline version than production actually serves. The continuous-integration configuration pins the analysis pipeline to its second version for its automated end-to-end run, while the project’s own log states that production has since been promoted to the fourth version as its default. This is recorded as a deliberate, documented trade-off, not an oversight, with newer-pipeline-specific behavior instead covered by point-in-time manual canary runs and unit tests — but it means a green automated test run is evidence for the deterministic subset of behavior it actually exercises, not for every version-specific feature production currently runs.

Only a minority of the full end-to-end test suite runs automatically on every push; the remainder require a live background worker process, live Supabase Storage, and/or live external bibliographic and AI APIs, and are documented as run manually rather than gated in continuous integration — an explicit, reasoned trade-off around cost and external-service availability, not an oversight, but one the project’s own log states plainly rather than implying full automated coverage.

Several research-discovery source providers are architecturally present in the codebase but not confirmed configured in the current production deployment. Five keyless scholarly sources are always enabled; four further providers (a web-search provider, a video platform, and two social-media adapters) each require an API key or access token that, as of the project’s most recent recorded audit, had not yet been rotated and configured in production — reported honestly as disabled, not silently skipped or faked, whenever the corresponding credential is absent.

Explicit-citation resolution has a measured, non-trivial miss rate, concentrated in older or book-form citations. The project’s own log reports a specific, measured recall figure on its private gold-evaluation fixture (improved substantially over the course of several targeted fixes, including the form-aware provider-ordering and Open Library trailing-year fixes described in §5.2), with the remaining misses attributed to genuine source-coverage gaps for older monographs at specific external providers, not an extraction-logic defect — this bounds any claim about citation-extraction completeness to the specific fixture it was measured against, not a general accuracy guarantee.

Authorization is enforced per-route rather than by a single centralized middleware layer. Every protected page and API route calls its own ownership/session-check helper individually rather than passing through one framework-level gate; the project’s own audit confirmed, by manually reviewing every route, that each one does in fact perform its own check, so this is not currently an exploitable gap — but it is a structural pattern where a newly added route that forgot to call the shared helpers would not be caught by any framework-level guarantee, only by code review discipline.

Feature-flagged capabilities default off, and their production-live status should never be assumed from code presence alone. Writer mode, the paid cross-library graph expansion, the interactive reader, canonical Library identity, the Ask Library chat, and conversational competency designation are each gated behind an independently addressable release flag defaulting to off — the project’s own log records at least one real incident where a local development environment had never set any of these flags, silently disabling an entire feature for every local end-to-end test run without anyone noticing until the gap was specifically investigated.

bibliographic_records is an unscoped, append-only shared catalog that orphans on deletion — an accepted trade-off at the project’s current single-user cost scale, discussed already in §3.5, with a periodic orphan-row cleanup recorded as future, unbuilt work rather than a currently scheduled task.

No shared packages/ui was ever scaffolded — reader, roadmap, and graph components live directly inside the web application rather than in an independently reusable package, a deliberate scoping decision (there is currently exactly one consuming application) rather than a defect, revisited only if a second application ever needs to share these components.

A repository-wide search for literal TODO/FIXME/HACK markers in application source found none of substance — every “stub” reference that did turn up refers to the intentionally-labeled deterministic heuristic classifier fallback, not an unfinished implementation. This is worth stating for methodological transparency: the genuine gaps recorded here come entirely from the project’s own structured status log and from direct code-versus-documentation cross-checking, not from code comments admitting incompleteness.

14.2 Testing posture

The project maintains: a substantial unit-test surface for every pure package described in this report (packages/roadmap’s ranking logic is tested against the plan’s own worked Heidegger and Vico acceptance cases; the heuristic AI classifier is tested with no network or key required, by construction; packages/consistency’s nine checks and packages/deletion’s state machine are both tested as pure functions against injected effects); a large integration-test surface at the worker layer covering extraction, passage annotations, citation integrity, RAG indexing, queue recovery, canonical-identity merging, and consistency-repair application; and an end-to-end browser test suite (over fifty specs at the time of the project’s most recent audit) split explicitly into a smaller continuous-integration-safe subset and a larger, manually-run full-stack subset requiring live external services. Manual VoiceOver (screen-reader) verification is recorded, repeatedly and explicitly, as never having been completed — an acknowledged, standing accessibility-verification gap distinct from the project’s substantial automated axe-core accessibility scanning, which the project’s log reports finding zero WCAG 2A/2AA violations across every route swept as of its most recent accessibility pass.

14.3 A characteristic development discipline

Across its whole recorded history, the project exhibits a consistent pattern worth naming explicitly, since it recurs in nearly every package documented in this report: a defect is found (often via a real production incident, a load test, or an adversarial fixture), the root cause is diagnosed and named precisely (a specific race condition, a specific library bug, a specific silent-fallback path), a fix is implemented that is deliberately narrow and evidenced rather than broadly defensive, and the incident and its fix are both recorded in the project’s own durable log rather than only in a commit message. This pattern is visible in the GROBID contamination guards (§4.3), the pg-boss connection-string TLS fix (§8.5), the citation-role foreign-key race recovery (§9.4), the camera-framing trigonometry fix (§13.2), and dozens of smaller instances throughout — a project culture of diagnosing before patching, and of recording the diagnosis alongside the patch, rather than merely making a symptom go away.

Part II · Research Paper — 01 / 06

The Paper’s Argument & Contributions

A theoretical, non-empirical manuscript arguing that Palimnote’s actually-built mechanisms instantiate constructs from four separate literatures — argued strictly as design analysis, never as a demonstrated effect.

The manuscript, titled simply Palimnote and authored solely by Hyder Husain Arastu (MLA format, XeLaTeX with biblatex-mla, Times New Roman, double-spaced), is a theoretical, non-empirical paper. It does not claim, anywhere, that Palimnote has been shown to work — no component of the software has been evaluated with real readers, and the paper is explicit and repeated about this boundary at every turn. What it claims instead is narrower and more disciplined: that Palimnote’s actually-implemented mechanisms can be read, at the level of design analysis, as a coherent instantiation of specific theoretical constructs drawn from four largely separate literatures — trust calibration and cognitive offloading in human-AI interaction, cognitive load theory’s account of guided sequencing, the philosophical literature on extended cognition, and scholarship on metaphor’s role in theory construction.

15.1 The central thesis

Stated in the paper’s own words, the argument is that Palimnote’s provenance records, honest heuristic-versus-model labeling, independent-dimension credibility scoring, retrieval-grounded and citation-required chat answering, user contestability, and verifiable-anchor citation lookup can be analyzed as a design-level instantiation of the constructs the trust-calibration and cognitive-offloading literatures associate with verifiable offloading and appropriately-calibrated reliance. This is argued strictly as a mechanism-to-construct correspondence at the level of design analysis — explicitly not as a demonstrated effect on any reader’s actual trust, reliance, or learning. Rule-based prerequisite sequencing (the reading roadmap and the five-stage curriculum) is offered as a genuine, supporting, but explicitly bounded instantiation of instructional-sequencing constructs, since it lacks the one feature the cited scaffolding literature treats as constitutive of the concept: adaptive fading of support as competence grows. The externalized-relational-structure (“expert map”) framing — the idea that Palimnote’s citation graph and missing-link detection externalize the relational structure an expert reader brings to a text — is retained only as a scope-capped motivating frame, not as the paper’s central claim. And the extended-mind reading (Clark and Chalmers’s criteria for when an external store becomes constitutive of an agent’s own cognition) is admitted only as a bounded, criterion-by-criterion analytic lens — one whose criteria the paper’s own analysis shows the design partly fails to satisfy, and treats that partial failure as itself an informative, reported finding rather than something to conceal.

15.2 The paper’s four contributions, plus a fifth offered more modestly

The paper states its contributions explicitly in its introduction:

  1. A design-level mechanism-to-construct correspondence between Palimnote’s provenance, labeling, credibility-scoring, citation-required-answering, contestability, and verifiable-anchor mechanisms and the constructs the trust-calibration and cognitive-offloading literatures associate with verifiable offloading and calibrated reliance — argued as design analysis, not as a demonstrated effect.
  2. Rule-based prerequisite sequencing offered as a genuine, explicitly bounded, supporting instantiation of instructional-sequencing constructs, with no claim of adaptive fading.
  3. A criterion-by-criterion analysis of Palimnote’s persistent library and profile against Clark and Chalmers’s four criteria for genuine cognitive extension, including the respect in which the design’s own mechanisms are shown not to satisfy those criteria — a documented partial failure the paper treats as itself informative rather than something to smooth over.
  4. A combined, cross-literature correspondence check performed on one concrete, fully inspectable system — checked across all four literatures at once rather than one at a time, which the paper argues surfaces an internal tension that no single-literature survey would by itself reveal: the very mechanisms that make the trust-calibration reading strongest (a standing requirement that generated output be verified rather than automatically accepted) are the same mechanisms that make the extended-mind reading weakest. The paper is careful to state that being fully built and code-inspectable is not, on its own, what distinguishes Palimnote from comparable tools — a reference manager, a literature-search assistant, or an established citation apparatus could each be just as inspectable — but rather that this project’s own direct, code-level access is what made this particular combined check possible to perform here.
  5. A fifth, more modestly framed contribution concerning the manuscript’s own production: the citation-and-verification protocol behind the paper’s own sourcing (described in full in §17–§19 below) is offered as a minor methodological note on producing a citation-disciplined manuscript under machine assistance, explicitly not as a contribution the paper centers.
15.3 What the argument does not claim

The paper is unusually, deliberately insistent about the boundary between “a mechanism exists” and “an effect has been shown,” repeating variants of this distinction in nearly every section. No claim anywhere states that displaying provenance improves a reader’s trust; that independently-scored credibility dimensions calibrate reliance; that the roadmap’s rule-based sequencing sequences learning effectively; or that the system’s design actually curbs over-reliance, metacognitive laziness, or automation bias — each of these stronger claims is named explicitly as exactly the kind of claim the paper does not make, precisely because the underlying literature it draws on (discussed in §16.2 below) gives active reason for caution rather than confidence about exactly this leap. The paper’s own concluding sentence states the discipline plainly: “the distinction this paper has held throughout is between a mechanism that exists and an effect that has been shown.”

Part II · Research Paper — 02 / 06

Section-by-Section Walkthrough

What each of the manuscript’s eight sections argues and why, from the Introduction’s background-knowledge problem through the Theoretical Argument’s central synthesis to the Conclusion’s closing discipline statement.

The manuscript’s eight sections follow a deliberate order chosen so that Palimnote itself is introduced early (in the Introduction) rather than held back, with its full built mechanisms only described in detail once the reader has the necessary constructs in hand from Background.

16.1 Introduction

The Introduction follows a six-part structure. It opens with the general problem of background-knowledge dependency in reading: drawing on four held secondary sources (a matched-pairs study of expert and novice teachers, a national consensus synthesis on learning science, a physics-education monograph reporting Ericsson, Krampe, and Tesch-Römer’s deliberate-practice findings, and Willingham’s review of critical-thinking research), it establishes that organized background knowledge measurably differs from disorganized information, and — citing Willingham’s own transfer-study data directly — that merely supplying background structure does not, by itself, equalize performance between a reader who has it organized and one who does not; only nineteen percent of subjects in the reviewed transfer studies spontaneously recognized an earlier problem’s relevance to a new one, and even being told outright raised the rate only to thirty-five percent. The paper is explicit that none of these four sources studies reading, annotation, or an AI-assisted tool at all — any connection to a reading application is an analogy the manuscript itself constructs, not a claim the sources make.

Palimnote is then introduced directly, in its second paragraph, described at the level of its actually-implemented mechanisms (provenance records, real-lookup-only citation resolution, the ten relationship categories, on-demand roadmap computation, retrieval-grounded chat) — with an explicit caveat that none of this is evidence the system adapts to a reader’s true competence or supplies the map an expert brings; that stronger claim is named as “a stated design aspiration for a future version of the product, not a claim made here about the built system.”

A third paragraph introduces the generation-reliability problem — the fact that a system generating content for a reader (whether a tutoring question or an attached annotation) introduces a difficulty a reader cannot independently check at the moment of generation — drawing on both a comprehensive hallucination survey and, as one named instance rather than the section’s organizing subject, the SocraticLM paper’s own framing of a “Question-Answering” paradigm that leaves students passively provided with answers. A fourth paragraph previews Palimnote’s response — the built mechanisms already described — and a fifth states the paper’s thesis, explicitly marked as provisional pending ratification of the underlying thesis-selection process (discussed in §17.4 below). The section closes by previewing the remainder of the manuscript’s structure.

16.2 Background

Background is the manuscript’s literature-review section, organized into four subsections corresponding to the four literatures the paper draws on, each drawing sources only for the specific construct it establishes, with an explicit disclaimer at the top that none of this literature studies a reading-and-annotation tool of the kind Palimnote is.

Trust calibration and the limits of disclosure. Anchored on a comprehensive hallucination-in-natural-language-generation survey, which divides hallucination into content that contradicts a source and content that cannot be verified from it, and states that reliable expression of a model’s own uncertainty remains, in the survey’s own words, “a recognized and still largely unsolved research problem.” The section’s sharpest complication — introduced here and returned to at length in the Theoretical Argument — is Kizilcec’s field experiment on an algorithmic peer-assessment interface, which found trust in the system was not monotonic with the amount of disclosed detail: a procedural explanation restored trust after an expectation-violating outcome, but adding the underlying raw data on top of that explanation did not sustain the improvement — it reopened the original trust gap. A second study, on AI-assisted credibility judgments, found that attaching any natural-language explanation to an automated verdict substantially increased reliance on that verdict regardless of whether the verdict was actually correct — users, in the study’s own words, “could not discern whether the AI directed them towards the truth.” A third source, a comparative study of how historians, professional fact-checkers, and undergraduates evaluate unfamiliar websites, found that the fact-checkers — who consistently outperformed the other two groups — did so chiefly by leaving a site to check independent corroborating sources rather than by scrutinizing the site’s own internal features, in real tension with any design (including Palimnote’s own) that relies on internal, per-source metadata display as its primary route to calibrated trust. A practitioner-validated set of eighteen human-AI interaction guidelines is cited for two directly relevant recommendations — “make clear what the system can do” and “make clear why the system did what it did” — while also reporting that the second of these was, across the very products the guidelines were validated against, one of the most frequently violated despite active research interest in explanation generally.

Cognitive load and the case for guided sequencing. Anchored on Sweller’s own cognitive load theory, whose supporting studies are drawn almost entirely from mathematics, geometry, and technical instruction — the paper is explicit that this literature neither tests nor discusses sustained narrative or humanistic reading, which is Palimnote’s actual use case. A crucial distinction the paper insists on: cognitive load theory’s own ordering criterion is a passage’s intrinsic element interactivity, not a dependency relation between separate works — a materially different basis from what Palimnote’s roadmap actually orders by. A separate strand establishes the scaffolding construct’s defining feature: Wood, Bruner, and Ross’s originating observational study of tutors assisting children describes support that is withdrawn gradually and non-automatically, tracked to the child’s own demonstrated competence — and later practitioner synthesis states the requirement directly: assistance “must gradually be reduced (or faded) so that the student ultimately learns to perform well independently.” The formal expertise-reversal effect — guidance calibrated for a novice becoming redundant or actively harmful once a learner’s own prior knowledge is sufficient — is documented both in Kirschner, Sweller, and Clark’s extension of cognitive load theory and, at its own primary source, in Kalyuga, Ayres, Chandler, and Sweller’s naming of the phenomenon.

Extended cognition as a bounded lens. Anchored on Clark and Chalmers’s classic argument, quoted at its own most consequential line — “there is nothing sacred about skull and skin” — and its four jointly-necessary criteria for treating an external resource as constitutive of a standing belief: constancy of use, easy accessibility, automatic endorsement of retrieved information without further scrutiny, and past conscious endorsement at the moment of storage (a fourth criterion the argument’s own authors flag as the most contestable). The paper is careful to frame this as a philosophical individuation condition for the concept of belief, not an empirically validated design instrument, and notes the argument itself predates retrieval-augmented generation and every AI system the manuscript describes entirely — applied here only as an analogy, never as a framework the source itself endorses.

Metaphor as a methodological guardrail. Drawing on a single edited volume containing an unresolved, internal three-way disagreement — Boyd arguing that computational vocabulary like “information processing” did genuine, theory-constitutive work in early cognitive psychology rather than serving as mere expository shorthand; Pylyshyn, in the same volume, denying this and holding that such vocabulary describes mental activity literally; and Kuhn, writing in direct dialogue with Boyd, arguing that repeated exposure to a metaphor can manufacture a reader’s sense of resemblance rather than merely report one already there. The manuscript states its own resulting discipline directly: any description of Palimnote’s retrieval pipeline in memory-adjacent language is treated throughout as an expository analogy, named as such wherever it appears, never as an unhedged equivalence between what a retrieval system does and what remembering is.

16.3 Methods

Methods opens not with a generic methodology statement but with the paper’s own origin narrative: the project began from an observation about SocraticLM, a large-language-model tutoring system, whose own paper defines Socratic teaching in a narrow, two-part way — dialogic exchange and probing questions — citing only critical-thinking-pedagogy literature, not classical or philosophy-of-education scholarship. Peer-reviewed classics scholarship on the Socratic elenchos, the paper reports, indicates the method is not reducible to this surface mechanics — it combines a repeatable question-and-answer form with a further strategic aim (testing an interlocutor’s doxastic coherence, not the truth of any single answer) and a substantive epistemological commitment to definitional knowledge, per Benson’s chapter in The Cambridge Companion to Socrates — and the same scholarship documents a genuine, unresolved scholarly debate over whether the elenchos is best read as capable of constructive results or purely refutative, on which the manuscript itself takes no position. This scope gap, the paper states, motivated a broader survey of the pedagogical, cognitive-science, and human-computer-interaction literatures the manuscript actually draws on.

The section then states the paper’s own citability discipline directly: every candidate source was logged with bibliographic metadata confirmed against the document itself, classified against a four-part citability rule (formal peer review, primary-source status, acceptance-for-publication, or scholarly synthesis meeting comparable provenance standards), with every candidate quotation independently, mechanically checked as a verbatim substring against the source’s extracted text before any claim resting on it could be treated as usable. Literature search proceeded across nine disciplinary areas, each independently reviewed by a second reviewer before its findings were treated as usable — this entire discipline, and the governed multi-agent process behind it, is the subject of §17–§19 below.

A distinctive subsection, “Process Architecture as a Methodological Contribution,” states that the manuscript’s own development instantiates, at manuscript scale, the same design principles Palimnote applies to reading support: verifiable offloading, multiple-agent parallel processing, and user-directed iteration. It discloses that this research project operated as one half of a supervised two-agent structure — two parallel instances of the same underlying model, one building the Palimnote application itself in a separate repository, the other conducting this research program — feeding each other through standing loops: research findings informing the application’s own refinement, and the application’s evolving, real state informing this manuscript’s implementation-status claims, so that a claim here about what the system does tracks the live application rather than an aspirational design. Every output from either side, the section states, passed through the author’s own review-and-correction gate before adoption. A further subsection frames the AI role in this process explicitly as an indexing mechanism — drawing a documented lineage from Otlet’s proposal to externalize and interlink recorded knowledge, through Garfield’s demonstration that citation indexing surfaces structure a subject index misses, de Solla Price’s finding that citation networks form a discoverable non-random structure, and Small’s operationalization of citation relatedness as a computable measure — rather than as an autonomous reasoner, with every such output mechanically verified (an exact-match substring check, or resolution against an external authoritative source) before being retained.

A “Conservative Novelty Statement” subsection is unusually candid about what the paper does not claim as new: multi-agent LLM pipelines for structured cognitive tasks, hierarchical prompt-chaining for long-form co-writing, and recursive-decomposition multi-source reasoning are each named as already-published, established practices this project does not claim to originate. What is offered as new is the specific combination and discipline — a verification ledger checked claim-by-claim and quote-by-quote before any material is treated as citable — not any one constituent mechanism alone. A final subsection, “A Single-Operator Capability Argument,” frames the process as a structured division of labor (mechanical, verifiable work offloaded to AI agents; judgment — acquisition direction, interpretive choices, final argument decisions, and verification of every quoted string — retained by a single author), analogized explicitly to Engelbart’s 1962 vision of machinery that extends human capability without displacing human direction and Licklider’s 1960 account of task allocation by comparative advantage — while stating plainly that this capability argument assumes, rather than removes, a demanding precondition: it describes what a user with sufficient domain competency can direct and verify, not a method that functions independent of that competency.

16.4 Palimnote Design

This section is the manuscript’s direct description of the built system, organized by subsystem, and is written under a strict implementation-status discipline: every capability is described as implemented, partially implemented, planned, proposed, or explicitly unknown where the project’s own records conflict — the same five-value vocabulary used throughout the project-analysis documents summarized in Part I of this report. It covers, in order: ingestion and structural extraction (including the corrected, corroborated account of the GROBID service actually running as a private, authenticated cloud instance in production, rather than strictly locally as an earlier document had stated, with a footnote explaining the discrepancy was resolved in favor of the more recent, corroborated record rather than silently picking a side); citation extraction, resolution, and classification (with explicit numeric ceilings on lookups, candidates, and inspected resources, and an explicit statement that the pipeline is single-hop from an uploaded work to its own citations’ metadata, never a multi-level recursive traversal into a cited work’s own bibliography — a distinct and narrower claim than the reading roadmap’s own, separately bounded, four-level-deep recursive traversal); the provenance and confidence discipline; independent-dimension credibility scoring; the annotated reader and its contestability controls; rule-based roadmap sequencing (explicitly non-adaptive, with the one mechanism that could carry a genuine competency signal — the conversational competency feature — noted as built into the schema, live in production, but sitting disabled behind two feature flags and invisible to any current user); the knowledge graph; retrieval-grounded chat (described precisely as Socratic-method-informed and locally authored, with the manuscript stating directly that no SocraticLM training data, weights, or weight-diff artifacts are used); and Writer mode, closing with a broader observation the section elevates to a design property in its own right — that a feature’s presence in the codebase is not evidence of its presence for any current user, since several substantial capabilities ship behind independently addressable release flags defaulting to off.

16.5 Theoretical Argument

This is the paper’s analytical core, developing the mechanism-to-construct correspondence in full. It opens by restating the argument’s evidential discipline precisely: every mechanism examined is one that is actually built and inspectable in the codebase, read against constructs an already-established literature associates with real effects on trust, reliance, verification behavior, and learning — never assessed by any measurement this project itself performed.

The “Provenance, Verifiable Offloading, and the Constructs of Calibrated Reliance” subsection argues that Palimnote’s central design commitment is a cluster of mechanisms that together offload mechanical work from the reader while structurally withholding automatic acceptance of what is offloaded — and connects this directly to the hallucination survey’s own framing of the general problem these mechanisms answer to, and to the human-AI interaction guidelines’ own normative recommendations. It is explicit, immediately afterward, about what this correspondence does not license: no source studies an annotation tool’s provenance display specifically, so the claim licensed is narrower — that the mechanisms instantiate the design elements this literature associates with calibrated reliance, argued at the level of what the code does, not at the level of what any reader has been shown to experience.

“Why Palimnote, and What the Mapping Adds” is the section’s most self-critical passage. It concedes, without qualification, that individual mechanisms of this kind are not novel to this project alone — a reference manager extended with a grounded-retrieval plugin, a literature-search assistant, an established citation apparatus, or a document-grounded notebook product could each already implement one or more of them — and states directly that being fully built and inspectable is not, on its own, a property that distinguishes Palimnote from those comparable tools, since their own codebases and documentation are equally inspectable. What the combined mapping adds, the section argues, is a genuine internal tension a single-literature survey would not by itself reveal: the very mechanisms that make the trust-calibration reading strongest are the same mechanisms that make the extended-mind reading weakest, discussed at length in the Synthesis subsection.

“The Strongest Objection: Non-Monotonic Transparency” treats Kizilcec’s field-experiment finding as primary, peer-reviewed evidence — not merely a caveat — that the naive intuition “more disclosure straightforwardly builds more trust” is false in at least one controlled, high-stakes setting, while carefully distinguishing Kizilcec’s comparative, outcome-linked, felt-discrepancy-triggered disclosure manipulation from Palimnote’s own constant, low-stakes, uniformly-attached provenance record — the two are not the same design object, and the manuscript states plainly that it does not import the “amount of transparency” finding as though it transferred directly.

“The Sequencing Co-Pillar: Order Without Fading” maps the roadmap and curriculum onto the guided-instruction literature’s constructs while insisting on a precise distinction: the roadmap’s ordering basis is bibliographic prerequisite dependency, not the intrinsic element-interactivity load basis cognitive-load-theory sequencing actually orders by — this is, the section states plainly, “a mapping onto the sequencing sense of the construct, not a claim that Palimnote’s ordering criterion is the ordering criterion the cited literature uses.” The construct the pillar borrows carries a defining feature Palimnote’s mechanism structurally lacks — fading — and the section names the expertise-reversal effect directly as a documented risk the fixed, non-adaptive tiers have no mechanism to detect.

“Extended Mind as a Criterion-by-Criterion Lens” is where the paper’s self-reported partial failure lives. Of the four Clark-and-Chalmers criteria, two carry the whole of the analysis: automatic endorsement and past conscious endorsement. On automatic endorsement, the section argues that every trust-calibration mechanism already described exists precisely to interrupt unexamined acceptance of generated output, not to smooth it into unreflective use — meaning the design withholds automatic endorsement by deliberate structural choice, which under the extended-mind criteria registers as non-satisfaction of the one criterion that would make the stronger extended-mind reading available at all. On past conscious endorsement, by contrast, the paper finds “the cleanest fit anywhere in this analysis”: the system’s own verification-status field and its approve/dispute/reject/hide/edit actions operationalize a discrete, timestamped act of endorsement rather than merely assuming one. The section is explicit that these two readings — the trust-calibration reading and the extended-mind non-satisfaction — are not evidence for each other; they are two re-descriptions of the identical mechanisms from two different theoretical vantage points, and neither is offered as an empirical test of anything.

“The Metaphor Guardrail: Retrieval Described, Not Memory Claimed” applies the Background section’s Boyd/Pylyshyn/Kuhn disagreement concretely, citing the technical description of retrieval-augmented generation itself (Lewis et al.) to state that the manuscript accordingly never describes Palimnote’s retrieval pipeline as working like human memory or as remembering — treating it strictly as a concrete object for discussing analogies and disanalogies, never as evidence the pipeline shares a cognitive property with human recall.

The closing “Synthesis” subsection states the paper’s central analytical payoff directly: read together, a system that offloads mechanical lookup while withholding automatic endorsement of what is offloaded is, on Clark and Chalmers’s own terms, simultaneously a design-level instantiation of verifiable-offloading constructs and a design that declines the one criterion that would make it a stronger extended-mind reading of the reader’s own cognition — two co-descriptive readings of the same mechanisms, neither one corroborating the other.

16.6 Limitations

Limitations is organized around several distinct kinds of bound on the paper’s own argument, each stated without softening. The roadmap and missing-link detection are bounded to the user’s own uploaded corpus. The rule-based sequencing carries the documented expertise-reversal design cost, with no mechanism to detect it, and no mechanism anywhere in the system measures or targets cognitive load at all, so any load-related framing in the paper is stated as “a hypothesis the design invites rather than a quantity the system tracks.” More fundamentally, every mechanism-to-construct correspondence in the paper is a design-level analysis, not a measured effect — no component of Palimnote has undergone empirical evaluation with readers, and production usage to date is the project’s own verification runs, not an established user base. The literature the paper itself uses to describe the provenance mechanisms is candidly reported as complicating the question those mechanisms address more than resolving it — Kizilcec’s and Pareek’s findings, both introduced in Background, are jointly evidence that more disclosed detail does not straightforwardly produce better-calibrated reliance, which the paper states is “the seam this paper’s central argument must be read against.” The extended-mind lens carries the identical limitation from the opposite direction: no study located for this project tests whether real users of a digital annotation tool actually withhold or grant automatic endorsement in practice; that the design structurally declines to invite it is an affordance the paper can describe, not a behavior it has observed. The section also names, candidly, two literatures the paper’s own motivating frame depends on but has not itself acquired and verified in full: the primary expertise-canon studies the Introduction’s secondary sources themselves rest on, and the classics scholarship that would confirm or disconfirm the paper’s own comparison between SocraticLM’s operationalization and a fuller reading of Socratic pedagogy. The section closes by stating, for every one of its central mechanism-to-construct mappings, the specific empirical study that would falsify it — a controlled comparison finding no verification-behavior difference with and without the provenance display; a finding that fixed priority tiers perform no better than an unordered list for novices, or worse than no order at all once a reader’s prior knowledge is high; and evidence that readers treat the system’s stored library with the same unexamined reliance the extended-mind criteria describe.

16.7 Future Work

Future Work names three categories of study the paper’s argument invites but does not itself design: a trust-calibration study testing whether the provenance display actually changes how a reader assesses reliability; a verification-behavior study testing whether a reader given a dispute affordance actually uses it; and an expertise-transfer study testing whether the roadmap and knowledge profile narrow any measurable novice-expert performance gap. It records specific, named further acquisitions that would strengthen particular claims (a still-unacquired study on whether AI explanations improve human-AI team performance beyond accuracy alone; two established credibility-research frameworks that would ground the independent-dimension credibility design in the field’s own vocabulary; a companion empirical paper to an already-held philosophical chapter on AI systems impersonating philosophers). It poses, without answering, two open design questions already discussed in Limitations and the Theoretical Argument — whether the roadmap should evolve toward genuinely contingent, competence-responsive fading, and whether the dormant conversational competency feature should be activated, explicitly framed as questions for empirical validation to settle, not implementation alone. It closes with a single, carefully bounded speculative gesture toward scale: extending the roadmap and knowledge-profile structure from one reader’s own library toward a larger, shared map linking many readers’ libraries would be, the paper states, “one small, bounded instance” of a decades-long documentation-science and citation-network-analysis research program — and immediately imports that program’s own strongest available caution against the gesture, via Bowker and Star’s argument that any classification scheme embeds contingent choices about what counts as related and whose material is well served, extended here by analogy, not asserted as Bowker and Star’s own claim, to any future, larger version of Palimnote’s own graph.

16.8 Conclusion

The Conclusion restates the argument’s four contributions and their explicit boundaries without introducing any new citation or claim beyond what the preceding sections already established — a discipline the section’s own construction notes record as checked directly against the paper’s own claim-tracking record. It adds one further, separately-argued claim concerning not Palimnote but the manuscript’s own production: the structured division of labor documented in Methods is offered as a design claim about concentrating a researcher’s own expert judgment in acquisition, verification, and interpretation while offloading mechanical work to constrained AI agents — itself a design claim, not a measured effect, and independent of anything argued about Palimnote’s own mechanisms. The paper’s final paragraph states its governing discipline as directly as the manuscript ever does: “the distinction this paper has held throughout is between a mechanism that exists and an effect that has been shown… Until such work is done, what stands is a mapping from mechanism to construct, offered as a way of describing, precisely and without inflation, what a specific built system does and does not warrant a reader in concluding from it.”

Part II · Research Paper — 03 / 06

Research Governance, Phases & Gates

The written governance program behind the manuscript — eight phases, a recursive research-write-review cycle, and six named gates each with a defined evidentiary bar and a required output artifact.

The manuscript summarized above was not produced by a single drafting pass. It was produced under an explicit, written governance program — recorded in the research project’s own PLAN.md, DECISIONS.md, claude.md, and a large body of logs, ledgers, and review artifacts — that treats the paper’s development as a governed research program with defined phases, checkpoint gates, and a recursive research-write-review cycle, rather than a single linear draft. This section documents that process in the same descriptive, evidence-grounded spirit as Part I documents the software.

17.1 Standing directives and absolute constraints

Before any phase work began, the research project’s own governing memory file recorded a fixed set of standing constraints that bind every later phase: the manuscript is a complete theoretical contribution with no original-empirical section (an empirical/ directory holds only archived scaffolding for a possible future funded phase, explicitly never cited as findings); published empirical studies are core evidence, cited in MLA style, subject to a four-tier citability rule; background, non-citable sources may be actively mined for hypotheses and search leads but never cited as evidence themselves; a strict quote policy requires paraphrase-first composition, direct quotations capped at twenty-five words, and every quotation mechanically verified against the source’s own extracted text before use; and — the most load-bearing discipline of all — every claim about Palimnote itself must resolve against a maintained claim-and-feature matrix, so that a planned, proposed, or only-partially-implemented capability, or a merely hypothesized effect, is never presented in the manuscript as an empirically demonstrated fact.

A separate, explicit set of authorship and AI-disclosure directives, recorded by the user and logged in the project’s decision record, governs exactly how the manuscript may describe its own production: the sole author is Hyder Husain Arastu, with the author’s name appearing only where MLA formatting itself requires it; no AI system is ever listed as an author; and AI systems are named in the manuscript’s own prose only where substantively important to explain (for instance, describing token use or the two-agent process architecture), with no other “process chatter” permitted elsewhere in the manuscript — this is exactly the discipline visible in the Methods section summarized in §16.3 above, and its Gate-E mechanical enforcement is described in §17.5 below.

The writable root for the entire research project is confined to the research repository itself; the reference software project (AutoCriticalEditionProject) is treated as strictly read-only throughout, and the research project’s own governance log records that this boundary was checked and, once, genuinely tested: an early audit found modified files in the reference repository, and a full transcript audit subsequently attributed every such change to external, concurrent development on that repository by a separate process — not to any action originating from this research project’s own agents — a finding the log records candidly, including the observed evidence and its resolution, rather than silently.

17.2 The phase structure

The program is organized into eight numbered phases, of which the first three are strictly sequential foundation work and the remainder run as a recursive cycle rather than a waterfall:

  • Phase 0 — Safety, initialization, project governance. Directory scaffold, ledgers, an empirical-scaffolding placeholder, and a local git repository with no remote.
  • Phase 1 — Reference-app comprehension and architectural analysis. A read-only inspection of the reference software project producing the project_analysis/ documents summarized throughout Part I of this report (current_state.md, intended_state.md, architecture.md, limitations.md, feature_inventory.csv), each capability tagged against the same five-value implementation-status vocabulary used in Part I.
  • Phase 2 — Source intake, verification, and extraction. Described in full in §18 below.
  • Phase 3 — Literature landscape mapping and discipline reviews. Organized by discipline workstream (cognitive psychology and expertise; cognitive load, instructional design, and scaffolding; learning sciences and retrieval practice; human-computer interaction and information retrieval; AI, retrieval-augmented generation, and large language models; extended mind and philosophy of cognition; metaphor and philosophy of science; writing and literacy history; and, added later as its own workstream, ancient philosophy and Socratic pedagogy specifically for the manuscript’s origin narrative) rather than by the cross-cutting thematic matrix a separate synthesis document had already begun — chosen specifically so each workstream maps cleanly onto an eventual manuscript section.
  • Phase 4 — Problem definition and research-question formalization. The point at which the paper’s thesis was actually selected, described in §17.4 below.
  • Phase 5 — Theoretical synthesis and conceptual framework. Construction of thematic “bridge” documents connecting each literature to the selected thesis, subjected to an independent integration review before Phase 6 drafting could begin.
  • Phase 6 — Manuscript construction and section drafting. The section-by-section drafting summarized in §17.3 below.
  • Phase 7 — (originally) empirical placeholder and future-study design; superseded. Following the user’s explicit decision to defer any original-empirical component pending future funding, this phase number is retained, unrenumbered, purely so historical task identifiers and cross-references elsewhere in the governance record stay stable — no phase-7 deliverable was produced for the manuscript itself.
  • Phase 8 — Continuous adversarial review and polish. The ongoing citation-audit, peer-review-audit, argument-review, and style-review cadence, most visibly the full-draft adversarial reviews described in §17.4 below.
17.3 The recursive research-write-review cycle and its six gates

Rather than treating research, drafting, and review as one-way sequential stages, the governing plan states explicitly that once literature mapping is underway the program proceeds as a recursive cycle: research a discipline or theme, synthesize, draft the manuscript section(s) it supports, adversarially review, return to research to fill any gap the review surfaced, and redraft — repeating per section or per unresolved gap rather than only once for the whole manuscript.

Six named gates structure this cycle, each with a defined evidentiary bar, a designated reviewer role, and a required output artifact logged in a task ledger:

  • Gate 0 (project scaffolding) and Gate A (reference-app research) were each passed once, early in the program.
  • Gate B (each source’s intake) recurs for every new source acquired — no source may be annotated, mined, or cited before its own Gate-B pass is complete, described fully in §18.
  • Gate C (a discipline review or thematic synthesis) requires every claim in the review to trace to a specific citable source or be explicitly marked as background-only framing, with agreements, disagreements, methodological differences, and evidential gaps each separately addressed, reviewed by a second party independent of whoever drafted the review.
  • Gate D (a research-question or thesis formulation) is where the paper’s actual thesis was chosen — discussed in §17.4 immediately below — explicitly required to consider and record disconfirming evidence for every candidate framing, not merely rank confirmatory framings against each other; a negative or weak-mapping verdict for any candidate construct is stated as an admissible, expected outcome of this gate, not a failure of the comparison process itself.
  • Gate E (a manuscript section draft) requires every empirical claim to trace to a citable source with a verified quote or paraphrase, and every claim about Palimnote to trace to the permitted wording for that specific claim id in the claim-feature matrix — its own mechanical sweeps are described in §17.5.
  • Gate F (manuscript-level readiness) requires every section to have passed Gate E individually, a cross-section consistency check confirming no section contradicts another’s claim-matrix wording or thesis framing, and a full adversarial-review pass over the assembled document.

A cycle failure at any gate routes back to the relevant research or drafting task, never silently forward past the gate — this reverse-routing is precisely what makes the cycle recursive rather than a one-way pipeline, and every pass or failure at every gate is recorded as a row in a persistent task ledger, following the same evidentiary pattern (objective, evidence, completion criteria, reviewing party, status, blockers) throughout the program’s history.

17.4 Selecting the thesis: the Gate-D comparison

Four candidate thesis framings emerged from the discipline-review pass and an earlier cross-source synthesis, and Gate D’s job was to compare them without pre-committing: Palimnote as externalized background/expert knowledge (the “expert map” framing); Palimnote as cognitive-load-aware scaffolding; Palimnote as provenance-based trust calibration; and Palimnote as a partial instantiation of extended cognition. The governing plan states plainly that a hybrid outcome — one framing as the central throughline with others retained as supporting analytic lenses — is an admissible Gate-D result, and that the comparison must not be run only as a ranking among favorites; for any candidate construct, “this construct maps only weakly to Palimnote’s actually-implemented mechanisms, or does not map at all, and is therefore not carried as a thesis pillar” is recorded as an explicitly legitimate finding the comparison must genuinely be able to reach.

The resulting selection — a centered hybrid thesis with provenance/trust-calibration and verifiable offloading as the central pillar, rule-based prerequisite sequencing as a co-pillar, the expert-map framing retained only as a motivating frame, and the extended-mind reading admitted only as a bounded, criterion-by-criterion lens with its own partial failure treated as a finding — is exactly the structure realized in the finished manuscript’s Theoretical Argument section (§16.5 above). At the time of the research project’s most recent recorded status, this Gate-D selection remained formally marked “provisionally adopted,” logged as awaiting a final user ratification step under the same two-pass precedent already established for the earlier reference-app-comprehension gate — a governance detail the manuscript’s own section headers and revision-history comments record consistently throughout, rather than treating the thesis as finally settled ahead of that ratification.

17.5 Adversarial review and the mechanical Gate-E sweeps

The manuscript’s own section files (.tex) carry an unusually dense, preserved record of their own revision history as inline comments — not stripped before compilation, but retained precisely so a later reviewer or author can see exactly what an adversarial-review finding required and how it was resolved. Two full-draft adversarial reviews are recorded in the project’s status history: a first-round review returning a verdict of “major revision,” citing severe defects (a genuine, self-contradictory citation-status claim for one source repeated across four different sections, and a stale, unresolved bibliography placeholder for another), major defects (a missing differentiation argument against generic retrieval-augmented tools, a broken link between the Introduction’s stated motivation and the paper’s actual thesis, and a sequencing-pillar argument that conflated citation-dependency ordering with cognitive-load-theory’s own element-interactivity ordering — precisely the distinction the finished manuscript’s Theoretical Argument section, quoted in §16.5, now states explicitly), and several moderate and minor findings; and a second-round review after those findings were addressed, whose remaining required changes (adding a required process-architecture figure and an explicit disclosure of the underlying model family used, softening an overclaimed comparative characterization, and reworking one section’s own rhetorical framing to avoid conflating “checking a claim’s fidelity to its source” with “judging an argument’s merit”) are each independently traceable to specific passages of the finished Methods and Theoretical Argument sections described above.

Beyond adversarial argument review, a standing set of mechanical sweeps — grep-pattern checks run over every section file before any Gate-E or Gate-F pass — enforce four purely textual disciplines with a zero-tolerance pass criterion: an instruction-leak sweep, catching any surviving meta-framing language that announces its own rhetorical choice (“we frame X as…”) rather than simply enacting that framing through substantive, citation-anchored prose; a path-leak sweep, catching any internal project filename, directory, or ledger reference that has leaked into rendered prose (the Methods section’s own revision-history comments record several such leaks being found and rephrased into generic prose exactly as required — a verification ledger described in general terms, never a literal file path, is the required form); a present-tense/“as of this writing” consistency sweep; and a check for a fixed list of prohibited overclaiming effect-verbs. A companion sweep specifically confirms that a scope-limiting caveat is carried in a footnote rather than doubling an inline hedge, so a stated contribution can read cleanly as a positive claim in the body while its honest scope limitation remains fully intact, just relocated — a pattern visible directly in the Introduction’s own fourth-contribution footnote and the Theoretical Argument’s “Why Palimnote” footnote, both discussed in §16.1 and §16.5 above. A final, independent voice-and-style pass checks the prose itself against a maintained catalog of common AI-generated writing patterns (mechanical connective words, hedge-phrase clusters, disguised numbered-list transitions, hollow intensifiers) and the author’s own established writing voice, correcting surface-level tells without touching any claim, citation, or quotation underneath them.

Part II · Research Paper — 04 / 06

The Source-Intake & Citability Pipeline

The six-step Gate-B pipeline every source passes through before it may be annotated, mined, or cited — metadata verification, a four-status citability rule, lawful acquisition only, dedupe, extraction, and provenance recording.

Every source entering the research project — whether pulled from a targeted acquisition shortlist or surfaced by a fresh literature search — passes through the identical six-step pipeline before it may be annotated, mined for ideas, or cited in the manuscript at all. This is Gate B, run once per source and recurring for the life of the project.

  1. Verify metadata. Title, authors, venue, year, and DOI or ISBN are confirmed directly against the document itself — its title page, running headers, or copyright page — never taken on faith from a filename or from how a citing secondary source paraphrases it. A field that is genuinely unrecoverable from the document is recorded as an explicit UNKNOWN, never guessed.

  2. Classify citability, against a four-status rule: peer_reviewed (formal peer review, verifiable through a journal’s own editorial apparatus or a Crossref record); primary_exempt (a primary source being analyzed as an object of study rather than cited as evidence); accepted_exempt (accepted for publication ahead of final published form); and scholarly_empirical_exempt (a scholarly empirical report or synthesis with identifiable researcher authors and verifiable provenance, explicitly and deliberately excluding journalism, blogs, how-to guides, slide decks, and trade books even when they discuss real studies — the rule requires citing the underlying study directly instead). Anything meeting none of these four conditions is retained only as a background source: usable for framing and hypothesis generation, but never cited as evidence for a claim.

    A later, separately logged and dated addition to this rule closes a real gap the four-status scheme originally had no clean slot for: a university-press secondary scholarly monograph — a single-author or edited argumentative, interpretive book, as distinct from a primary text, an empirical-research report, or a directly peer-review-checkable journal article. The confirmed rule classifies such a monograph peer_reviewed only when both hold: it is published by an academic or comparable scholarly-imprint press with a genuine editorial-vetting process for its monograph list, and it is a work of scholarship addressed to a scholarly audience rather than a popularization or textbook — with the specific evidence for both conditions documented per source, exactly as for a journal article, existence-and-metadata verification alone being necessary but never sufficient. A trade book aimed at a general audience, even one that discusses real empirical studies at length, explicitly does not qualify under this rule and stays background-only — the discipline instructs citing the underlying primary study directly instead, wherever one is held or acquirable.

  3. Acquire lawfully, or wait. No paywall circumvention is permitted under any circumstance. If access to a source is genuinely blocked, it is logged to a source-request record (citation, DOI or link, why it is needed, which claim or workstream it supports, what access attempts have already been made, and its current status) and the project waits for the source’s owner to supply it — a lower-quality substitute is never silently accepted as equivalent, and a book with no lawful digital edition is explicitly flagged as requiring physical purchase rather than any other route.

  4. Dedupe at the work level before registering. Every new source is checked by content hash, DOI, and normalized title-plus-year against the existing source manifest before it is added; the same underlying work under any edition or scan updates the existing manifest row in place rather than creating a duplicate, and a superseded or duplicate file is quarantined into a dedicated excluded-duplicates location rather than deleted outright.

  5. Land, extract, and manifest. The source is deposited into the project’s source tree, its text extracted (with an OCR fallback for image-only PDFs, and the extraction outcome — including a genuine failure requiring OCR that has not yet been performed — logged honestly rather than silently skipped), routed to a citable-sources location or a background-only location depending on its Step-2 classification, and registered simultaneously in two parallel manifest files that must never be allowed to diverge from one another.

  6. Record provenance. One provenance file per source records exactly how and when it was acquired, the original filename, any rename applied, and the classification record from Step 2 — a durable, auditable acquisition history independent of the manifest itself.

At the time of the research project’s most recorded status, this pipeline had processed a substantial and actively growing source library — beginning from an initial forty-seven-source intake batch, extended through a later user-upload intake cycle that added dozens more sources with a documented zero-duplicate-found result, and continuing to grow as individual discipline workstreams surfaced further acquisition targets. Two purged sources (works by Kant and Hegel, judged irrelevant to the eventual thesis after an early scoping pass) were explicitly removed and are recorded as never to be reintroduced.

A separate, standing discipline governs quotation specifically, layered on top of the citability rule above: composition is required to be paraphrase-first, any direct quotation is capped at twenty-five words, and — the mechanically enforced half of this rule — every quotation actually used in the manuscript is independently checked, by an automated verification script, as a verbatim substring against the source’s own extracted text before the claim resting on it may be treated as usable at all. This is the concrete implementation of the “mechanical verification, not model assertion” discipline the Methods section itself describes at the level of its own methodological framing (§16.3 above); it is not a metaphor, but a literal, repeatedly re-run script comparing drafted quotation text, character for character (modulo whitespace normalization), against the actual extracted document text.

Part II · Research Paper — 05 / 06

AI-Assisted Research Methodology

The fuller governance-level detail behind the manuscript’s own Methods disclosure — model-tier assignment by task complexity, delegation under a single moderating author, and the cost-and-scope discipline governing acquisition.

The manuscript’s own Methods section (§16.3) discloses its production process at the level the paper’s authorship and disclosure directives (§17.1) permit — naming the two-agent architecture, the tiered use of different model capability levels for different task complexities, and the general shape of the division of labor, without naming internal filenames, ledger identifiers, or other project-internal detail, per the path-leak sweep described in §17.5. This section supplements that disclosure with the fuller governance-level detail recorded in the research project’s own internal records, which is appropriate to include here — in a technical reference document about the project, rather than inside the manuscript’s own prose — but was deliberately excluded from the manuscript itself under its own disclosure discipline.

Model-tier assignment by task complexity. The research project’s own records describe a consistent pattern of assigning a cheaper, faster model to mechanical, verifiable work — metadata extraction, citation mining, annotation standardization — while reserving a more capable model for tasks genuinely requiring synthesis, genre analysis, or cross-document reconciliation, such as the Gate-D thesis-space comparison itself. This mirrors, at manuscript-production scale, the identical cost-tiering discipline documented throughout Part I of this report as a design principle of the software project itself (§2.4): default to the cheapest tier capable of the task, promote only where the task genuinely demands it.

Delegation and moderation. Both the software-development side and the research side of the project’s own two-agent structure were each authorized to delegate task-specific work to further subagents where doing so was more efficient, with every delegation recorded by role in the shared task ledger — but every such delegation remained under the supervision of a single primary moderator (the author, throughout), and every output from any agent or subagent, before being adopted into either the manuscript or the software it describes, passed through the author’s own review-and-correction gate. The research project’s own records note candidly, and specifically, at least one documented instance where a subagent returned a placeholder or overstated report over what turned out to be genuinely sound underlying work — caught and independently re-verified by the moderating process rather than being trusted either way on the strength of the subagent’s own self-report; this kind of self-caught verification failure is recorded in the project’s own log as a normal, expected part of the process’s own quality-control discipline, not concealed as an embarrassment.

Cost and scope discipline. The research project’s own governing plan is explicit that Cycle 1’s acquisition work should not assume a uniformly optimistic yield — each acquisition target is tagged, honestly, by its realistic access route (likely open access; paywalled with no known open route, which is logged and routed to the user rather than blocking the workstream; or a physical monograph requiring purchase) — and a standing contingency governs exactly what to do when a primary source cannot be lawfully obtained in time: continue drafting using an already-held secondary synthesis that discusses the primary, but cite that synthesis explicitly as secondary, and never attribute the unobtained primary’s specific findings, methods, or wording to it as though it had been read directly. This same discipline is directly visible in the finished manuscript’s own prose — for instance, in the Limitations section’s explicit statement that the primary expertise-canon studies underlying its own secondary sources “are named, not cited,” and in the Introduction’s care to attribute the classics-scholarship point specifically to Benson’s chapter rather than to Gerson’s separately unacquired, and therefore unused, reading of Plato.

Part II · Research Paper — 06 / 06

How the Project & the Paper Relate

Why the two bodies of work were kept structurally coupled rather than developed in isolation — and why neither could stand as a defensible piece of work without the other.

The relationship between the two bodies of work documented in this report is closer, and more disciplined, than a paper simply “being about” a piece of software it happens to describe. Several concrete points of contact are worth stating explicitly, since they are what make the paper’s own central claims defensible in a way a purely aspirational description of the software could not support.

The paper’s claims are checked, section by section, against the software’s actual, current implementation state — not its design intentions. Every one of the project_analysis/ documents summarized throughout Part I of this report (the architecture audit, the current-state audit, the intended-state audit, the limitations audit, and the underlying claim-and-feature matrix) exists specifically to give the manuscript a ground-truth, code-verified basis for every claim it makes about what Palimnote does — and the manuscript’s own Palimnote Design section (§16.4) visibly inherits this discipline directly, down to using the identical five-value implementation-status vocabulary (implemented, partially implemented, planned, proposed, unknown) that the software-side audits use throughout Part I. Where the software’s own documentation was found to be internally inconsistent — the GROBID local-versus-cloud discrepancy discussed in §4.3 and §16.4, or the dormant conversational-competency feature discussed in §7.4, §9.4, §16.4, and §16.5 — the manuscript does not silently resolve the inconsistency or quietly omit the feature; it states the honest, bounded fact directly in its own prose (a private, authenticated cloud instance is what actually runs in production; a schema-live, fully built feature currently produces zero observable effect for any real user because both of its release flags remain off), exactly as the software’s own internal logs state it.

The paper’s central thesis was itself selected, at Gate D, by weighing candidate framings specifically against which of the software’s genuinely implemented mechanisms each framing could honestly draw on — not against which framing sounded most compelling in the abstract. The provenance-and-trust-calibration pillar was judged, in the research project’s own words, to have “the richest single-mechanism argument” precisely because the corresponding software mechanisms (per-annotation provenance, honest heuristic labeling, independent-dimension credibility scoring, citation-required chat answering) are, among the four candidate framings’ respective mechanism sets, the most fully and unambiguously implemented — a judgment the software-side research_implications.md document states directly: this theme is “arguably the best-instantiated theme in the whole system.” The paper’s supporting sequencing pillar and its explicitly bounded extended-mind lens were each selected, and each explicitly qualified, for the identical reason in the opposite direction — the sequencing mechanism is real and fully inspectable but genuinely lacks the fading property its own borrowed construct treats as defining, and the extended-mind reading is retained only as a bounded lens specifically because the software’s own mechanisms are shown, on direct inspection, to satisfy some but not all of the reading’s own criteria.

The two development processes — building the software and writing the paper — were structurally coupled through standing feedback loops, not run in isolation from one another. The manuscript’s own Methods section states this explicitly (§16.3, §17.1): the software-development side’s evolving, real state — its feature-flag decisions, its disabled tiers, its architectural constraints as recorded in its own project log — fed directly into the research side’s own implementation-status assessments, specifically so that a manuscript claim about what the system does tracks the live application rather than an aspirational design frozen at some earlier point in the software’s development. This is a substantive methodological claim in its own right, and it is the reason a reader of this report can trust that Part I’s account of the software (current as of the same date this report was produced) and Part II’s account of what the paper says about that software are not two independently-drifting snapshots, but were deliberately kept in sync by the research process’s own design.

Where the two bodies of work diverge, they diverge for a documented reason, and the divergence is itself informative. The clearest instance is the software’s own “Phases 19–24” completion program (§3), which is substantially about accessibility hardening, reliability floors, and production-verification work — none of which bears directly on the manuscript’s own theoretical argument, since the paper’s claims are about design-level mechanism correspondence, not about production reliability or measured accuracy floors. The manuscript is correct not to import claims about, for instance, the project’s explicit-citation-recall percentage (discussed in §14.1) as though they bore on its own thesis; that recall figure is a fact about extraction accuracy, and the paper’s argument is explicitly, deliberately pitched at the level of design analysis rather than measured performance, which is exactly why the paper never cites it.

In sum: the software project answers the question “what, precisely, has actually been built, and to what degree of completeness and honesty in its own self-description?” — the subject of Part I of this report. The research paper answers a narrower, differently-pitched question: “what can be said, at the level of rigorous design analysis grounded in an established scholarly literature, about what this particular, actually-built system instantiates — and, just as importantly, what it does not license anyone to conclude from it?” Neither document could stand as a defensible piece of work without the other: the paper’s discipline of never overclaiming an effect depends entirely on the software audits’ unglamorous, code-level honesty about what is and is not actually implemented and live; and the software project’s own documentation of its design rationale (the “Important Design Decisions and Rationale” table quoted throughout Part I) reads, in retrospect, like a working engineer’s version of the same theoretical vocabulary the paper later makes explicit and rigorous — provenance, verifiability, honest fallback labeling, and a considered refusal to claim more than the evidence, whether code-level or citation-level, actually supports.


This concludes the report. Part I was compiled from direct reading of the AutoCriticalEditionProject source code, its Drizzle schema, its internal project log and architecture plan, and its generated project-status tracker. Part II was compiled from direct reading of the Palimnote_Research manuscript’s LaTeX source, its governing plan, decision log, and skill definitions, and its recorded adversarial-review and status history — all as they existed in this repository as of July 23, 2026.