Skip to content

How this wiki works

This site is also a QA artifact. It has requirements in tension, a threat model, regression guards in CI, and one production bug in its history — so it gets the same treatment I give any system I test. This page documents the architecture and, more importantly, why it is built this way, with the books and essays each decision leans on.

The requirements

  1. Public by default. The wiki is part of my portfolio: proof that I can explain testing in clear written English. A private notebook proves nothing.
  2. Useful in private. It is also my daily working reference, and real work includes employer-specific runbooks, decisions and rough notes that must never be published.
  3. Near-zero maintenance. Side-project infrastructure that needs babysitting eventually gets abandoned. Whatever I build has to keep working while I ignore it.

And one hard constraint on top: material about my employer's product stays private, permanently. Only generic lessons, rewritten in my own words, may ever be published.

One source tree, two builds

The public wiki lives in a public GitHub repo. The private notes live in a separate private repo, cloned inside the public one at docs/private/ — a path the public repo gitignores. VitePress sees a single documentation tree and builds it in two modes, switched by one environment variable:

                   one source tree
      ┌──────────────────────────────────────┐
      │ docs/                                │
      │ ├── fundamentals/ … istqb/           │ ← public repo
      │ └── private/                         │ ← separate private repo
      │     ├── company/  runbooks/  inbox/… │   (gitignored here)
      └───────────────────┬──────────────────┘

          ┌───────────────┴───────────────┐
          │                               │
    npm run build             WIKI_PRIVATE=1 npm run build:full
          │                               │
    public pages only           public + private overlay
          │                               │
    GitHub Pages                 Cloudflare Pages,
    (world-readable)             behind Cloudflare Access
                                 (exactly one allowed login: me)
CommandBuildsDeployed to
npm run buildPublic wiki onlyGitHub Pages — the site you are reading
npm run build:fullEverythingCloudflare Pages, behind authentication
npm run devEverything, locallyMy machine

Why one tree instead of two separate wikis? Because knowledge should live in exactly one place — the DRY principle from The Pragmatic Programmer: "every piece of knowledge must have a single, unambiguous, authoritative representation". Private notes can link to public articles, one dev server previews both, and there is one config, one theme and one deploy story instead of two drifting copies.

The public sidebar is curated by hand; the private one is generated by scanning the directory, so dropping a quick note into private/inbox/ publishes it (privately) with zero bookkeeping. Friction in a note-taking system is a bug: notes you don't capture are notes you lose.

Alternatives I rejected

  • Make the whole wiki private. Kills requirement 1 — the portfolio value is the point.
  • Per-page privacy flags in one repo. A private: true flag on each page relies on me never forgetting one flag, forever. Don Norman's The Design of Everyday Things has a name for the better option: a forcing function — make the wrong action structurally hard instead of relying on vigilance. A directory boundary in a different repo is exactly that: I cannot accidentally commit a file to the public repo when it belongs to a repo the public one refuses to track.
  • A dynamic wiki with login. A database and an auth layer to maintain, patch and back up — for one reader. Dan McKinley's Choose Boring Technology applies: every novel component spends innovation tokens I'd rather spend on content. Static files behind Cloudflare Access give me authentication with nothing to operate, and follow Saltzer & Schroeder's least privilege: the allowlist contains exactly one identity.

The leak guards

The threat model is asymmetric: a broken build is annoying, but a silent leak of private content is unacceptable and irreversible — once crawled, always public. So the pipeline is designed to fail closed.

The first line of defence is Saltzer & Schroeder's fail-safe default: public CI does not exclude the private overlay — it never has it on disk at all, because the overlay belongs to a repo CI has no access to. You cannot publish what is not there. On top of that, three explicit guards run in CI — in the pull-request workflow and duplicated in the deploy workflow, because direct pushes to main skip PR checks:

  1. Tracked-overlay guard. Fails if docs/private/ exists in the public checkout. This should be impossible (gitignore + separate repo); the guard is there for the day "impossible" has a bug.
  2. Confidential-flag guard. Employer-related files carry a confidential marker in their frontmatter. The guard greps the entire public tree and fails if any marked file has crept in.
  3. Build-output guard. After building, it fails if the output contains any /private/ route — or the name of the private repository, anywhere in the emitted HTML.

