Machine-Readable Contract
This page is the contract surface other systems consume: CI, agent harnesses, dashboards. For the human-readable workflows, start at Core Workflows.
Everything the fabric validates and catalogs is available in JSON, so CI and agent harnesses can consume it without parsing prose:
wf lint --format json # errors/warnings with code + page + message, ok flag
wf rebuild-index # writes registry/catalog.json (catalog with ids, types, scopes, statuses)The lint report codes are stable: FRONTMATTER, BROKEN-LINK, SCOPE, REVIEW-AFTER, CLAIM, CONCEPT, PATTERN, COMMITMENT, DUP-ID, SOURCE, SOURCE-DRIFT, SOURCE-EMPTY, SYNC-CONFLICT, ORPHAN, LLM-CONFIG, GENERATED, STALE-AFTER, TRUST-TIER, IGNORE-CONFIG, VERIFIED, TYPE, PROPOSED-TYPE, RECEIPT, QUESTION, SLOW-REGION, plus LAYOUT-GUARD (corpus path re-spelled outside scripts/lib/layout.py — enforced at COMMIT time in the harness hook body v6, at test time by the full AST guard), VOCABULARY (a domain: declaration resolving to no ontology domain — the alias map is the binding), IDENTITY (canonical repo-slug collisions error; non-canonical namespace spellings advise), JUDGMENT-GATE (a judged surface refused: no PASS judgment-eval receipt for the current judge identity — G-J, the compiler G4 gate's analog; the deterministic fallback stands loudly), CONTRADICTION-STAMP (a claim mechanically demoted by the newest-wins sweep, #175: contradicted-by: frontmatter stamp + status: contested — restores when the contradictor loses support), SECRETS (a credential-shaped string in a corpus content page, #188 — error tier, blocks the commit gate: rotate, redact, re-ingest; matches are masked in output, evidence/raw/ exempt as the immutable capture plane), plus the OKF-floor OKF-* codes (--okf mode). The rest are structural — FRONTMATTER (malformed metadata), BROKEN-LINK, DUP-ID, SOURCE — and each message names the offending page and field. registry/catalog.json carries every cataloged page with its id, type, scope, status, maturity, review_after, and last_verified — a deterministic, auditable answer to "what knowledge exists and how fresh is it."
Checks the contract enforces:
| Check | Meaning |
|---|---|
SCOPE | frontmatter scope: must match the path-implied scope (global/ domains/ projects/) — precedence comes from scope, so scope lies are errors |
REVIEW-AFTER | pages with a past review_after date are flagged stale (warning; message shows days overdue) — staleness is detected, not forgotten |
SYNC-CONFLICT | unresolved team-sync conflicts block commit/push |
SOURCE-DRIFT | a captured source's sha256 changed without re-ingest (ingest's drift trigger already stamped the old-revision claims contested+stale_after before this reports) |
| source lifecycle | review --verify-sources: fresh ⇒ roll review_after (capture-kind tier: pr/commit 30d, chats 45d, docs 180d), drifted ⇒ stale_after, upstream-gone ⇒ status: expired tombstone (provenance kept, context-excluded; #160) |
| canonical slugs | repo identity = wf_common.project_slug fold (comfyui_mcp ≡ comfyui-mcp); IDENTITY lint; hooks fold WF_SLUG |
The registry
The machine surfaces — each machine-written, human-readable:
registry/effects/ — judged-effect verdicts
wf verify-effects writes <claim-stem>.effects.json here (machine artifacts never sit beside pages — the layout contract). Audit trail of the independent second-opinion verdicts — {claim, project, route, pairs, judged_effects}: the claim's provenance namespace, the route used, the per-pair verdicts, and the contradicts/supersedes map the #186 synthesis prompt and the #175 newest-wins sweep both consume.
registry/promotion-queue.md — the human-maintained promotion checklist
Scaffolded by sync setup/init (idempotent; never overwritten by scripts). Pattern candidates in flight; dossiers link to it. wf sync commit-drift carries it across machines when you update it by hand.
questions/ — promoted open questions
promote-questions --apply writes here — domain-bound questions under domains/<domain>/questions/ (inherited from the source concept's binding). Harvest → promote lifecycle in #88; the plane is seeded by sync setup/init.
registry/wiki-graph.json — the wiki's edges (not its prose)
Written by wf export wiki. The human wiki is OpenWiki-style paraphrase; its prose is for people, and wf query/wf context skip it so models never re-ingest generated narrative (context bloat + drift). The wiki's machine value is its derived citation graph: topic/project → claim edges with staleness tiers + claim provenance — emitted as deterministic JSON. A model consumes the edges and cites a claim for content.
registry/embed-index.json — the optional semantic re-rank index
Written by the embeddings indexer (scripts/lib/embed_index.py) when integrations.embeddings.enabled and a query needs re-ranking: {content_hash, model, n, vectors} — a digest over all claim statements plus one vector per claim. Rebuilt only when the corpus fingerprint changes (the same no-op discipline as the export manifest). Cache hit ≈0.2 s for 500 claims; build ≈1 s. Consumers never require it: wf query ranks lexically + graph-expanded, the tier adds a top-40 fusion boost when active and self-reports in the answer.
evidence/traces/wiki-runs/<id>/.run.json — generation run checkpoint
The wiki-generation writer protocol (wf wiki-generate) checkpoints each run: {id, status, task, total, pages: [{page, title, sections, claims, status, deltas, file, cited, verified}]}. Page completion is the durability boundary — markdown + reconciled claim deltas + citation verification + manifest entry. An interrupted run resumes from this file; completed pages are never redone.
registry/catalog.json — what knowledge exists
Rebuilt by wf rebuild-index from actual files (never hand-edited):
{
"$schema": "wiki-fabric/registry-v1",
"generated": "2026-09-19",
"total": 4506,
"counts": { "claim": 3800, "pattern": 14, "...": 0 },
"pages": [
{
"id": "claim-my-project-retry-backoff-001",
"stem": "claim-my-project-retry-backoff-001",
"path": "evidence/claims/claim-my-project-retry-backoff-001.md",
"type": "claim",
"title": "...",
"scope": "project",
"description": "one-line summary",
"status": "supported",
"confidence": "high",
"last_verified": "2026-09-12"
}
]
}Every entry answers "what knowledge exists and how fresh is it" — the field CI and dashboards consume. --json prints it to stdout for piping.
Design decisions: one catalog, versioned envelopes
One catalog, no per-asset sidecars. The fabric deliberately does not write a JSON manifest next to every asset. catalog.json already carries every retrieval-relevant field a machine consumer needs; per-asset sidecars would triple Git-diff noise and create a second machine representation of each page — a second drift target. The invariant that keeps the registry honest: it must be deletable and rebuildable from canonical files (wf rebuild-index). The moment registry data can't be rebuilt, it has become a hidden source of truth.
Schema versions are the compatibility boundary. Machine surfaces carry their schema in-band: catalog.json writes $schema: wiki-fabric/registry-v1, the task manifest writes wiki-fabric/context-manifest-v1, and persisted context receipts write wiki-fabric/receipt-v1. Consumers bind to the version, not to incidental fields. The one-line rule: Markdown explains, YAML classifies, JSON executes — and JSON carries its version.
Machine surface shapes (v1 contracts)
Harness adapters and CI bind to these field inventories. Each is guarded by a pytest asserting the shape matches this table, so an accidental removal or rename fails CI instead of silently breaking a consumer.
wf context --format json → wiki-fabric/context-manifest-v1
| Field | Type | Meaning |
|---|---|---|
$schema | string | Always wiki-fabric/context-manifest-v1 |
task | string | The task text the manifest was compiled for |
paths | string[] | Code-path hints passed via --paths |
project | string | null | Pinned project namespace, if any |
compiled | date | Compile date (YYYY-MM-DD) |
integrations | object | {graphify: bool, embeddings: bool} — optional-integration state |
selected | object[] | Delivered artifacts; see item shape below |
excluded | object[] | Withheld artifacts: stem, path, reason |
precedence | string[] | Resolution order: ["project", "domain", "global"] |
Selected item shape: id, stem, path, type, scope, reason, priority (P1-project | P2-domain | P3-global), trust_tier (human-reviewed | machine-confirmed | unverified) — plus optional warning, stale_after, title.
Context receipt → wiki-fabric/receipt-v1
The manifest payload plus: receipt_id (filename must match), manifest (wrapped manifest schema), fabric_root, namespace (projects/<p> | registry), revision (corpus git HEAD sha, null outside a repo). Enforced by lint's RECEIPT code.
registry/threads.json → wiki-fabric/threads-v1
Captured chats/PRs are graph nodes (#102): wf rebuild-index derives registry/threads.json from evidence/ alone — chat-session and pr-record nodes (session id, harness, project, files_touched, pr state)
- claim→source provenance edges (
originated_in/decided_in/validated_in) + session-continuation edges (continues). Rebuildable, never hand-edited. Consumers:wf thread,wf querylineage, mining thread signals.
registry/wiki-export-manifest.json → wiki-fabric/wiki-export-manifest-v1
Written by wf export wiki (#112): path → sha256 for every wiki note at export time. The next export's harvest step diffs against it — human edits land as evidence before regeneration. Orphan files (nothing claims them) are tracked. Additive sections (within-version, compatibility policy): inputs: (#187) — {rel_path: {sig, sha}}, each page's generation-input signature carried forward by every rewriter; the next export's delta decision reads it (sig unchanged ⇒ the file stands byte-identical, 0 tokens).
registry/catalog.json → wiki-fabric/registry-v1
generated (date-grain for byte-deterministic rebuilds), total, counts, pages[] (id, stem, path, type, title, scope, optional description + freshness/lifecycle fields). Rebuilt by wf rebuild-index; never hand-edited.
Two linters, two roles
The fabric self-enforces with wf lint — deterministic python rules (SCOPE, CLAIM, SLOW-REGION, source drift, …) that run on every command and locally. Separately, CI cross-checks with okflint — the external OKF validator — using the shipped manifest okf-base.yaml (repo root; packaged into every install). The floor rules agree by design; the external check exists so the fabric's own linter can't drift from the published spec.
What belongs where:
okf-base.yaml— the shipped okflint profile for any bundle; lives in the harness repo, packaged into the tool. Fabric-agnostic.- Per-fabric excludes — two config keys in fabric.yaml, read by code:
okf.export_exclude(paths skipped bywf okf export) andokf.okflint_exclude(extra paths merged into the okflint manifest at validate time). Edit your fabric.yaml — never the shipped base. wf lint(scripts/cmd/lint.py) — the fabric's own rules, including SLOW-REGION and trust gates that okflint knows nothing about.
Compatibility policy
- Additive within a version: new fields may appear; existing fields are never removed, renamed, or retyped within a
-v1surface. A pytest guards the context-manifest shape against silent removal/rename. - Version bump: any removal, rename, or semantic change bumps the suffix (
-v2) — never mutates-v1in place. Consumers parse$schemaand branch. - Ephemeral vs. contractual: optional fields documented above (
warning,stale_after,title) are contractual-when-present. Anything not in these tables is incidental and may change without notice.
--write-receipt: delivery as an auditable artifact
wf context --write-receipt persists the manifest as a context receipt (schema wiki-fabric/receipt-v1) — the per-run record of what the fabric delivered and why. "Provably delivered" stops being a demo claim and becomes a checkable artifact: eval fixtures, attesters, and CI can assert after the fact that a given task received the required knowledge.
- Envelope: the full manifest payload plus
receipt_id,manifest(schema of the wrapped manifest),fabric_root,namespace, andrevision(the corpus's git HEAD sha — receipts record which corpus revision they compiled against, so an audit can re-run the exact compile;nullwhen the corpus isn't a git repo). - Id is content-derived:
receipt-<sha256[:12]>of the manifest payload. Same corpus + task + flags ⇒ same id ⇒ re-running overwrites in place — receipts never accumulate duplicates, and re-runs are byte-identical. - Partitioned by namespace: pinned project →
projects/<p>/receipts/; otherwiseregistry/receipts/. Receipts travel with their namespace in team sync, and no consumer must load "everything" to read one receipt. - stdout discipline: the receipt path goes to stderr; stdout remains the manifest, byte-identical with or without the flag.
- Lint (
RECEIPT): validates the envelope —$schemavalue, required fields (receipt_id,task,selected,excluded,precedence,namespace), and filename ↔receipt_idmatch. - Eval: behavior fixture
be5re-compiles with--write-receiptand asserts the persisted receipt proves delivery (schema, id/filename match, selected set equals the manifest's, required stems present).
Tunable thresholds (tuning:)
Behavioral constants (mining min_projects, cluster thresholds, judgment mining_threshold/near_band, capture: git_history {since, budget} (the activity-bounded window), ingest.budget (bulk-extraction cap), context caps, concept-density gate, promotion: auto_apply/auto_threshold (#190 — the judged-confident auto-apply tier for inbox candidates: OFF by default; the escalation band is auto_threshold − near_band to the floor, derived from the two settings, never constants) read from a tuning: section in fabric.yaml, falling back to shipped defaults. Per-repo: repos.<slug>.git_history (stage routing, graph_dir, git_history are the per-repo keys — never integrations blocks: those are fabric-global; "all" disables the window). Defaults reproduce shipped behavior exactly (no-op test guarded). Three are calibration-sensitive: judgment.mining_threshold (0.8, calibrated on Laya — see issue #39), judgment.near_band, and promotion.auto_threshold (0.90 — #190; overrides apply only with the judge's calibration receipt on record, G-J) — their provenance travels in the issue history; changing them is a policy change, not a tweak. As of G-J, the threshold's validity is EVAL-OWNED: the mining-spread fixtures must still separate around it (ac-judgment-eval), and tuning.judgment.* overrides apply only with a re-calibration on record — the eval, not the preference, owns the number's truth.
Keeping the catalog out of context (bloat guardrails)
The catalog is an index over the whole corpus; the task manifest is a selected subset. Those two surfaces never swap roles:
| Surface | Size | Consumer | Enters a prompt? |
|---|---|---|---|
registry/catalog.json | grows with the corpus (thousands of pages) | CI, dashboards, external tooling | No — never |
wf context manifest | bounded by --max, --project, --paths | the agent, before writing code | Yes — the only fabric JSON meant for model-adjacent use |
The fabric's own runtime never reads the catalog: wf context compiles from page frontmatter directly, so catalog size does not affect task-time context. Guardrails for external consumers:
- Filter, don't ingest. Slice the catalog with
jq(bytype,scope,status) instead of loading it whole; thecountsobject answers "how much knowledge exists" without readingpagesat all. - Descriptions are capped (140 chars) — the catalog is an index, not prose.
- The manifest's excluded list is capped (15 shown, remainder summarized), so a large corpus can't bloat task context either.
Same principle, enforced: per-run context receipts are partitioned under the namespace that produced them (projects/<p>/receipts/ or registry/receipts/) — machine artifacts stay partitioned so no consumer must load "everything" to see "anything".
registry/log.md — the append-only timeline
OKF §9 shape (the OKF bundle standard's log format — see OKF): one ## YYYY-MM-DD heading per day, * **<op> | <subject>** bullets beneath. Eval scripts, imports, hooks, and promotions append; no tooling rewrites past entries (CONTRIBUTING.md: "never rewrite history — the log is the history"). It's also the data source for the compiler-eval gate (why model swaps require re-evaluation — see Model Policy & Evals): promote/mine-promotions scan it for a PASS eval naming the current compiler model.
## 2026-09-19
* **eval-stability | gemma4:e4b-fixed** — Claim recall: 0.89 (threshold 0.8) — PASS
* **okf-import | team-a** — bundle ext-bundle: 12 concepts (quarantine 1), tiers {...}