Skip to content

fix(ferndesk): make problems-* slug sanitization explicit + regression-proof (COR-444) - #18

Draft
echobt wants to merge 1 commit into
mainfrom
droid/1a2462c9-cor-444-sanitize-problems-md
Draft

echobt wants to merge 1 commit into
mainfrom
droid/1a2462c9-cor-444-sanitize-problems-md

Conversation

@echobt

@echobt echobt commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

DRAFT — do not merge. Opened for review only.

Why the problems-* pages failed

Every problems/*.mdx whose file name contains an underscore failed POST /articles with 400 (19 pages). The five codes without an underscore — conflict, forbidden, gone, internal, unauthenticated — created fine.

Evidence from run 34902589600 (11d7114, before the slug fix):

created problems-conflict      created problems-forbidden
created problems-gone          created problems-internal
created problems-unauthenticated
ERROR create problems-bad_request          -> 400
ERROR create problems-concurrency_limit_reached -> 400
... 17 more, all with an underscore in the slug

The discriminator is the slug, not the article body: the failed set is exactly the 19 slugs containing _, and tables, JSON fences, and em dashes all appear in pages that synced successfully.

FernDesk accepts a slug only in [a-z0-9]+(-[a-z0-9]+)*. This repo mirrors the backend's snake_case ErrorCode names as file names, so problems/bad_request was sent verbatim and rejected.

#17 (b229ee9) fixed the symptom. Its follow-up run 34905578558 created 17 of the 19 pages with zero 400s; the two remaining failures are 429 rate_limited on problems-service-unavailable and problems-unsupported-media-type, i.e. quota, not the slug bug:

SUMMARY {"pages": 99, "created": 17, "updated": 80, "failed": 2,
         "failed_slugs": ["problems-service-unavailable", "problems-unsupported-media-type"]}

What this PR adds

#17 expressed the rule as a chained .replace("_", "-") with no test behind it. This PR makes the rule explicit and guards it:

scripts/ferndesk_sync.py

  • sanitize_slug() states the FernDesk charset in one place: lowercase, every run of other characters (_, /, spaces, --) collapsed to a single hyphen, ends trimmed. It raises rather than emitting an empty slug.
  • discover_pages() refuses to sync on a slug collision. Two paths that sanitize to one slug (a_b.mdx and a-b.mdx) would otherwise silently overwrite one article via the upsert-by-slug path.

scripts/tests/ferndesk-sync-retry.test.py — 13 new checks (62 total), still fully offline:

  • the sanitizer's cases (snake_case, folder separator, uppercase, repeated separators, edge trimming, empty result);
  • discover_pages run over this repository: every problem page must yield a FernDesk-legal slug, keep no underscore, keep a bare snake_case title out of the title, and not collide repo-wide;
  • a collision fixture proving discover_pages raises instead of overwriting.

scripts/FERNDESK.md documents the rule and why the MDX file names cannot simply be renamed: they match ErrorCode::as_str and the page's /problems/{code} type URI, so the sanitization belongs at the sync boundary.

Behavior change

None for current content. All 99 discovered slugs are byte-identical to before this commit, and titles/markdown are untouched. The new tests are proven to bite by mutation: reverting sanitize_slug to the underscore-preserving form fails 8 checks.

Test plan

node scripts/check-docs-site.mjs                       # ok (24 problem pages; site-only)
bash scripts/tests/check-docs-site.test.sh             # ok
python3 scripts/tests/ferndesk-sync-retry.test.py      # ok, 62 checks
npm exec --yes --package=mint@4.2.876 -- mint validate # build validation passed
FERNDESK_DRY_LOCAL=1 python3 scripts/ferndesk_sync.py  # 99 pages discovered

Mutation check: with sanitize_slug reverted to replace("/", "-"), the suite fails 8 checks (exit 1).

Not claimed

No live API write was attempted from this session. The remaining 429 on the two pending pages is FernDesk write quota, not this bug; a re-run of the sync workflow once the limit clears should land them. The FernDesk UI still needs Connect domain for docs.cortex.foundation HTTPS, which is separate operator work.

…n-proof (COR-444)

The 400s on `problems-*` creates were never about the article body. Every
`problems/*.mdx` whose file name carries an underscore failed `POST /articles`
with 400 (19 pages), while `problems/conflict`, `problems/forbidden`, and
`problems/gone` created fine. FernDesk accepts a slug only in
`[a-z0-9]+(-[a-z0-9]+)*`, and this repo mirrors the backend's snake_case
`ErrorCode` names as file names.

`#17` fixed the symptom by chaining `.replace("_", "-")` onto the slug. This
commit makes the rule explicit and guards it:

- `sanitize_slug()` states the FernDesk charset in one place: lowercase, every
  run of other characters collapsed to one hyphen, ends trimmed. It raises
  rather than emitting an empty slug.
- `discover_pages()` refuses to sync when two paths sanitize to one slug
  (`a_b.mdx` + `a-b.mdx`), which would otherwise silently overwrite an article.
- `ferndesk-sync-retry.test.py` runs `discover_pages` over this repository and
  fails if a problem page yields an illegal slug, keeps an underscore, collides
  with another page, or keeps a bare snake_case title. 13 new checks (62 total).
- `FERNDESK.md` documents why the MDX file names cannot be renamed: they match
  `ErrorCode::as_str` and the page's `/problems/{code}` type URI.

Behavior is unchanged for every current page: all 99 slugs are byte-identical to
before this commit, and titles/markdown are untouched. The guard is proven by
mutation: reverting `sanitize_slug` to the underscore-preserving form fails 8
checks.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
@mintlify

mintlify Bot commented Sep 14, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated
cortex-foundation-add13747 🟢 Ready View Preview Sep 14, 2026, 11:45 PM

💡 Tip: Enable Automations to automatically generate PRs for you.

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