From 52bb93a3dc6243970999db46995e1ae121ef2a8c Mon Sep 17 00:00:00 2001 From: pkuwkl Date: Fri, 17 Jul 2026 02:56:02 +0800 Subject: [PATCH 1/4] docs(contexts): establish canonical design contracts --- .../references/develop-components.md | 2 +- .../references/develop-components.md | 2 +- .gitattributes | 1 + .gitignore | 1 - AGENTS.md | 4 +- CLAUDE.md | 4 +- contexts/README.md | 17 +- contexts/design/README.md | 30 ++ .../en => contexts/design/flow}/news.md | 2 +- contexts/design/flow/paper.md | 337 ++++++++++++++++++ contexts/design/library/local.md | 110 ++++++ .../design/operations/naming.md | 0 contexts/dev/README.md | 4 +- contexts/usage/README.md | 4 +- docs/README.md | 6 +- .../en/assets/storage_system_design.png | Bin 62196 -> 0 bytes docs/design/en/storage.md | 132 ------- tests/contexts/__init__.py | 0 tests/contexts/test_paper_golden.py | 133 +++++++ tests/fixtures/paper/golden/expected.json | 144 ++++++++ tests/fixtures/paper/golden/paper.pdf | Bin 0 -> 6806 bytes tests/test_contexts.py | 5 + 22 files changed, 786 insertions(+), 152 deletions(-) create mode 100644 .gitattributes create mode 100644 contexts/design/README.md rename {docs/design/en => contexts/design/flow}/news.md (99%) create mode 100644 contexts/design/flow/paper.md create mode 100644 contexts/design/library/local.md rename docs/design/en/operations.md => contexts/design/operations/naming.md (100%) delete mode 100644 docs/design/en/assets/storage_system_design.png delete mode 100644 docs/design/en/storage.md create mode 100644 tests/contexts/__init__.py create mode 100644 tests/contexts/test_paper_golden.py create mode 100644 tests/fixtures/paper/golden/expected.json create mode 100644 tests/fixtures/paper/golden/paper.pdf diff --git a/.agents/skills/quantmind-dev/references/develop-components.md b/.agents/skills/quantmind-dev/references/develop-components.md index 7783585..5f06ff2 100644 --- a/.agents/skills/quantmind-dev/references/develop-components.md +++ b/.agents/skills/quantmind-dev/references/develop-components.md @@ -86,7 +86,7 @@ apply throughout. A public operation is complete only when all of these agree: -1. A stage and name consistent with `docs/design/en/operations.md`. +1. A stage and name consistent with `contexts/design/operations/naming.md`. 2. Typed input and config models, exported from `quantmind.configs`. 3. One intent-oriented `async def` operation exported from `quantmind.flows`, with its result contract exported from the canonical owning layer. diff --git a/.claude/skills/quantmind-dev/references/develop-components.md b/.claude/skills/quantmind-dev/references/develop-components.md index 7783585..5f06ff2 100644 --- a/.claude/skills/quantmind-dev/references/develop-components.md +++ b/.claude/skills/quantmind-dev/references/develop-components.md @@ -86,7 +86,7 @@ apply throughout. A public operation is complete only when all of these agree: -1. A stage and name consistent with `docs/design/en/operations.md`. +1. A stage and name consistent with `contexts/design/operations/naming.md`. 2. Typed input and config models, exported from `quantmind.configs`. 3. One intent-oriented `async def` operation exported from `quantmind.flows`, with its result contract exported from the canonical owning layer. diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..49dd532 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +tests/fixtures/paper/golden/*.pdf binary diff --git a/.gitignore b/.gitignore index 539aa6c..bcb1663 100644 --- a/.gitignore +++ b/.gitignore @@ -25,7 +25,6 @@ demo_data/ # Local-only development scratch notes (not tracked) new_feature.md -docs/design/zh/next-step-architecture.md docs/superpowers/ # Coverage artifacts (generated by pytest --cov) diff --git a/AGENTS.md b/AGENTS.md index f05b5c3..c6ebef2 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -73,8 +73,8 @@ the user explicitly authorizes it — fix the underlying issue instead. 6. **No meaningless wrappers** — a method must add logic, abstraction, or a side effect beyond the call it wraps; otherwise inline it. 7. **Name public operations by intent** — follow - `docs/design/en/operations.md`; use stage verbs, and reserve `pipeline` for - deliberate multi-stage composition. + `contexts/design/operations/naming.md`; use stage verbs, and reserve + `pipeline` for deliberate multi-stage composition. ## Tests and Examples diff --git a/CLAUDE.md b/CLAUDE.md index 37109ba..79d5155 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -74,8 +74,8 @@ the user explicitly authorizes it — fix the underlying issue instead. 6. **No meaningless wrappers** — a method must add logic, abstraction, or a side effect beyond the call it wraps; otherwise inline it. 7. **Name public operations by intent** — follow - `docs/design/en/operations.md`; use stage verbs, and reserve `pipeline` for - deliberate multi-stage composition. + `contexts/design/operations/naming.md`; use stage verbs, and reserve + `pipeline` for deliberate multi-stage composition. ## Tests and Examples diff --git a/contexts/README.md b/contexts/README.md index 8edf745..38fdea9 100644 --- a/contexts/README.md +++ b/contexts/README.md @@ -1,18 +1,25 @@ # QuantMind Repository Context -This directory is the public repository routing layer for coding agents. Start -with the index that matches the work you are doing: +This directory is the public repository context system for coding agents and +maintainers. Start with the index that matches the work you are doing: - [Develop QuantMind](dev/README.md) for architecture, contribution, testing, and verification guidance. - [Use QuantMind](usage/README.md) for public operations, examples, and usage documentation. +- [Design QuantMind](design/README.md) for canonical engineering designs and + cross-domain target contracts. ## Source of Truth and Ownership -- Context pages curate links and route readers; they do not copy full guidance. -- Code, public contracts, design documents, `AGENTS.md` / `CLAUDE.md`, and - workflow skills remain canonical for their stated domains. +- `contexts/design/` is the canonical source for engineering design decisions + and target contracts. +- `contexts/dev/` and `contexts/usage/` curate links and route readers; they do + not copy full guidance. +- Code and tests remain authoritative for current runtime behavior. Design + pages identify current gaps when they describe behavior that has not landed. +- `docs/` remains the home for user-facing guides, examples, and catalogs. It + may link to canonical design context but must not maintain a second copy. - Update a component's context index only when its discoverable entry points change, not for every implementation change. - Repository maintainers own this shared structure. Component contributors own diff --git a/contexts/design/README.md b/contexts/design/README.md new file mode 100644 index 0000000..0eda5ea --- /dev/null +++ b/contexts/design/README.md @@ -0,0 +1,30 @@ +# QuantMind Design + +This directory is the canonical home for QuantMind engineering design. Use it +for accepted ownership boundaries, cross-domain contracts, and target behavior +that implementation work must preserve. + +## Design Index + +| Domain | Design | +|---|---| +| Flow | [Paper end-to-end contract](flow/paper.md) | +| Flow | [News collection](flow/news.md) | +| Library | [Local semantic knowledge library](library/local.md) | +| Operations | [Public operation naming](operations/naming.md) | + +## Organization Rules + +- Organize designs directly by owning domain, such as `flow/`, `knowledge/`, + `library/`, `operations/`, or `preprocess/`. Do not add an intermediate + `components/` directory. +- Keep this page as the single global design index. Domain directories do not + need their own index. +- Add a design page only for real design content. Do not create empty + directories, placeholders, or speculative component pages. +- State whether a document describes current behavior, a target contract, or + both. Target contracts must identify current gaps so readers do not mistake + an intended guarantee for an implemented one. +- Keep code and tests authoritative for current runtime behavior. Keep `docs/` + focused on user-facing guides, examples, and catalogs; those pages may link + here but must not maintain a second design copy. diff --git a/docs/design/en/news.md b/contexts/design/flow/news.md similarity index 99% rename from docs/design/en/news.md rename to contexts/design/flow/news.md index 4943126..24a1bfa 100644 --- a/docs/design/en/news.md +++ b/contexts/design/flow/news.md @@ -8,7 +8,7 @@ intent-oriented collection operation, one time-window input, deterministic preprocessing, and explicit partial-failure reporting. Its public name follows the -[operation naming contract](operations.md): collection returns source-faithful +[operation naming contract](../operations/naming.md): collection returns source-faithful evidence and remains separate from semantic knowledge extraction. The primary requirement is that any caller can request a complete, one-shot diff --git a/contexts/design/flow/paper.md b/contexts/design/flow/paper.md new file mode 100644 index 0000000..7d03165 --- /dev/null +++ b/contexts/design/flow/paper.md @@ -0,0 +1,337 @@ +# Paper End-to-End Design Contract + +- **Status**: Target contract with current gaps called out below +- **Scope**: Paper source resolution through canonical `Paper` assembly +- **Golden span convention**: PDF pages, 1-based and inclusive + +## Decision Summary + +Paper extraction is a staged operation with one canonical output boundary: + +```text +Paper input + -> resolve and fetch + -> page-preserving source document plus authoritative metadata + -> structural extraction draft + -> deterministic canonical assembly + -> tree and provenance validation + -> Paper +``` + +Deterministic code owns source evidence, canonical identity, graph structure, +page slicing, citations, and validation. A model or future PageIndex adapter +may propose semantic structure, titles, summaries, and source spans, but its +output is a draft rather than a canonical `Paper`. + +PageIndex is not a prerequisite. The pipeline first preserves ordered source +pages and uses a narrow tree-builder seam; PageIndex can later implement that +seam without changing the `Paper` schema or taking ownership of canonical IDs. + +## Product and Ownership Boundary + +This contract covers one extraction result. It does not make Paper extraction +responsible for persistence, retrieval, or answer generation. + +| Concern | Owner | +|---|---| +| Resolve identifiers, fetch bytes, parse pages, and hash source content | `quantmind.preprocess` | +| Configure the operation and select the input variant | `quantmind.configs` | +| Produce semantic structure and assemble a canonical result | `quantmind.flows` | +| Define `Paper`, `TreeKnowledge`, `TreeNode`, source, citation, and extraction schemas | `quantmind.knowledge` | +| Persist and semantically index canonical knowledge | `quantmind.library` | +| Navigate a tree and synthesize an answer | A future `quantmind.mind` consumer or agent application | + +`flow/` is a documentation grouping for this cross-domain behavior. It does +not require a new `*_flow` public API and does not override the +[public operation naming contract](../operations/naming.md). + +## Inputs and Source Resolution + +### Supported input intents + +The target pipeline accepts the existing `PaperInput` intents with the +following semantics: + +| Input | Resolution behavior | +|---|---| +| `ArxivIdentifier` | Resolve the identifier to an exact arXiv version, authoritative metadata, and PDF bytes. Preserve the resolved source URL and version-specific availability time. | +| `HttpUrl` | Follow bounded redirects and accept supported PDF, HTML, Markdown, or plain-text content. Record the final canonical URL and fetched representation. | +| `LocalFilePath` | Read a supported PDF, HTML, Markdown, or plain-text file. The caller owns file retention; extraction records a local source reference and content hash. | +| `RawText` | Treat the supplied text as one non-paginated source document. It can produce a `Paper`, but it cannot claim PDF page spans. | + +`DoiIdentifier` remains explicitly unsupported until an open-access resolver +can produce an exact fetchable representation. A DOI landing page alone is +metadata, not necessarily the paper content. + +### Explicitly unsupported inputs + +V1 does not claim support for: + +- password-protected or corrupt PDFs; +- image-only scans that require OCR; +- authenticated, paywalled, or dynamically rendered sources without a + separately authorized fetch adapter; +- unsupported binary or media content types; +- a DOI that cannot be resolved to accessible paper content; +- a source whose exact fetched representation cannot be identified. + +Unsupported input fails before semantic extraction. The pipeline must not send +an error page, login page, or unresolved metadata page to a model and call the +result a successfully extracted paper. + +### Resolution rules + +Resolution produces one exact source version. Redirects, arXiv revisions, and +content negotiation are resolved before parsing. The resolved URI and SHA-256 +hash identify the bytes used for this extraction. A retry may fetch a changed +representation; if its content hash changes, it is a new source version and +must not be merged silently with an earlier attempt. + +## Page-Preserving Source Document + +PDF parsing must preserve ordered pages before any tree builder runs. The +conceptual deterministic handoff has this shape: + +```text +PaperSourceDocument +├── source metadata and exact content hash +├── span unit and indexing convention +└── ordered pages + ├── page number + ├── extracted text, including an empty string when the page has no text + └── optional deterministic layout or outline signals +``` + +Required properties: + +- PDF page numbers are 1-based and inclusive everywhere in the Paper golden + contract, structural drafts, and citations. +- Every physical page remains represented and in order. Empty pages are not + dropped because doing so would renumber later evidence. +- Text normalization may remove parser noise, but it must not erase the page + boundary or change which page owns an anchor. +- The exact source hash, parser identity/version, and any deterministic + normalization identity are available to provenance assembly. +- HTML, Markdown, plain text, and `RawText` are non-paginated. They retain + source text and character evidence but do not invent PDF page numbers. +- A page-based tree builder, including a future PageIndex adapter, accepts only + a source document whose span unit is `pdf_page`. + +The page-preserving document is an extraction intermediate, not a second +canonical knowledge schema. Raw PDF/HTML retention remains caller-owned and is +not embedded inside `Paper` by this contract. + +## Authoritative Metadata + +Source-derived metadata and model-derived semantics have different authority. +The model may fill an explicitly missing semantic field, but it may not +overwrite authoritative source evidence. + +| Field or fact | Authority and canonical destination | +|---|---| +| Resolved source URI and source kind | Resolver/fetcher; `SourceRef.kind` and `SourceRef.uri` | +| Exact fetched bytes and content hash | Fetcher; `SourceRef.content_hash` plus caller-owned raw artifact | +| Fetch time | Fetcher clock; `SourceRef.fetched_at` | +| Publication/version time | Authoritative provider metadata; establishes when the exact version became public and must not be replaced with the first-version date | +| Source availability time | Exact-version provider metadata when available; `Paper.available_at`. Fetch time is a conservative upper bound when publication availability is unknown. | +| Information cutoff | A source-declared study/data cutoff when present; otherwise the exact version publication time is a documented conservative fallback for `Paper.as_of`, never fetch time | +| Authors and authoritative title | Provider or document metadata; `Paper.authors` and root title unless the metadata is missing or demonstrably invalid | +| Extraction model, operation, run, and time | Runtime; `ExtractionRef` | +| Parser and normalization identity | Deterministic runtime provenance; retained with the extraction run until the canonical item schema has an explicit item-level field | +| Summaries, semantic section titles, methodology, findings, and limitations | Structural draft producer; validated before canonical assembly | +| Canonical item/node IDs and graph links | Deterministic assembly code only | + +For an arXiv revision, `available_at` refers to the exact revision used, not the +first submission date. `as_of` is the information cutoff represented by the +paper; it must remain distinct from fetch time. Unknown publication or +availability metadata remains unknown rather than being guessed by a model. + +## Structural Extraction Draft + +A draft is the only provider- or model-facing structural contract. It may +contain: + +- adapter-local node keys used only within the draft; +- candidate titles and summaries; +- an ordered parent/child outline expressed with draft-local references; +- candidate page spans using the source document's explicit span unit; +- confidence or diagnostic information needed to accept, repair, or reject the + draft. + +A draft must not contain canonical `Paper.id`, `TreeNode.node_id`, canonical +`parent_id`/`children_ids`, copied source text, authoritative metadata, or a +provider-specific object that leaks through the public result. + +The structural producer is intentionally narrow: conceptually it maps one +`PaperSourceDocument` to one structural draft. The default implementation can +use an LLM; a future PageIndex adapter can provide the same kind of draft. +Neither becomes the canonical `Paper` schema. + +## Deterministic Canonical Assembly + +Assembly treats the accepted draft as untrusted input and performs these steps +in code: + +1. Generate canonical item and node IDs. External or draft-local IDs never + escape their adapter. +2. Resolve each draft parent reference and derive both `parent_id` and ordered + `children_ids` from one checked relation. +3. Assign sibling positions deterministically from the accepted order. +4. Validate and normalize page spans against the preserved source pages. +5. Slice source content from the inclusive page range and construct citations + from the same source evidence. Model-generated paraphrases never become + source content or quotes. +6. Apply authoritative metadata and runtime extraction provenance. +7. Construct the canonical `Paper` and run the complete invariant validator. + +Canonical IDs are code-owned, but this contract does not require them to be +identical across independent extraction runs. Stable cross-run identity is a +separate deduplication decision. + +## Tree and Paper Invariants + +A successful `Paper` satisfies all of the following before it is returned: + +### Identity and root + +- `root_node_id` identifies exactly one entry in `nodes`. +- Every dictionary key equals that node's `node_id`. +- The root has `parent_id=None`; every other node has exactly one parent. +- All canonical item and node IDs are code-owned and unique within the result. + +### Edges and ordering + +- Every referenced parent and child exists. +- Parent/child relationships are bidirectionally consistent. +- A child appears at most once in a parent's `children_ids`. +- Sibling order is deterministic, and sibling positions are unique within one + parent. + +### Reachability and safe traversal + +- Every node is reachable from the root. +- The graph is acyclic and contains no self-edge. +- No node is shared by multiple parents. +- Depth-first walk and root-to-node path lookup terminate for every canonical + node. Unknown node lookup returns the documented safe result rather than + following unchecked links. + +### Source spans and citations + +- A PDF span uses `pdf_page`, starts at 1 or later, has + `start_page <= end_page`, and ends within the preserved PDF page count. +- Citation page ranges obey the same 1-based inclusive convention and source + bounds. +- Citation quotes and node content come from the identified source range. +- Non-paginated inputs do not carry fabricated PDF spans. +- Sibling spans may overlap. A child span is not required to be strictly + contained by its parent's span unless a later canonical rule adds that + independent requirement. + +## Branch Content and Source Slicing + +Branch nodes may retain source content. `content=None` remains useful for a +navigation-only node, but being a branch is not itself a reason to discard its +evidence. + +When content is present, deterministic code slices it from the node's tight +1-based inclusive source page range. Page-granular ranges can include a heading +or paragraph also used by an adjacent node; this is expected when spans +overlap. Assembly must not ask the model to reproduce source text, and it must +not derive a parent node's content by concatenating model summaries. + +Navigation and evidence loading remain separate operations: an agent can read +titles and summaries to select a node, then fetch the selected node's tight +source page range. A future PageIndex adapter may help build the outline, but +it does not own source-range fetching. + +## Determinism, Retry, and Failure + +| Stage | Determinism | Retry and failure contract | +|---|---|---| +| Input validation and resolution policy | Deterministic for one configuration | Invalid or unsupported input fails immediately. | +| Network fetch | Externally variable | Retry only bounded transient failures. Record and hash the exact successful response. Permanent status/content failures stop the operation. | +| PDF or text parsing | Deterministic for fixed bytes and parser version | Parser failure stops before semantic extraction. Do not skip corrupt pages or renumber around them. | +| Structural draft production | Nondeterministic when model-backed | Bounded retries may repair transport or draft-schema failures against the same source version. Each attempt is observable. | +| Canonical assembly | Deterministic for one accepted draft and source version, except generated UUID values | Invalid references, spans, or authoritative metadata conflicts reject the draft. | +| Invariant validation | Deterministic | Any failure rejects the entire result; no partial `Paper` is returned as success. | + +A successful result means one canonical `Paper` passed provenance, span, and +tree validation. Fetching bytes, obtaining a model response, or constructing a +Pydantic object alone is not success. Failure preserves enough stage and source +identity to diagnose or retry the operation, but it does not persist partial +canonical knowledge implicitly. + +## Output Boundary and Downstream Consumers + +Paper extraction returns canonical knowledge. The following are explicit +downstream consumers and stay outside this operation: + +- persistence and idempotent storage in `LocalKnowledgeLibrary`; +- embedding generation and semantic indexing; +- semantic retrieval across a collection; +- PageIndex-style tree navigation within one selected document; +- answer synthesis, conversational state, and citations in a final response; +- caller policy for retaining raw source bytes. + +Keeping these concerns separate lets Paper extraction land before PageIndex and +lets PageIndex arrive later without replacing semantic retrieval or the +canonical library. + +## Future PageIndex Compatibility Seam + +Future integration must preserve these decisions: + +1. PageIndex receives ordered source pages before any flattening step. +2. Its node IDs remain adapter-local. Canonical IDs and graph links are created + during deterministic assembly. +3. It may propose titles, summaries, ordering, and 1-based inclusive page + spans through the structural draft seam. +4. It does not become the `Paper`, `TreeKnowledge`, or `TreeNode` schema. +5. Outline navigation uses titles and summaries; evidence loading separately + fetches the selected tight source range. +6. Integration does not assume non-overlapping sibling spans or strict child + containment. + +## Golden Fixture Contract + +The repository-owned fixture lives at: + +```text +tests/fixtures/paper/golden/ +├── paper.pdf +└── expected.json +``` + +It is a small four-page synthetic paper with a nested subsection, multi-page +sections, and intentionally overlapping sibling page spans. `expected.json` +contains only stable facts: page count, titles and paths, 1-based inclusive +spans, distinctive text anchors, topology, and named invariants. It does not +pin summaries or any other wording produced by a model. + +The offline contract test parses the fixed PDF, checks every anchor against its +declared page, validates topology and invariants, and confirms the overlap case +remains representable. Paper extraction and future PageIndex work must reuse +this fixture rather than create a competing golden document. + +## Current Behavior and Known Gaps + +The repository does not yet guarantee the target pipeline above: + +- `pdf_to_markdown()` concatenates non-empty page text and drops page + boundaries and empty pages. +- `paper_flow()` sends the flattened document to one extraction agent and asks + it to return the canonical `Paper` directly. +- The model currently controls IDs, edges, citations, source fields, and + content instead of returning a restricted structural draft. +- Resolved source URL, content hash, publication/version time, fetch time, and + exact-version availability are not assembled authoritatively end to end. +- `DoiIdentifier` raises `NotImplementedError` because there is no accessible + content resolver. +- `TreeKnowledge` provides traversal helpers but does not yet enforce the full + root, edge, reachability, acyclicity, or span invariant set at construction. +- The structured-output failure tracked by issue #91 remains separate from + this design issue. + +Those gaps are implementation work after this contract. Adding PageIndex first +would not fix them and is not required to implement the staged Paper pipeline. diff --git a/contexts/design/library/local.md b/contexts/design/library/local.md new file mode 100644 index 0000000..7a3dc31 --- /dev/null +++ b/contexts/design/library/local.md @@ -0,0 +1,110 @@ +# Local Semantic Knowledge Library Design + +- **Status**: Current design +- **Runtime owner**: `quantmind.library` +- **User guide**: [`docs/library.md`](../../../docs/library.md) + +## Decision Summary + +`quantmind.library` owns persistence and semantic retrieval for canonical +QuantMind knowledge. It replaces the obsolete repository concept of a generic +storage layer that stored raw files, knowledge JSON, embeddings, and indexes +behind one extensible backend abstraction. + +The current public surface is deliberately domain-level: + +- `LocalKnowledgeLibrary` +- `SemanticQuery` +- `SemanticHit` + +There is no public `Storage`, `VectorStore`, `Retriever`, provider registry, or +backend hierarchy. A new backend abstraction is justified only by a second +real implementation and a stable shared contract. + +## Ownership Boundaries + +| Layer | Responsibility | +|---|---| +| `quantmind.knowledge` | Immutable canonical schemas and embedding projections; no I/O | +| `quantmind.library` | Persist canonical knowledge, maintain rebuildable semantic records, and return typed evidence | +| `quantmind.flows` | Produce canonical knowledge and optionally pass it to a library consumer | +| `quantmind.mind` or an agent application | Use retrieval as a tool and synthesize answers | +| Caller or source-specific pipeline | Retain raw PDF, HTML, media, and operational artifacts | + +Raw source retention is intentionally outside the V1 library. A canonical +`SourceRef` and citations preserve provenance identity; they do not turn the +library into an artifact store. + +## Canonical and Derived Data + +Canonical `BaseKnowledge` is the source of truth. Embeddings, projection text, +filter columns, and vector indexes are derived and rebuildable even when they +share one SQLite database. + +The local implementation separates three concerns: + +- `knowledge_items` stores one validated aggregate root per knowledge item. +- `knowledge_nodes` stores canonical `TreeNode` values separately with parent, + position, content hash, and item ownership. +- `semantic_records` stores item/root/node projections and vector metadata used + by exact cosine ranking. + +Concrete types do not get a table per Pydantic subtype. The aggregate-root plus +normalized-node design preserves typed reconstruction while giving future tree +navigation a node-level persistence boundary. + +## Retrieval Grain and Identity + +- A `FlattenKnowledge` item produces one semantic target from its exact + `embedding_text()` projection. +- A `TreeKnowledge` item produces one item target for its root with + `node_id=None`, plus one target for every non-root node. +- Target identity distinguishes a whole item from one of its nodes. +- `SemanticHit.matched_text` is the exact projection used for ranking. +- Callers use `get(item_id)` to resolve full canonical knowledge and node paths. + +Re-putting unchanged knowledge with the same canonical item ID is idempotent +and reuses its embeddings. This does not claim deduplication across independent +extraction runs that generated different canonical IDs. + +## Derived-Index Invalidation + +Every stored vector records the information needed to decide whether it is +reusable: embedding model, dimension, projection hash, source content hash, +knowledge schema version, and projection schema version. + +Changed metadata invalidates only affected targets. Canonical deletion removes +the item, its normalized nodes, and its derived semantic records in one +transaction. Corrupt dimensions, vector bytes, canonical payloads, or orphaned +derived records fail explicitly instead of producing a plausible partial hit. + +## Financial-Time Semantics + +`as_of` and `available_at` answer different questions: + +- `as_of` is the information cutoff represented by the knowledge. +- `available_at` is when that source version became observable. + +`available_at_before` excludes records whose availability is unknown or after +the cutoff. `as_of_before` alone does not prevent look-ahead. Source kind, +item type, confidence, tags, tree ID, and both time cutoffs combine before +ranking. + +## Local Ranking Choice + +SQLite provides transactions, foreign keys, typed reconstruction, and explicit +stale/corrupt behavior for canonical knowledge. NumPy exact cosine ranking is +sufficient for the current local scale and keeps the derived search layer +replaceable without changing user code. + +A future approximate or remote index may replace the private derived layer. It +does not replace canonical persistence and must not leak a provider-specific +result through `SemanticHit`. + +## Non-goals + +- raw source artifact storage; +- a generic RAG framework or answer synthesis; +- a public embedder, vector-store, retriever, or backend registry; +- PageIndex tree construction or navigation; +- cross-run knowledge deduplication without a separate stable-ID contract. diff --git a/docs/design/en/operations.md b/contexts/design/operations/naming.md similarity index 100% rename from docs/design/en/operations.md rename to contexts/design/operations/naming.md diff --git a/contexts/dev/README.md b/contexts/dev/README.md index 30d1e4e..6741c57 100644 --- a/contexts/dev/README.md +++ b/contexts/dev/README.md @@ -10,8 +10,8 @@ canonical source instead of treating this page as a replacement for it. | Issue and pull-request labels | [Repository label guidance](labels.md) | | Issue and pull-request body formatting | [GitHub writing style](github-writing.md) | | Public operation and source catalog | [`docs/README.md`](../../docs/README.md) | -| Public operation naming | [Operation naming contract](../../docs/design/en/operations.md) | -| Component designs | [`docs/design/`](../../docs/design/) | +| Public operation naming | [Operation naming contract](../design/operations/naming.md) | +| Component designs | [Canonical design index](../design/README.md) | | Test patterns and coverage | [`tests/`](../../tests/) | | Focused runnable examples | [`examples/`](../../examples/) | | Deterministic verification | [`scripts/verify.sh`](../../scripts/verify.sh) | diff --git a/contexts/usage/README.md b/contexts/usage/README.md index 93289a3..6524677 100644 --- a/contexts/usage/README.md +++ b/contexts/usage/README.md @@ -7,8 +7,8 @@ current public API, focused examples, and component-specific guidance. |---|---| | Current operations, inputs, results, and sources | [Public component catalog](../../docs/README.md) | | Installation and common usage | [Root README usage](../../README.md#-usage-examples) | -| Paper extraction | [Paper guide](../../docs/papers.md) | -| News collection | [News design and behavior](../../docs/design/en/news.md) | +| Paper extraction | [Paper E2E design contract](../design/flow/paper.md) | +| News collection | [News design and behavior](../design/flow/news.md) | | Local semantic search | [Library guide](../../docs/library.md) and [focused example](../../examples/library/README.md) | | Runnable operation examples | [`examples/flows/`](../../examples/flows/) | | Focused preprocessing examples | [`examples/preprocess/`](../../examples/preprocess/) | diff --git a/docs/README.md b/docs/README.md index 1a05f3e..ea4599d 100644 --- a/docs/README.md +++ b/docs/README.md @@ -6,7 +6,7 @@ acquisition mechanics remain internal unless they are intentionally documented as a public preprocessing primitive. Public callable names follow the -[operation naming contract](design/en/operations.md). The runtime API serves +[operation naming contract](../contexts/design/operations/naming.md). The runtime API serves Python callers; coding-agent guidance lives in the repository development harness. @@ -14,8 +14,8 @@ harness. | Operation | Import | Input and config | Result | Example | Design or guide | |---|---|---|---|---|---| -| Paper extraction | `quantmind.flows.paper_flow` | `PaperInput`, `PaperFlowCfg` | `Paper` | [README usage](../README.md#-usage-examples) | [Papers](papers.md) | -| News collection | `quantmind.flows.collect_news` | `NewsWindow`, `NewsCollectionCfg` | `NewsBatch` from `quantmind.preprocess` | [Collect news](../examples/flows/collect_news.py) | [News collection design](design/en/news.md) | +| Paper extraction | `quantmind.flows.paper_flow` | `PaperInput`, `PaperFlowCfg` | `Paper` | [README usage](../README.md#-usage-examples) | [Paper E2E design](../contexts/design/flow/paper.md) | +| News collection | `quantmind.flows.collect_news` | `NewsWindow`, `NewsCollectionCfg` | `NewsBatch` from `quantmind.preprocess` | [Collect news](../examples/flows/collect_news.py) | [News collection design](../contexts/design/flow/news.md) | | Bounded fan-out | `quantmind.flows.batch_run` | Operation inputs and shared config | `BatchResult` | [README usage](../README.md#-usage-examples) | API docstrings | | Local semantic search | `quantmind.library.LocalKnowledgeLibrary` | `BaseKnowledge`, `SemanticQuery` | `list[SemanticHit]` | [Library example](../examples/library/README.md) | [Library guide](library.md) | diff --git a/docs/design/en/assets/storage_system_design.png b/docs/design/en/assets/storage_system_design.png deleted file mode 100644 index 275b9eddee79d4e0c2cb485721c69393bd1abbbc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 62196 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 SBl(K~2s~Z=T-G@yGywoSp{mOO diff --git a/docs/design/en/storage.md b/docs/design/en/storage.md deleted file mode 100644 index 2fa583e..0000000 --- a/docs/design/en/storage.md +++ /dev/null @@ -1,132 +0,0 @@ -# QuantMind Storage Layer: Design Document - -![Storage System Design](./assets/storage_system_design.png) - -## 1. Introduction & Motivation (The "Why") - -The QuantMind framework is designed to process vast amounts of unstructured financial content into a structured, queryable knowledge base. The **Storage Layer** is the heart of this system, serving as the persistent, reliable, and efficient foundation for this knowledge base. - -Its primary motivations are: - -- **Centralized Data Management**: To provide a single source of truth for all processed data, including original source files, structured knowledge, vector embeddings, and operational metadata. -- **Decoupling & Modularity**: To separate the logic of data persistence from the components that produce or consume it (e.g., `Parsers`, `Taggers`, `Retrievers`). This allows other components to operate without worrying about the underlying storage mechanism (e.g., file system, database). -- **Efficiency & Performance**: The knowledge base can grow to millions of items. An efficient storage and retrieval mechanism is critical for the system's overall performance, especially for lookup-intensive operations. -- **Data Integrity & Resilience**: To ensure that data is stored correctly and consistently, and to provide mechanisms for recovery and maintenance in case of inconsistencies. -- **Extensibility**: To design a system that can easily be extended to support different storage backends (e.g., local files, cloud storage, a vector database) without requiring changes to the core application logic. - -## 2. Architecture & Core Concepts (The "How") - -The storage layer is built on a set of core principles: an abstract interface, a concrete file-based implementation, and a high-performance indexing system. - -### 2.1. `BaseStorage`: The Abstract Contract - -The architecture starts with `quantmind.storage.base.BaseStorage`, an abstract base class (ABC) that defines the universal interface for any storage implementation. This ensures that any new storage backend will be a drop-in replacement. - -The contract manages four distinct types of data: - -- **Raw Files**: The original, unprocessed source files (e.g., PDFs from ArXiv, raw HTML). -- **Knowledges**: Structured `KnowledgeItem` objects (including specialized types like `Paper`), typically stored as JSON. These represent the processed, value-added data. -- **Embeddings**: Vector representations of the knowledge items, used for semantic search and retrieval. -- **Extra**: Auxiliary data, primarily used for storing indexes and other metadata that supports the storage layer's operations. - -### 2.2. Specialized Knowledge Processing - -To handle the unique needs of different data types, the `BaseStorage` interface includes a specialized processing method: - -```python -def process_knowledge(self, knowledge: KnowledgeItem) -> str: -``` - -This method acts as a router. While it stores any `KnowledgeItem` by default, it contains special logic for subtypes. For instance, when it receives a `Paper` object: - -1. It first stores the `Paper` metadata as a knowledge item. -2. It then checks if the `Paper` has a `pdf_url`. -3. If a URL exists and the corresponding PDF is not already in the raw file storage, it automatically downloads the content and stores it using `store_raw_file`. - -This design elegantly handles type-specific logic within the storage layer, simplifying the upstream workflow agents. - -### 2.3. `LocalStorage`: A Concrete Filesystem Implementation - -`quantmind.storage.local_storage.LocalStorage` is the default, file-based implementation of `BaseStorage`. - -#### Directory Structure - -It organizes data on the filesystem in a clear, hierarchical structure defined by `LocalStorageConfig`: - -```text -/ -├── 📂 raw_files/ # Raw source files (e.g., paper_id.pdf) -├── 📂 knowledges/ # Structured data (e.g., paper_id.json) -├── 📂 embeddings/ # Vector embeddings (e.g., paper_id.json) -└── 📂 extra/ # Indexes and other metadata - ├── 📄 raw_files_index.json - ├── 📄 knowledges_index.json - └── 📄 embeddings_index.json -``` - -#### High-Performance Indexing System - -A key challenge with a simple filesystem approach is the inefficiency of lookups. Iterating through a directory to find a file (`glob`) is an O(n) operation, which becomes unacceptably slow as the dataset grows. - -To solve this, `LocalStorage` implements a **high-performance, persistent indexing system**. - -**How it Works:** - -1. **Index Files**: For each core data type (`raw_files`, `knowledges`, `embeddings`), a corresponding JSON index file is maintained in the `extra/` directory. This index is a simple key-value map: - - `file_id` -> `{ "path": "relative/path/to/file", "extension": ".pdf" }` - -2. **In-Memory Cache**: On initialization, `LocalStorage` loads these JSON files into in-memory dictionaries (`self._raw_files_index`, etc.) for instantaneous O(1) lookups. - -3. **Real-time Updates**: Every `store_*` or `delete_*` operation automatically updates both the in-memory index and the corresponding JSON file on disk, ensuring data consistency. - - ```python - # Example: store_raw_file - def store_raw_file(...): - # ... write file to disk ... - - # Update and save the index - self._raw_files_index[file_id] = {"path": ..., "extension": ...} - self._save_index("raw_files") - ``` - -4. **Efficient Lookups**: `get_*` methods now first consult the in-memory index for a near-instant lookup. The costly filesystem scan is only used as a last-resort fallback. - - ```python - # Example: get_raw_file - def get_raw_file(self, file_id: str): - # 1. Fast O(1) lookup in the index - if file_id in self._raw_files_index: - return ... - - # 2. Fallback to O(n) directory scan (if not found in index) - # ... glob filesystem ... - # If found, add to index for future fast lookups - ``` - -**Resilience and Self-Healing:** - -The indexing system is designed to be robust: - -- **Automatic Rebuilding**: If an index file is missing or corrupt on startup, `LocalStorage` will automatically scan the corresponding data directory and rebuild the index from scratch. -- **Self-Healing**: If an index entry points to a file that has been deleted from the filesystem externally, the `get_*` method will detect this, remove the stale entry from the index, and save the corrected index. -- **Manual Rebuild**: A public `rebuild_all_indexes()` method is provided for manual maintenance and data recovery. - -## 3. Configuration - -The storage layer is configured via `quantmind.config.storage.LocalStorageConfig`, a Pydantic model that allows for easy setup, primarily defining the `storage_dir`. - -```python -from quantmind.config import LocalStorageConfig - -# Default configuration -config = LocalStorageConfig() # Uses ./data - -# Custom configuration -config = LocalStorageConfig(storage_dir="/path/to/my/storage") -``` - -The config model's `model_post_init` hook automatically handles the creation of the storage directory and all its required subdirectories. - -## 4. Conclusion - -The QuantMind storage layer is a modular, extensible, and high-performance system for managing the project's knowledge base. By combining a clear abstract interface with an efficient, self-healing, file-based implementation, it provides a robust foundation that can scale effectively while remaining easy to manage and extend. The recent addition of the indexing system transforms its lookup performance from a potential bottleneck into a key strength. diff --git a/tests/contexts/__init__.py b/tests/contexts/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/contexts/test_paper_golden.py b/tests/contexts/test_paper_golden.py new file mode 100644 index 0000000..991c0e5 --- /dev/null +++ b/tests/contexts/test_paper_golden.py @@ -0,0 +1,133 @@ +import json +import unittest +from pathlib import Path + +import pymupdf + +_GOLDEN = Path(__file__).resolve().parents[1] / "fixtures" / "paper" / "golden" + + +class PaperGoldenFixtureTests(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + cls.expected = json.loads( + (_GOLDEN / "expected.json").read_text(encoding="utf-8") + ) + with pymupdf.open(_GOLDEN / "paper.pdf") as document: + cls.page_texts = [page.get_text() for page in document] + + def test_document_uses_pdf_golden_span_contract(self) -> None: + document = self.expected["document"] + + self.assertEqual(document["span_unit"], "pdf_page") + self.assertEqual(document["indexing"], "1-based-inclusive") + self.assertEqual(len(self.page_texts), document["page_count"]) + self.assertIn(document["title"], self.page_texts[0]) + + def test_titles_paths_spans_and_anchors_match_pdf(self) -> None: + page_count = self.expected["document"]["page_count"] + nodes = {node["key"]: node for node in self.expected["nodes"]} + + for key, node in nodes.items(): + with self.subTest(node=key): + start = node["span"]["start"] + end = node["span"]["end"] + self.assertGreaterEqual(start, 1) + self.assertLessEqual(start, end) + self.assertLessEqual(end, page_count) + + span_text = "\n".join(self.page_texts[start - 1 : end]) + self.assertIn(node["title"], span_text) + + anchor_pages = set() + for anchor in node["anchors"]: + page = anchor["page"] + anchor_pages.add(page) + self.assertGreaterEqual(page, start) + self.assertLessEqual(page, end) + self.assertIn(anchor["text"], self.page_texts[page - 1]) + + if start != end: + self.assertTrue({start, end}.issubset(anchor_pages)) + + expected_path = [] + cursor = node + path_keys = set() + while cursor is not None: + cursor_key = cursor["key"] + self.assertNotIn(cursor_key, path_keys) + path_keys.add(cursor_key) + expected_path.append(cursor["title"]) + parent = cursor["parent"] + cursor = nodes[parent] if parent is not None else None + self.assertEqual(node["path"], list(reversed(expected_path))) + + def test_topology_satisfies_declared_tree_invariants(self) -> None: + required_invariants = { + "single_root", + "node_keys_unique", + "parents_exist", + "children_exist", + "parent_child_bidirectional", + "all_nodes_reachable", + "acyclic", + "spans_within_document", + } + self.assertEqual(set(self.expected["invariants"]), required_invariants) + + nodes_list = self.expected["nodes"] + nodes = {node["key"]: node for node in nodes_list} + self.assertEqual(len(nodes), len(nodes_list)) + + roots = [node for node in nodes_list if node["parent"] is None] + self.assertEqual(len(roots), 1) + root = roots[0] + self.assertEqual(root["key"], self.expected["document"]["root"]) + + child_owners = {key: 0 for key in nodes} + for key, node in nodes.items(): + with self.subTest(node=key): + self.assertEqual( + len(node["children"]), len(set(node["children"])) + ) + if node["parent"] is not None: + self.assertIn(node["parent"], nodes) + for child_key in node["children"]: + self.assertIn(child_key, nodes) + self.assertEqual(nodes[child_key]["parent"], key) + child_owners[child_key] += 1 + + self.assertEqual(child_owners[root["key"]], 0) + for key, owner_count in child_owners.items(): + if key != root["key"]: + self.assertEqual(owner_count, 1) + + visiting = set() + visited = set() + + def visit(key: str) -> None: + self.assertNotIn(key, visiting, f"cycle detected at {key}") + if key in visited: + return + visiting.add(key) + for child_key in nodes[key]["children"]: + visit(child_key) + visiting.remove(key) + visited.add(key) + + visit(root["key"]) + self.assertEqual(visited, set(nodes)) + + def test_fixture_preserves_overlapping_sibling_spans(self) -> None: + nodes = {node["key"]: node for node in self.expected["nodes"]} + method = nodes["method"] + limitations = nodes["limitations"] + + self.assertEqual(method["parent"], limitations["parent"]) + self.assertLessEqual( + limitations["span"]["start"], method["span"]["end"] + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/fixtures/paper/golden/expected.json b/tests/fixtures/paper/golden/expected.json new file mode 100644 index 0000000..cc31564 --- /dev/null +++ b/tests/fixtures/paper/golden/expected.json @@ -0,0 +1,144 @@ +{ + "schema_version": "1.0", + "document": { + "title": "A Synthetic Cross-Sectional Momentum Study", + "root": "paper", + "page_count": 4, + "span_unit": "pdf_page", + "indexing": "1-based-inclusive" + }, + "nodes": [ + { + "key": "paper", + "title": "A Synthetic Cross-Sectional Momentum Study", + "path": [ + "A Synthetic Cross-Sectional Momentum Study" + ], + "span": { + "start": 1, + "end": 4 + }, + "anchors": [ + { + "page": 1, + "text": "A Synthetic Cross-Sectional Momentum Study" + }, + { + "page": 4, + "text": "This page closes the synthetic paper fixture." + } + ], + "parent": null, + "children": [ + "introduction", + "method", + "limitations" + ] + }, + { + "key": "introduction", + "title": "1 Introduction", + "path": [ + "A Synthetic Cross-Sectional Momentum Study", + "1 Introduction" + ], + "span": { + "start": 1, + "end": 2 + }, + "anchors": [ + { + "page": 1, + "text": "The introduction defines a twelve-month lookback signal." + }, + { + "page": 2, + "text": "The continuation discusses turnover and transaction costs." + } + ], + "parent": "paper", + "children": [] + }, + { + "key": "method", + "title": "2 Method", + "path": [ + "A Synthetic Cross-Sectional Momentum Study", + "2 Method" + ], + "span": { + "start": 2, + "end": 4 + }, + "anchors": [ + { + "page": 2, + "text": "The method ranks securities by trailing excess return." + }, + { + "page": 4, + "text": "Risk scaling completes the method on the final page." + } + ], + "parent": "paper", + "children": [ + "portfolio-construction" + ] + }, + { + "key": "portfolio-construction", + "title": "2.1 Portfolio Construction", + "path": [ + "A Synthetic Cross-Sectional Momentum Study", + "2 Method", + "2.1 Portfolio Construction" + ], + "span": { + "start": 3, + "end": 4 + }, + "anchors": [ + { + "page": 3, + "text": "The portfolio subsection forms equal-weighted quintiles." + }, + { + "page": 4, + "text": "The top and bottom quintiles define the long-short portfolio." + } + ], + "parent": "method", + "children": [] + }, + { + "key": "limitations", + "title": "3 Limitations", + "path": [ + "A Synthetic Cross-Sectional Momentum Study", + "3 Limitations" + ], + "span": { + "start": 4, + "end": 4 + }, + "anchors": [ + { + "page": 4, + "text": "The fixture uses synthetic observations and no investable returns." + } + ], + "parent": "paper", + "children": [] + } + ], + "invariants": [ + "single_root", + "node_keys_unique", + "parents_exist", + "children_exist", + "parent_child_bidirectional", + "all_nodes_reachable", + "acyclic", + "spans_within_document" + ] +} diff --git a/tests/fixtures/paper/golden/paper.pdf b/tests/fixtures/paper/golden/paper.pdf new file mode 100644 index 0000000000000000000000000000000000000000..4dee59bf42c4469b12e31dd4d86840ee0837ad02 GIT binary patch literal 6806 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 OM+oF2o9)jBAO8nB>ac$R literal 0 HcmV?d00001 diff --git a/tests/test_contexts.py b/tests/test_contexts.py index c84e538..e55c4e7 100644 --- a/tests/test_contexts.py +++ b/tests/test_contexts.py @@ -11,6 +11,11 @@ def test_context_index_targets_exist(self) -> None: "contexts/dev/README.md", "contexts/dev/github-writing.md", "contexts/dev/labels.md", + "contexts/design/README.md", + "contexts/design/flow/news.md", + "contexts/design/flow/paper.md", + "contexts/design/library/local.md", + "contexts/design/operations/naming.md", "contexts/usage/README.md", ) markdown_link = re.compile(r"\[[^]]+]\(([^)]+)\)") From c2097af0fd4ccd3a8dc3f7cdfd424e8de8db0d72 Mon Sep 17 00:00:00 2001 From: pkuwkl Date: Fri, 17 Jul 2026 14:33:51 +0800 Subject: [PATCH 2/4] docs(contexts): add progressive disclosure previews --- AGENTS.md | 15 ++++++++++ CLAUDE.md | 15 ++++++++++ contexts/README.md | 18 ++++++++++++ contexts/design/README.md | 16 +++++++++++ contexts/design/flow/news.md | 20 +++++++++++++ contexts/design/flow/paper.md | 27 +++++++++++++++-- contexts/design/library/local.md | 19 ++++++++++-- contexts/design/operations/naming.md | 15 ++++++++++ contexts/dev/README.md | 18 ++++++++++++ contexts/dev/github-writing.md | 15 ++++++++++ contexts/dev/labels.md | 20 +++++++++++++ contexts/usage/README.md | 20 +++++++++++++ tests/test_contexts.py | 43 ++++++++++++++++++++++++++++ 13 files changed, 256 insertions(+), 5 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index c6ebef2..b2bb4d3 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -6,6 +6,21 @@ aligned with `CLAUDE.md` (same core rules); update both in the same change. Use [`contexts/README.md`](contexts/README.md) as the repository information entry point for either development or library-usage work. +## Progressive Context Loading + +Pages under `contexts/` are agent-facing references designed for progressive +disclosure: + +1. Read lines 1-80 first. The preview contains `Quick Summary` and `Contents` + sections that explain the page's purpose, authority, and scope. +2. Use that preview to decide whether the page applies. Do not preload sibling + pages or follow unrelated links. +3. When a page applies, read the entire page before changing code, contracts, + or repository guidance. The preview routes work; it does not replace the + detailed contract. +4. Follow directly linked canonical sources only as the task requires. Avoid + deep reference chains and duplicate guidance in working context. + ## What This Is QuantMind is a knowledge extraction and retrieval library for quantitative diff --git a/CLAUDE.md b/CLAUDE.md index 79d5155..1533a21 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -7,6 +7,21 @@ update both in the same change. Use [`contexts/README.md`](contexts/README.md) as the repository information entry point for either development or library-usage work. +## Progressive Context Loading + +Pages under `contexts/` are agent-facing references designed for progressive +disclosure: + +1. Read lines 1-80 first. The preview contains `Quick Summary` and `Contents` + sections that explain the page's purpose, authority, and scope. +2. Use that preview to decide whether the page applies. Do not preload sibling + pages or follow unrelated links. +3. When a page applies, read the entire page before changing code, contracts, + or repository guidance. The preview routes work; it does not replace the + detailed contract. +4. Follow directly linked canonical sources only as the task requires. Avoid + deep reference chains and duplicate guidance in working context. + ## What This Is QuantMind is a knowledge extraction and retrieval library for quantitative diff --git a/contexts/README.md b/contexts/README.md index 38fdea9..e4b210d 100644 --- a/contexts/README.md +++ b/contexts/README.md @@ -1,5 +1,23 @@ # QuantMind Repository Context +## Quick Summary + +- **Purpose**: Route agents to the minimum canonical context needed for a + QuantMind task. +- **Read when**: Starting repository development, library usage, or design + work. +- **Load next**: Choose exactly one primary route below, then follow only links + required by the task. +- **Authority**: Design context is canonical for accepted contracts; code and + tests remain authoritative for current runtime behavior. + +## Contents + +- [Routes](#routes) +- [Source of Truth and Ownership](#source-of-truth-and-ownership) + +## Routes + This directory is the public repository context system for coding agents and maintainers. Start with the index that matches the work you are doing: diff --git a/contexts/design/README.md b/contexts/design/README.md index 0eda5ea..e324f40 100644 --- a/contexts/design/README.md +++ b/contexts/design/README.md @@ -1,5 +1,21 @@ # QuantMind Design +## Quick Summary + +- **Purpose**: Index accepted engineering designs and cross-domain target + contracts. +- **Read when**: A task changes architecture, ownership boundaries, public + operation semantics, or a documented invariant. +- **Load next**: Open only the domain design that matches the task, then read + that page in full before implementation. +- **Authority**: These pages define intended contracts; each page must identify + current gaps when the runtime does not yet satisfy them. + +## Contents + +- [Design Index](#design-index) +- [Organization Rules](#organization-rules) + This directory is the canonical home for QuantMind engineering design. Use it for accepted ownership boundaries, cross-domain contracts, and target behavior that implementation work must preserve. diff --git a/contexts/design/flow/news.md b/contexts/design/flow/news.md index 24a1bfa..25497cb 100644 --- a/contexts/design/flow/news.md +++ b/contexts/design/flow/news.md @@ -1,5 +1,25 @@ # News Collection Design +## Quick Summary + +- **Purpose**: Define the current contract for collecting source-faithful public news evidence. +- **Read when**: Changing `collect_news`, `NewsWindow`, a news source adapter, batch completeness, or news verification. +- **Status**: Current design; the open-source package currently supports PR Newswire. +- **Core boundary**: Collection returns normalized evidence and honest failure/completeness metadata; semantic extraction, persistence, scheduling, and domain pruning remain downstream concerns. + +## Contents + +- [Status and Scope](#status-and-scope) +- [Design Principles](#design-principles) +- [Public API Contract](#public-api-contract) +- [Returned Data](#returned-data) +- [Collection Pipeline](#collection-pipeline) +- [Failure and Completeness Semantics](#failure-and-completeness-semantics) +- [Responsibility Boundary](#responsibility-boundary) +- [Verification Contract](#verification-contract) +- [Non-Goals and Extension Rule](#non-goals-and-extension-rule) +- [Adding a Public News Source](#adding-a-public-news-source) + ## Status and Scope This document defines the OSS contract for collecting public company news in diff --git a/contexts/design/flow/paper.md b/contexts/design/flow/paper.md index 7d03165..81d42fe 100644 --- a/contexts/design/flow/paper.md +++ b/contexts/design/flow/paper.md @@ -1,8 +1,29 @@ # Paper End-to-End Design Contract -- **Status**: Target contract with current gaps called out below -- **Scope**: Paper source resolution through canonical `Paper` assembly -- **Golden span convention**: PDF pages, 1-based and inclusive +## Quick Summary + +- **Purpose**: Define the target end-to-end contract from paper source resolution through canonical `Paper` assembly. +- **Read when**: Changing paper inputs, parsing, structural extraction, tree assembly, provenance, spans, or future PageIndex integration. +- **Status**: Target contract with current implementation gaps called out below. +- **Core boundary**: A model or PageIndex adapter may propose structure; deterministic code owns canonical IDs, edges, ordering, spans, citations, validation, and source truth. +- **Golden span convention**: PDF pages are 1-based and inclusive. + +## Contents + +- [Decision Summary](#decision-summary) +- [Product and Ownership Boundary](#product-and-ownership-boundary) +- [Inputs and Source Resolution](#inputs-and-source-resolution) +- [Page-Preserving Source Document](#page-preserving-source-document) +- [Authoritative Metadata](#authoritative-metadata) +- [Structural Extraction Draft](#structural-extraction-draft) +- [Deterministic Canonical Assembly](#deterministic-canonical-assembly) +- [Tree and Paper Invariants](#tree-and-paper-invariants) +- [Branch Content and Source Slicing](#branch-content-and-source-slicing) +- [Determinism, Retry, and Failure](#determinism-retry-and-failure) +- [Output Boundary and Downstream Consumers](#output-boundary-and-downstream-consumers) +- [Future PageIndex Compatibility Seam](#future-pageindex-compatibility-seam) +- [Golden Fixture Contract](#golden-fixture-contract) +- [Current Behavior and Known Gaps](#current-behavior-and-known-gaps) ## Decision Summary diff --git a/contexts/design/library/local.md b/contexts/design/library/local.md index 7a3dc31..227c739 100644 --- a/contexts/design/library/local.md +++ b/contexts/design/library/local.md @@ -1,9 +1,24 @@ # Local Semantic Knowledge Library Design -- **Status**: Current design -- **Runtime owner**: `quantmind.library` +## Quick Summary + +- **Purpose**: Define local persistence and semantic retrieval for validated QuantMind knowledge. +- **Read when**: Changing `LocalKnowledgeLibrary`, indexing, semantic filters, retrieval identity, or storage ownership. +- **Status**: Current design owned at runtime by `quantmind.library`. +- **Core boundary**: Canonical knowledge is source truth, semantic records and vectors are derived indexes, and raw source artifacts remain caller-owned. - **User guide**: [`docs/library.md`](../../../docs/library.md) +## Contents + +- [Decision Summary](#decision-summary) +- [Ownership Boundaries](#ownership-boundaries) +- [Canonical and Derived Data](#canonical-and-derived-data) +- [Retrieval Grain and Identity](#retrieval-grain-and-identity) +- [Derived-Index Invalidation](#derived-index-invalidation) +- [Financial-Time Semantics](#financial-time-semantics) +- [Local Ranking Choice](#local-ranking-choice) +- [Non-goals](#non-goals) + ## Decision Summary `quantmind.library` owns persistence and semantic retrieval for canonical diff --git a/contexts/design/operations/naming.md b/contexts/design/operations/naming.md index c1866b0..2254f9a 100644 --- a/contexts/design/operations/naming.md +++ b/contexts/design/operations/naming.md @@ -1,5 +1,20 @@ # Public Operation Naming +## Quick Summary + +- **Purpose**: Define consistent names for public QuantMind operations and their input, config, and result types. +- **Read when**: Adding or renaming a public callable, operation type, or composed recipe. +- **Status**: Current naming direction; the legacy `paper_flow` name remains unchanged until a separately approved migration. +- **Core rule**: Name a public operation with its stage verb; reserve `pipeline` for composed recipes, and do not use `flow` as an operation verb. + +## Contents + +- [Scope](#scope) +- [Operation Stages](#operation-stages) +- [Type Names](#type-names) +- [Current API](#current-api) +- [Review Checklist](#review-checklist) + ## Scope This document defines how public QuantMind callables communicate intent. It is diff --git a/contexts/dev/README.md b/contexts/dev/README.md index 6741c57..64a86c1 100644 --- a/contexts/dev/README.md +++ b/contexts/dev/README.md @@ -1,5 +1,21 @@ # Develop QuantMind +## Quick Summary + +- **Purpose**: Route contributors and coding agents to canonical development + rules without copying them here. +- **Read when**: Extending, fixing, reviewing, testing, or publishing QuantMind. +- **Load next**: Select the single row that matches the task; load additional + sources only when that workflow explicitly requires them. +- **Required check**: Run `bash scripts/verify.sh` for every repository change. + +## Contents + +- [Canonical Sources](#canonical-sources) +- [Verification Rule](#verification-rule) + +## Canonical Sources + Use this index when extending or maintaining the repository. Follow the linked canonical source instead of treating this page as a replacement for it. @@ -16,5 +32,7 @@ canonical source instead of treating this page as a replacement for it. | Focused runnable examples | [`examples/`](../../examples/) | | Deterministic verification | [`scripts/verify.sh`](../../scripts/verify.sh) | +## Verification Rule + Run `bash scripts/verify.sh` for every change. For a public-network component, also run the bounded live verifier listed in the public component catalog. diff --git a/contexts/dev/github-writing.md b/contexts/dev/github-writing.md index ef1485c..37a2ce7 100644 --- a/contexts/dev/github-writing.md +++ b/contexts/dev/github-writing.md @@ -1,5 +1,20 @@ # GitHub Writing Style +## Quick Summary + +- **Purpose**: Define the source-format contract for QuantMind GitHub Issue, + Pull Request, Discussion, and comment bodies. +- **Read when**: Drafting or editing any text submitted to GitHub. +- **Key rule**: Keep each logical paragraph, list item, checklist item, table + row, and blockquote paragraph on one physical source line. +- **Required check**: Read the raw body back after writing and remove + formatter-introduced hard wrapping without changing meaning. + +## Contents + +- [No Hard-wrapped Prose](#no-hard-wrapped-prose) +- [Write Workflow](#write-workflow) + This guide is the canonical source-format contract for QuantMind Issue, Pull Request, Discussion, and comment bodies. It applies to text submitted to GitHub, not to Markdown files stored in the repository. diff --git a/contexts/dev/labels.md b/contexts/dev/labels.md index 0ecb799..e8c2939 100644 --- a/contexts/dev/labels.md +++ b/contexts/dev/labels.md @@ -1,5 +1,25 @@ # Repository Label Guidance +## Quick Summary + +- **Purpose**: Select consistent labels for QuantMind issues and pull requests. +- **Read when**: Creating, updating, triaging, or reviewing an issue or PR. +- **Cardinality**: Use exactly one `type:` label, normally one `area:` label + (at most two), and optional `impact:` labels. +- **Selection rule**: Label accepted intent and ownership, not every file path + touched by an implementation. + +## Contents + +- [Cardinality](#cardinality) +- [Type Labels](#type-labels) +- [Area Labels](#area-labels) +- [Impact Labels](#impact-labels) +- [Resolution and Community Labels](#resolution-and-community-labels) +- [Issues and Pull Requests](#issues-and-pull-requests) +- [Examples](#examples) +- [Existing Label Migration](#existing-label-migration) + This file is the canonical decision guide for labeling QuantMind issues and pull requests. Labels describe three independent dimensions: diff --git a/contexts/usage/README.md b/contexts/usage/README.md index 6524677..f925ff2 100644 --- a/contexts/usage/README.md +++ b/contexts/usage/README.md @@ -1,5 +1,23 @@ # Use QuantMind +## Quick Summary + +- **Purpose**: Route library users and agents to current public operations, + inputs, results, examples, and guides. +- **Read when**: Calling QuantMind as a library or selecting a supported public + operation. +- **Load next**: Start with the component row that matches the requested + operation; do not load unrelated component designs. +- **Import rule**: Inputs/configs come from `quantmind.configs`, operations from + `quantmind.flows`, and result types from their owning layer. + +## Contents + +- [Public Usage Sources](#public-usage-sources) +- [Import Boundary](#import-boundary) + +## Public Usage Sources + Use this index when calling QuantMind as a library. These links point to the current public API, focused examples, and component-specific guidance. @@ -13,6 +31,8 @@ current public API, focused examples, and component-specific guidance. | Runnable operation examples | [`examples/flows/`](../../examples/flows/) | | Focused preprocessing examples | [`examples/preprocess/`](../../examples/preprocess/) | +## Import Boundary + Import public inputs and configs from `quantmind.configs`, public operations from `quantmind.flows`, and result contracts from the owning layer identified by the component catalog. diff --git a/tests/test_contexts.py b/tests/test_contexts.py index e55c4e7..31bf945 100644 --- a/tests/test_contexts.py +++ b/tests/test_contexts.py @@ -4,6 +4,49 @@ class TestContextEntryPoints(unittest.TestCase): + def test_context_pages_support_progressive_disclosure(self) -> None: + repo_root = Path(__file__).resolve().parents[1] + context_root = repo_root / "contexts" + contents_link = re.compile(r"^- \[[^]]+]\(#([^)]+)\)$", re.MULTILINE) + + def github_anchor(heading: str) -> str: + normalized = re.sub(r"[^\w\s-]", "", heading.lower()) + return re.sub(r"[\s-]+", "-", normalized).strip("-") + + for page in sorted(context_root.rglob("*.md")): + lines = page.read_text(encoding="utf-8").splitlines() + preview_lines = lines[:80] + preview = "\n".join(preview_lines) + contents_targets = set(contents_link.findall(preview)) + detail_targets = { + github_anchor(line.removeprefix("## ")) + for line in lines + if line.startswith("## ") + and line not in {"## Quick Summary", "## Contents"} + } + with self.subTest(page=page.relative_to(repo_root)): + self.assertIn("## Quick Summary", preview) + self.assertIn("## Contents", preview) + self.assertLess( + preview_lines.index("## Quick Summary"), + preview_lines.index("## Contents"), + ) + self.assertEqual(contents_targets, detail_targets) + + def test_agent_guides_define_progressive_context_loading(self) -> None: + repo_root = Path(__file__).resolve().parents[1] + + for guide_path in ("AGENTS.md", "CLAUDE.md"): + guide = (repo_root / guide_path).read_text(encoding="utf-8") + with self.subTest(guide=guide_path): + self.assertIn("## Progressive Context Loading", guide) + self.assertIn("Read lines 1-80 first.", guide) + self.assertIn("read the entire page", guide) + self.assertIn( + "The preview routes work; it does not replace the", + guide, + ) + def test_context_index_targets_exist(self) -> None: repo_root = Path(__file__).resolve().parents[1] context_indexes = ( From f3b5fea92c2f8581693e43c4bc40b019a748e408 Mon Sep 17 00:00:00 2001 From: pkuwkl Date: Fri, 17 Jul 2026 14:45:15 +0800 Subject: [PATCH 3/4] docs(contexts): visualize paper extraction stages --- contexts/design/flow/paper.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/contexts/design/flow/paper.md b/contexts/design/flow/paper.md index 81d42fe..7eb516d 100644 --- a/contexts/design/flow/paper.md +++ b/contexts/design/flow/paper.md @@ -29,14 +29,14 @@ Paper extraction is a staged operation with one canonical output boundary: -```text -Paper input - -> resolve and fetch - -> page-preserving source document plus authoritative metadata - -> structural extraction draft - -> deterministic canonical assembly - -> tree and provenance validation - -> Paper +```mermaid +flowchart LR + input["Paper input"] --> resolve["Resolve and fetch"] + resolve --> source["Page-preserving source document
and authoritative metadata"] + source --> draft["Structural extraction draft"] + draft --> assembly["Deterministic canonical assembly"] + assembly --> validate["Tree and provenance validation"] + validate --> paper["Canonical Paper"] ``` Deterministic code owns source evidence, canonical identity, graph structure, From e2bac2d7f4a1d1e68cb80887c96df4949529d3dd Mon Sep 17 00:00:00 2001 From: pkuwkl Date: Fri, 17 Jul 2026 15:09:18 +0800 Subject: [PATCH 4/4] docs(contexts): replace jargon with plain language --- contexts/README.md | 33 +- contexts/design/README.md | 38 +-- contexts/design/flow/news.md | 294 +++++++++-------- contexts/design/flow/paper.md | 450 +++++++++++++-------------- contexts/design/library/local.md | 155 +++++---- contexts/design/operations/naming.md | 78 ++--- contexts/dev/README.md | 21 +- contexts/dev/github-writing.md | 37 ++- contexts/dev/labels.md | 57 ++-- contexts/usage/README.md | 19 +- 10 files changed, 586 insertions(+), 596 deletions(-) diff --git a/contexts/README.md b/contexts/README.md index e4b210d..950c378 100644 --- a/contexts/README.md +++ b/contexts/README.md @@ -2,19 +2,19 @@ ## Quick Summary -- **Purpose**: Route agents to the minimum canonical context needed for a +- **Purpose**: Route agents to the smallest set of context pages needed for a QuantMind task. - **Read when**: Starting repository development, library usage, or design work. - **Load next**: Choose exactly one primary route below, then follow only links required by the task. -- **Authority**: Design context is canonical for accepted contracts; code and - tests remain authoritative for current runtime behavior. +- **Authority**: Design pages record agreed behavior; code and tests show what + the current version implements. ## Contents - [Routes](#routes) -- [Source of Truth and Ownership](#source-of-truth-and-ownership) +- [Where Information Lives](#where-information-lives) ## Routes @@ -25,19 +25,20 @@ maintainers. Start with the index that matches the work you are doing: and verification guidance. - [Use QuantMind](usage/README.md) for public operations, examples, and usage documentation. -- [Design QuantMind](design/README.md) for canonical engineering designs and - cross-domain target contracts. - -## Source of Truth and Ownership - -- `contexts/design/` is the canonical source for engineering design decisions - and target contracts. -- `contexts/dev/` and `contexts/usage/` curate links and route readers; they do - not copy full guidance. -- Code and tests remain authoritative for current runtime behavior. Design - pages identify current gaps when they describe behavior that has not landed. +- [Design QuantMind](design/README.md) for accepted design decisions and + planned behavior that spans packages. + +## Where Information Lives + +- Keep each kind of information in one place so agents do not have to compare + competing versions. +- `contexts/design/` records accepted design decisions and planned behavior. +- `contexts/dev/` and `contexts/usage/` route readers to existing rules and + examples instead of copying them. +- Code and tests show current behavior. Design pages list any planned behavior + that is not implemented yet. - `docs/` remains the home for user-facing guides, examples, and catalogs. It - may link to canonical design context but must not maintain a second copy. + may link to a design page but must not maintain a second copy of the design. - Update a component's context index only when its discoverable entry points change, not for every implementation change. - Repository maintainers own this shared structure. Component contributors own diff --git a/contexts/design/README.md b/contexts/design/README.md index e324f40..f137b8f 100644 --- a/contexts/design/README.md +++ b/contexts/design/README.md @@ -2,45 +2,47 @@ ## Quick Summary -- **Purpose**: Index accepted engineering designs and cross-domain target - contracts. -- **Read when**: A task changes architecture, ownership boundaries, public - operation semantics, or a documented invariant. +- **Purpose**: Find accepted designs for QuantMind packages and public + operations. +- **Read when**: A task changes architecture, package responsibilities, public + behavior, or a required validation rule. - **Load next**: Open only the domain design that matches the task, then read that page in full before implementation. -- **Authority**: These pages define intended contracts; each page must identify - current gaps when the runtime does not yet satisfy them. +- **Authority**: These pages explain agreed behavior. Each page lists any part + that is not implemented yet. ## Contents - [Design Index](#design-index) - [Organization Rules](#organization-rules) -This directory is the canonical home for QuantMind engineering design. Use it -for accepted ownership boundaries, cross-domain contracts, and target behavior -that implementation work must preserve. +This directory records QuantMind engineering decisions. Use it to understand +which package owns each step, how packages work together, and what behavior an +implementation must preserve. ## Design Index | Domain | Design | |---|---| -| Flow | [Paper end-to-end contract](flow/paper.md) | +| Flow | [Paper extraction from input to validated result](flow/paper.md) | | Flow | [News collection](flow/news.md) | -| Library | [Local semantic knowledge library](library/local.md) | +| Library | [Local knowledge storage and meaning-based search](library/local.md) | | Operations | [Public operation naming](operations/naming.md) | ## Organization Rules -- Organize designs directly by owning domain, such as `flow/`, `knowledge/`, +- Organize designs directly by package or feature, such as `flow/`, `knowledge/`, `library/`, `operations/`, or `preprocess/`. Do not add an intermediate `components/` directory. - Keep this page as the single global design index. Domain directories do not need their own index. - Add a design page only for real design content. Do not create empty directories, placeholders, or speculative component pages. -- State whether a document describes current behavior, a target contract, or - both. Target contracts must identify current gaps so readers do not mistake - an intended guarantee for an implemented one. -- Keep code and tests authoritative for current runtime behavior. Keep `docs/` - focused on user-facing guides, examples, and catalogs; those pages may link - here but must not maintain a second design copy. +- State whether a document describes current behavior, planned behavior, or + both. List current gaps so readers can distinguish a plan from working code. +- Write headings and summaries as an action plus a concrete object. Avoid + unexplained project shorthand and define any necessary domain term when it + first appears. +- Use code and tests to check current behavior. Keep `docs/` focused on + user-facing guides, examples, and catalogs; those pages may link here but + must not maintain a second copy of a design. diff --git a/contexts/design/flow/news.md b/contexts/design/flow/news.md index 25497cb..98766d5 100644 --- a/contexts/design/flow/news.md +++ b/contexts/design/flow/news.md @@ -2,34 +2,34 @@ ## Quick Summary -- **Purpose**: Define the current contract for collecting source-faithful public news evidence. -- **Read when**: Changing `collect_news`, `NewsWindow`, a news source adapter, batch completeness, or news verification. +- **Purpose**: Define how QuantMind collects public news and records proof of what each source returned. +- **Read when**: Changing `collect_news`, `NewsWindow`, a news source, the `complete` flag, or news checks. - **Status**: Current design; the open-source package currently supports PR Newswire. -- **Core boundary**: Collection returns normalized evidence and honest failure/completeness metadata; semantic extraction, persistence, scheduling, and domain pruning remain downstream concerns. +- **Core rule**: Collection returns documents, failures, and whether the full time window was covered. Other code turns documents into knowledge, stores them, schedules runs, and removes irrelevant news. ## Contents -- [Status and Scope](#status-and-scope) +- [Scope and Current Support](#scope-and-current-support) - [Design Principles](#design-principles) -- [Public API Contract](#public-api-contract) +- [Public API](#public-api) - [Returned Data](#returned-data) -- [Collection Pipeline](#collection-pipeline) -- [Failure and Completeness Semantics](#failure-and-completeness-semantics) -- [Responsibility Boundary](#responsibility-boundary) -- [Verification Contract](#verification-contract) -- [Non-Goals and Extension Rule](#non-goals-and-extension-rule) +- [Collection Steps](#collection-steps) +- [Failures and the `complete` Flag](#failures-and-the-complete-flag) +- [Who Owns What](#who-owns-what) +- [How to Verify](#how-to-verify) +- [Out of Scope and When to Add Shared Code](#out-of-scope-and-when-to-add-shared-code) - [Adding a Public News Source](#adding-a-public-news-source) -## Status and Scope +## Scope and Current Support -This document defines the OSS contract for collecting public company news in -QuantMind. The MVP supports PR Newswire only. It is deliberately small: one -intent-oriented collection operation, one time-window input, deterministic -preprocessing, and explicit partial-failure reporting. +This page defines how the open-source package collects public company news. The +first version supports PR Newswire only. It has one collection function, one +time-window input, repeatable HTML cleanup, and visible item failures. Its public name follows the -[operation naming contract](../operations/naming.md): collection returns source-faithful -evidence and remains separate from semantic knowledge extraction. +[operation naming rules](../operations/naming.md): collection returns the news +as published plus fetch details. A separate operation turns those documents +into structured knowledge. The primary requirement is that any caller can request a complete, one-shot collection of a past time window. A daily poll is therefore not a separate @@ -37,24 +37,22 @@ operation; it is simply a short window evaluated on a schedule. ## Design Principles -1. **Express intent, not acquisition mechanics.** Callers ask for news from a - source and time window. They do not select RSS, listing pages, pagination, - or article-body rules. -2. **Return honest observations.** QuantMind does not silently deduplicate - source rows. Repeated source observations remain repeated output records, - and may share the same stable identity. -3. **Make partial work inspectable.** Item failures are values in the returned - batch. Invalid inputs still raise before network work starts. -4. **Keep source policy internal.** PR Newswire discovery can change from - listing pages to another public mechanism without changing the collection - contract. -5. **Make the supported set explicit.** The MVP uses exhaustive source - dispatch. It does not expose a provider protocol or a provider registry. -6. **Separate collection from production policy.** Persistence, deduplication, - rule-based pruning, target schemas, and scheduling belong to the consuming - data pipeline. - -## Public API Contract +1. **Ask for news, not fetch details.** Callers choose a source and time window. + They do not choose RSS, listing pages, how to move between pages, or article + parsing rules. +2. **Return every source row.** QuantMind does not silently remove duplicate + source rows. Repeated rows remain repeated output records, + and may share the same stable ID. +3. **Show partial failures.** The returned batch includes item failures. Invalid + inputs still raise before network work starts. +4. **Hide source implementation details.** PR Newswire may later use a public + mechanism other than listing pages without changing the public function. +5. **List supported sources explicitly.** The first version selects from a + closed source list. It does not expose a provider plugin API or registry. +6. **Keep downstream choices separate.** The calling pipeline owns storage, + duplicate handling, relevance rules, output formats, and scheduling. + +## Public API Callers use one entry point: @@ -74,73 +72,73 @@ batch = await collect_news( ) ``` -`NewsWindow` uses timezone-aware timestamps and the half-open interval -`[start, end)`. Both regular collection and historical backfill use this same -call. There are no separate `poll_*`, `backfill_*`, or `fetch_wire_*` public -entry points. +`NewsWindow` requires timezone-aware timestamps. Its `[start, end)` interval +includes `start` and excludes `end`. Regular collection and historical runs +use the same call; there are no separate `poll_*`, `backfill_*`, or +`fetch_wire_*` public functions. -`NewsCollectionCfg` retains the repository's shared `BaseFlowCfg` fields so it -works with the common typed-operation and magic-input tooling. Its only +`NewsCollectionCfg` keeps the shared `BaseFlowCfg` fields so it works with the +repository's shared config handling. Its only collection-specific field is `retain_raw_html`, which controls whether fetched article HTML bytes remain in the result. It defaults to `False`, which is the -conservative and storage-efficient behavior. The article is still fetched, -hashed, parsed, and represented by metadata; only its byte payload is -discarded. The deterministic collector does not otherwise consume the shared -model, tracing, or SDK-run fields. +storage-efficient behavior. The article is still fetched, hashed, parsed, and +described by fetch details; only its byte payload is discarded. The collector +does not otherwise use the shared model, tracing, or SDK-run fields. ### Collection records are not knowledge -QuantMind keeps source evidence and semantic extraction as separate contracts: +QuantMind keeps collected documents separate from structured knowledge: -| Operation | Result | Canonical layer | +| Operation | Result | Owning package | |-----------|--------|-----------------| -| `collect_news` | Source-faithful documents, artifacts, failures, and coverage | `quantmind.preprocess` | +| `collect_news` | Source documents, fetch details, failures, and whether the full window was scanned | `quantmind.preprocess` | | future `extract_news_knowledge` | Extracted financial events | `quantmind.knowledge.News` | -`NewsDocument` is therefore not a `KnowledgeItem`. It carries HTTP evidence, +`NewsDocument` is therefore not a `KnowledgeItem`. It carries HTTP details, raw bytes, parsing output, and collection status that remain useful before any -LLM or business schema is chosen. `knowledge.News` is a compact semantic event -with entities, sentiment, materiality, provenance, and an embedding view. The -two may later be composed, but neither substitutes for the other. +LLM or business data format is chosen. `knowledge.News` is a structured +financial event with entities, sentiment, financial importance +(`materiality`), source links, and text for embeddings. A pipeline may use both +types, but one cannot replace the other. ## Returned Data -`collect_news` returns a `NewsBatch` with four concepts. The collection -contracts are publicly imported from `quantmind.preprocess`: +`collect_news` returns a `NewsBatch` with four fields. Import these public types +from `quantmind.preprocess`: - `documents`: successfully collected `NewsDocument` observations; - `failures`: lightweight `NewsFailure` records for work that could not be completed; -- `observed_count`: the number of source rows successfully normalized into +- `observed_count`: the number of source rows successfully converted into in-window observations, before article processing; -- `complete`: whether discovery proved coverage of the requested window. +- `complete`: whether the listing scan covered the full requested window. -A `NewsDocument` contains the source name, stable identity, canonical URL, +A `NewsDocument` contains the source name, stable ID, preferred article URL, title, publisher, publication time, cleaned Markdown, content hash, ticker -hints, and two evidence artifacts: +hints, and two `NewsArtifact` records: -- a small discovery artifact representing the public source row; -- an article artifact containing fetch metadata and a content hash. Its +- a small record of the public listing row; +- an article record containing fetch details and a content hash. Its `bytes` field is `None` unless `retain_raw_html=True`. -`NewsArtifact` is the common evidence shape. It records the content hash, +`NewsArtifact` records what was fetched. It contains the content hash, content type, source and resolved URLs, status, headers, and fetch time, with an optional byte payload. Repeated listing rows are not removed. If two rows point to the same release, -the batch contains two observations with the same stable identity. The -consumer can use that identity for an idempotent upsert without losing what -QuantMind actually observed. +the batch contains two observations with the same stable ID. A consumer can +use that ID to update the same database row safely while still recording what +QuantMind observed. -## Collection Pipeline +## Collection Steps ```text NewsWindow - -> source dispatch - -> newest-to-oldest public discovery pages - -> observations inside [start, end) - -> linked article fetch - -> deterministic HTML-to-Markdown normalization + -> select the source collector + -> scan public listing pages from newest to oldest + -> keep rows inside [start, end) + -> fetch each linked article + -> convert HTML to Markdown -> NewsDocument or NewsFailure -> NewsBatch ``` @@ -149,125 +147,123 @@ PR Newswire discovery is based on its public news-release listing rather than the latest-items RSS snapshot. Pages are read newest to oldest until an observation strictly older than the requested start is seen. The strict boundary matters because several rows with the same minute-level timestamp may -span two pages. This makes a past-day request independently replayable instead -of dependent on a previously saved cursor. +span two pages. A caller can therefore rerun a past-day request without a +previously saved cursor. PR Newswire exposes listing timestamps at minute precision. Scheduled callers -should therefore use minute-aligned window bounds, as the live E2E does. +should therefore use minute-aligned window bounds, as the live end-to-end check +does. -RSS remains a lower-level parser and a live component check. It is not a -high-level `NewsInput`, because feed selection is a source implementation -detail and a bounded feed snapshot cannot prove complete time-window coverage. +RSS remains an internal parser and a live check. It is not a public +`NewsInput`: choosing a feed is an implementation detail, and a limited feed +snapshot cannot prove that it covered a complete time window. -The HTTP layer owns bounded retries, backoff, `Retry-After` handling, and -per-host rate limits. PR Newswire-specific URL construction and listing HTML -parsing stay in the PR Newswire source module. +The HTTP layer limits retries, waits between attempts, honors `Retry-After`, +and limits requests per host. PR Newswire-specific URL construction and +listing HTML parsing stay in the PR Newswire source module. -Supported source names form a closed set in `NewsWindow.source`. -`collect_news` dispatches them exhaustively, so widening the input schema -without implementing the corresponding collector fails static verification. +`NewsWindow.source` lists every supported source. `collect_news` has an +explicit branch for each one, so the type checker catches a source name added +without a matching collector. -## Failure and Completeness Semantics +## Failures and the `complete` Flag Configuration errors raise immediately. Examples include a naive timestamp, an empty source, an unsupported source, or `end <= start`. -After collection begins, recoverable failures are recorded and independent -items continue. Each `NewsFailure` identifies the source, processing stage, -URL, optional item identity, error category, and message. +After collection begins, one item failure does not stop independent items. +Each `NewsFailure` records the source, failed step, URL, optional item ID, error +category, and message. `complete=False` when any of the following is true: -- a discovery page could not be fetched or parsed; -- discovery stopped before crossing the window start; +- a listing page could not be fetched or parsed; +- the listing scan stopped before crossing the window start. -Article failures remain explicit in `failures` but do not change discovery -completeness. A caller can therefore distinguish "the source window was fully -enumerated" from "every observed article was processed." It can safely persist -successful records while separately monitoring and replaying the failed -portion. An empty batch is not considered complete unless discovery has -positively crossed the requested start. +Article failures stay in `failures` but do not change whether all listing rows +were found. A caller can distinguish "the full time window was scanned" from +"every found article was processed." It can store successful records and retry +failed articles separately. An empty batch is complete only when the listing +scan crossed the requested start. -## Responsibility Boundary +## Who Owns What QuantMind owns: -- public-source discovery and article acquisition; -- deterministic normalization; -- stable identities and evidence hashes; +- scanning public listings and fetching articles; +- repeatable HTML cleanup; +- stable IDs and content hashes; - honest batch counts, failure records, and completeness. The consuming production pipeline owns: -- its schedule and GitHub Action invocation; -- durable storage, watermarks, and idempotent upserts; -- deduplication policy; -- rule-based company-news pruning; -- downstream schemas, enrichment, and database writes; -- shared batch hooks, metrics, and monitoring infrastructure. +- its schedule and when its GitHub Action runs; +- database storage, saved progress, and safe repeated writes; +- rules for removing duplicates; +- rules that remove irrelevant company news; +- later data formats, added fields, and database writes; +- shared batch callbacks, metrics, and monitoring. -This boundary lets a separate ingestion job request the last day in one call, -apply its own rules, and write its own schema without coupling those policies -to this OSS library. +This split lets a separate ingestion job request the last day in one call, +apply its own rules, and write its own data format without adding those choices +to the open-source library. -## Verification Contract +## How to Verify -Verification has two intentionally separate layers. +Use separate offline and live checks. -### Deterministic verification +### Offline repository checks -`bash scripts/verify.sh` runs deterministic unit tests, linting, typing, and -coverage. News tests use saved HTML/RSS fixtures and mocked HTTP responses. -They cover window validation, time boundaries, pagination, duplicate +`bash scripts/verify.sh` runs repeatable unit tests, linting, typing, and +coverage. News tests use saved HTML/RSS files and mocked HTTP responses. +They cover window validation, time edges, multi-page listings, duplicate observations, raw-byte retention, retries, partial failures, and completeness. -### Live news E2E +### Live end-to-end news check -`python scripts/verify_news_e2e.py` is the component-specific live-network news -smoke test. It performs three bounded checks: +`python scripts/verify_news_e2e.py` is the news live-network check. It performs +three limited checks: 1. fetch and parse the official PR Newswire RSS feed; -2. discover PR Newswire listing observations for the preceding 24 hours and - prove that discovery crossed the window start; -3. fetch up to 25 unique article observations and require 100% ticker-hint - recall for the first supported exchange-coded symbol in each group. - -The ticker control uses an oracle regex that is independent from the production -extractor and accepts plain, Markdown-link, and emphasis-wrapped symbols. It -intentionally ignores later members of multi-symbol lists and exchanges outside -the production whitelist. The gate prints component-level PASS/FAIL records and -compact discovery, article-sample, failure, and recall summaries. It exits -non-zero if RSS or discovery is invalid, no sampled article parses, or a sample -with supported exchange-coded symbols falls below 100% recall. A parsed sample -with zero supported symbols reports `SKIP` and passes neutrally. - -The `news` job in `.github/workflows/e2e.yml` runs this check once daily, on -manual dispatch, and on pull requests that change its precise dependency paths. +2. scan PR Newswire listing rows for the preceding 24 hours and prove that the + scan crossed the window start; +3. fetch up to 25 unique articles and find the first supported exchange-coded + symbol in every group that has one (100% recall). + +The ticker check uses a separate comparison regex rather than the production +extractor. It accepts plain, Markdown-link, and emphasis-wrapped symbols. It +ignores later symbols in a multi-symbol list and exchanges that the extractor +does not support. The script prints PASS/FAIL plus short listing, article, +failure, and recall summaries. It fails when RSS or listing data is invalid, +no sampled article parses, or supported symbols fall below 100% recall. A +parsed sample with no supported symbols reports `SKIP` and still passes. + +The `news` job in `.github/workflows/e2e.yml` runs this check once daily, when +started manually, and on pull requests that change its direct dependencies. The required `.github/workflows/ci.yml` workflow remains network-free so local -development and unit tests stay deterministic. +development and unit tests remain repeatable. -## Non-Goals and Extension Rule +## Out of Scope and When to Add Shared Code -The MVP does not include GlobeNewswire, Business Wire, authenticated feeds, -continuous cursors, storage, deduplication, business materiality scoring, or a -generic batch-operation base class. +The first version does not include GlobeNewswire, Business Wire, authenticated +feeds, continuous cursors, storage, duplicate removal, financial importance +scoring, or a generic batch-operation base class. When a second source is implemented, compare its real behavior with PR -Newswire first. Extract a shared provider interface only for behavior the two -implementations genuinely share. The public -`collect_news(NewsWindow, *, cfg)` contract should remain unchanged. +Newswire first. Add a shared collector interface only for behavior that both +working collectors actually share. Keep the public +`collect_news(NewsWindow, *, cfg)` call unchanged. ## Adding a Public News Source -A coding agent adding a second source follows this closed checklist: +A coding agent adding a second source follows this checklist: 1. Add the source name to `NewsWindow.source`. 2. Add one private `quantmind/preprocess/.py` collector. -3. Add one explicit branch to `collect_news`; the exhaustive type check must - remain green. -4. Add fixture-based success, boundary, duplicate-observation, - partial-failure, and completeness tests for the source. -5. Add a routing test proving only the selected collector is called. +3. Add one explicit branch to `collect_news`; the type checker must pass. +4. Add saved-input tests for success, time edges, duplicate observations, + partial failures, and the `complete` flag. +5. Add a test proving that only the selected collector is called. 6. Update the supported-source table in `docs/README.md`, this design, and the focused example if its common path changes. 7. Add or extend a component-specific live verifier and its named job in the @@ -275,6 +271,6 @@ A coding agent adding a second source follows this closed checklist: public network endpoint. Add its command to `docs/README.md`; do not add the command to root agent guidance. -Only after two real collectors expose shared behavior should a common -`Protocol` be considered. A new source must never be added only to the input -Literal: static verification is expected to reject that incomplete change. +Only after two real collectors share behavior should they use a common Python +`Protocol`. Never add a source only to the input `Literal`; the type checker is +expected to reject that incomplete change. diff --git a/contexts/design/flow/paper.md b/contexts/design/flow/paper.md index 7eb516d..ca26d50 100644 --- a/contexts/design/flow/paper.md +++ b/contexts/design/flow/paper.md @@ -1,88 +1,89 @@ -# Paper End-to-End Design Contract +# Paper Extraction: End-to-End Design ## Quick Summary -- **Purpose**: Define the target end-to-end contract from paper source resolution through canonical `Paper` assembly. -- **Read when**: Changing paper inputs, parsing, structural extraction, tree assembly, provenance, spans, or future PageIndex integration. -- **Status**: Target contract with current implementation gaps called out below. -- **Core boundary**: A model or PageIndex adapter may propose structure; deterministic code owns canonical IDs, edges, ordering, spans, citations, validation, and source truth. -- **Golden span convention**: PDF pages are 1-based and inclusive. +- **Purpose**: Define how a paper input becomes a validated `Paper`. +- **Read when**: Changing paper inputs, parsing, section trees, source tracking, page ranges, or future PageIndex support. +- **Status**: Planned design; [Current Gaps](#current-gaps) lists what is not implemented yet. +- **Core rule**: A model or PageIndex may suggest a section tree. Code creates the final IDs, links, order, page ranges, citations, and source-backed text. +- **Page numbering**: PDF page ranges start at 1 and include both the first and last page. ## Contents -- [Decision Summary](#decision-summary) -- [Product and Ownership Boundary](#product-and-ownership-boundary) -- [Inputs and Source Resolution](#inputs-and-source-resolution) -- [Page-Preserving Source Document](#page-preserving-source-document) -- [Authoritative Metadata](#authoritative-metadata) -- [Structural Extraction Draft](#structural-extraction-draft) -- [Deterministic Canonical Assembly](#deterministic-canonical-assembly) -- [Tree and Paper Invariants](#tree-and-paper-invariants) -- [Branch Content and Source Slicing](#branch-content-and-source-slicing) -- [Determinism, Retry, and Failure](#determinism-retry-and-failure) -- [Output Boundary and Downstream Consumers](#output-boundary-and-downstream-consumers) -- [Future PageIndex Compatibility Seam](#future-pageindex-compatibility-seam) -- [Golden Fixture Contract](#golden-fixture-contract) -- [Current Behavior and Known Gaps](#current-behavior-and-known-gaps) - -## Decision Summary - -Paper extraction is a staged operation with one canonical output boundary: +- [Overview](#overview) +- [Who Owns Each Step](#who-owns-each-step) +- [Supported Inputs and Fetching](#supported-inputs-and-fetching) +- [Keep PDF Pages Separate](#keep-pdf-pages-separate) +- [Which Source Provides Each Field](#which-source-provides-each-field) +- [Model Output Is a Draft](#model-output-is-a-draft) +- [Build and Validate the Final Paper](#build-and-validate-the-final-paper) +- [Rules for a Valid Paper](#rules-for-a-valid-paper) +- [Get Node Content from Source Pages](#get-node-content-from-source-pages) +- [Retries and Failures by Step](#retries-and-failures-by-step) +- [What Paper Extraction Does Not Do](#what-paper-extraction-does-not-do) +- [How PageIndex Can Fit Later](#how-pageindex-can-fit-later) +- [Fixed Paper Test Data](#fixed-paper-test-data) +- [Current Gaps](#current-gaps) + +## Overview + +Paper extraction has six steps and returns one validated result: ```mermaid flowchart LR input["Paper input"] --> resolve["Resolve and fetch"] - resolve --> source["Page-preserving source document
and authoritative metadata"] - source --> draft["Structural extraction draft"] - draft --> assembly["Deterministic canonical assembly"] - assembly --> validate["Tree and provenance validation"] - validate --> paper["Canonical Paper"] + resolve --> source["Keep pages separate
and record source facts"] + source --> draft["Suggest a section tree"] + draft --> assembly["Build the final Paper in code"] + assembly --> validate["Validate the tree and source links"] + validate --> paper["Validated Paper"] ``` -Deterministic code owns source evidence, canonical identity, graph structure, -page slicing, citations, and validation. A model or future PageIndex adapter -may propose semantic structure, titles, summaries, and source spans, but its -output is a draft rather than a canonical `Paper`. +Code, not the model, records source facts, creates IDs and tree links, copies +text from source pages, creates citations, and validates the result. A model or +future PageIndex integration may suggest sections, titles, summaries, and page +ranges, but that output remains a draft. -PageIndex is not a prerequisite. The pipeline first preserves ordered source -pages and uses a narrow tree-builder seam; PageIndex can later implement that -seam without changing the `Paper` schema or taking ownership of canonical IDs. +Paper extraction does not require PageIndex. It first preserves ordered source +pages and passes them through a small `PaperSourceDocument -> draft` interface. +PageIndex can later implement that interface without changing the `Paper` type +or creating the final IDs. -## Product and Ownership Boundary +## Who Owns Each Step -This contract covers one extraction result. It does not make Paper extraction -responsible for persistence, retrieval, or answer generation. +This operation creates one extraction result. It does not store papers, search +across them, or write answers. -| Concern | Owner | +| Work | Owner | |---|---| | Resolve identifiers, fetch bytes, parse pages, and hash source content | `quantmind.preprocess` | | Configure the operation and select the input variant | `quantmind.configs` | -| Produce semantic structure and assemble a canonical result | `quantmind.flows` | -| Define `Paper`, `TreeKnowledge`, `TreeNode`, source, citation, and extraction schemas | `quantmind.knowledge` | -| Persist and semantically index canonical knowledge | `quantmind.library` | -| Navigate a tree and synthesize an answer | A future `quantmind.mind` consumer or agent application | +| Suggest a section tree and build the final `Paper` | `quantmind.flows` | +| Define the `Paper`, `TreeKnowledge`, `TreeNode`, source, citation, and extraction models | `quantmind.knowledge` | +| Store knowledge and make it searchable by meaning | `quantmind.library` | +| Navigate a tree and write an answer | A future `quantmind.mind` consumer or agent application | -`flow/` is a documentation grouping for this cross-domain behavior. It does -not require a new `*_flow` public API and does not override the -[public operation naming contract](../operations/naming.md). +The `flow/` directory groups this work because it spans several packages. It +does not require a new `*_flow` public API and does not override the +[public operation naming rules](../operations/naming.md). -## Inputs and Source Resolution +## Supported Inputs and Fetching -### Supported input intents +### Supported inputs -The target pipeline accepts the existing `PaperInput` intents with the -following semantics: +The planned pipeline accepts the existing `PaperInput` types with the following +behavior: -| Input | Resolution behavior | +| Input | What happens | |---|---| -| `ArxivIdentifier` | Resolve the identifier to an exact arXiv version, authoritative metadata, and PDF bytes. Preserve the resolved source URL and version-specific availability time. | -| `HttpUrl` | Follow bounded redirects and accept supported PDF, HTML, Markdown, or plain-text content. Record the final canonical URL and fetched representation. | +| `ArxivIdentifier` | Resolve the identifier to an exact arXiv version, provider metadata, and PDF bytes. Preserve the resolved URL and the time that version became available. | +| `HttpUrl` | Follow a limited number of redirects and accept supported PDF, HTML, Markdown, or plain-text content. Record the final URL and exact fetched content. | | `LocalFilePath` | Read a supported PDF, HTML, Markdown, or plain-text file. The caller owns file retention; extraction records a local source reference and content hash. | -| `RawText` | Treat the supplied text as one non-paginated source document. It can produce a `Paper`, but it cannot claim PDF page spans. | +| `RawText` | Treat the supplied text as one source document without pages. It can produce a `Paper`, but it cannot claim PDF page ranges. | -`DoiIdentifier` remains explicitly unsupported until an open-access resolver -can produce an exact fetchable representation. A DOI landing page alone is -metadata, not necessarily the paper content. +`DoiIdentifier` remains unsupported until an open-access resolver can fetch the +exact paper content. A DOI landing page alone contains metadata and may not +contain the paper itself. ### Explicitly unsupported inputs @@ -91,232 +92,223 @@ V1 does not claim support for: - password-protected or corrupt PDFs; - image-only scans that require OCR; - authenticated, paywalled, or dynamically rendered sources without a - separately authorized fetch adapter; + separately authorized fetcher; - unsupported binary or media content types; - a DOI that cannot be resolved to accessible paper content; -- a source whose exact fetched representation cannot be identified. +- a source whose exact fetched content cannot be identified. -Unsupported input fails before semantic extraction. The pipeline must not send -an error page, login page, or unresolved metadata page to a model and call the -result a successfully extracted paper. +Unsupported input fails before a model or tree builder runs. The pipeline must +not send an error page, login page, or unresolved metadata page to a model and +call the result a successfully extracted paper. -### Resolution rules +### Fetching rules -Resolution produces one exact source version. Redirects, arXiv revisions, and -content negotiation are resolved before parsing. The resolved URI and SHA-256 -hash identify the bytes used for this extraction. A retry may fetch a changed -representation; if its content hash changes, it is a new source version and -must not be merged silently with an earlier attempt. +Fetching produces one exact source version. Resolve redirects, arXiv revisions, +and the content selected by the server before parsing. The final URI and +SHA-256 hash identify the exact bytes used. A retry may receive changed +content; when the hash changes, treat it as a new source version rather than +silently merging it with the earlier attempt. -## Page-Preserving Source Document +## Keep PDF Pages Separate -PDF parsing must preserve ordered pages before any tree builder runs. The -conceptual deterministic handoff has this shape: +PDF parsing must preserve ordered pages before any tree builder runs. The next +step receives data shaped like this: ```text PaperSourceDocument -├── source metadata and exact content hash -├── span unit and indexing convention +├── source details and exact content hash +├── page or character range format └── ordered pages ├── page number ├── extracted text, including an empty string when the page has no text - └── optional deterministic layout or outline signals + └── optional parser-provided layout or outline hints ``` Required properties: -- PDF page numbers are 1-based and inclusive everywhere in the Paper golden - contract, structural drafts, and citations. +- PDF page ranges in the fixed test PDF, model draft, and citations start at 1 + and include both the first and last page. - Every physical page remains represented and in order. Empty pages are not - dropped because doing so would renumber later evidence. -- Text normalization may remove parser noise, but it must not erase the page + dropped because doing so would renumber later page references. +- Text cleanup may remove parser noise, but it must not erase the page boundary or change which page owns an anchor. -- The exact source hash, parser identity/version, and any deterministic - normalization identity are available to provenance assembly. -- HTML, Markdown, plain text, and `RawText` are non-paginated. They retain - source text and character evidence but do not invent PDF page numbers. -- A page-based tree builder, including a future PageIndex adapter, accepts only - a source document whose span unit is `pdf_page`. +- Record the exact source hash, parser name and version, and cleanup version + with the extraction run. +- HTML, Markdown, plain text, and `RawText` have no pages. They retain source + text and character positions but do not invent PDF page numbers. +- A page-based tree builder, including a future PageIndex integration, accepts + only a source document whose range unit is `pdf_page`. -The page-preserving document is an extraction intermediate, not a second -canonical knowledge schema. Raw PDF/HTML retention remains caller-owned and is -not embedded inside `Paper` by this contract. +`PaperSourceDocument` is a temporary value used during extraction, not another +public knowledge model. The caller keeps raw PDF or HTML files; this design +does not embed them inside `Paper`. -## Authoritative Metadata +## Which Source Provides Each Field -Source-derived metadata and model-derived semantics have different authority. -The model may fill an explicitly missing semantic field, but it may not -overwrite authoritative source evidence. +Facts from the fetched source take priority over model suggestions. A model may +fill a missing summary or section title, but it must not overwrite a URL, +content hash, publication time, or other known source fact. -| Field or fact | Authority and canonical destination | +| Field or fact | Who sets it and where it is stored | |---|---| | Resolved source URI and source kind | Resolver/fetcher; `SourceRef.kind` and `SourceRef.uri` | -| Exact fetched bytes and content hash | Fetcher; `SourceRef.content_hash` plus caller-owned raw artifact | +| Exact fetched bytes and content hash | Fetcher; `SourceRef.content_hash`, while the caller keeps the raw file | | Fetch time | Fetcher clock; `SourceRef.fetched_at` | -| Publication/version time | Authoritative provider metadata; establishes when the exact version became public and must not be replaced with the first-version date | -| Source availability time | Exact-version provider metadata when available; `Paper.available_at`. Fetch time is a conservative upper bound when publication availability is unknown. | -| Information cutoff | A source-declared study/data cutoff when present; otherwise the exact version publication time is a documented conservative fallback for `Paper.as_of`, never fetch time | -| Authors and authoritative title | Provider or document metadata; `Paper.authors` and root title unless the metadata is missing or demonstrably invalid | +| Publication/version time | Provider metadata for the exact version; do not replace it with the first-version date | +| Source availability time | Provider metadata for the exact version; `Paper.available_at`. Use fetch time as the latest possible availability time only when the publication time is unknown. | +| Latest date covered by the paper | A study or data cutoff stated by the source; otherwise use the exact version's publication time for `Paper.as_of`, never fetch time | +| Authors and title | Provider or document metadata; `Paper.authors` and root title unless that metadata is missing or clearly invalid | | Extraction model, operation, run, and time | Runtime; `ExtractionRef` | -| Parser and normalization identity | Deterministic runtime provenance; retained with the extraction run until the canonical item schema has an explicit item-level field | -| Summaries, semantic section titles, methodology, findings, and limitations | Structural draft producer; validated before canonical assembly | -| Canonical item/node IDs and graph links | Deterministic assembly code only | +| Parser and cleanup versions | Runtime; keep them with the extraction run until `Paper` has a dedicated field | +| Summaries, section titles, methodology, findings, and limitations | Model or tree builder; validate them before building the final `Paper` | +| Final item/node IDs and tree links | Code only | For an arXiv revision, `available_at` refers to the exact revision used, not the -first submission date. `as_of` is the information cutoff represented by the -paper; it must remain distinct from fetch time. Unknown publication or -availability metadata remains unknown rather than being guessed by a model. +first submission date. `as_of` is the latest date covered by the paper, not the +time it was fetched. Unknown publication or availability data remains unknown +rather than being guessed by a model. -## Structural Extraction Draft +## Model Output Is a Draft -A draft is the only provider- or model-facing structural contract. It may -contain: +A model or PageIndex integration returns a draft with only the information +needed to suggest a section tree. It may contain: -- adapter-local node keys used only within the draft; +- temporary node keys used only by that integration; - candidate titles and summaries; -- an ordered parent/child outline expressed with draft-local references; -- candidate page spans using the source document's explicit span unit; -- confidence or diagnostic information needed to accept, repair, or reject the - draft. - -A draft must not contain canonical `Paper.id`, `TreeNode.node_id`, canonical -`parent_id`/`children_ids`, copied source text, authoritative metadata, or a -provider-specific object that leaks through the public result. - -The structural producer is intentionally narrow: conceptually it maps one -`PaperSourceDocument` to one structural draft. The default implementation can -use an LLM; a future PageIndex adapter can provide the same kind of draft. -Neither becomes the canonical `Paper` schema. - -## Deterministic Canonical Assembly - -Assembly treats the accepted draft as untrusted input and performs these steps -in code: - -1. Generate canonical item and node IDs. External or draft-local IDs never - escape their adapter. -2. Resolve each draft parent reference and derive both `parent_id` and ordered - `children_ids` from one checked relation. -3. Assign sibling positions deterministically from the accepted order. -4. Validate and normalize page spans against the preserved source pages. -5. Slice source content from the inclusive page range and construct citations - from the same source evidence. Model-generated paraphrases never become - source content or quotes. -6. Apply authoritative metadata and runtime extraction provenance. -7. Construct the canonical `Paper` and run the complete invariant validator. - -Canonical IDs are code-owned, but this contract does not require them to be -identical across independent extraction runs. Stable cross-run identity is a -separate deduplication decision. - -## Tree and Paper Invariants +- an ordered parent/child outline using those temporary keys; +- suggested page ranges using the source document's range format; +- confidence values or errors needed to accept, repair, or reject the draft. + +A draft must not set the final `Paper.id`, `TreeNode.node_id`, +`parent_id`/`children_ids`, copied source text, known source facts, or a +object tied to one provider and exposed through the public result. + +The interface is intentionally small: one `PaperSourceDocument` produces one +draft. The default implementation can use an LLM; a future PageIndex +integration can return the same draft shape. Neither defines the public +`Paper` type. + +## Build and Validate the Final Paper + +Code treats the accepted draft as untrusted input and performs these steps: + +1. Generate the final item and node IDs. Temporary integration IDs never + appear in the returned result. +2. Set `parent_id` and ordered `children_ids` together for each parent-child + link so they cannot disagree. +3. Assign sibling positions from the accepted order. +4. Check page ranges against the preserved source pages. +5. Copy source content from the stated page range and build citations from the + same pages. Model-written paraphrases never become source text or quotes. +6. Apply known source facts and extraction run details. +7. Construct the final `Paper` and run every validation rule below. + +Code owns the final IDs, but separate extraction runs do not need to create the +same IDs. Merging results across runs is a separate decision. + +## Rules for a Valid Paper A successful `Paper` satisfies all of the following before it is returned: -### Identity and root +### IDs and root - `root_node_id` identifies exactly one entry in `nodes`. - Every dictionary key equals that node's `node_id`. - The root has `parent_id=None`; every other node has exactly one parent. -- All canonical item and node IDs are code-owned and unique within the result. +- All item and node IDs are created by code and unique within the result. -### Edges and ordering +### Parent and child links - Every referenced parent and child exists. -- Parent/child relationships are bidirectionally consistent. +- A parent lists each child, and each child points back to that parent. - A child appears at most once in a parent's `children_ids`. -- Sibling order is deterministic, and sibling positions are unique within one - parent. +- The same accepted draft produces the same sibling order, and sibling + positions are unique within one parent. ### Reachability and safe traversal - Every node is reachable from the root. -- The graph is acyclic and contains no self-edge. +- The graph has no cycles and no node points to itself. - No node is shared by multiple parents. -- Depth-first walk and root-to-node path lookup terminate for every canonical - node. Unknown node lookup returns the documented safe result rather than - following unchecked links. +- Depth-first walk and root-to-node path lookup finish for every node. Looking + up an unknown node returns the documented safe result. -### Source spans and citations +### Source page ranges and citations -- A PDF span uses `pdf_page`, starts at 1 or later, has +- A PDF page range uses `pdf_page`, starts at 1 or later, has `start_page <= end_page`, and ends within the preserved PDF page count. -- Citation page ranges obey the same 1-based inclusive convention and source - bounds. +- Citation page ranges also start at 1, include both ends, and stay within the + PDF page count. - Citation quotes and node content come from the identified source range. -- Non-paginated inputs do not carry fabricated PDF spans. -- Sibling spans may overlap. A child span is not required to be strictly - contained by its parent's span unless a later canonical rule adds that - independent requirement. +- Inputs without pages do not carry made-up PDF page ranges. +- Sibling page ranges may overlap. A child range is not required to fit + completely inside its parent range unless a future rule adds that + requirement. -## Branch Content and Source Slicing +## Get Node Content from Source Pages Branch nodes may retain source content. `content=None` remains useful for a navigation-only node, but being a branch is not itself a reason to discard its -evidence. +source text. -When content is present, deterministic code slices it from the node's tight -1-based inclusive source page range. Page-granular ranges can include a heading -or paragraph also used by an adjacent node; this is expected when spans -overlap. Assembly must not ask the model to reproduce source text, and it must -not derive a parent node's content by concatenating model summaries. +When content is present, code copies it from the node's 1-based inclusive page +range. Page-level ranges can include a heading or paragraph also used by an +adjacent node; this is expected when ranges overlap. Do not ask the model to +reproduce source text or create a parent node by joining model summaries. -Navigation and evidence loading remain separate operations: an agent can read -titles and summaries to select a node, then fetch the selected node's tight -source page range. A future PageIndex adapter may help build the outline, but -it does not own source-range fetching. +Choosing a section and loading its source text remain separate operations. An +agent can read titles and summaries to select a node, then fetch that node's +page range. A future PageIndex integration may help build the outline, but it +does not fetch the source text. -## Determinism, Retry, and Failure +## Retries and Failures by Step -| Stage | Determinism | Retry and failure contract | +| Step | Same input, same result? | Retry and failure behavior | |---|---|---| -| Input validation and resolution policy | Deterministic for one configuration | Invalid or unsupported input fails immediately. | -| Network fetch | Externally variable | Retry only bounded transient failures. Record and hash the exact successful response. Permanent status/content failures stop the operation. | -| PDF or text parsing | Deterministic for fixed bytes and parser version | Parser failure stops before semantic extraction. Do not skip corrupt pages or renumber around them. | -| Structural draft production | Nondeterministic when model-backed | Bounded retries may repair transport or draft-schema failures against the same source version. Each attempt is observable. | -| Canonical assembly | Deterministic for one accepted draft and source version, except generated UUID values | Invalid references, spans, or authoritative metadata conflicts reject the draft. | -| Invariant validation | Deterministic | Any failure rejects the entire result; no partial `Paper` is returned as success. | - -A successful result means one canonical `Paper` passed provenance, span, and -tree validation. Fetching bytes, obtaining a model response, or constructing a -Pydantic object alone is not success. Failure preserves enough stage and source -identity to diagnose or retry the operation, but it does not persist partial -canonical knowledge implicitly. - -## Output Boundary and Downstream Consumers - -Paper extraction returns canonical knowledge. The following are explicit -downstream consumers and stay outside this operation: - -- persistence and idempotent storage in `LocalKnowledgeLibrary`; -- embedding generation and semantic indexing; -- semantic retrieval across a collection; +| Input validation and fetching rules | Yes, for one configuration | Invalid or unsupported input fails immediately. | +| Network fetch | No; the remote source may change | Retry only a limited number of temporary failures. Record and hash the exact successful response. Permanent HTTP or content failures stop the operation. | +| PDF or text parsing | Yes, for the same bytes and parser version | Parser failure stops before the model or tree builder runs. Do not skip corrupt pages or renumber later pages. | +| Model or PageIndex draft | No when a model is used | Limited retries may repair network or invalid-draft failures against the same source version. Record every attempt. | +| Build the final `Paper` | Yes for one accepted draft and source version, except for new UUID values | Invalid links, page ranges, or conflicts with source facts reject the draft. | +| Final validation | Yes | Any failure rejects the entire result; never return a partial `Paper` as success. | + +A successful result means one `Paper` passed its source, page-range, and tree +checks. Fetching bytes, receiving a model response, or constructing a Pydantic +object alone is not success. A failure records enough about the failed step and +source to diagnose or retry it, but it does not store a partial `Paper`. + +## What Paper Extraction Does Not Do + +Paper extraction returns one validated `Paper`. Other components handle: + +- storing and safely updating it in `LocalKnowledgeLibrary`; +- generating embeddings and building search records; +- searching across a collection; - PageIndex-style tree navigation within one selected document; -- answer synthesis, conversational state, and citations in a final response; -- caller policy for retaining raw source bytes. +- writing answers, managing conversation state, and citing a final response; +- deciding whether to keep raw source bytes. -Keeping these concerns separate lets Paper extraction land before PageIndex and -lets PageIndex arrive later without replacing semantic retrieval or the -canonical library. +Keeping this work separate allows Paper extraction to be implemented before +PageIndex. PageIndex can then be added without replacing collection-wide search +or stored knowledge. -## Future PageIndex Compatibility Seam +## How PageIndex Can Fit Later Future integration must preserve these decisions: -1. PageIndex receives ordered source pages before any flattening step. -2. Its node IDs remain adapter-local. Canonical IDs and graph links are created - during deterministic assembly. +1. PageIndex receives ordered source pages before they are joined into one text. +2. Its node IDs remain temporary. Code creates the final IDs and tree links. 3. It may propose titles, summaries, ordering, and 1-based inclusive page - spans through the structural draft seam. -4. It does not become the `Paper`, `TreeKnowledge`, or `TreeNode` schema. -5. Outline navigation uses titles and summaries; evidence loading separately - fetches the selected tight source range. -6. Integration does not assume non-overlapping sibling spans or strict child - containment. + ranges through the draft described above. +4. It does not become the `Paper`, `TreeKnowledge`, or `TreeNode` type. +5. Navigation uses titles and summaries; loading source text separately fetches + the selected page range. +6. Sibling page ranges may overlap, and a child range does not need to fit + completely inside its parent range. -## Golden Fixture Contract +## Fixed Paper Test Data -The repository-owned fixture lives at: +The fixed test files live at: ```text tests/fixtures/paper/golden/ @@ -324,35 +316,35 @@ tests/fixtures/paper/golden/ └── expected.json ``` -It is a small four-page synthetic paper with a nested subsection, multi-page -sections, and intentionally overlapping sibling page spans. `expected.json` +It is a small four-page test paper with a nested subsection, multi-page +sections, and intentionally overlapping sibling page ranges. `expected.json` contains only stable facts: page count, titles and paths, 1-based inclusive -spans, distinctive text anchors, topology, and named invariants. It does not -pin summaries or any other wording produced by a model. +page ranges, distinctive text anchors, tree shape, and validation rules. It +does not pin summaries or any other wording produced by a model. -The offline contract test parses the fixed PDF, checks every anchor against its -declared page, validates topology and invariants, and confirms the overlap case -remains representable. Paper extraction and future PageIndex work must reuse -this fixture rather than create a competing golden document. +The offline test parses the fixed PDF, checks every text anchor against its +declared page, validates the tree and all rules, and confirms that overlapping +ranges work. Paper extraction and future PageIndex work must reuse these files +rather than create a competing test paper. -## Current Behavior and Known Gaps +## Current Gaps The repository does not yet guarantee the target pipeline above: - `pdf_to_markdown()` concatenates non-empty page text and drops page boundaries and empty pages. - `paper_flow()` sends the flattened document to one extraction agent and asks - it to return the canonical `Paper` directly. + it to return the final `Paper` directly. - The model currently controls IDs, edges, citations, source fields, and - content instead of returning a restricted structural draft. + content instead of returning the limited draft described above. - Resolved source URL, content hash, publication/version time, fetch time, and - exact-version availability are not assembled authoritatively end to end. + exact-version availability are not consistently collected from the source. - `DoiIdentifier` raises `NotImplementedError` because there is no accessible content resolver. -- `TreeKnowledge` provides traversal helpers but does not yet enforce the full - root, edge, reachability, acyclicity, or span invariant set at construction. +- `TreeKnowledge` provides traversal helpers but does not yet check every root, + link, reachability, cycle, or page-range rule when it is created. - The structured-output failure tracked by issue #91 remains separate from this design issue. -Those gaps are implementation work after this contract. Adding PageIndex first -would not fix them and is not required to implement the staged Paper pipeline. +Those gaps are future implementation work. Adding PageIndex first would not fix +them and is not required to implement the Paper steps above. diff --git a/contexts/design/library/local.md b/contexts/design/library/local.md index 227c739..e3aad98 100644 --- a/contexts/design/library/local.md +++ b/contexts/design/library/local.md @@ -1,125 +1,124 @@ -# Local Semantic Knowledge Library Design +# Local Knowledge Library Design ## Quick Summary -- **Purpose**: Define local persistence and semantic retrieval for validated QuantMind knowledge. -- **Read when**: Changing `LocalKnowledgeLibrary`, indexing, semantic filters, retrieval identity, or storage ownership. +- **Purpose**: Define how the local library stores validated knowledge and searches it by meaning. +- **Read when**: Changing `LocalKnowledgeLibrary`, search records, filters, query results, or file ownership. - **Status**: Current design owned at runtime by `quantmind.library`. -- **Core boundary**: Canonical knowledge is source truth, semantic records and vectors are derived indexes, and raw source artifacts remain caller-owned. +- **Core rule**: Stored `BaseKnowledge` is the data to keep. Search text and vectors can be rebuilt. The caller stores raw PDF, HTML, and media files. - **User guide**: [`docs/library.md`](../../../docs/library.md) ## Contents -- [Decision Summary](#decision-summary) -- [Ownership Boundaries](#ownership-boundaries) -- [Canonical and Derived Data](#canonical-and-derived-data) -- [Retrieval Grain and Identity](#retrieval-grain-and-identity) -- [Derived-Index Invalidation](#derived-index-invalidation) -- [Financial-Time Semantics](#financial-time-semantics) -- [Local Ranking Choice](#local-ranking-choice) -- [Non-goals](#non-goals) +- [Key Decisions](#key-decisions) +- [Who Owns What](#who-owns-what) +- [Stored Knowledge and Rebuildable Search Data](#stored-knowledge-and-rebuildable-search-data) +- [What Can Match a Query](#what-can-match-a-query) +- [When to Rebuild Search Data](#when-to-rebuild-search-data) +- [Time Fields and Look-Ahead](#time-fields-and-look-ahead) +- [Why SQLite and Simple Exact Ranking](#why-sqlite-and-simple-exact-ranking) +- [Out of Scope](#out-of-scope) -## Decision Summary +## Key Decisions -`quantmind.library` owns persistence and semantic retrieval for canonical -QuantMind knowledge. It replaces the obsolete repository concept of a generic -storage layer that stored raw files, knowledge JSON, embeddings, and indexes -behind one extensible backend abstraction. +`quantmind.library` stores validated QuantMind knowledge and searches it by +meaning. It replaces an older design for one generic storage layer that would +hide raw files, knowledge JSON, embeddings, and indexes behind one backend API. -The current public surface is deliberately domain-level: +The public API is intentionally small: - `LocalKnowledgeLibrary` - `SemanticQuery` - `SemanticHit` There is no public `Storage`, `VectorStore`, `Retriever`, provider registry, or -backend hierarchy. A new backend abstraction is justified only by a second -real implementation and a stable shared contract. +backend class hierarchy. Add a shared backend API only after a second working +implementation proves which behavior is truly shared. -## Ownership Boundaries +## Who Owns What -| Layer | Responsibility | +| Package or caller | Responsibility | |---|---| -| `quantmind.knowledge` | Immutable canonical schemas and embedding projections; no I/O | -| `quantmind.library` | Persist canonical knowledge, maintain rebuildable semantic records, and return typed evidence | -| `quantmind.flows` | Produce canonical knowledge and optionally pass it to a library consumer | -| `quantmind.mind` or an agent application | Use retrieval as a tool and synthesize answers | -| Caller or source-specific pipeline | Retain raw PDF, HTML, media, and operational artifacts | +| `quantmind.knowledge` | Define immutable knowledge models and the text used for embeddings; perform no I/O | +| `quantmind.library` | Store validated knowledge, maintain rebuildable search records, and return `SemanticHit` results | +| `quantmind.flows` | Produce validated knowledge and optionally pass it to a library | +| `quantmind.mind` or an agent application | Search the library and use matches to write answers | +| Caller or source-specific pipeline | Retain raw PDF, HTML, media, and operational files | -Raw source retention is intentionally outside the V1 library. A canonical -`SourceRef` and citations preserve provenance identity; they do not turn the -library into an artifact store. +The V1 library does not store raw source files. `SourceRef` and citations point +back to the source, but they do not contain the source file itself. -## Canonical and Derived Data +## Stored Knowledge and Rebuildable Search Data -Canonical `BaseKnowledge` is the source of truth. Embeddings, projection text, -filter columns, and vector indexes are derived and rebuildable even when they -share one SQLite database. +Validated `BaseKnowledge` is the record that must be preserved. Embeddings, +the text sent to the embedder, filter columns, and vector indexes can all be +rebuilt, even when they share one SQLite database with the knowledge records. The local implementation separates three concerns: -- `knowledge_items` stores one validated aggregate root per knowledge item. -- `knowledge_nodes` stores canonical `TreeNode` values separately with parent, - position, content hash, and item ownership. -- `semantic_records` stores item/root/node projections and vector metadata used - by exact cosine ranking. +- `knowledge_items` stores one complete validated knowledge item. +- `knowledge_nodes` stores each `TreeNode` in a separate row with its parent, + position, content hash, and owning item. +- `semantic_records` stores searchable text and vector details for whole items, + roots, and nodes. -Concrete types do not get a table per Pydantic subtype. The aggregate-root plus -normalized-node design preserves typed reconstruction while giving future tree -navigation a node-level persistence boundary. +Concrete types do not get a table per Pydantic subtype. Storing each complete +item plus separate node rows lets the library rebuild the original typed item +and later navigate a tree one node at a time. -## Retrieval Grain and Identity +## What Can Match a Query -- A `FlattenKnowledge` item produces one semantic target from its exact - `embedding_text()` projection. -- A `TreeKnowledge` item produces one item target for its root with - `node_id=None`, plus one target for every non-root node. -- Target identity distinguishes a whole item from one of its nodes. -- `SemanticHit.matched_text` is the exact projection used for ranking. -- Callers use `get(item_id)` to resolve full canonical knowledge and node paths. +- A `FlattenKnowledge` item produces one searchable record from its exact + `embedding_text()` value. +- A `TreeKnowledge` item produces one record for the whole item with + `node_id=None`, plus one record for every non-root node. +- Each record says whether it represents a whole item or one node. +- `SemanticHit.matched_text` is the exact text used to rank the match. +- Callers use `get(item_id)` to load the full knowledge item and node paths. -Re-putting unchanged knowledge with the same canonical item ID is idempotent -and reuses its embeddings. This does not claim deduplication across independent -extraction runs that generated different canonical IDs. +Putting unchanged knowledge with the same item ID is safe to repeat and reuses +its embeddings. The library does not merge separate extraction runs that +created different item IDs. -## Derived-Index Invalidation +## When to Rebuild Search Data Every stored vector records the information needed to decide whether it is -reusable: embedding model, dimension, projection hash, source content hash, -knowledge schema version, and projection schema version. +reusable: embedding model, dimension, embedding-text hash, source content hash, +knowledge model version, and embedding-text format version. -Changed metadata invalidates only affected targets. Canonical deletion removes -the item, its normalized nodes, and its derived semantic records in one -transaction. Corrupt dimensions, vector bytes, canonical payloads, or orphaned -derived records fail explicitly instead of producing a plausible partial hit. +When any of those values changes, rebuild only the affected search records. +Deleting an item removes the item, its nodes, and its search records in one +transaction. Invalid vector sizes or bytes, unreadable knowledge data, and +search rows without an item raise an error instead of returning an incomplete +match that looks valid. -## Financial-Time Semantics +## Time Fields and Look-Ahead `as_of` and `available_at` answer different questions: -- `as_of` is the information cutoff represented by the knowledge. +- `as_of` is the latest date covered by the knowledge. - `available_at` is when that source version became observable. -`available_at_before` excludes records whose availability is unknown or after -the cutoff. `as_of_before` alone does not prevent look-ahead. Source kind, -item type, confidence, tags, tree ID, and both time cutoffs combine before -ranking. +`available_at_before` excludes records that became available after the cutoff +or have no known availability time. Filtering only by `as_of_before` can still +leak future information. Apply source kind, item type, confidence, tags, tree +ID, and both time cutoffs before ranking results. -## Local Ranking Choice +## Why SQLite and Simple Exact Ranking -SQLite provides transactions, foreign keys, typed reconstruction, and explicit -stale/corrupt behavior for canonical knowledge. NumPy exact cosine ranking is -sufficient for the current local scale and keeps the derived search layer -replaceable without changing user code. +SQLite provides transactions, foreign keys, and reliable reconstruction of +typed knowledge. NumPy can compare every embedding with cosine similarity at +the current local data size. This simple exact ranking can later be replaced +without changing user code. -A future approximate or remote index may replace the private derived layer. It -does not replace canonical persistence and must not leak a provider-specific -result through `SemanticHit`. +A future approximate or remote index may replace the private search +implementation. It does not replace stored knowledge and must return the same +`SemanticHit` type regardless of provider. -## Non-goals +## Out of Scope -- raw source artifact storage; -- a generic RAG framework or answer synthesis; +- raw source file storage; +- a generic framework for retrieving context and writing answers; - a public embedder, vector-store, retriever, or backend registry; - PageIndex tree construction or navigation; -- cross-run knowledge deduplication without a separate stable-ID contract. +- merging knowledge across runs without a separate stable-ID design. diff --git a/contexts/design/operations/naming.md b/contexts/design/operations/naming.md index 2254f9a..69d4cf8 100644 --- a/contexts/design/operations/naming.md +++ b/contexts/design/operations/naming.md @@ -1,77 +1,77 @@ -# Public Operation Naming +# Public Operation Names ## Quick Summary -- **Purpose**: Define consistent names for public QuantMind operations and their input, config, and result types. -- **Read when**: Adding or renaming a public callable, operation type, or composed recipe. -- **Status**: Current naming direction; the legacy `paper_flow` name remains unchanged until a separately approved migration. -- **Core rule**: Name a public operation with its stage verb; reserve `pipeline` for composed recipes, and do not use `flow` as an operation verb. +- **Purpose**: Define clear names for public QuantMind functions and their input, config, and result types. +- **Read when**: Adding or renaming a public function, operation type, or pipeline. +- **Status**: Use these rules for new names. Keep the existing `paper_flow` name until a separate change explains how callers will migrate. +- **Core rule**: Use a verb that says what the function does. Use `pipeline` only when one function combines several public operations. Do not use `flow` as a verb. ## Contents - [Scope](#scope) -- [Operation Stages](#operation-stages) +- [Name Patterns](#name-patterns) - [Type Names](#type-names) - [Current API](#current-api) - [Review Checklist](#review-checklist) ## Scope -This document defines how public QuantMind callables communicate intent. It is -a naming contract, not a package-layout migration. Existing names may be -changed in focused compatibility work; they are not silently renamed here. +This page defines how to name public QuantMind functions. It does not rename +packages or existing functions. Any existing name change needs a separate plan +for users who depend on the old name. The runtime library API is designed for Python callers. Repository guidance -for coding agents belongs to the development harness (`AGENTS.md`, skills, -docs, fixtures, and verification), not in the runtime API description. +for coding agents belongs in `AGENTS.md`, skills, docs, fixtures, and checks; +it does not belong in the runtime API. -## Operation Stages +## Name Patterns -Public names use a stage verb plus the domain or result: +Start a public function name with a verb that describes its result: -| Stage | Name pattern | Transformation | +| Work | Name pattern | What it does | |-------|--------------|----------------| -| Collection | `collect_` | External sources to source-faithful documents and evidence | -| Knowledge extraction | `extract__knowledge` | Documents to typed `quantmind.knowledge` values | -| Index construction | `build__index` | Documents or knowledge to a retrieval index | +| Collection | `collect_` | Fetch source documents and the details needed to verify them | +| Knowledge extraction | `extract__knowledge` | Turn documents into typed `quantmind.knowledge` values | +| Index construction | `build__index` | Turn documents or knowledge into a search index | | Analysis | `analyze_` | Domain inputs to an analytical result | -| Generic execution | `batch_run` and similar combinators | Apply an operation without changing its domain meaning | -| Composed recipe | `__pipeline` | Compose multiple named stages into a reusable pipeline | +| Generic execution | `batch_run` and similar helpers | Apply an operation to a batch without changing what the operation means | +| Pipeline | `__pipeline` | Combine several named operations into one reusable function | -`flow` is not a domain verb. Do not add new public `*_flow` names merely -because a callable performs several steps. Use a precise operation name, or a -`*_pipeline` name only when the callable deliberately composes multiple public -stages as a reusable recipe. +`flow` is not a verb that explains a result. Do not add a public `*_flow` name +only because a function performs several steps. Name the result precisely, or +use `*_pipeline` when the function combines multiple public operations. ## Type Names -- Input types describe caller intent, such as `NewsWindow` or `PaperInput`. +- Input types describe what the caller supplies, such as `NewsWindow` or + `PaperInput`. - Config types name the domain and stage, such as `NewsCollectionCfg` or a future `PaperExtractionCfg`. - Result types describe returned data, such as `NewsBatch`, `Paper`, or `PageIndex`. -- Provider names stay out of public operation names unless provider-specific - behavior is itself the public contract. +- Keep provider names out of public function names unless callers are choosing + provider-specific behavior. ## Current API -- `collect_news` is a collection operation and follows this contract. -- `batch_run` is a generic execution combinator, not a news or paper stage. -- `paper_flow` is an existing semantic-extraction API with legacy naming. It - is not the naming precedent for new operations; any rename belongs in a - separate compatibility change. +- `collect_news` is a collection operation and follows these naming rules. +- `batch_run` is a generic batch helper, not a news or paper operation. +- `paper_flow` is an existing extraction API with an old name. Do not copy that + pattern for new operations. Renaming it requires a separate migration plan. -The current `quantmind.flows` package remains the apex implementation namespace -for this release. Whether it should become `operations` or be split from a -future `pipelines` package is deliberately outside this document. +The current `quantmind.flows` package continues to contain public operations in +this release. Renaming it to `operations` or adding a separate `pipelines` +package is outside this page. ## Review Checklist Before adding a public callable, state: -1. Its operation stage. -2. Its input and result contracts. -3. Whether it is one operation or a genuine multi-stage pipeline. -4. Why its verb matches the observable result. +1. What work it performs. +2. What it accepts and returns. +3. Whether it is one operation or combines several operations. +4. Why its verb describes the returned result. -If those answers are unclear, settle the contract before adding a public name. +If those answers are unclear, define the behavior before choosing a public +name. diff --git a/contexts/dev/README.md b/contexts/dev/README.md index 64a86c1..edd5cb9 100644 --- a/contexts/dev/README.md +++ b/contexts/dev/README.md @@ -2,7 +2,7 @@ ## Quick Summary -- **Purpose**: Route contributors and coding agents to canonical development +- **Purpose**: Route contributors and coding agents to existing development rules without copying them here. - **Read when**: Extending, fixing, reviewing, testing, or publishing QuantMind. - **Load next**: Select the single row that matches the task; load additional @@ -11,28 +11,29 @@ ## Contents -- [Canonical Sources](#canonical-sources) +- [Rule Index](#rule-index) - [Verification Rule](#verification-rule) -## Canonical Sources +## Rule Index Use this index when extending or maintaining the repository. Follow the linked -canonical source instead of treating this page as a replacement for it. +rule instead of treating this page as a replacement for it. -| Need | Canonical source | +| Need | Read | |---|---| -| Architecture constraints and module ownership | [`AGENTS.md`](../../AGENTS.md) or [`CLAUDE.md`](../../CLAUDE.md) | +| Package responsibilities and coding rules | [`AGENTS.md`](../../AGENTS.md) or [`CLAUDE.md`](../../CLAUDE.md) | | Component-development workflow | [`quantmind-dev` component workflow](../../.agents/skills/quantmind-dev/references/develop-components.md) | | Issue and pull-request labels | [Repository label guidance](labels.md) | | Issue and pull-request body formatting | [GitHub writing style](github-writing.md) | | Public operation and source catalog | [`docs/README.md`](../../docs/README.md) | -| Public operation naming | [Operation naming contract](../design/operations/naming.md) | -| Component designs | [Canonical design index](../design/README.md) | +| Public operation naming | [Operation naming rules](../design/operations/naming.md) | +| Component designs | [Design index](../design/README.md) | | Test patterns and coverage | [`tests/`](../../tests/) | | Focused runnable examples | [`examples/`](../../examples/) | -| Deterministic verification | [`scripts/verify.sh`](../../scripts/verify.sh) | +| All local checks | [`scripts/verify.sh`](../../scripts/verify.sh) | ## Verification Rule Run `bash scripts/verify.sh` for every change. For a public-network component, -also run the bounded live verifier listed in the public component catalog. +also run the limited live-network check listed in the public component +catalog. diff --git a/contexts/dev/github-writing.md b/contexts/dev/github-writing.md index 37a2ce7..d886e93 100644 --- a/contexts/dev/github-writing.md +++ b/contexts/dev/github-writing.md @@ -2,32 +2,31 @@ ## Quick Summary -- **Purpose**: Define the source-format contract for QuantMind GitHub Issue, - Pull Request, Discussion, and comment bodies. +- **Purpose**: Define how raw Markdown should be formatted in QuantMind GitHub + issues, pull requests, discussions, and comments. - **Read when**: Drafting or editing any text submitted to GitHub. -- **Key rule**: Keep each logical paragraph, list item, checklist item, table - row, and blockquote paragraph on one physical source line. +- **Key rule**: Keep each paragraph, list item, checklist item, table row, and + blockquote paragraph on one line in the raw Markdown. - **Required check**: Read the raw body back after writing and remove formatter-introduced hard wrapping without changing meaning. ## Contents -- [No Hard-wrapped Prose](#no-hard-wrapped-prose) +- [Do Not Wrap Prose at a Fixed Width](#do-not-wrap-prose-at-a-fixed-width) - [Write Workflow](#write-workflow) -This guide is the canonical source-format contract for QuantMind Issue, Pull -Request, Discussion, and comment bodies. It applies to text submitted to -GitHub, not to Markdown files stored in the repository. +This guide applies to text submitted to GitHub, not to Markdown files stored in +the repository. -## No Hard-wrapped Prose +## Do Not Wrap Prose at a Fixed Width -GitHub wraps rendered prose for the viewer. Do not insert source newlines at a -fixed width, including 80 columns. +GitHub wraps text for the viewer. Do not insert line breaks in the raw body at +a fixed width, including 80 columns. -- Keep each logical paragraph on one physical source line. -- Keep each list or checklist item on one physical source line. -- Keep each blockquote paragraph on one physical source line after `>`. -- Keep each Markdown table row on one physical source line. +- Keep each paragraph on one line in the raw Markdown. +- Keep each list or checklist item on one line. +- Keep each blockquote paragraph on one line after `>`. +- Keep each Markdown table row on one line. - Preserve line structure inside fenced code blocks. - Use blank lines, headings, lists, code fences, tables, or an intentional Markdown hard break only when they express real structure. @@ -39,14 +38,14 @@ to wrap prose. ## Write Workflow -1. Draft the complete body with one logical prose unit per source line. +1. Draft the complete body with one paragraph or list item per source line. 2. Preserve the selected Issue or Pull Request template and its checklist. 3. Submit the body as written, preferably with `--body-file` when using `gh`. 4. Read the raw body back after creation or editing and check that prose was not hard-wrapped before considering the write complete. -When Prettier is already available, use its no-wrap mode as a preflight on the -temporary body file: +When Prettier is already available, use its no-wrap mode to check the temporary +body file before submission: ```bash prettier --write --parser markdown --prose-wrap never /tmp/github-body.md @@ -54,7 +53,7 @@ prettier --check --parser markdown --prose-wrap never /tmp/github-body.md ``` Do not introduce a repository-wide Markdown formatter or change Python's -80-column setting for this purpose. The preflight applies only to the temporary +80-column setting for this purpose. This check applies only to the temporary GitHub body. When updating an existing hard-wrapped body, normalize the complete body while diff --git a/contexts/dev/labels.md b/contexts/dev/labels.md index e8c2939..0d5764d 100644 --- a/contexts/dev/labels.md +++ b/contexts/dev/labels.md @@ -4,72 +4,71 @@ - **Purpose**: Select consistent labels for QuantMind issues and pull requests. - **Read when**: Creating, updating, triaging, or reviewing an issue or PR. -- **Cardinality**: Use exactly one `type:` label, normally one `area:` label +- **Label count**: Use exactly one `type:` label, normally one `area:` label (at most two), and optional `impact:` labels. -- **Selection rule**: Label accepted intent and ownership, not every file path - touched by an implementation. +- **Selection rule**: Label the work's actual purpose and main area, not every + file it touches. ## Contents -- [Cardinality](#cardinality) +- [How Many Labels to Use](#how-many-labels-to-use) - [Type Labels](#type-labels) - [Area Labels](#area-labels) - [Impact Labels](#impact-labels) - [Resolution and Community Labels](#resolution-and-community-labels) - [Issues and Pull Requests](#issues-and-pull-requests) - [Examples](#examples) -- [Existing Label Migration](#existing-label-migration) +- [Rename Existing Labels](#rename-existing-labels) -This file is the canonical decision guide for labeling QuantMind issues and -pull requests. Labels describe three independent dimensions: +Labels describe three separate parts of the work: - `type:` answers what kind of change is proposed or delivered. -- `area:` answers which repository ownership surface is affected. -- `impact:` identifies special compatibility or live-network risk. +- `area:` answers which part of the repository owns the work. +- `impact:` identifies breaking changes or live-network risk. -Choose labels from the work's intent and accepted scope, not only from the -files it happens to touch. +Choose labels from the work's purpose and agreed scope, not only from the files +it happens to touch. -## Cardinality +## How Many Labels to Use Every issue and pull request must have **exactly one** `type:` label. Normally choose **one** `area:` label. Use two only when the work genuinely -spans two ownership surfaces; never use more than two. +belongs to two main areas; never use more than two. -`impact:` labels are optional and additive. Resolution and community labels -do not count toward these limits. +Add `impact:` labels only when needed. Resolution and community labels do not +count toward these limits. ## Type Labels | Label | Use when | |---|---| -| `type: bug` | Existing behavior does not meet its contract or documented expectation. | +| `type: bug` | Existing behavior does not match its documented or expected behavior. | | `type: feature` | The work adds a new capability or observable behavior. | | `type: refactor` | Existing implementation is reorganized without intentionally changing public behavior. | | `type: docs` | The primary deliverable is documentation, guidance, or explanatory example text. | | `type: maintenance` | The work performs specific repository upkeep, such as dependency, configuration, release, or housekeeping changes. | | `type: design` | The primary deliverable is an architecture decision, RFC, or design that precedes implementation. | -`type: maintenance` is not a miscellaneous bucket. If the intended outcome is +`type: maintenance` is not a catch-all label. If the intended outcome is unclear, clarify the work before labeling it. ## Area Labels -| Label | Ownership surface | +| Label | Main area | |---|---| | `area: contexts` | Repository information routing and discovery under `contexts/`. This does not mean all documentation. | | `area: harness` | Contributor and agent controls: `AGENTS.md`, `CLAUDE.md`, skills, CI, verification scripts, hooks, templates, and rulesets. | -| `area: knowledge` | Knowledge models, schemas, serialization, and representation under `quantmind/knowledge/`. | -| `area: configs` | Typed inputs and configuration contracts under `quantmind/configs/`. | -| `area: preprocess` | Deterministic acquisition, parsing, cleaning, formatting, and source handling under `quantmind/preprocess/`. | +| `area: knowledge` | Knowledge models, stored formats, serialization, and representation under `quantmind/knowledge/`. | +| `area: configs` | Typed inputs and configuration models under `quantmind/configs/`. | +| `area: preprocess` | Fetching, parsing, cleaning, formatting, and source handling under `quantmind/preprocess/`. | | `area: flows` | Public operation implementations under `quantmind/flows/`. It is not a generic synonym for pipeline or orchestration. | -| `area: mind` | Memory, tools, MCP integration, and the cognitive layer under `quantmind/mind/`. | +| `area: mind` | Memory, tools, MCP integration, and agent reasoning under `quantmind/mind/`. | | `area: utils` | The narrowly owned utilities surface under `quantmind/utils/`. | | `area: examples` | Examples are the primary deliverable, rather than supporting files for another area. | | `area: packaging` | Installation, builds, dependencies, releases, and package metadata. | -Area labels describe ownership, not every touched path. For example, a news +Area labels describe the main work, not every touched path. For example, a news design-document-only change is `type: docs` + `area: preprocess`, while a skill-only guidance change is `type: docs` + `area: harness`. @@ -77,7 +76,7 @@ skill-only guidance change is `type: docs` + `area: harness`. | Label | Use when | |---|---| -| `impact: breaking` | The accepted change can break a public API, serialized contract, or supported compatibility boundary. | +| `impact: breaking` | The accepted change can break a public API, stored data format, or supported integration. | | `impact: live-network` | The work depends on or changes real public-network behavior, external availability, or a live smoke test. | ## Resolution and Community Labels @@ -96,7 +95,7 @@ including its no-hard-wrap rule. - Label a pull request from its actual diff. - A pull request that closes an issue should normally align with the issue's type and area. If implementation differs, label the pull request accurately - and update the issue when its accepted scope changed. + and update the issue when its agreed scope changed. - Re-evaluate labels when scope changes; do not preserve a stale label merely because it was applied first. @@ -106,12 +105,12 @@ including its no-hard-wrap rule. |---|---|---| | Context framework in [#101](https://github.com/LLMQuant/quant-mind/issues/101) | `type: feature`, `area: contexts` | It added a new routing capability. | | CI/E2E consolidation in [#102](https://github.com/LLMQuant/quant-mind/issues/102) | `type: refactor`, `area: harness`, `impact: live-network` | It restructures repository verification, including live smoke tests. | -| This label-system issue and its PR | `type: docs`, `area: contexts`, `area: harness` | The canonical deliverable is context guidance routed through contributor controls. | -| Fix `paper_flow` contract behavior | `type: bug`, `area: flows` | An existing public operation is broken. | +| This label-system issue and its PR | `type: docs`, `area: contexts`, `area: harness` | The main result is context guidance routed through contributor controls. | +| Fix documented `paper_flow` behavior | `type: bug`, `area: flows` | An existing public operation is broken. | | Update only the news design document | `type: docs`, `area: preprocess` | The document belongs to the news preprocessing surface, not contexts. | | Add a public news source | `type: feature`, `area: preprocess`, `impact: live-network` | It adds acquisition behavior backed by a real external source. | -## Existing Label Migration +## Rename Existing Labels Rename labels instead of deleting and recreating them so GitHub preserves history and current assignments: @@ -125,6 +124,6 @@ history and current assignments: | `example` | `area: examples` | | `harness` | `area: harness` | -After renaming, create the missing taxonomy labels and audit open issues and +After renaming, create the missing labels and audit open issues and pull requests. Remove accidental multiple `type:` labels, choose one or two areas from accepted scope, and leave resolution/community labels intact. diff --git a/contexts/usage/README.md b/contexts/usage/README.md index f925ff2..3f973b1 100644 --- a/contexts/usage/README.md +++ b/contexts/usage/README.md @@ -8,31 +8,32 @@ operation. - **Load next**: Start with the component row that matches the requested operation; do not load unrelated component designs. -- **Import rule**: Inputs/configs come from `quantmind.configs`, operations from - `quantmind.flows`, and result types from their owning layer. +- **Import rule**: Import inputs and configs from `quantmind.configs`, + operations from `quantmind.flows`, and result types from the package shown in + the component catalog. ## Contents - [Public Usage Sources](#public-usage-sources) -- [Import Boundary](#import-boundary) +- [Where to Import From](#where-to-import-from) ## Public Usage Sources Use this index when calling QuantMind as a library. These links point to the current public API, focused examples, and component-specific guidance. -| Need | Canonical source | +| Need | Read | |---|---| | Current operations, inputs, results, and sources | [Public component catalog](../../docs/README.md) | | Installation and common usage | [Root README usage](../../README.md#-usage-examples) | -| Paper extraction | [Paper E2E design contract](../design/flow/paper.md) | +| Paper extraction | [Paper extraction design](../design/flow/paper.md) | | News collection | [News design and behavior](../design/flow/news.md) | -| Local semantic search | [Library guide](../../docs/library.md) and [focused example](../../examples/library/README.md) | +| Search local knowledge by meaning | [Library guide](../../docs/library.md) and [focused example](../../examples/library/README.md) | | Runnable operation examples | [`examples/flows/`](../../examples/flows/) | | Focused preprocessing examples | [`examples/preprocess/`](../../examples/preprocess/) | -## Import Boundary +## Where to Import From Import public inputs and configs from `quantmind.configs`, public operations -from `quantmind.flows`, and result contracts from the owning layer identified -by the component catalog. +from `quantmind.flows`, and result types from the package shown in the +component catalog.