Skip to content

Say why a name derived from non-ASCII key fields has none, and print it - #34

Open
dajiaohuang wants to merge 2 commits into
tigerless-labs:mainfrom
dajiaohuang:fix/non-ascii-derived-name-error
Open

dajiaohuang wants to merge 2 commits into
tigerless-labs:mainfrom
dajiaohuang:fix/non-ascii-derived-name-error

Conversation

@dajiaohuang

@dajiaohuang dajiaohuang commented Sep 11, 2026

Copy link
Copy Markdown

Refs #20.

What reproduces at 34d12a2

slugify drops every character that is not ASCII alphanumeric, so input written
entirely in CJK, Arabic, or Cyrillic folds to the empty string. Eight of the nine
factory types derive their file name from the abstract through portable_segment
(profile is the exception: it keys on the system-supplied user), so the write
path the prompts teach — --type and --abstract, no --name — rejects such a
write. A group value that slugs to nothing fails the same way.

What does not reproduce

The issue reports "a file named .md at the domain root", with non-ASCII abstracts
colliding on one path and overwriting each other. That is not the behaviour at
34d12a2: placement.resolve raises before any path is built, and no file is
created. The probe in the linked evidence shows the rejection and an empty store.
So this is not a data-loss bug at HEAD — it is a write that fails with a message
the caller cannot act on.

The change

Two defects, one user-visible outcome: a rejected write the user can act on.

The wording. In core, the rejection stays. A digest or transliterated fallback
would put unreadable names in a tree whose whole point is being walkable with ls
and grep, and a transliteration table is a dependency this repo does not take.
What changes is the diagnostic, which now names the cause and the way out:

name: key fields have no ASCII letters or digits to name the file; pass name
topic: has no ASCII letters or digits to name its directory

The output path. errors.py promises that a rejected write says which field
and why, and the JSON payload keeps that promise — but the plain renderer did not.
_emit formatted every list entry with the recall line, so any ValidationError
printed as

errors:
  -  —  ·  · score=None

with the field and the reason silently dropped. Wording a diagnostic better is
worth nothing on the output path most callers read, so a field error now renders as
field: reason:

code: validation_error
errors:
  - name: key fields have no ASCII letters or digits to name the file; pass name

Verified end to end: the rejected write names the field and the way out in the
default output mode, mem record --help shows that field as --name, and the same
abstract with --name deploy-rewrite-notes lands at
fact/default/deploy-rewrite-notes.md.

If the maintainers would rather that a non-ASCII abstract succeed, that is a
naming-policy decision — it needs an ASCII-safe fallback and a view on how legible
the resulting tree stays — and I would rather raise it than pick for you.

Tests

tests/unit/test_storage.py

  • test_a_derived_name_names_the_cause_and_the_way_out — the rejection carries the cause
  • test_a_non_ascii_abstract_is_written_under_an_explicit_name — the promised way out works
  • test_a_group_that_slugs_to_nothing_names_the_cause — the sibling path

The first and third fail on 34d12a2 with the old wording and pass after; the second
passes on both, which is the point — an explicit name was always the way through.

tests/system/test_cli.py

  • test_a_rejected_write_prints_the_field_and_the_reason — the plain output carries
    the same field and reason the structured payload does. It reads its expected text
    out of that payload instead of pinning the message, and it fails on the previous
    commit with the blank line shown above.

Validation

Local replica of .github/workflows/ci.yml: ruff check clean, ruff format --check clean, mypy clean (66 source files), pytest 392 passed, coverage 91.01%
against the 85% gate.

An abstract written entirely in CJK, Arabic or Cyrillic slugifies to the
empty string, and the store rejected the write with "key fields produce an
empty name" -- which names neither the cause nor the way out. The write path
the prompts teach (abstract and type only, no explicit name) is the one that
hits it, so the user is left with a failure they cannot act on.

The rejection stays: a digest or transliterated name would make the file
tree unreadable, and legible paths are the point of the store. What changes
is the diagnostic, which now states the cause and the remedy. The same
correction applies to a group value that slugifies to nothing.
errors.py promises that a rejected write says which field and why, and the
JSON payload keeps that promise. The human renderer did not: it formatted
every list entry with the recall line, so a validation error printed as
"  -  -  .  . score=None" with the field and the reason silently dropped.
Improving the wording of a diagnostic changes nothing on the output path
most callers read.

A field error now renders as "field: reason". The regression test takes
its expected text from the structured payload, so it holds the two
renderings together instead of pinning a message.
@dajiaohuang dajiaohuang changed the title Say why a name derived from non-ASCII key fields has none Say why a name derived from non-ASCII key fields has none, and print it Sep 11, 2026
@dajiaohuang
dajiaohuang marked this pull request as ready for review September 11, 2026 01:00
@dajiaohuang
dajiaohuang force-pushed the fix/non-ascii-derived-name-error branch from 1e1e383 to dfe440d Compare September 11, 2026 10:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant