# .krs Syntax Reference > **English** (this file) · [日本語](syntax.ja.md) > Language version: **`.krs language v1.0`** (frozen — [ADR-1314](../adr/1314-krs-spec-v1-freeze.md); independent from every package's npm version — [ADR-2124](../adr/2124-version-vocabulary.md)). `karasu --version` reports the language version a build implements. ## File structure ```krs @import "default.krs.style" @import "theme/dark.krs.style" // multiple allowed; later ones take precedence // Domains not yet assigned to a service (top-level) domain Payment { label "Payment" } system ECPlatform { label "EC Platform" // service, user, and edge declarations } ``` --- ## Overview of concepts karasu explicitly separates **logical structure** and **physical structure**. ### Logical structure (what / why) | Keyword | Meaning | May contain | |---------|---------|-------------| | `system` | Container showing the relationships between owned/external services and clients | `service`, `user`, `client`, `domain`, `database`, `queue`, `storage` | | `user` | A user of the system (human or AI agent) | — | | `client` | User-delegated software the project itself ships (mobile / web / desktop / cli / device / extension / embed) | — | | `service` | An independent unit of business capability | `domain` | | `domain` | A business-concern boundary (top-level, inside a system, or inside a service) | `usecase`, `entity` | | `usecase` | A business task or operation within a domain | `resource` | | `resource` | A target that a usecase reads or writes (table, external API, file, etc.) | — | | `entity` | A conceptual data entity owned by a domain — a name and its relations, no attributes. Maps to an infra sub-resource with `table` | — | > Related TPLs: [TPL-2158](../test-perspectives/TPL-2158-catalog-fenced-against-parser-not-generated-doc.md) — this table is generated from `REFERENCE_DATA`, so it cannot be used as the independent source a sync test compares that catalog against; the kind and property columns are fenced against the parser instead. The recognized `client` form-factor tags are listed below. #### `client` form-factor tags (recognized) karasu's tag system is intentionally open — any tag is accepted and styles react via selectors. For `client` specifically, **seven names are recognized** as form-factor classifications. Icon Mode renders each with a kind-specific icon; layout hints are a future addition. Tags outside this list still parse and behave as ordinary user-defined tags; they simply do not trigger karasu's built-in form-factor treatment. | Tag | Form factor | |-----|-------------| | `[mobile]` | iOS / Android native app | | `[web]` | SPA running on the vendor's own origin | | `[desktop]` | Desktop app (Electron, native) | | `[cli]` | Command-line tool / SDK shipped to users | | `[device]` | IoT / dedicated terminal / KIOSK | | `[extension]` | Plugin / extension hosted by another application (browser extension, IDE extension, design-tool plugin) | | `[embed]` | Widget / SDK embedded into third-party web content (Stripe Checkout, Intercom, etc.) | Recommended: pick at most one form-factor tag per client. Multiple tags go in **one** bracket group, comma-separated (`[mobile, desktop]`); repeating the group (`[mobile] [desktop]`) is a parse error. Combining unrelated form factors is parseable but conveys no additional architectural meaning. `client` is reserved for software the project itself ships. Third-party browsers / IDEs / AI agents that consume the system are modeled as `user` (typically `[human]` or `[ai]`), not `client`. #### `handles` property — what a client/service exposes to its callers Both `client` and `service` may declare a `handles` property listing **domains exposed to callers**, each named by a node reference path (see [§ Node reference path notation](#node-reference-path-notation)) — a bare id or a qualifying suffix like `handles Backend.Order`. It is a *validated cross-reference*: the reference must be reachable through a one-hop expose rule, otherwise an `unresolved-handles` warning is emitted, anchored on the reference that failed. A multi-match is broadcast rather than ambiguity here, so `handles` has no `*-target-ambiguous` code (see the notation section). ```krs system Shop { service Backend { domain Order {} // self-owned — handles entry not required } service Bff { handles Order // re-export: Order is owned by Backend, reached via the edge below } client WebApp [web] { handles Order // surfaces Order to the end user via the BFF } WebApp -> Bff Bff -> Backend } ``` Forms accepted: ```krs client A [web] { handles Order } client B [web] { handles Order, Catalog, Inventory } client C [web] { handles Order handles Catalog } ``` See [§ Comma-separated value lists](#comma-separated-value-lists) for the list grammar every such property shares: one line per list, with a dangling comma reported on the comma. **Expose rule** (used by the validator): > A node `N` *exposes* the domain a reference `D` resolves to iff: > 1. `N` has a child `domain` whose full path the reference suffixes (self-owned), **or** > 2. `N` declares a `handles` reference naming the same domain, and at least one outgoing communication edge target also exposes it. > > The rule is evaluated against the **resolved node**, not the reference text (#2088): `handles Backend.Order` and a re-exporter's bare `handles Order` chain through the same domain. `delivers` and other declarative properties do not count as edges. The rule expands one hop at a time, so each link in a `client → BFF → backend` chain must be declared explicitly — there is no implicit auto-passthrough. ### Infra layer (shared data stores) — rendered on the system view Some data stores are shared by several services rather than owned by a single `usecase`. Declare them at the **top level of a `.krs` file** (or directly inside a `system` block) using one of the three infra-block keywords below; each may nest leaf sub-resources. These nodes render on the **system view**, in the dependency tier next to `[external]` services — services *depend on* shared infra, never the other way round. They were promoted to first-class nodes in [ADR-316](../adr/316-database-as-first-class-node.md). | Keyword | Layer | Intended use | May contain | |---------|-------|--------------|-------------| | `database` | system-level infra block | A database shared by services (RDBMS, document store, …) | `table` | | `queue` | system-level infra block | A message queue / topic shared by services | `queue-item` | | `storage` | system-level infra block | An object store / blob storage shared by services (S3, GCS, …) | `bucket` | | `table` | leaf, inside a `database` block | A table / collection in the database | — | | `queue-item` | leaf, inside a `queue` block | A message / event type carried by the queue. Written with the `queue` keyword inside a `queue` block (parsed internally as `queue-item`) | — | | `bucket` | leaf, inside a `storage` block | A bucket / container in the object store | — | - Only `label`, `description`, and `link` properties apply to infra nodes and their sub-resources; all are optional, and omission emits a warning, not an error. The `operations` CRUD property is **not** valid here — it is only meaningful on `resource` declarations inside a `usecase` (see below). - `database` / `queue` / `storage` are valid only at the top level or as a direct child of `system`. Nesting one inside a `service`, `domain`, or `usecase` is rejected with `infra-not-in-context`. - `table` / `queue-item` / `bucket` are leaf nodes: they accept properties and edges but no nested declarations. - A `usecase` ties one of its `resource`s to a shared sub-resource with dot-notation — `resource .` (e.g. `resource OrderDB.OrderTable`). The resolver aggregates these references to derive the `service → database` (and `service → queue` / `service → storage`) edges shown on the system view, and may synthesize `[read]` / `[write]` tags on the usecase→resource edges — see [docs/spec/tags-annotations.md](./tags-annotations.md#system-assigned-tags). - `[external]` may be applied to `database` / `queue` / `storage` for a store that lives outside the system boundary (a managed third-party DB, an external event bus, …). - `[index]` may be applied to a `database` to mark it as a **derived search / secondary index** — a store derived as an index to search the system of record quickly — and adds an `index` badge. It denotes a **role, not a technology**: a vector DB / ElasticSearch that is itself the system of record stays a plain `database` (no `[index]`). The concrete engine stays in the physical layer (`store { type "ElasticSearch 8"; realizes SearchIndex }`). See [tags-annotations.md](./tags-annotations.md). - Writing `resource OrderTable` *without* a matching `database` block is allowed, so you can discover resources bottom-up while sketching a `usecase`, then group them into a `database` block and switch to the dot-notation reference. For as long as the id resolves to **nothing at all** (no dot-notation ref, and no unique `entity` of the same name), it warns `unassigned-resource` and **is drawn, but only inside its own usecase's drill-down view**: it is *not* promoted to a sibling node in the domain view, because promotion is what resolving the reference buys. Declaring a matching `entity` promotes it and clears the warning with no edit to the usecase, `database` block or not (see [`entity` declaration](#entity-declaration--conceptual-domain-entities)). Sketching bottom-up does give visual feedback, then, just one level deeper until the id resolves. - The infra-block **keyword** `table` (a `database` leaf, declaring the shared node) and the shape **tag** `[table]` (a usecase `resource`'s draw-shape) are related, not the same. A usecase references an infra leaf with a `resource` via the dot-notation above, and karasu **infers the shape tag from the referenced infra sub-resource kind** — `table` → `[table]`/cylinder, `queue-item` → `[queue]`, `bucket` → `[storage]` — so the reference is drawn in the same shape as the store it points to. The keyword declares the node's *kind*; the `[...]` tag is a suffix that sets only a `resource`'s *shape* (and may also be written by hand). The same word in two positions never collides. See [tags-annotations.md](./tags-annotations.md) for the full guidance. ```krs system ECPlatform { service ECommerce {} // domains / usecases omitted for brevity database OrderDB { label "Order DB" table OrderTable { label "Orders" } table ProductTable { label "Products" } } queue OrderEvents { queue OrderPlaced { label "Order placed" } // declared with `queue`, parsed as a queue-item } storage MediaStorage { bucket ProductImages { label "Product images" } } database ProductSearch [index] { // derived search index, not the SoT label "Product Search" table Products { label "Indexed products" } } } ``` > Related TPLs: [TPL-1415](../test-perspectives/TPL-1415-shared-vocabulary-dual-representation.md) — the infra-sub-kind → shape-tag inference (`INFRA_SUB_KIND_TO_TAG`) and the shape-tag table are two representations of one vocabulary that must stay in sync. [TPL-2200](../test-perspectives/TPL-2200-render-claim-names-its-view-level.md) — a claim that something "is rendered" names the view level it is rendered at, and both sides (the level it is promoted to, the level it stays in) are fenced; the unassigned-`resource` bullet above said only "rendered as an orphan node" and drifted for months (#2200). #### Store-scoped ER view (entity relations projected onto a `database` canvas) Drilling into a `database` shows its `table` leaves. That canvas also draws the **relations between those tables**, derived at render time from the `entity` layer: an entity relation whose **both** endpoints carry a `table .` mapping into the same store is drawn as a leaf-to-leaf edge on that store's canvas. Nothing is written to the `.krs` for this. The projected edge keeps the relation's label and its `->` / `-->` kind, and carries the system-assigned `[projected]` tag, which colours it (see [tags-annotations.md](./tags-annotations.md#system-assigned-tags)); line style stays owned by `[sync]` / `[async]`. **The recorded side.** A `table` leaf may declare edges of its own (`table orders { orders -> customers }`), and `translate --from db` writes one edge **per distinct source-target pair** it finds, inside the emitted `database` block: a declared `REFERENCES` / `FOREIGN KEY` becomes an untagged edge, a Soft FK (a `_id` / `_code` column naming another table) becomes `orders -> products [inferred]`, and under the default aggregate granularity a folded child's foreign keys roll up to its root with no self-edge. Several foreign keys to the same target are one edge at either granularity (two `customer_id` columns on `orders` do not draw two arrows to `customers`), and the pair is untagged as soon as **one** contributing foreign key is declared. This is what makes the view useful with **no `entity` layer at all**: a schema dump gets an ER view straight out of `translate`. A foreign key whose target is not in the dump is not recorded. The marks on the canvas are therefore three states on one axis, *who confirmed the relation*, never *which tool wrote the line*: | Mark | Meaning | Where it comes from | | --- | --- | --- | | untagged | confirmed | written by hand, or emitted from a declared foreign key | | `[inferred]` | guessed from a column-name convention | `translate --from db` (Soft FK); delete the tag once confirmed | | `[projected]` | asserted by the entity layer | assigned at render time, never in `.krs` | **Union rule.** When the record and the projection produce the same ordered pair, the canvas draws **one** edge, the recorded one, and it takes only what it lacks: the relation's label transfers when the record has none (a written label wins), while the record's `->` / `-->` kind always stands, so a `-->` relation over a `->` record stays solid. When they disagree on direction (recorded `A -> B`, projected `B -> A`) only the recorded side is drawn and the label does not move, since `"belongs to"` read backwards would be false. A recorded edge whose target is a leaf of **another** `database` is not drawn on either canvas; it gets the ordinary `edge-endpoint-not-at-scope` warning, since a `table`'s peers are its own store's leaves. **The diff is reported, not only drawn.** `karasu coverage` compares the two sets per `database` and reports four lists of ordered `{from, to}` leaf pairs (the same shape as `unmappedButReferenced`, in the physical section and in `--format json`): `recordedWithoutProjection` (the store states a relation the logical model lacks: mechanically repairable), `projectionWithoutRecorded` (application-level integrity: a fact, not a defect), `directionMismatch` and `kindMismatch` (the two disagreements the canvas resolves toward the recorded side, kept visible only here). The axis is recorded-vs-projected, not FK-vs-app-level: the first list holds every recorded relation the entity layer lacks, `[inferred]` Soft-FK edges included, and the report does not compare against the DDL. Endpoint resolution is the entity view's: the relation must start at the entity that declares it, a bare target is intra-domain only, and a qualified `DomainId.EntityId` target resolves within the owning system. A relation the entity view drops is not projected either. **This view is lossy and is not a complete ER diagram of the store.** It shows exactly the relations that travel through a `table` mapping on both ends: - a relation touching an entity with **no** `table` mapping does not appear (tableless entities are a legitimate state, so this is not a defect; `coverage` reports them as `tablelessEntities`); - a relation whose endpoints map into **two different** stores appears on neither canvas (it is already visible as a `service → database` edge on the system view); - a polymorphic reference (one column, several possible target tables) is whatever the entity layer chose to write. ```krs system Shop { service OrderService { domain Ordering { entity Order { table OrderDB.orders Order -> LineItem "has" // projected: orders -> line_items Order --> Customers.Customer "placed by" // projected, dashed: orders --> customers Order -> AuditEntry "audited by" // not projected: AuditEntry is tableless } entity LineItem { table OrderDB.line_items } entity AuditEntry {} } } service CustomerService { domain Customers { entity Customer { table OrderDB.customers } } } database OrderDB { table orders {} table line_items {} table customers {} } } ``` > Related TPLs: [TPL-2585](../test-perspectives/TPL-2585-partial-mapping-view-states-its-denominator.md) — a derived view that travels through an optional mapping counts what did not project (`coverage`) and states in the spec that it is not a complete diagram. [TPL-510](../test-perspectives/TPL-510-derivation-tag-semantics.md) — `[projected]` is colour only; the `[sync]` / `[async]` line style of the source relation is preserved. [TPL-1936](../test-perspectives/TPL-1936-cross-domain-entity-reference-qualified.md) — the projection resolves endpoints with the entity view's rules, so a bare cross-domain id is not projected and a qualified one is. [TPL-1944](../test-perspectives/TPL-1944-inferred-tag-only-soft-fk.md) — a recorded table edge is `[inferred]` only when every contributing FK is a Soft FK; one declared FK leaves it untagged. ### Organizational structure (who owns what) — rendered as a separate diagram An independent axis from logical/physical, describing the **ownership** of services and domains. `organization` is the root, with nested `team` declarations. Each team lists the nodes it owns via `owns` and may contain `member` entries. | Keyword | Meaning | May contain | |---------|---------|-------------| | `organization` | Root of an organization. Multiple declarations allowed | `team` | | `team` | A team with responsibility. May be nested | `team`, `member`, `owns` | | `member` | An individual belonging to a team | — | A related grouping overlay — **`boundary`** (experimental) — lets an author declare semantic clusters *within* the system view, drawn as a second "Group by" axis alongside team ownership. See [§ Grouping the system view (`boundary`)](#grouping-the-system-view-boundary--experimental). ### Physical structure (how) — rendered as a separate diagram Deployment units are declared inside a `deploy` block using a kind keyword. All properties are optional. When omitted, a warning is emitted rather than an error. | Keyword | Description | Properties | |---------|-------------|------------| | `war` | WAR / EAR (Servlet / EJB container) | `runtime`, `realizes` | | `jar` | Executable JAR (e.g. Spring Boot) | `runtime`, `realizes` | | `oci` | Container image | `image`, `runtime`, `realizes` | | `lambda` | AWS Lambda | `runtime`, `realizes` | | `function` | Azure Functions / Google Cloud Functions | `runtime`, `realizes` | | `assets` | Static files / SPA (served via CDN) | `runtime`, `realizes` | | `job` | Batch job. Without schedule: one-shot; with schedule: recurring | `runtime`, `schedule`, `realizes` | | `artifact` | Any kind not covered above | `type`, `runtime`, `realizes` | | `store` | Managed data store realizing a logical infra node (Aurora PostgreSQL, Amazon SQS, S3, …) | `type`, `realizes` | --- ## Node declaration ``` [] @ [{ }] ``` `id` is required. Tags, annotations, and the body block are optional. --- ## Property block Properties are written inside the body block `{ }`. Properties come before child nodes and edges. | Property | Syntax | Applicable kinds | Description | |----------|--------|-----------------|-------------| | `label` | `label ""` | All | Display name on the diagram. Defaults to the id when omitted | | `description` | `description ""` | All | Description text (use `"""..."""` for multi-line) | | `role` | `role ""` | user | Actor archetype, or a short one-line description of what this user does. **Not** an authz primitive (no `requires role = ...` predicate, no RBAC permission bundle) — see [ADR-832](../adr/832-no-runtime-authz-modeling.md) and [ADR-1281](../adr/1281-user-role-keyword-clarification.md) | | `delivers` | `delivers [, ...]` | service | Client(s) this service ships (BFF / SSR pattern). The renderer draws each entry as a distinct dashed edge from the service to the referenced `client` | | `link` | `link "" "