OKF Workbench

Architecture

Architecture

Goals

Structure

src/
├── core/
│   ├── model/         # serializable host types and migration oracle
│   ├── parser/        # migration oracle and host-side safety preflight
│   ├── validation/    # migration oracle
│   ├── graph/         # migration oracle
│   ├── indexes/       # guarded managed-region merge
│   ├── templates/     # migration oracle
│   └── wasm/          # production Extension Host ABI adapter
├── extension/
│   ├── commands/
│   ├── diagnostics/
│   ├── workspace/
│   └── webview/
└── webview/
    ├── graph/
    ├── details/
    └── state/
templates/
├── bundles/
├── concepts/
└── agents/
test/
├── fixtures/
├── unit/
└── integration/
crates/
├── okf-core/          # capability-free deterministic semantics
├── okf-wasm/          # raw wasm32-unknown-unknown ABI
└── okf-cli/           # native filesystem/terminal adapter

The accepted runtime, build, test, and packaging baseline is documented in Implementation environment, ADR 0004, and ADR 0007. ADR 0008 defines the dual bundled/standalone CLI distribution. npm/esbuild own the extension and Webview package; Cargo owns the deterministic core, portable Wasm artifact, and native CLI.

Implementation baseline

Shared core ports and adapters

VS Code workspace APIs                    local filesystem + terminal
          |                                         |
TypeScript Extension Host                      okf-cli
          |                                         |
versioned JSON / raw Wasm ABI              native Rust calls
          |                                         |
          +--------------- okf-core ----------------+
                 parser / validator / graph /
                 indexes / templates / agents

okf-core accepts bytes and serializable operation inputs and returns serializable results. It does not import filesystem, terminal, editor, Webview, or network capabilities. okf-wasm exports memory allocation/deallocation, ABI metadata, and one request dispatcher; it has no WASI or other imports. The Extension Host validates the ABI version, memory bounds, UTF-8, envelope, and operation result before publication. It loads the packaged module lazily on the first core operation and does not silently switch to the TypeScript migration oracle after a missing module, version mismatch, trap, or malformed response.

The Extension Host continues to own URI-first reads, Workspace Trust, previews, guarded writes, diagnostics, watchers, and the Webview lifecycle. The native CLI owns local path resolution, interactive confirmation, atomic local writes, and stdout/stderr. The Webview receives only the bounded presentation payload and never instantiates Wasm.

Supported target-platform VSIX packages add one native CLI at the distribution boundary, while the universal fallback remains CLI-free. The exact same executable is also shipped in a standalone archive. The extension validates it and exposes it only to new integrated terminals; extension commands do not invoke it.

Core model

interface Concept {
  kind: 'concept';
  id: string;
  source: SourceDocument;
  frontmatter: ParsedFrontmatter;
  type: string;
  title?: string;
  description?: string;
  resource?: string;
  tags: readonly string[];
  timestamp?: string;
  body: string;
  bodyRange: SourceRange;
  links: readonly ConceptLink[];
}

interface ConceptLink {
  sourceId: string;
  rawTarget: string;
  label: string;
  classification: LinkClassification;
  range: SourceRange;
  targetId?: string;
  fragment?: string;
  query?: string;
}

ParsedFrontmatter.raw retains the complete JSON-safe producer map, including unknown fields. Explicit standard YAML tags are represented by the reserved $okf-workbench:yaml-tag object with their canonical tag name, JSON-safe semantic value, and original lexical value source. In particular, YAML timestamps, binary data, and sets never leak Date, Buffer, or Set instances into the model. This object is a serialization representation, not a trust signal: ParsedFrontmatter.explicitTags is a separate sidecar derived only from the YAML AST. A direct explicit tag and an alias resolved to the same real tagged node retain provenance; a producer mapping or sequence that imitates the reserved object does not. Semantic-string compatibility checks accept a plain string or an AST-proven !!str scalar, including its aliases, and reject lookalike mappings, sequences, and values tagged with anything other than !!str. The exact frontmatter source remains separately available for source-preserving writes. Null-prototype mappings and a closed standard-tag converter prevent prototype pollution and fail closed on custom runtime objects. The normalized fields are conveniences, not a closed schema. Link state is represented by the explicit LinkClassification union rather than overlapping boolean flags.

