fix(create): convert in preflight, so a document defect creates nothing - #134
Merged
Conversation
The invariant create's preflight conversion will rest on. Its verdict is only as good as phase 3's because neither NameCollisionError nor goldmark's own failure reads the index -- and the page emphatically does depend on it, which is why a preflight result cannot be reused. Both halves are asserted, since "the errors match" proves nothing if the seeding never mattered.
A conversion failure surfaced in the publish phase, after reserve had already created a page and written its id into the file -- leaving a content-less page, a page_id nobody asked for, and a re-run that failed with "a page already exists at page_id" instead. Fixes #127. Preflight converts every file and keeps nothing but the error. The result cannot be reused by publish: reserve seeds the batch's page ids into the shared link index in between, so an in-set link renders unresolved in preflight and resolves in publish. The error is the same either way, because no error path reads the index. Phase 1 now rejects a file for more than one reason, so the code travels on the failure instead of being hardcoded by abort(): a conversion failure reports CONVERT, the code it already reported from publish. The abort line says "failed preflight" rather than "failed validation" to match.
The strongest safety property create has, written down now that #127 closed the part of it that was knowable from disk. Partial rather than Holds: a publish-phase server or network failure still leaves the reserved stub behind, which _plans/026 accepted deliberately, and closing that would mean deleting a page -- a change S4 does not authorise on its own.
- publishOne can fail locally, not only remotely: SyncAttachments opens every asset to checksum and upload it, which the converter never does. So can reserveOne's frontmatter write, which runs after CreatePage and leaves a stub whose id is *not* persisted -- the one case update cannot pick up. S7 now names all three residuals instead of one. - "No error path reads the index" was stronger than what holds. Whether renderImage runs does depend on it (renderLink skips a broken link's children, and Broken comes from FileExists). What holds is that reserve only calls SetPage, which writes idx.pages alone, while FileExists/Anchor read idx.anchors, fixed at Build time. - The end-to-end test's comment credited the fake's unexpected-request guard, which cannot fire: a revert fails at MdToConfluence, before SyncAttachments. Also: validationFailure replaces the two bare failure literals so nothing sets code by hand; --help says "checked" to match the abort line; the README notes that one bad file aborts a --dry-run preview of the whole batch.
_plans/034's Out of scope now cites #133, with the trap the fix has to avoid: CodeFor alone answers NETWORK for any non-HTTPError.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #127.
The bug
createis three-phase: preflight validates every file (any failure aborts the batch), reserve creates a content-less page per file and writes itspage_idinto the frontmatter, publish converts the markdown and fills each page in.MdToConfluencewas only called in publish, so a defect knowable purely from the files on disk surfaced after a page had already been created and the file edited.Concretely, with #59's base-name attachment naming:
doc.mdreferencing botharch/diagram.pnganddeploy/diagram.pngwants one attachment nameddiagram.pngfor two assets, which the converter refuses. The author was left with a content-less page, apage_idthey did not ask for, and a re-run that refused with "a page already exists at page_id" — recoverable only by deleting the page or hand-editing the frontmatter.markfluence checkdiagnoses this with no client and no network, which is what makes it worth fixing: everything the diagnosis needs is resolved by the end of preflight, and preflight simply was not asking.The fix
Preflight converts each file and keeps nothing but the error, so the batch aborts with nothing created and nothing written.
Publish cannot reuse the result, which was #127's open question. Reserve seeds the batch's page ids into the shared link index in between (
index.SetPage), andrewriteDocLinkputsentry.PageIDstraight into the built URL — so a preflight conversion renders "link not resolved" for every in-set sibling link, and publishing that would reintroduce the ordering dependency_plans/026removed. The cost is one extra local conversion per file, over bytes already in memory. Preflight'sBroken/Warningsare discarded for the same reason.The error survives the phase boundary, for a narrower reason than "the converter ignores the index". Whether
renderImageruns at all does depend on it:renderLinkskips a broken link's children, andBrokencomes fromindex.FileExists. What holds is that reserve only ever callsSetPage, which writesidx.pagesalone — nothing there can raise an error or change one's text — whileFileExists/Anchorreadidx.anchors, fixed atBuildtime and identical in both phases. A change makingSetPagealso mark a file as existing would break this, so it is pinned by a test rather than left to a comment.The conversion runs last in
resolveFile, after every server check, so thepage_id-first error precedence is untouched. The cost, stated plainly: a file that cannot convert still makes those API calls before being told.Reporting
Phase 1 now rejects a file for more than one reason, so the code travels on the
failurestruct instead of being hardcoded byabort(): a conversion failure reportsCONVERT, the code it already reported from publish.validationFailureconstructs the failures that have no error value behind them, so nothing setscodeby hand —abortedResultemits whatever is there, and""is not in the schema's enum. No schema change: thecodeenum is global and already contains both.The abort line reads
failed preflightrather thanfailed validation, since it now covers more than validation.S7
New guarantee S7
no-partial-create, status Partial: a file thatcreatefails to publish leaves no page behind. Partial rather than Holds, and the three residuals are named rather than glossed — a server or network failure while publishing; an unreadable attachment (SyncAttachmentsopens every asset to checksum and upload it, which the converter never does, so an image thatLstat'd fine can still fail); and an unwritable frontmatter file, sincereserveOnewrites afterCreatePageand leaves a stub whose id is not persisted. Closing any of them would mean deleting a page, which S4 does not authorise on its own.Behavior change
--dry-runon a defective file now aborts the batch instead of printing a per-fileCONVERTfailure. Forced by the design — a dry run has to predict a real run, and a real run aborts — but it means one bad file no longer previews the rest. Noted in the README, pointing atcheckfor linting files independently.Verification
make checkpasses. I disabled the preflight conversion and confirmed all three newcmd/createtests fail against the old behavior, with the bug visible in the output:bad.mdgot a page created and apage_idwritten into its frontmatter. Theinternal/convertinvariant test asserts both halves — the error is identical across an unseeded and a seeded index, and the HTML differs — so it cannot pass vacuously.A code review of the branch found three of my comments overstated what the change guarantees; those are corrected in the last two commits, and the S7 residuals above are the substance of it. It also found that a preflight
*client.HTTPErrorstill reportsVALIDATIONrather thanAUTH— pre-existing, left alone here because it changes the code on failures this issue is not about, and filed as #133 with the rule to copy (cmd/fix'slocateCode) and the trap to avoid (CodeForalone answersNETWORKfor any non-HTTPError).Design and rationale:
_plans/034_create-preflight-conversion.md.