Site Content Bundles
putnami.dev can serve documentation produced in another repository — without that repository ever touching this site's build. Content travels as a site content bundle (site-content-bundle/v1): a digest-addressed tar.gz of raw markdown plus a bundle.json manifest declaring the site URL prefixes (mounts) the bundle owns.
Docs content is data, not code: bundles are versioned in the put registry on their own cadence, while the site binary is versioned with deploys.
Baked consumption (v1)
The pipeline is deterministic by construction:
- Pin.
sites/putnami.dev/content.lock.jsoncommits, per bundle, its registry package, achannelHint, the resolvedversion, and — the only field a build ever fetches by — the blob's sha256digest. Version and channel are provenance for reviewers. - Fetch by digest. At generate time the site fetches each pinned blob by digest (with a digest-keyed local cache, so builds are offline-safe once fetched), verifies the digest, validates the manifest and payload (traversal, symlink, unlisted-file, and digest-mismatch rejection), and unpacks the files into
.gen/public/docs/<mount>. The runtime navigation tree and docs loader scan.gen/public/docs, so mounted pages are served and navigable like any other doc. A mount dropped from the lock is pruned from.genon the next generate (a recorded manifest of the previous run's mounts), so stale content is never re-captured into the build cache. - Bump to advance.
bun run content:bump(insites/putnami.dev) is the only place a channel is resolved: it asks the registry whatpackage@channelHintcurrently points at, computes the digest from the fetched bytes, and rewrites the lock. The committed lock diff is the change detection and the review surface. Aputnami content bumpCLI verb is deferred until a second site consumes bundles.
The build never resolves a channel and never checks a URL for "newness" — that would make the generate cache key lie about its inputs. The lock file participates in the generate cache key, so the same lock reproduces byte-identical docs and a lock bump is what produces a new build.
Mounts partition the URL space
A bundle declares the URL prefixes it owns, e.g. /docs/08-cloud. Bundle-provided sections use the same NN- ordering prefix as site-local sections, so navigation ordering just works. Equal, parent, or child prefix overlap — between two bundles, or between a bundle and content the site materializes itself — is a hard build error, never a silent overwrite.
Failure policy
Baked content is the always-available fallback; the registry is never on the serving path.
- A pinned bundle that cannot be fetched or fails verification fails the build in CI/deploy and is skipped with a warning in local dev, so an offline laptop still builds the site.
- A bundle with a newer
formatVersionmajor than the site understands is always skipped in favor of baked content — updating the site is the fix, and forcing a deploy is exactly what the baked path is for.
Known limitations (populated bundles)
The v1 ships behind an empty lock; these apply only once a bundle is actually pinned, and are tracked as follow-ups:
- Static prerender is best-effort for bundled pages. SSG static-path enumeration and bundle materialization both run in the concurrent generate phase with no ordering edge between them, so a mounted page may not be prerendered to static HTML. It is still served at runtime (the docs loader and nav scan
.gen/public/docs), so this is a performance/first-paint concern, not a correctness one. Making it first-class needs a build-ordering guarantee that materialization completes before enumeration. - Bundled pages are not in the search index. The search index is built from the site's own
doc/source tree, which — as with all docs merged in viagenerate.assets(the framework and tooling docs) — excludes copied-in content. Indexing the materialized.gen/public/docstree instead would cover bundles and copied docs alike, but is a broader change with the same ordering constraint as above.