You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
A static-site generator reading a stack is the first real consumer of the commons
publishing types. page@1 already names it as its intended writer:
Status: Draft. Intended writer: a static-site-generator integration reading a
stack (in progress by the project maintainer — either a standalone generator or an
Eleventy data source).
That project is now concrete. It is three pieces:
@haverstack/eleventy — an Eleventy plugin that builds a site from a stack
@haverstack/cli — a general-purpose, type-agnostic record editing tool
A migration of an existing Cloudflare Pages + D1 + R2 site onto a stack, including
a one-time import of several years of dated records
Between them they exercise most of the commons publishing cluster, the attachment path,
the query path, permissions, and the change feed — which is why they surface a specific
set of core gaps rather than a diffuse wish list.
This issue exists to say what those gaps are and why they want doing in a particular
order, so the sequencing is a recorded decision rather than something rediscovered
later. None of the linked issues is blocked on this one; it is a map, not a gate.
graph TD
A["#202 unlisted<br/><i>decision</i>"] --> S["sidecar type set"]
B["#204 post@1"] --> I["one-time import"]
C["#203 settable createdAt"] --> I
D["#16 relationship union"] --> E["@haverstack/eleventy"]
D --> F["@haverstack/cli"]
S --> E
I --> E
G["#194 resume"] -.-> H["incremental builds<br/><i>v2</i>"]
E -.-> H
Loading
Three tracks that barely touch each other:
Types track — #202, then #204.#202 is a decision, not an implementation: it
determines whether the generator's sidecar type carries a hidden field or defers to a
permission tier. Cheap to settle, and settling it late means rewriting a type that
already has records written against it. #204 is independent of #202 and can proceed in
parallel.
Core track — #203 and #16. Independent of each other and of the types track.
Deployment track — #160 and #161. Entirely independent of everything above. These
decide where the stack runs, which no type or API decision depends on.
Ordering, and why
1. #202 — unlisted. First because it is the cheapest and the most downstream-shaping.
The sidecar type set is waiting on the answer, and it is a design decision rather than
work. Deciding it wrong is recoverable; deciding it late is not, because by then there
are records.
2. #203 — settable createdAt. A hard blocker for the import, not a convenience.
Without it every imported record is stamped with the import moment and years of archive
collapse to a single day. It is also load-bearing for #204 specifically: post@1
carries no content date by design, so native createdAt is the only ordering key a post
will ever have.
3. #16 — relationship target union. Before the two new repos exist, not after. Both
read and write relationship associations — the commons cross-type conventions
(author, location, series, about), curated collection membership, and Filter.relatedTo on the generator's read path. Landing it afterwards means paying the
migration across two fresh codebases simultaneously, neither of which has users to
justify the churn. The window where this is nearly free is open now and closes when the
first of those repos gets written.
4. #204 — post@1. Blocks the import from landing short-form entries on a correct
type. The fallback — filing them as note@1 and migrating later — collides with the
commons' own rule that contracts don't convert in place, and re-minting records would
destroy permalinks that are otherwise preserved exactly. Can start any time; needs to
finish before the import runs.
5. #160 — blob-adapter-s3. Only if attachment bytes stay in R2. Determines whether
media is a data migration or a config change.
6. #161 — record-adapter-do-sqlite. The deployment fork: VPS versus staying on
Cloudflare. Needs deciding before deployment work starts, since almost none of that work
transfers between the two targets. The decision is urgent; the implementation may well
be "not now."
7. #194 — feed resume. Incremental builds. Full rebuilds work at this scale, so this
is a v2 improvement rather than a launch requirement.
The site generator's own design documents — the type set it uses and the shape of the
two new packages — live outside this repository for now, since neither package exists
yet. Sidecar types are namespaced under the integration rather than under any one site,
so org.haverstack stays reserved for the commons as specified.
Motivation
A static-site generator reading a stack is the first real consumer of the commons
publishing types.
page@1already names it as its intended writer:That project is now concrete. It is three pieces:
@haverstack/eleventy— an Eleventy plugin that builds a site from a stack@haverstack/cli— a general-purpose, type-agnostic record editing toola one-time import of several years of dated records
Between them they exercise most of the commons publishing cluster, the attachment path,
the query path, permissions, and the change feed — which is why they surface a specific
set of core gaps rather than a diffuse wish list.
This issue exists to say what those gaps are and why they want doing in a particular
order, so the sequencing is a recorded decision rather than something rediscovered
later. None of the linked issues is blocked on this one; it is a map, not a gate.
The issues
unlistedpermission tier — reachable by URI, absent from feedspost@1— the broadcast utterancecreatedAt/updatedAtfor owner-side importsblob-adapter-s3record-adapter-do-sqlitesubscribe()cannot resumeDependencies
Three tracks that barely touch each other:
Types track — #202, then #204. #202 is a decision, not an implementation: it
determines whether the generator's sidecar type carries a
hiddenfield or defers to apermission tier. Cheap to settle, and settling it late means rewriting a type that
already has records written against it. #204 is independent of #202 and can proceed in
parallel.
Core track — #203 and #16. Independent of each other and of the types track.
Deployment track — #160 and #161. Entirely independent of everything above. These
decide where the stack runs, which no type or API decision depends on.
Ordering, and why
1. #202 —
unlisted. First because it is the cheapest and the most downstream-shaping.The sidecar type set is waiting on the answer, and it is a design decision rather than
work. Deciding it wrong is recoverable; deciding it late is not, because by then there
are records.
2. #203 — settable
createdAt. A hard blocker for the import, not a convenience.Without it every imported record is stamped with the import moment and years of archive
collapse to a single day. It is also load-bearing for #204 specifically:
post@1carries no content date by design, so native
createdAtis the only ordering key a postwill ever have.
3. #16 — relationship target union. Before the two new repos exist, not after. Both
read and write relationship associations — the commons cross-type conventions
(
author,location,series,about), curated collection membership, andFilter.relatedToon the generator's read path. Landing it afterwards means paying themigration across two fresh codebases simultaneously, neither of which has users to
justify the churn. The window where this is nearly free is open now and closes when the
first of those repos gets written.
4. #204 —
post@1. Blocks the import from landing short-form entries on a correcttype. The fallback — filing them as
note@1and migrating later — collides with thecommons' own rule that contracts don't convert in place, and re-minting records would
destroy permalinks that are otherwise preserved exactly. Can start any time; needs to
finish before the import runs.
5. #160 —
blob-adapter-s3. Only if attachment bytes stay in R2. Determines whethermedia is a data migration or a config change.
6. #161 —
record-adapter-do-sqlite. The deployment fork: VPS versus staying onCloudflare. Needs deciding before deployment work starts, since almost none of that work
transfers between the two targets. The decision is urgent; the implementation may well
be "not now."
7. #194 — feed resume. Incremental builds. Full rebuilds work at this scale, so this
is a v2 improvement rather than a launch requirement.
What can run in parallel
unlistedpermission tier — reachable by URI, absent from feeds and listings (split from #15) #202,createdAt/updatedAtcan't be set, so importing an existing archive collapses it to one timestamp #203, Commons:post@1— the broadcast utterance, with a concrete intended writer #204 and RFC: discriminated union for RelationshipAssociation targets #16 have no dependencies on each other. Four independentworkstreams.
can run at any point.
createdAt/updatedAtcan't be set, so importing an existing archive collapses it to one timestamp #203 and Commons:post@1— the broadcast utterance, with a concrete intended writer #204 are done, the import script can be written and run — and theimport is the real test of the type mapping, so it wants to happen before the plugin
and CLI are far along.
once RFC: discriminated union for RelationshipAssociation targets #16 has landed.
What is explicitly not blocking
wake-up triggers are not met by a site with modestly sized images.
that blob-adapter-s3 — blob storage is Node-disk-only, so object storage means writing your own #160 needs
listFiles()for GC against object storage and Optional adapter capabilities (deleteUnreferencedAttachmentRecords, listFiles) have no discovery mechanism for implementors #93 is the checklistthat would make that obvious to whoever writes it.
post@1proposal in Commons:post@1— the broadcast utterance, with a concrete intended writer #204 argues that the ATProto chain gatesthe bridge machinery, not the content type. If that argument fails, Commons:
post@1— the broadcast utterance, with a concrete intended writer #204 is blocked andthis ordering changes materially; if it holds, RFC: ATProto compatibility layer — core type and API changes #15 is not on this project's path at
all.
Notes
The site generator's own design documents — the type set it uses and the shape of the
two new packages — live outside this repository for now, since neither package exists
yet. Sidecar types are namespaced under the integration rather than under any one site,
so
org.haverstackstays reserved for the commons as specified.