| tags |
|
||||||
|---|---|---|---|---|---|---|---|
| technologies |
|
||||||
| standards |
|
||||||
| projects |
|
Version: 0.1
Status: v1 implemented (server + CLI) — see the "Implemented" notes below
Last updated: July 2026
Owner: TBD
Companion: docs/OKF_EXPORT_SPEC.md (the producer side)
v1 shipped:
POST /api/v1/collections?format=okfingests a JSON manifest (src/lib/okf-import.ts+ the import branch ofsrc/app/api/v1/collections/route.ts); the CLIdm import <dir>walks a local bundle directory and posts it. Frontmatter is parsed with theyamldependency (option 1, §5). Intra-bundle concept links are rewritten to the new docs' share URLs (§8). The import is atomic (one transaction) and additive. Deferred from v1:seoSlugfor imported public docs (skipped — they resolve via/share/{slug}), tarball input to the CLI (untar locally first), and everything in §10.
Make Draftmark a consumer of Open Knowledge Format (OKF) bundles: ingest an external OKF bundle (a directory / tarball / manifest of .md concept docs) and materialize it as a Draftmark Collection whose members are Docs. This is the symmetric inverse of the export capability, closing the round trip: Draftmark → OKF → Draftmark, and — more importantly — anyone's OKF bundle → Draftmark, so a bundle authored in another tool (or committed to a git repo) can be pulled in for review, commenting, and sharing.
This is the largest of the OKF follow-ups and has one genuinely hard sub-problem (YAML frontmatter parsing, §5) that export did not. It is written as a scoping doc: read §5–§7 to decide whether the value justifies the cost.
Non-goals (v1): git-URL ingestion, dedup/upsert of a previously-imported bundle, log.md → version-history reconstruction. All named in §10.
- Round-trip / portability. Export already lets knowledge leave Draftmark. Import lets it come back — and lets foreign bundles (Anthropic, Google, open-source, hand-written) land in Draftmark's review surface (comments, reactions, reviews, threaded replies).
- Onboarding. "I have an OKF bundle / an LLM wiki / a folder of markdown — get it into Draftmark" becomes one command.
- Symmetry is a credibility signal. Being a full producer and consumer of an open format is a stronger position than producer-only.
A bundle (see export spec §3) is a tree of .md files:
<bundle>/
okf.json # optional sidecar (our export writes it): { okf_version, bundle }
index.md # reserved: directory listing, no frontmatter
log.md # reserved: changelog, no frontmatter
concepts/
orders.md # concept doc: YAML frontmatter + markdown body
customers.md
- Concept doc = YAML frontmatter (
typerequired;title/description/resource/tags/timestamprecommended) + markdown body. - Reserved files (
index.md,log.md) and theokf.jsonsidecar are not concept docs. - Foreign bundles may nest arbitrarily, use any
type, carry unknown frontmatter keys, and contain broken links. We MUST tolerate all of it (OKF conformance rule for consumers).
| OKF source | Draftmark target | Notes |
|---|---|---|
| Bundle (directory) | new Collection |
title from index.md H1, else the bundle/dir name |
Each non-reserved .md |
new Doc |
one Doc per concept |
frontmatter type |
Doc.meta.type |
passthrough (inverse of export §5) |
frontmatter title |
Doc.title |
else derive from first H1, else slug |
frontmatter description |
Doc.meta.description |
if present |
frontmatter tags |
Doc.meta.tags |
list passthrough |
frontmatter resource, timestamp |
dropped (or meta.okf_resource) |
provenance; see §9 Q3 |
| markdown body (after frontmatter) | Doc.content |
verbatim |
index.md list entry order + label |
CollectionDoc.position + label |
parse the list; fall back to file order |
log.md |
ignored in v1 | see §10 |
| unknown frontmatter keys | preserved under Doc.meta? or dropped |
see §9 Q2 |
The meta.type / meta.description / meta.tags targets are exactly what the exporter reads (src/lib/okf.ts), so an export→import round trip is lossless for those fields.
Export emits YAML with a hand-rolled double-quoted-scalar writer (no dependency — see export spec). Import must parse YAML, which is materially harder: foreign bundles can use block scalars, flow and block sequences, anchors, comments, multi-line strings, quoted/unquoted forms, etc. Three options:
- Add a small parser dependency (
yaml~= 1 file, no transitive deps, orjs-yaml). Correct against arbitrary YAML; costs the "zero-YAML-dep" stance the codebase has held. Recommended — parsing correctness is not a place to hand-roll, and OKF explicitly invites arbitrary producers. - Hand-roll a constrained parser that recognizes only the frontmatter shapes we care about (top-level
key: scalar,key: [inline, list], andkey:\n - block\n - list) and ignores the rest. Zero dep, but silently mis-reads anything fancier — and "silently mis-reads a foreign file" is a bad failure mode for a consumer whose whole job is tolerance. - Require our own dialect — only import bundles that match our exact emitter output. Trivial to parse, but defeats the point (can't import foreign bundles). Rejected.
This choice gates the feature. If a YAML dependency is unacceptable, import is only worth doing in the narrow round-trip sense (option 3), which is low value. Recommendation: option 1.
Two clean surfaces; they compose:
- A. Server accepts a JSON manifest — the exact inverse of what export produces:
POST /api/v1/collections?format=okfwith body{ okf_version?, bundle?, files: [{ path, content }] }. The server parses frontmatter, creates Docs + a Collection, returns them. Simple, streamable, no binary handling. - B. CLI
dm import <path>— reads a local directory or.tar.gz, builds the{files:[…]}manifest client-side (untarring locally if needed), and POSTs to surface A. Keeps tar/zip reading and filesystem walking out of the server.
v1 ships A + B with directory + manifest; tarball reading in the CLI is a small add (Node has zlib; a minimal tar reader mirrors the hand-rolled writer). Server never touches binaries.
Size limits (mirror the batch-comments cap of 50): reject a manifest over e.g. 100 files or some total-bytes ceiling, with a clear error. Silent truncation is forbidden.
Import creates docs and a collection, so it needs the same stance as POST /docs:
- Anonymous import: allowed; each created Doc + the Collection get fresh
magic_token+api_key, returned in the response (and written to.draftmark.jsonby the CLI). Consistent with token-only doc creation. - Account import: with an
acct_…key, created docs/collection are owned by the account (Doc.userId), like authenticatedPOST /docs. - Visibility: default public (the content arrived in a shareable bundle);
--visibility privaterequires an account (private-doc creation already requires auth — see thePOST /docsgate). One flag, applied to every doc in the bundle.
No existing data is mutated — import is purely additive — so there is no destructive-overwrite risk to gate. Re-importing simply makes a second, independent collection (no dedup in v1, §10).
Bundle concept docs link to sibling members as /concepts/{bundleSlug}.md. On import each concept becomes a Doc with a new Draftmark slug, so those links would dangle. Symmetric to export's rewriting:
- Build a map
bundleFilename → newDocSlugas docs are created. - Rewrite
/concepts/{x}.mdlinks in each body to the new share URL (/share/{newSlug}) or keep them collection-relative. - Same conservatism as export: skip fenced code, skip query strings, only touch known-member targets.
Given it's the mirror of already-shipped, already-tested export rewriting, it's medium effort and probably worth doing in v1 (a bundle whose internal links all dangle on import is a poor experience).
- Rendering: imported markdown flows through the existing safe renderer (react-markdown + rehype-highlight) — no new XSS surface beyond what user-authored docs already have.
- No SSRF: v1 takes file content directly (manifest/local files). No git-URL / remote-fetch surface (that's §10, and would need SSRF review).
- Resource bounds: enforce the file-count / byte cap (§6); consider per-import rate limiting like other write endpoints.
- Frontmatter injection: a malicious
resource/typeis just data landing inDoc.meta; it is never executed. Fine.
- Git-URL ingestion —
POST …?format=okfwith{ git: "https://…" }; server clones. Needs SSRF hardening + a clone sandbox. Large. - Dedup / re-import upsert — detect a previously-imported bundle (e.g. an import id in
Collection.meta) and update in place instead of duplicating. Medium. log.md→ version history — parse the changelog and seedDocVersion.versionNoterows. Small–Medium, low value.- Tarball/zip upload directly to the server — if a browser "upload a bundle" UI is wanted, the server would need a tar reader. Medium.
| Increment | Scope | Estimate |
|---|---|---|
| Frontmatter parse decision + dep | pick option 1, wire yaml |
Quick (gated on approval) |
Server import (POST …?format=okf, JSON manifest) |
parse, create Docs + Collection, order/labels from index.md, size caps |
Medium |
| Link rewriting on import (§8) | mirror export rewriter | Short–Medium |
CLI dm import <dir> |
walk dir → manifest → POST → write .draftmark.json |
Short |
| CLI tarball input | local untar → manifest | Short |
| Tests | round-trip (export→import identity), foreign-bundle tolerance, caps, privacy | Medium |
| Total v1 | Medium–Large (clearly the biggest OKF item) |
- YAML dependency (§5): accept
yaml/js-yaml, or keep zero-dep and scope import to round-trip-only? This is the decision that unblocks everything. - Unknown frontmatter keys: preserve verbatim under
Doc.meta(lossless, but pollutes meta) or keep only the known set (type/description/tags)? Leaning: keep known set, stash the rest undermeta.okf_extrafor round-trip fidelity. resource/timestamp: drop, or retain as provenance inmeta(meta.okf_resource,meta.okf_timestamp)? Leaning: retain — cheap, and useful for "where did this come from".- Round-trip identity target: should
export(import(bundle))be byte-identical for a Draftmark-origin bundle? A useful test oracle if we commit to it; may constrain choices 2–3. - Collection title source:
index.mdH1 vsokf.json/dir name precedence when they disagree.