Claude Cursor Skill

skmtc-architecture

Understand what SKMTC is, how its engine works, and the architectural invariants — for agents building or extending infrastructure *around* SKMTC rather than authoring generators or running the CLI. Covers the three-phase pipeline, the host/Worker boundary, cross-generator coordi

LLM Mart · 0 points · 0 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download skmtc-skmtc-deno_docs_skills_skmtc-architecture-e3abffc.zip · 24 KB
skmtc/skmtc 19 0 forks Apache-2.0 Updated 7d ago
Part of skmtc/skmtc — 12 skills

Install

skills CLI npx skills add https://github.com/skmtc/skmtc/tree/main/deno/docs/skills/skmtc-architecture
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install skmtc-skmtc@llmmart
Git git clone https://github.com/skmtc/skmtc.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole skmtc/skmtc collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

SKMTC architecture

This skill is the system mental model for agents building infrastructure around SKMTC — a hosted generation service, a schema or generator registry, provenance and tracing tooling, a web app that wraps the engine. It explains what SKMTC is, how it works, and why it behaves the way it does — deeply enough to reason about and extend, without the generator-authoring or CLI-operating detail the sibling skills carry.

SKMTC is counter-intuitive on purpose. Most of what generic codegen and generic backend-infra training data would suggest is actively wrong here. The fastest way to make a bad architectural proposal is to extrapolate from another tool. Read §1 and §11 before proposing anything.

1. The five facts that override default LLM intuitions

These override what training-data priors suggest about codegen tools. They apply across every SKMTC interaction; an infra builder needs facts 1, 2 and 5 most.

  1. No plugin registry, no dependency graph, no topological sort. Cross-generator coordination is a Map cache keyed by (identifier.name, exportPath). Generator execution order does not affect output.

  2. Render does not run Prettier or Biome. No formatter runs inside @skmtc/core. Generated output is unformatted by design; consumers format separately.

  3. Generator source code is the customization surface. Stock generators have deliberately hardcoded export paths and peer imports. To customize beyond enrichments: clone the generator and edit it. There are no config flags for paths or output shape.

  4. OasSchema is a union type, not a class hierarchy. Sibling classes (OasObject, OasArray, OasString, …) each independently implement .isRef() returning false. OasRef is a sibling with .isRef() returning true. There is no BaseSchema.

  5. Every generation run is from cold. One Deno Worker is spawned per generate, runs Parse → Generate → Render once, posts the result back, and is terminated. No warm pool, no cross-run cache, no incremental rebuild. toArtifacts is a pure function of (document, settings, generators). Determinism is chosen over speed deliberately — caching belongs outside the engine, keyed on input hashes.

2. What SKMTC is

