Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 113 additions & 0 deletions api-reference/snapshot_artifacts_and_deployment_diffs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
---
title: "Snapshot artifacts and deployment diffs"
description: "How Kosli decides what an artifact replaced in an environment, what deployment_diff in the get snapshot response means, and when it is null."
---

This page explains the `deployment_diff` object that the [get snapshot](/api-reference/snapshots/get-snapshot) endpoint returns for every artifact in an environment snapshot: how Kosli picks the artifact it compares against, why there is one answer per flow, and when the value is `null`. The field descriptions themselves are in the endpoint's response schema; this page covers the semantics behind them.

## Two kinds of diff

Kosli answers two different "what changed" questions about an environment. They are easy to confuse because both are called a diff.

| Question | Use |
|---|---|
| What is in snapshot B that was not in snapshot A, and vice versa? | [`kosli diff snapshots`](/client_reference/kosli_diff_snapshots), which compares two whole snapshots as sets of artifacts. |
| For this one running artifact, what was running in its place before it, and what changed between them? | `deployment_diff` on the artifact, in the [get snapshot](/api-reference/snapshots/get-snapshot) response. |

The first is a set difference between two points in time. The second is a pointer from a running artifact back to its predecessor, and it is the one to read for lead-time and change reporting. The rest of this page is about the second.

## How a snapshot records change

Kosli creates a new snapshot only when the reported set of running artifacts differs from the latest snapshot. An artifact is identified by its **fingerprint** (its SHA-256 digest), not its name. When a fingerprint appears that was not in the previous snapshot, Kosli records a **started** event for it; when one disappears, an **exited** event. The name is recorded alongside, but it carries no identity: `web:cbe481c` and `web:236898f` are two different artifacts because their fingerprints differ, and two containers with different names but the same digest are the same artifact.

`deployment_diff` is computed from these started events, not from snapshot numbering.

## What "previous" means

`deployment_diff` hangs off the **current** artifact and points **backwards**. Within a flow, the previous artifact is the one that:

1. was also reported into the **same flow**,
2. has a **different fingerprint**, and
3. **started running in this environment most recently before** the current artifact started.

If the current artifact was reported with a `template_reference_name`, the match is further restricted to artifacts with the same `template_reference_name`. This is what keeps a multi-service environment honest: the `web` container is compared against the previous `web` container, not against whatever else deployed around the same time.

Three consequences follow:

- **It is not the artifact in the previous snapshot.** Snapshots are created whenever anything in the environment changes, including other services. The match is driven by start events, so a deploy of another service does not make an artifact's `deployment_diff` go stale or disappear.
- **It is not matched on name.** `previous_artifact_name` is reported separately because the predecessor may have run under a different name, which is normal when the tag carries the commit.
- **It is anchored to when the current artifact started**, not to when you make the request. Every artifact in the latest snapshot still carries the diff describing what it replaced when it started, and the value is stable for as long as the artifact keeps running.

## One answer per flow

An artifact can be reported into several flows: a build flow, a promotion flow, a scanning flow. Each entry in `artifacts[].flows[]` carries its **own** `deployment_diff`, computed independently using only artifacts reported into that flow.

The public `cyber-dojo` organization shows this. Its `web` image is reported into four flows, and the latest `aws-prod` snapshot returns four different answers for the same running image:

| Flow | Previous fingerprint | `diff_url` compares |
|---|---|---|
| `web-ci` | `36ad0020` | `cyber-dojo/web` `cbe481c...236898f` |
| `production-promotion` | `36ad0020` | `cyber-dojo/aws-prod-co-promotion` `7494758...7494758` |
| `snyk-aws-beta-per-artifact` | `36ad0020` | `cyber-dojo/snyk-scanning` `ed3c81d...30111f1` |
| `snyk-aws-prod-per-artifact` | `29c69c2f` | `cyber-dojo/snyk-scanning` `00c4797...30111f1` |
Comment on lines +45 to +52

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Improvement — the example is pinned to a snapshot the page never names.