When decoding, frontmatter parsing, or Markdown parsing fails for a non-reserved document, the bundle model still contains a partial Concept. It retains the canonical ID and complete SourceDocument identity (URI, bundle path, and content hash), while frontmatter, type, tags, body, and links use JSON-safe empty sentinels. The accompanying source-scoped ParseFailure is the authoritative diagnostic; validators suppress derivative metadata and orphan findings for that partial concept. Other concepts can still resolve links to its stable identity, so graph cardinality and source-repair navigation do not change merely because one file is temporarily malformed.

Data flow

workspace URIs
-> TypeScript capability adapter
-> Rust core through the versioned Wasm ABI
-> normalized serializable bundle model
-> validator + graph builder + index generator
-> extension messages
-> Webview graph and details panel

Initial load

  1. Resolve an explicit or detected bundle root. Automatic discovery streams exact-name index.md candidates, skips known VCS/dependency/generated trees, and records an unreadable subtree while continuing with its siblings. It is intentionally bounded to 32 workspace roots, 16 relative path segments, 512 candidate indexes, 1 MiB per index, 8 MiB of total index content, and 64 retained failure records. Provider traversal also stops at 10,000 directories or 100,000 entries per root. Any access or safety-limit truncation prevents automatic/current-candidate selection and routes the user to explicit directory selection.
  2. Stream Markdown through the URI-first workspace port. Exclude every .agents/ subtree during traversal and omit a root AGENTS.md from the document inventory because both are project-local agent integration controls rather than OKF concepts; a nested AGENTS.md outside .agents/ remains eligible. Provider-reported relative path segments retain their verbatim identity; they never re-enter the percent-decoding path used to validate user-entered write targets. An unreadable document or child subtree becomes a path- and URI-scoped conformance finding while readable siblings continue through parsing, validation, and graph construction. One load admits at most 2,000 Markdown documents, 2 MiB per document, 32 MiB of reported and actual Markdown bytes, eight concurrent provider calls, 64 path segments, 32 MiB of cumulative traversal identity, and 128 retained failures. A 4 KiB relative path uses a 16 KiB serialized-URI envelope to accommodate percent expansion. One oversized document is retained as a typed identity-only input; aggregate, root, count, depth, and traversal-identity failures remain fatal for that refresh. Already-started provider calls settle before another generation may perform physical I/O. Every materialized byte is charged to the actual-byte aggregate before any later parent-access failure or resource limit discards that document. The runtime retains both the logical bundle root and its containing workspace safety root. Every load, including watcher refreshes, captures the complete URI/provider directory chain between them before traversal and rechecks that same generation after enumeration, after each bounded read batch, and before publication. A missing, symbolic-link, non-directory, or changed-generation segment fails closed and clears all previously derived graph and diagnostic state. Traversal additionally captures the workspace-root-to-current-directory chain for every directory it enters and does not release any readDirectory tuple until both that chain and the traversal-root chain have been revalidated. A nested generation failure is fatal to the complete load rather than an ordinary unreadable subtree. Before every distinct document stat/read batch, the loader captures its complete workspace-root-to-parent chain and sandwiches the file operation with parent and bundle-root checks; safe siblings cannot make a generation failure publishable as a partial result.

    For a local file: URI, stat captures native lstat identity (dev, ino, mode, nanosecond ctime, and birthtime). A later read must present that identity, opens one handle with O_NOFOLLOW where the host defines it, verifies the handle with fstat before reading, reads only through that handle, and compares the handle and pathname generations again before returning bytes. This prevents a transient ancestor or final-file swap from redirecting the read even when the original path is restored before the caller resumes, and the handle is closed on every outcome. A close rejection also rejects otherwise valid bytes; when a read and close both fail, the read failure remains primary and the close failure is attached for diagnosis. Non-file: providers expose no file handle, inode, ETag, or conditional read through VS Code, so they use an explicit trusted-provider metadata sandwich (stat / readFile / stat) plus the directory-generation checks. That branch is a provider trust boundary, not an atomic-snapshot claim.

  3. Parse frontmatter and Markdown links. Every enumerated in-bundle non-reserved input contributes a concept identity; decode or parse failure yields a safe partial concept plus a precise failure.
  4. Resolve concept IDs and internal targets.
  5. Produce diagnostics and a bounded serializable graph payload. A document-scoped failure remains an identity-only sourceFailed node so it is navigable and included in concept cardinality, but it contributes no invented type, tag, outgoing-link, broken-link, or orphan state. Valid siblings may still link to that stable identity. A bundle-scoped resource failure invalidates the complete derived view.

