# Deployment source

Identify what initiated each deployment — a Git push, the CLI, the dashboard, the API, or the platform itself.

Every deployment records a **`deploy_source`**: what initiated it. Use it to tell an automated Git push apart from a manual CLI deploy, or to filter a project's deployment history by origin.

`deploy_source` is returned on Function and Frontend deployments and on the project deployment feed.

## Values

| Value     | Meaning                                                                                             |
| --------- | --------------------------------------------------------------------------------------------------- |
| `git`     | A Git push to the connected repository's production branch (auto-deploy).                           |
| `cli`     | A deploy from the Volcano CLI.                                                                      |
| `web`     | A deploy from the dashboard.                                                                        |
| `api`     | A deploy from a direct API call with a platform token.                                              |
| `system`  | Platform-initiated work with no user actor, such as region convergence or a project-delete cascade. |
| `unknown` | Origin not recorded — historical deployments created before this field existed.                     |

A deployment correlated to a Git deployment run is always `git`, regardless of how the request reached the API.

## Reading the deployment feed

The project deployment feed returns `deploy_source` on each attempt:

```bash
curl -H "Authorization: Bearer $VOLCANO_TOKEN" \
  "https://api.volcano.dev/projects/$PROJECT_ID/deployments?limit=20"
```

```json
{
  "data": [
    {
      "id": "d1f8…",
      "resource": { "type": "function", "id": "fn_…", "name": "api", "kind": "standard" },
      "operation": "deploy",
      "status": "active",
      "deploy_source": "git",
      "created_at": "2026-08-06T17:04:11Z"
    }
  ],
  "total": 1,
  "limit": 20,
  "has_more": false
}
```