This is poka-yoke — Shigeo Shingo's mistake-proofing from Zero Quality Control: inspection at the source, automatic and unconditional, instead of downstream vigilance. And it is deliberately layered: in James Reason's Swiss cheese model, every defence has holes, so you stack slices and make sure the holes don't line up. Guard 1 catches repo mistakes, guard 2 catches file mistakes, guard 3 catches generator mistakes — a category the first two cannot see. It is shift-left applied to publishing: prevent the defect from shipping instead of detecting it in production.

The guard that exists because of a bug

Guard 3 is not theoretical — it is a regression test for a real leak. Each page's edit link must point to the right repo (public pages to the public repo, private pages to the private one), so I made the edit-link option a branching function. What I missed: VitePress serializes function-valued theme options into every rendered page. The public HTML didn't show anything private, but its page payload contained the function's source — including the private repo's name.

The root cause wasn't "bad luck", it was an untested assumption about the framework's serialization boundary. The fix: the function is only used in the full build; the public build gets a plain string. And per The Pragmatic Programmer's "Find Bugs Once" rule, the class of bug got a permanent guard: grep the built output, because that is the only place generator leaks are visible.

The guards have no allowlist — including for this page

A detail I enjoy: this very page cannot write the confidential frontmatter marker as a literal key–value pair, and cannot name the private repository — the guards would fail the build, because they grep raw trees and emitted HTML with no documentation exception. The temptation to add one ("it's just the page explaining the guard!") is exactly how defences erode: an exception is a hole in the cheese slice. Writing around it costs me one awkward sentence; the property "no page is exempt" is worth far more.

What the guards cannot catch

Honesty required by my own trade: these guards stop mechanical leaks — a tracked file, a flagged file, a leaking generator. They cannot stop a judgment leak: if I write something employer-specific directly into a public article, unflagged, no grep will ever know. Reason's work on human error is clear that systems narrow the space for mistakes but never empty it.

The mitigation is workflow, not tooling: new material lands in the private inbox first, and publishing is never a file move — it is a deliberate act of rewriting, which is the next section.

Publishing means rewriting

The rule agreed with my employer: private material never moves to the public wiki. If a lesson is worth publishing, it gets rewritten from scratch — generic, in my own words, stripped of anything specific, and set in this wiki's two fictional example domains (a threat-modelling platform and a telecom operator).

The constraint turned out to be a feature. Rewriting-to-publish is elaboration — explaining an idea in your own words and connecting it to what you already know — which Sönke Ahrens' How to Take Smart Notes argues is where learning actually happens (the Feynman technique is the same idea with better marketing). The split gives each half of the wiki a clean identity: the private overlay records what I did; the public wiki records what I understand. Only the second one is portfolio material — precisely because rewriting is the proof of understanding.

Why public at all

Three influences convinced me the public half was worth the extra machinery:

  • Learn in public (swyx's essay): the feedback, connections and opportunities all live on the public side of your notes.
  • Digital gardens (Maggie Appleton): a wiki that grows by small tending beats a blog that demands finished essays — lower the publishing bar and you publish.
  • Show Your Work! (Austin Kleon): showing process, not just outcomes, is what makes work findable — and a hiring manager reading this page is the concrete use case.

The delivery mechanism follows docs as code (Anne Gentle's Docs Like Code): Markdown in git, pull requests, a CI build that fails on broken links, automatic deploys. The same pipeline discipline as software — because for a QA engineer, the pipeline is part of the portfolio.

The moving parts

For the code-inclined, the whole mechanism is a handful of small files in the public repo:

  • docs/.vitepress/config.mts — the build switch: base path, defensive srcExclude of the overlay, sitemap only for the public site, and the edit-link branching described above.
  • docs/.vitepress/sidebar.ts — the curated public sidebar.
  • docs/.vitepress/private.ts — the directory scanner that generates the private sidebar.
  • .github/workflows/ci.yml and deploy.yml — the three leak guards, the Pages deploy, and a repository-dispatch ping so the authenticated site rebuilds with fresh public content.

References

On mistake-proofing and human error

  • Andrew Hunt & David Thomas — The Pragmatic Programmer (DRY; "Find Bugs Once")
  • Shigeo Shingo — Zero Quality Control: Source Inspection and the Poka-yoke System (mistake-proofing at the source)
  • James Reason — Human Error; short version in “Human error: models and management” (the Swiss cheese model)
  • Don Norman — The Design of Everyday Things (constraints and forcing functions over vigilance)

On security posture

On learning and publishing