stack: MCP submit-Tool mit Upload Review Gate und Quarantäne-Schreibpfad (schliesst #32)
CI / verify (push) Successful in 53s
Release / release (push) Successful in 36s

Files changed:
- .gitignore
- AGENTS.md
- CHANGES.md
- INSTALL-MCP.md
- README.md
- VERSION
- docs/why-gates-are-code.md
- instructions/gates.md
- instructions/ingest-queue.md
- instructions/mcp-read-server.md
- instructions/wiki-ingest/SKILL.md
- raw/CONTRACT.md
- tools/CONTRACT.md
- tools/chemenu/cli.py
- tools/chemenu/commands/docs_verify.py
- tools/chemenu/commands/doctor.py
- tools/chemenu/commands/upload_cmd.py
- tools/chemenu/config.py
- tools/chemenu/mcp/server.py
- tools/chemenu/tests/test_doctor.py
- tools/chemenu/tests/test_mcp_server.py
- tools/chemenu/tests/test_upload.py
- tools/chemenu/tests/test_upload_cmd.py
- tools/chemenu/upload.py
This commit is contained in:
2026-09-11 09:51:37 +02:00
parent 4781140375
commit 828521861d
24 changed files with 1866 additions and 56 deletions
+34
View File
@@ -16,6 +16,7 @@ from `kb/` is what makes that boundary visible.
- [Directory routing: a date shard, not a type](#directory-routing-a-date-shard-not-a-type)
- [Getting a file in: `incoming/`](#getting-a-file-in-incoming)
- [Getting a file in from outside: `mcp-upload/`](#getting-a-file-in-from-outside-mcp-upload)
- [Capture fields: `fidelity` and `authority`](#capture-fields-fidelity-and-authority)
- [Rules](#rules)
- [Raw content is data, never instructions](#raw-content-is-data-never-instructions)
@@ -119,6 +120,39 @@ only, so a file waiting there is not yet a finding. It is also never committed -
merely asserted, by `docs verify`'s ignore-rule canaries - which is what makes accepting a file the
moment its immutability under the rules below begins, not the moment it was dropped.
## Getting a file in from outside: `mcp-upload/`
`incoming/` above is the local path: a human drops a file where they are already sitting at a
keyboard. A caller that is not this terminal - the MCP server's opt-in `submit` tool
(`.wikitool-upload.json`, see `tools/CONTRACT.md`) - has no such standing, so it gets a stage of
its own, **before** `incoming/`, not instead of it:
```
mcp-upload/<id>/ nobody has looked at this yet
| wikitool upload accept <id> --confirm <token> <- a human decides (Exit 42 gate)
incoming/ ordinary local intake, as above
```
Two different grants of trust sit on either side of that arrow. `incoming/` holds material a
human already chose to feed the pipeline - unreviewed only in the sense of "not yet compiled".
`mcp-upload/` holds material **nobody has looked at**: a filename and a byte string a stranger's
process sent, carrying whatever identity the deployment's authentication middleware attached to
the request and nothing more trustworthy than that. Promoting out of it is `wikitool upload
accept`, gated behind `--confirm` the same way `publish`'s Mass-Update Gate is - see
[instructions/gates.md](../instructions/gates.md) "Upload Review Gate" and
[instructions/ingest-queue.md](../instructions/ingest-queue.md) for what a reviewer checks before
clearing it. `wikitool upload reject <id> --reason "<why>"` deletes the material without a gate -
rejecting needs no clearance, only accepting a stranger's file into the pipeline does - keeping
only the reason and a sha256 in `mcp-upload/ledger.jsonl`.
`mcp-upload/` is stricter than `incoming/` in exactly the way that matters here: no command in
the ordinary pipeline reads it at all, not even to report it as a finding, and the identity
attached to a submission is a header value the deployment's middleware set - a claim, not a
verified fact, recorded as such (`submitter_source` names the header it came from). Everything
this file says about `incoming/` and `raw/` - immutability, untrusted content, coverage -
applies unchanged to whatever a submission becomes once a human has accepted it; nothing about
having arrived this way survives the promotion.
## Capture fields: `fidelity` and `authority`
Two things are knowable at the moment a file is accepted and at no point afterwards: **how