Lab Notes Ledger Write Contract
This standard defines how any writer (Web Admin UI, CLI, API clients, AI agents, imports) must create and update Lab Notes so the system remains deterministic and non-haunted.
Core Model (v2)
lab_notes = identity + metadata + pointers
lab_note_revisions = append-only truth for content + canonical frontmatter
Key invariants
- Content truth lives in
lab_note_revisions. lab_notes.current_revision_idpoints at the latest draft content.lab_notes.published_revision_idpoints at the published content snapshot.- Publishing is explicit; drafts do not silently become public.
Identity Rules
Two identities, one truth
- Public identity:
(slug, locale) - DB identity:
id
Required uniqueness
(slug, locale)must be unique (enforced by DB index).
Client state key
- Use
${slug}:${locale}for editor caches. - After first save, store
idand use it for subsequent updates.
Write Contract: What a “Save” Must Do
A writer performing a save MUST:
- Create exactly one new revision in
lab_note_revisions. - Update exactly one pointer in
lab_notes:current_revision_id = <new revision id>
- Never modify
published_revision_idas part of a normal save. - Always bump
lab_notes.updated_at.
Required revision fields (minimum meaningful set)
lab_note_revisions must include:
id(uuid)note_id(lab_notes.id)revision_num=MAX(revision_num)+1per notesupersedes_revision_id= previouslab_notes.current_revision_id(orNULLfor first)frontmatter_json(stringified JSON; canonicalized frontmatter)content_markdown(markdown body)content_hash(sha256 of canonical representation)schema_version(e.g."0.1")source(see Source Semantics)intent,intent_version(see Intent Semantics)auth_type,scopes_json(see Auth Semantics)created_at(db default ok)
Canonical hash input
To ensure stable dedupe and diffs, content_hash should be computed from:
frontmatter_json(stringified JSON)- delimiter line
\n---\n content_markdown
Publish Contract
Publishing is the only operation that changes published pointers.
A writer performing a publish MUST:
- Set:
status = 'published'published_revision_id = current_revision_id
- Ensure
published_atis set (auto-fill if missing). - Bump
updated_at.
Unpublish Contract
A writer performing an unpublish MUST:
- Set
status='draft' - Clear
published_revision_id - Clear
published_at - Bump
updated_at
Source Semantics
source describes the channel of origin:
cliwebapiimport
Human vs AI is represented via audit events, not source.
Auth Semantics
auth_type:
human_sessionlab_token
Scopes should always be recorded.
Intent Semantics
Use stable, action-shaped intents:
admin_save,admin_publishcli_save_draft,cli_publishapi_saveseed_marker_note
Audit Semantics
Every revision SHOULD emit a lab_events row with:
actor_type:human | ai | systemactor_idrevision_id,note_idintent,auth_type,scopes_json
Regression Tests
- Draft nulls publish metadata
- Publish pins content
- Slug+locale uniqueness
Definition of Done
When this contract is followed:
- Drafts are safe
- Published content is stable
- Audits are complete
- The database becomes boring (highest compliment)