feat(inbox): fail-fast on filenames over the 255-byte limit

The slug inherited the whole filename stem with no length bound, so a long
dropped name produced a filename the filesystem cannot hold — surfacing as an
untyped OSError at the write, with an errno that differs per platform (63 on
macOS, 36 on Linux). The inbox contract promises a typed per-file outcome, so
the gate belongs in the pure function that forms the name.

Refusal, not truncation: truncating is lossy AND collision-prone — two long
names sharing a prefix would reduce to one filename and the second write would
silently claim the first file. Same posture as `inbox_slug_empty`: the library
never invents a filename the operator did not give it. The message carries the
actual size and the limit, since the fix is to rename the dropped file.

Limit verified empirically on APFS 2026-07-25: a 255-byte component writes, a
258-byte one raises errno 63. ext4 and NTFS cap at the same 255.

New MaterializationError code `inbox_slug_too_long`, registered in the errors
docstring and in the error-code conformance suite.
This commit is contained in:
Kjell Tore Guttormsen 2026-07-25 06:27:43 +02:00
commit b7f5ce3800
4 changed files with 102 additions and 2 deletions

View file

@ -98,6 +98,9 @@ class MaterializationError(IngestError):
occupied by a file without the ingest stamp
- `inbox_slug_empty` a dropped file's name reduces to an empty slug
under the id grammar (Door B; never an invented fallback name)
- `inbox_slug_too_long` the generated inbox filename would exceed the
255-byte filesystem limit (Door B; never a truncated name, which would
be lossy and could collide with another long name sharing its prefix)
- `inbox_title_invalid` an inbox title is multi-line or contains `[`/`]`,
either of which would break frontmatter or an index link
- `inbox_source_file_invalid` an inbox `source_file` is multi-line and