Core specification
1.0-rccanpuff-v1.md
The vault layout, the journal (JSONL events), the catalog (Markdown + YAML frontmatter cards), attachments, identifiers, units, import merging, and the extension mechanism.
Open standard · v1.0-rc · July 2026
CanPUFF documents the data an individual creates when they track what they consume, what they have, where they got it, and how it affected them. It is a format for people, not businesses — and it is designed to outlive any application, including the ones its authors write.
Think “RSS for personal cannabis data”: plain files you own, readable in any text editor, portable between apps, with no account, no API key, and no company that can take them away.
The gap
Seed-to-sale systems, dispensary point-of-sale, and regulatory traceability are all standardised and interoperable. Meanwhile every consumer tracking app keeps its records in an undocumented internal database — so the data dies with the app, and years of personal history go with it.
CanPUFF is the missing half: a documented, implementable format for the consumer's own records, published under CC0, with conformance fixtures so a second implementation can prove it agrees with the first.
The documents
canpuff-v1.md
The vault layout, the journal (JSONL events), the catalog (Markdown + YAML frontmatter cards), attachments, identifiers, units, import merging, and the extension mechanism.
canpuff-sealed-v1.md
End-to-end encryption with age v1, a key hierarchy rooted in a BIP39 mnemonic, and a synchronisation protocol that works against a dumb file server which never sees plaintext.
mapping-pufftab-ios.md
Field by field, lossless, both directions: how the first implementation's 13-entity SwiftData model becomes CanPUFF and comes back unchanged.
schemas/v1/<type>.json
JSON Schema 2020-12 for every object type, served at the canonical
$id URLs. Normative for the JSON form.
examples/
A complete example plain vault, plus one standalone JSON object per type that validates against the schemas.
fixtures/
Edge-heavy valid vaults and deliberately broken ones, each with the reference validator's expected verdict, plus a runner. This is what makes a third-party implementation testable.
tools/
validate-vault.py for conformance and
diff-vaults.py for semantic equivalence of two vaults.
Standalone, no dependencies; both accept a directory or a
.canpuff.zip.
At a glance
Two tiers: a private journal of events, and a shareable catalog of documents. Binary files are content-addressed by SHA-256. Nothing else.
<vault>/ manifest.json REQUIRED vault identity & format version journal/ <YYYY>/<MM>.jsonl journal events, one JSON object per line catalog/ supplies/<id>.md shops · chains · brands · producers methods/<id>.md tax-rates · terpenes attachments/ <hh>/<sha256>.<ext> content-addressed binary files apps/ <app-id>/... application-private data, carried through intact
Readers must tolerate unknown files and directories, so the format can grow without breaking anything.
{
"type": "consumption",
"version": 1,
"id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"at": "2026-07-09T16:20:00-07:00",
"supply": "9f3c1a2e-7b41-4c8e-9d2a-51f08c77b3aa",
"amount": { "grams": 0.35 },
"shared": { "people": 2, "gift": false },
"effects": { "rating": 7 },
"notes": "Paired with a movie."
}
Pretty-printed here; on disk it is one line. Private by default — journal data never leaves a trusted device unencrypted.
--- type: supply version: 1 id: 9f3c1a2e-7b41-4c8e-9d2a-51f08c77b3aa name: Pink Kush variety: indica thc: 22.5 terpenes: - { name: Myrcene, percentage: 0.8 } grams: 2.1 cost: 29.99 currency: CAD --- Dense buds, gassy. Better in the bong than rolled. Second time buying — first batch was drier.
Opens in any text editor, and natively in Obsidian. Contains nothing about the user's behaviour, so it is safe to share.
Design principles
The format is the product. Applications — including PuffTab — are replaceable CRUD interfaces over it.
Events are private by default and never leave the user's control unencrypted. Product, shop and brand records contain nothing personal and are inherently shareable.
Catalog cards are Markdown with YAML frontmatter. The journal is JSONL — still greppable, built for machines.
The canonical data model is JSON, specified by JSON Schema. Interchange, QR payloads and validation always use the JSON form.
Consumption records are health data and, in some jurisdictions, self-incriminating. The format treats that as a design input, not a disclaimer.
Unknown fields are ignored, exactly one blessed extension mechanism exists, and every object carries type and version.
UUIDs, RFC 3339, SHA-256, JSON Schema, age encryption. No invented primitives, no clever tricks to regret later.
Status & governance
The core format is a v1.0 release candidate: frozen, standing on a five-lens adversarial review with 83 findings applied, and a proven lossless round-trip of real data between two independent implementations — the PuffTab iOS app and the PuffTab web app. Breaking changes now require a demonstrated interoperability failure; additive proposals target v1.1 under the versioning rules in Core §10. The sealed profile remains a draft, and v1.0 final follows its validation.
Spec text and schemas are published under CC0-1.0, with an
OWFa 1.0 patent non-assertion covering implementations. Feedback,
implementations and proposals are all welcome — the
extension mechanism
is the intended first stop for a new need, and fields that prove themselves
in ext are candidates for the next minor version.