Skip to content

docs(branding): make branding/design-lab the lab's single home (#3310) - #3437

Merged
Xore merged 1 commit into
mainfrom
oc/3310-dedupe
Sep 27, 2026
Merged

Xore merged 1 commit into
mainfrom
oc/3310-dedupe

Conversation

@Xore

@Xore Xore commented Sep 27, 2026

Copy link
Copy Markdown
Owner

Summary

branding/design-lab/ is now the design lab's only home. The docs/design-lab/ fork is removed, its unique content folded into the canonical tree, and a gate makes the duplication structurally impossible to reintroduce.

Canonical tree: branding/design-lab/ — not merely because it has more files, but because three independent things depend on it:

evidence
it is the tree CI exercises quality.yml:177,194 run node --test branding/design-lab/lab.test.mjs in both the self-hosted and the GitHub-hosted lane (design-lab-readonly, design-lab-readonly-cloud)
it is the tree that ships pages.yml triggers on branding/** and builds it with branding/scripts/build_pages_site.py
it is a strict superset 12 files to the fork's 10, including lab.mjs + lab.test.mjs (#1828/#1935), which the fork never had

The fork was the dead one: the #1763 snapshot, re-added incidentally by the #3182 a11y PR (526eaf5c), frozen since 2026-08-24 while the canonical tree moved to 2026-08-25.

The issue's characterisation was wrong in two places — verified, not assumed

1. playground/compare.html is not "slightly newer" in the fork. That is its commit date (2026-09-14 vs 2026-08-24), not its content. Content-wise the canonical file is the later, stronger fix. The #1763 snapshot (f6614521) has no safePath at all — both copies added one:

  • branding/: character-by-character whitelist, explicitly drops @, \, quote and angle characters, and keeps || '/' at all four call sites. The comment names CodeQL as the reason.
  • docs/: resolves against new URL(p, ORIGIN + '/') and keeps only a matching origin, with no || '/' fallback at the call sites.

So the fork had nothing the canonical tree lacked. Dropped, not moved.

2. The fork was not purely stale — it was edited, and it carried the safer copy. The 2026-09-27 markdown reconciliation (b2b230cd, this repo's HEAD) gave the docs/ copy content branding/ never received:

  • the redaction notice in README.md
  • the design-notes.md "Public, redacted copy" banner, including the note that the Go dashboard these findings cite was deleted in Port Foundation: production cutover checklist #1628
  • a redaction of one captured attacker IP — 85.14.245.122 → RFC 5737 203.0.113.122 (27 occurrences in playground/elements.html, one in design-notes.md)
  • a "Colour research" section on where the palettes came from, which the canonical README had never had

A blind git rm -r docs/design-lab would have deleted all four and left the unredacted captured address in the tree pages.yml publishes. That is why this is a fold.

Proof the fold is lossless for the two pure-redaction files — after the fold both are byte-identical to the fork:

IDENTICAL  design-notes.md  (b18a76c6b89e54c9b6f034633f19ce183f6291a2)
IDENTICAL  playground/elements.html  (33c118dded041c5248e6beaa1319df0e84875d2b)

and the elements.html diff is purely the address swap — every one of its 25 changed lines contains 85.14.245. or 203.0.113. and nothing else.

Nothing lost — full enumeration

10 files deleted. Every one exists in branding/design-lab/:

deleted file where it went
docs/design-lab/README.md merged, not moved — canonical README kept (it documents lab.mjs/lab.test.mjs/the read-only gate, which this one lacked) and gained the redaction notice + Colour research. Its closing line ("does not carry the harness itself; run it from branding/design-lab/") was dropped as false of either tree.
docs/design-lab/contrast_scan.js already byte-identical, untouched
docs/design-lab/design-notes.md folded — now byte-identical (banner + 3 redactions)
docs/design-lab/gen_palettes.py already byte-identical, untouched
docs/design-lab/palettes.css already byte-identical, untouched
docs/design-lab/playground/compare.html dropped — canonical version is strictly stronger; see above
docs/design-lab/playground/elements.html folded — now byte-identical (address redaction)
docs/design-lab/playground/index.html already byte-identical, untouched
docs/design-lab/playground/layouts.html already byte-identical, untouched
docs/design-lab/v5-picks-override.css already byte-identical, untouched

8 of the 12 canonical files were already byte-identical; after the fold 10 of 12 are, and the two that differ (lab.mjs, lab.test.mjs) never existed in the fork.

The gate

tests/docs/test_3310_design_lab_dedupe.py — six tests, no new CI wiring (the existing pytest tests/docs/ row at quality.yml:1633 picks it up). Stdlib + pytest only; the CodeQL assertion reads YAML as text, the same trade test_3331_fix.py makes.

Three of the six name no path. They key off five basenames (design-notes.md, gen_palettes.py, contrast_scan.js, palettes.css, v5-picks-override.css) that exist nowhere in the repository except the lab, so a copy reappearing at docs/design-lab/, branding/docs/, a new lab/, or a merge under a new name fails too. A gate that only asserted docs/design-lab is absent would go green the moment the fork came back one directory over.

RED, before the fix — 5 failed, 1 passed, each for the right reason:

AssertionError: docs/design-lab/ is tracked again (10 files: [...]). The design lab has one home, branding/design-lab/; a second tree is a dead fork someone will edit instead of the real one (#3310).
AssertionError: 2 tracked file(s) still reference docs/design-lab: ['.github/codeql/codeql-config.yml', 'scripts/check-docs-reachable.py'].
AssertionError: branding/design-lab/design-notes.md lost the public-copy banner the duplicate carried.
AssertionError: branding/design-lab is missing from .github/codeql/codeql-config.yml's paths-ignore ([...]).
AssertionError: design-lab sentinels (...) are tracked under ['branding/design-lab', 'docs/design-lab'], not only under branding/design-lab.

GREEN, after — 6 passed.

Zero remaining references

$ git grep -n --fixed-strings "docs/design-lab" -- .   # excluding the gate file
  (no matches)

$ git ls-files | grep 'design-lab'
branding/design-lab/README.md
branding/design-lab/contrast_scan.js
branding/design-lab/design-notes.md
branding/design-lab/gen_palettes.py
branding/design-lab/lab.mjs
branding/design-lab/lab.test.mjs
branding/design-lab/palettes.css
branding/design-lab/playground/compare.html
branding/design-lab/playground/elements.html
branding/design-lab/playground/index.html
branding/design-lab/playground/layouts.html
branding/design-lab/v5-picks-override.css

The gate is strict enough that my own first draft of the explanatory prose failed it — the CodeQL comment, the check-docs-reachable.py docstring and two READMEs all named the path in order to explain the history. Rather than add a prose allowlist, those four now carry provenance by issue number (#3310), which is this repo's existing convention for exactly this (every paths-ignore entry carries a #NNNN comment). That keeps the gate maximally strict: it would also catch the path reappearing in a link.

References repointed

file change
.github/codeql/codeql-config.yml paths-ignore entry docs/design-lab → branding/design-lab
scripts/check-docs-reachable.py dropped the now-dead docs/design-lab/ record-tree exemption (a dead exemption exempts nothing, and is one more place a re-added fork could hide)
docs/README.md design-lab/ removed from the subdirectory list; links the canonical README instead

On the CodeQL exclusion — this is a repoint of an existing entry, not a new allowlist, and it is not a zizmor finding. The exclusion's stated reasoning is a property of the lab, not of the directory: playground/compare.html is a local-only harness, not shipped and not served by any route. Those files are still there. Dropping the entry would have broken the CodeQL job; keeping the old path would have exempted nothing while silently blessing a re-added duplicate. No new GitHub action was added, so no new pin was needed.

Issues

Refs #3310. Not merging.

Security impact

  • No real credentials, private addresses, payloads, PCAPs, keys, or .env files were added. — Net effect is the reverse: the unredacted captured attacker address (85.14.245.122, 27 occurrences) is removed from branding/design-lab/playground/elements.html and one from design-notes.md, replacing it with RFC 5737 203.0.113.122, because that is the copy pages.yml publishes.
  • Sandbox/network-isolation implications were reviewed. — branding/design-lab/lab.mjs is untouched, so the read-only harness (BACKEND_URL gate, BACKEND_MOUNTED_URL stub) is unchanged; node --test branding/design-lab/lab.test.mjs still passes 8/8. No container, mount or route is touched.
  • Publicly exposed ports and routes are unchanged. — playground/compare.html keeps the canonical stricter safePath; no listener, no route, no compose file is modified.

The redaction is deliberately left as partial as it was: 123.188.73.228, 213.176.26.114, 45.156.87.34, 172.98.32.65 and 172.110.223.159 are still literal in playground/elements.html, and lab.mjs / playground/index.html still name the homeserver and 10.8.0.2. The original redaction covered one captured address; widening it is a separate privacy decision, not a dedupe, so it is flagged rather than done.

Validation

$ python -m pytest tests/docs/ -q
592 passed, 1 xfailed, 17 subtests passed in 68.47s     # was 591 before; +1 is the new gate

$ node --test branding/design-lab/lab.test.mjs          # what quality.yml runs
ℹ pass 8  ℹ fail 0

$ python scripts/check-docs-reachable.py     → passed (87 reachable, 35 in exempt record trees)
$ python scripts/check-doc-paths-exist.py    → passed (123 files, 538 tokens, 41 allowlisted)
$ python scripts/check-doc-links.py          → OK — 434 local refs in 156 files all resolve
$ python scripts/check-doc-stale-paths.py    → passed
$ node scripts/check-mermaid.mjs             → OK — 40 mermaid blocks in 156 files parse cleanly
$ python scripts/check-public-leaks.py       → passed

No existing test was weakened, skipped or deleted. The two design-lab mentions in scripts/tests/test_ci_lane_summary.py and the one in tests/docs/test_3331_fix.py are CI job names (design-lab-readonly), not paths, and are correctly untouched.

Not validated: scripts/tests/test_compose_drift_watch_sweep.py has 2 failures — pre-existing, confirmed by running them on a clean HEAD worktree with none of these changes. They are out of scope here and untouched. CodeQL and the hosted CI lanes themselves were not executed (no runner available locally); the CodeQL config change is a one-line path repoint with the reasoning preserved in place.

Rollout

None. Documentation tree only; nothing is deployed by merging, and no service reads either path.

What I did not do

  • Did not extend the redaction beyond the one address the fork had already replaced (see Security impact).
  • Did not add a tombstone or stub at docs/design-lab/. A directory holding one file is still a directory, and one more thing to keep in sync. Git history plus the #3310 references are the breadcrumb.
  • Did not touch lab.mjs / lab.test.mjs / quality.yml / pages.yml — the canonical harness and its CI wiring are unchanged, which is the point.
  • Did not hand-edit openapi.json — untouched, and nothing here affects the OpenAPI contract.
  • Did not fix one cosmetic artefact of the original redaction. design-notes.md now reads homeserver xore@<homeserver>, which is clumsy. It is a preserved record whose own banner says "Nothing else was edited", so silently rewriting a redaction placeholder inside it is the wrong kind of edit. Flagging it here instead — say the word and it is a one-line follow-up.
  • Did not merge.

The design lab was tracked twice. `branding/design-lab/` (12 files, #1827 +
#1935) is the real one: `branding/` is the visual spec, `pages.yml` builds
and publishes it, and `quality.yml` runs `lab.test.mjs` out of it in both
the self-hosted and the GitHub-hosted lane. `docs/design-lab/` (10 files) was
the #1763 snapshot re-added incidentally by the #3182 a11y PR (526eaf5) and
had drifted, so anyone editing it was editing a dead fork.

It was not a pure duplicate, which is why this is a fold and not just a
delete. The `docs/` copy had been maintained after the fact: the 2026-09-27
markdown reconciliation (b2b230c) gave it the redaction notice and the
design-notes staleness banner, it carried the redaction of one captured
attacker IP (85.14.245.122 -> RFC 5737 203.0.113.122) that `branding/` never
received, and it held a colour-research section the canonical README had
never had. All of it moves to `branding/`, which is the tree that gets
published, so the redaction now lives where it is read.

Two files keep the canonical version on purpose. `playground/compare.html`:
both copies added a `safePath` guard, the canonical one whitelists the path
character by character and keeps the `|| '/'` fallback at every call site,
the other resolves against an origin and drops it, so the duplicate had
nothing the canonical tree lacked. `README.md`: the canonical one documents
`lab.mjs`, `lab.test.mjs` and the read-only gate; the duplicate's last
section said it "does not carry the harness itself", which is no longer true
of either tree.

Ten files removed, none dropped: every one of the duplicate's filenames
exists in `branding/design-lab/`, and the ten byte-identical ones plus the
two redacted files are asserted in the new gate. The one change of substance
is that the redaction moved rather than disappeared, so `branding/` no longer
carries the address the `docs/` copy had already replaced.

Gate: tests/docs/test_3310_design_lab_dedupe.py, picked up by the existing
`pytest tests/docs/` row. Three of its six tests name no path -- they key
off basenames that exist nowhere but the lab, so a copy reappearing at
`docs/design-lab/`, `branding/docs/` or a new `lab/` fails, not just the one
path this issue does.

References repointed: the CodeQL `paths-ignore` entry moves to the tree that
exists (its reasoning is a property of the lab, not the directory, so
dropping it would have broken the CodeQL job and keeping it would have
exempted nothing while blessing a re-added fork), the `docs/` record-tree
exemption goes with the tree it named, and docs/README.md links the canonical
README. No workflow, no action pin and no openapi.json change.
@github-actions

Copy link
Copy Markdown

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Scanned Files

None

@Xore
Xore enabled auto-merge (squash) September 27, 2026 20:28
@Xore
Xore merged commit b29e706 into main Sep 27, 2026
119 of 120 checks passed
@Xore
Xore deleted the oc/3310-dedupe branch September 27, 2026 21:03
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