Skip to content

feat: publish source material, one document at a time - #74

Merged
khaneight merged 1 commit into
mainfrom
feat/publish-sources
Aug 27, 2026
Merged

feat: publish source material, one document at a time#74
khaneight merged 1 commit into
mainfrom
feat/publish-sources

Conversation

@khaneight

Copy link
Copy Markdown
Owner

Stacked on #73. Safeguard 6 — and the "originating source material" half of the brief.

$ sentinel sources
Sources — 3 document(s), 0 publishable

  private  On Cases
      raw/philosophy/on-cases.md · authored · cited by 2 article(s)

$ sentinel sources on-cases.md --publish
published: raw/philosophy/on-cases.md
  readers can see this document once you run `export --with-sources`

Why there is no flag that publishes raw/

That directory holds whatever its owner put there: work under someone else's
copyright, private notes, correspondence, drafts nobody was meant to see.
Nothing about a file tells the tool which of those it is. So the decision is
per document, made by hand, recorded in the manifest, and false by default.

Opting in and publishing stay separate acts. --publish says a document may
go out; export --with-sources puts it there. Marking something safe should not
be the same keystroke as putting it on the internet.

Published articles gain a ## Sources section linking only to documents a
reader can actually open — a link naming a withheld file is the worst kind of
dead end, because it names the file and then denies it.

A leak that predated this PR

The published copy of an article carried its sources: frontmatter verbatim,
putting the raw/ path of every cited document on the site — including withheld
ones. Filenames are often the private part; therapy-notes-2019.md says plenty
without being opened.

export now rewrites the field to name only what was published, and removes it
when that is nothing — sources: [] claims an article cites nothing, which is a
different statement from declining to say. The rewrite is textual and confined
to the frontmatter block, like mv's, with its own unit tests (block lists,
inline lists, a same-named key nested under something else, a document with no
block).

I found this because the test asserting a withheld document is never named
failed while the one asserting it is never copied passed.

--clean reckons with sources

Copied sources are not markdown-only and carry no article slug, so the existing
staleness rule could not see them — and a withdrawn document left readable in
the destination is the exact failure this opt-in exists to prevent. A file under
sources/ whose path matches a manifest entry is one an export produced.

Also

export::run took eight positional arguments, five of them bools. At that width
a caller can transpose clean and flat and the compiler is perfectly happy,
which for a command that deletes files in a destination is not a risk worth
carrying for brevity. It takes an Options struct now — clippy flagged it, and
the fix was the right one rather than an allow.

sentinel sources joins export and review in NOT_FOR_AGENTS: whether a
document may be published is a judgement about copyright and privacy that
nothing in the file can make.

525 tests (+19), clippy and fmt clean.

🤖 Generated with Claude Code

"Others can view your clone and its originating source material" needs a
way to say which material. `raw/` holds whatever its owner put there —
work under someone else's copyright, private notes, correspondence,
drafts nobody was meant to see — and nothing about a file tells the tool
which of those it is. So there is no flag that publishes the directory,
only `sentinel sources <doc> --publish`, recorded per document and
defaulting to false.

Opting in and publishing stay separate acts: `--publish` says a document
*may* go out, `export --with-sources` puts it there. Marking something
safe should not be the same keystroke as putting it on the internet.
Published articles gain a Sources section linking only to documents a
reader can open — a link naming a withheld file is the worst kind of dead
end, because it names the file and then denies it.

Building this turned up a leak that predated it. The published copy of an
article carried its `sources:` frontmatter verbatim, putting the `raw/`
path of every cited document on the site — including withheld ones, whose
*filenames* are often the private part; "therapy-notes-2019.md" says
plenty without being opened. `export` now rewrites the field to name only
what was published, and removes it when that is nothing, since
`sources: []` claims an article cites nothing rather than declining to
say.

`--clean` reckons with copied sources too. They are not markdown-only and
carry no article slug, so the existing staleness rule could not see them
— and a withdrawn document left readable in the destination is the exact
failure this opt-in exists to prevent.

`export::run` took eight positional arguments, five of them bools. At
that width a caller can transpose `clean` and `flat` with the compiler
perfectly happy, which for a command that deletes files in a destination
is not a risk worth carrying for brevity. It takes an `Options` struct.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@khaneight
khaneight force-pushed the feat/publish-sources branch from 846a0b8 to 3e3f7bc Compare August 27, 2026 02:23
@khaneight
khaneight changed the base branch from feat/export-gate to main August 27, 2026 02:23
@khaneight
khaneight merged commit 8e97415 into main Aug 27, 2026
6 checks passed
@khaneight
khaneight deleted the feat/publish-sources branch August 27, 2026 02:33
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