intake: several configured intake channels (mcp, folder, command) all end in quarantine/ (was mcp-upload/), and upload collect pulls the synced-folder and command channels into it (#213); 10.0.0-beta.11
CI / verify (push) Successful in 8m41s
CI / pwsh (push) Successful in 2m44s
Release / release (push) Successful in 35s
Release / wiki-mirror (push) Skipped

Files changed:
- .gitignore
- CHANGES.md
- VERSION
- docs/explanation/architecture.md
- docs/explanation/pipeline-rationale.md
- docs/how-to/README.md
- docs/how-to/back-up-to-a-synced-folder.md
- docs/how-to/collect-from-a-synced-folder.md
- docs/how-to/ingest-a-source.md
- docs/how-to/set-up-the-mcp-server.md
- docs/reference/commands/README.md
- docs/reference/commands/git.md
- docs/reference/commands/instance-health.md
- docs/reference/commands/raw-material-and-uploads.md
- docs/reference/configuration.md
- instructions/gates.md
- instructions/ingest-queue.md
- instructions/mcp-read-server.md
- instructions/migrations/10.0.0-settings-tiers.md
- instructions/wiki-ingest/SKILL.md
- raw/CONTRACT.md
- tools/CONTRACT.md
- tools/README.md
- tools/chemenu/cli_contract.py
- tools/chemenu/collect.py
- tools/chemenu/commands/backup_cmd.py
- tools/chemenu/commands/docs_verify.py
- tools/chemenu/commands/doctor.py
- tools/chemenu/commands/raw/pending.py
- tools/chemenu/commands/upload_cmd.py
- tools/chemenu/config.py
- tools/chemenu/config_shapes.py
- tools/chemenu/legacy_settings.py
- tools/chemenu/mcp/server.py
- tools/chemenu/reference_doc.py
- tools/chemenu/tests/test_backup.py
- tools/chemenu/tests/test_collect.py
- tools/chemenu/tests/test_config_shapes.py
- tools/chemenu/tests/test_doctor.py
- tools/chemenu/tests/test_legacy_settings.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

Co-Authored-By: Claude Code <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SnAJ7Z3CpVD3PRbN73QtU2
This commit is contained in:
torbenandClaude Code committed 2026-10-12 22:04:18 +02:00
1 parent 3627168403
commit cceb9a7bfe
44 files changed
+1910 -346

No files matched your search

+2 -2
View File
@@ -47,7 +47,7 @@ def instance(tmp_path, monkeypatch):
(root / "incoming").mkdir()
(root / "incoming" / ".gitkeep").write_text("", encoding="utf-8")
(root / ".gitignore").write_text(
"/incoming/*\n!/incoming/.gitkeep\n/.wikitool-local.json\n/ENVIRONMENT.md\n/mcp-upload/\n"
"/incoming/*\n!/incoming/.gitkeep\n/.wikitool-local.json\n/ENVIRONMENT.md\n/quarantine/\n"
"/reports/\n/.wikitool-tools.json\n", encoding="utf-8")
(root / "README.md").write_text("init\n", encoding="utf-8")
_git(root, "add", "-A")
@@ -222,7 +222,7 @@ def _fill(root: Path) -> None:
(root / "incoming" / "paper.pdf").write_bytes(b"%PDF-1.7 fixture")
(root / "incoming" / "old").mkdir()
(root / "incoming" / "old" / "note.md").write_text("note\n", encoding="utf-8")
for relative in ("mcp-upload/sub-1/x.md", "reports/lint.md", ".wikitool-tools.json"):
for relative in ("quarantine/sub-1/x.md", "reports/lint.md", ".wikitool-tools.json"):
(root / relative).parent.mkdir(parents=True, exist_ok=True)
(root / relative).write_text("not mirrored\n", encoding="utf-8")
(root / ".claude").mkdir()
+297
View File
@@ -0,0 +1,297 @@
"""Tests for the pulled intake channels (`chemenu/collect.py`) and `wikitool
upload collect`: the `folder` and `command` handlers, and what the command does
across several channels."""
from __future__ import annotations
import json
import os
import subprocess
import sys
import time
from pathlib import Path
import pytest
import typer
from chemenu import collect, config, upload
from chemenu.commands.upload_cmd import upload_collect_command, upload_list_command
OLD = time.time() - 3600
@pytest.fixture
def root(tmp_path, monkeypatch):
instance = tmp_path / "instance"
instance.mkdir()
(instance / "incoming").mkdir()
monkeypatch.setattr(config, "ROOT", instance)
return instance
@pytest.fixture
def drop(tmp_path):
folder = tmp_path / "drop"
folder.mkdir()
return folder
def _put(folder: Path, name: str, data: bytes = b"content\n", age: float = 3600) -> Path:
path = folder / name
path.write_bytes(data)
stamp = time.time() - age
os.utime(path, (stamp, stamp))
return path
def _folder_channel(drop: Path, **overrides) -> upload.Channel:
fields = dict(
name="onedrive", handler="folder", max_bytes=1024, allowed_extensions=(".md", ".pdf"),
path=drop, settle_seconds=60,
)
fields.update(overrides)
return upload.Channel(**fields)
def _command_channel(argv: list[str], **overrides) -> upload.Channel:
fields = dict(
name="mail", handler="command", max_bytes=1024, allowed_extensions=(".md", ".pdf"),
command_name="scan", command=tuple(argv),
)
fields.update(overrides)
return upload.Channel(**fields)
def _script(body: str) -> list[str]:
return [sys.executable, "-c", f"import sys, pathlib\nout = pathlib.Path(sys.argv[1])\n{body}"]
def _write_intake(root: Path, body: dict) -> None:
(root / config.LOCAL_SETTINGS_FILENAME).write_text(
json.dumps({"schema": 1, "intake": body}), encoding="utf-8")
def _tree(folder: Path) -> dict[str, bytes]:
return {str(p.relative_to(folder)): p.read_bytes() for p in sorted(folder.rglob("*")) if p.is_file()}
def _manifests(root: Path) -> list[dict]:
return upload.list_submissions(root)
# --- folder -----------------------------------------------------------------
def test_a_folder_pull_stores_each_file_and_empties_the_source(root, drop):
_put(drop, "a.md", b"alpha\n")
_put(drop, "b.pdf", b"bravo\n")
result = collect.collect(root, _folder_channel(drop))
assert result.ok and len(result.taken) == 2
assert _tree(drop) == {}
manifests = {m["filename"]: m for m in _manifests(root)}
assert set(manifests) == {"a.md", "b.pdf"}
first = manifests["a.md"]
assert (first["channel"], first["handler"]) == ("onedrive", "folder")
assert first["origin"] == str(drop / "a.md")
assert (first["submitter"], first["submitter_source"]) == ("unknown", "folder")
assert (root / "quarantine" / first["id"] / "a.md").read_bytes() == b"alpha\n"
def test_a_folder_pull_leaves_what_it_does_not_take_untouched(root, drop):
(drop / "sub").mkdir()
(drop / "sub" / "inner.md").write_bytes(b"inner")
_put(drop, ".x.pdf", b"hidden")
_put(drop, "~$a.docx", b"lock")
_put(drop, "notes.exe", b"bad extension")
_put(drop, "big.md", b"x" * 2000)
target = _put(drop, "real.md", b"target")
link = drop / "link.md"
link.symlink_to(target)
target.unlink()
before = _tree(drop)
result = collect.collect(root, _folder_channel(drop))
assert result.ok and result.taken == [] and _manifests(root) == []
assert _tree(drop) == before
assert link.is_symlink()
reasons = dict(result.skipped)
assert reasons["sub"] == "subdirectory"
assert reasons[".x.pdf"] == "hidden" and reasons["~$a.docx"] == "hidden"
assert reasons["notes.exe"] == "extension not allowed"
assert reasons["link.md"] == "symbolic link"
assert "max_bytes" in reasons["big.md"]
def test_a_file_younger_than_settle_seconds_waits_for_the_next_run(root, drop):
_put(drop, "fresh.md", b"arriving", age=5)
result = collect.collect(root, _folder_channel(drop, settle_seconds=60))
assert result.taken == [] and "still in motion" in dict(result.skipped)["fresh.md"]
assert (drop / "fresh.md").read_bytes() == b"arriving"
os.utime(drop / "fresh.md", (OLD, OLD))
later = collect.collect(root, _folder_channel(drop, settle_seconds=60))
assert [name for name, _ in later.taken] == ["fresh.md"]
def test_a_source_that_changes_during_the_copy_is_kept_and_nothing_is_stored(root, drop, monkeypatch):
source = _put(drop, "a.md", b"one\n")
monkeypatch.setattr(collect, "source_matches", lambda *args: False)
result = collect.collect(root, _folder_channel(drop))
assert result.taken == [] and "changed while it was copied" in dict(result.skipped)["a.md"]
assert source.read_bytes() == b"one\n"
assert _manifests(root) == []
assert [p for p in (root / "quarantine").iterdir() if p.is_dir()] == []
def test_a_duplicate_is_reported_with_the_existing_id_and_the_source_is_removed(root, drop):
_put(drop, "a.md", b"same\n")
first = collect.collect(root, _folder_channel(drop))
existing = first.taken[0][1]
_put(drop, "again.md", b"same\n")
second = collect.collect(root, _folder_channel(drop))
assert second.taken == [] and second.duplicates == [("again.md", existing)]
assert not (drop / "again.md").exists()
assert len(_manifests(root)) == 1
def test_a_missing_folder_fails_the_channel(root, tmp_path):
result = collect.collect(root, _folder_channel(tmp_path / "gone"))
assert not result.ok and "does not exist" in result.error
# --- command ----------------------------------------------------------------
def test_a_command_hands_over_the_files_it_left_in_the_staging_directory(root):
channel = _command_channel(_script("(out / 'mail.md').write_bytes(b'from a script\\n')"))
result = collect.collect(root, channel)
assert result.ok and [name for name, _ in result.taken] == ["mail.md"]
manifest = _manifests(root)[0]
assert (manifest["channel"], manifest["handler"], manifest["origin"]) == ("mail", "command", "command:scan")
assert (manifest["submitter"], manifest["submitter_source"]) == ("unknown", "command")
assert not (root / "quarantine" / ".staging").exists()
def test_a_failing_command_takes_nothing_and_shows_its_stderr_tail(root):
channel = _command_channel(_script(
"(out / 'half.md').write_bytes(b'x')\nsys.stderr.write('boom ' * 600 + 'THE END')\nsys.exit(3)"))
result = collect.collect(root, channel)
assert not result.ok and result.taken == []
assert "exit 3" in result.error and result.error.rstrip().endswith("THE END")
assert len(result.error) < 2500
assert _manifests(root) == []
assert not (root / "quarantine" / ".staging").exists()
def test_a_command_that_times_out_fails_its_channel_and_cleans_up(root, monkeypatch):
monkeypatch.setattr(collect, "COMMAND_TIMEOUT_SECONDS", 1)
channel = _command_channel(_script("(out / 'a.md').write_bytes(b'x')\nimport time\ntime.sleep(30)"))
result = collect.collect(root, channel)
assert not result.ok and "did not finish" in result.error
assert _manifests(root) == []
assert not (root / "quarantine" / ".staging").exists()
def test_a_command_that_does_not_exist_fails_its_channel(root, tmp_path):
result = collect.collect(root, _command_channel([str(tmp_path / "nope.sh")]))
assert not result.ok and "does not exist" in result.error
def test_a_command_result_skips_what_is_not_a_plain_allowed_file(root):
channel = _command_channel(_script(
"(out / 'sub').mkdir()\n(out / 'x.exe').write_bytes(b'x')\n(out / 'ok.md').write_bytes(b'ok')"))
result = collect.collect(root, channel)
assert [name for name, _ in result.taken] == ["ok.md"]
assert dict(result.skipped) == {"sub": "not a regular file", "x.exe": "extension not allowed"}
# --- upload collect ---------------------------------------------------------
def _two_channels(root, drop):
_write_intake(root, {
"commands": {"scan": _script("(out / 'mail.md').write_bytes(b'mail\\n')")},
"channels": {
"onedrive": {"handler": "folder", "path": str(drop), "max_bytes": 1024,
"allowed_extensions": [".md"]},
"mail": {"handler": "command", "command": "scan", "max_bytes": 1024,
"allowed_extensions": [".md"]},
"server": {"handler": "mcp", "max_bytes": 1024, "allowed_extensions": [".md"],
"quota_submissions_per_day": 1, "quota_bytes_per_day": 1000},
},
})
def test_collect_without_a_pull_channel_has_nothing_to_do(root, capsys):
upload_collect_command(names=None)
assert "nothing to collect" in capsys.readouterr().out
def test_collect_pulls_every_pull_channel_and_reports_per_channel(root, drop, capsys):
_two_channels(root, drop)
_put(drop, "a.md", b"alpha\n")
upload_collect_command(names=None)
out = capsys.readouterr().out
assert "onedrive (folder)" in out and "mail (command)" in out
assert {m["channel"] for m in _manifests(root)} == {"onedrive", "mail"}
def test_collect_names_one_channel_and_leaves_the_others(root, drop):
_two_channels(root, drop)
_put(drop, "a.md", b"alpha\n")
upload_collect_command(names=["mail"])
assert {m["channel"] for m in _manifests(root)} == {"mail"}
assert (drop / "a.md").exists()
def test_a_failing_channel_does_not_stop_the_others_but_the_exit_is_1(root, drop, tmp_path, capsys):
_two_channels(root, drop)
body = json.loads((root / config.LOCAL_SETTINGS_FILENAME).read_text())
body["intake"]["channels"]["onedrive"]["path"] = str(tmp_path / "gone")
(root / config.LOCAL_SETTINGS_FILENAME).write_text(json.dumps(body), encoding="utf-8")
with pytest.raises(typer.Exit) as excinfo:
upload_collect_command(names=None)
assert excinfo.value.exit_code == 1
assert "FAILED" in capsys.readouterr().out
assert {m["channel"] for m in _manifests(root)} == {"mail"}
@pytest.mark.parametrize("names", [["nope"], ["server"], ["mail", "nope"], ["mail", "server"]])
def test_an_unknown_or_mcp_argument_exits_1_before_any_effect(root, drop, names):
_two_channels(root, drop)
_put(drop, "a.md", b"alpha\n")
before = _tree(drop)
with pytest.raises(typer.Exit) as excinfo:
upload_collect_command(names=names)
assert excinfo.value.exit_code == 1
assert _tree(drop) == before
assert _manifests(root) == []
def test_a_malformed_intake_section_exits_1(root):
_write_intake(root, {"channels": {"a": {"handler": "folder"}}})
with pytest.raises(typer.Exit) as excinfo:
upload_collect_command(names=None)
assert excinfo.value.exit_code == 1
def test_the_command_refuses_while_a_legacy_quarantine_holds_submissions(root, drop):
_two_channels(root, drop)
_put(drop, "a.md", b"alpha\n")
(root / "mcp-upload" / "sub-1").mkdir(parents=True)
(root / "mcp-upload" / "sub-1" / "a.md").write_bytes(b"waiting")
with pytest.raises(typer.Exit) as excinfo:
upload_collect_command(names=None)
assert excinfo.value.exit_code == 1
assert (drop / "a.md").exists()
with pytest.raises(typer.Exit):
upload_list_command(json_out=False)
# --- the quarantine's own .gitignore ----------------------------------------
def _git(root: Path, *args: str) -> str:
return subprocess.run(["git", *args], cwd=root, check=True, capture_output=True, text=True).stdout
def test_collected_material_shows_in_no_git_status_even_without_a_root_entry(root, drop):
_git(root, "init", "-q")
_put(drop, "a.md", b"alpha\n")
collect.collect(root, _folder_channel(drop))
collect.collect(root, _command_channel(_script("(out / 'm.md').write_bytes(b'mail')")))
assert (root / "quarantine" / ".gitignore").read_text().strip() == "*"
assert _git(root, "status", "--porcelain", "--untracked-files=all") == ""
+21 -15
View File
@@ -29,10 +29,10 @@ runner = CliRunner()
PACKAGE = Path(config_shapes.__file__).parent
UPLOAD_OK = {
"identity_header": "X-User", "max_bytes": 1000, "allowed_extensions": [".md", ".PDF"],
INTAKE_OK = {"channels": {"mcp": {
"handler": "mcp", "identity_header": "X-User", "max_bytes": 1000, "allowed_extensions": [".md", ".PDF"],
"quota_submissions_per_day": 5, "quota_bytes_per_day": 5000,
}
}}}
TASKS_OK = {"provider": "caldav", "stalled_waiting_days": 14, "unpaged_project_weeks": 3,
"someday_stale_months": 5}
CALDAV_OK = {"url": "https://dav.example/", "username": "u", "app_password": "pw",
@@ -78,7 +78,7 @@ def test_the_telemetry_policy_reads_the_record():
def test_one_record_per_section():
assert [shape.key for shape in config_shapes.SETTINGS_SECTIONS] == [
"telemetry", "remotes", "upload", "tasks", "superproductivity", "caldav", "sites", "fetch",
"telemetry", "remotes", "intake", "tasks", "superproductivity", "caldav", "sites", "fetch",
"backup",
]
@@ -126,24 +126,30 @@ def _swap(monkeypatch, attr, shape):
)
def test_upload_follows_a_field_made_required_or_optional(root, monkeypatch):
def test_intake_follows_a_field_made_required(root, monkeypatch):
from chemenu import upload
original = config_shapes.UPLOAD
write_settings(root, monkeypatch, upload=UPLOAD_OK)
original = config_shapes.INTAKE
write_settings(root, monkeypatch, intake=INTAKE_OK)
assert upload.read_config(root).max_bytes == 1000
_swap(monkeypatch, "UPLOAD", _add_required(original))
_swap(monkeypatch, "INTAKE", _add_required(original))
with pytest.raises(ValidationError, match="added is missing"):
upload.read_config(root)
_swap(monkeypatch, "UPLOAD", original)
(root / config.LOCAL_SETTINGS_FILENAME).unlink()
(root / config.INSTANCE_SETTINGS_FILENAME).unlink()
write_settings(root, monkeypatch, upload={k: v for k, v in UPLOAD_OK.items() if k != "max_bytes"})
with pytest.raises(ValidationError):
def test_intake_rules_across_fields_are_checked_by_the_record(root, monkeypatch):
from chemenu import upload
write_settings(root, monkeypatch, intake={"channels": {"mcp": {"handler": "mcp", "max_bytes": 1}}})
with pytest.raises(ValidationError, match="quota_submissions_per_day"):
upload.read_config(root)
_swap(monkeypatch, "UPLOAD", _not_required(original, "max_bytes", 99))
assert upload.read_config(root).max_bytes == 99
@pytest.mark.parametrize("key", ["intake.channels", "intake.commands"])
def test_the_intake_fields_are_not_settable_by_config_set(root, key):
result = runner.invoke(app, ["config", "set", key, "{}"])
assert result.exit_code == 42, result.output
assert not (root / config.LOCAL_SETTINGS_FILENAME).exists()
def test_tasks_follows_a_field_made_required(root, monkeypatch):
+33 -8
View File
@@ -350,27 +350,52 @@ def test_publish_remotes_warns_on_several_remotes_without_an_allowlist(instance)
def test_upload_intake_ok_when_no_section(instance):
checks = doctor.run_doctor()
assert _status(checks, "upload-intake") == "OK"
assert "not registered" in _detail(checks, "upload-intake")
assert "no channel" in _detail(checks, "upload-intake")
def test_upload_intake_ok_and_armed_when_section_is_valid(instance, monkeypatch):
write_settings(config.ROOT, monkeypatch, upload={
"max_bytes": 100, "allowed_extensions": [".md"],
"quota_submissions_per_day": 1, "quota_bytes_per_day": 100,
})
_MCP = {"handler": "mcp", "max_bytes": 100, "allowed_extensions": [".md"],
"quota_submissions_per_day": 1, "quota_bytes_per_day": 100}
def test_upload_intake_names_each_channel_with_its_handler(instance, monkeypatch, tmp_path_factory):
folder = tmp_path_factory.mktemp("synced")
write_settings(config.ROOT, monkeypatch, intake={"channels": {
"mcp": _MCP,
"onedrive": {"handler": "folder", "path": str(folder), "max_bytes": 100, "allowed_extensions": [".md"]},
}})
checks = doctor.run_doctor()
assert _status(checks, "upload-intake") == "OK"
detail = _detail(checks, "upload-intake")
assert "armed" in detail
assert "mcp (mcp)" in detail and "onedrive (folder)" in detail
assert "0 submission(s) waiting" in detail
def test_upload_intake_warns_when_a_folder_does_not_exist(instance, monkeypatch, tmp_path_factory):
write_settings(config.ROOT, monkeypatch, intake={"channels": {
"onedrive": {"handler": "folder", "path": str(tmp_path_factory.mktemp("outside") / "gone"), "max_bytes": 100,
"allowed_extensions": [".md"]},
}})
checks = doctor.run_doctor()
assert _status(checks, "upload-intake") == "WARN"
assert "onedrive" in _detail(checks, "upload-intake")
def test_upload_intake_fails_on_a_malformed_section(instance, monkeypatch):
write_settings(config.ROOT, monkeypatch, upload={"max_bytes": "many"})
write_settings(config.ROOT, monkeypatch, intake={"channels": {"mcp": {"handler": "mcp", "max_bytes": "many"}}})
checks = doctor.run_doctor()
assert _status(checks, "upload-intake") == "FAIL"
def test_upload_intake_fails_on_a_leftover_mcp_upload_directory(instance):
waiting = config.ROOT / "mcp-upload" / "sub-1"
waiting.mkdir(parents=True)
(waiting / "a.md").write_bytes(b"waiting")
checks = doctor.run_doctor()
assert _status(checks, "upload-intake") == "FAIL"
assert "mv mcp-upload quarantine" in _detail(checks, "upload-intake")
assert (waiting / "a.md").read_bytes() == b"waiting"
def test_tasks_provider_ok_when_no_section(instance):
checks = doctor.run_doctor()
assert _status(checks, "tasks-provider") == "OK"
+18 -3
View File
@@ -30,6 +30,9 @@ APP_PASSWORD = "sekrit-app-password-0815"
THRESHOLDS = {"stalled_waiting_days": 14, "unpaged_project_weeks": 3, "someday_stale_months": 5}
# name -> (old files by file name, what is in effect afterwards)
_MCP_CHANNEL = {"name": "mcp", "handler": "mcp", "path": None, "settle_seconds": 60,
"command_name": "", "command": ()}
CASES = {
"telemetry-full": (
{".wikitool-telemetry.json": {"schema": 1, "enabled": False, "max_session_bytes": 1000,
@@ -54,7 +57,7 @@ CASES = {
"schema": 1, "identity_header": "X-Forwarded-User", "max_bytes": 2048,
"allowed_extensions": [".md", ".txt"],
"quota": {"submissions_per_day": 4, "bytes_per_day": 9000}}},
{"upload": {"identity_header": "X-Forwarded-User", "max_bytes": 2048,
{"upload": {**_MCP_CHANNEL, "identity_header": "X-Forwarded-User", "max_bytes": 2048,
"allowed_extensions": (".md", ".txt"), "submissions_per_day": 4,
"bytes_per_day": 9000}},
),
@@ -185,7 +188,8 @@ def _expected(case: dict) -> dict:
for key, value in case.items():
if key == "upload" and isinstance(value, str):
value = {
"identity_header": _default("upload", "identity_header"),
**_MCP_CHANNEL,
"identity_header": upload.DEFAULT_IDENTITY_HEADER,
"max_bytes": 100, "allowed_extensions": (".md",),
"submissions_per_day": 1, "bytes_per_day": 5,
}
@@ -210,6 +214,17 @@ def test_every_shape_keeps_its_effective_value(checkout, case):
assert _normal(_effective(checkout)) == _normal(_expected(expected))
def test_the_old_upload_file_becomes_the_mcp_channel_of_the_intake_section(checkout):
_write_old(checkout, CASES["upload-full"][0])
legacy_settings.migrate(checkout)
data = json.loads((checkout / config.LOCAL_SETTINGS_FILENAME).read_text(encoding="utf-8"))
assert "upload" not in data
assert data["intake"] == {"channels": {"mcp": {
"handler": "mcp", "identity_header": "X-Forwarded-User", "max_bytes": 2048,
"allowed_extensions": [".md", ".txt"],
"quota_submissions_per_day": 4, "quota_bytes_per_day": 9000}}}
# --- b: only the tools file remains; no secret in the tree or the index -------
@@ -376,7 +391,7 @@ def test_the_telemetry_hook_neither_reads_nor_refuses_over_an_old_file(checkout)
@pytest.mark.parametrize("key,value", [
("upload.max_bytes", "100"),
("intake.channels", '{"mcp": {"handler": "mcp"}}'),
("remotes.allowed_push_urls", '["ssh://git@example.net/a.git"]'),
])
def test_config_set_on_a_gated_section_exits_42_and_writes_nothing(checkout, key, value):
+34 -7
View File
@@ -9,8 +9,8 @@ The acceptance criteria that are not about a value coming back:
importable from the server - structural, not filtered;
- every response carries the commit it was computed from;
- telemetry cannot land inside the served checkout;
- the sixth tool, `submit`, exists only when the `upload` section of `.wikitool-local.json` opts a
checkout in, writes only under `mcp-upload/`, and never resolves an
- the sixth tool, `submit`, exists only when the `intake` section of `.wikitool-local.json` opts a
checkout in, writes only under `quarantine/`, and never resolves an
identity from anywhere but the request's own headers.
"""
from __future__ import annotations
@@ -44,13 +44,14 @@ from chemenu.mcp.server import ( # noqa: E402 - after the skip guard
_UPLOAD_CFG = {
"schema": 1,
"upload": {
"intake": {"channels": {"mcp": {
"handler": "mcp",
"identity_header": "X-Forwarded-User",
"max_bytes": 1024,
"allowed_extensions": [".md"],
"quota_submissions_per_day": 10,
"quota_bytes_per_day": 100_000,
},
}}},
}
@@ -368,13 +369,38 @@ def test_submit_is_present_once_armed(corpus):
assert names == {"search", "types", "describe_type", "lint", "status", "submit"}
def test_submit_is_absent_when_the_section_has_only_pulled_channels(corpus, tmp_path_factory):
folder = tmp_path_factory.mktemp("synced")
section = {"schema": 1, "intake": {"channels": {"onedrive": {
"handler": "folder", "path": str(folder), "max_bytes": 1024, "allowed_extensions": [".md"]}}}}
(corpus / ".wikitool-local.json").write_text(json.dumps(section), encoding="utf-8")
names = {tool.name for tool in asyncio.run(build_server(corpus, check_trace=False).list_tools())}
assert "submit" not in names
def test_submit_carries_the_limits_of_the_mcp_channel(corpus):
_arm_upload(corpus)
tools = asyncio.run(build_server(corpus, check_trace=False).list_tools())
submit = next(tool for tool in tools if tool.name == "submit")
assert "1024 bytes" in submit.description and ".md" in submit.description
def test_a_leftover_mcp_upload_directory_refuses_to_build(corpus):
_arm_upload(corpus)
(corpus / "mcp-upload" / "sub-1").mkdir(parents=True)
(corpus / "mcp-upload" / "sub-1" / "a.md").write_bytes(b"waiting")
with pytest.raises(ValidationError, match="mcp-upload"):
build_server(corpus, check_trace=False)
assert (corpus / "mcp-upload" / "sub-1" / "a.md").read_bytes() == b"waiting"
def test_a_malformed_upload_config_refuses_to_build(corpus):
(corpus / ".wikitool-local.json").write_text("{not json", encoding="utf-8")
with pytest.raises(ValidationError):
build_server(corpus, check_trace=False)
def test_submit_writes_only_into_mcp_upload_incoming_stays_untouched(corpus):
def test_submit_writes_only_into_the_quarantine_incoming_stays_untouched(corpus):
_arm_upload(corpus)
server = build_server(corpus, check_trace=False)
before = _tree(corpus)
@@ -391,15 +417,16 @@ def test_submit_writes_only_into_mcp_upload_incoming_stays_untouched(corpus):
assert manifest["submitter"] == "alice"
assert manifest["submitter_source"] == "X-Forwarded-User"
assert (manifest["channel"], manifest["handler"], manifest["origin"]) == ("mcp", "mcp", "mcp:submit")
after = _tree(corpus)
changed = {p for p in after if after.get(p) != before.get(p)}
assert changed
assert all(p.startswith("mcp-upload/") for p in changed)
assert all(p.startswith("quarantine/") for p in changed)
assert list((corpus / "incoming").iterdir()) == []
# git itself is untouched - the fixture's own committed tree (kb/, types/)
# is unchanged, and HEAD did not move. `mcp-upload/` and the config file
# is unchanged, and HEAD did not move. `quarantine/` and the config file
# are new, untracked paths (this fixture carries no .gitignore of its
# own, unlike the real repo), which `git status` reports and is exactly
# what a real checkout's `.gitignore` would hide - not a write to git.
+81 -15
View File
@@ -20,8 +20,10 @@ CONTENT = b"hello world, this is a test submission\n"
CONTENT_B64 = base64.b64encode(CONTENT).decode("ascii")
def _cfg(**overrides) -> upload.UploadConfig:
def _cfg(**overrides) -> upload.Channel:
defaults = dict(
name="mcp",
handler="mcp",
identity_header="X-Forwarded-User",
max_bytes=1024,
allowed_extensions=(".md", ".pdf"),
@@ -29,7 +31,7 @@ def _cfg(**overrides) -> upload.UploadConfig:
bytes_per_day=10_000,
)
defaults.update(overrides)
return upload.UploadConfig(**defaults)
return upload.Channel(**defaults)
def _tree(root: Path) -> dict[str, bytes]:
@@ -53,15 +55,21 @@ def test_sanitize_filename_accepts_a_bare_name():
assert upload.sanitize_filename("report.md") == "report.md"
# --- read_config -------------------------------------------------------------
# --- read_config / read_channels -----------------------------------------------
def test_read_config_absent_is_none(tmp_path):
assert upload.read_config(tmp_path) is None
assert upload.read_channels(tmp_path) == {}
def _section(tmp_path, **fields):
channel = dict({"handler": "mcp"}, **fields)
_intake(tmp_path, {"channels": {"mcp": channel}})
def _intake(tmp_path, body):
(tmp_path / config.LOCAL_SETTINGS_FILENAME).write_text(
json.dumps({"schema": 1, "upload": fields}), encoding="utf-8")
json.dumps({"schema": 1, "intake": body}), encoding="utf-8")
GOOD = {"max_bytes": 100, "allowed_extensions": [".md"],
@@ -110,6 +118,7 @@ def test_read_config_valid_file_parses(tmp_path):
_section(tmp_path, identity_header="X-User", max_bytes=2048, allowed_extensions=[".MD", ".pdf"],
quota_submissions_per_day=3, quota_bytes_per_day=999)
cfg = upload.read_config(tmp_path)
assert cfg.name == "mcp" and cfg.handler == "mcp"
assert cfg.identity_header == "X-User"
assert cfg.max_bytes == 2048
assert cfg.allowed_extensions == (".md", ".pdf")
@@ -128,13 +137,70 @@ def test_read_config_refuses_a_file_of_an_earlier_version(tmp_path):
upload.read_config(tmp_path)
def test_two_mcp_channels_are_an_error(tmp_path):
channel = dict({"handler": "mcp"}, **GOOD)
_intake(tmp_path, {"channels": {"a": channel, "b": channel}})
with pytest.raises(ValidationError, match="mcp"):
upload.read_channels(tmp_path)
def test_a_field_that_does_not_match_the_handler_is_an_error(tmp_path, tmp_path_factory):
outside = tmp_path_factory.mktemp("outside")
channel = {"handler": "folder", "path": str(outside), "max_bytes": 100, "allowed_extensions": [".md"],
"identity_header": "X-User"}
_intake(tmp_path, {"channels": {"f": channel}})
with pytest.raises(ValidationError, match="identity_header"):
upload.read_channels(tmp_path)
def test_a_command_channel_needs_a_commands_entry(tmp_path):
channel = {"handler": "command", "command": "scanner", "max_bytes": 100, "allowed_extensions": [".md"]}
_intake(tmp_path, {"channels": {"s": channel}})
with pytest.raises(ValidationError, match="scanner"):
upload.read_channels(tmp_path)
def test_a_folder_path_must_be_absolute_and_outside_the_instance(tmp_path, tmp_path_factory):
base = {"handler": "folder", "max_bytes": 100, "allowed_extensions": [".md"]}
_intake(tmp_path, {"channels": {"f": dict(base, path="relative/dir")}})
with pytest.raises(ValidationError, match="absolute"):
upload.read_channels(tmp_path)
inside = tmp_path / "drop"
_intake(tmp_path, {"channels": {"f": dict(base, path=str(inside))}})
with pytest.raises(ValidationError, match="inside the instance"):
upload.read_channels(tmp_path)
outside = tmp_path_factory.mktemp("outside")
_intake(tmp_path, {"channels": {"f": dict(base, path=str(outside))}})
channel = upload.read_channels(tmp_path)["f"]
assert channel.handler == "folder" and channel.path == outside
assert channel.settle_seconds == 60
assert upload.read_config(tmp_path) is None
def test_a_leftover_mcp_upload_directory_is_refused_and_untouched(tmp_path):
legacy = tmp_path / "mcp-upload" / "sub-1"
legacy.mkdir(parents=True)
(legacy / "a.md").write_bytes(b"waiting")
before = _tree(tmp_path)
problem = upload.legacy_dir_problem(tmp_path)
assert "mv mcp-upload quarantine" in problem and "Move-Item" in problem
with pytest.raises(ValidationError, match="mcp-upload"):
upload.refuse_legacy_dir(tmp_path)
assert _tree(tmp_path) == before
def test_an_empty_mcp_upload_directory_is_no_problem(tmp_path):
(tmp_path / "mcp-upload").mkdir()
assert upload.legacy_dir_problem(tmp_path) is None
# --- the write choke point ----------------------------------------------------
def test_write_primitive_refuses_a_relative_escape(tmp_path):
with pytest.raises(ValidationError):
upload._write_atomic_within(tmp_path, Path("../outside.txt"), b"x")
assert not (tmp_path / "outside.txt").exists()
assert not (tmp_path / "mcp-upload").exists()
assert not (tmp_path / "quarantine").exists()
def test_write_primitive_refuses_an_absolute_path(tmp_path):
@@ -146,7 +212,7 @@ def test_write_primitive_refuses_an_absolute_path(tmp_path):
def test_write_primitive_refuses_a_symlink_escape(tmp_path):
outside = tmp_path / "outside"
outside.mkdir()
upload_dir = tmp_path / "mcp-upload"
upload_dir = tmp_path / "quarantine"
upload_dir.mkdir()
(upload_dir / "escape").symlink_to(outside, target_is_directory=True)
@@ -173,12 +239,12 @@ def test_submit_happy_path_writes_manifest_and_content(tmp_path):
assert manifest["submitter_source"] == "X-Forwarded-User"
assert manifest["size"] == len(CONTENT)
submission_dir = tmp_path / "mcp-upload" / manifest["id"]
submission_dir = tmp_path / "quarantine" / manifest["id"]
assert (submission_dir / "report.md").read_bytes() == CONTENT
stored_manifest = json.loads((submission_dir / "manifest.json").read_text())
assert stored_manifest == manifest
ledger_lines = (tmp_path / "mcp-upload" / "ledger.jsonl").read_text().splitlines()
ledger_lines = (tmp_path / "quarantine" / "ledger.jsonl").read_text().splitlines()
assert len(ledger_lines) == 1
event = json.loads(ledger_lines[0])
assert event["event"] == "submitted"
@@ -342,10 +408,10 @@ def test_promote_moves_file_deletes_dir_and_ledgers_accepted(tmp_path):
assert dest == tmp_path / "incoming" / "a.md"
assert dest.read_bytes() == CONTENT
assert not (tmp_path / "mcp-upload" / manifest["id"]).exists()
assert not (tmp_path / "quarantine" / manifest["id"]).exists()
events = [json.loads(line) for line in
(tmp_path / "mcp-upload" / "ledger.jsonl").read_text().splitlines()]
(tmp_path / "quarantine" / "ledger.jsonl").read_text().splitlines()]
assert [e["event"] for e in events] == ["submitted", "accepted"]
# nothing else in the tree changed
@@ -363,7 +429,7 @@ def test_promote_refuses_when_the_file_is_missing_from_disk(tmp_path):
defense in depth, since nothing in this module deletes a content file
without its manifest."""
manifest = upload.submit(tmp_path, _cfg(), filename="a.md", content_b64=CONTENT_B64, submitter="t")
(tmp_path / "mcp-upload" / manifest["id"] / "a.md").unlink()
(tmp_path / "quarantine" / manifest["id"] / "a.md").unlink()
with pytest.raises(ValidationError):
upload.promote(tmp_path, manifest["id"])
@@ -378,7 +444,7 @@ def test_promote_refuses_when_incoming_already_has_the_name(tmp_path):
upload.promote(tmp_path, manifest["id"])
# nothing moved: quarantine still holds it, incoming/ untouched
assert (tmp_path / "mcp-upload" / manifest["id"] / "a.md").read_bytes() == CONTENT
assert (tmp_path / "quarantine" / manifest["id"] / "a.md").read_bytes() == CONTENT
assert (tmp_path / "incoming" / "a.md").read_text() == "already here"
@@ -388,9 +454,9 @@ def test_reject_deletes_material_and_ledgers_with_reason(tmp_path):
manifest = upload.submit(tmp_path, _cfg(), filename="a.md", content_b64=CONTENT_B64, submitter="t")
upload.reject(tmp_path, manifest["id"], "license unclear")
assert not (tmp_path / "mcp-upload" / manifest["id"]).exists()
assert not (tmp_path / "quarantine" / manifest["id"]).exists()
events = [json.loads(line) for line in
(tmp_path / "mcp-upload" / "ledger.jsonl").read_text().splitlines()]
(tmp_path / "quarantine" / "ledger.jsonl").read_text().splitlines()]
assert events[-1]["event"] == "rejected"
assert events[-1]["reason"] == "license unclear"
assert events[-1]["sha256"] == manifest["sha256"]
@@ -401,7 +467,7 @@ def test_reject_requires_a_reason(tmp_path):
with pytest.raises(ValidationError):
upload.reject(tmp_path, manifest["id"], "")
# nothing deleted
assert (tmp_path / "mcp-upload" / manifest["id"]).exists()
assert (tmp_path / "quarantine" / manifest["id"]).exists()
def test_reject_unknown_id_is_rejected(tmp_path):
+8 -6
View File
@@ -20,7 +20,9 @@ from chemenu.commands.upload_cmd import (
CONTENT = b"a test submission for the CLI layer\n"
CONTENT_B64 = base64.b64encode(CONTENT).decode("ascii")
_CFG = upload.UploadConfig(
_CFG = upload.Channel(
name="mcp",
handler="mcp",
identity_header="X-Forwarded-User",
max_bytes=1024,
allowed_extensions=(".md",),
@@ -50,7 +52,7 @@ def test_accept_without_a_token_needs_clearance(root, capsys):
assert manifest["sha256"] in out
assert "--confirm" in out
# nothing moved
assert (root / "mcp-upload" / manifest["id"]).exists()
assert (root / "quarantine" / manifest["id"]).exists()
assert not (root / "incoming" / manifest["filename"]).exists()
@@ -59,7 +61,7 @@ def test_accept_with_a_stale_token_needs_clearance_again(root):
with pytest.raises(typer.Exit) as excinfo:
upload_accept_command(submission_id=manifest["id"], confirm="not-the-real-token")
assert excinfo.value.exit_code == EXIT_NEEDS_CLEARANCE
assert (root / "mcp-upload" / manifest["id"]).exists()
assert (root / "quarantine" / manifest["id"]).exists()
def test_accept_with_the_right_token_promotes(root):
@@ -67,13 +69,13 @@ def test_accept_with_the_right_token_promotes(root):
token = upload.confirm_token(manifest)
upload_accept_command(submission_id=manifest["id"], confirm=token)
assert (root / "incoming" / manifest["filename"]).read_bytes() == CONTENT
assert not (root / "mcp-upload" / manifest["id"]).exists()
assert not (root / "quarantine" / manifest["id"]).exists()
def test_reject_deletes_and_needs_no_gate(root):
manifest = _seed(root)
upload_reject_command(submission_id=manifest["id"], reason="license unclear")
assert not (root / "mcp-upload" / manifest["id"]).exists()
assert not (root / "quarantine" / manifest["id"]).exists()
def test_reject_without_a_reason_is_a_validation_error(root):
@@ -81,7 +83,7 @@ def test_reject_without_a_reason_is_a_validation_error(root):
with pytest.raises(typer.Exit) as excinfo:
upload_reject_command(submission_id=manifest["id"], reason="")
assert excinfo.value.exit_code == 1
assert (root / "mcp-upload" / manifest["id"]).exists()
assert (root / "quarantine" / manifest["id"]).exists()
def test_list_json_matches_pending_manifests(root, capsys):