Skip to content

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:

bash
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:

CheckMeaning
SCOPEfrontmatter scope: must match the path-implied scope (global/ domains/ projects/) — precedence comes from scope, so scope lies are errors
REVIEW-AFTERpages with a past review_after date are flagged stale (warning; message shows days overdue) — staleness is detected, not forgotten
SYNC-CONFLICTunresolved team-sync conflicts block commit/push
SOURCE-DRIFTa 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 lifecyclereview --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 slugsrepo 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):

json
{
  "$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 ​

FieldTypeMeaning
$schemastringAlways wiki-fabric/context-manifest-v1
taskstringThe task text the manifest was compiled for
pathsstring[]Code-path hints passed via --paths
projectstring | nullPinned project namespace, if any
compileddateCompile date (YYYY-MM-DD)
integrationsobject{graphify: bool, embeddings: bool} — optional-integration state
selectedobject[]Delivered artifacts; see item shape below
excludedobject[]Withheld artifacts: stem, path, reason
precedencestring[]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 query lineage, 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 by wf okf export) and okf.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 -v1 surface. 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 -v1 in place. Consumers parse $schema and 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, and revision (the corpus's git HEAD sha — receipts record which corpus revision they compiled against, so an audit can re-run the exact compile; null when 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/; otherwise registry/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 — $schema value, required fields (receipt_id, task, selected, excluded, precedence, namespace), and filename ↔ receipt_id match.
  • Eval: behavior fixture be5 re-compiles with --write-receipt and 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:

SurfaceSizeConsumerEnters a prompt?
registry/catalog.jsongrows with the corpus (thousands of pages)CI, dashboards, external toolingNo — never
wf context manifestbounded by --max, --project, --pathsthe agent, before writing codeYes — 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 (by type, scope, status) instead of loading it whole; the counts object answers "how much knowledge exists" without reading pages at 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.

markdown
## 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 {...}

Alpha — expect breaking changes.