export type Severity = 'error' | 'warn'; export interface Finding { level: Severity; code: string; file?: string; message: string; } export interface Ref { uri: string; scheme: string; column?: string; syntaxOk: boolean; /** true/false after inventory reconciliation; null when no inventory. */ resolved: boolean | null; } export interface Row { id: string; cells: Record; refs: Ref[]; state?: string; /** stable identity of a sidecar-borne record; absent for a markdown table * row. The display id in `id` is a MASK over this, not a substitute — it * stays an ordinary cell so nothing downstream needs a special case. */ uuid?: string; /** typed ROW references — each value of a `ref:` column that resolved to a * sibling row of THIS file (per-file, like the dangling check that guards * them). Distinct from `refs`, which carries URI-scheme citations. One * entry per resolved value ("t1 · t3" makes two); `uuid` is the TARGET * row's identity when the record carries one, so a consumer that stores * the edge can survive a display-id rename. Absent — never [] — when the * row references nothing. */ rowRefs?: { column: string; block: string; id: string; uuid?: string; }[]; } export interface LinkEdge { src: string; dst: string; label: string; resolved?: boolean; } export interface SectionIR { heading: string | null; /** block name when this section is claimed by a typed block, else null. */ block: string | null; md: string; } export type { DiagramIR, DiagramEdge } from './diagram.js'; export type { MentionIR } from './mentions.js'; export interface ConceptIR { path: string; type: string; title: string; description: string; tags: string[]; domain: string; /** declared per-type frontmatter fields (e.g. app, database, status) — * queryable in dashboards and shown as badges by renderers */ meta: Record; flags: Record; sections: SectionIR[]; blocks: Record; links: LinkEdge[]; /** parsed ```mermaid fences — diagrams as data (kind, nodes, edges) */ diagrams: import('./diagram.js').DiagramIR[]; /** ```dj-records fences — a VIEW over THIS document's own records, resolved * by a renderer at read time. `section` is the heading it appeared under * (null = preamble), exactly as DiagramIR.section works. Only resolvable * embeds land here: an embed naming a block that does not exist, or one * with no records, is a finding instead. */ embeds: { section: string | null; block: string; columns: string[]; }[]; /** ![alt](src) references — media as data. rel = vault-relative resolved * path for local refs (null for remote URLs); resolved = the file exists; * size = bytes on disk when known */ media: { src: string; alt: string; rel: string | null; resolved: boolean | null; size?: number; }[]; /** E37 t2 — person mentions as data, parallel to `links` (design §2.6, * L11): every @handle in prose or cell text plus every person-column * value, each carrying its owner (section for prose; block/row/uuid/ * column for cells). Extraction adds no findings — an unresolved handle * is a render-time state (0020), never a parse-time error. */ mentions: import('./mentions.js').MentionIR[]; } export interface IR { format: { name: string; version: string; okf_version?: string; }; files: string[]; concepts: ConceptIR[]; } export interface Artifact { uri: string; kind?: string; name?: string | null; namespace?: string; in_scope?: boolean; exempt?: string | null; [k: string]: unknown; } export interface Inventory { artifacts: Artifact[]; } export interface DeriveRule { when?: string; then?: string; else?: string; } /** * `about:` — the one thing a definition cannot otherwise say. * * `enum: [chore, spike]` pins a vocabulary; nothing else in this file pins what * "spike" MEANS to this vault, that Kind is not a priority field, or that a * note records WHY rather than what. That knowledge used to be written as a * YAML comment, and the parser strips comments before the loader sees them, so * it reached no consumer at all. The shipped skills teach the format LANGUAGE; * they cannot teach THIS vault's choices, because no skill has read this vault. * * It is prose: the referee cannot check it, it adds no finding, and it changes * nothing about how a document parses. It exists to REACH consumers — `dj * catalog`, and anything downstream that reads a compiled definition — so * absent must stay `undefined` rather than `''`: "no guidance" and "empty * guidance" are different answers to a consumer deciding whether to print it. */ export type About = string | undefined; export interface ColumnDef { /** lowercased header key */ key: string; /** prose about THIS column, kept verbatim (see About) */ about?: About; /** the column name as written in the definition — used when a form write * has to add the (declared but absent) column to a table's header */ nameRaw?: string; idGrammar?: RegExp; /** original grammar source (e.g. "q") — used by forms to mint the next id */ idGrammarSrc?: string; unique?: Severity; sequential?: Severity; required?: boolean; /** the column need not exist in every table of this block — declaring it * optional is how a format says "some tables carry this, some do not" */ optional?: boolean; /** E28 t4 — what the cell HOLDS, declared. Absent means `text`, so a format * that says nothing is unchanged. `number` and `date` are checked where the * record is parsed (parseDoc), refused at error level, with the format's * declared empty markers exempt: "not applicable" is an answer, and it is * what becomes NULL rather than zero in a typed projection column. */ type?: 'number' | 'date' | 'text'; /** E28 t4 — metadata, never validated: the unit every value in this column * shares (`USD/month`). It sits on the column because that is where it is * true; a per-row currency needs a column of its own, and summing across * currencies needs a rate and a date, which is a business rule. */ unit?: string; /** severity code for a `type:` violation — `--type` unless the * column names its own, exactly as enumCode works */ typeCode?: string; enum?: string[]; /** per-value prose from `enum: [{value, about}]` entries, keyed by the * LOWERCASED value so it lines up with `enum` above (and with tones, and * with the enum check). The prose itself is verbatim (see About). Absent — * never `{}` — when no value carries any: the enum's element type stays * `string`, so every consumer that walks the vocabulary is untouched. */ enumAbout?: Record; /** severity for enum misses */ unknown?: Severity; enumCode?: string; refs?: string[]; /** Intra-file pointer at another block's ids. `blocks` is a LIST because the * thing a cell is about need not live in one table: an `About` column serves * a features row in one document type and a tickets row in another, and the * alternative — naming the target in prose because the column may only point * at one block — is exactly the untyped reference this format exists to * remove. `block: features` in YAML still compiles; it arrives here as a * one-element list, so every consumer has ONE shape to handle. */ ref?: { blocks: string[]; column: string; dangling: Severity; code: string; }; /** E38 t1 (ruling P4) — the DECLARED relation this column carries: the * lowercased name of a `relations:` vocabulary entry. The cell is then a * comma-joined uuid LIST addressing records of the relation's `to:` blocks * (L13 — the address must survive renames). The relation binds to THIS * authored column — nothing is synthesized, so the storage key stays the * column's own and declaring an existing column a relation moves no data. * `ref:` is the same idea intra-file and key-addressed; this is `ref:` * grown up — cross-document, uuid-addressed, directed and named. */ relation?: string; } /** E38 t1 (rulings P4–P7) — one entry of the `relations:` VOCABULARY: what a * relation is called, which way it points, and which blocks it may target. * Vocabulary only: a block CARRIES the relation by declaring a column with * `relation: `, which is why `from` is DERIVED here, never authored. * One stored direction (P6): the inverse is a derived READING of the same * edge — never stored, never projected. */ export interface RelationDef { /** the relation's name — lowercase words joined by hyphens */ name: string; /** display label for the forward reading (defaults to the name) */ label: string; /** prose about THIS relation, kept verbatim (see About) */ about?: About; /** directed = the two ends mean different things (blocks / blocked-by); * symmetric = one meaning both ways (relates-to). REQUIRED, no default. */ direction: 'directed' | 'symmetric'; /** the derived reverse reading's name — present iff directed (required * there, refused on symmetric) */ inverse?: string; /** display label for the inverse reading (defaults to the inverse name); * present iff directed */ inverseLabel?: string; /** target blocks — where a relation cell's uuids may point */ to: string[]; /** DERIVED — the blocks that declare a column with `relation: `, * in block declaration order. Never authored (P4). */ from: string[]; } export interface RowCheck { code: string; severity: Severity; when: (ctx: Record) => boolean; message: string; } /** A check over the block AS A WHOLE rather than row by row. Some things are * only true of a set: that a column varies across it, that every row records * something, that it is empty. Context is booleans over all rows, so the * expression language is unchanged. */ export interface BlockCheck extends RowCheck { } /** Declared question wiring: which column points at the target rows and what * "open" means. Replaces the old ref-column/'open'-literal heuristic. */ export interface QuestionsDef { /** lowercased key of the ref: column pointing at the target block(s) */ refColumn: string; /** the ref column's target blocks — whose rows get open_question_about. One * wiring serves every block the ref names: a question about a ticket derives * on the ticket row, a question about a feature on the feature row, and * neither reaches into the other's rows (ids are matched per block). */ targets: string[]; /** compiled open_when — QUERY language over the question row's cells */ open: import('./expr.js').QueryFn; } /** Which LAYER carries a block's records (decision 0021). * * `canonical` rows live in git — markdown tables or `.records.yaml` — and are * what the referee gates. `operational` rows live in the host's working layer * (the DO's op_record store) and reach a checkout only as an untracked * mirror; `conversational` is the same routing for discussion-shaped state * (comments, reactions). The word is a ROUTING declaration, never opaque * metadata: parse, the referee, the write doors and the catalogue all branch * on it. */ export type Tier = 'canonical' | 'operational' | 'conversational'; /** A block that says nothing about its tier is canonical — today's behaviour, * byte for byte. Every consumer asks THIS function rather than reading * `b.tier` raw, so "absent means canonical" is decided in exactly one place * (the loader even normalizes an explicit `tier: canonical` to absence — see * definition.ts — so `'tier' in b` is never a signal anything may branch on). */ export declare function tierOf(b: BlockDef): Tier; /** E36 t6 (ruling L2, design §2.4) — the dj/work BINDING: which of a block's * columns carry the SPINE ROLES (title · state · people · dates). The * binding is grammar, not storage: dj/work owns no table of its own — truth * stays in the host's one operational store, and the host's `work_index` is * a rebuildable witness derived through this declaration. Column values are * stored LOWERCASED (ColumnDef.key), the way every compiled column address * is spelled; identity needs no role because an operational block already * requires `identity: uuid` and the uuid IS the spine's address (L13). */ export interface WorkBinding { /** column key carrying the record's human title — required: the spine has * nothing to call a record without it */ title: string; /** column key carrying the workflow state */ state?: string; /** state values (lowercased) that mean CLOSED — where present, THE * definition of closed for this block (L19): the wipe-report, the org * open-work query and everything after answer from this list, never from * a heuristic */ closedWhen?: string[]; /** E39 t4 (ruling P25) — dj/approvals' NARROWING of the same clause: with * this present, closed means `state ∈ closedWhen` AND the subject's * approvals satisfy this reading (`all` = every reviewer asked has * approved, `any` = at least one has, and zero requests satisfies * neither — approvalsSatisfied in plugins/registry.ts is the ONE * predicate every consumer answers from). Legal only with dj/approvals * installed and a `closedWhen` to narrow; both enforced at load. */ approvedWhen?: 'all' | 'any'; /** column key carrying the assignee(s) — split host-side per person-cell * grammar, so multi-assignee cells index every handle */ people?: string; /** column keys carrying the date roles */ start?: string; due?: string; } export interface BlockDef { name: string; heading: string; /** prose about THIS block, kept verbatim (see About) */ about?: About; /** present ONLY for non-canonical blocks; read it through tierOf() */ tier?: Tier; /** E36 t6 — the dj/work binding (legal only when dj/work is installed AND * tier is operational; both enforced at load). Absent = this block is not * on the spine, and nothing downstream may guess otherwise. */ work?: WorkBinding; idColumn?: string; /** lowercased name of the record field carrying STABLE IDENTITY, declared by * the format (`identity: uuid`). Absent means the block has no identity * field and records are addressed by their display id alone. * * Identity is not display: the value is lifted onto `Row.uuid` and, unless * the format ALSO declares it as a column, kept out of `Row.cells` so no * renderer, facet or explorer panel shows a reader a raw uuid. */ identity?: string; questions?: QuestionsDef; columns: ColumnDef[]; forbid: { key: string; severity: Severity; code: string; }[]; rowChecks: RowCheck[]; /** checks over the whole block (checks[].scope: block) */ blockChecks: BlockCheck[]; emptyTable?: { code: string; severity: Severity; message: string; }; derive?: { state: DeriveRule[]; }; display?: unknown; forms?: FormDef[]; } export interface FormField { name: string; label: string; kind: 'text' | 'textarea' | 'options' | 'image' | 'date' | 'number' | 'rating' | 'section' | 'diagram' | 'concepts' | 'person' | 'record'; required: boolean; /** muted helper text under the label — any kind */ hint?: string; /** kind number: declarative bounds */ min?: number; max?: number; step?: number; /** kind concepts: restrict selectable entities to these types */ types?: string[]; /** kind options: lowercased row-column key holding the proposed choices */ from?: string; /** kind options: lowercased column key whose DECLARED enum is the choice * list. Unlike `from`, the vocabulary is the format's, not the row's — so a * value can be offered before any row uses it. */ fromEnum?: string; /** kind options: choices are the TITLES of documents of this type. The vault * already maintains the set, so it is not duplicated as an enum. */ fromConcepts?: string; /** kind options: separator inside the cell (default ';') */ separator?: string; /** kind options: single (radio) or multi (checkboxes) */ mode?: 'single' | 'multi'; /** kind options: offer an "Other" free-text choice */ other?: boolean; /** kind record (E38 t1, ruling P5): the declared relation whose `to:` * blocks the picker offers records of — the concepts field's shape one * altitude down (rows, not documents). Picking writes the target's UUID. */ relation?: string; /** kind record: pick several targets — the cell is a comma-joined uuid list */ multi?: boolean; } export interface FormDef { name: string; label: string; mode: 'edit' | 'append'; /** edit mode: lowercased column key → value template ({fieldname}, {date}) */ set?: Record; /** append mode: lowercased column key → value template ({fieldname}, {date}, {auto}) */ row?: Record; fields: FormField[]; /** E40 (ruling P3) — the declared-write opt-in on an APPEND form: "any * member may file this". Present ONLY when the format spells `open: true`; * refused at load on a canonical block (`form-open-canonical`) because a * canonical write moves a commit and passes the referee. What capability * the word grants is the HOST's doctrine — the engine only carries it. */ open?: boolean; /** E40 (ruling P3) — the self-service opt-in on an EDIT form: the * lowercased key of the PERSON column whose named person may run this * form. Refused at load on an append form (`form-self-append`) and when * the named column is not `style: person` (`form-self-not-person`). * Proving the actor against the cell is the host's business. */ self?: string; } export interface FeedbackCapture { /** capture kind name, e.g. "comment", "answer", "use-case" */ name: string; on: 'concept' | 'rows'; /** append (default): every submission is a new record — comments, multiple * proposals. replace: ONE record per entity+kind — the single source of * truth; a new submission supersedes the old (same id, status reset). */ submit: 'append' | 'replace'; /** on: rows — which block's rows accept this capture */ block?: string; /** on: concept — restrict to these types (empty = every type) */ types?: string[]; /** "read-only": offer this capture only where the host cannot write * documents. A vault that answers its rows through a block form on a * writable host, and through the ledger on a served page, declares it — * so the reader is never shown two ways to record the same thing. * Absent (the default) means the capture is always offered. */ when?: 'read-only'; label: string; fields: FormField[]; /** reader-side review actions on captured records of this kind: each button * sets the record to a status from the declared vocabulary. Rendered only * while the record still carries the default status, and only where a * feedback endpoint exists. */ review?: { label: string; status: string; }[]; /** card layouts: when a row carries records of this kind, offer a link * beside this column's label (e.g. "View proposal" beside Gap) that opens * the row's comments panel. */ beside?: { column: string; label: string; }; } export interface FeedbackDef { statuses: string[]; default: string; capture: FeedbackCapture[]; } export interface FeedbackRecord { id: string; at: string; kind: string; status: string; target: { concept: string; block?: string; row?: string; }; /** structured by design (0009 q3): concepts fields hold string[] natively */ fields: Record; } export type FieldSpec = { kind: 'string' | 'date' | 'uri' | 'list'; } /** `about` = per-value prose from `{value, about}` entries, keyed by the * LOWERCASED value (values themselves keep their written case — the * frontmatter check compares verbatim). Absent — never `{}` — when no * value carries any. Surfaced by `dj catalog --format` (formatValues). */ | { kind: 'enum'; values: string[]; about?: Record; }; export interface TypeDef { name: string; /** prose about THIS document type, kept verbatim (see About) */ about?: About; required: Record; optional: Record; expectSections: string[]; duplicates: Severity | null; blocks: string[]; flagFromTags: Record; /** Declarative FRONTMATTER forms (E18 t9) — the same FormDef grammar block * forms speak, over the type's declared frontmatter fields instead of a * block's columns. Always mode 'edit' (frontmatter has no row to append), * and `set:` keys are stored in the field's DECLARED spelling, because * frontmatter keys — unlike table headers — are matched verbatim by the * parser. The loader restricts targets to date-kind fields: those are the * timeline's write case, and every other frontmatter field is load-bearing * structure (hierarchy roots, graph edges, facets) that a write surface * must not open by accident. Widening past dates is a future ruling. */ forms?: FormDef[]; } export interface StructuralChecks { 'unclosed-comment': Severity; 'unclosed-fence': Severity; 'comment-hides-heading': Severity; 'multiple-h1': Severity; } export interface CompiledDefinition { format: { name: string; version: string; okf_version?: string; }; ignore: string[]; reserved: string[]; forbidWikilinks: { severity: Severity; code: string; } | null; brokenInternal: Severity | null; tree: { prefix: string; types: string[]; }[]; misplacedSeverity: Severity; schemes: Record; refToken: RegExp; stripTrailing: RegExp; emptyMarkers: Set; stubMarker?: string; /** severity for a `type` the format does not declare. OKF §11 says a * consumer must tolerate one; a profile validating its own vault usually * wants to know. Declared via vault.unknown_type, default error. */ unknownType: Severity; comments: RegExp[]; structural: StructuralChecks; /** validate index.md files (coverage + entry resolution) when vault.index is declared */ indexValidate: boolean; /** shared.colors — value (state, kind, type, tag…) → resolved CSS color; renderers * consult this first, then builtin semantic defaults, then a palette fallback */ colors: Record; blocks: Record; types: Record; /** E38 t1 — the relation VOCABULARY this definition declares (rulings * P4–P7), keyed by relation name in declaration order. ALWAYS present: a * format with no `relations:` line compiles to an empty Map — the * zero-consumers branch every pre-relations format rides, so nothing * downstream branches on the key's existence. */ relations: Map; /** E36 t1 — the plugins this definition installs, resolved against the * engine registry (plugins/compile.ts). ALWAYS present: a format with no * `plugins:` line compiles to `[]`, the zero-consumers branch every * pre-plugin format rides — engineViews returns nothing, no plugin block * joins `blocks`, and no widget kind gains availability. Plugin-contributed * blocks already sit inside `blocks` (compiled by the same loop), so this * list is the INSTALL fact, not a second block store. */ plugins: import('./plugins/compile.js').InstalledPlugin[]; feedback?: FeedbackDef; /** vault.media.embed_max_kb — renderers embed local media up to this size */ mediaEmbedMaxKb: number; /** vault.media.missing — severity when a local ref resolves to nothing */ mediaMissingSeverity: Severity; /** severity for a cited URI the inventory does not contain (vault.evidence.broken) */ evidenceBrokenSeverity: Severity; /** vault.media.prefer_remote_kb — local files above this size warn toward * cloud storage (dj media upload); defaults to embed_max_kb */ mediaPreferRemoteKb: number; } export interface EngineResult { ir: IR; findings: Finding[]; }