> ## Documentation Index
> Fetch the complete documentation index at: https://vitruvio.sh/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Registering evidence

> Media types, normalized views, and why images have no pipeline.

```bash theme={null}
vitruvio source register paper.pdf --media-type application/pdf --normalize-with pdf-text
vitruvio source register notes.md --origin "https://example.com/notes" --license CC-BY-4.0
vitruvio source replace paper-v2.pdf --supersedes <BLOCK_ID>
```

## What is refused, and before what

A path is checked before the file is read and before a brain is opened for a write. Four refusals, exit 2:

| refused | why |
| - | - |
| a symlink | a link registers the same bytes under a second origin; register the target |
| outside the declared directory | only reachable when a caller bounds the read, as a declared source does |
| not a regular file | `read_bytes()` on a FIFO blocks forever, which is a hang and not an error |
| over `[ingest] max_bytes` | checked against the file's size *before* the read: a refusal rather than an out-of-memory kill |

`max_bytes` is unset by default, which is right for a person registering their own files and wrong for anything
accepting evidence from elsewhere. It is declared per brain and enforced in the runtime, so bytes assembled in
memory cannot exceed what bytes read from a file may not.

```toml theme={null}
[ingest]
max_bytes = 52428800
```

These are the same four a declared source has applied since it existed. What changed is that a path a *person*
types goes through them too, which is the one that has a symlink in it.

## Where it came from is never guessed

`--origin` is recorded in provenance and is the key a later pull uses to answer "already have this?". Omitted, it
is the path you named — which is meaningful because you named it. Nothing else invents one: the runtime is handed
bytes, a media type and an origin, and if a caller has no origin to give it says so rather than having this
machine's directory layout written into a permanent record. See [ADR-0021](https://github.com/getsfumato/vitruvio/blob/main/adr/0021-transport-neutral-evidence.md).

## The media type is load-bearing

It is recorded in the block, it travels with the artifact, and it is what **both** normalization dispatch and
projection read. A Markdown file filed as `application/octet-stream` is a file no text pipeline will offer to
normalise and no analyzer will read as prose.

`mimetypes` does not know `.md`, so vitruvio carries a small table of extensions a knowledge brain meets constantly
(`.md`, `.rst`, `.tex`, `.jsonl`, `.yaml`, `.toml`, `.webp`, `.avif`). Declaring `--media-type` is still better: an
extension is a claim, not evidence.

## Normalized views

A **normalized view** is a deterministic, content-addressed rendering of a source, produced by a named and versioned
pipeline, and recorded in provenance. It exists to be *read* — by a proposer, by an analyzer, by a person, and by
search: a canonical block holds no prose of its own, so the view is the only text the indices and the exhaustive
scan can observe.

One policy decides which pipeline runs, and `source register`, `source replace` and `ingest run` all follow it.
Nothing said means the pipeline that suits the media type, from the table below, and no view when none suits — a
raster image. `--normalize-with NAME` overrides it. `--normalize-with none` produces no view at all. A declared
source is the exception: it applies exactly what its declaration says, and nothing more.

```bash theme={null}
vitruvio ingest pipelines
```

| pipeline | accepts |
| - | - |
| `text` | text/plain, text/csv, text/tab-separated-values |
| `markdown` | text/markdown |
| `html-text` | text/html, application/xhtml+xml |
| `svg-text` | image/svg+xml |
| `json-canonical` | application/json and the `+json` family |
| `pdf-text` | application/pdf — needs `[vision]` |

Because a view is evidence, one requirement dominates every implementation choice: **the same input and the same
pipeline version must produce identical bytes on every machine.** Which is why:

* The HTML extractor is `html.parser` from the standard library, not BeautifulSoup or lxml. Both are better parsers
  and both change their whitespace and entity handling between releases, which would move a view's digest on a
  dependency bump.
* Nothing reflows prose, fixes spelling or normalises quotes. Those are *interpretations*, and an interpretation
  belongs in semantic memory as a cited proposal.
* **Raster images have no pipeline at all.** A "normalized PNG" is a re-encode, and re-encoding is not reproducible
  across libpng versions. Vision embeddings read the original blob instead — the bytes that were observed are the
  evidence. SVG is the exception only because it is text.

## Re-registering is free

Content is addressed by its hash, so registering the same bytes twice produces one block and reports
`duplicate: true`. This is what makes an ingestion pipeline safe to re-run.

The view is part of that identity. The same bytes registered under a different pipeline — or under none — are a
second block, not a duplicate, because a block that names a view and one that does not are different statements
about the evidence. A block registered before views were the default keeps its identity, and stays searchable by
media type only, until it is registered or replaced again.

## No in-place edit

There is none, anywhere. A newer edition of a source is a **new block plus a supersession edge**, which is what
`source replace` does. Immutability is what lets a root mean something.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.