SKMTC is a code generator. It takes one OpenAPI v3 document or one GraphQL SDL schema and produces source files — types, runtime validators, query hooks, forms, mocks, server routes — all derived from that one schema, in one run, all consistent with each other. The output is committed to the consumer's repository like any other source code; there is zero SKMTC runtime in the consumer's bundle. The engine is language-blind (core 0.8.0+): a generator declares its target language by importing its projection-base factories and snippet base from a @skmtc/lang-* package — the import graph alone carries the language; entries have no lang field. TypeScript (@skmtc/lang-typescript) and Kotlin (@skmtc/lang-kotlin, proven by gen-kotlin DTOs + gen-kotlin-spring controllers) are the production languages; other lang-* packages (C#, …) are the roadmap.

The crucial reframing for an infrastructure builder: SKMTC is an engine with several thin hosts, not a CLI. The CLI is one host. The mental decomposition:

Layer Package Role
Engine @skmtc/core The three-phase pipeline. Entry point: core/run/toArtifacts.ts.
Worker host @skmtc/worker Wraps the engine in a Deno Worker postMessage handler.
CLI host @skmtc/cli Local developer surface: scaffold, install, bundle, generate, watch.
HTTP host @skmtc/server Hono app — the hosted "Sandbox API". POST /artifacts.
MCP host @skmtc/mcp Model Context Protocol server surface.
Schema normalizer @skmtc/convert, @skmtc/openapi-down-convert Swagger 2 / OAS 3.1 → OAS 3.0. Runs before the engine.
Generators @skmtc/gen-* The actual codegen logic, distributed as JSR packages.

Every host does the same thing: get a schema, call toArtifacts, do something with { artifacts, manifest }. A SaaS is just another host of the same engine.

3. What you get — the benefits

  • Multi-artifact coherence. One schema yields N artifact types (types + validators + hooks + forms + mocks + routes) that reference each other correctly. Add a field, regenerate, every artifact updates consistently.
  • Output is committed source code. Reviewable in git diff, grep-able, refactorable. No runtime library, no peer-dependency package at deploy time. Schema/output drift is visible in version control.
  • Determinism. Same inputs → byte-identical output. No hidden state, no order dependence, no warm-cache effects.
  • Idempotency by construction. Generators coordinate by memoization, not by a dependency graph — so they can be written, tested, and reasoned about in isolation, and order never matters.
  • Clone-to-customize. Generators are owned source code, not opaque configured dependencies (the shadcn/ui model).
  • Lenient input, strict diagnostics. One malformed schema does not kill the run; it is logged and its dependents pruned. The manifest.json is an exhaustive record even when output is partial.
  • OAS and GraphQL through one engine. The GraphQL pipeline reuses the same DSL, the same renderer, the same manifest.

4. When to use SKMTC — and when not to

Verdict Situation
Strong fit An OpenAPI v3 or GraphQL schema is the contract; you need multiple artifact types from it; you want generated code committed to the repo.
Overkill You only need types (openapi-typescript); you only need a typed fetch client (@hey-api/openapi-ts); schemas are dynamic at runtime (use a runtime renderer).
Wrong tool Can't run Deno and won't use the hosted Sandbox API; you need production output in a language with no @skmtc/lang-* package yet (today that is everything except TypeScript — use openapi-generator).

SKMTC's closest peer is kubb — multi-target, TypeScript-native. The distinguishing bet is the customization model: clones (source you own) over plugins (configured packages), plus coordination by name (memoization) over explicit composition. Full landscape: explanation/comparison-to-other-tools.md.

5. How it works — the pipeline

A generation run is a one-way pipeline of three phases, each producing an immutable artifact the next consumes:

        ┌─────────────── HOST PROCESS ───────────────┐
        │  bootstrap · fetch schema · OAS pre-parse   │
        └──────────────────────┬──────────────────────┘
                               │  postMessage(GENERATE)
        ┌──────────────────────▼──────────────────────┐
        │        DENO WORKER  (sandboxed)             │
        │   PARSE  ─▶  GENERATE  ─▶  RENDER            │
        │   model     files map      { path: text }   │
        │   +issues   (in memory)    artifacts        │
        └──────────────────────┬──────────────────────┘
                               │  postMessage(RESULT)
        ┌──────────────────────▼──────────────────────┐
        │  HOST: write files to disk · write manifest │
        └─────────────────────────────────────────────┘
  • Parse — schema → typed object model (OasDocument / GqlDocument). Lenient: a per-item parser that throws becomes a ParseIssue; removeErroredItems then prunes one hop of $ref consumers of any failed component. Generate can trust every surviving item.
  • Generate — walk the configured generators over the parsed document, producing an in-memory Map<path, File>. Generators produce output via side effects (register / insert*), never by return value. Memoized Drivers dedupe and coordinate (§6).
  • Render — serialize each File to a string by joining re-exports, imports, and definitions. No formatter runs. Output is Record<path, content>.

Each phase boundary is an immutable hand-off — the next phase reads, never mutates. Detail: concepts/the-three-phases.md.

The host / Worker boundary

The engine physically runs in a Deno Worker spawned by the host, one per run (fact 5). The boundary is postMessage, which uses the structured-clone algorithm — and that shapes a real asymmetry:

  • OAS is parsed host-side. A plain OpenAPIV3.Document is JSON — it survives structured clone. The host normalizes Swagger 2 / OAS 3.1 to 3.0 via @skmtc/convert, then posts the plain document.
  • GraphQL is parsed Worker-side. A parsed GqlDocument has class instances with cyclic back-references — structured clone strips methods and prototypes. So the host posts the raw SDL string and the Worker parses it.

The Worker boundary is also the security boundary. The Worker is spawned with Deno permissions read: true, write: true, env: true, net: false, run: false. Generator code (third-party JSR packages or team-edited clones) cannot make network calls or spawn subprocesses. It is a soft sandbox — it limits the blast radius of a buggy or compromised generator, not a determined attacker. Schema fetching happens host-side, before the Worker exists. Detail: concepts/the-worker-runtime.md, explanation/security-model.md.

The engine entry point

Every host calls one function:

toArtifacts({
  traceId, spanId, startAt,          // run correlation
  document,                          // SkmtcDocumentInput (oas | gql)
  settings,                          // ClientSettings (basePath, enrichments, skip, include)
  toGeneratorConfigMap,              // () => the registered generators
  stackTrail,                        // position/trace stack
  silent,
  attribution                        // optional — turns on gen-maps (§8)
}): { artifacts: Record<string,string>; manifest: ManifestContent;
      sidecars?; generationMap? }

It is pure with respect to its inputs and does no I/O of its own beyond reading. core/run/toArtifacts.ts, worker/mod.ts, and server/src/createServer.ts are three independent callers of it — study them as the templates for a fourth.

6. How it works — cross-generator coordination

This is the single most counter-intuitive piece. There is no dependency graph and no topological sort. Coordination is memoization:

  • A generator that needs a peer's output calls this.insertOperation(PeerProjection, op) (or insertModel / insertNormalizedModel).
  • A Driver (OasOperationDriver, GqlOperationDriver, ModelDriver) computes a cache key (identifier.name, exportPath) — both pure functions of (operation, enrichments, variant, options) via the peer's static methods. options is typed data the caller passes on the insert; a peer that uses it folds it into the name.
  • Cache hit → the existing Definition is reused, after an integrity check (generator key, class, and the caller's options — a hit built with different options throws). Miss → the peer's Projection is constructed (which may recurse), wrapped in a Definition, registered, and its import stitched into the calling file.

Because the key is a pure function of inputs, whichever generator asks first triggers construction and everyone after gets a cache hit — so the output Map<path, File> is identical regardless of generator order. A generatorKey integrity check throws "Registered definition mismatch" if two different generator-and-input pairs collide on one cache key.

Why this matters for infra: order-independence means there is nothing to schedule or sequence. Determinism means the correct caching layer is outside the engine — cache the whole { artifacts, manifest } result keyed on a hash of (schema, settings, bundle). Never try to cache inside a run. Detail: concepts/cross-generator-coordination.md.

7. The DSL in one screen

You do not need to author generators to build infrastructure, but you should recognize the vocabulary (defer authoring to skmtc-generator):

  • A generator is a JSR package exporting an entry built with toOasOperationEntry / toGqlOperationEntry / toModelEntry. Entries are pure pipeline config — no lang field; the generator declares its target language by importing its projection bases from a @skmtc/lang-* package, and the engine's Drivers read it off the projection class's inherited static.
  • The entry's transform hook runs once per matched operation/model and produces output by calling register / insert* — its return value is discarded.
  • A Projection is a named, file-scope artifact (export const X = …), wrapped in a Definition, cached by (name, exportPath), reachable by other generators. A Snippet is an anonymous fragment embedded into a Projection's body via template-literal interpolation.
  • Templates are TypeScript template literals inside classes — not .hbs/.mustache files. Composition is by ${...} interpolation of anything Stringable. (Generators are authored in TypeScript/Deno regardless of the target language they emit.)

Vocabulary discipline: in SKMTC prose use register, insert, render. Avoid emit, dispatch, stitch — they map to no exported surface. See reference/glossary.md.

8. The manifest — the run contract and tracing

Every run writes a manifest.json (to .skmtc/<project>/.settings/manifest.json for the CLI host; returned in-band for the HTTP host). It is the canonical record of a run — written always, even on failure. The terminal output is a summary; the manifest is the full story.

type ManifestContent = {
  deploymentId: string      // the run; from DENO_DEPLOYMENT_ID or a timestamp
  traceId: string           // OpenTelemetry-shaped correlation key
  spanId: string            // sub-span within the run
  region?: string           // set only for hosted (Deno Deploy) runs
  files:    Record<path, { lines, characters, destinationPath }>
  results:  ResultsItem     // nested tree of per-(generator × item) outcomes
  previews, mappings        // optional UI metadata (per-Definition source descriptors)
  parseIssues: ParseIssue[] // every Parse-phase diagnostic
  startAt, endAt: number    // unix ms; endAt - startAt = worker wall time
}

What an infra builder must internalize:

  • Tracing is built in. traceId / spanId are OpenTelemetry-shaped. The StackTrail carries them as its root frames, and the results tree is keyed by stack-trail strings — so an outcome is addressable as traceId → spanId → generate → generatorId → item. deploymentId and region come from Deno Deploy env vars on the hosted path. Wire these straight into an observability backend.
  • Exit / status derives from parseIssues, not from throws. The CLI returns exit 1 iff any parseIssue.level === 'error'. The engine fails open — a bad schema does not throw, it logs. Your HTTP host must compute status from manifest.parseIssues, not from a try/catch around toArtifacts.
  • results outcomes: success means "the transform ran without throwing" — not "produced output". notSupported is normal (a generator that doesn't apply to an item). skipped is filter exclusion. Diagnose "no output" via the files map, not results.
  • No history. The on-disk manifest is overwritten every run. If you want run history, persist each manifest yourself — it is already the right per-run telemetry payload.

Detail: concepts/the-manifest.md, reference/manifest-format.md.

9. Provenance — the attribution / gen-maps subsystem

core/anchors/ is the provenance layer. Capture is always on; emission is opt-in: pass attribution: { postPass: {...} } to toArtifacts (the engine-level AttributionState has no enabled field — the client.json#settings.anchors.enabled switch is the user-facing toggle). When emission is on, the run produces two extra artifact types alongside the code:

  • Sidecar (<file>.skm.json) — one per generated file. A pooled, position-indexed map: byte ranges in the rendered file → attribution tuples { genId, srcPtr, variant, defName }, plus generator version and source registry. It is a source map for provenance — it answers "which generator, which schema location, which variant produced this span of code?". srcPtr is a schema pointer like oas:#/components/schemas/User.
  • Generation map (_map.ndjson) — a project-level, per-Definition reverse-query index: "which files came from refName User?", "which files did gen-zod produce?". Wholly rewritten each run.

Both live under .skmtc/<project>/.maps/ (gitignored by default). Mechanism: SnippetBase instrumentation caches each producer's rendered text; a post-pass walks the producer tree and AST-resolves byte spans to landmarks. The AST parser (oxc-parser) does not bundle into a Worker, so inside the Worker landmark names come from Definition identifiers and a host-side post-pass can fill AST detail later.

A lighter, always-on channel exists too: the manifest's previews and mappings pair a per-Definition module with a source descriptor ({ generatorId, operationPath, operationMethod } etc.) — enough for a UI to say "this form was generated from POST /contacts".

If you are building provenance or "trace generated code back to its schema" tooling, build on this subsystem (Sidecar, GenerationMapEntry, AttributionState — exported from @skmtc/core/Anchors). Do not reinvent it.

Full treatment — the four-stage mechanism, the Sidecar v2 format, the worker-side parser omission, the doctor checks: attribution-and-gen-maps.md.

10. The package graph and dependencies

The @skmtc/* packages

@skmtc/openapi-down-convert   (OAS 3.1 → 3.0, vendored fork)
        ▲
@skmtc/convert                (Swagger 2 / 3.1 → 3.0; YAML/JSON parse)
        ▲
@skmtc/core ◀───────────────── the engine; depended on by everything
        ▲   ▲   ▲   ▲
        │   │   │   └── @skmtc/gen-*   (generators; + peer generators)
        │   │   └────── @skmtc/server  (Hono HTTP host)
        │   └────────── @skmtc/worker  (Deno Worker host)
        └────────────── @skmtc/cli ──▶ @skmtc/mcp (MCP host wraps the CLI)

cli also depends on convert and worker. Exact versions live in each package's deno.json — treat that as canonical, not this skill.

The substrate

SKMTC's design principle "build on the substrate, don't rebuild it" means Deno is the platform:

  • deno bundle is the bundler — it compiles a project's worker.ts into bundle.js.
  • new Worker(...) with deno.permissions is the sandbox.
  • JSR is the package registry. Generators and packages are ordinary JSR packages — there is no bespoke registry layer (that was explicitly rejected). Two registries are in play: jsr.io (public) and jsr.skmtc.dev (the SKMTC private registry).
  • The hosted Sandbox API runs on Deno Deploy (hence DENO_DEPLOYMENT_ID / DENO_REGION flowing into the manifest).

The project is Deno-locked, and that is an accepted trade. The generated output, however, runs anywhere TypeScript runs.

Key third-party dependencies

Dependency Used for
valibot Runtime validation of the manifest, settings, parse issues, generator configs, and sidecars. Each schema is paired with a TS type via an unread _driftCheck binding — do not delete those.
graphql GraphQL SDL parsing (Worker-side).
oxc-parser AST parsing for the attribution post-pass (chosen over tsc because tsc won't bundle into a Worker).
openapi-types Type definitions for OpenAPI documents.
hono The @skmtc/server HTTP framework.
@modelcontextprotocol/sdk The @skmtc/mcp server.
swagger2openapi Swagger 2.0 → OpenAPI 3.0 conversion inside @skmtc/convert.
@cliffy/command, ink, react CLI command parsing and terminal UI.

Version-pin discipline: inter-package @skmtc/* dependencies are pinned to exact JSR versions — no caret ranges — so a cloned generator and the engine it compiles against can't silently skew. skmtc doctor checks that a project's @skmtc/core pin matches the CLI's. Pins can lag between packages; always read deno.json.

11. Building infrastructure around SKMTC

The user's context: a GitHub-like SaaS for hosting APIs and generators, running them, and supporting tracing and provenance. Here is how the engine's concepts map onto that platform — and where the engine stops and your platform code begins.

The integration map

Platform concern What the engine gives you What you build
Hosting APIs (schemas) A schema enters toArtifacts as SkmtcDocumentInput — { type:'oas', value: OpenAPIV3.Document } or { type:'gql', value: sdlString }. @skmtc/convert normalizes Swagger 2 / OAS 3.1 to 3.0 before the engine. Schema storage, versioning, ingest validation, the normalize-on-ingest-or-on-run decision.
Hosting generators Generators are ordinary JSR packages (@skmtc/gen-*). jsr.skmtc.dev is already a JSR-compatible private registry. A registry UX; discovery, search, featured ranking; the publish pipeline.
Running generators A run needs a bundle — worker.ts (templated from the import map) compiled to bundle.js. toArtifacts then executes it. Three reference hosts exist: local Worker (CLI), Hono POST /artifacts (@skmtc/server), the Worker message protocol (@skmtc/worker). The run service: validate → convert → toArtifacts → return/store { artifacts, manifest }. Bundle build & cache. Execution pooling if you need throughput.
Tracing traceId / spanId / deploymentId / region already populate the manifest and StackTrail; the results tree is trace-addressable. Shipping them to an observability backend; cross-run dashboards.
Provenance The attribution / gen-maps subsystem (§8): sidecars + generationMap, opt-in via attribution. Persisting and serving them; a viewer that maps generated code ↔ schema ↔ generator version.

@skmtc/server is the seed of the SaaS

server/src/createServer.ts is ~150 lines: a Hono app with POST /artifacts (validate a discriminated body → convert → toArtifacts → { artifacts, manifest }), GET /generators, and POST /to-v3-json. A production run service is this pattern with auth, tenancy, persistence, and bundle management added around it. The MCP host already calls a deployed instance of this server.

Where the engine stops — build these at the platform layer

The engine deliberately does schema in → artifacts out and nothing else. It has no notion of:

  • Users, identity, auth, multi-tenancy. A SKMTC "project" is a generator configuration, not a tenant. Tenancy is entirely yours to build.
  • Persistence or run history. toArtifacts returns a value; the on-disk manifest.json is overwritten every run.
  • Result caching. Determinism makes this safe and easy — cache { artifacts, manifest } by a hash of (schema, settings, bundle) — but it is your layer, never the engine's.
  • A warm execution pool, rate limiting, quotas, streaming. Fact 5: one cold Worker per run. Pool at the container/process level if you need throughput; never share a context across runs.
  • A plugin / hook API. There isn't one and it is a rejected design. Extension happens by cloning generators or by writing a new host — not by hooking the engine.

12. Counter-intuitive facts for infrastructure builders

Beyond the five facts in §1. Left column = a reflex from generic backend / platform training data; right column = SKMTC reality.

Infra reflex SKMTC reality
Keep a warm worker pool for throughput One Worker per run, spawned cold, terminated after RESULT. Determinism depends on fresh contexts. Pool containers, not engine contexts.
Do incremental builds — only regenerate changed operations There are none. Every run is whole-document and from cold. Cache the whole result by input hash instead.
Retry a failed run Runs are deterministic — a retry reproduces the same failure exactly. Fix the input; don't retry.
Wrap toArtifacts in try/catch and 500 on throw It fails open — a bad schema logs a ParseIssue, it does not throw. Derive status from manifest.parseIssues, not from exceptions.
Stream artifacts for large outputs The Worker batch-postMessages the whole result. No streaming protocol; structured clone handles typical sizes.
Format the output before returning it Output is unformatted by design. Formatting is the consumer's separate step.
Let generators fetch schemas / templates at run time The Worker has net: false. Everything a generator needs must already be in its inputs. Fetching is host-side, pre-engine.
The on-disk manifest.json is the run record It is overwritten every run. Persist each manifest yourself for history.
Add an engine plugin/hook API for the platform Rejected design. Extend by cloning generators or adding a host.
A "project" is a tenant / a customer A project is a generator configuration. It is not a unit of tenancy, identity, or billing.
Generated code needs the SKMTC runtime at deploy time Zero runtime. Output is plain committed source; the engine never ships to the consumer's bundle.

13. Boundaries with other skills

  • skmtc-generator — authoring and editing generators (Projections, Snippets, the DSL, customization seams). Load when writing generator code.
  • skmtc-cli — running the CLI, configuring client.json, enrichments, skip/include. Load when operating SKMTC.
  • skmtc-debug — diagnosing broken runs (no output, wrong output, errors). Verify-first stance. Load when something is broken.
  • skmtc-retro — end-of-session reflection / friction capture.
  • This skill (skmtc-architecture) — the system mental model for reasoning about and building infrastructure around the engine.

If the question is how the system works or how to build a service around it, this skill. If it is how to write a generator, how to run a command, or why a run is broken, hand off.

14. Cross-references

Concepts — the-three-phases.md · the-worker-runtime.md · the-manifest.md · stack-trail.md (reference) · cross-generator-coordination.md · generators-as-packages.md · attribution-and-gen-maps.md

Explanation — design-philosophy.md · security-model.md · comparison-to-other-tools.md · status-and-roadmap.md

Reference — glossary.md · manifest-format.md · llms.md (consolidated operational reference)

Source landmarks — core/run/toArtifacts.ts (engine entry) · worker/mod.ts (Worker host) · server/src/createServer.ts (HTTP host) · core/context/ (the three context classes) · core/anchors/ (attribution / gen-maps).

Files (skmtc)
  • attribution-and-gen-maps.md 23.9 KB
    # Attribution and gen-maps
    
    > SKMTC's **provenance** subsystem. When emission is enabled, a
    > generation run produces — alongside the code — a **sidecar** per
    > file that maps byte ranges in the generated output back to the
    > generator, schema location, and variant that produced them, plus a
    > project-level **generation map** for reverse queries ("which files
    > came from `User`?", "which files did `gen-zod` produce?"). It is a
    > source map for provenance: generated code ↔ schema position ↔
    > generator. It lives in `core/anchors/`. Capture is always on and
    > cheap; emission of the on-disk artifacts is opt-in and off by
    > default.
    
    This page explains what the subsystem produces, why it exists, how
    the four stages of the mechanism work, and the format of the
    artifacts. For the engine pipeline it sits inside, see
    [the-three-phases.md](../../concepts/the-three-phases.md). For the run record it
    runs *alongside* (but is not part of), see
    [the-manifest.md](../../concepts/the-manifest.md).
    
    ## The one-line definition
    
    **Attribution** is the act of deciding, for a span of generated
    text, *which generator, which schema location, and which variant
    produced it*. **Gen-maps** are the two on-disk artifacts that record
    those decisions: a per-file **sidecar** and a per-project
    **generation map**. Attribution *capture* is always on and
    unconditional; only **emission** of the on-disk artifacts is opt-in
    (supply a `postPass` config to the run).
    
    ## Why it exists
    
    SKMTC turns one schema into many files through many generators. Once
    the output is on disk it is just TypeScript — nothing in the
    generated text says *"this `z.string()` came from
    `#/components/schemas/User/properties/email`, rendered by
    `@skmtc/gen-zod`, in the `main` variant."* That linkage is lost the
    moment the file is written.
    
    Attribution preserves it. With gen-maps a tool can answer questions
    that are otherwise un-answerable without re-running generation:
    
    - **Forward** — "this line of generated code: where did it come
      from?" (sidecar: byte range → schema pointer + generator).
    - **Reverse** — "I changed `User` in the schema: which generated
      files are affected?" (generation map: schema/refName → files).
    - **By generator** — "which files did `gen-shadcn-form` produce?"
      (generation map: generator id → files).
    
    The intended consumers are **downstream tooling** — a provenance
    viewer, an IDE extension that shows schema-origin on hover, a
    "jump to schema" command, an impact-analysis check in CI. The
    engine produces the data; it does not ship a viewer.
    
    This is the heavyweight, byte-level provenance channel. A
    lighter-weight, always-on, per-`Definition` channel also exists —
    the manifest's `previews` and `mappings` — see [Relationship to the
    manifest](#relationship-to-the-manifests-previews-and-mappings)
    below.
    
    ## The two artifacts
    
    ### The sidecar — one per generated file
    
    A **sidecar** is a JSON document carried alongside each generated
    source file, named `<filePath>.skm.json`. It records, for that file,
    every **anchor** — a byte range plus the attribution of whatever
    producer contributed it. A producer is a `Snippet` or `Definition`;
    a file's Definitions and the Snippets nested inside them each
    contribute one anchor. Sidecar granularity is therefore *byte
    range* — finer than a whole file, finer than a whole Definition.
    
    ### The generation map — one per project
    
    The **generation map** (`_map.ndjson`) is a project-level
    newline-delimited-JSON index with **one row per Definition**
    (landmark), not per anchor. Each row pairs a generated artifact with
    the schema location, generator, and variant it came from. It is the
    reverse-query index; it is wholly rewritten every run (stale rows
    would mislead a viewer).
    
    The relationship: the generation map is a *projection* of the
    sidecars — `entriesForSidecar` (`core/anchors/generationMap.ts:55`)
    extracts one row per landmark from each sidecar, and the run
    concatenates them.
    
    ## Turning it on: `AttributionState` and `client.json`
    
    Capture is **always on** and needs no configuration — it is intrinsic
    to the pipeline (see Stages 1–2 below). What you opt into is
    **emission**: the on-disk sidecars and generation map.
    
    The engine-level config is `AttributionState`
    (`core/types/AttributionState.ts`), threaded into `toArtifacts`. It
    has a single field — the post-pass config:
    
    ```ts
    type AttributionState = {
      postPass?: {
        parser?: ParserAdapter          // AST landmark/path resolution
        schemaSrc: string               // e.g. 'openapi.json' → sidecar.src
        generatorMeta?: GeneratorMetaLookup  // generatorId → { version, registry }
      }
    }
    ```
    
    - **No `postPass`** (or no `attribution` at all): capture still
      happens, but nothing is emitted — the run produces no sidecars.
    - **With a `postPass` block**: the post-render pass runs and surfaces
      `sidecars` + `generationMap` on the `toArtifacts` result.
    
    For a CLI user the switch is `client.json#settings.anchors`
    (`core/types/Settings.ts`):
    
    ```jsonc
    { "settings": { "anchors": { "enabled": true, "out": ".maps" } } }
    ```
    
    `out` is optional (defaults to `.maps`). The CLI flags `--anchors` /
    `--no-anchors` override `anchors.enabled` for a single run.
    `cli/lib/to-attribution-payload.ts` converts the `anchors` block
    into the worker's `SerializableAttribution` payload.
    
    ## How it works
    
    The mechanism has four stages: two always-on capture concerns (in
    Parse and Generate), an emission pass folded into Render, and a
    host-side write after the Worker returns. (The `core/anchors/` source
    comments label these Phase A–D, referencing the original gen-maps
    plan.)
    
    ```
    PARSE ───────▶ GENERATE ─────────▶ RENDER ──────────────▶ host
      │               │                  │  └─ [post-pass]      │
    Stage 1         Stage 2            Stage 3                Stage 4
    location      producer-tree      resolve spans,          write
    capture       instrumentation    build sidecars          .maps/
    (always on)   (always on)        (when postPass set)
    ```
    
    ### Stage 1 — location capture during Parse
    
    Capture is unconditional. Every parsed OAS / GraphQL node snapshots
    the visitor's `StackTrail` into its `OasBase` base at construction —
    `toLocation()` renders it as a JSON Pointer. This runs on every parse,
    whether or not emission is configured. A producer's schema pointer is
    later derived from that trail (`stackTrail.toSchemaPointer()`); a
    producer with no trail of its own falls back to a coarse pointer
    derived from its `generatorKey` (see
    [the attribution tuple](#the-attribution-tuple)).
    
    ### Stage 2 — producer-tree instrumentation during Generate
    
    Every DSL element extends `SnippetBase`. The constructor installs a
    capturing `toString` **unconditionally**
    (`core/dsl/SnippetBase.ts`) — there is no attribution flag to check at
    construction time. The wrapper is a no-op *at call time* unless the
    capture interval is active, gated by `this.context.captureSink`:
    
    ```ts
    const capturingToString = function (this: SnippetBase): string {
      const sink = this.context.captureSink // undefined outside the capture interval
      // outside capture: just delegate to the subclass toString
      // inside capture: push onto the render stack, record parent/child
      //                 edges + byte spans into the sink, then delegate
      // ...
    }
    ```
    
    The capture interval is opened by Render (Stage 3) around the single
    capture render. While it is open, the sink records which producer is
    rendering and the parent/child edges as a parent's `toString`
    interpolates a child (via `${...}`) — building the tree of every
    producer that contributed to the file, and the byte span each one
    occupies. Outside the interval the wrapper adds nothing observable.
    Subclass authors write nothing different either way; the
    instrumentation is transparent.
    
    ### Stage 3 — the post-pass (folded into Render)
    
    There is no separate pass between Generate and Render. Render is a
    single capture pass: `RenderContext` renders each file once with the
    capture interval open, and — *when the run supplies
    `attribution.postPass`* — immediately runs the post-pass over that
    file's resolved spans. `postPass` (`core/anchors/postPass.ts`) is a
    pure function over a file's text + spans; per code `File` (JSON
    artifacts have no producer tree and are skipped) it:
    
    1. Takes the byte spans the capture sink resolved from the occurrence
       tree — `{ from, to, producer }` for every contributing Definition /
       Snippet, in document order.
    2. **`attribute(span.producer)`** — derives the
       `{ generatorId, schemaPointer, variant, definitionName, producerName }`
       tuple for each span (see below).
    3. **Landmark + AST path resolution** — *if* a `ParserAdapter` is
       present, ascends each span to its enclosing top-level export
       (the **landmark**) and records the AST child-index **path** down
       to the span. If no parser is present (the default — see [the
       worker boundary](#the-worker-boundary--why-the-parser-is-omitted)),
       the landmark is the enclosing `Definition`'s identifier name and
       the path is empty.
    4. **`buildSidecar(...)`** — pools and interns everything into the
       Sidecar v2 object.
    
    `runPostPassForFiles` does not exist — the post-pass is not a distinct
    whole-run stage; it runs inline in `RenderContext.render`, once per
    File.
    
    ### Stage 4 — disk persistence on the host
    
    The Worker returns `sidecars` and `generationMap` as fields on the
    `RESULT` message, *separate from the manifest*. The host writes
    them. `writeSidecars` (`core/anchors/writeSidecars.ts:59`, called
    from `cli/lib/generate-local.ts`) **wholly rewrites** the output
    directory each run:
    
    ```
    <root>/.skmtc/<project>/.maps/
      <relative-file-path>.skm.json   ← one sidecar per generated file
      _map.ndjson                     ← project-level generation map
    ```
    
    Wholly rewriting (not merging) keeps the index honest — a stale row
    would point a viewer at code that no longer exists — and keeps the
    mtime invariant simple for `doctor`'s staleness check.
    
    ## The Sidecar v2 format
    
    A sidecar is **pooled and position-indexed**
    (`core/anchors/sidecar.ts:66`). Rather than repeating strings, it
    holds flat **pools** and an **anchor table** of integer indices into
    them:
    
    ```ts
    const sidecarSchema = v.object({
      v: v.literal(2),       // format version
      f: v.string(),         // file path, relative to basePath
      src: v.string(),       // schema source (e.g. 'openapi.json')
      parser: v.string(),    // "<id>@<version>" or 'none'
      R: v.array(registryEntry),   // registry pool  { host, type }
      G: v.array(generatorEntry),  // generator pool { name, version, r }
      S: v.array(v.string()),      // schema-pointer pool
      V: v.array(v.string()),      // variant pool
      L: v.array(v.string()),      // landmark pool
      P: v.array(v.string()),      // AST-path pool ('.'-joined)
      A: v.array(anchorRow),       // the anchor table
      N: v.optional(v.array(v.string())),  // producer-name pool (optional)
      An: v.optional(v.array(v.number()))  // A[i]'s producer → N (optional)
    })
    ```
    
    Each **anchor row** is a 7-tuple of pool indices plus a byte range
    (`core/anchors/sidecar.ts:44`):
    
    ```
    [ Li, Pi, gi, si, vi, fromByte, toByte ]
       │   │   │   │   │
       │   │   │   │   └─ V[vi]  variant
       │   │   │   └───── S[si]  schema pointer
       │   │   └───────── G[gi]  generator   (G[gi].r indexes into R)
       │   └───────────── P[Pi]  AST path inside the landmark
       └───────────────── L[Li]  landmark (enclosing top-level export)
    ```
    
    A minimal sidecar for a file holding one `User` type, generated
    worker-side (no AST parser):
    
    ```json
    {
      "v": 2,
      "f": "src/types/User.generated.ts",
      "src": "openapi.json",
      "parser": "none",
      "R": [{ "host": "jsr.io", "type": "jsr" }],
      "G": [{ "name": "@skmtc/gen-typescript", "version": "", "r": 0 }],
      "S": ["#/components/schemas/User"],
      "V": ["main"],
      "L": ["User"],
      "P": [""],
      "A": [[0, 0, 0, 0, 0, 0, 142]]
    }
    ```
    
    The single `A` row reads: landmark `L[0]="User"`, path `P[0]=""`
    (the landmark node itself), generator `G[0]`, schema pointer
    `S[0]`, variant `V[0]="main"`, byte range `[0, 142)`. The pooling
    pays off on real files where the same generator, variant, and
    schema pointer recur across dozens of spans.
    
    The `parser` field is the adapter id (`oxcAdapter.id` is
    `"oxc@<version>"`) or the sentinel `'none'` when the AST step was
    skipped — a re-anchoring consumer warns on a parser mismatch and can
    detect "no landmark data" without inspecting the pools.
    
    The format is frozen at **v2** and validated by Valibot
    (`sidecarSchema`), so it round-trips reliably across the worker
    boundary and on disk. Format evolution bumps `v` and ships an
    adapter in the consumer.
    
    ## The attribution tuple
    
    `attribute()` (`core/anchors/attribute.ts`) is a pure function
    over a producer that yields:
    
    ```ts
    type Attribution = {
      generatorId: string                 // from the generatorKey; '<unknown>' if none
      schemaPointer: string               // document-relative schema pointer ('' if none)
      variant: string                     // from the key; defaults to 'main'
      definitionName: string | undefined  // identifier name, for Definition producers
      producerName: string                // the producer's class name
    }
    ```
    
    `generatorId` and `variant` come from parsing the producer's
    `generatorKey` (see [generators-as-packages.md](../../concepts/generators-as-packages.md)
    for the key shapes). `schemaPointer` is resolved in priority order:
    
    1. The producer's **own position** — `stackTrail.toSchemaPointer()`
       when its `StackTrail` is non-empty (the fine-grained pointer
       captured in Stage 1). `toSchemaPointer()` strips the run's
       operational prefix so the pointer is document-relative.
    2. Otherwise, a **coarse** fallback derived from the `generatorKey`.
       Pointers are **protocol-agnostic** — no `oas:` / `gql:` prefix;
       the protocol is a property of the run's input schema, not of each
       pointer:
       - OAS operation → `#/paths/<escaped-path>/<method>`
       - GraphQL operation → `<rootKind>.<fieldName>`
       - Model → `#/components/schemas/<refName>`
       - Generator-only / no key → `''` (empty — no schema location)
    
    Path segments are RFC 6901 JSON-Pointer escaped (`~`→`~0`,
    `/`→`~1`). A producer with no `generatorKey` (a test double or a
    runtime-orphaned Snippet) gets `generatorId: '<unknown>'`.
    `producerName` is the producer's class name (e.g. `TsObject`),
    carried in the sidecar's optional `N` / `An` pools.
    
    ## The worker boundary — why the parser is omitted
    
    The AST step (Stage 3 step 3) needs a TypeScript/JavaScript parser.
    The implemented `ParserAdapter` is `oxcAdapter`, backed by the Rust
    `oxc-parser` via napi. **Native parsers do not bundle into a Deno
    Worker** — `oxc-parser`'s napi `bindings.js` statically references
    platform-specific `.node` files, and `tsc`'s npm package pulls in
    `source-map-support`. Either makes the worker bundle unbuildable or
    non-portable.
    
    So `oxcAdapter` is deliberately **not** re-exported from
    `@skmtc/core/Anchors` (importing it would poison the worker bundle);
    host-side consumers import it from `@skmtc/core/Anchors/oxc`
    directly.
    
    The consequence: the **default CLI path runs the post-pass
    worker-side with `parser: undefined`.** In that mode the sidecar
    still carries byte ranges, attributions, generators, schema
    pointers, and variants — but landmark names come from the enclosing
    `Definition`'s identifier and the AST `path` is empty. Re-anchoring
    a file *after a formatter has reshaped it* needs the AST paths and
    so is not possible in this mode; hover, pin, and related-artifact
    flows all work fine without them.
    
    The serialization detail: `AttributionState` holds a `parser`
    (function-bearing object) and a `generatorMeta` (function) — neither
    survives structured clone. The wire type `SerializableAttribution`
    (`worker/types.ts`) carries only plain data; `buildAttributionState`
    (`worker/mod.ts:35`) reconstitutes the state worker-side, omitting
    the parser by design and rebuilding `generatorMeta` from a plain
    `Record`. See [the-worker-runtime.md](../../concepts/the-worker-runtime.md) for the
    boundary in general.
    
    A host-side post-pass that re-runs `postPass` with the real
    `oxcAdapter` — to fill in true landmarks and AST paths — is a
    designed-for but not-yet-wired extension.
    
    ## Where the data lives
    
    ```
    <root>/.skmtc/<project>/.maps/          ← default; set by anchors.out
      src/types/User.generated.ts.skm.json
      src/forms/CreateUserForm.generated.tsx.skm.json
      ...
      _map.ndjson
    ```
    
    The `.maps` subtree is **derived output** — wholly rewritten every
    run, never a historical record. It should be gitignored. (The
    `writeSidecars` source notes the `skmtc init` template adds it to
    `.gitignore`; verify against the current `init` implementation
    rather than relying on that comment.)
    
    ## `doctor` checks
    
    `skmtc doctor` runs three gen-maps checks
    (`cli/lib/doctor-anchors.ts`), each `skipped` when anchors are not
    enabled:
    
    | Check id | What it verifies |
    |---|---|
    | `anchors-config/<project>` | The `settings.anchors` block in `client.json` is well-formed. |
    | `anchors-coverage/<project>` | Every file in `manifest.files` has a matching `.skm.json` sidecar. `ok` at ≥ 95%, `warning` below (JsonFile artifacts have no sidecar — expected). |
    | `anchors-staleness/<project>` | No sidecar's mtime is older than the file it describes — a stale sidecar means the file changed without a re-generate. |
    
    ## Cost model
    
    Capture is always on, but it is cheap; the real cost is emission,
    which you opt into.
    
    - **Capture (always on).** Parse snapshots each node's `StackTrail`
      into its `OasBase` — the trail is already carried through parse, so
      this is a reference, not new work. Every `SnippetBase` gets the
      capturing `toString`, but outside the capture interval
      (`context.captureSink` unset) it delegates straight to the subclass
      `toString` — no stack pushes, no allocation.
    - **Emission (opt-in via `postPass`).** Rendering opens the capture
      interval, so the wrapper now records parent/child edges and byte
      spans; then the post-pass resolves spans, attributes each, and
      builds the sidecar, and the host writes `.maps/`. This is where the
      cost lives, and it runs only when the run supplies
      `attribution.postPass`.
    
    There is no cross-run state — like every SKMTC run, an
    attribution-enabled run is from cold (see
    [the-worker-runtime.md](../../concepts/the-worker-runtime.md)).
    
    ## Public API surface
    
    Building tooling on this subsystem? The contract is
    `@skmtc/core/Anchors` (`core/anchors/mod.ts`):
    
    - **Types** — `Sidecar`, `RegistryEntry`, `GeneratorEntry`,
      `AnchorRow`, `GenerationMapEntry`, `Span`, `Attribution`,
      `ParserAdapter`, `LandmarkLocation`.
    - **Schemas** — `sidecarSchema`, `anchorRow`, `generatorEntry`,
      `registryEntry`, `generationMapEntry` (Valibot; use for
      validation / round-trip).
    - **Functions** — `postPass`, `writeSidecars`, `entriesForSidecar`,
      `toNdjson`, `parseNdjson`, `emptySidecar`.
    
    `AttributionState` is exported from `@skmtc/core/AttributionState`.
    `oxcAdapter` is host-only, at `@skmtc/core/Anchors/oxc` (never
    import it into worker-bundled code).
    
    Internal helpers — `resolveSpans`, `attribute`, `buildSidecar`'s
    interning — are deliberately not exported; they are load-bearing for
    `postPass` but not part of the cross-package contract.
    
    ## Status and limitations
    
    The subsystem is **partially wired**. Working today: the opt-in
    config, the render-time instrumentation, sidecar emission, the
    generation map, disk persistence, and the `doctor` checks.
    
    Not yet wired in the default path:
    
    - **AST-quality landmarks and paths.** The default (worker-side)
      post-pass runs without a parser; landmarks are Definition
      identifiers and AST paths are empty. The host-side post-pass with
      `oxcAdapter` is designed for but not yet wired.
    - **Generator version metadata.** `cli/lib/to-attribution-payload.ts`
      currently leaves `generatorMeta` undefined, so generator pool
      entries land with `version: ''` and a default `jsr.io` registry.
      Populating it from the project's `deno.json` + lockfile is planned.
    
    When reasoning about or extending this subsystem, verify the wiring
    in `cli/lib/generate-local.ts` and `worker/mod.ts` against the
    current source — this is an actively evolving area.
    
    ## Relationship to the manifest's `previews` and `mappings`
    
    Sidecars are **not part of the manifest**. They are a parallel
    output: `sidecars` / `generationMap` are separate fields on the
    `toArtifacts` result, written to `.maps/`, while the manifest is
    written to `.settings/manifest.json`. Two distinct provenance
    channels exist, by design:
    
    | | `previews` / `mappings` (manifest) | gen-maps (sidecars) |
    |---|---|---|
    | Opt-in? | Always on (if a generator implements the hooks) | Opt-in via `anchors` |
    | Granularity | Per `Definition` | Per byte range |
    | Carries | A module + a source descriptor (operation/model) | Full anchor table, AST paths, generator version |
    | Lives in | `manifest.json` | `.maps/*.skm.json` + `_map.ndjson` |
    | Consumer | A UI listing generated artifacts | A viewer mapping code spans ↔ schema |
    
    Use `previews` / `mappings` for "list what was generated and roughly
    where it came from"; use gen-maps for "trace this exact span of
    code." See [the-manifest.md](../../concepts/the-manifest.md#previews-and-mappings--for-tooling).
    
    ## Common questions
    
    ### Are sidecars committed to the repo?
    
    No. The `.maps` subtree is derived output — it should be gitignored,
    and it is wholly rewritten each run. If you need provenance history,
    capture the `.maps` tree (or the `sidecars` result field) per run
    yourself — the same way the manifest must be captured for run
    history.
    
    ### Does enabling attribution change the generated code?
    
    No. Render is unchanged by attribution. The instrumentation only
    *observes* rendering (it caches `_rendered` and records parent/child
    edges); it never alters output. An attribution-on run and an
    attribution-off run produce byte-identical artifacts.
    
    ### Why does the worker-side sidecar say `"parser": "none"`?
    
    Native parsers do not bundle into a Deno Worker, so the worker-side
    post-pass runs without one. Byte ranges and attribution are still
    recorded; AST landmarks/paths are not. See [the worker
    boundary](#the-worker-boundary--why-the-parser-is-omitted).
    
    ### What is a "landmark"?
    
    The top-level export a span lives under — a `Definition`'s name
    (`User`, `createUser`). With a parser, it is resolved from the AST;
    without one, it is the enclosing `Definition`'s identifier. A span
    outside any landmark (empty landmark string) is skipped by
    `buildSidecar` — it has nothing stable to re-anchor from.
    
    ### Can a generator opt a single file out of attribution?
    
    No. Attribution is a run-level switch. Every code `File` in a run
    either gets a sidecar or none do. `JsonFile` artifacts never get
    one (they have no producer tree).
    
    ### How does the generation map dedupe one Definition across many anchors?
    
    `entriesForSidecar` emits one row per unique landmark, preferring
    the anchor whose AST path is empty (the landmark node itself). If no
    path-empty anchor exists — rare; happens when a Definition's text
    was reshaped between render and post-pass — it falls back to the
    first anchor for that landmark, so the Definition still appears in
    the map.
    
    ## Further reading
    
    - [The three phases](../../concepts/the-three-phases.md) — the Parse / Generate /
      Render pipeline the post-pass sits between
    - [The worker runtime](../../concepts/the-worker-runtime.md) — the structured-clone
      boundary that forces the parser-omitted worker-side post-pass
    - [The manifest](../../concepts/the-manifest.md) — the run record, and the
      lighter-weight `previews` / `mappings` provenance channel
    - [Generators as packages](../../concepts/generators-as-packages.md) — `generatorKey`
      shapes, which `attribute()` parses for `generatorId` / `variant`
    - [The StackTrail](../../reference/api/stack-trail.md) — the parse-phase position
      stack behind Stage 1 location capture
    - [`skmtc-architecture` skill §9](SKILL.md)
      — the compressed mental model for infrastructure builders
    - Source: `core/anchors/` (the subsystem), `core/dsl/SnippetBase.ts`
      (instrumentation), `core/context/CoreContext.ts` (post-pass
      wiring), `cli/lib/doctor-anchors.ts` (the `doctor` checks)
    
  • design.md 7.2 KB
    # skmtc-architecture skill — design document
    
    > Plan for the skill that gives agents a system-level mental model of
    > SKMTC — for building and extending infrastructure *around* the
    > engine rather than authoring generators or running the CLI.
    >
    > The corresponding loadable skill is [`SKILL.md`](SKILL.md) in this
    > directory. This design document describes *what the skill should
    > contain and why*; the SKILL.md is the operational artifact.
    
    ## Purpose
    
    Give an AI assistant enough of SKMTC's architecture to **reason about
    the system and build infrastructure around it** — a hosted generation
    service, a schema or generator registry, tracing and provenance
    tooling, a web app or SaaS wrapping the engine.
    
    The skill exists because SKMTC is *deeply counter-intuitive*. An
    agent extrapolating from generic codegen knowledge (Mustache
    templates, plugin registries, dependency graphs) or generic backend
    infra knowledge (warm pools, incremental builds, retry-on-failure,
    fail-closed error handling) will make confidently wrong
    architectural proposals. The existing `skmtc-cli` and
    `skmtc-generator` skills correct codegen intuitions for their own
    task domains, but neither equips an agent to reason about the engine
    *as a system to host* — they assume the engine is a given and stop at
    their own surface.
    
    ## Audience
    
    Agents working on SKMTC platform infrastructure:
    
    - Building the hosted Sandbox / run API (`@skmtc/server` and beyond).
    - Building a schema or generator registry — the "GitHub for
      OpenAPI / generators" product.
    - Building tracing, provenance, or run-history tooling.
    - Building a web app or SaaS that wraps `toArtifacts`.
    - Platform-level CI integration.
    
    Explicitly **not** the audience: generator authors (`skmtc-generator`),
    day-to-day CLI users (`skmtc-cli`), people debugging a broken run
    (`skmtc-debug`).
    
    ## Triggers
    
    Intent phrases that should load this skill:
    
    - "what is SKMTC" / "explain SKMTC"
    - "how does the SKMTC engine work" / "SKMTC architecture"
    - "build a service / API / SaaS around SKMTC"
    - "host SKMTC" / "run SKMTC on a server"
    - "SKMTC tracing" / "SKMTC provenance" / "gen-maps"
    - "the SKMTC package graph / dependencies"
    - editing files under `core/run/`, `worker/`, `server/`, `mcp/`,
      `convert/`, or `core/anchors/` for *infrastructure* reasons
    
    Should NOT auto-load on:
    
    - "write / clone / customize a generator" → `skmtc-generator`
    - "run skmtc" / "install a generator" / CLI subcommands → `skmtc-cli`
    - "why is my generation failing / wrong / empty" → `skmtc-debug`
    - "retro this session" → `skmtc-retro`
    
    ## Scope boundary
    
    ### In skill (load-bearing system model)
    
    - The five facts (canonical four + a fifth tuned to infra:
      cold-start determinism).
    - What SKMTC is — the engine / hosts decomposition.
    - Benefits and the when-to-use / when-not table.
    - The three-phase pipeline; the host/Worker boundary; the
      structured-clone OAS/GraphQL asymmetry; sandboxing.
    - The `toArtifacts` entry-point signature.
    - Cross-generator coordination by memoization (enough to know it is
      *not* a dependency graph, and that result-caching belongs outside
      the engine).
    - The DSL in one screen — Projection vs Snippet, vocabulary
      discipline — as recognition only, with a hand-off to
      `skmtc-generator`.
    - The manifest as the run contract: tracing IDs, the results tree,
      parseIssues-derived exit status, no run history.
    - The attribution / gen-maps provenance subsystem.
    - The package graph, the Deno + JSR substrate, key third-party
      dependencies, the version-pin discipline.
    - The infrastructure integration map — engine concept → platform
      concern, and explicitly where the engine stops.
    - A counter-intuitive-facts table aimed at infra reflexes (distinct
      from §1's codegen reflexes).
    
    ### Deferred to other skills / docs
    
    - Generator authoring (Projections, Snippets, scaffolds,
      anti-patterns) → `skmtc-generator`.
    - CLI command surface, `client.json` shape, enrichment routing →
      `skmtc-cli`.
    - Failure diagnosis → `skmtc-debug`.
    - Full pipeline / worker / manifest detail → `concepts/`.
    - Design rationale and rejected alternatives →
      `explanation/design-philosophy.md`.
    - The full manifest Valibot schema → `reference/manifest-format.md`.
    - The gen-maps wire format → `core/anchors/` source + the gen-maps
      plan notes.
    
    ### Boundary with adjacent skills
    
    `skmtc-architecture` is the *understand* skill; the others are *do*
    (`cli`, `generator`), *diagnose* (`debug`), and *reflect* (`retro`).
    When triggers overlap, the test is **which stance the agent should
    be in**: reasoning about the system as a whole → this skill;
    producing or operating a concrete artifact → the matching task
    skill.
    
    ## Content-source mapping
    
    The skill was distilled from the SKMTC source and the docs tree:
    
    | SKILL.md section | Primary sources |
    |---|---|
    | §1 Five facts | `llms.md` ("four facts"); fact 5 from `concepts/the-worker-runtime.md` |
    | §2 What SKMTC is | `core/CLAUDE.md` (name expansion); `docs/README.md`; the package `deno.json` files |
    | §3–4 Benefits / when to use | `docs/README.md`; `explanation/design-philosophy.md`; `explanation/comparison-to-other-tools.md` |
    | §5 Pipeline | `concepts/the-three-phases.md`; `concepts/the-worker-runtime.md`; `core/run/toArtifacts.ts` |
    | §6 Coordination | `concepts/cross-generator-coordination.md`; `explanation/design-philosophy.md` §2, §8 |
    | §7 DSL | `concepts/projections-and-snippets.md`; `reference/glossary.md` |
    | §8 Manifest | `concepts/the-manifest.md`; `core/run/toArtifacts.ts`; `worker/mod.ts` |
    | §9 Provenance | `core/anchors/*` file-headers; `worker/mod.ts` `buildAttributionState` |
    | §10 Package graph / deps | every package `deno.json`; `explanation/design-philosophy.md` §6 |
    | §11 Infra map | `server/src/createServer.ts`; `mcp/src/mcp-server.ts`; `convert/mod.ts` |
    | §12 Counter-intuitive infra facts | `concepts/the-worker-runtime.md`; `the-manifest.md`; `explanation/design-philosophy.md` |
    
    ## Open design questions
    
    ### Should this skill front the five facts at all?
    
    The other skills front the canonical five facts as a duplication
    discipline. This skill keeps them but reframes — facts 1, 2, 5 carry
    most weight for an infra audience, and fact 5 is replaced with a
    cold-start-determinism statement (the slot is skill-specific in the
    sibling skills too). If `llms.md`'s canonical list changes, fact
    1–4 wording here should be reviewed.
    
    ### Versioning of the package graph
    
    §10 deliberately omits version numbers (they drift; `deno.json` is
    canonical). If a future reader needs a pinned snapshot, that belongs
    in a reference doc, not the skill.
    
    ### Does the gen-maps subsystem deserve its own concept doc?
    
    **Resolved.** `concepts/attribution-and-gen-maps.md` was authored as
    the full treatment of the subsystem. §9 of this skill is kept as the
    compressed mental model (the selective-duplication policy says skills
    stay self-contained — see `skills/README.md`) and now cross-
    references the concept doc for depth rather than being trimmed away.
    
    ### Sandbox API maturity
    
    `status-and-roadmap.md` lists the hosted Sandbox API as
    experimental. As the SaaS infrastructure this skill targets is built
    out, §11 ("Building infrastructure around SKMTC") will need to track
    what becomes real — and may eventually split into its own
    `authoring/` or `platform/` documentation tree.
    
  • SKILL.md 29.6 KB
    ---
    name: skmtc-architecture
    version: 0.1.1
    description: |
      Understand what SKMTC is, how its engine works, and the
      architectural invariants — for agents building or extending
      infrastructure *around* SKMTC rather than authoring generators or
      running the CLI. Covers the three-phase pipeline, the host/Worker
      boundary, cross-generator coordination, the manifest, the
      attribution / gen-maps (provenance) subsystem, the package graph,
      the dependency substrate, and the design decisions that make SKMTC
      behave unlike a typical codegen tool.
    
      Use this skill when the user asks "what is SKMTC", "how does the
      SKMTC engine work", "explain the SKMTC architecture", or is
      building platform infrastructure around SKMTC — a hosted generate
      API, a schema or generator registry, tracing or provenance
      tooling, a web app or SaaS that wraps the engine, or
      platform-level CI integration. This skill is the system mental
      model. It does NOT cover authoring generators (→ skmtc-generator),
      running CLI commands (→ skmtc-cli), or diagnosing broken runs
      (→ skmtc-debug).
    allowed-tools:
      - Bash
      - Read
      - Glob
      - Grep
      - Write
      - Edit
    metadata:
      internal: true
    ---
    
    # SKMTC architecture
    
    This skill is the **system mental model** for agents building
    infrastructure around SKMTC — a hosted generation service, a schema
    or generator registry, provenance and tracing tooling, a web app
    that wraps the engine. It explains *what SKMTC is, how it works, and
    why it behaves the way it does* — deeply enough to reason about and
    extend, without the generator-authoring or CLI-operating detail the
    sibling skills carry.
    
    SKMTC is **counter-intuitive on purpose**. Most of what generic
    codegen and generic backend-infra training data would suggest is
    actively wrong here. The fastest way to make a bad architectural
    proposal is to extrapolate from another tool. Read §1 and §11 before
    proposing anything.
    
    ## 1. The five facts that override default LLM intuitions
    
    These override what training-data priors suggest about codegen
    tools. They apply across every SKMTC interaction; an infra builder
    needs facts 1, 2 and 5 most.
    
    1. **No plugin registry, no dependency graph, no topological sort.**
       Cross-generator coordination is a `Map` cache keyed by
       `(identifier.name, exportPath)`. Generator execution order does
       not affect output.
    
    2. **Render does not run Prettier or Biome.** No formatter runs
       inside `@skmtc/core`. Generated output is unformatted by design;
       consumers format separately.
    
    3. **Generator source code is the customization surface.** Stock
       generators have *deliberately* hardcoded export paths and peer
       imports. To customize beyond enrichments: clone the generator and
       edit it. There are no config flags for paths or output shape.
    
    4. **`OasSchema` is a union type, not a class hierarchy.** Sibling
       classes (`OasObject`, `OasArray`, `OasString`, …) each
       independently implement `.isRef()` returning `false`. `OasRef` is
       a *sibling* with `.isRef()` returning `true`. There is no
       `BaseSchema`.
    
    5. **Every generation run is from cold.** One Deno Worker is spawned
       per `generate`, runs Parse → Generate → Render once, posts the
       result back, and is terminated. No warm pool, no cross-run cache,
       no incremental rebuild. `toArtifacts` is a pure function of
       `(document, settings, generators)`. Determinism is chosen over
       speed deliberately — **caching belongs outside the engine**, keyed
       on input hashes.
    
    ## 2. What SKMTC is
    
    > SKMTC is a code generator. It takes one **OpenAPI v3** document or
    > one **GraphQL SDL** schema and produces
    > **source files** — types, runtime validators, query hooks, forms,
    > mocks, server routes — all derived from that one schema, in one
    > run, all consistent with each other. The output is committed to the
    > consumer's repository like any other source code; there is **zero
    > SKMTC runtime** in the consumer's bundle. The engine is
    > **language-blind** (core 0.8.0+): a generator declares its target
    > language by importing its projection-base factories and snippet
    > base from a `@skmtc/lang-*` package — the import graph alone
    > carries the language; entries have no `lang` field. TypeScript
    > (`@skmtc/lang-typescript`) and Kotlin (`@skmtc/lang-kotlin`, proven
    > by `gen-kotlin` DTOs + `gen-kotlin-spring` controllers) are the
    > production languages; other `lang-*` packages (C#, …) are the
    > roadmap.
    
    The crucial reframing for an infrastructure builder: **SKMTC is an
    engine with several thin hosts, not a CLI.** The CLI is one host.
    The mental decomposition:
    
    | Layer | Package | Role |
    |---|---|---|
    | **Engine** | `@skmtc/core` | The three-phase pipeline. Entry point: `core/run/toArtifacts.ts`. |
    | **Worker host** | `@skmtc/worker` | Wraps the engine in a Deno Worker `postMessage` handler. |
    | **CLI host** | `@skmtc/cli` | Local developer surface: scaffold, install, bundle, generate, watch. |
    | **HTTP host** | `@skmtc/server` | Hono app — the hosted "Sandbox API". `POST /artifacts`. |
    | **MCP host** | `@skmtc/mcp` | Model Context Protocol server surface. |
    | **Schema normalizer** | `@skmtc/convert`, `@skmtc/openapi-down-convert` | Swagger 2 / OAS 3.1 → OAS 3.0. Runs *before* the engine. |
    | **Generators** | `@skmtc/gen-*` | The actual codegen logic, distributed as JSR packages. |
    
    Every host does the same thing: get a schema, call `toArtifacts`,
    do something with `{ artifacts, manifest }`. **A SaaS is just
    another host of the same engine.**
    
    ## 3. What you get — the benefits
    
    - **Multi-artifact coherence.** One schema yields N artifact types
      (types + validators + hooks + forms + mocks + routes) that
      reference each other correctly. Add a field, regenerate, every
      artifact updates consistently.
    - **Output is committed source code.** Reviewable in `git diff`,
      grep-able, refactorable. No runtime library, no peer-dependency
      package at deploy time. Schema/output drift is visible in version
      control.
    - **Determinism.** Same inputs → byte-identical output. No hidden
      state, no order dependence, no warm-cache effects.
    - **Idempotency by construction.** Generators coordinate by
      memoization, not by a dependency graph — so they can be written,
      tested, and reasoned about in isolation, and order never matters.
    - **Clone-to-customize.** Generators are owned source code, not
      opaque configured dependencies (the shadcn/ui model).
    - **Lenient input, strict diagnostics.** One malformed schema does
      not kill the run; it is logged and its dependents pruned. The
      `manifest.json` is an exhaustive record even when output is
      partial.
    - **OAS and GraphQL through one engine.** The GraphQL pipeline reuses
      the same DSL, the same renderer, the same manifest.
    
    ## 4. When to use SKMTC — and when not to
    
    | Verdict | Situation |
    |---|---|
    | **Strong fit** | An OpenAPI v3 or GraphQL schema is the contract; you need *multiple* artifact types from it; you want generated code committed to the repo. |
    | **Overkill** | You only need types (`openapi-typescript`); you only need a typed fetch client (`@hey-api/openapi-ts`); schemas are dynamic at runtime (use a runtime renderer). |
    | **Wrong tool** | Can't run Deno *and* won't use the hosted Sandbox API; you need production output in a language with no `@skmtc/lang-*` package yet (today that is everything except TypeScript — use `openapi-generator`). |
    
    SKMTC's closest peer is `kubb` — multi-target, TypeScript-native.
    The distinguishing bet is the **customization model**: clones (source
    you own) over plugins (configured packages), plus **coordination by
    name** (memoization) over explicit composition. Full landscape:
    [`explanation/comparison-to-other-tools.md`](../../explanation/comparison-to-other-tools.md).
    
    ## 5. How it works — the pipeline
    
    A generation run is a one-way pipeline of three phases, each
    producing an immutable artifact the next consumes:
    
    ```
            ┌─────────────── HOST PROCESS ───────────────┐
            │  bootstrap · fetch schema · OAS pre-parse   │
            └──────────────────────┬──────────────────────┘
                                   │  postMessage(GENERATE)
            ┌──────────────────────▼──────────────────────┐
            │        DENO WORKER  (sandboxed)             │
            │   PARSE  ─▶  GENERATE  ─▶  RENDER            │
            │   model     files map      { path: text }   │
            │   +issues   (in memory)    artifacts        │
            └──────────────────────┬──────────────────────┘
                                   │  postMessage(RESULT)
            ┌──────────────────────▼──────────────────────┐
            │  HOST: write files to disk · write manifest │
            └─────────────────────────────────────────────┘
    ```
    
    - **Parse** — schema → typed object model (`OasDocument` /
      `GqlDocument`). Lenient: a per-item parser that throws becomes a
      `ParseIssue`; `removeErroredItems` then prunes one hop of `$ref`
      consumers of any failed component. Generate can trust every
      surviving item.
    - **Generate** — walk the configured generators over the parsed
      document, producing an in-memory `Map<path, File>`. Generators
      produce output via side effects (`register` / `insert*`), never by
      return value. Memoized **Drivers** dedupe and coordinate (§6).
    - **Render** — serialize each `File` to a string by joining
      re-exports, imports, and definitions. **No formatter runs.** Output
      is `Record<path, content>`.
    
    Each phase boundary is an immutable hand-off — the next phase reads,
    never mutates. Detail:
    [`concepts/the-three-phases.md`](../../concepts/the-three-phases.md).
    
    ### The host / Worker boundary
    
    The engine physically runs in a **Deno Worker** spawned by the host,
    one per run (fact 5). The boundary is `postMessage`, which uses the
    structured-clone algorithm — and that shapes a real asymmetry:
    
    - **OAS is parsed host-side.** A plain `OpenAPIV3.Document` is JSON —
      it survives structured clone. The host normalizes Swagger 2 / OAS
      3.1 to 3.0 via `@skmtc/convert`, then posts the plain document.
    - **GraphQL is parsed Worker-side.** A parsed `GqlDocument` has class
      instances with cyclic back-references — structured clone strips
      methods and prototypes. So the host posts the raw SDL **string**
      and the Worker parses it.
    
    The Worker boundary is also the **security boundary**. The Worker is
    spawned with Deno permissions `read: true, write: true, env: true,
    net: false, run: false`. Generator code (third-party JSR packages or
    team-edited clones) cannot make network calls or spawn subprocesses.
    It is a *soft sandbox* — it limits the blast radius of a buggy or
    compromised generator, not a determined attacker. Schema fetching
    happens host-side, before the Worker exists. Detail:
    [`concepts/the-worker-runtime.md`](../../concepts/the-worker-runtime.md),
    [`explanation/security-model.md`](../../explanation/security-model.md).
    
    ### The engine entry point
    
    Every host calls one function:
    
    ```ts
    toArtifacts({
      traceId, spanId, startAt,          // run correlation
      document,                          // SkmtcDocumentInput (oas | gql)
      settings,                          // ClientSettings (basePath, enrichments, skip, include)
      toGeneratorConfigMap,              // () => the registered generators
      stackTrail,                        // position/trace stack
      silent,
      attribution                        // optional — turns on gen-maps (§8)
    }): { artifacts: Record<string,string>; manifest: ManifestContent;
          sidecars?; generationMap? }
    ```
    
    It is pure with respect to its inputs and does no I/O of its own
    beyond reading. `core/run/toArtifacts.ts`, `worker/mod.ts`, and
    `server/src/createServer.ts` are three independent callers of it —
    study them as the templates for a fourth.
    
    ## 6. How it works — cross-generator coordination
    
    This is the single most counter-intuitive piece. There is **no
    dependency graph and no topological sort.** Coordination is
    **memoization**:
    
    - A generator that needs a peer's output calls
      `this.insertOperation(PeerProjection, op)` (or `insertModel` /
      `insertNormalizedModel`).
    - A **Driver** (`OasOperationDriver`, `GqlOperationDriver`,
      `ModelDriver`) computes a cache key `(identifier.name, exportPath)`
      — both pure functions of `(operation, enrichments, variant, options)`
      via the peer's static methods. `options` is typed data the caller
      passes on the insert; a peer that uses it folds it into the name.
    - **Cache hit** → the existing `Definition` is reused, after an
      integrity check (generator key, class, and the caller's options — a
      hit built with different options throws). **Miss** → the
      peer's Projection is constructed (which may recurse), wrapped in a
      `Definition`, registered, and its import stitched into the calling
      file.
    
    Because the key is a pure function of inputs, whichever generator
    asks first triggers construction and everyone after gets a cache
    hit — so the output `Map<path, File>` is **identical regardless of
    generator order**. A `generatorKey` integrity check throws
    `"Registered definition mismatch"` if two different
    generator-and-input pairs collide on one cache key.
    
    **Why this matters for infra:** order-independence means there is
    nothing to schedule or sequence. Determinism means the *correct
    caching layer is outside the engine* — cache the whole
    `{ artifacts, manifest }` result keyed on a hash of
    `(schema, settings, bundle)`. Never try to cache *inside* a run.
    Detail:
    [`concepts/cross-generator-coordination.md`](../../concepts/cross-generator-coordination.md).
    
    ## 7. The DSL in one screen
    
    You do not need to author generators to build infrastructure, but
    you should recognize the vocabulary (defer authoring to
    `skmtc-generator`):
    
    - A **generator** is a JSR package exporting an *entry* built with
      `toOasOperationEntry` / `toGqlOperationEntry` / `toModelEntry`.
      Entries are pure pipeline config — no `lang` field; the generator
      declares its target language by importing its projection bases
      from a `@skmtc/lang-*` package, and the engine's Drivers read it
      off the projection class's inherited static.
    - The entry's `transform` hook runs once per matched operation/model
      and produces output by calling `register` / `insert*` — its return
      value is discarded.
    - A **Projection** is a *named, file-scope* artifact (`export const
      X = …`), wrapped in a `Definition`, cached by `(name, exportPath)`,
      reachable by other generators. A **Snippet** is an *anonymous*
      fragment embedded into a Projection's body via template-literal
      interpolation.
    - Templates are TypeScript template literals inside classes — not
      `.hbs`/`.mustache` files. Composition is by `${...}` interpolation
      of anything `Stringable`. (Generators are *authored* in
      TypeScript/Deno regardless of the target language they emit.)
    
    **Vocabulary discipline:** in SKMTC prose use `register`, `insert`,
    `render`. Avoid *emit*, *dispatch*, *stitch* — they map to no
    exported surface. See
    [`reference/glossary.md`](../../reference/glossary.md#skmtc-vocabulary--load-bearing-terms).
    
    ## 8. The manifest — the run contract and tracing
    
    Every run writes a `manifest.json` (to
    `.skmtc/<project>/.settings/manifest.json` for the CLI host;
    returned in-band for the HTTP host). It is the **canonical record of
    a run** — written *always, even on failure*. The terminal output is
    a summary; the manifest is the full story.
    
    ```ts
    type ManifestContent = {
      deploymentId: string      // the run; from DENO_DEPLOYMENT_ID or a timestamp
      traceId: string           // OpenTelemetry-shaped correlation key
      spanId: string            // sub-span within the run
      region?: string           // set only for hosted (Deno Deploy) runs
      files:    Record<path, { lines, characters, destinationPath }>
      results:  ResultsItem     // nested tree of per-(generator × item) outcomes
      previews, mappings        // optional UI metadata (per-Definition source descriptors)
      parseIssues: ParseIssue[] // every Parse-phase diagnostic
      startAt, endAt: number    // unix ms; endAt - startAt = worker wall time
    }
    ```
    
    What an infra builder must internalize:
    
    - **Tracing is built in.** `traceId` / `spanId` are
      OpenTelemetry-shaped. The `StackTrail` carries them as its root
      frames, and the `results` tree is keyed by stack-trail strings —
      so an outcome is addressable as
      `traceId → spanId → generate → generatorId → item`. `deploymentId`
      and `region` come from Deno Deploy env vars on the hosted path.
      Wire these straight into an observability backend.
    - **Exit / status derives from `parseIssues`, not from throws.** The
      CLI returns exit 1 iff any `parseIssue.level === 'error'`. The
      engine *fails open* — a bad schema does not throw, it logs. Your
      HTTP host must compute status from `manifest.parseIssues`, not from
      a `try/catch` around `toArtifacts`.
    - **`results` outcomes:** `success` means "the transform ran without
      throwing" — **not** "produced output". `notSupported` is normal (a
      generator that doesn't apply to an item). `skipped` is filter
      exclusion. Diagnose "no output" via the `files` map, not `results`.
    - **No history.** The on-disk manifest is overwritten every run. If
      you want run history, **persist each manifest yourself** — it is
      already the right per-run telemetry payload.
    
    Detail: [`concepts/the-manifest.md`](../../concepts/the-manifest.md),
    [`reference/manifest-format.md`](../../reference/manifest-format.md).
    
    ## 9. Provenance — the attribution / gen-maps subsystem
    
    `core/anchors/` is the **provenance layer**. Capture is always on;
    **emission is opt-in**: pass `attribution: { postPass: {...} }` to
    `toArtifacts` (the engine-level `AttributionState` has no `enabled`
    field — the `client.json#settings.anchors.enabled` switch is the
    user-facing toggle). When emission is on, the run produces two extra
    artifact types alongside the code:
    
    - **Sidecar** (`<file>.skm.json`) — one per generated file. A
      pooled, position-indexed map: byte ranges in the rendered file →
      **attribution tuples** `{ genId, srcPtr, variant, defName }`, plus
      generator version and source registry. It is a *source map for
      provenance* — it answers "which generator, which schema location,
      which variant produced *this span* of code?". `srcPtr` is a schema
      pointer like `oas:#/components/schemas/User`.
    - **Generation map** (`_map.ndjson`) — a project-level, per-Definition
      **reverse-query index**: "which files came from refName `User`?",
      "which files did `gen-zod` produce?". Wholly rewritten each run.
    
    Both live under `.skmtc/<project>/.maps/` (gitignored by default).
    Mechanism: `SnippetBase` instrumentation caches each producer's
    rendered text; a post-pass walks the producer tree and AST-resolves
    byte spans to landmarks. The AST parser (`oxc-parser`) does not
    bundle into a Worker, so **inside the Worker** landmark names come
    from `Definition` identifiers and a host-side post-pass can fill AST
    detail later.
    
    A lighter, always-on channel exists too: the manifest's `previews`
    and `mappings` pair a per-Definition module with a **source
    descriptor** (`{ generatorId, operationPath, operationMethod }` etc.)
    — enough for a UI to say "this form was generated from `POST
    /contacts`".
    
    **If you are building provenance or "trace generated code back to
    its schema" tooling, build on this subsystem** (`Sidecar`,
    `GenerationMapEntry`, `AttributionState` — exported from
    `@skmtc/core/Anchors`). Do not reinvent it.
    
    Full treatment — the four-stage mechanism, the Sidecar v2 format,
    the worker-side parser omission, the `doctor` checks:
    [`attribution-and-gen-maps.md`](attribution-and-gen-maps.md).
    
    ## 10. The package graph and dependencies
    
    ### The `@skmtc/*` packages
    
    ```
    @skmtc/openapi-down-convert   (OAS 3.1 → 3.0, vendored fork)
            ▲
    @skmtc/convert                (Swagger 2 / 3.1 → 3.0; YAML/JSON parse)
            ▲
    @skmtc/core ◀───────────────── the engine; depended on by everything
            ▲   ▲   ▲   ▲
            │   │   │   └── @skmtc/gen-*   (generators; + peer generators)
            │   │   └────── @skmtc/server  (Hono HTTP host)
            │   └────────── @skmtc/worker  (Deno Worker host)
            └────────────── @skmtc/cli ──▶ @skmtc/mcp (MCP host wraps the CLI)
    ```
    
    `cli` also depends on `convert` and `worker`. Exact versions live in
    each package's `deno.json` — treat that as canonical, not this skill.
    
    ### The substrate
    
    SKMTC's design principle "build on the substrate, don't rebuild it"
    means **Deno is the platform**:
    
    - `deno bundle` is the bundler — it compiles a project's `worker.ts`
      into `bundle.js`.
    - `new Worker(...)` with `deno.permissions` is the sandbox.
    - **JSR** is the package registry. Generators and packages are
      *ordinary JSR packages* — there is no bespoke registry layer (that
      was explicitly rejected). Two registries are in play: `jsr.io`
      (public) and `jsr.skmtc.dev` (the SKMTC private registry).
    - The hosted Sandbox API runs on **Deno Deploy** (hence
      `DENO_DEPLOYMENT_ID` / `DENO_REGION` flowing into the manifest).
    
    The project is **Deno-locked**, and that is an accepted trade. The
    *generated output*, however, runs anywhere TypeScript runs.
    
    ### Key third-party dependencies
    
    | Dependency | Used for |
    |---|---|
    | `valibot` | Runtime validation of the manifest, settings, parse issues, generator configs, and sidecars. Each schema is paired with a TS type via an unread `_driftCheck` binding — **do not delete those**. |
    | `graphql` | GraphQL SDL parsing (Worker-side). |
    | `oxc-parser` | AST parsing for the attribution post-pass (chosen over `tsc` because `tsc` won't bundle into a Worker). |
    | `openapi-types` | Type definitions for OpenAPI documents. |
    | `hono` | The `@skmtc/server` HTTP framework. |
    | `@modelcontextprotocol/sdk` | The `@skmtc/mcp` server. |
    | `swagger2openapi` | Swagger 2.0 → OpenAPI 3.0 conversion inside `@skmtc/convert`. |
    | `@cliffy/command`, `ink`, `react` | CLI command parsing and terminal UI. |
    
    **Version-pin discipline:** inter-package `@skmtc/*` dependencies are
    pinned to **exact JSR versions — no caret ranges** — so a cloned
    generator and the engine it compiles against can't silently skew.
    `skmtc doctor` checks that a project's `@skmtc/core` pin matches the
    CLI's. Pins *can* lag between packages; always read `deno.json`.
    
    ## 11. Building infrastructure around SKMTC
    
    The user's context: a **GitHub-like SaaS** for hosting APIs and
    generators, running them, and supporting tracing and provenance.
    Here is how the engine's concepts map onto that platform — and where
    the engine stops and your platform code begins.
    
    ### The integration map
    
    | Platform concern | What the engine gives you | What you build |
    |---|---|---|
    | **Hosting APIs (schemas)** | A schema enters `toArtifacts` as `SkmtcDocumentInput` — `{ type:'oas', value: OpenAPIV3.Document }` or `{ type:'gql', value: sdlString }`. `@skmtc/convert` normalizes Swagger 2 / OAS 3.1 to 3.0 *before* the engine. | Schema storage, versioning, ingest validation, the normalize-on-ingest-or-on-run decision. |
    | **Hosting generators** | Generators are ordinary JSR packages (`@skmtc/gen-*`). `jsr.skmtc.dev` is already a JSR-compatible private registry. | A registry UX; discovery, search, featured ranking; the publish pipeline. |
    | **Running generators** | A run needs a **bundle** — `worker.ts` (templated from the import map) compiled to `bundle.js`. `toArtifacts` then executes it. Three reference hosts exist: local Worker (CLI), Hono `POST /artifacts` (`@skmtc/server`), the Worker message protocol (`@skmtc/worker`). | The run service: validate → convert → `toArtifacts` → return/store `{ artifacts, manifest }`. Bundle build & cache. Execution pooling if you need throughput. |
    | **Tracing** | `traceId` / `spanId` / `deploymentId` / `region` already populate the manifest and `StackTrail`; the `results` tree is trace-addressable. | Shipping them to an observability backend; cross-run dashboards. |
    | **Provenance** | The attribution / gen-maps subsystem (§8): `sidecars` + `generationMap`, opt-in via `attribution`. | Persisting and serving them; a viewer that maps generated code ↔ schema ↔ generator version. |
    
    ### `@skmtc/server` is the seed of the SaaS
    
    `server/src/createServer.ts` is ~150 lines: a Hono app with
    `POST /artifacts` (validate a discriminated body → convert →
    `toArtifacts` → `{ artifacts, manifest }`), `GET /generators`, and
    `POST /to-v3-json`. A production run service is this pattern with
    auth, tenancy, persistence, and bundle management added around it.
    The MCP host already calls a deployed instance of this server.
    
    ### Where the engine stops — build these at the platform layer
    
    The engine deliberately does **schema in → artifacts out** and
    nothing else. It has **no notion of**:
    
    - **Users, identity, auth, multi-tenancy.** A SKMTC "project" is a
      *generator configuration*, **not a tenant**. Tenancy is entirely
      yours to build.
    - **Persistence or run history.** `toArtifacts` returns a value; the
      on-disk `manifest.json` is overwritten every run.
    - **Result caching.** Determinism makes this safe and easy — cache
      `{ artifacts, manifest }` by a hash of `(schema, settings, bundle)`
      — but it is *your* layer, never the engine's.
    - **A warm execution pool, rate limiting, quotas, streaming.** Fact
      5: one cold Worker per run. Pool at the *container/process* level
      if you need throughput; never share a context across runs.
    - **A plugin / hook API.** There isn't one and it is a rejected
      design. Extension happens by cloning generators or by writing a
      new host — not by hooking the engine.
    
    ## 12. Counter-intuitive facts for infrastructure builders
    
    Beyond the five facts in §1. Left column = a reflex from generic
    backend / platform training data; right column = SKMTC reality.
    
    | Infra reflex | SKMTC reality |
    |---|---|
    | Keep a warm worker pool for throughput | One Worker per run, spawned cold, terminated after `RESULT`. Determinism depends on fresh contexts. Pool *containers*, not engine contexts. |
    | Do incremental builds — only regenerate changed operations | There are none. Every run is whole-document and from cold. Cache the *whole result* by input hash instead. |
    | Retry a failed run | Runs are deterministic — a retry reproduces the same failure exactly. Fix the input; don't retry. |
    | Wrap `toArtifacts` in try/catch and 500 on throw | It fails *open* — a bad schema logs a `ParseIssue`, it does not throw. Derive status from `manifest.parseIssues`, not from exceptions. |
    | Stream artifacts for large outputs | The Worker batch-`postMessage`s the whole result. No streaming protocol; structured clone handles typical sizes. |
    | Format the output before returning it | Output is unformatted *by design*. Formatting is the consumer's separate step. |
    | Let generators fetch schemas / templates at run time | The Worker has `net: false`. Everything a generator needs must already be in its inputs. Fetching is host-side, pre-engine. |
    | The on-disk `manifest.json` is the run record | It is overwritten every run. Persist each manifest yourself for history. |
    | Add an engine plugin/hook API for the platform | Rejected design. Extend by cloning generators or adding a host. |
    | A "project" is a tenant / a customer | A project is a *generator configuration*. It is not a unit of tenancy, identity, or billing. |
    | Generated code needs the SKMTC runtime at deploy time | Zero runtime. Output is plain committed source; the engine never ships to the consumer's bundle. |
    
    ## 13. Boundaries with other skills
    
    - **skmtc-generator** — authoring and editing generators (Projections,
      Snippets, the DSL, customization seams). Load when *writing
      generator code*.
    - **skmtc-cli** — running the CLI, configuring `client.json`,
      enrichments, skip/include. Load when *operating* SKMTC.
    - **skmtc-debug** — diagnosing broken runs (no output, wrong output,
      errors). Verify-first stance. Load when *something is broken*.
    - **skmtc-retro** — end-of-session reflection / friction capture.
    - **This skill (skmtc-architecture)** — the system mental model for
      reasoning about and building infrastructure *around* the engine.
    
    If the question is *how the system works* or *how to build a service
    around it*, this skill. If it is *how to write a generator*, *how to
    run a command*, or *why a run is broken*, hand off.
    
    ## 14. Cross-references
    
    **Concepts** —
    [`the-three-phases.md`](../../concepts/the-three-phases.md) ·
    [`the-worker-runtime.md`](../../concepts/the-worker-runtime.md) ·
    [`the-manifest.md`](../../concepts/the-manifest.md) ·
    [`stack-trail.md` (reference)](../../reference/api/stack-trail.md) ·
    [`cross-generator-coordination.md`](../../concepts/cross-generator-coordination.md) ·
    [`generators-as-packages.md`](../../concepts/generators-as-packages.md) ·
    [`attribution-and-gen-maps.md`](attribution-and-gen-maps.md)
    
    **Explanation** —
    [`design-philosophy.md`](../../explanation/design-philosophy.md) ·
    [`security-model.md`](../../explanation/security-model.md) ·
    [`comparison-to-other-tools.md`](../../explanation/comparison-to-other-tools.md) ·
    [`status-and-roadmap.md`](../../explanation/status-and-roadmap.md)
    
    **Reference** —
    [`glossary.md`](../../reference/glossary.md) ·
    [`manifest-format.md`](../../reference/manifest-format.md) ·
    [`llms.md`](../../llms.md) (consolidated operational reference)
    
    **Source landmarks** — `core/run/toArtifacts.ts` (engine entry) ·
    `worker/mod.ts` (Worker host) · `server/src/createServer.ts` (HTTP
    host) · `core/context/` (the three context classes) · `core/anchors/`
    (attribution / gen-maps).
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related