The CMDB stores Configuration Items (CIs) - discrete assets and services that other TATER entities reference. Replaces the freeform configurationItems: string[] array on TaskerTask with a proper entity that supports typed relationships, criticality-weighted impact analysis, environment tagging, and auto-discovery.
CI types (16 shared, plus your own)
| Category | Types |
|---|---|
| Hardware / endpoints | server · workstation · mobile |
| Software / services | application · saas · database |
| Logical | service · business-service |
| Network | network · firewall · load-balancer |
| Cloud | cloud-account · cloud-resource |
| External | integration · vendor |
| Catch-all | other |
Asset types your organization defines
The sixteen above are the vocabulary every organization shares. When you need one nobody else wants - a Active Building Station, say, with the area it stands in and a URL that connects to it - define it under Assets → Asset Types in TATER Ops (the ⚙ button on the register toolbar).
A custom type does not become a seventeenth value of type. It
declares a base type from the list above, and the asset carries both: its
type is always one of the sixteen, so impact analysis, reports, the type filter and
every integration keep working unchanged, while typeKey names your definition and
supplies the extra fields. An organization that defines no types sees exactly what it saw
before.
Each type carries up to 25 extra fields. Field kinds: text,
textarea, number, date, url (validated as
http/https and rendered as a link), select (with a fixed choice list),
checkbox, and ci-ref (holds another asset's id, so a station can point
at the area it sits in). A field can be marked required.
Required is enforced when it should be, not always. A required field is checked when an asset is created with that type, and when an asset is moved onto it - never on an unrelated edit. That matters because you will add fields to a type after assets already exist: if required were checked on every write, adding one would make every existing asset unsaveable, and someone changing an unrelated status would get an error naming a field that did not exist when the record was created.
A field's key is derived from its label the first time it is saved and never changes afterwards, because assets store their values under it. Renaming the label is always safe. Retiring a type (untick "Available in the picker") hides it from the picker while leaving existing assets readable, and is almost always what you want instead of deleting - deleting is refused while assets still reference the type, and forcing it leaves their custom values unlabelled.
Typed relationships (10, with auto-inverse)
| Forward | Inverse (auto-maintained) | Use when |
|---|---|---|
| depends_on | used_by | A's failure causes B's failure |
| runs_on | hosts | App lives on a server |
| member_of | contains | CI is part of a service |
| connects_to | connects_to (self-inverse) | Network/integration peer |
| managed_by | managed_by (self-inverse) | Vendor or team owns the CI |
| replaces | replaces (self-inverse) | New CI supersedes old |
| related_to | related_to (self-inverse) | Generic association (last resort) |
When you add A depends_on B, the system automatically adds B used_by A. Never edit the relationship array directly - always use the /relate endpoint or relate_cis MCP tool.
Auto-discovery
POST /api/cmdb/discover (Admin) walks three sources and imports CIs idempotently:
- Devices → workstation/server CIs (criticality high for servers, low for workstations)
- CloudAccounts (from multiCloud.ts) → cloud-account CIs (criticality high)
- Vendors (active only) → saas/vendor CIs (criticality from
riskTier)
Idempotent via discoveryRefId dedupe (each imported CI carries the source record's id). Run after onboarding an org, then periodically as fleet grows. Run from app: CMDB page → Auto-Discover button.
Impact analysis
GET /api/cmdb/cis/:id/impact?maxDepth=N walks used_by + hosts + contains outbound up to N hops (default 5, max 10). Returns:
- severity badge - CRITICAL BLAST RADIUS (any critical impacted) / HIGH / MEDIUM / LOW
- count by criticality across all impacted CIs
- full impacted tree with the relationship path that led to each downstream CI
Use cases: change-request approval (justify CAB scrutiny), major incident triage (estimate scope), upgrade prioritization.
Lifecycle status
| Status | Means |
|---|---|
active | In service. |
in-stock | Owned but not deployed to anyone - a spare on a shelf. Distinct from planned, which is not acquired yet, and from retired, which is owned but out of service for good. |
in-maintenance | Temporarily out of service, expected back. |
retired | Out of service for good. Kept rather than deleted so its history survives. |
planned | Not acquired yet. |
Locations, and the stock location
An asset’s Location used to be free text. It can now be a list your administrators define in Manage → CMDB Locations, so everyone picks from the same vocabulary instead of typing eleven spellings of the same cupboard.
Two fields carry it, deliberately:
location– the label people read. Still free text, still never validated, because the CSV import andupdate_ciboth write it directly and nine surfaces render it as prose.locationKey– the stable reference into the defined list. Validated: an unknown key is refused rather than stored, so a typo cannot create a pointer to nothing.
Nothing is relabelled behind you. Defining locations does not rewrite assets that already carry text. An unrecognised value renders in the editor as its own selected option marked “— unrecognised”, so it round-trips visibly and you adopt it when you choose to, per asset. There is no backfill.
Exactly one location can be flagged as the stock location. Choosing a new one moves the flag off the old one in the same write — you are told which — so there is never a moment with no stock location, which is precisely when To Stock would otherwise stop working for a reason nobody could see.
To Stock
One click on an asset that has come back from someone. It sets three things at once:
- status →
in-stock - location → the organisation’s stock location
- the assignment is cleared
Who last held it is written to the asset’s change log, not to a
field on the record. An asset register that forgets who had the laptop has lost the thing people
open it to ask — but a previousOwner column would keep reading like a current
assignment long after it stopped being one.
It refuses in two situations, and in both the asset is left byte-identical:
- A retired asset. Retired means out of service for good; returning it to the issuable pool would quietly undo a disposal decision someone made on purpose.
- No stock location defined. It will not guess. The destination is resolved before anything is written, so the action cannot half-apply and leave an asset in stock but nowhere.
Pressing it twice is the same as pressing it once: the second press reports “already in stock — nothing changed” and writes no second history entry.
Owner enrichment knows about this. An asset in stock has no owner by design, which used to look exactly like an asset waiting to be filled in from Entra device data — so enrichment would have quietly re-assigned everything you shelved. Assets that are in stock or retired are now reported as not deployed and deliberately skipped, rather than silently omitted from the proposal list.
Change log
Every asset carries its own history: what changed, when, who changed it, and whether it came from a
person, the CSV import, auto-discovery, owner enrichment or To Stock. Open it with History on
the asset, or get_ci_history over MCP.
This is separate from the platform audit trail on purpose. The audit log records that an asset was updated; it did not record what moved, and the two highest-volume writers — discovery and owner enrichment — logged under a batch id rather than the asset’s, so their changes were not answerable per item at all. Those are exactly the changes people are confused by (“why does this laptop keep showing Dana as the owner?”), so the change log captures them alongside the hand edits.
An update that changed nothing writes no entry — discovery reconciles thousands of assets on a schedule, and a timeline full of entries recording nothing is not a timeline.
Filling in owners from Entra
Device-discovered assets arrive with no owner, because discovery reads the machine, not the assignment. Owner enrichment proposes one from each device's Entra registered owner - the person the machine is joined to, recorded once as an assignment. That is deliberately not the agent's last-logged-in user, which is whoever sat down most recently and is wrong for a machine somebody borrowed for an afternoon.
It proposes; it does not sweep. Entra's registered owner is evidence about an assignment, not the assignment itself - a machine re-imaged for a new starter keeps its old owner until it is re-joined - so a bulk apply would overwrite a human's correction with stale directory state. Instead:
POST /api/cmdb/owner-enrichment/preview(Admin) changes nothing. It groups every device-sourced asset as fill (the register has no owner), conflict (the register disagrees with Entra), match (already correct), or no owner in Entra - which is the right answer for domain controllers, session hosts and shared VMs, not a coverage gap.POST /api/cmdb/owner-enrichment/apply(Admin) sets owners on only the asset ids you list. It recomputes the proposals at apply time, so an asset edited since the preview is skipped with a reason rather than written blind, and an asset that already has an owner is refused unless you explicitly accept the overwrite.
Requires the tenant credential app to hold Device.Read.All (or
Directory.Read.All). If it does not, the preview says so and names the permission
rather than failing with an opaque 403.
REST endpoints
GET /api/cmdb/cis(Viewer+) - list, filter by type/criticality/status/environment/tag/qGET /api/cmdb/cis/:id(Viewer+) - single CI with full relationship graphPOST /api/cmdb/cis(Admin) - create/update; manual entry for logical CIsDELETE /api/cmdb/cis/:id(Admin) - auto-strips this CI from other CIs' relationship arrays firstPOST /api/cmdb/cis/:id/relate(Admin) - add/remove with auto-inverseGET /api/cmdb/cis/:id/impact(Viewer+) - recursive blast-radius walkPOST /api/cmdb/discover(Admin) - auto-import from devices/cloud/vendorsGET /api/cmdb/types(Viewer+) - your organization's asset types, their fields, and the shared base vocabularyPOST /api/cmdb/types(Admin) - define or edit an asset typeDELETE /api/cmdb/types/:id(Admin) - refuses while assets still use it;?force=trueoverridesPOST /api/cmdb/owner-enrichment/preview(Admin) - what owners Entra would fill in; writes nothingPOST /api/cmdb/owner-enrichment/apply(Admin) - set owners on the asset ids you list
MCP tools (14, HTTP + stdio parity)
list_cis- browse, filter by type/criticality/status/env/tag/qget_ci- single CI with relationship graphcreate_ci- logical CIs only; use discover_cis for inventory-sourced onesupdate_ci- change fields on an existing asset: reassign the owner, record an asset tag or serial, set a location, retire it, adjust criticality or tags. Only the fields you supply change - everything else on the record is left as it was, so a single-field edit is safe. Send an empty string to deliberately clear a field.relate_cis- add/remove relationships, auto-inverseget_ci_impact- recursive impact with severity badgediscover_cis- auto-import from devices/cloud/vendorslist_ci_types- your organization's asset types and their fields. Read this before creating an asset with a custom type: it gives you thetypeKey, whichcustomFieldskeys are valid, which are required, and what a select acceptsupsert_ci_type- define or edit an asset type. OmitcustomFieldsto leave the field list alone; send the FULL list to change itci_to_stock- return an asset to the shelf in one call: status toin-stock, location to the organisation’s stock location, assignment cleared. Refuses on aretiredasset, and refuses if no stock location is defined rather than guessing one; in both cases nothing is written. Calling it on an asset already in stock is a no-op that reports as suchget_ci_history- the change log for one asset: what changed, when, by whom, and whether the writer was a person, the CSV import, discovery, owner enrichment or To Stock. This is where the previous holder of a shelved asset is recordeddelete_ci_type- refuses while assets use it and says how manypreview_asset_owner_enrichment- read-only; what Entra would fill inapply_asset_owner_enrichment- set owners on the ids you list, never a sweep
create_ci and update_ci also take typeKey and
customFields. Custom values merge per key: a field you omit keeps
its stored value, a field set to null is cleared, and only keys defined on the type
are accepted. Passing typeKey on a create supplies the base type, so
type is not needed.
Editing a CI in TATER Ops
The Configuration Item dialog covers the full inventory record, grouped as Assignment (owner, owner email), Inventory (asset tag, serial number, location, environment, tags) and Lifecycle (in service, warranty expires, retired on).
Owner suggests from your directory as you type - people from Entra plus your local People records - and fills in the owner email for you when the name matches someone. It is still a free-text box on purpose, so you can assign an asset to a team ("Infra Team") rather than a person, and so an owner that came from device discovery is never thrown away.
Asset tag defaults to the Name on a new CI while you type it, and stops following the moment you edit the tag yourself. On an existing CI it is left alone - use the Use name button next to the field if you want the same thing. That is deliberate: filling asset tags automatically on records nobody edited would stamp hostnames across the whole estate, and afterwards nobody could tell an invented tag from one that came off a real label.
Only the fields you change are written. Saving the dialog never disturbs a field you did not touch, and clearing a box clears that field for real. Asset tag, serial, location and the lifecycle dates are also available as optional columns on the CI list via the column customizer.
Hostnames, IP addresses and the discovery source are shown read-only. Those are maintained by the discovery run. On a CI that came from device discovery you will see a note saying that name, type, hostnames, IPs and serial are refreshed from the source on each run - so an edit to one of those can be replaced, while everything you set under Assignment, Inventory and Lifecycle is yours and is never overwritten.
Bulk changes go through Import CIs (CSV). A column you leave out of the file is never touched, and a blank Criticality or Status cell leaves the stored value alone on an update rather than resetting it.
These tools are gated by the Assets permission module, not Ops - so someone
holding only asset-steward or asset-viewer can manage the register over
MCP without being granted the service desk. Every bundle that granted Ops before the split keeps
the same access it had.
Pitfalls
- Discovery is idempotent via
discoveryRefId. If you delete an auto-discovered CI and re-run discover, it gets recreated. To permanently exclude a discovered CI, deactivate the source record (offboard the vendor, retire the device). - Cross-org relationships are blocked. A CI in org A can't relate to a CI in org B. This is intentional - CMDB is per-org. If you need cross-org dependencies, model them as
related_towith a note rather than a hard relationship. - Delete cascade strips relationships but doesn't delete dependents. If you delete CI A that has B depends_on A, B is left with a dangling reference (rendered as targetId only, no name). Future cleanup pass will GC these.
- Service Portfolio depends on CMDB. Services are CIs of type business-service or service. See Service Portfolio guide for the consumer view.
Related guides
- Service Portfolio - uses CMDB to define business services backed by CI infrastructure
- Major Incident workflow - uses CMDB for affected-CI rollup + impact
- CAB workflow on Change Requests - uses CMDB for change conflict detection