TeamAPI latest
On this page
  1. Relationship to the base spec
  2. File format and layout
  3. Root object
  4. Info
  5. Channel
  6. SearchTerm
  7. Ref
  8. Roles vs. Members
  9. Role
  10. Responsibility
  11. RoleRef
  12. Member
  13. CognitiveLoadAssessment
  14. Services and bounded contexts
  15. Work
  16. Meeting
  17. Interactions and context mapping
  18. Dependencies
  19. AI-native domains
  20. Context bundles
  21. Knowledge graph
  22. Unified search
  23. Design note: read-only, git-managed, no new persistence
  24. Toolchain-generated artifacts
  25. Enum reference

Team API Extended Specification (v1)

This document specifies the extended Team API as Code schema used by this toolchain. It is a superset of TeamTopologies/TeamAPI-As-Code v0.1.0, adding role/people definitions, cognitive load self-assessment, and DDD-style bounded context and context-mapping annotations. The canonical schema is the Zod schema in packages/schema/src/v1; this document is a human-readable mirror of it, in the spirit of the upstream project's own spec/teamapi.md.

A mirror drifts, so the mirroring is tested. conformance/ holds one fixture per normative statement below — the documents and the outcome the statement promises — and the runner additionally reads the enum reference, the root object table, and the list of $ref fields in File format and layout straight out of this file and compares them against the schema and the resolver. A sentence here that the implementation does not honour fails the build.

Relationship to the base spec#

This is a new, non-strictly-backwards-compatible extension, not a drop-in replacement. The root field is teamApiVersion (currently only "1.0.0" is supported) — a name deliberately distinct from the upstream teamapi: field, so tooling that understands only the base spec and tooling that understands this extension never mistake one document for the other.

Versioning and migration. teamApiVersion is a closed set validated against a registry of supported versions (SCHEMA_REGISTRY in packages/schema/src/registry.ts), currently just "1.0.0". A document declaring an unsupported version fails validation rather than being parsed against the wrong schema. There is no 1.x deprecation policy yet, since no second version has shipped — when one does, expect it to be additive (new optional fields) where possible, with a breaking change reflected as a new registry entry rather than silently changing "1.0.0"'s meaning out from under existing documents.

The migration mechanism exists ahead of any migrations: MIGRATIONS in packages/schema/src/migrate.ts is an ordered chain a document walks toward LATEST_TEAM_API_VERSION, and teamapi migrate runs it. It is empty today, deliberately — a placeholder migration would be one real documents could hit. What it provides before a second version exists is diagnosis: assessVersion distinguishes a document that is behind this build (migratable, or with no registered path) from one ahead of it (the toolchain needs upgrading, not the document) from one carrying no version at all. Those read identically to the schema, which can only report Invalid literal value, expected "1.0.0", and they need opposite responses.

File format and layout#

