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
Install
npx skills add https://github.com/skmtc/skmtc/tree/main/deno/docs/skills/skmtc-architecture
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install skmtc-skmtc@llmmart
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.
No plugin registry, no dependency graph, no topological sort. Cross-generator coordination is a
Mapcache keyed by(identifier.name, exportPath). Generator execution order does not affect output.Render does not run Prettier or Biome. No formatter runs inside
@skmtc/core. Generated output is unformatted by design; consumers format separately.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.
OasSchemais a union type, not a class hierarchy. Sibling classes (OasObject,OasArray,OasString, …) each independently implement.isRef()returningfalse.OasRefis a sibling with.isRef()returningtrue. There is noBaseSchema.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.toArtifactsis 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 nolangfield. TypeScript (@skmtc/lang-typescript) and Kotlin (@skmtc/lang-kotlin, proven bygen-kotlinDTOs +gen-kotlin-springcontrollers) are the production languages; otherlang-*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.jsonis 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 aParseIssue;removeErroredItemsthen prunes one hop of$refconsumers 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
Fileto a string by joining re-exports, imports, and definitions. No formatter runs. Output isRecord<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.Documentis 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
GqlDocumenthas 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)(orinsertModel/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.optionsis typed data the caller passes on the insert; a peer that uses it folds it into the name. - Cache hit → the existing
Definitionis 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 aDefinition, 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 — nolangfield; 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
transformhook runs once per matched operation/model and produces output by callingregister/insert*— its return value is discarded. - A Projection is a named, file-scope artifact (
export const X = …), wrapped in aDefinition, 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/.mustachefiles. Composition is by${...}interpolation of anythingStringable. (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/spanIdare OpenTelemetry-shaped. TheStackTrailcarries them as its root frames, and theresultstree is keyed by stack-trail strings — so an outcome is addressable astraceId → spanId → generate → generatorId → item.deploymentIdandregioncome 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 anyparseIssue.level === 'error'. The engine fails open — a bad schema does not throw, it logs. Your HTTP host must compute status frommanifest.parseIssues, not from atry/catcharoundtoArtifacts. resultsoutcomes:successmeans "the transform ran without throwing" — not "produced output".notSupportedis normal (a generator that doesn't apply to an item).skippedis filter exclusion. Diagnose "no output" via thefilesmap, notresults.- 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?".srcPtris a schema pointer likeoas:#/components/schemas/User. - Generation map (
_map.ndjson) — a project-level, per-Definition reverse-query index: "which files came from refNameUser?", "which files didgen-zodproduce?". 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 bundleis the bundler — it compiles a project'sworker.tsintobundle.js.new Worker(...)withdeno.permissionsis 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) andjsr.skmtc.dev(the SKMTC private registry). - The hosted Sandbox API runs on Deno Deploy (hence
DENO_DEPLOYMENT_ID/DENO_REGIONflowing 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.
toArtifactsreturns a value; the on-diskmanifest.jsonis 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.
Reviews (0)
No reviews yet.
No comments yet.