Incremental update

  1. Listen for create, change, delete, and rename events.
  2. Coalesce equal events at insertion time. Retain at most 512 distinct pending paths, collapsing overflow to one rescan. Use a 250 ms trailing debounce with a 1,000 ms maximum latency.
  3. Reparse changed concepts.
  4. Recompute affected outgoing links, backlinks, and diagnostics.
  5. Send a graph patch or replacement payload to the Webview.

Refresh work is strictly single-flight: invalidated work is aborted logically, but a replacement does not start until every already-issued provider promise settles; at most one trailing refresh runs next. The first implementation may replace the full graph payload if profiling shows it is sufficiently fast. Incremental rendering is a performance optimization, not a correctness requirement.

VS Code integration

Webview security

Graph behavior

Graph construction admits at most 2,000 nodes, 10,000 retained internal/broken relationships, 128 tags per concept, 20,000 tag assignments, 512 unique types, and 4,096 unique tags. Edge identities are compact deterministic base-36 ordinals assigned after stable relationship sorting; producer concept IDs are not copied into the edge ID. Producer concept/source/failure/finding identities are bounded to 4 MiB in aggregate before lookup keys are built. Before publication, a streaming escaped-JSON byte counter computes the exact serialization size without creating the JSON string and refuses payloads over 16 MiB. The extension-to-Webview decoder mirrors the same string, count, cardinality, reference, backlink, statistics, and byte limits.

The renderer is the standalone 3d-force-graph package behind a repository-owned adapter, as recorded in ADR 0003. A repository-owned camera controller inside that adapter coordinates OrbitControls, toolbar commands, keyboard commands, search focus, and node focus. It classifies Chromium wheel input conservatively, owns the only custom wheel listener, prevents default scrolling only on the graph surface, and keeps library-specific control objects inside the adapter. It is locally bundled, exposes an accessible non-spatial navigation surface, resumes rendering only for bounded camera interaction windows, stops its animation loop after cooldown, and uses d3 as the checked-in runtime fallback.

Folder hierarchy is derived inside the Webview from the canonical POSIX GraphNode.id; it is not added to the extension/Webview protocol or core semantic model. Folder selection is another presentation filter, while breadcrumbs provide the same action from selected-concept details. The renderer adapter may attach folder-path fields to its private render nodes and install one named custom d3 force for deterministic top-level clusters and weaker nested offsets. It never creates folder nodes or edges, changes type colors, or mutates the received graph payload. The internal ngraph benchmark candidate receives deterministic initial positions instead of a d3 API call.

The former schema-v3 headed comparison selected d3, but the strengthened evidence contract invalidated that selection for release qualification. A fresh full same-Electron comparison must observe real WebGL clears/draws, verify every interaction outcome, and produce a positive coherent camera rate with camera-period graph draws covering every observed clear before selecting the current release engine. Dependency licensing and packaged notices are reviewed separately from the project-license gate. Any renderer or candidate-bundle change invalidates an evidence binding and requires a new evaluation.

Performance targets

Accepted MVP thresholds:

A genuine current-input schema-v3 headed VS Code 1.129.1 run passes QR-002 at 832 ms p95 across 20 samples and QR-003 with d3 selected; its strict CDP envelope records zero remote HTTP(S)/WS or other-scheme requests. The retained VS Code 1.127.0 run predates the current diagnostics-correlation, WebGL/UI-outcome, causal-timing, and network-envelope requirements and remains historical-only. See performance evidence for the exact environment, authority rules, and candidate-binding limits.

Testing strategy

Unit tests

Fixture tests

Extension integration tests

Webview browser tests

Playwright browser results are not used as Electron Webview performance evidence. Performance targets require a headed VS Code or VSCodium benchmark with the environment recorded.