The four rows are verifiable today: they match the generated sample in client_reference/kosli_get_snapshot.md:1668-1810 exactly (36ad0020 for web-ci, production-promotion and snyk-aws-beta-per-artifact; 29c69c2f for snyk-aws-prod-per-artifact; cbe481c...236898f, 7494758...7494758, ed3c81d...30111f1, 00c4797...30111f1).

The problem is the framing: "the latest aws-prod snapshot", plus $BASE/-1 in See it yourself (line 100). cyber-dojo deploys continuously, so the next web deploy makes -1 disagree with this table, and a reader who follows the instructions and gets four different fingerprints concludes the page is wrong rather than stale. The PR description says these came from snapshot 5352 — say so on the page, and use that id in the walkthrough so the numbers keep reproducing.

Suggested: "the aws-prod snapshot 5352 (the latest at the time of writing) returns four different answers", and $BASE/5352 in the first curl, keeping -1 for the "show me current state" example.

Fix this →


Two separate things are happening:

- **Different flows can pick a different previous artifact.** Three flows agree the predecessor was `36ad0020`. `snyk-aws-prod-per-artifact` picks `29c69c2f` because `36ad0020` was never reported into that flow, so the search skipped further back to the last image that was.
- **Even when they agree on the artifact, they disagree on the commit.** `git_commit` is recorded per flow, and each flow here tracks a different repository. The same image is commit `236898f` of `cyber-dojo/web` to the build flow and commit `30111f1` of the scanning repository to the Snyk flows. `previous_git_commit` and `diff_url` describe **that flow's repository**, not the image in the abstract.

To answer "what source code changed", read the entry for the flow that **builds** the artifact. The other entries answer a real question, but a different one, and can be degenerate: the promotion flow compares `7494758` against itself because the promotion repository did not move between the two promotions.

### The top-level field is `flows[0]`

`deployment_diff` appears twice in each artifact:

- `artifacts[].deployment_diff`, at the top level, and
- `artifacts[].flows[].deployment_diff`, one per flow.

The top-level field is a verbatim copy of `flows[0].deployment_diff`, kept for convenience and backwards compatibility. It is not an aggregate and does not pick the most relevant flow; it reflects stored order.

<Warning>
If your artifacts are reported into more than one flow, read `flows[]` and select the flow you mean by `flow_name`. Do not build on the top-level copy.
</Warning>

For a single-flow artifact the two are identical and the top-level field is all you need.

## When it is `null`

`deployment_diff` is always present in the response. When there is nothing to compare against, the value is `null`. That happens when:

- **The artifact has no provenance.** Something is running that was never reported to a flow, so there is no history to search.
- **Nothing from this flow has run here before.** The first deploy of a service into an environment has nothing to diff against.
- **No start event can be resolved** for the current artifact before this snapshot, an edge case in older data.

When it is not `null`, every sub-field is present. `previous_trail_name` and `previous_template_reference_name` can individually be `null` for artifacts reported before trails and templates existed. `diff_url` can be an empty string (see below). The remaining fields always have a value.

## Three caveats

- **`previous_running` is about this snapshot, not history.** `false` means the predecessor is gone, the normal steady state after a deploy completes. `true` means both versions are present in this snapshot at once: a rolling deploy caught mid-flight, a canary, or a partially drained service.
- **`previous_artifact_compliance_state` is evaluated when you make the request**, not frozen at snapshot time. If someone attests evidence against the older artifact tomorrow, this field can change for a snapshot taken today. Compliance is a live judgment about an artifact, not a historical record of the deploy.
- **`diff_url` can be `""`.** It is built by matching the commit URL against GitHub, GitLab, Bitbucket, and Azure DevOps. Any other git host produces an empty string rather than a broken link. `previous_git_commit` and the artifact's own `git_commit` are still there, so you can build the link yourself.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Improvement — previous_git_commit_url is missing, and it's the field this caveat should point at.

The response carries a ninth sub-field the page never mentions: previous_git_commit_url (54 occurrences in the generated sample, e.g. client_reference/kosli_get_snapshot.md:1675). It is a ready-made link to the predecessor's commit.

