Tags and Annotations Reference
English (this file) · 日本語
Tags ([...])
Section titled “Tags ([...])”Tags declare architectural meaning. Styles change in response to tags.
A tag is a semantic declaration, not a direct appearance override. Visual control is handled in .krs.style.
Names lex as kebab-case identifiers. [my-team-internal-tag] is one tag named my-team-internal-tag, and the same rule holds at every position that accepts open vocabulary: annotation names (@my-mark), capability names, and legend ref targets. A fragment may be spelled like a keyword ([legacy-system] is one tag). .krs.style folds hyphens into identifiers natively, so the tag written in .krs and the selector written in .krs.style land on the same name. (#2509)
Related TPLs: TPL-2509 — every kebab-case name position shares one lexical helper; a new name position must be verified against hyphenated names on both the
.krsand.krs.stylesides.
| Tag | Meaning | Effect on default rendering |
|---|---|---|
[external] |
Outside the system boundary | Dashed border, gray-toned color |
[index] |
A derived index for fast search over the system of record — a role, not the SoR itself (omit on a vector DB / ElasticSearch that is itself the SoR) | Adds an index badge to the database node |
[cache] |
Not the system of record — a store whose loss is recoverable by rebuilding it (recompute, refetch, re-login); a TTL is typical | Adds a cache badge to the database / storage node |
[analytics] |
A derived store ingested from the system of record for analysis and aggregation (DWH / data lake) | Adds an analytics badge to the database / storage node |
[async] |
Asynchronous communication (for edges) | Dashed arrow |
[sync] |
Synchronous communication (for edges, default) | Solid arrow (default) |
[human] |
A human user | Used only on user nodes. No effect on default style |
[ai] |
An AI agent | Used only on user nodes. No effect on default style |
[mobile] |
Mobile native app (client) | Recognized form-factor tag for client nodes |
[web] |
Browser SPA (client) | Recognized form-factor tag for client nodes |
[desktop] |
Desktop app (client) | Recognized form-factor tag for client nodes |
[cli] |
Command-line tool / SDK (client) | Recognized form-factor tag for client nodes |
[device] |
IoT / dedicated terminal / KIOSK (client) | Recognized form-factor tag for client nodes |
[extension] |
Host-app plugin — Chrome / VS Code / Figma, etc. (client) | Recognized form-factor tag for client nodes |
[embed] |
Widget / SDK embedded in third-party sites (client) | Recognized form-factor tag for client nodes |
[table] |
Table-like resource (shape: cylinder) | Rendered as a cylinder shape |
[queue] |
Queue-like resource (shape: queue) | Rendered as a queue shape |
[api] |
API-like resource (shape: hexagon) | Rendered as a hexagon shape |
[storage] |
Storage-like resource (shape: cloud) | Rendered as a cloud shape |
The seven
clientform-factor tags are recognized by karasu — Icon Mode renders each with a kind-specific icon (Phase 2 of #823); layout hints (Phase 6) are a future addition. Tags outside the builtin table are accepted in v1.x but deprecated — see Non-builtin tag names are deprecated below.
Shape tags mirror the infra-block keywords — they are related, not interchangeable. An infra-block keyword (
tableinside adatabase,queue-iteminside aqueue,bucketinside astorage) declares the actual shared-store node on the system view. A usecase’sresourceis the operational reference to what that usecase reads or writes; when aresourcepoints at an infra leaf via dot-notation —resource OrderDB.OrderTable— karasu infers the matching 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 shape tags[table]/[queue]/[storage]therefore deliberately mirror the infra sub-resource kinds; you can also write them by hand on anyresourceas a pure shape hint when there is no infra leaf to reference.[api](hexagon) has no infra counterpart — it is a manual-only shape for API-like resources. The same word in two positions never collides: the keyword starts a declaration and sets a node’s kind; the[...]tag is a suffix on aresourceand sets only its shape — they are complementary layers linked by the resource reference. See the Infra layer section of syntax.md.Related TPLs: TPL-1415 — 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.
database [index]marks adatabasenode as a derived search / secondary index — an ElasticSearch / OpenSearch cluster, or a vector store such as pgvector / Pinecone / Weaviate — rather than the system of record. It keeps the database cylinder and adds anindexbadge. The concrete technology stays in the physical layer viastore { type "ElasticSearch 8"; realizes SearchIndex }, so the logical model does not churn when the engine is swapped. The same store can be both the system of record and its own index (e.g. Postgres + pgvector) — there the[index]tag is simply omitted.[index]denotes a role, not a technology: tag a secondary store that is derived as an index to search the system of record quickly. Even when it is a vector DB / ElasticSearch, do not add[index]if that store is itself the system of record. Background: ADR-316, Issue #1718.Related TPLs: TPL-1503 —
[index]is an accepted tag that must carry an effect (theindexbadge), not merely a label.
Store role tags — one axis, four states
Section titled “Store role tags — one axis, four states”[index], [cache] and [analytics] form a single axis: which way this store is not the system of record. No tag means it is the system of record, so four states say everything about a store’s role.
| Tag | What kind of non-SoR store | Applies to |
|---|---|---|
| (no tag) | The system of record | any store |
[index] |
Derived for search | database |
[cache] |
Recoverable by rebuilding — recompute, refetch, re-login | database / storage |
[analytics] |
Ingested from the SoR for analysis | database / storage |
The test for [cache] is one question: if this store vanished, is business data lost? If it is, the store is a system of record — do not tag it. A session store, a Redis cache and a Cloudflare KV namespace all pass; so does a CDN origin cache, a bucket of generated thumbnails or rendered artifacts, or a scratch area for exports (which is why [cache] applies to storage too). Note that the test is volatility, not derivation: a session store is the record of that session rather than a copy of something else, and a rule limited to derived copies would miss the most common case. [index] — derived and for search — sits inside that definition without contradiction.
[analytics] marks the warehouse / data-lake side: a store ingested from the system of record for analysis and aggregation. It applies to storage because a data lake is typically object storage (Parquet on S3 / GCS). Where a warehouse also holds data ingested from other systems, it is still not this system’s SoR, so the tag still applies. The tag is named for the role, not for the product category — [warehouse] would name the thing (Snowflake, BigQuery), where [analytics] names what the derivation is for, matching [index].
All three stay out of the shared-store diagnostics. shared-infra-fan-in and cross-domain-store-access describe a shared system of record; several services reading one search index, one cache or one warehouse is a normal shape, not the Database-per-Service smell.
Where this axis stops. Role tags express the SoR difference within one kind, and nothing else. A difference of technology (graph, time-series, column-oriented) belongs to the physical layer’s store { type "…" }; a difference of operational placement (read replica, shard) belongs to the physical layer or is not modeled at all. That is why [kv], [graph], [timeseries] and [replica] are not builtin tags and warn as tag-not-builtin: judging a new role tag reduces to the same single question as [cache] does — is this about being the system of record?
Related TPLs: TPL-2172 — the stopping rule above is the third of the three questions a builtin-addition request must pass, and the rejections it produced are recorded rather than re-argued. TPL-1503 — every kind listed in a tag’s
appliesTocarries the badge, so a tag is never accepted-and-inert on one of its own kinds.
Non-builtin tag names are deprecated (v1.x)
Section titled “Non-builtin tag names are deprecated (v1.x)”Bare [<identifier>] still accepts any name in v1.x — the v1.0 freeze (ADR-1314) keeps parse behaviour unchanged — but a tag outside the tool vocabulary (the builtin table above plus the system-assigned tags below) is deprecated: karasu emits a tag-not-builtin warning on every use. There is deliberately no suppression condition — a .krs.style selector or a legend ref proves the name is intentional, but intent does not change the outcome: syntax v2.0 accepts tool vocabulary only (still enforced as a warning, never a parse error — existing files keep parsing). Migration targets:
- Membership or model-specific labeling (PCI scope, PII, “requires auth”) → the
facetconstruct: declare the set once at the top level and writefacets <id>on the elements. - A missing archetype → request a builtin tag addition. A deprecated tag keeps working meanwhile — warned, without default-rendering effect. #2172 is the worked example of this route:
[cache]and[analytics]were adopted,[kv](a technology, not a role) and[bff](already expressed structurally bydelivers <ClientId>) were rejected with their reasons recorded.
A .krs.style rule that targets such a name ([pci] { … }) is deprecated on the same terms (style-tag-selector-not-builtin) and rewrites to a facet selector. Both halves warn: the tag on the node and the selector in the sheet are two edits, and reporting only one leaves the other unfound.
See Vocabulary registers below for how to pick the right construct.
Related TPLs: TPL-1503 — non-builtin tag names previously sat in the forbidden fourth state (accepted, inert, undocumented);
tag-not-builtinresolves them into state (2), warned as unknown. TPL-2172 — a builtin-addition request arriving through this route is judged by three questions (register / already expressible / stopping rule), and rejections are recorded.
Example
Section titled “Example”system Shop { service Payment [external] { label "Payment Service" } service ECommerce {} service Inventory {} user Customer [human] { label "Customer" } user AIAgent [ai] { label "Order Automation Agent" }
ECommerce --> Inventory "Sync inventory" [async]}Annotations (@...)
Section titled “Annotations (@...)”Annotations are metadata expressing lifecycle and state. They are a separate concept from tags.
| Annotation | Meaning | Default rendering |
|---|---|---|
@deprecated |
Slated for removal | ⚠ badge, node rendered semi-transparent |
@new |
Newly added | ✦ badge |
@experimental |
Experimental | ⚗ badge |
@migration_target |
Migration target | → badge |
@planned |
Placed by design, but does not exist yet | ◇ badge |
@draft |
Asserted but not confirmed by a human | ✎ badge |
Example
Section titled “Example”Multiple annotations can be applied. Tags and annotations can be combined.
service Legacy [external] @deprecated @migration_target { label "Legacy System"}service NewAPI @new @experimental { label "New API"}Domain coexistence during migration
Section titled “Domain coexistence during migration”When @deprecated or @migration_target is applied to a domain, duplicate domain IDs within the same system are tolerated (modeling a migration period).
The domain carrying @migration_target becomes the preferred navigation target.
system OrderSystem { service LegacyService { domain Contract @deprecated { // migration source — scheduled for removal -> Billing } } service NewService { domain Contract @migration_target { // migration target — preferred navigation -> Billing } }}Duplication is tolerated as long as at least one side carries
@deprecatedalone, or@migration_targetalone. If neither annotation is present, the duplicate remains an error.
Non-builtin annotation names are deprecated (v1.x)
Section titled “Non-builtin annotation names are deprecated (v1.x)”@<identifier> still accepts any identifier in v1.x — the open annotation set itself is frozen by ADR-1314 — but a name outside the builtin table above is deprecated: karasu emits an annotation-not-builtin warning on every use, with no suppression condition (a stylesheet selector proves intent, but intent does not change the outcome: syntax v2.0 accepts tool vocabulary only, still enforced as a warning, never a parse error). Non-builtin annotations have no default rendering; in v1.x they remain syntactically valid targets for annotation selectors in .krs.style, and that use is now deprecated too (style-annotation-selector-not-builtin) — the styling hook has moved to facet selectors, which is where the before/after rewrite is written out. Migration targets:
- Membership or model-specific labeling (team ownership marks, audience labels) → the
facetconstruct. - A missing lifecycle state → request a builtin annotation addition. #2172 is the worked example:
@plannedwas adopted, while@canary(a runtime rollout state that lives for hours, not the slowly-changing structure karasu models — and overlapping@experimental) and@sunset(@deprecatedalready says it) were rejected. A long-lived canary is@new @experimental; a coexisting old and new is@migration_target.
The near-miss typo hint (annotation-possible-typo, info) also still fires: a typo in a builtin name (e.g. @depracated) would otherwise surface only as “my badge did not appear”. The hint stays suppressed for names that appear in a stylesheet annotation selector. Both diagnostics coexist during v1.x — a near-miss can carry both — and are consolidated in v2.0.
service Billing @team_alpha // deprecated: annotation-not-builtin warningservice Legacy @depracated // warned twice: typo hint (info) + not-builtin (warning)Related TPLs: TPL-1503 — the deprecation keeps non-builtin names in state (2), warned as unknown, instead of the former undocumented open-set acceptance. TPL-2172 — the same three-question gate applies to a requested builtin annotation, where the lifecycle register is the first filter.
Annotation parameters
Section titled “Annotation parameters”A built-in lifecycle annotation can carry parameters that record migration intent, with @name(key: "value"[, key: "value"]*):
service Legacy @deprecated(until: "2026-Q3")service NewSvc @migration_target(from: LegacyMonolith)Recognized keys (built-ins only):
| Annotation | Key | Meaning |
|---|---|---|
@deprecated / @experimental |
until |
When the node is expected to be removed / stabilized |
@migration_target |
from |
The node this one is migrating away from |
@draft |
confidence |
How sure the author is of this statement — low / medium / high |
- Graceful degradation by precision: a
untilvalue that parses as a date (YYYY-MM-DD), year-month (YYYY-MM), or quarter (YYYY-Qn) is machine-usable (sortable / filterable); any other string (e.g."sometime next year") is kept verbatim as an opaque, display-only value. No validation error is raised for opaque values. - No runtime evaluation:
untilis recorded intent, not a deadline — karasu never compares it to the current date (no “overdue” diagnostic). Consistent withjob.schedule(stored, not simulated) and the warn-don’t-error stance. - Unsupported parameters warn, not silently ignored: a parameter on any other annotation, or with an unrecognized key, is dropped with an
annotation-param-unsupportedwarning (TPL-1503 — accepted vocabulary must have an effect or be warned). Custom annotations are param-less for now. - The annotation name list is unchanged by parameters, so
.krs.styleannotation selectors (@deprecated) and annotation inheritance are unaffected.
Related TPLs: TPL-1503 — an
@name(key: …)with an unrecognized key/annotation is warned, never silently accepted.
@draft — asserted, not confirmed
Section titled “@draft — asserted, not confirmed”@draft marks a statement the model makes but nobody has confirmed. It is the honesty layer for models that were not written by hand: karasu-nest reverses a repository into .krs with an LLM (ADR-1990 decision 4), and a generated model that cannot say which parts it guessed at invites the reader to trust all of it equally.
system Payments { service Ledger { label "Ledger" domain Posting } service Reconciliation @draft(confidence: "low") { label "Reconciliation" // The seam between posting and reconciliation was a judgement call. domain Settlement @draft }}-
The mark is the unit, the level is a refinement. A bare
@draftis complete.confidenceis optional and takeslow/medium/high; any other string is kept verbatim as an opaque, display-only value, exactly likeuntil. A reviewer writingconfidence: "we argued about this one"is recording something real, and rejecting it would push that note into a comment where nothing can read it. -
Per node, not per document. The spike behind decision 4 found that a generated decomposition errs by splitting at genuine judgement-call seams rather than by scrambling, so the useful signal is which seam was uncertain. A document-level score would average that away.
-
No gate. karasu never refuses to render, warns about, or downranks a low-confidence node. The level is recorded judgement, consistent with
untilbeing intent rather than a deadline. -
Removing the mark is the point.
@draftis what a human review deletes. That deletion is the human ratchet ADR-1990 decision 4 rests on (#2228), so the annotation is designed to be cheap to remove: one token, no restructuring. -
An absent mark means nothing was claimed, not that something was checked. karasu does not track review state, so a node without
@draftis simply a node nobody marked. In a hand-written model that is the normal case; in a generated one, treat the generator’s marking as the only signal about itself. A generated model that carries no@draftanywhere is making a strong claim, and is worth doubting.
@draft is a lifecycle annotation, not a tag or a facet: it describes the state of a statement in a review process, the same register as @new and @experimental, and it is tool-owned rather than a user-declared set.
Related TPLs: TPL-1995 — generated content states its own uncertainty where the uncertainty is, and the mark is removable by the human who resolves it. TPL-1503 —
@drafthas a default badge in the same PR that accepts the name. TPL-2172 — the three-question gate for adding a builtin annotation.
@planned — designed, not yet built
Section titled “@planned — designed, not yet built”@planned marks an element that the design places but that does not exist yet. The other lifecycle states all presuppose existence: @new is a real addition, @experimental is real but unstable, @deprecated is real and on the way out. Nothing said “not there”.
system Payments { service Ledger { label "Ledger" domain Posting } service Reconciliation @planned { // Agreed in the design review; no code yet. label "Reconciliation" domain Settlement }}- The moment a diagram is drawn is usually the moment of a decision, and what a decision draws is the target state. An architecture note that cannot say “this part is the plan” either omits the plan — losing the reason the diagram was drawn — or draws it as if it shipped.
@plannedis about existence;@draftis about confidence.@plannedsays the element is not built yet (the author is certain about what it is);@draftsays nobody has confirmed the statement (the author is uncertain that it is right). They compose: a@planned @draftservice is a proposal an LLM inferred and nobody has reviewed.- No gate. As with every other lifecycle annotation, karasu records the mark and never refuses to render, downranks it, or exempts it from a diagnostic. A
@plannedservice no deploy unit realizes still reportsunassigned-service— that is a true statement about the model as written, and silencing it would make the annotation a way to hide gaps. - Removing the mark is shipping it. Like
@draft, the annotation is one token to delete.
Related TPLs: TPL-2172 —
@plannedpassed the three-question gate (lifecycle register / no existing way to say “not yet” / no state-enumeration creep) and ships with a badge in the same PR. TPL-1503 — the◇ Plannedbadge is that effect.
Client capabilities
Section titled “Client capabilities”capability <name> declares a device or browser capability the client requests permission to use. See docs/spec/syntax.md for the syntax.
The identifier set is open — any kebab-case identifier is accepted, no warnings are emitted for names outside the recommended set, and authors can express domain-specific capabilities (industry devices, internal-only features) freely. The recommended set below covers the cases the validator and editor tooling expect to see most often.
Recommended capability identifiers
Section titled “Recommended capability identifiers”| Group | Identifiers |
|---|---|
| Web / browser | camera, microphone, geolocation, notification, push, clipboard, webauthn, bluetooth, usb, midi, screen-wake-lock, accelerometer, gyroscope, storage-access |
| Mobile (additional) | contacts, calendar, photo-library, face-id, touch-id, background-processing, local-network, bluetooth-le-peripheral |
| Desktop (additional) | file-system-access, global-shortcuts, auto-launch, screen-recording |
| IoT / device (additional) | gpio, serial, zigbee, lora, nfc, rfid |
Naming conventions
Section titled “Naming conventions”- Use kebab-case (
screen-wake-lock,face-id). - Prefer the Web Permissions API / W3C name when one exists (
geolocation,notification). - Avoid OS-specific identifiers (
android.permission.CAMERA); use the abstract feature name. - For names outside the recommended set, attach a
descriptionso other readers understand what the capability covers.
What capability is NOT
Section titled “What capability is NOT”| Concept | Where it lives |
|---|---|
Operation-tied storage (localStorage, indexedDB, keychain) |
resource <storageKind> "<name>" |
| HTTP session / authentication credentials | Separate vocabulary, tracked under #834 |
| Runtime authorization (RBAC permission bundles, license / feature flag gates) | Not modelled in karasu — see ADR-832. The user.role property is an actor-archetype label, not an authz primitive — see ADR-1281 |
Vocabulary registers — boundary / annotation / tag / facet
Section titled “Vocabulary registers — boundary / annotation / tag / facet”karasu separates “what kind of label is this?” into four registers. The tag and annotation vocabularies are tool-owned; the sole user extension point is the facet construct (experimental).
| Register | Construct | Vocabulary | Question it answers |
|---|---|---|---|
| Archetype | tag [...] |
tool-owned (builtin table above) | What is this element, architecturally? ([external], [index]) |
| Lifecycle | annotation @... |
tool-owned (builtin table above) | What development state is it in? (@deprecated, @new) |
| View grouping | boundary |
user-declared ids | How should peers be grouped in this view? (see syntax.md) |
| Set membership | facet (experimental) |
user-declared ids | Which externally defined set does it belong to? (PCI scope, PII, “requires auth”) |
Worked decomposition — modeling PCI compliance and authentication without misusing tags:
| Concern component | Register | Where it goes |
|---|---|---|
| The element’s architectural role (a search index, an external store) | tag | builtin tags — [index], [external] |
| “This table holds cardholder data” / “this entity is PII” (regulatory membership) | facet | facets pci / facets pii, against a top-level facet declaration |
| “This usecase requires authentication” (policy scope) | facet | facets requires_auth, with the policy itself in the declaration’s description / link |
| Who may call it, under which plan / condition (rule content) | prose | description + link to the policy document — never modelled (ADR-832) |
The registers matter because membership semantics differ from archetype semantics: an element is a database whether or not it is in PCI scope, and a diagram where 9 of 10 in-scope elements carry a membership tag silently reads as a false audit guarantee. Membership therefore gets its own construct with declared metadata (label / description / link) instead of borrowing the tag namespace.
Related TPLs: TPL-1503 — each register’s accepted vocabulary must have an effect or be warned; the v1.x deprecation diagnostics (
tag-not-builtin/annotation-not-builtin) keep the tool-owned registers in state (2).
Difference between tags and annotations
Section titled “Difference between tags and annotations”| Tag | Annotation | |
|---|---|---|
| What it expresses | Architectural position / role | Lifecycle / development state |
| Example | [external] (outside the boundary) |
@deprecated (scheduled for removal) |
| Style impact | Controlled by tag selectors in .krs.style |
Controlled by annotation selectors in .krs.style |
System-assigned tags
Section titled “System-assigned tags”The following tags are not written by users in .krs files; they are automatically assigned by the tool.
They can be referenced and overridden via tag selectors in .krs.style.
Automatic tags on edges
Section titled “Automatic tags on edges”| Tag | Assignment condition | Default style |
|---|---|---|
[implicit] |
An implicit service-level edge derived from domain edges | Amber (#F59E0B). Line style follows the kind of the source domain edge ([async] = dashed, [sync] = solid) |
[async] |
An edge declared with --> |
Dashed |
[sync] |
An edge declared with -> |
Solid |
[cyclic] |
Detected as part of a cyclic dependency | Red (#EF4444) solid |
[write] |
A synthesized usecase→resource edge whose target resource declares any of create / update / delete in its operations |
stroke-width: 2, label "W" |
[read] |
A synthesized usecase→resource edge classified as read-only (no write verbs, or operations omitted) |
stroke-width: 1.5, label "R" |
[inferred] |
An entity relation translate --from db derived from a Soft FK (a <stem>_id / <stem>_code column with no declared REFERENCES / FOREIGN KEY). Relations from an explicit FK are left untagged (confirmed) |
Muted grey (#94A3B8 dark / #64748B light). Colour only — line style stays owned by [sync] / [async], so an inferred async relation still reads as dashed |
[implicit]uses color (amber) to signal “derived,” while the line style distinguishes sync from async. When both sync and async domain edges exist between the same service pair, they are derived as separate implicit edges, one per kind.
[write]/[read]are auto-injected on synthesized usecase→resource edges only. Do not write them by hand on explicit edges — the resolver will accept them syntactically, but the semantics (write-dominates classification of the target resource’soperations) only make sense for the synthesized edges. The width hierarchy is intentionallyread (1.5) < write (2) < cyclic (2.5)so that cyclic remains the most attention-grabbing axis.
[inferred]is different in kind from the others in this table:[implicit]/[read]/[write]/[cyclic]are synthesized by the resolver at render time and never appear in.krssource, whereas[inferred]is stamped into the emitted source bytranslate --from dband then persists — it marks a relation the tool guessed from a naming convention rather than a declared FK. Curation is by hand: once you have confirmed the relation is real, delete the single[inferred]tag and it becomes a confirmed edge. Its colour is deliberately kept orthogonal to[sync]/[async]line style so that stamping it never hides the sync/async distinction (TPL-510).
Related TPLs: TPL-1944 —
translate --from dbassigns[inferred]to a relation only when every contributing FK is a Soft FK; a single explicit FK leaves the relation untagged (confirmed), and the tag must render with an effect. TPL-510 — a derived auto-tag must stay orthogonal to thekind([sync]/[async]) dimension it does not own.
Customization example
Section titled “Customization example”edge[implicit] { color: purple; border-style: dotted;}Team contact convention (owns + link)
Section titled “Team contact convention (owns + link)”To support organizational queries in the AI chat (“who is the owner team of this service?”, “I want to contact the affected teams”), declare teams in an organization block, have them owns the services / domains, and add contact links to the team block.
The old
team "..."string property onservice/domainhas been removed (per the deprecation plan of ADR-14). The owner team is derived fromorganization/owns.
organization Corp { team fintech { label "Fintech Team" owns Payment link "https://slack.com/archives/C..." "Fintech Team Slack" link "https://notion.so/..." "Team page" }}
system Shop { service Payment { label "Payment" }}Ownership (owns)
Section titled “Ownership (owns)”A team lists the services / domains it owns. The AI uses this ownership relation (the ownerIndex built at parse time) when answering organizational queries.
Team annotations and primary owner during a handoff
Section titled “Team annotations and primary owner during a handoff”A team block accepts annotations the same way services and domains do, written before the {:
organization Corp { team legacy @deprecated { owns Payment } team payments @migration_target(from: "legacy") { owns Payment }}@migration_target and @deprecated render as a badge on the team in the organization view, mirroring the node badges in the system diagram.
A node can legitimately be owns-ed by more than one team during an inverse-Conway handoff. ownerIndex is 1:1, so a single primary owner is chosen by migration priority — @migration_target (the destination) wins, an unmarked team is next, and @deprecated (the source) loses. Ties keep the first declaration. This mirrors the domain migration-coexistence rule (see Migration annotations above, where the @migration_target domain wins the navigation target). Co-ownership itself stays a tolerated fact, surfaced through the duplicate-owner-assignment info diagnostic — it is never an error.
Related TPLs: TPL-1583 (the
@migration_target-wins / first-wins rule must be consistent across every 1:1 index), TPL-1386 (co-ownership is a fact, kept in the info register).
link property (team contact)
Section titled “link property (team contact)”Add contact URLs to the team block in the form link "<url>" "<label>".
When the label contains any of the following keywords, the AI recognizes the link as a team contact:
| Keyword examples | Purpose |
|---|---|
Slack |
Slack channel |
Teams |
Microsoft Teams channel |
Team page |
Team page on Notion, Confluence, etc. |
Runbook |
On-call / operations runbook |
Usage example (AI chat queries)
Section titled “Usage example (AI chat queries)”When the model contains the information above, queries like the following become possible in the Chat tab:
Q: "Which teams depend on the Order service?"A: - Fintech Team (Payment service) → https://slack.com/... (Fintech Team Slack) - Platform Team (Notification service) → https://slack.com/... (Platform Team Slack)
Q: "Who should I meet first during onboarding?"A: ECommerce (most edges): EC Team → https://notion.so/... (Team page)© 2026 Hiroki Kondo · Licensed underApache-2.0