The feed lists functions and frontends together. Standard and
[durable](https://docs.volcano.dev/platform/functions/durable-functions.md) functions both appear as
`"type": "function"`, and `resource.kind` tells them apart. Narrow the feed with
`resource_type=function` or `resource_type=frontend`, and with
`function_kind=standard` or `function_kind=durable`:

```bash
curl -H "Authorization: Bearer $VOLCANO_TOKEN" \
  "https://api.volcano.dev/projects/$PROJECT_ID/deployments?function_kind=durable&limit=20"
```

`function_kind` implies function deployments, so frontends drop out. Combining
it with `resource_type=frontend` answers `400`.

## Commit

A deploy client can report the commit its upload came from. Volcano records it on the deployment as `commit`, separate from `deploy_source`. Send it as a JSON-encoded `commit` field on a function, durable function, or frontend upload, or as `commit` on each entry of a batch upload's `functions` manifest:

```bash
curl -X POST "https://api.volcano.dev/projects/$PROJECT_ID/frontends" \
  -H "Authorization: Bearer $VOLCANO_TOKEN" \
  -F name=web \
  -F archive=@web.tar.gz \
  -F 'commit={"repository":"acme/app","sha":"4f1c2b7d9e0a8b3c6d5e4f3a2b1c0d9e8f7a6b5c","dirty":false,"root":"apps/web"}'
```

| Field        | Value                                                                                                                                                                                                                                                      |
| ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `repository` | `owner/name`, or a github.com remote URL such as `https://github.com/acme/app.git` or `git@github.com:acme/app.git`. Stored as `owner/name`, without credentials. An SSH host alias such as `github.com-work` isn't recognized; send `owner/name` instead. |
| `sha`        | The full 40-character commit ID.                                                                                                                                                                                                                           |
| `dirty`      | `true` when the uploaded files differ from the commit.                                                                                                                                                                                                     |
| `root`       | The uploaded directory's path from the repository root, such as `apps/web`, or `""` for the repository root. Optional.                                                                                                                                     |

`repository`, `sha`, and `dirty` are required. Fields Volcano doesn't know are ignored. An empty `commit` field, `null`, or `""` reports no commit. A malformed commit, or a repository not on GitHub, answers `400` and deploys nothing.

Volcano then checks the commit through the project's [GitHub connection](https://docs.volcano.dev/platform/projects/git-deploy.md) and records what it confirmed as `verification`:

| `verification` | What Volcano confirmed                                                                                                                                                                                                                                      |
| -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `files`        | The commit is the connected repository's, on its production branch, and every uploaded file is the commit's. Only a frontend upload is checked this far.                                                                                                    |
| `repository`   | The commit is the connected repository's and on its production branch. A function or durable function upload holds build output, not the repository's layout, so its files aren't compared. Only a function commit reported with `dirty: false` is checked. |
| `none`         | Nothing.                                                                                                                                                                                                                                                    |

`verified` is `true` only for `files`. A `repository` commit says which commit on the production branch the deploy client built from, not that the deployed code is that commit's: anyone who can deploy to the project can report any commit on that branch, an older one included. [LavaFlow](https://docs.volcano.dev/platform/guides/lavaflow.md) reads the repository at a function deployment's `repository` commit, never the uploaded files, and only while the project's repository link is unchanged since Volcano checked the commit. Relinking, reinstalling the GitHub App, renaming the repository, or changing the production branch or root directory ends it; see [Code LavaFlow reads](https://docs.volcano.dev/platform/guides/lavaflow.md#code-lavaflow-reads).

A frontend's commit is `files` when:

- `repository` is the project's connected repository, matched by repository ID so a rename still matches;
- `sha` is on its production branch;
- every uploaded file is the commit's file at `root` plus its path in the archive, with the same owner execute bit. A file matches byte for byte, after Git's CRLF-to-LF conversion, or as its Git LFS pointer where the commit's `.gitattributes` files apply `filter=lfs` to its path;
- the upload holds every file the commit has under `root`, except ones deploy clients leave out: a path with a segment named `.git`, `.gitignore`, `node_modules`, `.next`, `dist`, `build`, `.vscode`, `.idea`, `.DS_Store`, `__pycache__`, `.pytest_cache`, `.mypy_cache`, `.ruff_cache`, `.bundle`, `.env.local`, or `volcano.env`, or matching `.env*.local`, `*.log`, `*.pyc`, `*.pyo`, `*.rbc`, `*.swp`, or `*.swo`, and `volcano/*.env` at `root`.

`root` must be the project's root directory, a directory inside it, or one of its parents. An upload from a parent must name an app inside the project's root directory by its path from `root`: in `app_root`, or else on the first line of a `.volcano/frontend-app-path` file at the archive root, which the CLI writes when it uploads a monorepo app from the workspace root. An uploaded file in the project's root directory that the commit doesn't hold sets `dirty: true`, even when another file can't be checked. A file under a symlink or submodule, a file whose owner execute bit doesn't match the commit's file mode, a file outside the project's root directory that the commit doesn't hold, or one whose match depends on an attribute or attribute pattern Volcano doesn't read leaves the commit unverified without setting `dirty`.

A commit Volcano can't verify is still recorded, with `verification: none`, and the deploy goes ahead. That covers a commit from another branch or repository, an upload without `root`, a `root` or archive path longer than 4,096 bytes or 128 directories deep, an upload with no file to compare, an upload that leaves out another file, symlink, or submodule the commit holds under `root`, an upload from a parent without a `package.json` at its archive root whose only top-level directory, counting dot-directories, has one, a project with no connection, an archive whose global header sets anything but a comment, an archive entry behind more than one extended header, an extended or long-name header with anything but NULs after its data, a directory entry with a `.` or `..` segment, a directory entry the commit holds no file under, a sparse or pre-POSIX archive entry, a tar header with a number that isn't plain octal digits or with a name prefix outside a POSIX ustar header, and a check GitHub doesn't answer in time. Volcano doesn't check a commit reported with `dirty: true`; it's recorded with `verification: none`.

A function or durable function commit is `repository` when `repository` is the project's connected repository, matched by repository ID, `sha` is on its production branch, and the commit is reported with `dirty: false`. Any other function commit, and one Volcano can't check in time, is `none`.

Function and frontend deployment listings, the project deployment feed, and `GET /deployments` return the commit. A deployment whose client reported none has no `commit`:

```bash
curl -H "Authorization: Bearer $VOLCANO_TOKEN" \
  "https://api.volcano.dev/projects/$PROJECT_ID/frontends/$FRONTEND_ID/deployments"
```

```json
{
  "data": [
    {
      "id": "7c2e…",
      "status": "active",
      "deploy_source": "cli",
      "commit": {
        "repository": "acme/app",
        "sha": "4f1c2b7d9e0a8b3c6d5e4f3a2b1c0d9e8f7a6b5c",
        "dirty": false,
        "verified": true,
        "verification": "files"
      }
    }
  ]
}
```

A frontend deployed from another frontend's build carries the commit of the deployment it publishes. A redeploy reports no commit; it serves the code of the deployment that built its artifact.

## Attribution

Request-initiated deployments (`cli`, `web`, `api`) also record `initiated_by`, the platform user that triggered them. `git` and `system` deployments have no user actor, so `initiated_by` is absent.

## Declaring the dashboard client

Authentication is authoritative for the `cli` classification. A non-CLI client that is the dashboard should send the `X-Volcano-Client: web` request header so its deployments are recorded as `web` rather than `api`.