Two consequences:

  • This bullet tells the reader to fall back to previous_git_commit and the artifact's own git_commit and "build the link yourself" — when for the previous commit specifically they already have previous_git_commit_url. The genuinely missing piece when diff_url is "" is only the compare URL.
  • Line 84's nullability enumeration walks the sub-fields and skips this one, so the page reads as a complete field inventory while being one short.

Recommend naming previous_git_commit_url in the line 84 sentence, and in this bullet saying what is actually absent: the compare link, which you can construct from previous_git_commit and git_commit (or note whether previous_git_commit_url is also empty for an unrecognized host — worth checking, since it's reported rather than pattern-matched).

Fix this →


## See it yourself

`cyber-dojo` is a public organization, so no API token is needed:

```shell
BASE="https://app.kosli.com/api/v2/snapshots/cyber-dojo/aws-prod"

# the deployment diff for the web service, from the flow that builds it
curl -s "$BASE/-1" | jq '.artifacts[] | select(.name | contains("/web:")) | .flows[] | select(.flow_name == "web-ci") | .deployment_diff'

# one line per artifact: name and the top-level diff_url
curl -s "$BASE/-1" | jq -r '.artifacts[] | "\(.name) -> \(.deployment_diff.diff_url // "no diff")"'
```

`-1` is the latest snapshot. The other snapshot expressions (`#N`, `~N`, `@{...}`) are listed on the [get snapshot](/api-reference/snapshots/get-snapshot) page. For a private organization, authenticate with your [API token](/administration/authentication/api_authentication_methods).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Improvement — the other snapshot expressions don't work in a URL the way this reads.

#N, ~N and @{...} are the CLI's ENVIRONMENT-NAME-OR-EXPRESSION grammar, where they attach to the environment name (prod#42, prod~5, prod@{2.hours.ago} — see client_reference/kosli_get_snapshot.md:13-28). Here the only example is a URL path segment ($BASE/-1), so a reader naturally tries $BASE/#42 — and # starts a fragment, so curl never sends it and the request silently returns the latest snapshot instead of snapshot 42. @{...} has the same encoding problem with {/}.

Recommend showing the API form explicitly rather than deferring to the CLI grammar — e.g. a plain numbered id ($BASE/5352) alongside -1, and if #N really is accepted on this path, say it must be percent-encoded as %23N.

Fix this →


## Related

- [get snapshot](/api-reference/snapshots/get-snapshot) — the response schema, including every `deployment_diff` field.
- [`kosli get snapshot`](/client_reference/kosli_get_snapshot) and [`kosli diff snapshots`](/client_reference/kosli_diff_snapshots) — the CLI equivalents.
- [Querying Kosli](/tutorials/querying_kosli) and [From commit to production](/tutorials/following_a_git_commit_to_runtime_environments) — tutorials that walk through snapshot diffs.
- [Environments](/getting_started/environments) — creating environments and reporting snapshots.
13 changes: 12 additions & 1 deletion config/navigation.json
Original file line number Diff line number Diff line change
Expand Up @@ -460,7 +460,18 @@
{
"item": "API Reference",
"icon": "code",
"openapi": "https://app.kosli.com/api/v2/openapi.json"
"groups": [
{
"group": "Guides",
"pages": [
"api-reference/snapshot_artifacts_and_deployment_diffs"
]
},
{
"group": "Endpoints",
"openapi": "https://app.kosli.com/api/v2/openapi.json"
}
]
Comment on lines +463 to +474

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Improvement (verify on the preview) — this changes the shape of the API Reference item that ~14 existing links depend on.

openapi moved from being the item's direct value to sitting inside a groups entry. The auto-generated endpoint URLs (/api-reference/<tag>/<operation>) should be unaffected, since Mintlify derives page paths from the item, not the group label — but nothing in CI will tell you if that assumption is wrong: mint broken-links does not resolve auto-generated OpenAPI pages, and the link-rot check is unreliable per CLAUDE.md.

If the prefix did change, these break silently:

  • understand_kosli/what_is_kosli.md:12/api-reference/actions/list-actions
  • getting_started/enforce_policies.md:127,143/api-reference/asserts/assert-artifact
  • tutorials/rotating_api_keys.mdx:99,127,148-150 and administration/authentication/api_key_rotation.md:23-25/api-reference/service-accounts/...
  • user/default_organization.md:25/api-reference/user/set-default-organization
  • changelog/index.mdx:1068
  • config/redirects.json:84 — whose destination is /api-reference/actions/list-actions, so a broken destination turns a working redirect into a dead end
  • this PR's own page, which links /api-reference/snapshots/get-snapshot four times

The PR notes "a generated endpoint page renders (200)". Worth confirming specifically that one of the pre-existing URLs above still resolves on the Mintlify preview, not just that some endpoint page exists.

},
{
"item": "GitHub Action Reference",
Expand Down
2 changes: 2 additions & 0 deletions tutorials/querying_kosli.md
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,8 @@ You can also diff two different environments to see what's running in one but no
kosli diff snapshots aws-beta~3 aws-prod
```

A snapshot diff tells you what changed between two points in time. To find out what a single running artifact *replaced*, and the commit range between the two, read its `deployment_diff` in the API response instead. See [Snapshot artifacts and deployment diffs](/api-reference/snapshot_artifacts_and_deployment_diffs).

## Don't parse the raw CLI output

The human-readable output you see above (tables, `COMPLIANT`/`NON-COMPLIANT` labels, history lines, etc.) is intended for people, not scripts. The exact wording, casing, and field labels may change between CLI versions to improve clarity or to stay aligned with the UI, and parsing this text in scripts or CI gates will silently break when it does.
Expand Down
4 changes: 4 additions & 0 deletions understand_kosli/glossary.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ The chain of custody that links an artifact's fingerprint to its origin, build e

A repeatable, verifiable activity that mitigates one or more [risks](/understand_kosli/risks). Controls span build, release, runtime, and lifecycle phases.

### Deployment Diff

For an artifact running in an Environment, a pointer to the artifact it replaced there: the previous artifact from the same Flow, its commit, and the commit range between the two. Computed per Flow from snapshot start events. See [Snapshot artifacts and deployment diffs](/api-reference/snapshot_artifacts_and_deployment_diffs).

### Environment

A Kosli representation of a runtime system you want to monitor (Kubernetes cluster, ECS service, Lambda function, S3 bucket, server directory, Docker host, Azure Web App).
Expand Down
2 changes: 1 addition & 1 deletion understand_kosli/how_kosli_works.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ Attestations can carry attached evidence files (test reports, scan output, appro

Kosli tracks what is running in your production (and non-production) systems through **Environments**. The reason Environments are modeled separately from Flows is that what you build and what you run are fundamentally different concerns that need independent tracking. Each physical or virtual runtime you want to monitor gets its own Kosli Environment: a Kubernetes cluster, an ECS service, a Lambda function, an S3 bucket, or a server directory.

Kosli supports periodic **Environment Snapshots** that capture the artifacts running at a point in time. For each artifact in a snapshot, Kosli traces it back to the Flow and Trail that produced it, creating a direct link between what was built and what is running. This is how Kosli implements [runtime controls](/understand_kosli/controls) like drift detection and shadow change identification.
Kosli supports periodic **Environment Snapshots** that capture the artifacts running at a point in time. For each artifact in a snapshot, Kosli traces it back to the Flow and Trail that produced it, creating a direct link between what was built and what is running. This is how Kosli implements [runtime controls](/understand_kosli/controls) like drift detection and shadow change identification. Each artifact in a snapshot also carries a [deployment diff](/api-reference/snapshot_artifacts_and_deployment_diffs): a pointer to the artifact it replaced in that environment and the commit range between them.

An **Environment Policy** defines compliance requirements for what may run in an environment. If an artifact appears in a snapshot that does not satisfy the policy, Kosli flags it as non-compliant. Third-party artifacts that you trust but did not build yourself can be allowlisted.

Expand Down
Loading