import type { BlockDef, Finding, TypeDef } from './types.js'; /** RFC-4122, any version. Used to WARN, never to reject: a vault migrating in * from another scheme has stable unique ids that are not v4, and uniqueness — * not the byte layout — is what identity actually needs. */ export declare const UUID_RE: RegExp; /** * A fresh identity. `globalThis.crypto` — no import, no dependency: present in * Node ≥ 19 (this package requires ≥ 20) and in Workers, where `node:crypto` * is not. * * THE WRITE PATH IS THE ONLY CALLER. `readSidecar` does not import this * function and must never learn to: a reader that repairs what it reads makes * `dj lint` mutate the vault, and a linter with side effects is one nobody * trusts. A record with no identity is a FINDING (see `sidecar-record-no-uuid` * in parseDoc), not a silent repair. */ export declare function mintUuid(): string; export interface AppendedRecord { /** the whole sidecar, ready to write back */ text: string; /** the identity the appended record now carries — minted here unless the * caller supplied one */ uuid: string; } /** * Append one record to a block's list, minting its identity. * * Pure — text in, text out, exactly like `annotate()` — so the CLI can put the * referee gate around it. `src === null` means "no sidecar yet": a document * getting its first record. * * Written through `parseDocument`/`toString` rather than parse → re-stringify * so the author's existing lines survive byte-for-byte. A write path that * reformats the whole file on first use produces a diff nobody can review, * which is the same discipline `renderRow` and `planIndexUpdates` already hold * themselves to. */ export declare function appendRecord(src: string | null, block: string, record: Record, identity?: string | null): AppendedRecord; /** * Set fields on ONE existing record — the edit half of the sidecar write path, * beside `appendRecord`. `idKey` names the field carrying the DISPLAY id the * caller addresses the record by (a block's id column), never its uuid: forms * target rows the way a reader names them. * * Pure, `parseDocument`/`toString`, exactly like `appendRecord`: every line * the write does not touch survives byte-for-byte, so a one-cell edit is a * one-line diff. Everything it cannot do it refuses loudly — a file that does * not parse, a block the sidecar does not carry, a record that is not there — * because a form write that lands nowhere must say so, not no-op. */ export declare function updateRecord(src: string, block: string, rowId: string, sets: Record, idKey?: string): { text: string; }; /** * THE ROUTING RULE for form writes: a block is sidecar-backed iff the sidecar * text exists AND carries the block as a top-level key — then a write edits * the YAML; otherwise the markdown table, exactly as before sidecars existed. * (A block in BOTH is already a lint error, so the ambiguous case never * reaches a write.) A sidecar that does not parse or is not a mapping answers * `false`: the write then routes to the table, where the referee gate judges * it against the broken sidecar the vault already reports. */ export declare function sidecarHasBlock(src: string | null, block: string): boolean; /** * THE ONE `{auto}` ALLOCATION ANSWER (E40 PR1): the largest TRAILING number * any value in the set carries. Three allocators, three containers, one rule * — `nextId` over table lines (annotate.ts), `maxRecordNumber` over sidecar * YAML (below), and `compileRecordWrite`'s `existingIds` (whatever id values * the host's own store holds, live AND archived) all funnel here, so "max + 1 * over what the container already holds" can never mean three slightly * different things. Anything unreadable contributes nothing: whether a value * is well formed is the validator's judgement, not the allocator's. * * Declared HERE rather than in annotate.ts because annotate.ts already * imports this module — the reverse import would be the engine's first * runtime cycle for the sake of a file preference. */ export declare function maxIdNumber(values: Iterable): number; /** * Every value an `idKey` field carries across a block's records — the sidecar * container's answer to "what ids does this store already hold", which is the * allocation domain `compileRecordWrite` asks its caller for. Keys compare * lowercased, matching how `parseDoc` lifts record keys into cells. A record * without the key contributes '' (which allocates nothing); a file that does * not parse contributes nothing at all — whether it is fit to append to is * `appendRecord`'s judgement, not the reader's. */ export declare function recordIdValues(src: string | null, block: string, idKey: string): string[]; /** * The largest trailing number an `idKey` field carries across a block's * records — the sidecar half of the `{auto}` allocator whose table half is * `nextId` in annotate.ts: ONE rule (max + 1 over what the container already * holds), and since E40 PR1 the rule itself is `maxIdNumber` — this function * is now just the sidecar reader composed with it. */ export declare function maxRecordNumber(src: string | null, block: string, idKey: string): number; /** * Give every record that lacks one an identity — the WRITE half of * `sidecar-record-no-uuid`, and the command that finding's message points at. * * Pure, like `appendRecord`: text in, text out, `parseDocument`/`toString` so * every line the author already wrote survives byte-for-byte and the diff * shows exactly one added `uuid:` line per repaired record. Without this * command, a format adopting `identity:` would put every pre-existing record * at error level with no `dj` command able to clear it — hand-written uuids * being precisely the uncontrolled minting the design exists to prevent. * * `blocks` maps block name → identity key for every block of the type that * declares one. Anything else in the file — unknown keys, non-list values — * is left alone here: judging the sidecar is `readSidecar`'s job, and this * function refuses only what it cannot safely edit (a file that does not * parse, where "appending" could destroy what the author was writing). */ export declare function backfillUuids(src: string, blocks: ReadonlyMap): { text: string; minted: number; }; /** Is this path the UNTRACKED sidecar spelling — `X.records.local.yaml`? * * The filename is law (L6): a `.records.yaml` may carry only canonical-tier * blocks, a `.records.local.yaml` only non-canonical ones, and telling the * two apart needs no git knowledge — the suffix alone says which side of the * commit boundary the records belong on. Every consumer that must exempt the * local mirror from the committed-records refusal (`sidecar-tier-operational`) * asks THIS predicate, so the rule cannot drift into per-site regexes. */ export declare function isLocalSidecarPath(p: string): boolean; /** `Flows/Checkout.md` → `Flows/Checkout.records.local.yaml` — the pairing * rule for the UNTRACKED mirror, beside `sidecarPathFor`'s rule for the * committed file (L6). Two suffixes, one document: the committed sidecar * carries canonical-tier blocks, this one carries everything else, and the * whole-file-per-path seam (bake injection, `dj pull --records`'s wholesale * rewrite) stays collision-free because the two paths are distinct. */ export declare function localSidecarPathFor(docPath: string): string; /** The `.md` twin of EITHER sidecar suffix. One function, because the naive * `replace(/\.records\.yaml$/, '.md')` silently no-ops on the local suffix * — which is exactly how an orphan sweep would report every healthy mirror * as a records file whose document does not exist. Local first: the local * suffix contains the committed one's tail, never the reverse. */ export declare function documentFor(sidecarPath: string): string; /** THE one remedy sentence every materialised-file refusal ends with — * `applyRowPatch`, `dj mint`, serve `PATCH /rows`, and whatever the cloud * adds later. One constant, not four spellings, so the person refused in * any of those places is sent to the same door. */ export declare const LOCAL_SIDECAR_REMEDY = "its records live in the host's working layer and the .records.local.yaml mirror is machine-written \u2014 send the write through the host with `dj rows patch`, then refresh the mirror with `dj pull --records`"; /** * A COMMITTED `.records.local.yaml` is refused by name (`sidecar-local-committed`, * L6). The suffix promises "untracked mirror, wholesale-rewritten on every * pull" — committing one turns the next pull into a working-tree conflict and * puts working-layer records under the referee that must never gate them. * * The predicate the caller owes this function is COMMITTED-NESS, which a pure * engine cannot see: the cloud referee feeds it the pushed tree's sidecar * paths (everything in a pushed tree is committed by construction); `dj lint` * feeds it what `git ls-files` answers. Both callers, one finding. */ export declare function refuseCommittedLocalSidecars(paths: Iterable, findings: Finding[]): void; /** One block's worth of serializable records: the BlockDef supplies the * column order and the identity key; the records supply the values. */ export interface SerializableBlock { block: BlockDef; records: RawRecord[]; } /** * Records → sidecar text: the WRITE half of `readSidecar`, for whole-file * producers — `dj pull --records`'s wholesale mirror rewrite and the cloud's * bake-time materialisation. (Hand-edited committed sidecars keep the * byte-preserving `appendRecord`/`updateRecord` writers; this one owns the * files no hand ever edits, where determinism IS the byte preservation.) * * The layout is fixed so the output is a FIXED POINT of the reader: cells in * the block's declared column order, then any undeclared keys in the record's * own order, identity LAST (the same "fields a person reads lead, the * machine's key trails" rule appendRecord holds). serialize(read(serialize(x))) * === serialize(x), byte for byte — pinned against a linear-port-shaped * 213-record fixture, because a mirror that reshuffles on every pull is a * diff nobody can read even when nobody commits it. * * No blocks at all → `{}`: readSidecar treats an EMPTY file as an error * ("write {} if this document has no records yet"), and this writer must * never produce a file its own reader refuses. */ export declare function serializeSidecar(blocks: Iterable<[string, SerializableBlock]>): string; /** What the `# dj:pull {...}` header line carries: where the mirror came * from, and the per-block `op-` stamps `resolveStamp` falls back to * when a write arrives with no fresher readAt. */ export interface PullHeader { host: string; kb: string; /** lowercased block name → `op-` — the working-layer revision each * block's records were read at */ readAt: Record; /** ISO timestamp of the pull */ at: string; } /** * The pull header of a local sidecar, or null. Null covers everything short * of a well-formed header — no header line, unparseable JSON, missing or * mis-typed fields — because the header is a CACHE of stamps, not a record: * a caller holding null falls back to the next rung of `resolveStamp`'s * ladder, and an error here would make a hand-mangled comment line block * reads of records that are perfectly intact below it. */ export declare function pullHeaderOf(src: string): PullHeader | null; /** * Prepend (or replace) the pull header on a sidecar text. Field order is * spelled out so the line is deterministic regardless of what object the * caller assembled — the mirror is wholesale-rewritten on every pull, and a * header that reorders its keys per pull is a phantom diff on every one. */ export declare function withPullHeader(text: string, header: PullHeader): string; /** A record as it comes off the YAML: column key → scalar, before any of the * block's own rules run. `uuid` is an ordinary field among the rest — the * display id (f15) is a MASK over it, not a replacement for it. */ export type RawRecord = Record; /** * The records a sidecar carries, keyed by block name. * * A `Map`, not an object, so iteration follows the author's YAML order — * determinism is a standing property of this engine, and block order decides * the order findings come out in. * * Returns EMPTY both when there is no sidecar (which is fine: a document may * keep its rows in markdown tables, and both forms coexist during a migration) * and when the sidecar is broken (which is not). The difference is carried * entirely by `findings` — a caller that treats "empty" as "fine" has thrown * away the only signal that says otherwise. */ export declare function readSidecar(sidecarPath: string, src: string | null, typeDef: TypeDef, findings: Finding[]): Map;