playground-design-system/docs/vendoring-and-re-sync.md
Kjell Tore Guttormsen efaa3d0e79 docs(vendoring): name what source becomes, and why push must precede re-sync
The doc argued that the first real re-sync is what makes source_commit
truthful, but never said what the restored script actually writes: source is
the repo name (not a path, not a machine-local absolute path) and source_commit
is git rev-parse HEAD in the source checkout.

Naming the second half makes an ordering constraint visible that was implicit:
a re-sync run before this repo's commit reaches the public mirror stamps two
public consumer repos with a hash that exists on one machine. That is the
c1b7bad defect again with a fresher date, so the check to run first is written
out.
2026-08-27 00:49:46 +02:00

200 lines
12 KiB
Markdown

# Vendoring and re-sync
**Status:** decision recorded 2026-08-27; the re-sync script was restored the
same day with an explicit source boundary. See
[Current state](#current-state) for the published command.
This document answers three questions raised by a consumer repo on 2026-08-26:
whether a current re-sync path exists, whether vendoring is still the
recommended consumer model, and where `MANIFEST.json`'s `source` field should
point now that the marketplace is a polyrepo.
Every number below was measured against ground truth on 2026-08-27, not
recalled. The measurements are listed in [Verification log](#verification-log)
so a reader can re-run them.
## Background
Until 2026-06-18 the marketplace was a monorepo. This design system lived at
`shared/playground-design-system/`, and `scripts/sync-design-system.mjs`
vendored it into each plugin's `playground/vendor/playground-design-system/`.
Commit `e84dffd` ("thin catalog to manifest + docs (polyrepo migration
complete)") deleted both paths in the same change. This repo was extracted as a
sibling with the CSS files at its root and a fresh 17-commit history.
Consumer documentation kept pointing at the monorepo layout, so the instructions
it gave named a directory and a script that no longer existed anywhere.
## Decision
### 1. Is vendoring still the recommended model? Yes.
This is settled first, because the other two answers depend on it.
The measurement supports keeping it. Three and a half months after the last
sync, in both consumers, **26 of 27 vendored files are byte-identical to this
repo's current content**, and each `MANIFEST.json` still matches its own files
exactly — no consumer has edited a vendored file locally. The one file that
differs is this repo's own `README.md`, which documents the system rather than
being part of what it delivers.
Vendoring did what it promised: the consumers stayed standalone and kept
rendering correctly when the source moved out from under them. What broke was
the *documentation pointing at the mechanism*, not the mechanism.
Nothing replaces it. A git submodule or an npm package would contradict the
Non-goals in [README.md](../README.md): no build step, no bundler, no package
registry, and a Playground that opens from `file://` with only the CSS files
alongside it.
### 2. Is there a current re-sync path? Yes, since 2026-08-27.
`scripts/sync-design-system.mjs` was absent from the entire marketplace tree.
It survived in full in the `catalog` repo's history, which carries the
pre-migration monorepo history (977 commits), and was read back with:
```sh
git -C <marketplace>/catalog show e84dffd^:scripts/sync-design-system.mjs
```
It is 225 lines, has no npm dependencies, and — this matters — it was **already
prepared for the polyrepo split before the split happened**. It accepts
`--source <dir>` and `--target <dir>`, both of which override the hardcoded
monorepo paths, and its own comment says the `--target` flag exists "so the
script still works once `plugins/<name>` no longer exists in-repo
(post-migration / extracted repo)". It also has a `--check` mode that re-hashes
a consumer's vendored tree against its committed `MANIFEST.json` and exits
non-zero on drift, needing no source at all.
So restoring it is recovery, not new development, and it belongs in **this
repo** — the mechanism vendors *from* the design system, so the design system
owns it.
**It could not be restored unchanged.** The script walked its source directory
and copied everything it found. In the monorepo, `shared/playground-design-system/`
contained only the design system. This repo's root contains the design system
*plus* the repo apparatus: `STATE.md`, `LICENSE`, `SECURITY.md`, `.git/`,
`docs/`, and `playground-examples/`. That is not a hypothetical: run verbatim
against this repo's root, the recovered script vendored **109 files instead of
27**, `STATE.md` and the whole of `.git/` among them — and `STATE.md` is
deliberately gitignored because this repo's remote is public.
The boundary chosen is an **explicit allowlist** (`DELIVERED_FILES` in the
script) rather than a `dist/` directory. A `dist/` would duplicate every
stylesheet and insert a copy step that can silently go stale; the allowlist
names the delivered set in one place that the tests read directly, so there is
no second definition to drift from.
An allowlist has one failure mode of its own: a file added to the design system
but not to the list would silently stop shipping, and `--check` would never
notice, because it hashes a consumer's tree against that consumer's own
`MANIFEST.json` and never against the source. This repo has already lived
through that shape once — `components-tier4-project-view.css` was added in
v0.6.0 and was missing from the README's own directory tree until 2026-08-18.
So the script scans the source for design-system files the allowlist does not
name (root-level `*.css`, plus everything under `fonts/` and `schemas/`) and
refuses to run, naming the file. Both directions are covered by
`tests/sync-source-boundary.test.mjs`.
### 3. Where should `MANIFEST.json`'s `source` point? Here — but not yet.
This is not the consumer's business alone: the field is written by this repo's
script and describes this repo's source, so the value is ours to define.
The important finding is that `source_commit` is not merely unreachable — **it
is false**. Both consumers record
`source_commit: c1b7bad3899c5cfe9ff90663003609b018aa79a0`, but the vendored tree
contains `components-tier4-project-view.css`, and that file demonstrably did not
exist in `c1b7bad`. The sync labelled its output with the marketplace's `HEAD`
while copying the working tree, which at that moment held a v0.6.0 file that had
not been committed yet.
The consequence for repair: **do not fix the field by translating the path.**
Rewriting `shared/playground-design-system/` to this repo's name while keeping
`c1b7bad` would turn an obviously stale pointer into a plausible false one, and
a plausible false pointer is worse — it stops inviting the check that would
catch it.
The correct order is: restore the script with a source boundary, run one
re-sync, and let the script write a truthful manifest in which `source_commit`
is this repo's `HEAD` — reachable from this repo, unlike `c1b7bad`. Until then,
a visibly stale field is the more honest state.
Concretely, the restored script writes `source: "playground-design-system"`
the repo name, not a path inside it and not the absolute path of whatever
checkout happened to be read, since either would be a machine-local string in a
committed file. `source_commit` comes from `git rev-parse HEAD` in the source
checkout. **That makes the push order load-bearing: run the first re-sync only
after this repo's commit is on the public mirror**, or the manifest gets a hash
that exists on one machine — the same unreachable-pointer defect as `c1b7bad`,
with a fresher date on it. Check with
`git -C <this repo> rev-parse HEAD` against `git -C <this repo> rev-parse origin/main`
before syncing.
One correction to the premise this question arrived with: the lineage is **not**
severed. `c1b7bad` is unreachable from this repo and from either consumer, but
it is alive in `catalog`. "Nobody can compute how far behind a vendored copy is"
does not hold — it was computed, and the answer is one file.
## Current state
Re-sync, run from a checkout of this repo with the consumer checked out
alongside it:
```sh
node scripts/sync-design-system.mjs <consumer-name> --target ../<consumer-repo>
```
Integrity check, which needs no source checkout and writes nothing to the
consumer:
```sh
node scripts/sync-design-system.mjs <consumer-name> --target ../<consumer-repo> --check
```
`--target` is the consumer's **repo root**; the script appends
`playground/vendor/playground-design-system/` itself. Pointing `--target` at
the vendor directory instead makes `--check` look one level too deep, find no
`MANIFEST.json`, and report drift that does not exist.
One trap worth naming for anyone checking by hand instead. The sync injects
`/* Code generated by sync-design-system.mjs; DO NOT EDIT. */` as line 1 of
every `.css`, and the source has no such line, so a plain `diff` or `cmp`
against this repo reports **all nine stylesheets as drifted** when none of them
is: raw `cmp` matches 17 of 27 files, and stripping line 1 from the vendored
`.css` files puts it back at 26 of 27 with this repo's README the only real
difference. `--check` is immune, because it hashes the vendored tree against
`MANIFEST.json`, which was built after the header was injected. This was found
by `ms-ai-architect` on their own copy and re-measured here (see the
[Verification log](#verification-log)).
`MANIFEST.json` should still be left alone. This session restored the script
but ran no re-sync, so the fields described in § 3 above are unchanged and the
reasoning there still holds: the first real re-sync is what rewrites them
truthfully.
## Verification log
Measured 2026-08-27 against ground truth. `<marketplace>` is the polyrepo root.
| Claim | How it was checked | Result |
|---|---|---|
| `sync-design-system.mjs` absent from the marketplace | `find` over the tree, maxdepth 5, `.git`/`node_modules` pruned | 0 hits. Denominator: the same query returned 836 `.mjs` files; known-positive control found `catalog/scripts/check-versions.mjs`, so the query can find. |
| `shared/` absent | `find -type d -name 'shared*'`, maxdepth 3 | 0 hits |
| Two consumers vendor the system | `find -type d -name playground-design-system`, maxdepth 4 | `ms-ai-architect` and `llm-security`, 27 files + `MANIFEST.json` each |
| `c1b7bad` unreachable from this repo | `git cat-file -t` | fatal: could not get object info. Known-positive control on this repo's `HEAD` returned `commit`. |
| `c1b7bad` reachable from `catalog` | `git -C catalog cat-file -t` | `commit` — 2026-05-15, monorepo history, 977 commits |
| The monorepo commit held both deleted paths | `git -C catalog ls-tree -r c1b7bad` | `shared/playground-design-system/` (26 files) and `scripts/sync-design-system.mjs` both present |
| Both paths died in the migration | `git -C catalog log --diff-filter=D` | `e84dffd`, 2026-06-18, deleted both |
| `source_commit` is false | `git -C catalog cat-file -e c1b7bad:shared/playground-design-system/components-tier4-project-view.css` | path does not exist in `c1b7bad`. Known-positive control on `components-tier3.css` in the same commit succeeded. |
| No local drift in either consumer | SHA-256 of each vendored file vs its own `MANIFEST.json` | both `MANIFEST OK`, 27/27 |
| Drift vs this repo | SHA-256 vs this repo's root, generated-header stripped from `.css` | 26/27 identical in both consumers; `README.md` the only difference |
| The recovered script supports polyrepo | read of `git show e84dffd^:scripts/sync-design-system.mjs` | `--source`, `--target`, `--check` all present and honoured in `main()` |
| The verbatim script leaks this repo's apparatus | ran the recovered script unchanged, `--source <repo root>`, into a temp target | 109 files vendored instead of 27; `STATE.md`, `.git`, `.gitignore`, `docs`, `playground-examples`, `LICENSE`, `SECURITY.md` all present. `tests/sync-source-boundary.test.mjs` red on this build, with `tokens.css` present as the known-positive control proving the assertions read the right path. |
| The allowlisted script does not leak | `node --test tests/sync-source-boundary.test.mjs` after the boundary was added | 2/2 pass: 27 files + `MANIFEST.json`, none of the seven apparatus entries present |
| An unlisted design-system file fails the sync | test adds `components-tier5.css` to a complete synthetic source | non-zero exit, filename named in stderr. Known-positive control: the same source without the extra file syncs cleanly. |
| The published `--check` command works against both consumers | `node scripts/sync-design-system.mjs <name> --target ../<name> --check` | `MANIFEST OK (27 files, source_commit c1b7bad3899c5cfe9ff90663003609b018aa79a0)`, exit 0, for `ms-ai-architect` and `llm-security` alike |
| A raw `cmp` misreports the CSS as drifted | `cmp` per file vs this repo's root, then `cmp` with line 1 stripped | raw: 17/27 match, the 10 differing being 9 `.css` + `README.md`; line-1-stripped: 26/27, `README.md` alone. Independently re-measured here; matches `ms-ai-architect`'s finding exactly. |
Not verified: whether `catalog`'s history is itself mirrored anywhere off this
machine. The recovery path above assumes a local `catalog` checkout.