Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ This is a monorepo. Packages are published to npm under the `@haverstack` scope.
| [`@haverstack/record-adapter-sqlite`](./packages/record-adapter-sqlite) | Node native SQLite (`node:sqlite`) `StackRecordAdapter` — used by `adapter-local` |
| [`@haverstack/blob-adapter-disk`](./packages/blob-adapter-disk) | Disk filesystem `StackBlobAdapter` |
| [`@haverstack/adapter-api`](./packages/adapter-api) | HTTP adapter for remote stack servers |
| [`@haverstack/commons`](./packages/commons) | Canonical Schema Commons type definitions (`note`, `task`, `contact`, ...) |

Planned:

Expand Down Expand Up @@ -288,6 +289,10 @@ packages/
src/
index.ts # APIAdapter (StackAdapter)
tests/
commons/ # @haverstack/commons
src/
index.ts # Canonical Schema Commons type constants + defineCommonsTypes()
tests/
```

---
Expand Down
37 changes: 21 additions & 16 deletions docs/commons/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ org.haverstack/contact@1
org.haverstack/article@1
org.haverstack/place@1
org.haverstack/page@1
org.haverstack/photo@1 (staged — see below)
org.haverstack/photo@1
org.haverstack/message@1 (proposed — see below)
org.haverstack/event@1 (proposed)
org.haverstack/poll@1 (proposed)
Expand Down Expand Up @@ -140,7 +140,7 @@ authority on **any** record, of any type:
- **`embed`** — `{ kind: 'attachment', label: 'embed', fileId }` marks a file
referenced from a record's body text (`note`, `article`, `page`, `message`). How the
body refers to the embed is app territory in v1; a commons syntax is an expected
follow-up once `file-ref` fields (#63) land.
follow-up proposal.
- **`series`** — `{ kind: 'relationship', label: 'series', recordId }` groups records
that are occurrences of one recurring thing (materialized [`event`](./event.md)
occurrences are the motivating case). Reserved now so recurrence proposals build on
Expand Down Expand Up @@ -217,7 +217,7 @@ shape** — notes are kept, messages are sent, articles are published.
| `org.haverstack/article@1` | [`article.md`](./article.md) | Draft | `{ title, text }` |
| `org.haverstack/place@1` | [`place.md`](./place.md) | Draft | `{ latitude, longitude }` |
| `org.haverstack/page@1` | [`page.md`](./page.md) | Draft | `{ slug, text }` |
| `org.haverstack/photo@1` | [`photo.md`](./photo.md) | Staged | `{ image }` (pending #63) |
| `org.haverstack/photo@1` | [`photo.md`](./photo.md) | Draft | `{ image }` |
| `org.haverstack/message@1` | [`message.md`](./message.md) | Proposed | `{ text }` |
| `org.haverstack/event@1` | [`event.md`](./event.md) | Proposed | `{ title, startsAt }` |
| `org.haverstack/poll@1` | [`poll.md`](./poll.md) | Proposed | `{ question, options }` |
Expand All @@ -226,24 +226,29 @@ shape** — notes are kept, messages are sent, articles are published.

**Statuses.** _Draft_: settled enough to build against (still subject to in-place
change until there's an install base). _Staged_: design recorded, registration blocked
on a named implementation issue (`photo` waits on #63 so its required image is
schema-enforced rather than convention-only). _Proposed_: design recorded to fix
intent, but per the governance rule it stays parked until a concrete intended writer
exists — the group cluster graduates when a group-tools app or demo is real. The group
cluster additionally depends on the grant/group reshape (#57/#58) landing as decided.
on a named implementation dependency, so a required field is schema-enforced rather
than convention-only from day one. No type currently holds this status. _Proposed_:
design recorded to fix intent, but per the governance rule it stays parked until a
concrete intended writer exists — the group cluster graduates when a group-tools app
or demo is real, building on the grant/group primitives (`_group`, type-level grants)
documented in the identity and access-control specs.

Deliberately absent from the initial set: `post` (public social shapes are reconciled
with the ATProto-compat RFC, #15 — `message` is the group-scoped shape, not the social
one), recurrence rules (see `event`: occurrences are materialized in @1),
`file`/`document` (a first-class `file` type is expected to follow `photo`'s pattern
once #63 lands; until then a record plus attachment covers it), and `checkin` (subsumed
by the `location` cross-type convention plus any record).
`file`/`document` (a first-class `file` type is expected to follow `photo`'s pattern;
until a real writer needs it, a record plus attachment covers it), and `checkin`
(subsumed by the `location` cross-type convention plus any record).

---

## Planned tooling
## Tooling

A `@haverstack/commons` package that exports the canonical schemas as constants and a
`defineCommonsTypes(stack, [...])` helper, so "register the type exactly as written"
is a one-liner and drift is structurally impossible. Not started; the definitions in
this directory are authoritative until it exists.
[`@haverstack/commons`](../../packages/commons) exports the canonical schemas for
every Draft-status type as constants and a `defineCommonsTypes(stack, [...])` helper,
so registering a type exactly as written is a one-liner and the transcription drift
the governance process exists to prevent is structurally impossible. Apps should
depend on the package rather than transcribing a fenced code block from this
directory. These files are the design record: rationale, conventions, and read-compat
cores live here, and the package's constants are kept in lockstep with them. Proposed
types (not yet Draft) are docs-only and stay out of the package until they graduate.
4 changes: 2 additions & 2 deletions docs/commons/bookmark.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ await stack.defineType('org.haverstack/bookmark@1', 'Bookmark', {
- **Page snapshot**: an archived copy of the page's content is an attachment association
with label `snapshot` (one per capture; multiple captures are multiple associations).
The underlying file's `mimeType` — a property of the `_attachment@1` record, not the
association — follows the deterministic first-recorded rule (#65); apps should prefer
association — follows the deterministic first-recorded rule; apps should prefer
archival-friendly types (PDF, WARC, single-file HTML stored as an attachment — served
under the #66 safe-list rules).
under the dangerous-type safe-list rules).
- **Favicon / preview image**: attachment association with label `preview`.

## Read-compat core
Expand Down
11 changes: 5 additions & 6 deletions docs/commons/contact.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,10 @@ or via associations, not by address.
`{ kind: 'relationship', label: 'entity', recordId: <_entity record id> }` on the
contact. This keeps the directory entry (mutable, user-owned petname territory) apart
from the identity record, in line with the properties-vs-perspectives principle and
the DID identity direction (#49): your name for someone is your perspective; their key
is a property.
the DID identity model: your name for someone is your perspective; their key is a
property.
- **Grouping** (family, team, club): tag associations for casual grouping. A commons
position on linking contacts to `_group` records is deferred until the group reshape
(#58) settles.
position on linking contacts to `_group` records has not been proposed yet.
- **Birthdays**: excluded from @1 — calendar-shaped data (partial dates, year-unknown
birthdays, recurrence) belongs with the future `event` proposal.

Expand All @@ -84,8 +83,8 @@ lead — can be listed by a contacts consumer.
- Postal addresses — genuinely structured, rarely shared between apps; future proposal
with vCard `ADR` as prior art.
- `birthday` and dates — see conventions.
- Any identity/key material — that is `_entity`/#49 territory; a contact asserts
nothing cryptographic.
- Any identity/key material — that is `_entity` territory; a contact asserts nothing
cryptographic.

## Changelog

Expand Down
2 changes: 1 addition & 1 deletion docs/commons/event.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ await stack.defineType('org.haverstack/event@1', 'Event', {
| Field | Kind | Required | Meaning |
| ------------- | --------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `title` | `string` | yes | What it is ("Monthly meeting", "Spring cleanup day"). |
| `startsAt` | `date` | yes | When it begins — an absolute instant (ISO 8601, per #69). An event is meaningless without a time. |
| `startsAt` | `date` | yes | When it begins — an absolute instant (ISO 8601). An event is meaningless without a time. |
| `endsAt` | `date` | no | When it ends. Absent means unspecified duration (a deadline, a "starts at 7pm" with no stated end). |
| `allDay` | `boolean` | no | Absent means `false`. When `true`, times-of-day are ignored: the event occupies the calendar date(s) of `startsAt`(–`endsAt`) interpreted in the stack's configured timezone. |
| `description` | `text` | no | Details. Markdown by convention. |
Expand Down
9 changes: 4 additions & 5 deletions docs/commons/folder.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,14 @@ await stack.defineType('org.haverstack/folder@1', 'Folder', {
- **A "file" in the drive** is, today, any record carrying an attachment association —
typically a `note` with the file attached and the body as commentary. A first-class
`file` type (a required `file-ref` plus name) is expected to follow
[`photo`](./photo.md)'s pattern once #63 lands; this file's conventions will be
amended then.
[`photo`](./photo.md)'s pattern; this file's conventions will be amended when a real
writer needs it.
- **Folder-level permissions do not exist** — permissions are per-record and grants are
per-type; a folder's `permissions` field governs the folder record itself, **not**
its contents. Apps must not present a folder as an access boundary unless they also
set permissions on every contained record. Recorded bluntly because every user will
assume otherwise; whether reference-implies-access (#51) or a future inherited model
changes this is an open question for the permissions work, not something this type
can promise.
assume otherwise: the permission model has no reference-implies-access or inherited
mechanism, so a folder's own access and its contents' access are always independent.
- **Moving** is re-parenting — one field write, atomic, history preserved.

## Read-compat core
Expand Down
6 changes: 4 additions & 2 deletions docs/commons/message.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ write, which is the whole reason this type needs no author field.
- **Comments across trust boundaries**: a public blog's comment section is this same
shape living in the author's personal stack — commenters are entities holding a
`create` grant on `message@1`. Webmention-grade interop with real primitives
underneath; making that practical for strangers depends on the identity work (#49).
underneath, practical for strangers because entity identity is a self-certifying DID
(a public key, nothing else) rather than an account in some directory.

## Read-compat core

Expand All @@ -85,7 +86,8 @@ discussion.

- Social-post semantics — #15 territory (see above).
- Reactions — a future micro-proposal (likely tag associations by non-authors — which
needs the association-permission rules from the #57/#58 reshape to settle first).
needs finer-grained association permissions than today's `update-own`/`update-any`
grant actions provide).
- Chat features (presence, ephemerality, delivery/read receipts) — out of scope by
design posture, not by omission.
- `to`/`cc` addressing — presence in the group's stack is the addressing.
Expand Down
7 changes: 3 additions & 4 deletions docs/commons/note.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ await stack.defineType('org.haverstack/note@1', 'Note', {
associations with label `embed` — see the cross-type conventions in the
[README](./README.md). How the body refers to an embedded file (e.g. a markdown
image reference) is app territory in @1; a commons convention here is an expected
follow-up proposal once `file-ref` fields (#63) land.
follow-up proposal.
- **Cross-references**: a note that is _about_ another record (annotating a bookmark, a
contact) uses `{ kind: 'relationship', label: 'about', recordId }`.

Expand All @@ -51,16 +51,15 @@ await stack.defineType('org.haverstack/note@1', 'Note', {

Consumers wanting maximum reach should accept any type compatible with this shape and
treat `title`/`format` as optional enrichments. Note that `text` ⇄ `string` are mutually
readable under the strengthened `isCompatible` relation (#54), so short-string types
also qualify.
readable under `isCompatible()`, so short-string types also qualify.

## Deliberately excluded

- `createdAt`/`updatedAt`/author fields — native record fields cover these.
- `tags: string[]` — tag associations exist.
- `pinned`, `archived`, `color` — perspectives; app sidecar or tags.
- Rich-text formats beyond markdown/plain (HTML in particular) — HTML notes are an
XSS-shaped liability the dangerous-type work (#66) exists to avoid; apps holding HTML
XSS-shaped liability the dangerous-type safeguards exist to avoid; apps holding HTML
should convert on write.

## Changelog
Expand Down
24 changes: 11 additions & 13 deletions docs/commons/photo.md
Original file line number Diff line number Diff line change
@@ -1,24 +1,19 @@
# `org.haverstack/photo@1` — Photo

> **Status:** Staged — blocked on `file-ref` fields (#63). The design is recorded now
> so intent is fixed; **do not register this type until #63 lands** and this file is
> promoted to Draft.
> **Status:** Draft.

An image as a first-class object — the photo-library / Tumblr-photo-post shape — as
opposed to an image illustrating some other record (which is just an attachment
association on that record). A photo record is "this image is in my library": the
binary plus caption, alt text, and capture time.

## Why staged

The essential field of a photo is the image itself, but type schemas validate `content`
only: a "required attachment association" would be a convention the schema can't
enforce and `isCompatible()` can't see. Issue #63's `file-ref` field kind fixes exactly
this — making `photo` its first and motivating consumer. Shipping earlier with a
convention-only image, then making a `file-ref` field required later, would force a
version bump for no gain; waiting means `photo@1` is right from day one.
enforce and `isCompatible()` can't see. The `file-ref` field kind fixes exactly this,
making `photo` its first and motivating consumer — the required `image` field below is
schema-enforced, not convention-only.

## Schema (pending #63)
## Schema

```ts
await stack.defineType('org.haverstack/photo@1', 'Photo', {
Expand All @@ -38,7 +33,7 @@ await stack.defineType('org.haverstack/photo@1', 'Photo', {
| `alt` | `string` | no | Accessibility description of the image content — a property of the image (describes the bytes), not a perspective; travels with it. |
| `takenAt` | `date` | no | Capture time (typically from EXIF). Distinct from `createdAt`, which is when the record entered the stack (import time). |

## Conventions (to finalize at promotion)
## Conventions

- **Geotag**: the cross-type `location` relationship to a [`place`](./place.md)
record — not raw coordinates in content.
Expand All @@ -52,12 +47,15 @@ await stack.defineType('org.haverstack/photo@1', 'Photo', {
- **Video/audio**: sibling proposals, not a generalized `media` type — photo-specific
prior art (EXIF, photo libraries) is too good to dilute.

## Read-compat core (pending #63)
## Read-compat core

```ts
{ image: { kind: 'file-ref', required: true } }
```

## Changelog

- **Staged** — design recorded; registration blocked on #63 (`file-ref` field kind).
- **Draft** — graduated from Staged: the `file-ref` field kind now exists, so `image`
is schema-enforced as specified, unchanged from the staged design.
- **Staged** — design recorded; registration blocked on the `file-ref` field kind not
yet existing.
Loading
Loading