Same conventions as the base spec: one file per team, YAML or JSON (a strict JSON-compatible subset of YAML 1.2), conventionally named teamapi.yml. Teams reference each other across files/repos via $ref, resolved by this toolchain as whole-document references only (no JSON-pointer fragments into another team's nested fields) in exactly five places:

work.*[].$ref is not traversed by the resolver — it points at repos, wikis, or other non-team resources, and $ref is optional there.

Every object in the schema allows unknown x-*-style vendor extension fields (JSON Schema .passthrough()), so teams can attach organization-specific metadata without forking the schema — see x-pagerduty-service on the ledger service in examples/acme-org/platform-payments/teamapi.yml for a worked example.

Root object#

Several fields below are typed slug: a lowercase kebab-case identifier matching ^[a-z0-9]+(-[a-z0-9]+)*$ (e.g. stream-checkout, head-of-engineering) — not a display name, which is what info.name/role.name/member.name are for.

Field Type Required Description
teamApiVersion "1.0.0" Yes Version of this extended spec.
id slug Yes Stable identifier used for $ref linking (matched against another team's id once its $ref'd document is resolved — a referencing document itself only ever carries a $ref path/URL plus a human-readable teamName, never the target's id directly). Never renamed once other teams reference it — info.name is the renameable display label instead.
info Info Yes Core team identity.
channels Channel No Communication channels.
searchTerms SearchTerm No Free-text terms for org-wide search.
platform Ref No The platform team this team's services are built on.
services Service No Services/software owned by this team.
work Work No Current work items (not traversed as team-graph edges).
roles Role No Positions/functions within the team.
members Member No People on the team, optionally assigned to roles.
cognitiveLoad CognitiveLoadAssessment No Self-assessment.
meetings Meeting No Recurring meetings.
interactions Interaction No Team Topologies interactions with other teams.
dependencies Dependency No Dependencies on other teams.
agents Agent No AI assistants treated as first-class team participants.
memory MemoryEntry No Persistent organizational memory.
specifications Specification No Specification-driven-development artifacts.
steeringDocuments SteeringDocument No Coding standards, conventions, principles — inheritable org->team->project.
prompts Prompt No Version-controlled, renderable prompt library.
playbooks Playbook No Structured operational procedures.
policies Policy No Machine-readable governance for external automation.
knowledgeBase KnowledgeBaseEntry No ADRs, FAQs, meeting notes, runbooks, design docs.
workflows Workflow No Process state machines, independent of any CI/CD system.
sessions AiSession No Record of AI collaboration sessions.

Info#

{ name: string, focus?: string, type: "stream-aligned" | "platform" | "complicated-subsystem" | "enabling" }

Channel#

A communication channel for reaching the team.

Field Type Required Description
type string Yes The channel medium, e.g. "slack", "email", "teams" — not an enum, since organizations use different tools.
name string Yes The channel identifier within that medium, e.g. a Slack channel name.

SearchTerm#

A free-text term surfaced by org-wide search (searchOrg/GET /search/search_org) in addition to whatever's already searchable on the team (name, focus, services, roles, members) — useful for synonyms, former team names, or jargon someone might search for that doesn't appear verbatim elsewhere in the document.

Field Type Required Description
term string Yes The searchable text.

Ref#

The base shape used everywhere this spec links to another team's document: a required $ref (a relative file path or absolute URL, resolved by the toolchain's loader) allowing unknown vendor-extension fields via passthrough. platform is exactly this shape with no additional fields; RoleRef, Interaction, and Dependency all extend it with their own additional fields (teamName plus whatever else each needs).

Field Type Required Description
$ref string Yes Path or URL to the target team's document.

Roles vs. Members#

Team Topologies' Team API template asks "who does what," but a role (a position, e.g. "Payments Tech Lead") and a member (a specific person) are different things worth keeping separate:

Role#

Field Type Required Description
id slug Yes Unique within this team's roles[].
name string Yes The role's title, e.g. "Payments Tech Lead"not a person's name.
kind string Yes A broad category for filtering/analytics, e.g. TechLead, Engineer, Designer, SRE (suggested values in SUGGESTED_ROLE_KINDS, not enforced).
responsibilities Responsibility[] No What this role owns.
reportsTo slug No Another role's id within the same team. Validated: must match an existing role in this team's roles[], and same-team reportsTo cycles (including a role reporting to itself) are rejected.
reportsToRef RoleRef No Formal reporting line to a role on another team. Mutually exclusive with reportsTo — a document setting both fails validation, since a role reports to exactly one manager, same-team or not.
alignsWith RoleRef[] No Dotted-line/matrix relationships that aren't formal reporting, e.g. a community-of-practice lead this role coordinates with. Same-team or cross-team.

Responsibility#

A plain string, or an object pairing the responsibility with an optional doneWhen — a definition of done, for consumers that need one (e.g. teamapi generate crewai, where it becomes a task's expected_output). Most consumers (diagrams, REST API, MCP tools) have no use for doneWhen, so it's never required — plain strings remain valid everywhere.

Field Type Required Description
text string Yes The responsibility itself, same content as the plain-string form.
doneWhen string No What "done" looks like for this responsibility.
responsibilities:
  - Payments platform architecture # plain string — no doneWhen
  - text: On-call escalation point
    doneWhen: A runbook exists and the on-call rotation is staffed for the current quarter.

RoleRef#

kind (new, optional) says what sort of informal tie an alignsWith[] entry is: aligns-with (the default when omitted, and the original dotted-line meaning) | advises | learns-from | community-of-practice. These name the network work actually travels along — who a role takes advice from, who it learned a practice from, which community it belongs to — which the reporting hierarchy never explains, and which routinely exists for months before anyone draws a box for it.

Each becomes a RoleGraphEdge of the matching kind, drawn as a labelled dashed edge by --scope org-hierarchy and surfaced in the knowledge graph. teamapi gaps reports how many cross-team role relationships the reporting lines explain, and how many they don't.

kind is rejected on reportsToRef, which is always formal reporting, rather than being silently ignored.

A reference to another team's role — same $ref convention as Interaction/Dependency.

Field Type Required Description
teamName string Yes Human-readable label for the target team, kept inline alongside $ref.
roleId slug Yes The target role's id within the referenced team's roles[].
$ref string Yes Path/URL to the target team's document.

Member#

Field Type Required Description
id slug Yes Unique within this team's members[].
name string Yes The person's name.
contact string No Email or other contact info.
roleIds slug[] No Zero or more roles[].id this member currently fills.
allocation number (0-100) No FTE % this member allocates to this team.
githubUsername string No GitHub login, used by teamapi apply/teamapi import github-org to resolve this member to a real account.

CognitiveLoadAssessment#

Inspired by Team-Cognitive-Load-Assessment: a 1-10 self-assessment across the three load types from Team Topologies.

Field Type Required
intrinsic number (1-10) Yes
extraneous number (1-10) Yes
germane number (1-10) Yes
supervision number (1-10) No
notes string No
assessedOn string No

@jgalego/teamapi-core's scoreCognitiveLoad derives a sustainable | elevated | overloaded label, weighting extraneous load more heavily than the total score — Team Topologies treats extraneous (avoidable overhead) as the load type teams should actively minimize.

supervision (new) is the load of supervising AI agents: reviewing what they produce, maintaining their prompts, being the person everyone asks. It is not part of total, but it is one of the label's independent triggers, on the same thresholds as extraneous (≥4 elevated, ≥7 overloaded).

Those are two separate decisions. The three types above come from Team Topologies and the thresholds are calibrated against their sum, so summing a fourth term would re-scale total for every team that adopted an agent. But the label has never been a function of total alone — a high extraneous score alone is already sufficient — and supervision joins it there, because a team drowning in agent review must not be able to report "sustainable" on the strength of three modest other scores. A team that has not scored supervision is unaffected: an absent value reads as 0.

It is not folded into extraneous either, because reviewing an agent's output is frequently the work itself rather than avoidable friction around it.

teamapi gaps reports an unscored-supervision warning for a team that assesses its cognitive load and runs active agents[] but leaves this blank — the load exists whether or not anyone scored it.

Because it sits outside total, teamapi diff tracks it as its own field: a team whose supervision load doubles without touching the other three types would otherwise show up as no change at all, which is exactly the quiet growth this field exists to expose. The Port generator emits it as supervisionLoad, a sortable number beside cognitiveLoad, and the dashboard shows it as a separate chip rather than widening the load bar.

Services and bounded contexts#

Service: { name, url?, repository?, versioning?: { type }, boundedContext?: BoundedContext }.

BoundedContext (new) lets a team describe a service as a DDD bounded context:

boundedContext:
  ubiquitousLanguage:
    - term: Charge
      definition: A single attempt to move money from a customer's payment method
  aggregates: [Charge, Refund]
  publishedEvents: [ChargeAuthorized, ChargeSettled]
  subscribedEvents: []

Work#

Point-in-time work items — deliberately not resolved into graph edges (see File format and layout), since they describe transient work rather than a standing team-to-team relationship. Each of the three arrays holds the same shape: a required name plus an optional $ref to a repo, wiki page, ticket, or other resource (not necessarily another team's teamapi.yml).

Field Type Required Description
services NamedRef[] No Work items tied to a specific service this team owns.
waysOfWorking NamedRef[] No Practices/playbooks the team follows (e.g. "Trunk-based development").
crossTeam NamedRef[] No Cross-team initiatives this team is currently part of.

NamedRef: { name: string, $ref?: string }, allowing unknown vendor-extension fields.

work:
  waysOfWorking:
    - name: Trunk-based development

Meeting#

A recurring meeting.

Field Type Required Description
purpose string Yes What the meeting is for, e.g. "daily sync".
dayOfWeek string No e.g. "Tuesday" — a free-text day name, not an enum.
timeOfDay string No e.g. "09:30" — a free-text time, no enforced format.
durationMinutes positive integer No How long the meeting runs.

Interactions and context mapping#

Interaction extends the base spec's shape with an optional contextMappingPattern:

Field Type Required
teamName string Yes
mode collaboration | x-as-a-service | facilitating Yes
purpose string No
startDate string (free-text; no enforced date format) No
expectedDuration positive number No
expectedDurationUnit days | weeks | months No
contextMappingPattern Partnership | CustomerSupplier | Conformist | OpenHostService | AnticorruptionLayer | SharedKernel No
$ref string Yes

When contextMappingPattern is omitted, @jgalego/teamapi-core's deriveContextMap applies a heuristic:

Mode Inferred pattern
x-as-a-service OpenHostService
collaboration Partnership
facilitating (none) — coaching/enabling relationships aren't a runtime integration pattern

Each team's interaction declaration is an independent directed edge. If two teams describe the same relationship with different modes, deriveContextMap surfaces it as a conflict rather than silently reconciling — that disagreement is itself useful organizational signal.

Dependencies#

{ teamName, description?, type: "OK" | "Slowing" | "Blocking", $ref } — unchanged from the base spec.

AI-native domains#

Every section below is additive/optional (.default([])), so a document written before they existed still parses identically. Every resource in every array below requires a slug id, unique within that array (validated the same way roles[].id/members[].id are). Every array-of-objects field not called out below (capabilities, tags, reviewers, etc.) is a plain string array.

Agent: { id, name, description?, provider, model?, role, capabilities: string[], status: "active" | "inactive" | "deprecated" (default "active"), ownerId?: slug (a members[].id), permissions: string[], tags: string[] }. Worked example: examples/acme-org/platform-payments/teamapi.yml declares five agents, each scoped to one review concern (architecture, tests, security, docs, compliance) rather than one do-everything agent — platform-payments's memory[] (below) records why. examples/acme-org/stream-onboarding/teamapi.yml declares none, backed by a policies[] entry rather than silent omission.

Example: an agent with a resolvable human owner#

ownerId names a member of the same team. The schema accepts any slug here; teamapi gaps reports a missing owner as dangling-owner, and the knowledge graph creates an accountability edge only when the member resolves.

members:
  - id: priya-raman
    name: Priya Raman
    roleIds: [tech-lead]
    allocation: 100

agents:
  - id: security-scanner
    name: Security Scanner
    provider: in-house
    model: acme-secscan-v3
    role: security-auditor
    capabilities: [dependency-scanning, secret-detection]
    ownerId: priya-raman
    permissions: ["read:pull-requests", "comment:pull-requests"]

Structurally valid, semantically unresolved: changing ownerId to departed-member still passes schema validation because cross-resource identity is a graph concern. teamapi gaps then reports a blocking finding rather than silently attaching the agent to the wrong person.

MemoryEntry: { id, title, kind: "architecture-decision" | "convention" | "lesson-learned" | "recurring-issue" | "domain-knowledge" | "historical-decision", body (markdown), tags: string[], contributors: string[], relatedRefs: Ref[], createdAt?, updatedAt? }.

Specification: { id, title, kind: "requirement" | "design" | "task" | "acceptance-criteria", status: "draft" | "in-review" | "approved" | "in-progress" | "implemented" | "deprecated" (default "draft"), body?, reviewers: string[], approvals: { reviewer, approvedAt?, comment? }[], linkedPullRequests: string[], linkedIssues: string[], linkedDocuments: Ref[], tags: string[] }. linkedPullRequests/linkedIssues are plain strings (URLs or owner/repo#123), not $refs — they point at GitHub/GitLab/Jira, not another team's document.

Example: recording a review in progress#

reviewers[] records who is expected to review; approvals[] records review events. They are intentionally independent: the schema stores the declared lifecycle but does not infer or enforce a status from the number of approvals.

specifications:
  - id: oauth-login
    title: Support OAuth login
    kind: requirement
    status: in-review
    reviewers: [security-lead, identity-owner]
    approvals:
      - reviewer: security-lead
        approvedAt: "2026-07-15T10:30:00Z"
        comment: Aligns with the threat model.
    linkedPullRequests: ["acme/checkout-api#418"]
    tags: [identity, security]

SteeringDocument: { id, title, category: "coding-standards" | "api-conventions" | "security-guidelines" | "architecture-principles" | "documentation-style" | "custom", scope: "organization" | "team" | "project" (default "team"), appliesTo?, body, tags: string[] }. @jgalego/teamapi-core's resolveEffectiveSteering(graph, teamId) returns a team's own documents plus every document declared on the team(s) reachable by walking the existing platform.$ref chain upward — reusing that edge rather than inventing a second hierarchy mechanism. A document the team declares itself always wins over an inherited one sharing its id.

Example: inheriting and overriding steering#

# platform-payments/teamapi.yml
id: platform-payments
steeringDocuments:
  - id: security-baseline
    title: Payment security baseline
    category: security-guidelines
    scope: organization
    body: All services must enable audit logging.
---
# stream-checkout/teamapi.yml
id: stream-checkout
platform:
  $ref: ../platform-payments/teamapi.yml
steeringDocuments:
  - id: security-baseline
    title: Checkout security baseline
    category: security-guidelines
    scope: team
    body: Checkout must enable audit logging and redact cart tokens.

For stream-checkout, the local security-baseline replaces the inherited document with the same id. Other inherited documents remain available.

Prompt: { id, name, description?, template (with {{variable}} placeholders), variables: { name, description?, required (default false), default? }[], version (default "1.0.0"), versions: { version, template, changelog?, publishedAt? }[], tags: string[], owner? }. @jgalego/teamapi-core's renderPrompt(prompt, variables) fills placeholders, falling back to each variable's default, and throws MissingPromptVariableError for a required variable left unfilled.

Example: required variables and defaults#

prompts:
  - id: release-review
    name: Release review
    template: "Review {{service}} for {{environment}}. Focus on {{focus}}."
    variables:
      - name: service
        required: true
      - name: environment
        default: production
      - name: focus
        default: rollback safety

Rendering with { "service": "checkout-api" } produces:

Review checkout-api for production. Focus on rollback safety.

Omitting service throws MissingPromptVariableError; an optional variable with neither a supplied value nor a default leaves its placeholder unchanged.

Playbook: { id, name, category: "incident-response" | "release" | "onboarding" | "offboarding" | "production-deployment" | "custom", steps: { order, title, description?, requiredRoles: string[], automationHook? }[], documentation?, attachments: Ref[], tags: string[] }.

Policy: { id, name, category: "pr-requirements" | "required-approvals" | "documentation" | "security" | "dependency" | "custom", severity: "info" | "warning" | "blocking" (default "warning"), description?, rules: { key, description?, value? }[], enforcedBy: string[], tags: string[] }. This schema declares what a policy requires; enforcing it is external automation's job, named (loosely) in enforcedBy.

KnowledgeBaseEntry: { id, title, kind: "adr" | "faq" | "meeting-notes" | "architecture-doc" | "runbook" | "design-doc", category?, body (markdown), relatedRefs: Ref[], attachments: Ref[], tags: string[] }.

Workflow: { id, name, description?, states: { id, name, description? }[], transitions: { from, to, trigger }[], automation: { trigger, action }[], tags: string[] }. Validated: every transitions[].from/.to must match a declared states[].id.

Example: a validated release transition#

workflows:
  - id: release
    name: Release process
    states:
      - id: testing
        name: Testing
      - id: approved
        name: Approved
      - id: deployed
        name: Deployed
    transitions:
      - from: testing
        to: approved
        trigger: tests-pass
      - from: approved
        to: deployed
        trigger: deploy-scheduled
    automation:
      - trigger: tests-pass
        action: github-actions:request-approval
      - trigger: deploy-scheduled
        action: deployment:execute

Rejected: a transition such as { from: testing, to: production, trigger: deploy } fails validation because production is not present in states[]. Automation actions are declarations for external tooling; TeamAPI does not execute them.

AiSession: { id, agentId?: slug (an agents[].id), assistant, model?, objective, promptIds: slug[] (this team's prompts[].id), generatedArtifacts: Ref[], referencedDocuments: Ref[], decisions: string[], startedAt?, endedAt?, tags: string[] }. Written after the fact — a durable record, the same way meetings[] records a standing meeting rather than driving one live.

Context bundles#

@jgalego/teamapi-core's deriveContextBundle(graph, { goal, teamId?, limit? }) (exposed as POST /context and the get_context_bundle MCP tool) assembles the specifications, steering documents, policies, memory, knowledge base entries, prompts, and playbooks most relevant to a stated goal, plus (when teamId is given) that team's related teams, members, and services.

It also returns seams[]: every pair of teams the matched entries span, with the interaction mode declared between them, and undeclared: true when neither team declares any edge to the other. A bundle otherwise reads as if the goal belongs to whichever team was scoped, when in practice the highest-scoring entries routinely straddle a boundary — which is where the risk is. An undeclared seam deserves more caution than a declared one, not less: the work is about to cross a line nobody has written down. Derived from the teamId each scored entry already carries, so it costs one pass and no extra lookups.

Relevance is a heuristic: goal is tokenized (lowercased, alphanumeric runs of length >= 3), and each candidate resource is scored by how many of those tokens appear in its text fields/tags — returned as matchedTerms alongside each result, so the ranking is auditable rather than a black box. A resource belonging to the scoped teamId gets a fixed score boost, so a team's own material usually outranks equally-relevant org-wide material without burying something more relevant found elsewhere. This is a v1 scorer — nothing about the interface assumes keyword overlap specifically, so a semantic/embeddings-based scorer can replace it later without changing the request/response shape.

Knowledge graph#

@jgalego/teamapi-core's deriveKnowledgeGraph(graph) (exposed as GET /knowledge-graph and the get_knowledge_graph MCP tool) links every team, member, role, service, agent, and AI-native document into one graph of { nodes, edges }. Edge kinds, each backed by something the schema can actually resolve:

traverseKnowledgeGraph(graph, nodeId, maxDepth) (exposed as GET /knowledge-graph/:nodeId/traverse and traverse_knowledge_graph) does a breadth-first, edges-treated-as-undirected walk from one node, for scoping a visualization or answering "what's connected to this ADR."

searchOrg/GET /search/search_org now also covers every AI-native domain — agent, memory, specification, steeringDocument, prompt, playbook, policy, knowledgeBase, workflow, and session are all valid SearchResult.kind values, matched the same way team/service/role/member always were (case-insensitive substring, now also over each resource's tags).

Design note: read-only, git-managed, no new persistence#

Every AI-native domain above follows the same rule as the rest of this API: there is no write path. A POST you might expect from a typical CRUD API (POST /teams/:id/agents to register a new agent, say) does not exist here — an agent, a policy, a prompt is added the same way a role or a service is: by editing teamapi.yml and committing it. The only new POST endpoints are POST /context (a stateless computation over the current graph, not a resource creation) and POST /teams/:id/prompts/:promptId/render (ditto). This preserves the existing architecture's central property — the YAML documents, versioned in git, are the single source of truth — rather than bolting on a second, parallel persistence layer that could drift from it.

Toolchain-generated artifacts#

Given a resolved org graph, @jgalego/teamapi-core (consumed identically by the REST API, MCP server, and CLI) can produce:

See the root README.md (or packages/cli) for the CLI commands, REST endpoints, and MCP tools that expose these.

Enum reference#

Every enum in the schema, in one place (each is also mentioned inline where its field is documented above):

Enum Values Used by
Team type stream-aligned | platform | complicated-subsystem | enabling info.type
Interaction mode collaboration | x-as-a-service | facilitating interactions[].mode
Duration unit days | weeks | months interactions[].expectedDurationUnit
Context-mapping pattern Partnership | CustomerSupplier | Conformist | OpenHostService | AnticorruptionLayer | SharedKernel interactions[].contextMappingPattern
Dependency type OK | Slowing | Blocking dependencies[].type
Suggested role kind (not enforced) ProductManager | TechLead | EngineeringManager | Engineer | Designer | SRE | DataScientist | DomainExpert | DeliveryLead (SUGGESTED_ROLE_KINDS; roles[].kind accepts any non-empty string) roles[].kind
Agent status active | inactive | deprecated agents[].status
Memory kind architecture-decision | convention | lesson-learned | recurring-issue | domain-knowledge | historical-decision memory[].kind
Specification kind requirement | design | task | acceptance-criteria specifications[].kind
Specification status draft | in-review | approved | in-progress | implemented | deprecated specifications[].status
Steering category coding-standards | api-conventions | security-guidelines | architecture-principles | documentation-style | custom steeringDocuments[].category
Steering scope organization | team | project steeringDocuments[].scope
Playbook category incident-response | release | onboarding | offboarding | production-deployment | custom playbooks[].category
Policy category pr-requirements | required-approvals | documentation | security | dependency | custom policies[].category
Policy severity info | warning | blocking policies[].severity
Knowledge base kind adr | faq | meeting-notes | architecture-doc | runbook | design-doc knowledgeBase[].kind