GitLab API sandbox

GitLab

Point your GitLab client (e.g. python-gitlab) at https://gl.sandboxapis.dev. Paths mirror gitlab.com/api/v4.

REST · 743 endpointsGraphQL · 12 entitiesMCP-ready

Coverage badge

Deep751/755 · 99%

The share of read rows with a final answer — served and verified, a deviation, retired upstream, or a reviewed empty/refusal. Computed in the coverage manifest, copied here.

Live host
gl.sandboxapis.dev
Pinned hosts
10 — generation 1, 2, 3, 5, 6, 8, 9, 10, 11, 12
Serving since
2026-07-31
Lifecycle
Live

Served & verified

452 / 60%

Answering with real universe data, each response checked against GitLab's published spec by the conformance suite on this build. 83 of them are marked a deviation — served faithfully, but failing the vendored spec.

The full read API

755

Every read surface GitLab publishes, deferred long tail included, minus the rows excluded by policy. Writes are out of scope: this universe is read-only.

Not served yet

303

Each returns an explicit, provider-shaped coverage error naming the gap — never invented data.

01 / The swap

Point your client at a different base URL.

No SDK of ours, no shim, no recorded fixtures. The same client library you already use, one environment variable different.

shell
curl https://gl.sandboxapis.dev/api/v4/projects/olympus-labs%2Fparthenon

Verified drop-in clients

  • renovate44.171.1

The versions the conformance suite drives against this host on every build — pinned in coverage/client-pins.yaml, and watched weekly for upstream releases, because a client library moving without us is how a shipped integration breaks silently.

02 / Coverage by family

Every read surface, grouped the way GitLab groups it.

All 755 rows the coverage manifest carries for GitLab, deferred long tail included and nothing capped. Open a family, or filter by path to find the exact endpoint your client calls.

Status — what a conformance test found

served & verified
answers with real universe data, and this build checked that response against the vendor's spec.
deviation
served and faithful to the real provider, but failing the vendored spec — usually a bug in the spec.
retired
the vendor removed the endpoint; snapshots pinned before that date still serve it.
planned / deferred
not served yet — an explicit coverage error naming the gap, never invented data.
excluded
out of the claim by policy (writes, and surfaces we refuse); not in any denominator on this page.

Mode — what kind of answer a row gets

derive
the response is computed from artifact rows that already exist
generate
canon does not carry this yet; the generator will produce it, then derive
empty
the true answer for this universe is an empty collection — reason + reviewed date required
refuse
mirror the provider's OWN refusal (e.g. its 403 for a non-admin token) — reason + reviewed date required
read-only
a write named in the manifest because clients probe it; the read-only 403 IS its final response, and it never joins the badge denominator
scratch
a write this replica ACCEPTS, into a per-key 24-hour overlay, answering with the provider's own success shape - reason + reviewed date required. The compiled artifact is unchanged and no other caller ever sees the row; a caller WITHOUT a scratch-enabled key gets the read-only refusal on the identical path. Like read-only it never joins the badge denominator (founder ruling 5, 2026-09-17: the published figure has meant READ operations served since A0.1), and unlike read-only it ANSWERS, so the generation report counts it in its own `scratch` column rather than in (excluded). Scratch never creates a pull request, a branch, a commit or a diff - those are canon structure the overlay cannot invent

A row with no mode shown has not been judged yet. Modes are the manifest's own words, from coverage/MODES.yaml; every empty and refuse carries a written reason and a review date before it counts as final.

Held — buildable, deliberately not built yet

data-licensing
the values are VENDOR reference data this project holds no grant to redistribute. Serving needs an independently licensed source, so the outstanding work is a licensing answer, not code.

held is a label, not a mode and not a status. A held row is one somebody looked at, judged buildable, and left with the blocker named — so it is not covered, counts against the ratio above exactly like any other unbuilt row, and still answers the coverage 404. It buys nothing; it only tells you why.

Badge — where Deep starts

Full
every published read row has a final answer
Deep
60% up to 100%
Partial
25% up to 60%
Preview
under 25%

REST + GraphQL + git rows that are not `excluded`. Write operations are NOT rows (DECISIONS 2026-09-01 decision 9): they are counted in meta.write_operations and never enter this ratio.

755 read surfaces in 107 families

packages27 of 79 served & verified · 3 deviations
GET
/api/v4/group/{id}/-/packages/composer/p/{sha}

SERVED 2026-10-23 (fleet reserve). REAL GITLAB ANSWERS 200 HERE whatever a group has published, and the body is now transcribed from GitLab's own source rather than guessed: `Packages::Composer::PackagesPresenter` over an empty package set, which is the set here because `package.kind` carries no `composer` row. This row is the PROVIDER document, `{"providers":{}}`, served whatever sha addresses it, because GitLab never checks the sha: it is the client's cache key, and the root publishes the digest of exactly these bytes.

served & verified
Long tail
GET
/api/v4/group/{id}/-/packages/composer/packages

SERVED 2026-10-23 (fleet reserve). REAL GITLAB ANSWERS 200 HERE whatever a group has published, and the body is now transcribed from GitLab's own source rather than guessed: `Packages::Composer::PackagesPresenter` over an empty package set, which is the set here because `package.kind` carries no `composer` row. This row is the ROOT `packages.json`: `packages: []`, the `metadata-url` template, and for a Composer v1 client the `provider-includes` sha256 over the provider document's own bytes (`{"providers":{}}`) plus the `providers-url` template; a `Composer/2` User-Agent gets the short root, GitLab's own branch. Conformance hashes the provider route's bytes and holds them to the published sha.

served & verified
Long tail
GET
/api/v4/groups/{id}/-/debian_distributions

A `Packages::Debian::Distribution` is a CONFIGURATION OBJECT an operator creates with a write, not a package - and this universe has issued no writes, so both containers publish the empty list GitLab publishes for a scope that never ran `POST /debian_distributions`. This is a claim about olympus-labs rather than about the generation: the registry EXISTS on a hello-20 artifact and holds no Debian distribution. All eleven declared filters are answered rather than refused, because each narrows a collection that is empty before it narrows anything and the answer is the same either way (the two array parameters are accepted in Grape's bracketed spelling too, `components[]` and `architectures[]`, which is what a real client sends); the `shape` DRIVES the claim, so the day canon grows a distribution the row reds rather than letting those filters go on being ignored. Classified `derive` rather than `empty` DELIBERATELY: `packages/conformance/src/manifest/honest-empty-served.test.ts` runs every GitLab `empty` row on the DEFAULT hello-4 harness and argues that "a row that needed hello-20 to answer `[]` would be a generation gap wearing a ruling's clothes" - and this row answers `noPackageRegistry` on a hello-19 artifact, so it is exactly the row that rule excludes. Its conformance entry carries a `shape` instead, which is STRICTER than `expectsEmpty`: it checks BOTH ENDS, the empty collection on the wire AND the absence of that ecosystem in `packagesForRepo()`, so the day canon publishes one the row reds on the canon side before the response even changes.. This route answers the group's list. SERVED 2026-09-14 (hello-20 `packageRegistry`): gated on `reader.hasPackageRegistry()`, with the project or group resolving FIRST so an unknown one is GitLab's own 404 on every generation. An older artifact - the 127 pins frozen before hello-20, and live until the hello-20 cutover - answers `noPackageRegistry`, the named generation gap, rather than `[]`: an empty package list would claim an organization with three tagged releases, 344 build outputs and a root `Dockerfile` publishes nothing, which the same artifact contradicts

served & verified
Long tail
GET
/api/v4/groups/{id}/-/packages/nuget/index

A NuGet v3 SERVICE INDEX, which describes the feed rather than its contents and is therefore as true of a scope with no NuGet package as of one with a thousand. It advertises exactly ONE resource - the `SearchQueryService` at this scope's own `/packages/nuget/query` - because that is the only resource of this feed the replica serves; GitLab's own index also names a publish resource, a registrations base and a package base address, and advertising three endpoints that answer the coverage 404 would be a service document lying about its own service. Conformance follows every advertised `@id` and requires it to answer 200. This route answers the group's feed. SERVED 2026-09-14 (hello-20 `packageRegistry`): gated on `reader.hasPackageRegistry()`, with the project or group resolving FIRST so an unknown one is GitLab's own 404 on every generation. An older artifact - the 127 pins frozen before hello-20, and live until the hello-20 cutover - answers `noPackageRegistry`, the named generation gap, rather than `[]`: an empty package list would claim an organization with three tagged releases, 344 build outputs and a root `Dockerfile` publishes nothing, which the same artifact contradicts

served & verified
Long tail
GET
/api/v4/groups/{id}/-/packages/nuget/query

The NuGet search, RUN over `packagesForRepo()` rather than skipped: `package.kind` is npm, golang, pypi or generic on every row of every artifact, so no `nuget` package exists to match any query and `{totalHits: 0, data: []}` is exactly what GitLab answers for a feed with nothing in it. `q`, `skip`, `take` and `prerelease` are all accepted; each narrows a result set that is empty before it narrows anything. The conformance `shape` DRIVES the claim, so the day canon publishes a NuGet package this row goes red instead of hiding it. Classified `derive` rather than `empty` DELIBERATELY: `packages/conformance/src/manifest/honest-empty-served.test.ts` runs every GitLab `empty` row on the DEFAULT hello-4 harness and argues that "a row that needed hello-20 to answer `[]` would be a generation gap wearing a ruling's clothes" - and this row answers `noPackageRegistry` on a hello-19 artifact, so it is exactly the row that rule excludes. Its conformance entry carries a `shape` instead, which is STRICTER than `expectsEmpty`: it checks BOTH ENDS, the empty collection on the wire AND the absence of that ecosystem in `packagesForRepo()`, so the day canon publishes one the row reds on the canon side before the response even changes.. This route answers the group's search. SERVED 2026-09-14 (hello-20 `packageRegistry`): gated on `reader.hasPackageRegistry()`, with the project or group resolving FIRST so an unknown one is GitLab's own 404 on every generation. An older artifact - the 127 pins frozen before hello-20, and live until the hello-20 cutover - answers `noPackageRegistry`, the named generation gap, rather than `[]`: an empty package list would claim an organization with three tagged releases, 344 build outputs and a root `Dockerfile` publishes nothing, which the same artifact contradicts

served & verified
Long tail
GET
/api/v4/groups/{id}/-/packages/nuget/v2

SERVED 2026-10-23 (fleet reserve). REAL GITLAB ANSWERS 200 HERE whatever a namespace has published, and the body is now transcribed from GitLab's own source rather than guessed: the old reason held these rows until the bytes could be checked against GitLab's publication, and GitLab's presenters ARE its publication. This row is the group's OData v2 service document, `ServiceIndexPresenter#xml`: an AtomPub workspace whose `xml:base` is the group's own feed root and whose one collection is `Packages`, the same document GitLab's NuGet API documentation prints.

served & verified
Long tail
GET
/api/v4/groups/{id}/-/packages/nuget/v2/$metadata

SERVED 2026-10-23 (fleet reserve). REAL GITLAB ANSWERS 200 HERE whatever a namespace has published, and the body is now transcribed from GitLab's own source rather than guessed: the old reason held these rows until the bytes could be checked against GitLab's publication, and GitLab's presenters ARE its publication. This row is the EDMX metadata document, `MetadataIndexPresenter#xml`, which is the same for every project and group because it describes the feed's entity type (`V2FeedPackage`) and its `FindPackagesById` function, not any package.

served & verified
Long tail
GET
/api/v4/groups/{id}/-/packages/pypi/simple

`packagesForRepo()` narrowed to `kind = 'pypi'` - the one ecosystem index in these 88 rows with something in it, because this organization really does publish a Python distribution (`requirements.txt` is in the head tree, which is what mints the package). The body is PEP 503's simple repository index: one anchor per project, whose href is the per-project page REAL GitLab links to. That per-project route is not a manifest row on this replica and answers the coverage 404; naming it anyway is the mirror - a simple index that linked somewhere else would not be one - and a caller who follows it gets invariant #4's explicit answer rather than a silent one. Nothing but the Python distributions is listed: an index carrying the npm or Go packages would hand `pip` a project it cannot install. This route answers the group's index, over every project it holds, linking through the group's own address; the subgroup answers an index with no anchors, founder ruling F1 again. SERVED 2026-09-14 (hello-20 `packageRegistry`): gated on `reader.hasPackageRegistry()`, with the project or group resolving FIRST so an unknown one is GitLab's own 404 on every generation. An older artifact - the 127 pins frozen before hello-20, and live until the hello-20 cutover - answers `noPackageRegistry`, the named generation gap, rather than `[]`: an empty package list would claim an organization with three tagged releases, 344 build outputs and a root `Dockerfile` publishes nothing, which the same artifact contradicts

served & verified
Long tail
GET
/api/v4/packages/conan/v1/conans/search

Conan's OWN search, RUN over `packagesForRepo()` rather than skipped: `package.kind` is npm, golang, pypi or generic on every row of every artifact (the F1 decision's own enum ruling), so no Conan recipe of the form `name/version@user/channel` exists in this registry, so no pattern can match and `{"results": []}` is the whole answer. the `shape` DRIVES it, so the day canon publishes a Conan recipe this row goes red. The recipe ADDRESSES under the same prefix are `refuse` - the same fact read the other way round, and GitLab's own 404 for an object that cannot exist. Classified `derive` rather than `empty` DELIBERATELY: `packages/conformance/src/manifest/honest-empty-served.test.ts` runs every GitLab `empty` row on the DEFAULT hello-4 harness and argues that "a row that needed hello-20 to answer `[]` would be a generation gap wearing a ruling's clothes" - and this row answers `noPackageRegistry` on a hello-19 artifact, so it is exactly the row that rule excludes. Its conformance entry carries a `shape` instead, which is STRICTER than `expectsEmpty`: it checks BOTH ENDS, the empty collection on the wire AND the absence of that ecosystem in `packagesForRepo()`, so the day canon publishes one the row reds on the canon side before the response even changes.. This route is the INSTANCE-wide v1 search, across every project the caller can read. SERVED 2026-09-14 (hello-20 `packageRegistry`): gated on `reader.hasPackageRegistry()`, with the project or group resolving FIRST so an unknown one is GitLab's own 404 on every generation. An older artifact - the 127 pins frozen before hello-20, and live until the hello-20 cutover - answers `noPackageRegistry`, the named generation gap, rather than `[]`: an empty package list would claim an organization with three tagged releases, 344 build outputs and a root `Dockerfile` publishes nothing, which the same artifact contradicts

served & verified
Long tail
GET
/api/v4/packages/conan/v1/ping

GitLab answers `ping` with a HEADER-ONLY 200 whose `X-Conan-Server-Capabilities` is the EMPTY list - `header 'X-Conan-Server-Capabilities', [].join(',')` in its own Conan API - and this replica supports no Conan capability either, so the empty value is the TRUE one rather than a stub and the body is genuinely absent. It is the one Conan route that asks nothing about a recipe, which is why it is served where the recipe addresses are refused. This route is the instance-wide ping, which a client sends before it has named a project. SERVED 2026-09-14 (hello-20 `packageRegistry`): gated on `reader.hasPackageRegistry()`, with the project or group resolving FIRST so an unknown one is GitLab's own 404 on every generation. An older artifact - the 127 pins frozen before hello-20, and live until the hello-20 cutover - answers `noPackageRegistry`, the named generation gap, rather than `[]`: an empty package list would claim an organization with three tagged releases, 344 build outputs and a root `Dockerfile` publishes nothing, which the same artifact contradicts

served & verified
Long tail
GET
/api/v4/projects/{id}/debian_distributions

A `Packages::Debian::Distribution` is a CONFIGURATION OBJECT an operator creates with a write, not a package - and this universe has issued no writes, so both containers publish the empty list GitLab publishes for a scope that never ran `POST /debian_distributions`. This is a claim about olympus-labs rather than about the generation: the registry EXISTS on a hello-20 artifact and holds no Debian distribution. All eleven declared filters are answered rather than refused, because each narrows a collection that is empty before it narrows anything and the answer is the same either way (the two array parameters are accepted in Grape's bracketed spelling too, `components[]` and `architectures[]`, which is what a real client sends); the `shape` DRIVES the claim, so the day canon grows a distribution the row reds rather than letting those filters go on being ignored. Classified `derive` rather than `empty` DELIBERATELY: `packages/conformance/src/manifest/honest-empty-served.test.ts` runs every GitLab `empty` row on the DEFAULT hello-4 harness and argues that "a row that needed hello-20 to answer `[]` would be a generation gap wearing a ruling's clothes" - and this row answers `noPackageRegistry` on a hello-19 artifact, so it is exactly the row that rule excludes. Its conformance entry carries a `shape` instead, which is STRICTER than `expectsEmpty`: it checks BOTH ENDS, the empty collection on the wire AND the absence of that ecosystem in `packagesForRepo()`, so the day canon publishes one the row reds on the canon side before the response even changes.. This route answers the project's list. SERVED 2026-09-14 (hello-20 `packageRegistry`): gated on `reader.hasPackageRegistry()`, with the project or group resolving FIRST so an unknown one is GitLab's own 404 on every generation. An older artifact - the 127 pins frozen before hello-20, and live until the hello-20 cutover - answers `noPackageRegistry`, the named generation gap, rather than `[]`: an empty package list would claim an organization with three tagged releases, 344 build outputs and a root `Dockerfile` publishes nothing, which the same artifact contradicts

served & verified
Long tail
GET
/api/v4/projects/{id}/packages/{package_id}/package_files

`package_file` through `packageFiles(version)`: the file's name, the `size` the compiler measured and the `file_sha256` it measured over the same bytes. `file_md5` and `file_sha1` are OMITTED rather than nulled - the compiler measured ONE digest over these bytes and a second here would be a number no part of this universe ever computed (invariant #4). `order_by` and `sort` are answered over `id` and `file_name`, the two of the operation's three enum members this artifact can order by. SERVED 2026-09-14 (hello-20 `packageRegistry`): gated on `reader.hasPackageRegistry()`, with the project or group resolving FIRST so an unknown one is GitLab's own 404 on every generation. An older artifact - the 127 pins frozen before hello-20, and live until the hello-20 cutover - answers `noPackageRegistry`, the named generation gap, rather than `[]`: an empty package list would claim an organization with three tagged releases, 344 build outputs and a root `Dockerfile` publishes nothing, which the same artifact contradicts

served & verified
Long tail
GET
/api/v4/projects/{id}/packages/{package_id}/package_files/{package_file_id}/download

FOUNDER RULING F4 (2026-09-14), "package files = digest + deterministic rebuild", on the wire. The artifact stores `size_bytes` and `sha256` and NO BYTES, and this route rebuilds them at request time through `packageArchive(reader.treeFiles(version.commit_id), packageArchiveRoot(package.name, version.version), version.published_epoch, packageArchiveFormat(package.kind))` - the SAME `@sandboxapis/archive` entry point `compiler/src/compile.ts`'s `packageArchiveBytes()` measured the digest with, so `shasum` on the download returns the `file_sha256` the file list published. A `source_kind: artifact` file rebuilds nothing: it republishes the build output a job really uploaded, whose bytes are `artifactContent()` and whose digest the compiler measured over exactly those bytes, so the package surface and the Actions surface quote the same two numbers for the same upload. The digest is deliberately NOT rehashed on the request path - `verifyPackageFileDigests()` already fails the COMPILE on a mismatch, on every compile of every template - and conformance hashes the served bytes against the row instead, which is the check that catches this renderer rebuilding the archive a different way from that one. SERVED 2026-09-14 (hello-20 `packageRegistry`): gated on `reader.hasPackageRegistry()`, with the project or group resolving FIRST so an unknown one is GitLab's own 404 on every generation. An older artifact - the 127 pins frozen before hello-20, and live until the hello-20 cutover - answers `noPackageRegistry`, the named generation gap, rather than `[]`: an empty package list would claim an organization with three tagged releases, 344 build outputs and a root `Dockerfile` publishes nothing, which the same artifact contradicts

served & verified
Long tail
GET
/api/v4/projects/{id}/packages/{package_id}/pipelines

`package_version.job` resolved to its job and that job's run, rendered through the SAME `shapePipeline(..., full: false)` `/projects/{id}/pipelines` renders with, so one pipeline cannot be answered two ways (invariant #5), plus the `user` `APIEntitiesPackagePipeline` carries. A version canon records no build job for answers `[]` rather than an invented pipeline: `job` is NULL exactly when no successful `build` finished before that release inside the window. SERVED 2026-09-14 (hello-20 `packageRegistry`): gated on `reader.hasPackageRegistry()`, with the project or group resolving FIRST so an unknown one is GitLab's own 404 on every generation. An older artifact - the 127 pins frozen before hello-20, and live until the hello-20 cutover - answers `noPackageRegistry`, the named generation gap, rather than `[]`: an empty package list would claim an organization with three tagged releases, 344 build outputs and a root `Dockerfile` publishes nothing, which the same artifact contradicts

served & verified
Long tail
GET
/api/v4/projects/{id}/packages/cargo/config.json

CARGO'S OWN registry-index configuration - `dl`, `api`, `auth-required` - which describes the registry rather than its crates and is therefore as true of a project with none. Both URLs are this project's own cargo root, derived from the request's own host and the project's id; `auth-required` is `true` because real GitLab requires a token on every cargo route, and reporting `false` would describe THIS host rather than the one being mirrored (invariant #2). The five crate-index and download addresses under it are `refuse`: no `cargo` package exists to be indexed, and GitLab's own 404 is the whole answer there. SERVED 2026-09-14 (hello-20 `packageRegistry`): gated on `reader.hasPackageRegistry()`, with the project or group resolving FIRST so an unknown one is GitLab's own 404 on every generation. An older artifact - the 127 pins frozen before hello-20, and live until the hello-20 cutover - answers `noPackageRegistry`, the named generation gap, rather than `[]`: an empty package list would claim an organization with three tagged releases, 344 build outputs and a root `Dockerfile` publishes nothing, which the same artifact contradicts

served & verified
Long tail
GET
/api/v4/projects/{id}/packages/conan/v1/conans/search

Conan's OWN search, RUN over `packagesForRepo()` rather than skipped: `package.kind` is npm, golang, pypi or generic on every row of every artifact (the F1 decision's own enum ruling), so no Conan recipe of the form `name/version@user/channel` exists in this registry, so no pattern can match and `{"results": []}` is the whole answer. the `shape` DRIVES it, so the day canon publishes a Conan recipe this row goes red. The recipe ADDRESSES under the same prefix are `refuse` - the same fact read the other way round, and GitLab's own 404 for an object that cannot exist. Classified `derive` rather than `empty` DELIBERATELY: `packages/conformance/src/manifest/honest-empty-served.test.ts` runs every GitLab `empty` row on the DEFAULT hello-4 harness and argues that "a row that needed hello-20 to answer `[]` would be a generation gap wearing a ruling's clothes" - and this row answers `noPackageRegistry` on a hello-19 artifact, so it is exactly the row that rule excludes. Its conformance entry carries a `shape` instead, which is STRICTER than `expectsEmpty`: it checks BOTH ENDS, the empty collection on the wire AND the absence of that ecosystem in `packagesForRepo()`, so the day canon publishes one the row reds on the canon side before the response even changes.. This route is the project-scoped v1 search. SERVED 2026-09-14 (hello-20 `packageRegistry`): gated on `reader.hasPackageRegistry()`, with the project or group resolving FIRST so an unknown one is GitLab's own 404 on every generation. An older artifact - the 127 pins frozen before hello-20, and live until the hello-20 cutover - answers `noPackageRegistry`, the named generation gap, rather than `[]`: an empty package list would claim an organization with three tagged releases, 344 build outputs and a root `Dockerfile` publishes nothing, which the same artifact contradicts

served & verified
Long tail
GET
/api/v4/projects/{id}/packages/conan/v1/ping

GitLab answers `ping` with a HEADER-ONLY 200 whose `X-Conan-Server-Capabilities` is the EMPTY list - `header 'X-Conan-Server-Capabilities', [].join(',')` in its own Conan API - and this replica supports no Conan capability either, so the empty value is the TRUE one rather than a stub and the body is genuinely absent. It is the one Conan route that asks nothing about a recipe, which is why it is served where the recipe addresses are refused. This route is the project-scoped ping, whose project still has to resolve - a ping for a project nobody has is GitLab's own 404, not a cheerful 200. SERVED 2026-09-14 (hello-20 `packageRegistry`): gated on `reader.hasPackageRegistry()`, with the project or group resolving FIRST so an unknown one is GitLab's own 404 on every generation. An older artifact - the 127 pins frozen before hello-20, and live until the hello-20 cutover - answers `noPackageRegistry`, the named generation gap, rather than `[]`: an empty package list would claim an organization with three tagged releases, 344 build outputs and a root `Dockerfile` publishes nothing, which the same artifact contradicts

served & verified
Long tail
GET
/api/v4/projects/{id}/packages/conan/v2/conans/search

Conan's OWN search, RUN over `packagesForRepo()` rather than skipped: `package.kind` is npm, golang, pypi or generic on every row of every artifact (the F1 decision's own enum ruling), so no Conan recipe of the form `name/version@user/channel` exists in this registry, so no pattern can match and `{"results": []}` is the whole answer. the `shape` DRIVES it, so the day canon publishes a Conan recipe this row goes red. The recipe ADDRESSES under the same prefix are `refuse` - the same fact read the other way round, and GitLab's own 404 for an object that cannot exist. Classified `derive` rather than `empty` DELIBERATELY: `packages/conformance/src/manifest/honest-empty-served.test.ts` runs every GitLab `empty` row on the DEFAULT hello-4 harness and argues that "a row that needed hello-20 to answer `[]` would be a generation gap wearing a ruling's clothes" - and this row answers `noPackageRegistry` on a hello-19 artifact, so it is exactly the row that rule excludes. Its conformance entry carries a `shape` instead, which is STRICTER than `expectsEmpty`: it checks BOTH ENDS, the empty collection on the wire AND the absence of that ecosystem in `packagesForRepo()`, so the day canon publishes one the row reds on the canon side before the response even changes.. This route is the project-scoped v2 search, Conan 2.x's own spelling of the same question. SERVED 2026-09-14 (hello-20 `packageRegistry`): gated on `reader.hasPackageRegistry()`, with the project or group resolving FIRST so an unknown one is GitLab's own 404 on every generation. An older artifact - the 127 pins frozen before hello-20, and live until the hello-20 cutover - answers `noPackageRegistry`, the named generation gap, rather than `[]`: an empty package list would claim an organization with three tagged releases, 344 build outputs and a root `Dockerfile` publishes nothing, which the same artifact contradicts

served & verified
Long tail
GET
/api/v4/projects/{id}/packages/helm/{channel}/index.yaml

HELM'S OWN repository-index format - `apiVersion`, `entries`, `generated` - which is what a Helm client asks a repository for before it knows whether there is anything in it, so every channel answers rather than only a configured one. `entries` is `{}` because `package.kind` carries no `helm` row, and `generated` is the ARTIFACT'S OWN ANCHOR (`reader.meta().anchorEpochSec`) rather than a clock read: a frozen universe has no now, and a timestamp that moved with the calendar would make one pin answer differently on two days - the call ./depth-runners.ts makes for a runner's `online`. GitLab also emits `serverInfo.contextPath`; it is OMITTED rather than guessed, because Helm's format does not require it and no client reads it to find a chart. The `.tgz` chart address is `refuse`. SERVED 2026-09-14 (hello-20 `packageRegistry`): gated on `reader.hasPackageRegistry()`, with the project or group resolving FIRST so an unknown one is GitLab's own 404 on every generation. An older artifact - the 127 pins frozen before hello-20, and live until the hello-20 cutover - answers `noPackageRegistry`, the named generation gap, rather than `[]`: an empty package list would claim an organization with three tagged releases, 344 build outputs and a root `Dockerfile` publishes nothing, which the same artifact contradicts

served & verified
Long tail
GET
/api/v4/projects/{id}/packages/nuget/index

A NuGet v3 SERVICE INDEX, which describes the feed rather than its contents and is therefore as true of a scope with no NuGet package as of one with a thousand. It advertises exactly ONE resource - the `SearchQueryService` at this scope's own `/packages/nuget/query` - because that is the only resource of this feed the replica serves; GitLab's own index also names a publish resource, a registrations base and a package base address, and advertising three endpoints that answer the coverage 404 would be a service document lying about its own service. Conformance follows every advertised `@id` and requires it to answer 200. This route answers the project's feed. SERVED 2026-09-14 (hello-20 `packageRegistry`): gated on `reader.hasPackageRegistry()`, with the project or group resolving FIRST so an unknown one is GitLab's own 404 on every generation. An older artifact - the 127 pins frozen before hello-20, and live until the hello-20 cutover - answers `noPackageRegistry`, the named generation gap, rather than `[]`: an empty package list would claim an organization with three tagged releases, 344 build outputs and a root `Dockerfile` publishes nothing, which the same artifact contradicts

served & verified
Long tail
GET
/api/v4/projects/{id}/packages/nuget/query

The NuGet search, RUN over `packagesForRepo()` rather than skipped: `package.kind` is npm, golang, pypi or generic on every row of every artifact, so no `nuget` package exists to match any query and `{totalHits: 0, data: []}` is exactly what GitLab answers for a feed with nothing in it. `q`, `skip`, `take` and `prerelease` are all accepted; each narrows a result set that is empty before it narrows anything. The conformance `shape` DRIVES the claim, so the day canon publishes a NuGet package this row goes red instead of hiding it. Classified `derive` rather than `empty` DELIBERATELY: `packages/conformance/src/manifest/honest-empty-served.test.ts` runs every GitLab `empty` row on the DEFAULT hello-4 harness and argues that "a row that needed hello-20 to answer `[]` would be a generation gap wearing a ruling's clothes" - and this row answers `noPackageRegistry` on a hello-19 artifact, so it is exactly the row that rule excludes. Its conformance entry carries a `shape` instead, which is STRICTER than `expectsEmpty`: it checks BOTH ENDS, the empty collection on the wire AND the absence of that ecosystem in `packagesForRepo()`, so the day canon publishes one the row reds on the canon side before the response even changes.. This route answers the project's search. SERVED 2026-09-14 (hello-20 `packageRegistry`): gated on `reader.hasPackageRegistry()`, with the project or group resolving FIRST so an unknown one is GitLab's own 404 on every generation. An older artifact - the 127 pins frozen before hello-20, and live until the hello-20 cutover - answers `noPackageRegistry`, the named generation gap, rather than `[]`: an empty package list would claim an organization with three tagged releases, 344 build outputs and a root `Dockerfile` publishes nothing, which the same artifact contradicts

served & verified
Long tail
GET
/api/v4/projects/{id}/packages/nuget/v2

SERVED 2026-10-23 (fleet reserve). REAL GITLAB ANSWERS 200 HERE whatever a namespace has published, and the body is now transcribed from GitLab's own source rather than guessed: the old reason held these rows until the bytes could be checked against GitLab's publication, and GitLab's presenters ARE its publication. This row is the project's OData v2 service document, `ServiceIndexPresenter#xml`: an AtomPub workspace whose `xml:base` is the project's own feed root and whose one collection is `Packages`, the same document GitLab's NuGet API documentation prints.

served & verified
Long tail
GET
/api/v4/projects/{id}/packages/nuget/v2/$metadata

SERVED 2026-10-23 (fleet reserve). REAL GITLAB ANSWERS 200 HERE whatever a namespace has published, and the body is now transcribed from GitLab's own source rather than guessed: the old reason held these rows until the bytes could be checked against GitLab's publication, and GitLab's presenters ARE its publication. This row is the EDMX metadata document, `MetadataIndexPresenter#xml`, which is the same for every project and group because it describes the feed's entity type (`V2FeedPackage`) and its `FindPackagesById` function, not any package.

served & verified
Long tail
GET
/api/v4/projects/{id}/packages/pypi/simple

`packagesForRepo()` narrowed to `kind = 'pypi'` - the one ecosystem index in these 88 rows with something in it, because this organization really does publish a Python distribution (`requirements.txt` is in the head tree, which is what mints the package). The body is PEP 503's simple repository index: one anchor per project, whose href is the per-project page REAL GitLab links to. That per-project route is not a manifest row on this replica and answers the coverage 404; naming it anyway is the mirror - a simple index that linked somewhere else would not be one - and a caller who follows it gets invariant #4's explicit answer rather than a silent one. Nothing but the Python distributions is listed: an index carrying the npm or Go packages would hand `pip` a project it cannot install. This route answers one project's index. SERVED 2026-09-14 (hello-20 `packageRegistry`): gated on `reader.hasPackageRegistry()`, with the project or group resolving FIRST so an unknown one is GitLab's own 404 on every generation. An older artifact - the 127 pins frozen before hello-20, and live until the hello-20 cutover - answers `noPackageRegistry`, the named generation gap, rather than `[]`: an empty package list would claim an organization with three tagged releases, 344 build outputs and a root `Dockerfile` publishes nothing, which the same artifact contradicts

served & verified
Long tail
GET
/api/v4/projects/{id}/packages/rubygems/api/v1/dependencies

RUBYGEMS' dependency API, which speaks MARSHAL rather than JSON - the protocol `gem install` actually uses - so the body is `Marshal.dump([])`, the four bytes `04 08 5B 00`, at `application/octet-stream`. Every name a caller asks after resolves to nothing because no `rubygems` package exists here, which is the answer RubyGems' own protocol gives for an unknown gem rather than a 404. `gems` is REQUIRED by the operation and its absence is Grape's own `{"error": "gems is missing"}` envelope - a different body from every other GitLab error, and GitLab's (./render-archives.ts mirrors the same envelope for a missing `job`). The three `.gem` file addresses are `refuse`. SERVED 2026-09-14 (hello-20 `packageRegistry`): gated on `reader.hasPackageRegistry()`, with the project or group resolving FIRST so an unknown one is GitLab's own 404 on every generation. An older artifact - the 127 pins frozen before hello-20, and live until the hello-20 cutover - answers `noPackageRegistry`, the named generation gap, rather than `[]`: an empty package list would claim an organization with three tagged releases, 344 build outputs and a root `Dockerfile` publishes nothing, which the same artifact contradicts

served & verified
Long tail
GET
/api/v4/projects/{project_id}/packages/nuget/v2/FindPackagesById\(\)

SERVED 2026-10-23 (fleet reserve). REAL GITLAB ANSWERS 200 HERE whatever a namespace has published, and the body is now transcribed from GitLab's own source rather than guessed: the old reason held these rows until the bytes could be checked against GitLab's publication, and GitLab's presenters ARE its publication. This row is `OdataPackageEntryService`, which READS NO PACKAGE: it answers one Atom entry built from the requested name with an empty version, and GitLab's documentation says the v2 feed cannot return a latest version, so the client must name one. The entry is the service's heredoc after `squish`; `id` is required and must be a NuGet package name, and `project_id` must be a positive integer, both Grape 400s.

served & verified
Long tail
GET
/api/v4/projects/{project_id}/packages/nuget/v2/Packages\(\)

SERVED 2026-10-23 (fleet reserve). REAL GITLAB ANSWERS 200 HERE whatever a namespace has published, and the body is now transcribed from GitLab's own source rather than guessed: the old reason held these rows until the bytes could be checked against GitLab's publication, and GitLab's presenters ARE its publication. This row is `OdataPackageEntryService` reached through `$filter=(tolower(Id) eq '<name>')`, the same version-less Atom entry `FindPackagesById()` answers for the same name, because GitLab routes both to one service that reads no package. `$filter` is required and must name a package, a Grape 400 otherwise.

served & verified
Long tail
GET
/api/v4/groups/{id}/packages

`package` and `package_version` through `packagesForRepo()` then `packageVersions()` - the two SCOPED accessors, because hello-19's reader rule leaves no `listPackages()`. A GitLab package row is a (name, version) pair, so canon's four packages are ELEVEN GitLab packages and `{package_id}` is the VERSION's id, never the `package` row's. `tags` is `[]` and is an ARRAY against a document that types it `string` - a declared deviation, because `Entities::Package` exposes it unconditionally and GitLab's own group example prints `"tags": []`, and canon mints no package tag so no npm dist-tag points at any version. `last_downloaded_at` is OMITTED (canon records no download) and so is `_links.delete_api_path`, which GitLab's own entity omits exactly when the caller may not destroy the package - on a read-only replica, every caller. This route answers the same rows across every project the group holds, with the `project_id`/`project_path` pair GitLab's own entity gates on the group context and the project-scoped list therefore does not carry. `exclude_subgroups` is answered rather than refused because both values select the same rows - FOUNDER RULING F1 (2026-09-09), the subgroup owns no repository - and the SUBGROUP itself answers `[]` for that same fact, the branch `/groups/{id}/projects` already makes. SERVED 2026-09-14 (hello-20 `packageRegistry`): gated on `reader.hasPackageRegistry()`, with the project or group resolving FIRST so an unknown one is GitLab's own 404 on every generation. An older artifact - the 127 pins frozen before hello-20, and live until the hello-20 cutover - answers `noPackageRegistry`, the named generation gap, rather than `[]`: an empty package list would claim an organization with three tagged releases, 344 build outputs and a root `Dockerfile` publishes nothing, which the same artifact contradicts

deviation
Long tail
GET
/api/v4/projects/{id}/packages

`package` and `package_version` through `packagesForRepo()` then `packageVersions()` - the two SCOPED accessors, because hello-19's reader rule leaves no `listPackages()`. A GitLab package row is a (name, version) pair, so canon's four packages are ELEVEN GitLab packages and `{package_id}` is the VERSION's id, never the `package` row's. `tags` is `[]` and is an ARRAY against a document that types it `string` - a declared deviation, because `Entities::Package` exposes it unconditionally and GitLab's own group example prints `"tags": []`, and canon mints no package tag so no npm dist-tag points at any version. `last_downloaded_at` is OMITTED (canon records no download) and so is `_links.delete_api_path`, which GitLab's own entity omits exactly when the caller may not destroy the package - on a read-only replica, every caller. This route answers one row per published version of this project, in GitLab's documented default order (`order_by=created_at`, `sort=asc`, canonical id as the total-order tie-break), with all six declared filters answered. `include_versionless` is answered rather than refused because BOTH VALUES SELECT THE SAME ROWS - every package here has a version - which is ./render-archives.ts's `include_lfs_blobs` precedent and not a silently dropped filter; `status` narrows to the empty list for anything but `default`, because canon mints no hidden, processing or error package. SERVED 2026-09-14 (hello-20 `packageRegistry`): gated on `reader.hasPackageRegistry()`, with the project or group resolving FIRST so an unknown one is GitLab's own 404 on every generation. An older artifact - the 127 pins frozen before hello-20, and live until the hello-20 cutover - answers `noPackageRegistry`, the named generation gap, rather than `[]`: an empty package list would claim an organization with three tagged releases, 344 build outputs and a root `Dockerfile` publishes nothing, which the same artifact contradicts

deviation
Long tail
GET
/api/v4/projects/{id}/packages/{package_id}

`package` and `package_version` through `packagesForRepo()` then `packageVersions()` - the two SCOPED accessors, because hello-19's reader rule leaves no `listPackages()`. A GitLab package row is a (name, version) pair, so canon's four packages are ELEVEN GitLab packages and `{package_id}` is the VERSION's id, never the `package` row's. `tags` is `[]` and is an ARRAY against a document that types it `string` - a declared deviation, because `Entities::Package` exposes it unconditionally and GitLab's own group example prints `"tags": []`, and canon mints no package tag so no npm dist-tag points at any version. `last_downloaded_at` is OMITTED (canon records no download) and so is `_links.delete_api_path`, which GitLab's own entity omits exactly when the caller may not destroy the package - on a read-only replica, every caller. This route answers the single read, which adds what only a single read publishes: the package's OTHER versions (`packageVersions()` minus this one, as GitLab's own example shows) and the pipeline that built this one. Both are ARRAYS against a document that types each a single object - the same declared deviation as `tags`. A package id this project has not got is GitLab's own 404, and so is `%ZZ`: the path param arrives already decoded from Hono and is never decoded again (PR #481's class). SERVED 2026-09-14 (hello-20 `packageRegistry`): gated on `reader.hasPackageRegistry()`, with the project or group resolving FIRST so an unknown one is GitLab's own 404 on every generation. An older artifact - the 127 pins frozen before hello-20, and live until the hello-20 cutover - answers `noPackageRegistry`, the named generation gap, rather than `[]`: an empty package list would claim an organization with three tagged releases, 344 build outputs and a root `Dockerfile` publishes nothing, which the same artifact contradicts

deviation
Long tail
GET
/api/v4/groups/{id}/-/debian_distributions/{codename}

refuse — This address names a Debian DISTRIBUTION by codename, and no `debian_distribution` exists in this universe: a distribution is created by a write and this replica has issued none, which is exactly why the two LIST routes answer `[]` rather than a 404. GitLab's own 404 for an object that cannot exist is the whole answer here (the group's).

deferred
Long tail
GET
/api/v4/groups/{id}/-/debian_distributions/{codename}/key.asc

refuse — A REPOSITORY SIGNING KEY IS A CREDENTIAL, and canon stores no private half of anything: `credential` rows carry public keys and token METADATA, never a secret, which is the rule the whole hello-16 credential surface was built on. Publishing an armoured key here would mean minting one in the renderer - a secret invented by the replica, the thing invariant #4 forbids most plainly. It is additionally unreachable: no `debian_distribution` exists for any codename to name, so GitLab's own 404 is the answer either way (the group's).

deferred
Long tail
GET
/api/v4/groups/{id}/-/packages/debian/pool/{distribution}/{project_id}/{letter}/{package_name}/{package_version}/{file_name}

refuse — This address names a FILE IN THE DEBIAN POOL - a `.deb`, `.dsc` or `.tar.xz` of a published source or binary package. `package.kind` carries no `debian` row on any artifact, and no `debian_distribution` exists to hold a pool, so both halves of the address name things that cannot exist; GitLab's own 404 is the whole answer, and serving an npm tarball under a `.deb` name would break every client that unpacks by extension (the group's pool, which addresses the project inside the path).

deferred
Long tail
GET
/api/v4/packages/conan/v1/conans/{package_name}/{package_version}/{package_username}/{package_channel}

refuse — This address names a Conan RECIPE, a binary package of one, or a file inside one, at the instance-wide v1 API, and `package.kind` is npm, golang, pypi or generic on every row of every artifact (the F1 decision's own enum ruling), so no Conan recipe of the form `name/version@user/channel` exists in this registry. GitLab's own 404 for an object that cannot exist is the whole answer; serving an npm tarball or a Go module through Conan's recipe API would hand `conan install` something that is not a recipe, which is invariant #4's invented value wearing a provider's clothes. The two Conan questions that ask nothing about a recipe - `conans/search` and `ping` - ARE served (hello-20 `packageRegistry`, 2026-09-14)

deferred
Long tail
GET
/api/v4/packages/conan/v1/conans/{package_name}/{package_version}/{package_username}/{package_channel}/digest

refuse — This address names a Conan RECIPE, a binary package of one, or a file inside one, at the instance-wide v1 API, and `package.kind` is npm, golang, pypi or generic on every row of every artifact (the F1 decision's own enum ruling), so no Conan recipe of the form `name/version@user/channel` exists in this registry. GitLab's own 404 for an object that cannot exist is the whole answer; serving an npm tarball or a Go module through Conan's recipe API would hand `conan install` something that is not a recipe, which is invariant #4's invented value wearing a provider's clothes. The two Conan questions that ask nothing about a recipe - `conans/search` and `ping` - ARE served (hello-20 `packageRegistry`, 2026-09-14)

deferred
Long tail
GET
/api/v4/packages/conan/v1/conans/{package_name}/{package_version}/{package_username}/{package_channel}/download_urls

refuse — This address names a Conan RECIPE, a binary package of one, or a file inside one, at the instance-wide v1 API, and `package.kind` is npm, golang, pypi or generic on every row of every artifact (the F1 decision's own enum ruling), so no Conan recipe of the form `name/version@user/channel` exists in this registry. GitLab's own 404 for an object that cannot exist is the whole answer; serving an npm tarball or a Go module through Conan's recipe API would hand `conan install` something that is not a recipe, which is invariant #4's invented value wearing a provider's clothes. The two Conan questions that ask nothing about a recipe - `conans/search` and `ping` - ARE served (hello-20 `packageRegistry`, 2026-09-14)

deferred
Long tail
GET
/api/v4/packages/conan/v1/conans/{package_name}/{package_version}/{package_username}/{package_channel}/packages/{conan_package_reference}

refuse — This address names a Conan RECIPE, a binary package of one, or a file inside one, at the instance-wide v1 API, and `package.kind` is npm, golang, pypi or generic on every row of every artifact (the F1 decision's own enum ruling), so no Conan recipe of the form `name/version@user/channel` exists in this registry. GitLab's own 404 for an object that cannot exist is the whole answer; serving an npm tarball or a Go module through Conan's recipe API would hand `conan install` something that is not a recipe, which is invariant #4's invented value wearing a provider's clothes. The two Conan questions that ask nothing about a recipe - `conans/search` and `ping` - ARE served (hello-20 `packageRegistry`, 2026-09-14)

deferred
Long tail
GET
/api/v4/packages/conan/v1/conans/{package_name}/{package_version}/{package_username}/{package_channel}/packages/{conan_package_reference}/digest

refuse — This address names a Conan RECIPE, a binary package of one, or a file inside one, at the instance-wide v1 API, and `package.kind` is npm, golang, pypi or generic on every row of every artifact (the F1 decision's own enum ruling), so no Conan recipe of the form `name/version@user/channel` exists in this registry. GitLab's own 404 for an object that cannot exist is the whole answer; serving an npm tarball or a Go module through Conan's recipe API would hand `conan install` something that is not a recipe, which is invariant #4's invented value wearing a provider's clothes. The two Conan questions that ask nothing about a recipe - `conans/search` and `ping` - ARE served (hello-20 `packageRegistry`, 2026-09-14)

deferred
Long tail
GET
/api/v4/packages/conan/v1/conans/{package_name}/{package_version}/{package_username}/{package_channel}/packages/{conan_package_reference}/download_urls

refuse — This address names a Conan RECIPE, a binary package of one, or a file inside one, at the instance-wide v1 API, and `package.kind` is npm, golang, pypi or generic on every row of every artifact (the F1 decision's own enum ruling), so no Conan recipe of the form `name/version@user/channel` exists in this registry. GitLab's own 404 for an object that cannot exist is the whole answer; serving an npm tarball or a Go module through Conan's recipe API would hand `conan install` something that is not a recipe, which is invariant #4's invented value wearing a provider's clothes. The two Conan questions that ask nothing about a recipe - `conans/search` and `ping` - ARE served (hello-20 `packageRegistry`, 2026-09-14)

deferred
Long tail
GET
/api/v4/packages/conan/v1/conans/{package_name}/{package_version}/{package_username}/{package_channel}/search

refuse — This address names a Conan RECIPE, a binary package of one, or a file inside one, at the instance-wide v1 API, and `package.kind` is npm, golang, pypi or generic on every row of every artifact (the F1 decision's own enum ruling), so no Conan recipe of the form `name/version@user/channel` exists in this registry. GitLab's own 404 for an object that cannot exist is the whole answer; serving an npm tarball or a Go module through Conan's recipe API would hand `conan install` something that is not a recipe, which is invariant #4's invented value wearing a provider's clothes. The two Conan questions that ask nothing about a recipe - `conans/search` and `ping` - ARE served (hello-20 `packageRegistry`, 2026-09-14)

deferred
Long tail
GET
/api/v4/packages/conan/v1/files/{package_name}/{package_version}/{package_username}/{package_channel}/{recipe_revision}/export/{file_name}

refuse — This address names a Conan RECIPE, a binary package of one, or a file inside one, at the instance-wide v1 API, and `package.kind` is npm, golang, pypi or generic on every row of every artifact (the F1 decision's own enum ruling), so no Conan recipe of the form `name/version@user/channel` exists in this registry. GitLab's own 404 for an object that cannot exist is the whole answer; serving an npm tarball or a Go module through Conan's recipe API would hand `conan install` something that is not a recipe, which is invariant #4's invented value wearing a provider's clothes. The two Conan questions that ask nothing about a recipe - `conans/search` and `ping` - ARE served (hello-20 `packageRegistry`, 2026-09-14)

deferred
Long tail
GET
/api/v4/packages/conan/v1/files/{package_name}/{package_version}/{package_username}/{package_channel}/{recipe_revision}/package/{conan_package_reference}/{package_revision}/{file_name}

refuse — This address names a Conan RECIPE, a binary package of one, or a file inside one, at the instance-wide v1 API, and `package.kind` is npm, golang, pypi or generic on every row of every artifact (the F1 decision's own enum ruling), so no Conan recipe of the form `name/version@user/channel` exists in this registry. GitLab's own 404 for an object that cannot exist is the whole answer; serving an npm tarball or a Go module through Conan's recipe API would hand `conan install` something that is not a recipe, which is invariant #4's invented value wearing a provider's clothes. The two Conan questions that ask nothing about a recipe - `conans/search` and `ping` - ARE served (hello-20 `packageRegistry`, 2026-09-14)

deferred
Long tail
GET
/api/v4/packages/conan/v1/users/authenticate

refuse — This address is a Conan AUTHENTICATION PROBE - `users/authenticate` answers a JWT, `check_credentials` validates one - and it is refused on two independent legs. FIRST, the condition: this project's Conan registry holds nothing to authenticate against, because `package.kind` is npm, golang, pypi or generic on every row of every artifact, and 404 is one of the THREE responses the vendored operation itself declares (200 / 401 / 404) - REFUSE_SHAPES.md paragraph 2 picks between an operation's declared errors by the condition the reason describes, and 401 is GitLab's answer for a caller whose credential is missing or invalid, which would be a false claim about the CALLER on a host whose gateway authenticated them. SECOND, THIS UNIVERSE ISSUES NO TOKEN: there is no session and no secret to sign with, so a JWT minted here would be a credential invented by the replica (the PagerDuty routing-key rule, 2026-09-03). Keeping the whole Conan block on one status is what makes it reviewable, and it widens no type union (ruled by the wave coordinator, 2026-09-14)

deferred
Long tail
GET
/api/v4/packages/conan/v1/users/check_credentials

refuse — This address is a Conan AUTHENTICATION PROBE - `users/authenticate` answers a JWT, `check_credentials` validates one - and it is refused on two independent legs. FIRST, the condition: this project's Conan registry holds nothing to authenticate against, because `package.kind` is npm, golang, pypi or generic on every row of every artifact, and 404 is one of the THREE responses the vendored operation itself declares (200 / 401 / 404) - REFUSE_SHAPES.md paragraph 2 picks between an operation's declared errors by the condition the reason describes, and 401 is GitLab's answer for a caller whose credential is missing or invalid, which would be a false claim about the CALLER on a host whose gateway authenticated them. SECOND, THIS UNIVERSE ISSUES NO TOKEN: there is no session and no secret to sign with, so a JWT minted here would be a credential invented by the replica (the PagerDuty routing-key rule, 2026-09-03). Keeping the whole Conan block on one status is what makes it reviewable, and it widens no type union (ruled by the wave coordinator, 2026-09-14)

deferred
Long tail
GET
/api/v4/projects/{id}/debian_distributions/{codename}

refuse — This address names a Debian DISTRIBUTION by codename, and no `debian_distribution` exists in this universe: a distribution is created by a write and this replica has issued none, which is exactly why the two LIST routes answer `[]` rather than a 404. GitLab's own 404 for an object that cannot exist is the whole answer here (the project's).

deferred
Long tail
GET
/api/v4/projects/{id}/debian_distributions/{codename}/key.asc

refuse — A REPOSITORY SIGNING KEY IS A CREDENTIAL, and canon stores no private half of anything: `credential` rows carry public keys and token METADATA, never a secret, which is the rule the whole hello-16 credential surface was built on. Publishing an armoured key here would mean minting one in the renderer - a secret invented by the replica, the thing invariant #4 forbids most plainly. It is additionally unreachable: no `debian_distribution` exists for any codename to name, so GitLab's own 404 is the answer either way (the project's).

deferred
Long tail
GET
/api/v4/projects/{id}/packages/cargo/{package_name}/{package_version}/download

refuse — A crate's `.crate` archive. `package.kind` carries no `cargo` row on any artifact, so this project publishes no crate and this address names one that cannot exist; GitLab's own 404 is the whole answer. The registry CONFIGURATION a `cargo` client reads first - `cargo/config.json` - IS served (hello-20 `packageRegistry`, 2026-09-14), because it describes the registry rather than its crates

deferred
Long tail
GET
/api/v4/projects/{id}/packages/cargo/{prefix_1}/{prefix_2}/{package_name}

refuse — The four-and-longer sparse-index shard for a crate. `package.kind` carries no `cargo` row on any artifact, so this project publishes no crate and this address names one that cannot exist; GitLab's own 404 is the whole answer. The registry CONFIGURATION a `cargo` client reads first - `cargo/config.json` - IS served (hello-20 `packageRegistry`, 2026-09-14), because it describes the registry rather than its crates

deferred
Long tail
GET
/api/v4/projects/{id}/packages/cargo/1/{package_name}

refuse — The single-character sparse-index shard for a crate. `package.kind` carries no `cargo` row on any artifact, so this project publishes no crate and this address names one that cannot exist; GitLab's own 404 is the whole answer. The registry CONFIGURATION a `cargo` client reads first - `cargo/config.json` - IS served (hello-20 `packageRegistry`, 2026-09-14), because it describes the registry rather than its crates

deferred
Long tail
GET
/api/v4/projects/{id}/packages/cargo/2/{package_name}

refuse — The two-character sparse-index shard for a crate. `package.kind` carries no `cargo` row on any artifact, so this project publishes no crate and this address names one that cannot exist; GitLab's own 404 is the whole answer. The registry CONFIGURATION a `cargo` client reads first - `cargo/config.json` - IS served (hello-20 `packageRegistry`, 2026-09-14), because it describes the registry rather than its crates

deferred
Long tail
GET
/api/v4/projects/{id}/packages/cargo/3/{first_char}/{package_name}

refuse — The three-character sparse-index shard for a crate. `package.kind` carries no `cargo` row on any artifact, so this project publishes no crate and this address names one that cannot exist; GitLab's own 404 is the whole answer. The registry CONFIGURATION a `cargo` client reads first - `cargo/config.json` - IS served (hello-20 `packageRegistry`, 2026-09-14), because it describes the registry rather than its crates

deferred
Long tail
GET
/api/v4/projects/{id}/packages/conan/v1/conans/{package_name}/{package_version}/{package_username}/{package_channel}

refuse — This address names a Conan RECIPE, a binary package of one, or a file inside one, inside one project's v1 API, and `package.kind` is npm, golang, pypi or generic on every row of every artifact (the F1 decision's own enum ruling), so no Conan recipe of the form `name/version@user/channel` exists in this registry. GitLab's own 404 for an object that cannot exist is the whole answer; serving an npm tarball or a Go module through Conan's recipe API would hand `conan install` something that is not a recipe, which is invariant #4's invented value wearing a provider's clothes. The two Conan questions that ask nothing about a recipe - `conans/search` and `ping` - ARE served (hello-20 `packageRegistry`, 2026-09-14)

deferred
Long tail
GET
/api/v4/projects/{id}/packages/conan/v1/conans/{package_name}/{package_version}/{package_username}/{package_channel}/digest

refuse — This address names a Conan RECIPE, a binary package of one, or a file inside one, inside one project's v1 API, and `package.kind` is npm, golang, pypi or generic on every row of every artifact (the F1 decision's own enum ruling), so no Conan recipe of the form `name/version@user/channel` exists in this registry. GitLab's own 404 for an object that cannot exist is the whole answer; serving an npm tarball or a Go module through Conan's recipe API would hand `conan install` something that is not a recipe, which is invariant #4's invented value wearing a provider's clothes. The two Conan questions that ask nothing about a recipe - `conans/search` and `ping` - ARE served (hello-20 `packageRegistry`, 2026-09-14)

deferred
Long tail
GET
/api/v4/projects/{id}/packages/conan/v1/conans/{package_name}/{package_version}/{package_username}/{package_channel}/download_urls

refuse — This address names a Conan RECIPE, a binary package of one, or a file inside one, inside one project's v1 API, and `package.kind` is npm, golang, pypi or generic on every row of every artifact (the F1 decision's own enum ruling), so no Conan recipe of the form `name/version@user/channel` exists in this registry. GitLab's own 404 for an object that cannot exist is the whole answer; serving an npm tarball or a Go module through Conan's recipe API would hand `conan install` something that is not a recipe, which is invariant #4's invented value wearing a provider's clothes. The two Conan questions that ask nothing about a recipe - `conans/search` and `ping` - ARE served (hello-20 `packageRegistry`, 2026-09-14)

deferred
Long tail
GET
/api/v4/projects/{id}/packages/conan/v1/conans/{package_name}/{package_version}/{package_username}/{package_channel}/packages/{conan_package_reference}

refuse — This address names a Conan RECIPE, a binary package of one, or a file inside one, inside one project's v1 API, and `package.kind` is npm, golang, pypi or generic on every row of every artifact (the F1 decision's own enum ruling), so no Conan recipe of the form `name/version@user/channel` exists in this registry. GitLab's own 404 for an object that cannot exist is the whole answer; serving an npm tarball or a Go module through Conan's recipe API would hand `conan install` something that is not a recipe, which is invariant #4's invented value wearing a provider's clothes. The two Conan questions that ask nothing about a recipe - `conans/search` and `ping` - ARE served (hello-20 `packageRegistry`, 2026-09-14)

deferred
Long tail
GET
/api/v4/projects/{id}/packages/conan/v1/conans/{package_name}/{package_version}/{package_username}/{package_channel}/packages/{conan_package_reference}/digest

refuse — This address names a Conan RECIPE, a binary package of one, or a file inside one, inside one project's v1 API, and `package.kind` is npm, golang, pypi or generic on every row of every artifact (the F1 decision's own enum ruling), so no Conan recipe of the form `name/version@user/channel` exists in this registry. GitLab's own 404 for an object that cannot exist is the whole answer; serving an npm tarball or a Go module through Conan's recipe API would hand `conan install` something that is not a recipe, which is invariant #4's invented value wearing a provider's clothes. The two Conan questions that ask nothing about a recipe - `conans/search` and `ping` - ARE served (hello-20 `packageRegistry`, 2026-09-14)

deferred
Long tail
GET
/api/v4/projects/{id}/packages/conan/v1/conans/{package_name}/{package_version}/{package_username}/{package_channel}/packages/{conan_package_reference}/download_urls

refuse — This address names a Conan RECIPE, a binary package of one, or a file inside one, inside one project's v1 API, and `package.kind` is npm, golang, pypi or generic on every row of every artifact (the F1 decision's own enum ruling), so no Conan recipe of the form `name/version@user/channel` exists in this registry. GitLab's own 404 for an object that cannot exist is the whole answer; serving an npm tarball or a Go module through Conan's recipe API would hand `conan install` something that is not a recipe, which is invariant #4's invented value wearing a provider's clothes. The two Conan questions that ask nothing about a recipe - `conans/search` and `ping` - ARE served (hello-20 `packageRegistry`, 2026-09-14)

deferred
Long tail
GET
/api/v4/projects/{id}/packages/conan/v1/conans/{package_name}/{package_version}/{package_username}/{package_channel}/search

refuse — This address names a Conan RECIPE, a binary package of one, or a file inside one, inside one project's v1 API, and `package.kind` is npm, golang, pypi or generic on every row of every artifact (the F1 decision's own enum ruling), so no Conan recipe of the form `name/version@user/channel` exists in this registry. GitLab's own 404 for an object that cannot exist is the whole answer; serving an npm tarball or a Go module through Conan's recipe API would hand `conan install` something that is not a recipe, which is invariant #4's invented value wearing a provider's clothes. The two Conan questions that ask nothing about a recipe - `conans/search` and `ping` - ARE served (hello-20 `packageRegistry`, 2026-09-14)

deferred
Long tail
GET
/api/v4/projects/{id}/packages/conan/v1/files/{package_name}/{package_version}/{package_username}/{package_channel}/{recipe_revision}/export/{file_name}

refuse — This address names a Conan RECIPE, a binary package of one, or a file inside one, inside one project's v1 API, and `package.kind` is npm, golang, pypi or generic on every row of every artifact (the F1 decision's own enum ruling), so no Conan recipe of the form `name/version@user/channel` exists in this registry. GitLab's own 404 for an object that cannot exist is the whole answer; serving an npm tarball or a Go module through Conan's recipe API would hand `conan install` something that is not a recipe, which is invariant #4's invented value wearing a provider's clothes. The two Conan questions that ask nothing about a recipe - `conans/search` and `ping` - ARE served (hello-20 `packageRegistry`, 2026-09-14)

deferred
Long tail
GET
/api/v4/projects/{id}/packages/conan/v1/files/{package_name}/{package_version}/{package_username}/{package_channel}/{recipe_revision}/package/{conan_package_reference}/{package_revision}/{file_name}

refuse — This address names a Conan RECIPE, a binary package of one, or a file inside one, inside one project's v1 API, and `package.kind` is npm, golang, pypi or generic on every row of every artifact (the F1 decision's own enum ruling), so no Conan recipe of the form `name/version@user/channel` exists in this registry. GitLab's own 404 for an object that cannot exist is the whole answer; serving an npm tarball or a Go module through Conan's recipe API would hand `conan install` something that is not a recipe, which is invariant #4's invented value wearing a provider's clothes. The two Conan questions that ask nothing about a recipe - `conans/search` and `ping` - ARE served (hello-20 `packageRegistry`, 2026-09-14)

deferred
Long tail
GET
/api/v4/projects/{id}/packages/conan/v1/users/authenticate

refuse — This address is a Conan AUTHENTICATION PROBE - `users/authenticate` answers a JWT, `check_credentials` validates one - and it is refused on two independent legs. FIRST, the condition: this project's Conan registry holds nothing to authenticate against, because `package.kind` is npm, golang, pypi or generic on every row of every artifact, and 404 is one of the THREE responses the vendored operation itself declares (200 / 401 / 404) - REFUSE_SHAPES.md paragraph 2 picks between an operation's declared errors by the condition the reason describes, and 401 is GitLab's answer for a caller whose credential is missing or invalid, which would be a false claim about the CALLER on a host whose gateway authenticated them. SECOND, THIS UNIVERSE ISSUES NO TOKEN: there is no session and no secret to sign with, so a JWT minted here would be a credential invented by the replica (the PagerDuty routing-key rule, 2026-09-03). Keeping the whole Conan block on one status is what makes it reviewable, and it widens no type union (ruled by the wave coordinator, 2026-09-14)

deferred
Long tail
GET
/api/v4/projects/{id}/packages/conan/v1/users/check_credentials

refuse — This address is a Conan AUTHENTICATION PROBE - `users/authenticate` answers a JWT, `check_credentials` validates one - and it is refused on two independent legs. FIRST, the condition: this project's Conan registry holds nothing to authenticate against, because `package.kind` is npm, golang, pypi or generic on every row of every artifact, and 404 is one of the THREE responses the vendored operation itself declares (200 / 401 / 404) - REFUSE_SHAPES.md paragraph 2 picks between an operation's declared errors by the condition the reason describes, and 401 is GitLab's answer for a caller whose credential is missing or invalid, which would be a false claim about the CALLER on a host whose gateway authenticated them. SECOND, THIS UNIVERSE ISSUES NO TOKEN: there is no session and no secret to sign with, so a JWT minted here would be a credential invented by the replica (the PagerDuty routing-key rule, 2026-09-03). Keeping the whole Conan block on one status is what makes it reviewable, and it widens no type union (ruled by the wave coordinator, 2026-09-14)

deferred
Long tail
GET
/api/v4/projects/{id}/packages/conan/v2/conans/{package_name}/{package_version}/{package_username}/{package_channel}/latest

refuse — This address names a Conan RECIPE REVISION, a package revision of one, or a file or search inside one, at Conan 2.x's v2 API, and `package.kind` is npm, golang, pypi or generic on every row of every artifact (the F1 decision's own enum ruling), so no Conan recipe of the form `name/version@user/channel` exists in this registry. GitLab's own 404 for an object that cannot exist is the whole answer; serving an npm tarball or a Go module through Conan's recipe API would hand `conan install` something that is not a recipe, which is invariant #4's invented value wearing a provider's clothes. The two Conan questions that ask nothing about a recipe - `conans/search` and `ping` - ARE served (hello-20 `packageRegistry`, 2026-09-14)

deferred
Long tail
GET
/api/v4/projects/{id}/packages/conan/v2/conans/{package_name}/{package_version}/{package_username}/{package_channel}/revisions

refuse — This address names a Conan RECIPE REVISION, a package revision of one, or a file or search inside one, at Conan 2.x's v2 API, and `package.kind` is npm, golang, pypi or generic on every row of every artifact (the F1 decision's own enum ruling), so no Conan recipe of the form `name/version@user/channel` exists in this registry. GitLab's own 404 for an object that cannot exist is the whole answer; serving an npm tarball or a Go module through Conan's recipe API would hand `conan install` something that is not a recipe, which is invariant #4's invented value wearing a provider's clothes. The two Conan questions that ask nothing about a recipe - `conans/search` and `ping` - ARE served (hello-20 `packageRegistry`, 2026-09-14)

deferred
Long tail
GET
/api/v4/projects/{id}/packages/conan/v2/conans/{package_name}/{package_version}/{package_username}/{package_channel}/revisions/{recipe_revision}/files

refuse — This address names a Conan RECIPE REVISION, a package revision of one, or a file or search inside one, at Conan 2.x's v2 API, and `package.kind` is npm, golang, pypi or generic on every row of every artifact (the F1 decision's own enum ruling), so no Conan recipe of the form `name/version@user/channel` exists in this registry. GitLab's own 404 for an object that cannot exist is the whole answer; serving an npm tarball or a Go module through Conan's recipe API would hand `conan install` something that is not a recipe, which is invariant #4's invented value wearing a provider's clothes. The two Conan questions that ask nothing about a recipe - `conans/search` and `ping` - ARE served (hello-20 `packageRegistry`, 2026-09-14)

deferred
Long tail
GET
/api/v4/projects/{id}/packages/conan/v2/conans/{package_name}/{package_version}/{package_username}/{package_channel}/revisions/{recipe_revision}/files/{file_name}

refuse — This address names a Conan RECIPE REVISION, a package revision of one, or a file or search inside one, at Conan 2.x's v2 API, and `package.kind` is npm, golang, pypi or generic on every row of every artifact (the F1 decision's own enum ruling), so no Conan recipe of the form `name/version@user/channel` exists in this registry. GitLab's own 404 for an object that cannot exist is the whole answer; serving an npm tarball or a Go module through Conan's recipe API would hand `conan install` something that is not a recipe, which is invariant #4's invented value wearing a provider's clothes. The two Conan questions that ask nothing about a recipe - `conans/search` and `ping` - ARE served (hello-20 `packageRegistry`, 2026-09-14)

deferred
Long tail
GET
/api/v4/projects/{id}/packages/conan/v2/conans/{package_name}/{package_version}/{package_username}/{package_channel}/revisions/{recipe_revision}/packages/{conan_package_reference}/latest

refuse — This address names a Conan RECIPE REVISION, a package revision of one, or a file or search inside one, at Conan 2.x's v2 API, and `package.kind` is npm, golang, pypi or generic on every row of every artifact (the F1 decision's own enum ruling), so no Conan recipe of the form `name/version@user/channel` exists in this registry. GitLab's own 404 for an object that cannot exist is the whole answer; serving an npm tarball or a Go module through Conan's recipe API would hand `conan install` something that is not a recipe, which is invariant #4's invented value wearing a provider's clothes. The two Conan questions that ask nothing about a recipe - `conans/search` and `ping` - ARE served (hello-20 `packageRegistry`, 2026-09-14)

deferred
Long tail
GET
/api/v4/projects/{id}/packages/conan/v2/conans/{package_name}/{package_version}/{package_username}/{package_channel}/revisions/{recipe_revision}/packages/{conan_package_reference}/revisions

refuse — This address names a Conan RECIPE REVISION, a package revision of one, or a file or search inside one, at Conan 2.x's v2 API, and `package.kind` is npm, golang, pypi or generic on every row of every artifact (the F1 decision's own enum ruling), so no Conan recipe of the form `name/version@user/channel` exists in this registry. GitLab's own 404 for an object that cannot exist is the whole answer; serving an npm tarball or a Go module through Conan's recipe API would hand `conan install` something that is not a recipe, which is invariant #4's invented value wearing a provider's clothes. The two Conan questions that ask nothing about a recipe - `conans/search` and `ping` - ARE served (hello-20 `packageRegistry`, 2026-09-14)

deferred
Long tail
GET
/api/v4/projects/{id}/packages/conan/v2/conans/{package_name}/{package_version}/{package_username}/{package_channel}/revisions/{recipe_revision}/packages/{conan_package_reference}/revisions/{package_revision}/files

refuse — This address names a Conan RECIPE REVISION, a package revision of one, or a file or search inside one, at Conan 2.x's v2 API, and `package.kind` is npm, golang, pypi or generic on every row of every artifact (the F1 decision's own enum ruling), so no Conan recipe of the form `name/version@user/channel` exists in this registry. GitLab's own 404 for an object that cannot exist is the whole answer; serving an npm tarball or a Go module through Conan's recipe API would hand `conan install` something that is not a recipe, which is invariant #4's invented value wearing a provider's clothes. The two Conan questions that ask nothing about a recipe - `conans/search` and `ping` - ARE served (hello-20 `packageRegistry`, 2026-09-14)

deferred
Long tail
GET
/api/v4/projects/{id}/packages/conan/v2/conans/{package_name}/{package_version}/{package_username}/{package_channel}/revisions/{recipe_revision}/packages/{conan_package_reference}/revisions/{package_revision}/files/{file_name}

refuse — This address names a Conan RECIPE REVISION, a package revision of one, or a file or search inside one, at Conan 2.x's v2 API, and `package.kind` is npm, golang, pypi or generic on every row of every artifact (the F1 decision's own enum ruling), so no Conan recipe of the form `name/version@user/channel` exists in this registry. GitLab's own 404 for an object that cannot exist is the whole answer; serving an npm tarball or a Go module through Conan's recipe API would hand `conan install` something that is not a recipe, which is invariant #4's invented value wearing a provider's clothes. The two Conan questions that ask nothing about a recipe - `conans/search` and `ping` - ARE served (hello-20 `packageRegistry`, 2026-09-14)

deferred
Long tail
GET
/api/v4/projects/{id}/packages/conan/v2/conans/{package_name}/{package_version}/{package_username}/{package_channel}/revisions/{recipe_revision}/search

refuse — This address names a Conan RECIPE REVISION, a package revision of one, or a file or search inside one, at Conan 2.x's v2 API, and `package.kind` is npm, golang, pypi or generic on every row of every artifact (the F1 decision's own enum ruling), so no Conan recipe of the form `name/version@user/channel` exists in this registry. GitLab's own 404 for an object that cannot exist is the whole answer; serving an npm tarball or a Go module through Conan's recipe API would hand `conan install` something that is not a recipe, which is invariant #4's invented value wearing a provider's clothes. The two Conan questions that ask nothing about a recipe - `conans/search` and `ping` - ARE served (hello-20 `packageRegistry`, 2026-09-14)

deferred
Long tail
GET
/api/v4/projects/{id}/packages/conan/v2/conans/{package_name}/{package_version}/{package_username}/{package_channel}/search

refuse — This address names a Conan RECIPE REVISION, a package revision of one, or a file or search inside one, at Conan 2.x's v2 API, and `package.kind` is npm, golang, pypi or generic on every row of every artifact (the F1 decision's own enum ruling), so no Conan recipe of the form `name/version@user/channel` exists in this registry. GitLab's own 404 for an object that cannot exist is the whole answer; serving an npm tarball or a Go module through Conan's recipe API would hand `conan install` something that is not a recipe, which is invariant #4's invented value wearing a provider's clothes. The two Conan questions that ask nothing about a recipe - `conans/search` and `ping` - ARE served (hello-20 `packageRegistry`, 2026-09-14)

deferred
Long tail
GET
/api/v4/projects/{id}/packages/conan/v2/users/authenticate

refuse — This address is a Conan AUTHENTICATION PROBE - `users/authenticate` answers a JWT, `check_credentials` validates one - and it is refused on two independent legs. FIRST, the condition: this project's Conan registry holds nothing to authenticate against, because `package.kind` is npm, golang, pypi or generic on every row of every artifact, and 404 is one of the THREE responses the vendored operation itself declares (200 / 401 / 404) - REFUSE_SHAPES.md paragraph 2 picks between an operation's declared errors by the condition the reason describes, and 401 is GitLab's answer for a caller whose credential is missing or invalid, which would be a false claim about the CALLER on a host whose gateway authenticated them. SECOND, THIS UNIVERSE ISSUES NO TOKEN: there is no session and no secret to sign with, so a JWT minted here would be a credential invented by the replica (the PagerDuty routing-key rule, 2026-09-03). Keeping the whole Conan block on one status is what makes it reviewable, and it widens no type union (ruled by the wave coordinator, 2026-09-14)

deferred
Long tail
GET
/api/v4/projects/{id}/packages/conan/v2/users/check_credentials

refuse — This address is a Conan AUTHENTICATION PROBE - `users/authenticate` answers a JWT, `check_credentials` validates one - and it is refused on two independent legs. FIRST, the condition: this project's Conan registry holds nothing to authenticate against, because `package.kind` is npm, golang, pypi or generic on every row of every artifact, and 404 is one of the THREE responses the vendored operation itself declares (200 / 401 / 404) - REFUSE_SHAPES.md paragraph 2 picks between an operation's declared errors by the condition the reason describes, and 401 is GitLab's answer for a caller whose credential is missing or invalid, which would be a false claim about the CALLER on a host whose gateway authenticated them. SECOND, THIS UNIVERSE ISSUES NO TOKEN: there is no session and no secret to sign with, so a JWT minted here would be a credential invented by the replica (the PagerDuty routing-key rule, 2026-09-03). Keeping the whole Conan block on one status is what makes it reviewable, and it widens no type union (ruled by the wave coordinator, 2026-09-14)

deferred
Long tail
GET
/api/v4/projects/{id}/packages/debian/pool/{distribution}/{letter}/{package_name}/{package_version}/{file_name}

refuse — This address names a FILE IN THE DEBIAN POOL - a `.deb`, `.dsc` or `.tar.xz` of a published source or binary package. `package.kind` carries no `debian` row on any artifact, and no `debian_distribution` exists to hold a pool, so both halves of the address name things that cannot exist; GitLab's own 404 is the whole answer, and serving an npm tarball under a `.deb` name would break every client that unpacks by extension (the project's pool).

deferred
Long tail
GET
/api/v4/projects/{id}/packages/helm/{channel}/charts/{file_name}.tgz

refuse — This address names a HELM CHART ARCHIVE. `package.kind` carries no `helm` row on any artifact, so no chart exists in any channel and GitLab's own 404 is the whole answer. The channel INDEX - `helm/{channel}/index.yaml` - IS served (hello-20 `packageRegistry`, 2026-09-14) and truthfully reports `entries: {}`, which is the same fact from the other end.

deferred
Long tail
GET
/api/v4/projects/{id}/packages/rubygems/{file_name}

refuse — This address names a `.gem` FILE or its Marshal'd spec. `package.kind` carries no `rubygems` row on any artifact, so no gem exists here and GitLab's own 404 is the whole answer. The DEPENDENCY API - `rubygems/api/v1/dependencies` - IS served (hello-20 `packageRegistry`, 2026-09-14) and answers `Marshal.dump([])`, which is the same fact in RubyGems' own protocol (the flat address).

deferred
Long tail
GET
/api/v4/projects/{id}/packages/rubygems/gems/{file_name}

refuse — This address names a `.gem` FILE or its Marshal'd spec. `package.kind` carries no `rubygems` row on any artifact, so no gem exists here and GitLab's own 404 is the whole answer. The DEPENDENCY API - `rubygems/api/v1/dependencies` - IS served (hello-20 `packageRegistry`, 2026-09-14) and answers `Marshal.dump([])`, which is the same fact in RubyGems' own protocol (the `gems/` address a `gem fetch` uses).

deferred
Long tail
GET
/api/v4/projects/{id}/packages/rubygems/quick/Marshal.4.8/{file_name}

refuse — This address names a `.gem` FILE or its Marshal'd spec. `package.kind` carries no `rubygems` row on any artifact, so no gem exists here and GitLab's own 404 is the whole answer. The DEPENDENCY API - `rubygems/api/v1/dependencies` - IS served (hello-20 `packageRegistry`, 2026-09-14) and answers `Marshal.dump([])`, which is the same fact in RubyGems' own protocol (the quick-spec address).

deferred
Long tail
instance-admin3 of 46 served & verified
GET
/api/v4/broadcast_messages

no broadcast message has ever been posted on this instance: a broadcast exists only because an admin created one, and this universe has no admin acting on it

served & verified
Long tail
GET
/api/v4/metadata

SERVED 2026-09-12 (coverage wave W8): `/api/v4/version` plus the two facts GitLab's Metadata object adds, and all of them are facts about THIS REPLICA rather than about olympus-labs — so the row needs no artifact read and no generation gate. `version` and `revision` come from GITLAB_METADATA in packages/renderer-gitlab/src/ids.ts, the same constant `/api/v4/version` reads, and conformance asserts the two endpoints agree. `kas.enabled` is FALSE because this host runs no Kubernetes agent server — `/projects/{id}/cluster_agents` is classified `generate` for exactly that reason — and the three fields GitLab renders off that server's own configuration are omitted rather than nulled. `enterprise` is TRUE and it is checkable rather than decorative: this host serves `/groups/{id}/epics`, `/groups/{id}/iterations` and `/projects/{id}/approval_rules`, every one of them EE-only, and conformance drives one of them to prove it

served & verified
Long tail
GET
/api/v4/version
served & verified
Long tail
GET
/api/v4/admin/batched_background_migrations

refuse — GitLab answers 403 to a non-admin token here (Geo, Sidekiq, storage moves, LDAP, application settings, feature toggles, background migrations); a tenant-shaped mirror has no instance to administer

deferred
Long tail
GET
/api/v4/admin/batched_background_migrations/{id}

refuse — GitLab answers 403 to a non-admin token here (Geo, Sidekiq, storage moves, LDAP, application settings, feature toggles, background migrations); a tenant-shaped mirror has no instance to administer

deferred
Long tail
GET
/api/v4/admin/batched_background_operations

refuse — GitLab answers 403 to a non-admin token here (Geo, Sidekiq, storage moves, LDAP, application settings, feature toggles, background migrations); a tenant-shaped mirror has no instance to administer

deferred
Long tail
GET
/api/v4/admin/batched_background_operations/{id}

refuse — GitLab answers 403 to a non-admin token here (Geo, Sidekiq, storage moves, LDAP, application settings, feature toggles, background migrations); a tenant-shaped mirror has no instance to administer

deferred
Long tail
GET
/api/v4/admin/databases/{database_name}/dictionary/tables/{table_name}

refuse — GitLab answers 403 to a non-admin token here (Geo, Sidekiq, storage moves, LDAP, application settings, feature toggles, background migrations); a tenant-shaped mirror has no instance to administer

deferred
Long tail
GET
/api/v4/application/appearance

refuse — GitLab answers 403 to a non-admin token here (Geo, Sidekiq, storage moves, LDAP, application settings, feature toggles, background migrations); a tenant-shaped mirror has no instance to administer

deferred
Long tail
GET
/api/v4/application/plan_limits

refuse — GitLab answers 403 to a non-admin token here (Geo, Sidekiq, storage moves, LDAP, application settings, feature toggles, background migrations); a tenant-shaped mirror has no instance to administer

deferred
Long tail
GET
/api/v4/application/settings

refuse — GitLab answers 403 to a non-admin token here (Geo, Sidekiq, storage moves, LDAP, application settings, feature toggles, background migrations); a tenant-shaped mirror has no instance to administer

deferred
Long tail
GET
/api/v4/application/statistics

refuse — GitLab answers 403 to a non-admin token here (Geo, Sidekiq, storage moves, LDAP, application settings, feature toggles, background migrations); a tenant-shaped mirror has no instance to administer

deferred
Long tail
GET
/api/v4/broadcast_messages/{id}

refuse — the collection this id would index is empty in this universe, so the only honest answer is the vendor's own 404

deferred
Long tail
GET
/api/v4/databases/{database_name}/dictionary/tables

refuse — GitLab answers 403 to a non-admin token here (Geo, Sidekiq, storage moves, LDAP, application settings, feature toggles, background migrations); a tenant-shaped mirror has no instance to administer

deferred
Long tail
GET
/api/v4/features

refuse — GitLab answers 403 to a non-admin token here (Geo, Sidekiq, storage moves, LDAP, application settings, feature toggles, background migrations); a tenant-shaped mirror has no instance to administer

deferred
Long tail
GET
/api/v4/features/definitions

refuse — GitLab answers 403 to a non-admin token here (Geo, Sidekiq, storage moves, LDAP, application settings, feature toggles, background migrations); a tenant-shaped mirror has no instance to administer

deferred
Long tail
GET
/api/v4/geo_nodes

refuse — GitLab answers 403 to a non-admin token here (Geo, Sidekiq, storage moves, LDAP, application settings, feature toggles, background migrations); a tenant-shaped mirror has no instance to administer

deferred
Long tail
GET
/api/v4/geo_nodes/{id}

refuse — GitLab answers 403 to a non-admin token here (Geo, Sidekiq, storage moves, LDAP, application settings, feature toggles, background migrations); a tenant-shaped mirror has no instance to administer

deferred
Long tail
GET
/api/v4/geo_nodes/{id}/status

refuse — GitLab answers 403 to a non-admin token here (Geo, Sidekiq, storage moves, LDAP, application settings, feature toggles, background migrations); a tenant-shaped mirror has no instance to administer

deferred
Long tail
GET
/api/v4/geo_nodes/status

refuse — GitLab answers 403 to a non-admin token here (Geo, Sidekiq, storage moves, LDAP, application settings, feature toggles, background migrations); a tenant-shaped mirror has no instance to administer

deferred
Long tail
GET
/api/v4/geo_sites

refuse — GitLab answers 403 to a non-admin token here (Geo, Sidekiq, storage moves, LDAP, application settings, feature toggles, background migrations); a tenant-shaped mirror has no instance to administer

deferred
Long tail
GET
/api/v4/geo_sites/{id}

refuse — GitLab answers 403 to a non-admin token here (Geo, Sidekiq, storage moves, LDAP, application settings, feature toggles, background migrations); a tenant-shaped mirror has no instance to administer

deferred
Long tail
GET
/api/v4/geo_sites/{id}/status

refuse — GitLab answers 403 to a non-admin token here (Geo, Sidekiq, storage moves, LDAP, application settings, feature toggles, background migrations); a tenant-shaped mirror has no instance to administer

deferred
Long tail
GET
/api/v4/geo_sites/status

refuse — GitLab answers 403 to a non-admin token here (Geo, Sidekiq, storage moves, LDAP, application settings, feature toggles, background migrations); a tenant-shaped mirror has no instance to administer

deferred
Long tail
GET
/api/v4/geo/proxy

refuse — GitLab answers 403 to a non-admin token here (Geo, Sidekiq, storage moves, LDAP, application settings, feature toggles, background migrations); a tenant-shaped mirror has no instance to administer

deferred
Long tail
GET
/api/v4/geo/repositories/{gl_repository}/pipeline_refs

refuse — GitLab answers 403 to a non-admin token here (Geo, Sidekiq, storage moves, LDAP, application settings, feature toggles, background migrations); a tenant-shaped mirror has no instance to administer

deferred
Long tail
GET
/api/v4/geo/retrieve/{replicable_name}/{replicable_id}

refuse — GitLab answers 403 to a non-admin token here (Geo, Sidekiq, storage moves, LDAP, application settings, feature toggles, background migrations); a tenant-shaped mirror has no instance to administer

deferred
Long tail
GET
/api/v4/group_repository_storage_moves

refuse — GitLab answers 403 to a non-admin token here (Geo, Sidekiq, storage moves, LDAP, application settings, feature toggles, background migrations); a tenant-shaped mirror has no instance to administer

deferred
Long tail
GET
/api/v4/group_repository_storage_moves/{repository_storage_move_id}

refuse — GitLab answers 403 to a non-admin token here (Geo, Sidekiq, storage moves, LDAP, application settings, feature toggles, background migrations); a tenant-shaped mirror has no instance to administer

deferred
Long tail
GET
/api/v4/groups/{id}/ldap_group_links

refuse — GitLab answers 403 to a non-admin token here (Geo, Sidekiq, storage moves, LDAP, application settings, feature toggles, background migrations); a tenant-shaped mirror has no instance to administer

deferred
Long tail
GET
/api/v4/groups/{id}/repository_storage_moves

refuse — GitLab answers 403 to a non-admin token here (Geo, Sidekiq, storage moves, LDAP, application settings, feature toggles, background migrations); a tenant-shaped mirror has no instance to administer

deferred
Long tail
GET
/api/v4/groups/{id}/repository_storage_moves/{repository_storage_move_id}

refuse — GitLab answers 403 to a non-admin token here (Geo, Sidekiq, storage moves, LDAP, application settings, feature toggles, background migrations); a tenant-shaped mirror has no instance to administer

deferred
Long tail
GET
/api/v4/ldap/{provider}/groups

refuse — GitLab answers 403 to a non-admin token here (Geo, Sidekiq, storage moves, LDAP, application settings, feature toggles, background migrations); a tenant-shaped mirror has no instance to administer

deferred
Long tail
GET
/api/v4/ldap/groups

refuse — GitLab answers 403 to a non-admin token here (Geo, Sidekiq, storage moves, LDAP, application settings, feature toggles, background migrations); a tenant-shaped mirror has no instance to administer

deferred
Long tail
GET
/api/v4/project_repository_storage_moves

refuse — GitLab answers 403 to a non-admin token here (Geo, Sidekiq, storage moves, LDAP, application settings, feature toggles, background migrations); a tenant-shaped mirror has no instance to administer

deferred
Long tail
GET
/api/v4/project_repository_storage_moves/{repository_storage_move_id}

refuse — GitLab answers 403 to a non-admin token here (Geo, Sidekiq, storage moves, LDAP, application settings, feature toggles, background migrations); a tenant-shaped mirror has no instance to administer

deferred
Long tail
GET
/api/v4/projects/{id}/repository_storage_moves

refuse — GitLab answers 403 to a non-admin token here (Geo, Sidekiq, storage moves, LDAP, application settings, feature toggles, background migrations); a tenant-shaped mirror has no instance to administer

deferred
Long tail
GET
/api/v4/projects/{id}/repository_storage_moves/{repository_storage_move_id}

refuse — GitLab answers 403 to a non-admin token here (Geo, Sidekiq, storage moves, LDAP, application settings, feature toggles, background migrations); a tenant-shaped mirror has no instance to administer

deferred
Long tail
GET
/api/v4/sidekiq/compound_metrics

refuse — GitLab answers 403 to a non-admin token here (Geo, Sidekiq, storage moves, LDAP, application settings, feature toggles, background migrations); a tenant-shaped mirror has no instance to administer

deferred
Long tail
GET
/api/v4/sidekiq/job_stats

refuse — GitLab answers 403 to a non-admin token here (Geo, Sidekiq, storage moves, LDAP, application settings, feature toggles, background migrations); a tenant-shaped mirror has no instance to administer

deferred
Long tail
GET
/api/v4/sidekiq/process_metrics

refuse — GitLab answers 403 to a non-admin token here (Geo, Sidekiq, storage moves, LDAP, application settings, feature toggles, background migrations); a tenant-shaped mirror has no instance to administer

deferred
Long tail
GET
/api/v4/sidekiq/queue_metrics

refuse — GitLab answers 403 to a non-admin token here (Geo, Sidekiq, storage moves, LDAP, application settings, feature toggles, background migrations); a tenant-shaped mirror has no instance to administer

deferred
Long tail
GET
/api/v4/snippet_repository_storage_moves

refuse — GitLab answers 403 to a non-admin token here (Geo, Sidekiq, storage moves, LDAP, application settings, feature toggles, background migrations); a tenant-shaped mirror has no instance to administer

deferred
Long tail
GET
/api/v4/snippet_repository_storage_moves/{repository_storage_move_id}

refuse — GitLab answers 403 to a non-admin token here (Geo, Sidekiq, storage moves, LDAP, application settings, feature toggles, background migrations); a tenant-shaped mirror has no instance to administer

deferred
Long tail
GET
/api/v4/snippets/{id}/repository_storage_moves

refuse — GitLab answers 403 to a non-admin token here (Geo, Sidekiq, storage moves, LDAP, application settings, feature toggles, background migrations); a tenant-shaped mirror has no instance to administer

deferred
Long tail
GET
/api/v4/snippets/{id}/repository_storage_moves/{repository_storage_move_id}

refuse — GitLab answers 403 to a non-admin token here (Geo, Sidekiq, storage moves, LDAP, application settings, feature toggles, background migrations); a tenant-shaped mirror has no instance to administer

deferred
Long tail
deploy-keys-tokens17 of 35 served & verified · 9 deviations
GET
/api/v4/groups/{id}/deploy_tokens

this organization's one deploy token is scoped to its one REPOSITORY, which is where a registry-pull token belongs: reader.listCredentials("deploy-token") (reader.ts:3085) holds one row and its scope_ref is a RepoId, so the GROUP-level list is truthfully empty while /api/v4/projects/{id}/deploy_tokens serves that row. SERVED 2026-09-10 as GitLab's empty page with the pagination headers the vendored operation's page/per_page declare, behind the hello-16 credential gate — an older artifact answers the named generation gap, not []. The subgroup resolves here and is empty for a second reason: it owns no repository and so has no registry to pull (founder ruling F1). RULED by the founder 2026-09-10

served & verified
Long tail
GET
/api/v4/groups/{id}/ssh_certificates

a group SSH CERTIFICATE is a certificate AUTHORITY an administrator registers so members can authenticate with short-lived certificates. Registering one is an ACTION and canon records none in any generation; every `ssh-auth` row in reader.listCredentials is a plain ed25519 public key rather than a certificate, which is the same fact read from the members' end. SERVED 2026-09-10 as GitLab's empty page with the pagination headers the vendored operation's page/per_page declare, UNGATED — the fact is a property of the model rather than of the credential domain, so an older artifact answers the same empty list. The vendored document scopes this operation to "a specified group" and not to a top-level one, so the subgroup answers it too. RULED by the founder 2026-09-10

served & verified
Long tail
GET
/api/v4/personal_access_tokens/self/associations

SERVED 2026-09-12 (hello-17): the groups and projects the caller's token reaches, derived from the caller's own membership rows and the projects this organization owns; access_level is their DIRECT standing where they hold one and their organization standing otherwise, which is GitLab's inheritance rule. THE VENDORED DOCUMENT POINTS THIS OPERATION'S 200 AT APIEntitiesPersonalAccessToken - the token, not its associations - contradicting its own summary and description; invariant #2 mirrors the provider, so the body is {groups, projects} and the permissive vendored schema validates it anyway. Paged as ONE sequence, groups first, then split back into the two keys

served & verified
Long tail
GET
/api/v4/projects/{id}/deploy_keys

SERVED 2026-09-12 (coverage wave W8) (hello-15 `ciConfig`): canon.CheckoutKey IS a deploy key — `CheckoutKeyType` is literally "deploy-key" — and reader.listCheckoutKeys(repo) already carries the OpenSSH line and BOTH fingerprints in the forms GitLab prints them. `title` is the comment at the end of that same line, READ OFF the material rather than kept as a second copy, so the title and the key can never name two different things; `created_at` is created_epoch; `fingerprint` is the colon-hex MD5 and `fingerprint_sha256` the OpenSSH form. `expires_at`, `last_used_at` and `usage_type` are OMITTED rather than nulled: this universe issues no key with an expiry, records no use of one and stores no usage flag, all three are optional, and a null typed `string` would buy a deviation for nothing. GENERATION-GATED: an artifact without the CI-configuration domain answers that gap rather than `[]`, because `[]` would say this project's CI clones with nothing. `can_push` is FALSE and it is a fact rather than a default: a checkout key exists so CI can CLONE, and nothing in this universe pushes with one. The two `projects_with_*_access` arrays are omitted here, as real GitLab's `Entities::DeployKeysProject` omits them — `can_push` carries the same fact on a project-scoped read

served & verified
Long tail
GET
/api/v4/projects/{id}/deploy_keys/{key_id}

SERVED 2026-09-12 (coverage wave W8) (hello-15 `ciConfig`): canon.CheckoutKey IS a deploy key — `CheckoutKeyType` is literally "deploy-key" — and reader.listCheckoutKeys(repo) already carries the OpenSSH line and BOTH fingerprints in the forms GitLab prints them. `title` is the comment at the end of that same line, READ OFF the material rather than kept as a second copy, so the title and the key can never name two different things; `created_at` is created_epoch; `fingerprint` is the colon-hex MD5 and `fingerprint_sha256` the OpenSSH form. `expires_at`, `last_used_at` and `usage_type` are OMITTED rather than nulled: this universe issues no key with an expiry, records no use of one and stores no usage flag, all three are optional, and a null typed `string` would buy a deviation for nothing. GENERATION-GATED: an artifact without the CI-configuration domain answers that gap rather than `[]`, because `[]` would say this project's CI clones with nothing. The same row by id, byte-identical to the list's entry, which conformance compares directly; a key id nobody holds is GitLab's own 404

served & verified
Long tail
GET
/api/v4/projects/{id}/deploy_tokens

SERVED 2026-09-10 (hello-16): reader.listCredentials("deploy-token") filtered on scope_ref (reader.ts:3085). `username` is GitLab's OWN derivation, gitlab+deploy-token-{id}, so it is the real value and moves with the id. The scope vocabulary is a DIFFERENT closed set from a PAT's — a deploy token cannot push and cannot reach the API — so canon's scopes map through their own table and it throws on a scope no deploy token can hold. `revoked` and `expired` are read off canon's anchored state and never off a clock, so no Date.now() decides whether a token still works. `expires_at` IS a timestamp here, unlike a personal token's. Gated on hasCredentials()

served & verified
Long tail
GET
/api/v4/projects/{id}/deploy_tokens/{token_id}

SERVED 2026-09-10 (hello-16): the same row by id, byte-identical to the list's first entry

served & verified
Long tail
GET
/api/v4/projects/{id}/deployments
served & verified
Long tail
GET
/api/v4/projects/{id}/deployments/{deployment_id}
served & verified
Long tail
GET
/api/v4/user/gpg_keys

SERVED 2026-09-12 (hello-17): the caller's OpenPGP keys, from the founding member's four kinds. The armored `key` block stays omitted rather than invented: a transferable key needs a self-signature made with a private half this universe never generates, and an armored block that failed to import would be a placeholder in a convincing costume (the hello-16 GPG ruling, unchanged). Answers the named hello-17 generation gap on every older artifact

served & verified
Long tail
GET
/api/v4/user/gpg_keys/{key_id}

SERVED 2026-09-12 (hello-17): the by-id twin of the caller's GPG key list, resolving exactly what that list carries

served & verified
Long tail
GET
/api/v4/user/keys

SERVED 2026-09-12 (hello-17): features.callerCredentials makes the FOUNDING MEMBER hold one of every credential kind this organization accepts, and defines the caller as the founding member - so the persona GET /user reports now holds an ssh-auth key, a signing key, a GPG key and an ACTIVE personal token. This route serves both SSH kinds off credentialsForPerson told apart by usage_type, and the rows are byte-identical to /users/{id}/keys for the same person (one canonical person rendered once). On hello-16 and on the 127 pins frozen before hello-20 it answers the named hello-17 generation gap and never [], because an empty caller key list reports a fact about WHO THE CALLER IS rather than about this organization

served & verified
Long tail
GET
/api/v4/user/keys/{key_id}

SERVED 2026-09-12 (hello-17): the by-id twin of the caller's SSH key list, resolving exactly what that list carries so the two cannot disagree about what this caller holds. Blocked until now by the same fact - the caller held no key, so no id could ever return 200 - and unblocked by the same one

served & verified
Long tail
GET
/api/v4/users/{id}/gpg_keys

SERVED 2026-09-10 (hello-16): reader.credentialsForPerson(person, "gpg") (reader.ts:3075). THE ARMORED `key` FIELD IS OMITTED AND THAT IS THE HONEST ANSWER: `APIEntitiesGpgKey` has three properties and `key` is the ARMORED TRANSFERABLE key, which is a public-key packet PLUS a user id PLUS a self-signature binding them — and a self-signature needs a private half this universe never generated (decisions/2026-09-09-1347). An armored block emitted here would fail `gpg --import`: a placeholder that LOOKS importable. The component declares no `required` list, so a response without `key` is a legal one; `null` would additionally violate its declared `type: string`. GitHub's shape has somewhere honest to put the material (a required `public_key` beside a nullable `raw_key`) and GitLab's does not. `id` and `created_at` are the row's own

served & verified
Long tail
GET
/api/v4/users/{id}/gpg_keys/{key_id}

SERVED 2026-09-10 (hello-16): the same rows by id, byte-identical to the list's first entry (conformance asserts it). The armored-key omission is the list row's ruling and applies here unchanged

served & verified
Long tail
GET
/api/v4/users/{id}/keys/{key_id}

SERVED 2026-09-10 (hello-16): the same rows addressed by gitlabId(PersonCredentialRow.id) — conformance asserts the by-id form is byte-identical to the list's own first row, so the two cannot describe one key two ways. An id the person does not hold is GitLab's own 404 Key Not Found

served & verified
Long tail
GET
/api/v4/users/{user_id}/keys

SERVED 2026-09-10 (hello-16): reader.credentialsForPerson(person, "ssh-auth") + ("ssh-signing") (packages/artifact/src/reader.ts:3075). ONE LIST FOR BOTH GRANTS, which is GitLab's shape rather than GitHub's: an authentication key and a signing key are told apart by `usage_type` (auth/signing) instead of by two endpoints, so canon's two kinds survive the crossing. `key` is the artifact's own OpenSSH line and re-hashes to its `fingerprint_sha256` under ssh-keygen -lf, which conformance recomputes; `expires_at` and `last_used_at` are omitted when canon carries none (the vendored component declares no required list and types both non-nullable, the deploy-key precedent). Gated on reader.hasCredentials(): every pre-hello-16 pin answers the noCredentials generation-gap 404 rather than [], because an empty key list asserts that this person pushes with nothing

served & verified
Long tail
GET
/api/v4/groups/{id}/access_tokens

SERVED 2026-09-10 (hello-16): reader.listCredentials("group-token") filtered on scope_ref (reader.ts:3085) — one row, the roster-sync token scoped to the organization's own group. THE SUBGROUP RESOLVES AND ANSWERS []: it owns no repository and has no automation to run (founder ruling F1, 2026-09-09), and conformance drives both ids. `access_level` is the holder's own organization standing, the same derivation the project rows use. Unpaginated, per the vendored operation. Gated on hasCredentials()

deviation
Long tail
GET
/api/v4/groups/{id}/access_tokens/{token_id}

SERVED 2026-09-10 (hello-16): the same row by id, byte-identical to the list's first entry. The organization's token id does NOT resolve under the subgroup — conformance asserts it, because a token belongs to one group rather than to every group on the host

deviation
Long tail
GET
/api/v4/personal_access_tokens

SERVED 2026-09-10 (hello-16): reader.credentialsForPerson(athena, "personal-token") (reader.ts:3075) — "all personal access tokens accessible by the authenticated user", which on a NON-ADMINISTRATOR is their own, and athena is who GET /user reports. A TOKEN IS METADATA AND NEVER A VALUE: name, scopes, creation, expiry, last use and state, because a provider shows a token's value once at creation and there is no creation here (the PagerDuty routing-key rule). Canon's neutral TokenScopes map to GitLab's coarser vocabulary in one place (render-credentials.ts) and the mapper THROWS on a scope it has never heard of rather than dropping it. `expires_at` is GitLab's DATE form — its own docs example is "2021-01-31" while created_at/last_used_at beside it are timestamps — which the vendored document mistypes date-time, so the row is a deviation. `last_used_ips` is omitted: an IP means something outside this universe and canon records none. Gated on hasCredentials()

deviation
Long tail
GET
/api/v4/personal_access_tokens/{id}

SERVED 2026-09-10 (hello-16): the caller's own tokens by id, byte-identical to the list's first row. Another member's token id answers GitLab's 404 — the same non-administrator visibility the list reports, read from the other end, and conformance drives it

deviation
Long tail
GET
/api/v4/personal_access_tokens/self

SERVED 2026-09-12 (hello-17): `self` names the token the REQUEST AUTHENTICATED WITH, and hello-17 gives the caller one that works: the founding member holds a personal token by construction and the setup-token rotation keeps it live, so the row answers an ACTIVE token rather than the expired one that would have described a request GitLab would have answered 401. The PENDING request canon also carries can never surface here - credentialScope() excludes state='pending' from every list accessor, and credentialRequests() is the only place a request is reachable

deviation
Long tail
GET
/api/v4/projects/{id}/access_tokens

SERVED 2026-09-10 (hello-16): reader.listCredentials("project-token") filtered on scope_ref (reader.ts:3085) — TWO ROWS, AND THEY ARE THE ROTATION: the CI token in force when the incident opened was revoked at the incident's first instant and a replacement minted in the same breath, so the revoked row's ended_epoch IS the active row's created_epoch (conformance asserts they abut). `user_id` is the principal the token acts as — this universe's CI identity, a person a client can then fetch, where real GitLab mints a bot user. `access_level` is that holder's OWN standing in the repository, read off reader.membershipFor("repo", …) and mapped once by GITLAB_ACCESS_LEVEL: GitLab's own rule is that a resource token cannot exceed its creator's role. Unpaginated, because the vendored operation declares neither page nor per_page. Gated on hasCredentials()

deviation
Long tail
GET
/api/v4/projects/{id}/access_tokens/{token_id}

SERVED 2026-09-10 (hello-16): the same two rows by id, byte-identical to the list's first entry. A DEPLOY token's id does NOT resolve here and conformance asserts it — GitLab keeps the two families in separate tables and so does canon

deviation
Long tail
GET
/api/v4/projects/{id}/deployments/{deployment_id}/merge_requests

SERVED 2026-09-12 (coverage wave W8): the deployment names a commit and reader.pullsForCommit resolves the merge requests that commit belongs to — the ones listing it among their commits plus the one whose MERGE commit it is, which is the shape every staging deployment in this universe takes. THE SAME READ backs the DORA lead-time fold, so a lead time and this list cannot disagree about which changes went out. The rows are `glMergeRequest`'s own, carrying the iid `/merge_requests/{iid}` answers to — conformance fetches each one by that iid and asserts it is the same merge request — and the same nullable-key `deviation` the merge-request list already declares. A deployment id nobody has is GitLab's own 404

deviation
Long tail
GET
/api/v4/users/{user_id}/project_deploy_keys

SERVED 2026-09-12 (coverage wave W8) (hello-15 `ciConfig`): canon.CheckoutKey IS a deploy key — `CheckoutKeyType` is literally "deploy-key" — and reader.listCheckoutKeys(repo) already carries the OpenSSH line and BOTH fingerprints in the forms GitLab prints them. `title` is the comment at the end of that same line, READ OFF the material rather than kept as a second copy, so the title and the key can never name two different things; `created_at` is created_epoch; `fingerprint` is the colon-hex MD5 and `fingerprint_sha256` the OpenSSH form. `expires_at`, `last_used_at` and `usage_type` are OMITTED rather than nulled: this universe issues no key with an expiry, records no use of one and stores no usage flag, all three are optional, and a null typed `string` would buy a deviation for nothing. GENERATION-GATED: an artifact without the CI-configuration domain answers that gap rather than `[]`, because `[]` would say this project's CI clones with nothing. `Entities::DeployKey` RATHER THAN `DeployKeysProject`, which is why this row alone publishes the two project arrays and carries no `can_push`: the whole point of a user-scoped key list is saying WHICH projects each key reaches. `projects_with_write_access` is EMPTY and the project sits under `projects_with_readonly_access`, because `can_push` is false on these keys — listing the project as a write grant while the project-scoped row says the key cannot push would be this host contradicting itself about one key (invariant #5). Both arrays are a `deviation`, the vendored component typing each as a single object while real GitLab sends a collection. The PERSON still has to resolve, so an id nobody holds is GitLab's own 404 rather than a list about nobody

deviation
Long tail
GET
/api/v4/deploy_keys

refuse — listing every key, deploy token or impersonation token across the instance requires admin on GitLab; answering would be inventing an admin session

deferred
Long tail
GET
/api/v4/deploy_tokens

refuse — listing every key, deploy token or impersonation token across the instance requires admin on GitLab; answering would be inventing an admin session

deferred
Long tail
GET
/api/v4/groups/{id}/deploy_tokens/{token_id}

refuse — the by-id twin of the group deploy-token list: that list is truthfully empty, so no id can resolve and GitLab's own 404 is the whole answer rather than a coverage miss. SERVED 2026-09-10 with x-sandboxapis-refusal naming this row, and UNGATED unlike the list — no generation of this universe holds a group-level deploy token, so the 404 is true on every artifact and naming the credential generation gap here would point a caller at a live host that answers the identical 404. The group still has to resolve: an id nobody holds is 404 Group Not Found, and the subgroup gets this same refusal. RULED by the founder 2026-09-10

deferred
Long tail
GET
/api/v4/groups/{id}/service_accounts/{user_id}/personal_access_tokens

refuse — every actor in olympus-labs is a person on the roster — canon mints no bot principal, and a service account served here would be a member no other host lists (invariant #5); GitLab answers `[]` at 200 for a group with none. THE NAMED HALF: `{user_id}` names a service account, and no service account exists to be named — so this operation never reaches a token list to be empty of and GitLab's own 404 is the whole answer. UNGATED: canon has no `ServiceAccount` in any generation — hello-17 dropped the federation domain that would have introduced one. RULED by the founder 2026-09-12

deferred
Long tail
GET
/api/v4/keys

refuse — listing every key, deploy token or impersonation token across the instance requires admin on GitLab; answering would be inventing an admin session

deferred
Long tail
GET
/api/v4/keys/{id}

refuse — listing every key, deploy token or impersonation token across the instance requires admin on GitLab; answering would be inventing an admin session

deferred
Long tail
GET
/api/v4/projects/{id}/service_accounts/{user_id}/personal_access_tokens

refuse — every actor in olympus-labs is a person on the roster — canon mints no bot principal, and a service account served here would be a member no other host lists (invariant #5); GitLab answers `[]` at 200 for a group with none. THE NAMED HALF: `{user_id}` names a service account, and no service account exists to be named — so this operation never reaches a token list to be empty of and GitLab's own 404 is the whole answer. UNGATED: canon has no `ServiceAccount` in any generation — hello-17 dropped the federation domain that would have introduced one. RULED by the founder 2026-09-12

deferred
Long tail
GET
/api/v4/users/{user_id}/impersonation_tokens

refuse — listing every key, deploy token or impersonation token across the instance requires admin on GitLab; answering would be inventing an admin session

deferred
Long tail
GET
/api/v4/users/{user_id}/impersonation_tokens/{impersonation_token_id}

refuse — listing every key, deploy token or impersonation token across the instance requires admin on GitLab; answering would be inventing an admin session

deferred
Long tail
merge-requests20 of 34 served & verified · 11 deviations
GET
/api/v4/groups/{id}/approval_rules

SERVED 2026-09-12 (hello-17): an HONEST EMPTY, and gated. features.reviewPolicy exists and the route QUERIES the container's own id against scope_kind/scope rather than hard-coding [] - so it serves a namespace-scoped rule the day canon writes one. None exists: a rule is measured off the merges in its scope, the organization's merges all happen in its one project, and the subgroup owns no repository (founder ruling F1), so neither group id has a rule of its own. On every artifact without the domain the route answers the named hello-17 generation gap instead, because [] there would assert that nothing gates a merge in an organization whose every merge carried reviews

served & verified
Core
GET
/api/v4/groups/{id}/merge_request_approval_setting

SERVED 2026-09-12 (hello-17): the group-level setting is its projects' rules read from above - GitLab's own cascade, where a group setting governs every project the group owns and a subgroup inherits its parent's. This organization owns one project, so the rules in scope are that project's and both group ids answer the same body. One field, for the reason the project twin gives

served & verified
Core
GET
/api/v4/projects/{id}/merge_request_approval_setting

SERVED 2026-09-12 (hello-17): one field, and it is the one canon answers: allow_overrides_to_approver_list_per_merge_request IS canon's `optional`, which says whether a rule may be overridden on a single merge request. The other six settings this entity carries are policy toggles canon does not model and are omitted rather than defaulted, the same ruling /projects/{id}/approvals takes

served & verified
Core
GET
/api/v4/projects/{id}/merge_requests/{merge_request_iid}/approval_rules

SERVED 2026-09-12 (hello-17): the project's rule as one merge request sees it. source_rule reports the project rule's requirement and `overridden` is read off canon's `optional` rather than hard-coded - nothing in this universe writes a per-merge-request rule, and a rule canon marks non-overridable cannot have been overridden

served & verified
Core
GET
/api/v4/projects/{id}/merge_requests/{merge_request_iid}/approval_rules/{approval_rule_id}

SERVED 2026-09-12 (hello-17): the by-id twin of the merge request's approval-rule list, byte-identical to the same rule inside it

served & verified
Core
GET
/api/v4/projects/{id}/merge_requests/{merge_request_iid}/approval_state

SERVED 2026-09-12 (hello-17): how far along one merge request is: approved_by is MEASURED from the reviewers whose verdict was `approve` - the same review table canon derived the rule's approver list from - and `approved` is the arithmetic of approved_by against approvals_required, so a client can recompute it from the two fields printed beside it. Every approver is checked to be an eligible one, and /merge_requests/{iid}/approvals (green since the depth wave) now reads approvals_required off the same rule so the two surfaces cannot disagree

served & verified
Core
GET
/api/v4/projects/{id}/merge_requests/{merge_request_iid}/approvals
served & verified
Core
GET
/api/v4/projects/{id}/merge_requests/{merge_request_iid}/blockees

canon models no dependency between merge requests, so no merge request blocks or is blocked by another

served & verified
Core
GET
/api/v4/projects/{id}/merge_requests/{merge_request_iid}/blocks

the other direction of the merge-request dependency pair, held to the same fact: canon models no dependency between merge requests

served & verified
Core
GET
/api/v4/projects/{id}/merge_requests/{merge_request_iid}/commits
served & verified
Core
GET
/api/v4/projects/{id}/merge_requests/{merge_request_iid}/context_commits

a context commit is one a person manually ATTACHES to a merge request to widen the diff a reviewer sees. It is an action, canon models no such action, and so no merge request carries one. Unpaginated: the vendored document declares no page/per_page on this operation. RULED by the founder 2026-09-09

served & verified
Core
GET
/api/v4/projects/{id}/merge_requests/{merge_request_iid}/diffs
served & verified
Core
GET
/api/v4/projects/{id}/merge_requests/{merge_request_iid}/draft_notes

a draft note is an unsubmitted review comment, by definition never visible to anyone but its author; every review in this universe is already submitted, so the draft list is truthfully empty

served & verified
Core
GET
/api/v4/projects/{id}/merge_requests/{merge_request_iid}/participants
served & verified
Core
GET
/api/v4/projects/{id}/merge_requests/{merge_request_iid}/pipelines
served & verified
Core
GET
/api/v4/projects/{id}/merge_requests/{merge_request_iid}/raw_diffs
served & verified
Core
GET
/api/v4/projects/{id}/merge_requests/{merge_request_iid}/related_issues
served & verified
Core
GET
/api/v4/projects/{id}/merge_requests/{merge_request_iid}/reviewers
served & verified
Core
GET
/api/v4/projects/{id}/merge_requests/{merge_request_iid}/versions
served & verified
Core
GET
/api/v4/projects/{id}/merge_requests/{merge_request_iid}/versions/{version_id}
served & verified
Core
GET
/api/v4/groups/{id}/merge_requests

"all merge requests for a specified group AND ANY SUBGROUPS", which for the organization is every merge request in the universe. THE SUBGROUP RESOLVES HERE as of 2026-09-10 and answers `200 []`: it owns no project and has no subgroup below it (founder ruling F1)

deviation
Core
GET
/api/v4/merge_requests
deviation
Core
GET
/api/v4/projects/{id}/approval_rules

SERVED 2026-09-12 (hello-17): features.reviewPolicy gives the repository the rule its merges were actually held to - approvals_required MEASURED off the merged pulls, approvers derived from the people the review log records approving. rule_type is `regular` by GitLab's own definition (a rule that names its approvers), eligible_approvers IS users because `groups` is empty, and `groups` is empty as a fact about GITLAB'S MODEL rather than about the policy: canon's approver TEAMS have no GitLab container on this host, which publishes exactly two groups and neither is a team. applies_to_all_protected_branches is true because canon's rule carries no branch filter; the vendored component types the accompanying protected_branches as a single object where real GitLab sends an array, so the row is a deviation

deviation
Core
GET
/api/v4/projects/{id}/approval_rules/{approval_rule_id}

SERVED 2026-09-12 (hello-17): the by-id twin of the project's approval-rule list, byte-identical to the same rule inside it

deviation
Core
GET
/api/v4/projects/{id}/approval_settings

SERVED 2026-09-12 (hello-17): the private settings surface over the same rule, rendered by the same shaper as /approval_rules so the two cannot drift. fallback_approvals_required is the rule's own number because canon carries ONE approval measurement for a repository - what its merges actually carried - and there is no second figure for a fallback to differ by. `target_branch` narrows nothing and is answered rather than refused, because the rule applies to every protected branch. The vendored component types `rules` as a single object where real GitLab sends an array, so the row is a deviation

deviation
Core
GET
/api/v4/projects/{id}/approvals

SERVED 2026-09-12 (hello-17): the project's approval configuration: approvals_before_merge is the rule's own requirement and disable_overriding_approvers_per_merge_request is canon's `optional` read from the other end. `approvers` and `approver_groups` are the empty arrays real GitLab sends on any instance that uses approval RULES, which this one does - not a gap - and the vendored components type both as single objects, so the row is a deviation. The seven policy toggles beside them (reset-on-push, author approval, committer approval, password to approve) are OMITTED rather than defaulted: canon models no such policy and a chosen false would be a placeholder wearing a boolean's clothes

deviation
Core
GET
/api/v4/projects/{id}/merge_requests
deviation
Core
GET
/api/v4/projects/{id}/merge_requests/{merge_request_iid}
deviation
Core
GET
/api/v4/projects/{id}/merge_requests/{merge_request_iid}/changes
deviation
Core
GET
/api/v4/projects/{id}/merge_requests/{merge_request_iid}/closes_issues
deviation
Core
GET
/api/v4/projects/{id}/merge_requests/{merge_request_iid}/time_stats

no persona in olympus-labs logs time against an issue or a merge request — canon records no TimeEntry; GitLab's own answer for an untracked issue is a 200 carrying `time_estimate: 0`, `total_time_spent: 0` and both human_ fields null. NOT A COLLECTION: the operation returns `APIEntitiesIssuableTimeStats`, an object, so the honest empty is the zeroed object rather than `[]`. It is the SAME object `glIssue` and `glMergeRequest` already embed as `time_stats` (`emptyTimeStats` in packages/renderer-gitlab/src/render2.ts), read from one function so the standalone endpoint and the embedded block can never disagree (invariant #5). canon's `Issue.estimate` is a story-point estimate on a fibonacci scale rather than a duration, and `time_estimate` is documented in seconds, so mapping one onto the other would be an invention. The two human_ fields are a `deviation` against the vendored component, which types them `string` with no `nullable` while real GitLab renders nil for a zero — the same two entries NULLABLE_SPEC_BUGS already carries for the embedded block. UNGATED: no generation of this universe records a time entry. RULED by the founder 2026-09-12

deviation
Core
GET
/api/v4/projects/{id}/merge_requests/{merge_request_iid}/blocks/{block_id}

refuse — the collection this id would index is empty in this universe, so the only honest answer is the vendor's own 404

planned
Core
GET
/api/v4/projects/{id}/merge_requests/{merge_request_iid}/draft_notes/{draft_note_id}

refuse — the collection this id would index is empty in this universe, so the only honest answer is the vendor's own 404

planned
Core
GET
/api/v4/projects/{id}/merge_requests/{merge_request_iid}/merge_ref

refuse — the head of refs/merge-requests/{iid}/merge — a commit produced by MERGING source into target. GitLab writes that ref from a mergeability check and refuses with the documented 400 when the check does not succeed, which is every settled merge request; for an open one canon records no merge result and the sha would have to be invented. RULED by the founder 2026-09-09

planned
Core
groups26 of 32 served & verified · 6 deviations
GET
/api/v4/groups
served & verified
Core
GET
/api/v4/groups/{id}
served & verified
Core
GET
/api/v4/groups/{id}/audit_events/{audit_event_id}

the by-id twin of the group's register, scanned over THAT GROUP'S fold and then re-read through reader.personAuditEvent(id) — so a PROJECT's audit event answers GitLab's own 404 Audit Event Not Found through this route, which conformance drives directly and which is the whole point of scoping the fold rather than the shaping. The subgroup's ids and the organization's are likewise not interchangeable. An id nobody holds is that same 404, and `%ZZ` is a 404 rather than a 500 (PR #481). SERVED 2026-09-14 (hello-20 `auditActor`): gated on reader.hasAuditActor() after the group has resolved; an older artifact answers the named hello-20 gap

served & verified
Core
GET
/api/v4/groups/{id}/billable_members

every member counts against the seats: canon models no guest, no minimal access and no service account, so nobody here holds a membership without occupying one. THE SUBGROUP RESOLVES HERE as of 2026-09-10 AND ANSWERS GITLAB'S OWN 404: the vendored document says this lists billable members "of a specified TOP-LEVEL group", so the operation does not exist for a subgroup — a different fact from having no billable member, and a `200 []` would report the second while the provider reports the first

served & verified
Core
GET
/api/v4/groups/{id}/groups/shared

there IS a second group as of hello-16 and this is still empty, because a SHARE is an action and canon records none: no group here has ever been shared with another. THE SUBGROUP RESOLVES HERE as of 2026-09-10 and answers the same empty list, for the same reason

served & verified
Core
GET
/api/v4/groups/{id}/invited_groups

an INVITATION between groups is an action and canon records none, so no group here has been invited to another. THE SUBGROUP RESOLVES HERE as of 2026-09-10 and answers the same empty list, for the same reason

served & verified
Core
GET
/api/v4/groups/{id}/issues_statistics

the counts over the same rows the group issue list serves. THE SUBGROUP RESOLVES HERE as of 2026-09-10 and answers REAL ZEROS — `{all: 0, closed: 0, opened: 0}` — rather than an empty object: a group whose issue list is empty has zero of each, which is what real GitLab returns

served & verified
Core
GET
/api/v4/groups/{id}/members

reader.orgMembers() for the organization's own group. THE SUBGROUP RESOLVES HERE as of 2026-09-10 and answers `200 []`: the vendored document is explicit that this lists "all DIRECT members ... does not return inherited members from ancestor groups", and no `membership` row in this artifact scopes to a namespace (decisions/2026-09-09-1742). /members/all, which DOES include ancestors, answers the full roster for it — the two are GitLab's own split rather than one of them being a stub. Before 2026-09-10 this row answered `404 Group Not Found` for an id the same host lists, which was invariant #5 failing from the inside

served & verified
Core
GET
/api/v4/groups/{id}/members/{user_id}

one DIRECT member of the group. THE SUBGROUP RESOLVES HERE as of 2026-09-10 and answers `404 Not found` for every person: it has no direct member, which is the same fact its list row reports as `[]`. The inherited form /members/all/{user_id} is where an organization member resolves

served & verified
Core
GET
/api/v4/groups/{id}/members/all

the direct-plus-INHERITED listing over one membership table. THE SUBGROUP RESOLVES HERE as of 2026-09-10 and answers the ORGANIZATION'S FULL ROSTER for it, which is the vendored document's own semantics — "also returns inherited members from ancestor groups" — and the one family in this sweep where the parent's data flows DOWN rather than the child being empty

served & verified
Core
GET
/api/v4/groups/{id}/members/all/{user_id}

one member of the direct-plus-inherited listing. THE SUBGROUP RESOLVES HERE as of 2026-09-10 and resolves every organization member through inheritance, matching its own list row; the DIRECT-only twin /members/{user_id} answers GitLab's 404 there instead

served & verified
Core
GET
/api/v4/groups/{id}/pending_members

SERVED 2026-09-12 (coverage wave W3): the same rows /api/v4/groups/{id}/invitations serves, in GitLab's pending-member shape. THIS CORRECTS A FALSIFIED EMPTY: the row was terminal on the reason that this group has no outstanding invitation, and the artifact carries two org-scope memberships that are pending with an invitee_email - one of which GitHub has served at /orgs/{org}/invitations since the hello-16 access wave. The vendored description is the wider operation - members in an awaiting state AND those invited without a GitLab account, across the group and any subgroups and projects - and this universe makes the two coincide: canon has no awaiting state (a Membership is active or pending, and a pending one with an address is an invitation) and no subgroup- or project-scoped pending row exists, which conformance asserts rather than assumes. `invited` is person IS NULL AND invitee_email IS NOT NULL; `approved` is true because nobody here is awaiting an administrator's approval, a state canon models nowhere; `name`, `username` and `web_url` are omitted because there is no account to name. The vendored operation declares no schema, so those assertions are the row's whole check. THE SUBGROUP STILL ANSWERS GitLab's 404 - the document says the operation works on top-level groups only. Every artifact without the membership domain goes on answering the same empty list it always did, because reader.listMemberships returns nothing there

served & verified
Core
GET
/api/v4/groups/{id}/placeholder_reassignments

a pending reassignment is an IMPORT artifact and this group was never imported from another instance. THE SUBGROUP RESOLVES HERE as of 2026-09-10 and answers the same empty list, for the same reason

served & verified
Core
GET
/api/v4/groups/{id}/projects

the projects a group owns. The organization owns the one repository; the hello-16 subgroup owns NONE by founder ruling F1 (repo.namespace is null on every row), so this list is truthfully empty for it and the row resolves both group ids rather than 404ing one of them.

served & verified
Core
GET
/api/v4/groups/{id}/projects/shared

the organization owns every project directly and none is shared in from anywhere. THE SUBGROUP RESOLVES HERE as of 2026-09-10 and answers the same empty list: it owns no project of its own (founder ruling F1) and nothing has been shared with it either

served & verified
Core
GET
/api/v4/groups/{id}/provisioned_users

SERVED 2026-09-12 (hello-18): THE SAME SIXTEEN PEOPLE ANSWER saml_users, provisioned_users AND enterprise_users, and that is GitLab's own definition rather than a shortcut: an enterprise user is one the group provisioned through its own SAML/SCIM, and a SAML user and a provisioned user are that sentence read from the other two ends. canon carries ONE identity_provider_config (protocol saml, scim_enabled 1) and one external_uid per person, so the three lists are one set and conformance asserts they are identical rather than leaving it a coincidence. THE MACHINE ACCOUNT IS EXCLUDED and that is the domain's load-bearing ruling (decisions/2026-09-12-1120): canon writes no external_uid for the CI bot, because a directory provisions humans and GitLab's word for a machine principal is a SERVICE ACCOUNT — a different object, a different API family, and one five armed `…/service_accounts` rulings say this universe does not mint. The rows are glUser, byte-identical to what /api/v4/users/{id} serves for each of them, which conformance checks person by person. THE SUBGROUP RESOLVES THIS ONE AND ANSWERS []: it is the one of the three the vendored document does NOT restrict to a top-level group, and a subgroup runs no directory of its own, so it has provisioned nobody. Gated on hasExternalUid(); an older artifact answers the named hello-18 identity gap rather than [], because an empty list there would say nobody here was provisioned by a directory while this same host publishes a SAML configuration with SCIM enabled — the replica disagreeing with itself (invariant #5)

served & verified
Core
GET
/api/v4/groups/{id}/saml_users

SERVED 2026-09-12 (hello-18): THE SAME SIXTEEN PEOPLE ANSWER saml_users, provisioned_users AND enterprise_users, and that is GitLab's own definition rather than a shortcut: an enterprise user is one the group provisioned through its own SAML/SCIM, and a SAML user and a provisioned user are that sentence read from the other two ends. canon carries ONE identity_provider_config (protocol saml, scim_enabled 1) and one external_uid per person, so the three lists are one set and conformance asserts they are identical rather than leaving it a coincidence. THE MACHINE ACCOUNT IS EXCLUDED and that is the domain's load-bearing ruling (decisions/2026-09-12-1120): canon writes no external_uid for the CI bot, because a directory provisions humans and GitLab's word for a machine principal is a SERVICE ACCOUNT — a different object, a different API family, and one five armed `…/service_accounts` rulings say this universe does not mint. The rows are glUser, byte-identical to what /api/v4/users/{id} serves for each of them, which conformance checks person by person. THE SUBGROUP GETS GITLAB'S OWN not_found!: the vendored description scopes this operation to a specified TOP-LEVEL group, so the operation does not exist for a subgroup — a different fact from having nothing to list, and the one the provider reports (the per-family table of decisions/2026-09-10). Gated on hasExternalUid(); an older artifact answers the named hello-18 identity gap rather than [], because an empty list there would say nobody here was provisioned by a directory while this same host publishes a SAML configuration with SCIM enabled — the replica disagreeing with itself (invariant #5)

served & verified
Core
GET
/api/v4/groups/{id}/transfer_locations

the namespaces a GROUP can be transferred into. Empty for the organization because a group cannot be transferred into its own descendant and the only other namespace is one. THE SUBGROUP RESOLVES HERE as of 2026-09-10 and is empty for the OPPOSITE reason, which is why this row now carries both: the only candidate for the subgroup is the parent it already sits under, and a transfer that moves nothing is no transfer. Neither emptiness is a gap

served & verified
Core
GET
/api/v4/groups/{id}/uploads

SERVED 2026-09-12 (hello-18): the group's own uploads — reader.namespaceUploads. THE ROOT GROUP HOLDS NONE AND THE SUBGROUP HOLDS ONE, which is the split /groups/{id}/labels already makes and is a fact rather than a gap: a group-level upload belongs to the CONTAINER it was attached to, and canon attaches its one such file — the chart of the release the subgroup was created around — to the subgroup. So the root answers [] at 200 (the group resolves, it simply has nothing attached) and the subgroup answers its file; conformance drives BOTH ids and asserts each. Gated on hasAttachmentParents(); an older artifact answers the named hello-18 upload gap rather than [], because [] there would say nobody here ever attached a file — false of the story every generation tells, where only the PARENT column was missing

served & verified
Core
GET
/api/v4/groups/{id}/uploads/{secret}/{filename}

SERVED 2026-09-12 (hello-18): the FILE. The vendored operation declares no response schema at all, because the body is the bytes: this route answers the artifact's own attachment.body verbatim, with the artifact's own content_type and `Content-Disposition: attachment` naming the file. The disposition is `attachment` for every kind rather than a branch this host guesses at — GitLab serves an upload inline only for a type on its own safe list, and image/svg+xml, which every image in this canon carries, is explicitly not on it because an SVG can carry script. Conformance re-reads the artifact row and asserts the served bytes are identical to it and that their length is the `size` the list advertised. THE `secret` IS THE FILE'S OWN DIGEST, first 32 characters. GitLab mints a 32-character hex token per upload and canon mints none; an invented one would be a value no client could cross-check, while the first half of attachment.sha256 — measured from the body at compile — is 32 hex characters, is a pure function of the bytes it addresses, and is reproducible by any caller that has downloaded the file. Conformance asserts the secret address and the id address serve the same bytes, and that a secret which is not this file's digest is GitLab's own 404. Gated on hasAttachmentParents(); an older artifact answers the named hello-18 upload gap

served & verified
Core
GET
/api/v4/groups/{id}/uploads/{upload_id}

SERVED 2026-09-12 (hello-18): the FILE. The vendored operation declares no response schema at all, because the body is the bytes: this route answers the artifact's own attachment.body verbatim, with the artifact's own content_type and `Content-Disposition: attachment` naming the file. The disposition is `attachment` for every kind rather than a branch this host guesses at — GitLab serves an upload inline only for a type on its own safe list, and image/svg+xml, which every image in this canon carries, is explicitly not on it because an SVG can carry script. Conformance re-reads the artifact row and asserts the served bytes are identical to it and that their length is the `size` the list advertised. Scoped to the group named in the path, so a project upload's id is GitLab's 404 here exactly as a group upload's is on the project path. Gated on hasAttachmentParents(); an older artifact answers the named hello-18 upload gap

served & verified
Core
GET
/api/v4/projects/{id}/invitations

no pending repository invitations in this universe — the repository roster is the approvers whose organization role is a plain member, plus the CI identity, and all of them were GRANTED rather than offered; GitLab's own answer for a project with none is `[]` at 200. THE GROUP TWIN IS NOT EMPTY and is served with real rows: hello-16 models an invitation as a pending membership carrying an invitee_email, two org-scope memberships are in exactly that state, and /groups/{id}/invitations has served them since wave W3 — what canon does not carry is a REPOSITORY-scoped pending membership, since reader.listMemberships("repo", repo) holds six rows and every one is active. The same reading the founder made of GitHub's twin /repos/{owner}/{repo}/invitations on 2026-09-10. UNGATED: every repository membership in every generation is granted rather than offered, including the four that predate features.memberships and carry no membership table at all. Falsifiable: the `expectsEmpty` conformance entry reds this row the day the generator offers a repository seat. RULED by the founder 2026-09-12 (round 20)

served & verified
Core
GET
/api/v4/projects/{id}/members
served & verified
Core
GET
/api/v4/projects/{id}/members/{user_id}
served & verified
Core
GET
/api/v4/projects/{id}/members/all
served & verified
Core
GET
/api/v4/projects/{id}/members/all/{user_id}
served & verified
Core
GET
/api/v4/groups/{id}/billable_members/{user_id}/indirect

SERVED 2026-09-12 (hello-17): WAS `empty` AND THE RULING WAS FALSIFIED ON SCHEDULE. The 2026-09-09 namespaces decision said in as many words that the day canon gives a namespace a member this goes red rather than hiding it; features.grants gives the subgroup a roster of five - the people whose authority is over the PLAN rather than over the code - so this route serves that person's namespace membership, with created_at read off the membership row's own instant rather than the person's first sighting. A person on no namespace roster still gets [], which is what keeps this a derive rather than a claim that everybody is in the subgroup. THE SUBGROUP STILL ANSWERS GITLAB'S OWN 404: the vendored document says the operation works on top-level groups only. On every artifact without the domain namespaceMembers() finds nothing and the answer is the [] the 127 pins frozen before hello-20 serve

deviation
Core
GET
/api/v4/groups/{id}/billable_members/{user_id}/memberships

the memberships that put one person on a seat: the group, then every project it owns — the same two levels /users/{id}/memberships reports. THE SUBGROUP RESOLVES HERE as of 2026-09-10 and answers GitLab's 404, with the rest of the billable-members family: it addresses a billable member OF a group whose own billable list does not exist for a subgroup, and a member of a list that 404s cannot resolve (invariant #5 from the other end)

deviation
Core
GET
/api/v4/groups/{id}/descendant_groups

the transitive twin of the subgroup list, from the same reader.listNamespaces walk. One level deep in this universe, so it answers the same single row; older pins still answer the empty list. SERVED 2026-09-09 (hello-16)

deviation
Core
GET
/api/v4/groups/{id}/invitations

SERVED 2026-09-12 (coverage wave W3): the OUTSTANDING invitations on this group - reader.listMemberships('org', org) restricted to state pending with no person, an invitee_email and no failed_epoch. GitHub serves exactly these rows at /orgs/{org}/invitations, so the two hosts name the same outstanding offers. A LAPSED offer is excluded and the exclusion is asserted: canon marks an expired invitation with failed_epoch and GitHub publishes those separately, while GitLab has no failed-invitation surface at all - listing one as pending would tell a client an offer can still be accepted when it cannot. `invite_token` is omitted and always will be: it is the secret a recipient redeems the invitation with, and canon's no-secret contract means no row carries one. `user_name` is omitted because every invitation here was sent to an ADDRESS with no account behind it. `access_level` is the integer real GitLab sends where the component types it string, so the row is a deviation. The subgroup resolves and answers its own set. The old reason - a new canon entity Invitation is needed - was falsified by hello-16's membership domain, which models an invitation as a pending membership with an address

deviation
Core
GET
/api/v4/groups/{id}/issues

every issue in the organization's projects. THE SUBGROUP RESOLVES HERE as of 2026-09-10 and answers `200 []`: it owns no repository (founder ruling F1, 2026-09-09) and has no subgroup of its own, so the rollup is empty BY CONSTRUCTION rather than by a gap — the artifact asserts `repo.namespace IS NULL` on every row and conformance re-checks it

deviation
Core
GET
/api/v4/groups/{id}/subgroups

features.namespaces (hello-16) gives this organization one subgroup, so reader.listNamespaces(parent) answers a real child. The 2026-09-09 empty said a subgroup was on hello-16 and this is that generation; older pins still answer the empty list, which is what they truthfully hold. SERVED 2026-09-09 (hello-16)

deviation
Core
projects24 of 30 served & verified · 4 deviations
GET
/api/v4/projects
served & verified
Core
GET
/api/v4/projects/{id}
served & verified
Core
GET
/api/v4/projects/{id}/audit_events

the containment fold at the project: the repository's own row plus the 9 memberships scoped to it and the 4 credential acts whose scope_ref IS this repository (two project access tokens issued, one of them later revoked, and the deploy token) — 10 rows at 90 days, which is WIDER than personAuditEventsForTarget('repo', 'repo-01') returns, and conformance asserts that inequality so the fold cannot quietly collapse back into the single-target read. Every object is read through the scoped accessor that publishes it, so the four repository grants whose principal is a TEAM are absent by construction: listMemberships drops a team-principal grant, GitLab has no team, and an event naming one would resolve nowhere on this host. details.target_type is the Rails class GitLab names the object by (Project, User, PersonalAccessToken, DeployToken) and every target_id resolves to something this host serves. SERVED 2026-09-14 (hello-20 `auditActor`): gated on reader.hasAuditActor() after the project has resolved; an older artifact answers the named hello-20 gap rather than `[]`, which would claim nobody ever created this repository

served & verified
Core
GET
/api/v4/projects/{id}/audit_events/{audit_event_id}

the by-id twin of the project's register, scanned over THAT PROJECT'S fold and re-read through reader.personAuditEvent(id) — so a group's audit event answers GitLab's own 404 Audit Event Not Found through this route, which conformance drives directly. An id nobody holds is that same 404 and `%ZZ` is a 404 rather than a 500 (PR #481). SERVED 2026-09-14 (hello-20 `auditActor`): gated on reader.hasAuditActor() after the project has resolved; an older artifact answers the named hello-20 gap

served & verified
Core
GET
/api/v4/projects/{id}/forks

no project in this universe is a fork and none has been forked: the group owns every project and there is no namespace outside it to fork into — the same fact every project object already reports as forks_count: 0

served & verified
Core
GET
/api/v4/projects/{id}/groups
served & verified
Core
GET
/api/v4/projects/{id}/invited_groups

the project-scoped twin of the group invitation list, held to the same fact: a second group exists from hello-16, and no invitation to this project has ever been made. Reviewed again 2026-09-09 against the subgroup.

served & verified
Core
GET
/api/v4/projects/{id}/issues_statistics
served & verified
Core
GET
/api/v4/projects/{id}/languages
served & verified
Core
GET
/api/v4/projects/{id}/packages/protection/rules

these govern who may PUBLISH into a registry; a read-only mirror accepts no publishes, so nothing has ever configured a rule here — and real GitLab answers a project with none configured 200 []

served & verified
Core
GET
/api/v4/projects/{id}/registry/protection/repository/rules

these govern who may PUBLISH into a registry; a read-only mirror accepts no publishes, so nothing has ever configured a rule here — and real GitLab answers a project with none configured 200 []

served & verified
Core
GET
/api/v4/projects/{id}/registry/protection/tag/rules

these govern who may PUBLISH into a registry; a read-only mirror accepts no publishes, so nothing has ever configured a rule here — and real GitLab answers a project with none configured 200 []

served & verified
Core
GET
/api/v4/projects/{id}/starrers

every person who bookmarked this project, off reader.starsForRepo(repo) in `(repo, person)` key order, with `search` honoured as a real narrowing over the login and the name. THE SAME CANON ROW GITHUB SERVES AT `…/stargazers`, which is the founder's own ruling this row's old reason quoted - stars are generated on both hosts so a starred repo is the same repo on GitHub and GitLab - and it is honoured BY CONSTRUCTION rather than by agreement: one `repo_star` table, two renderings. hello-17 landed only the person-to-person half of that ruling (`person_follow`); this is the other half, four generations later. A STAR IS A BOOKMARK ON WORK YOU HAVE DONE, so the set is strictly smaller than the roster - the CI identity keeps no bookmarks and somebody who has only filed issues never had a moment at which they would have made one - and conformance fails if it ever equals the roster, because a rule that cannot exclude anybody is a constant wearing a fold's clothes. DEVIATION: the vendored document types the 200 `APIEntitiesUserBasic`, a bare user; real GitLab presents `Entities::UserStarrer`, which is `{starred_since, user}`, and its own REST documentation for List starrers of a project shows that pair. Invariant #2 mirrors the provider rather than its documentation, and the difference is not cosmetic - `starred_since` is the one column this domain minted beyond the pair of ids (the earlier of that person's first commit and first review here) and `APIEntitiesUserBasic` has nowhere to put it. SERVED 2026-09-14 (hello-20 `watchGraph`), gated on hasWatchGraph(); an older artifact answers the named hello-20 star gap, because [] there would claim nobody in this organization has bookmarked the one project they all work in.

served & verified
Core
GET
/api/v4/projects/{id}/statistics
served & verified
Core
GET
/api/v4/projects/{id}/storage
served & verified
Core
GET
/api/v4/projects/{id}/terraform/state_protection_rules

these govern who may PUBLISH into a registry; a read-only mirror accepts no publishes, so nothing has ever configured a rule here — and real GitLab answers a project with none configured 200 []

served & verified
Core
GET
/api/v4/projects/{id}/transfer_locations

the namespaces a project can be moved into, excluding its current parent. From hello-16 there is one that qualifies - the subgroup - so reader.listNamespaces() answers a real row where the 2026-09-09 ruling had nowhere to point. Older pins still answer the empty list. SERVED 2026-09-09 (hello-16)

served & verified
Core
GET
/api/v4/projects/{id}/uploads

SERVED 2026-09-12 (hello-18): APIEntitiesMarkdownUploadAdmin over the files hanging off THIS PROJECT'S OWN RECORDS — its issues, those issues' comments and its merge requests — which is a JOIN rather than a column, because decisions/2026-09-12-1148 declines a `repo` parent in as many words: a row claiming a project upload with no record behind it would be a file nobody uploaded. Newest first, which is the operation's own stated order, tie-broken on the canonical id so the list paginates identically on every request. `size` is the artifact's MEASURED length, computed at compile from the body rather than drawn, so the list and the download cannot describe different files; `uploaded_by` is APIEntitiesUserSafe and every uploader resolves to a person this host serves. THE ALERT METRIC IMAGES ARE DELIBERATELY NOT IN THIS LIST and conformance asserts their absence: GitLab keeps them in its system store (/uploads/-/system/alert_metric_image/…, which is the vendored component's own example path), not among a project's markdown uploads, and serving one file at both addresses would assert two uploads where canon records one. Gated on hasAttachmentParents(); an older artifact answers the named hello-18 upload gap rather than [], because [] there would say nobody here ever attached a file — false of the story every generation tells, where only the PARENT column was missing

served & verified
Core
GET
/api/v4/projects/{id}/uploads/{secret}/{filename}

SERVED 2026-09-12 (hello-18): the FILE. The vendored operation declares no response schema at all, because the body is the bytes: this route answers the artifact's own attachment.body verbatim, with the artifact's own content_type and `Content-Disposition: attachment` naming the file. The disposition is `attachment` for every kind rather than a branch this host guesses at — GitLab serves an upload inline only for a type on its own safe list, and image/svg+xml, which every image in this canon carries, is explicitly not on it because an SVG can carry script. Conformance re-reads the artifact row and asserts the served bytes are identical to it and that their length is the `size` the list advertised. THE `secret` IS THE FILE'S OWN DIGEST, first 32 characters. GitLab mints a 32-character hex token per upload and canon mints none; an invented one would be a value no client could cross-check, while the first half of attachment.sha256 — measured from the body at compile — is 32 hex characters, is a pure function of the bytes it addresses, and is reproducible by any caller that has downloaded the file. Conformance asserts the secret address and the id address serve the same bytes, and that a secret which is not this file's digest is GitLab's own 404. The filename must match too: a right secret with a wrong name addresses nothing. Gated on hasAttachmentParents(); an older artifact answers the named hello-18 upload gap

served & verified
Core
GET
/api/v4/projects/{id}/uploads/{upload_id}

SERVED 2026-09-12 (hello-18): the FILE. The vendored operation declares no response schema at all, because the body is the bytes: this route answers the artifact's own attachment.body verbatim, with the artifact's own content_type and `Content-Disposition: attachment` naming the file. The disposition is `attachment` for every kind rather than a branch this host guesses at — GitLab serves an upload inline only for a type on its own safe list, and image/svg+xml, which every image in this canon carries, is explicitly not on it because an SVG can carry script. Conformance re-reads the artifact row and asserts the served bytes are identical to it and that their length is the `size` the list advertised. The id is gitlabId(attachment.id), the same id the list publishes, and an id this project's records do not carry is GitLab's own 404 — a group upload's id included. Gated on hasAttachmentParents(); an older artifact answers the named hello-18 upload gap

served & verified
Core
GET
/api/v4/projects/{id}/users
served & verified
Core
GET
/api/v4/users/{user_id}/contributed_projects
served & verified
Core
GET
/api/v4/users/{user_id}/projects

the projects a person owns in their OWN namespace. Canon models no personal namespace — the group owns every project — so nobody owns one. users/{id}/contributed_projects answers the question this is often mistaken for, and it is served with real rows. RULED by the founder 2026-09-09; a subgroup and group-level objects are on hello-16

served & verified
Core
GET
/api/v4/users/{user_id}/starred_projects

the same `repo_star` table read from the person's end, off reader.starsByPerson(person), rendered as the `APIEntitiesBasicProjectDetails` `/api/v4/projects` publishes - conformance asserts the served project is BYTE-IDENTICAL to that list's row, so one project cannot be described two ways. `{user_id}` accepts an ID OR A USERNAME, which is what the operation's own parameter description says GitLab takes, and conformance drives both and asserts they answer the same list. Somebody who starred nothing is an honest empty under a parent that resolves, and the suite fails if NOBODY in the roster is in that position, because the exclusion the derivation makes would then be unobservable; a person nobody has is GitLab's own 404. SERVED 2026-09-14 (hello-20 `watchGraph`), gated on hasWatchGraph(); an older artifact answers the named hello-20 star gap.

served & verified
Core
GET
/api/v4/projects/{id}/issues
deviation
Core
GET
/api/v4/projects/{id}/issues/{issue_iid}
deviation
Core
GET
/api/v4/projects/{id}/issues/{issue_iid}/closed_by
deviation
Core
GET
/api/v4/projects/{id}/share_locations

the groups a project can be invited to, excluding the one that already owns it. The subgroup qualifies from hello-16. Unpaginated: the vendored document declares no page/per_page on this operation. Older pins still answer the empty list. SERVED 2026-09-09 (hello-16)

deviation
Core
GET
/api/v4/projects/{id}/pages_access

refuse — olympus-labs publishes no GitLab Pages site — no project in this universe has a pages deployment or a custom domain; GitLab's own 404 for a project with Pages unused is the whole answer. ALL FOUR ROWS REFUSE, the domain list included: a Pages domain collection is addressable only on a project that HAS Pages, and no project here does — a 200 `[]` there would advertise a Pages site with no custom domain rather than the absence of Pages. The vendored document declares `404 Not Found` on all four operations. UNGATED: canon has no PagesSite in any generation. RULED by the founder 2026-09-12

planned
Core
GET
/api/v4/projects/{id}/security_settings

refuse — the TIER-1 row of §A-R1, and worth being explicit about. Its 200 is a settings OBJECT the vendored document declares no schema for, and every field in it is an answer about a SCANNER - which scanners auto-fix, whether secret push protection is on. No security product is enabled on olympus-labs (the founder's ruling of 2026-09-12, extended to this family 2026-09-14), so there is no such configuration to read; publishing an all-false object would mean inventing a field set the vendored document does not declare and which drifts release to release, which is the placeholder invariant #4 forbids. GitLab's own answer where the feature is not available is the 404 this operation declares, and it agrees with the money surface: hello-20's F8 derives this namespace's plan rung as `premium` precisely because nothing here evidences the product the top rung is bought for. UNGATED: no generation of this universe enables a scanner; driven in conformance through a project that resolves.

planned
Core
events16 of 23 served & verified · 4 deviations
GET
/api/v4/events
served & verified
Next
GET
/api/v4/groups/{id}/epics/{eventable_id}/resource_label_events

NO EPIC IN THIS UNIVERSE WAS EVER LABELLED, and the epic itself says so: glEpic publishes `labels: []` on every one of the 27 epics, and hello-20's `resource_event` table is keyed on an ISSUE id (`issue TEXT NOT NULL REFERENCES issue(id)`) so not one of its 205 rows at 90 days names an epic and none ever can. `[]` at 200 under an epic that RESOLVES is therefore GitLab's own answer and the replica agreeing with itself; a feed with events in it would contradict the epic beside it (invariant #5). UNGATED, which is what separates this from a generation gap: the fact is true of every artifact, so the epic's own hello-16 gate is the only one, and a pre-hello-16 artifact answers the EPIC gap rather than this empty. RULED 2026-09-14 on the hello-20 GitLab history wave.

served & verified
Next
GET
/api/v4/groups/{id}/epics/{eventable_id}/resource_state_events

reader.listEpics() — the epic's own state, closed_epoch and author, which is the merge request's rule in ./depth-history.ts one entity over: canon needs no history table for an epic's state, because an epic that closed carries the instant it closed at and THAT instant is the one event GitLab records. 17 of the 27 epics are closed at 90 days and answer one `closed` event each; the other 10 answer `[]` because they resolve and simply never closed. `resource_type` is Epic and `resource_id` the epic, `user` its author, `created_at` its closed_at. `source_commit` and `source_merge_request_id` are OMITTED throughout: canon records neither against an epic's close and either invented here would be a placeholder. THE GATE IS THE EPIC AND NOT THIS GENERATION — the epic resolves through reader.hasPortfolio() and glEpicOrder's iid sequence, exactly as every other epic route on this host, so a pre-hello-16 artifact answers the EPIC generation gap and a hello-19 one answers this feed in full. The subgroup owns no plan, so `{id}` naming it is GitLab's 404 Epic Not Found. SERVED 2026-09-14 (hello-20 `resourceEvents` wave, gated on hello-16 `portfolio` rather than on hello-20): an artifact without epics answers the epic gap, never `[]`

served & verified
Next
GET
/api/v4/groups/{id}/epics/{eventable_id}/resource_state_events/{event_id}

the by-id twin of the epic state feed, off the same EpicRow fold and byte-identical to its entry in the list. Addressed by gitlabId of (epic, state); an open epic holds no id at all, so every id on one is GitLab's own 404 Resource Not Found, and `%ZZ` is that 404 rather than the raw 500 PR #481 fixed. Gated on hello-16 `portfolio` for its list's reason. SERVED 2026-09-14 (hello-20 `resourceEvents` wave, gated on the epic)

served & verified
Next
GET
/api/v4/projects/{id}/events
served & verified
Next
GET
/api/v4/projects/{id}/issues/{eventable_id}/resource_iteration_events

NOBODY IN THIS UNIVERSE EVER PUT AN ISSUE IN AN ITERATION AS AN ACT. canon's `ResourceEventKind` is `label` | `milestone` and nothing else, ruled in the hello-20 canon decision because a sprint is PLANNING STATE rather than something a person attached at an instant — an issue's sprint is a fact about the plan, and the plan is served as the iteration itself (/groups/{id}/iterations, coverage wave W8). And this host claims no iteration on the issue: glIssue omits the field under its 2026-08-30 bounding rule (an EE-tier field gitlab.com serializes and a self-managed CE instance omits, so this replica makes neither claim), so `[]` says exactly what the issue says. `[]` at 200 under an issue that RESOLVES; an iid nobody holds is still GitLab's own 404 Not found. UNGATED: the fact is true of every artifact, not of this snapshot, which is what separates a ruling from a generation gap wearing one's clothes. RULED 2026-09-14 on the hello-20 GitLab history wave.

served & verified
Next
GET
/api/v4/projects/{id}/issues/{eventable_id}/resource_label_events

reader.resourceEventsOfKind(issue, 'label') — hello-20's `resource_event` table, which is the history `issue_label` could not carry: a bare join with no instant on it. 205 rows at 90 days (101 label adds, 30 removes, 49 milestone adds, 25 removes) and every one of them written as the simulation played out, so the instant is an event's own instant and the actor the person who performed it — a label lands when the issue is TRIAGED, by whoever made that transition, or at filing by its author when nobody ever triaged it, and the starter label comes off at triage when somebody picked the work up. `id` is (issue, ord) run through gitlabId, the composition ./depth-history.ts already uses for the state events beside these: the table carries NO id column, because a resource event is addressed by its issue and its position. `label` is the SAME object /projects/{id}/labels publishes, resolved through reader.listLabels, so a caller that follows a label out of an event lands on a row this host serves. The list is `ord` order and never `at_epoch` order: two events share an instant when a label lands and the starter label comes off at one transition, and `ord` is the position in the finalized log, which is the only stable one. `{eventable_id}` is the ISSUE's iid, GitLab's own parameter description, so an iid nobody holds is GitLab's 404 Not found and an unknown project is its 404 Project Not Found — on every generation. SERVED 2026-09-14 (hello-20 `resourceEvents`): gated on reader.hasResourceEvents(); an older artifact answers the NAMED hello-20 gap (`the history of a label or a milestone on an issue`) rather than `[]`, because `[]` would claim no label on this issue was ever applied by anybody while the same artifact serves the labels themselves on the issue beside it

served & verified
Next
GET
/api/v4/projects/{id}/issues/{eventable_id}/resource_label_events/{event_id}

the by-id twin of the issue label feed, off the same reader.resourceEventsOfKind(issue, 'label') rows and byte-identical to the event's entry in the list, which conformance compares directly. The id a caller walks to is the id this route accepts: both are gitlabId of the (issue, ord) pair, and an id this issue does not hold is GitLab's own 404 Resource Not Found rather than a neighbouring issue's event. `%ZZ` in the id segment is that same 404 and not a 500 — Hono has already decoded the segment and a second decodeURIComponent threw a raw URIError (fixed fleet-wide in PR #481), which conformance asserts here. SERVED 2026-09-14 (hello-20 `resourceEvents`): gated on reader.hasResourceEvents(); an older artifact answers the named hello-20 gap after the project and the iid have resolved, so the gap is a statement about the snapshot rather than about whether the issue exists

served & verified
Next
GET
/api/v4/projects/{id}/issues/{eventable_id}/resource_state_events

SERVED 2026-09-12 (coverage wave W8): GitLab's ResourceStateEvent vocabulary is NARROW — opened, closed, merged, reopened — and canon's `issue_transition` is a WORKFLOW-COLUMN log (backlog, todo, in_progress, in_review, done, canceled). So a column move is NOT a state event and is not served as one: only a move into a terminal column (done or canceled, exactly the two workflow states canon marks an issue `closed` in) is a CLOSE, and a move back out of one would be a REOPEN. `wave-w8.test.ts` walks the whole issue roster and asserts that constant against the artifact, so the day canon grows a third terminal column the row goes red rather than quietly under-reporting a close. `id` is (issue, ord) minted through gitlabId; `user` is the transition's actor, `created_at` its instant, and `source_merge_request_id` the merge request that performed the close where `by_pull` records one and nothing at all where it does not. `source_commit` is omitted — canon records no commit against a close. An OPEN issue has no state event and says so with `[]` rather than a 404: the issue resolves, it simply never changed state. An iid nobody has is GitLab's own 404

served & verified
Next
GET
/api/v4/projects/{id}/issues/{eventable_id}/resource_state_events/{event_id}

SERVED 2026-09-12 (coverage wave W8): GitLab's ResourceStateEvent vocabulary is NARROW — opened, closed, merged, reopened — and canon's `issue_transition` is a WORKFLOW-COLUMN log (backlog, todo, in_progress, in_review, done, canceled). So a column move is NOT a state event and is not served as one: only a move into a terminal column (done or canceled, exactly the two workflow states canon marks an issue `closed` in) is a CLOSE, and a move back out of one would be a REOPEN. `wave-w8.test.ts` walks the whole issue roster and asserts that constant against the artifact, so the day canon grows a third terminal column the row goes red rather than quietly under-reporting a close. `id` is (issue, ord) minted through gitlabId; `user` is the transition's actor, `created_at` its instant, and `source_merge_request_id` the merge request that performed the close where `by_pull` records one and nothing at all where it does not. `source_commit` is omitted — canon records no commit against a close. The same event by id, byte-identical to its entry in the list, which conformance compares directly

served & verified
Next
GET
/api/v4/projects/{id}/issues/{eventable_id}/resource_weight_events

NOBODY IN THIS UNIVERSE EVER SET AN ISSUE'S WEIGHT. canon's `ResourceEventKind` is `label` | `milestone` and nothing else, ruled in the hello-20 canon decision because a weight is a VALUE rather than a row — canon carries `issue.estimate`, which is the team's sizing and not GitLab's weight, and no event in the log ever changes it. And this host claims no weight on the issue: glIssue omits the field under its 2026-08-30 bounding rule, so `[]` says exactly what the issue says. `[]` at 200 under an issue that RESOLVES; an iid nobody holds is still GitLab's own 404 Not found. UNGATED, for the iteration row's reason. RULED 2026-09-14 on the hello-20 GitLab history wave.

served & verified
Next
GET
/api/v4/projects/{id}/merge_requests/{eventable_id}/resource_label_events

reader.pullLabels(pull) restated at the instant the merge request opened, and NOT `resource_event`: a pull request's labels are labelFor(storyline.kind), fixed when the pull opens and never moved by anybody in this universe, so its label feed is `pull_label` at one instant — the identical composition ./depth-history.ts performs when it restates merged_at as a ResourceStateEvent. The hello-20 canon decision says so in as many words (`It needs no canon, so it gets none. The serving wave should classify the two MR rows derive, not generate`). Measured on the 90-day artifact: 24 merge requests, every one carrying exactly one label and not one carrying two. `action` is always `add` and never `remove` — nothing in the canonical log ever takes a label off a merge request, and a remove composed here would be an act nobody performed. `id` is gitlabId of (pull, label), `user` the pull's author, `created_at` its created_at, `label` the row /projects/{id}/labels publishes. THIS ROW IS ON NO GENERATION GATE, which is what `derive` means here: `pull_label` is a hello-1 table, so the same rule holds on every registered pin and conformance asserts it against a hello-19 artifact as well as a hello-20 one. SERVED 2026-09-14 (hello-20 `resourceEvents` wave, ungated): an older artifact answers the SAME feed, computed from its own pulls; an iid nobody holds is GitLab's 404 Not found on both

served & verified
Next
GET
/api/v4/projects/{id}/merge_requests/{eventable_id}/resource_label_events/{event_id}

the by-id twin of the merge request's label feed, off the same reader.pullLabels rows and byte-identical to its entry in the list. Addressed by gitlabId of the (pull, label) pair the list publishes; an id this merge request does not hold is GitLab's own 404 Resource Not Found, and `%ZZ` is that 404 rather than the raw 500 PR #481 fixed. Ungated for its list's reason. SERVED 2026-09-14 (hello-20 `resourceEvents` wave, ungated): an older artifact answers the same event computed from its own pulls

served & verified
Next
GET
/api/v4/projects/{id}/merge_requests/{eventable_id}/resource_state_events

SERVED 2026-09-12 (coverage wave W8): canon needs no history table for a merge request's state — one that settled carries the instant it settled at, and that instant IS the one event GitLab records: `merged` for one that landed, `closed` for one that did not, and nothing for one still open. reader.listPulls; `user` is the author, `resource_id` the merge request, `id` minted from (pull, state). An open merge request answers `[]` rather than a 404: it resolves, it simply has not settled. An iid nobody has is GitLab's own 404

served & verified
Next
GET
/api/v4/projects/{id}/merge_requests/{eventable_id}/resource_state_events/{event_id}

SERVED 2026-09-12 (coverage wave W8): canon needs no history table for a merge request's state — one that settled carries the instant it settled at, and that instant IS the one event GitLab records: `merged` for one that landed, `closed` for one that did not, and nothing for one still open. reader.listPulls; `user` is the author, `resource_id` the merge request, `id` minted from (pull, state). The same event by id, byte-identical to its entry in the list

served & verified
Next
GET
/api/v4/users/{id}/events
served & verified
Next
GET
/api/v4/projects/{id}/issues/{eventable_id}/resource_milestone_events

reader.resourceEventsOfKind(issue, 'milestone') — the milestone half of the same `resource_event` table. A milestone is set at SPRINT PLANNING (the `sprint-opened` instant of the issue's own sprint, by the lead who opened it, or at the issue's creation when the sprint was already running) and cleared when an issue SLIPS a sprint (its sprint closed while it was neither done nor canceled), so both instants are acts the event log already records. `milestone` is the SAME object /projects/{id}/milestones publishes, through the same glMilestone, so the two surfaces cannot disagree about one milestone's dates. `state` is GitLab's own field for the ISSUABLE's state at the instant, derived from the issue's own close rather than from its state today — a milestone removed at a sprint close after the issue was finished reads `closed` and the identical event on a live issue reads `opened`. DEVIATION, and it is the milestone's own: the vendored document types APIEntitiesMilestone.start_date and due_date as non-nullable strings while real GitLab emits both always and null where unset — the bug NULLABLE_SPEC_BUGS has recorded since 2026-08-30 for the milestone embedded in every issue and merge request, carried here because it is the same object. SERVED 2026-09-14 (hello-20 `resourceEvents`): gated on reader.hasResourceEvents(); an older artifact answers the named hello-20 gap rather than `[]`, which would claim this issue was never planned into a sprint while the issue beside it carries the milestone

deviation
Next
GET
/api/v4/projects/{id}/issues/{eventable_id}/resource_milestone_events/{event_id}

the by-id twin of the issue milestone feed, off the same reader.resourceEventsOfKind(issue, 'milestone') rows and byte-identical to its entry in the list. Addressed by gitlabId of the (issue, ord) pair, so the id the list publishes is the id this route accepts, and an id this issue does not hold is GitLab's own 404 Resource Not Found; `%ZZ` is that same 404 rather than the raw URIError 500 PR #481 fixed fleet-wide. Carries the milestone's own start_date/due_date deviation for its list's reason. SERVED 2026-09-14 (hello-20 `resourceEvents`): gated on reader.hasResourceEvents(); an older artifact answers the named hello-20 gap after the project and the iid have resolved

deviation
Next
GET
/api/v4/projects/{id}/merge_requests/{eventable_id}/resource_milestone_events

PullRow.milestone restated at the instant the merge request opened — the label feed's rule on the merge request's other fixed attribute, and NOT the blanket empty PullRow alone suggests. Measured on the 90-day artifact: exactly ONE of the 24 merge requests carries a milestone, so twenty-three answer `[]` because they were never milestoned and one answers a single `add`; conformance asserts that count against the artifact rather than against a hope, so the day canon milestones a second one the row goes red instead of under-reporting. `state` is `opened` on every row by construction: the instant is the instant the merge request opened, and nothing is merged before it exists. `milestone` is the SAME object /projects/{id}/milestones publishes, and it carries that object's own start_date/due_date deviation (NULLABLE_SPEC_BUGS, 2026-08-30). Ungated for the label feed's reason: nothing here reads a hello-20 table. SERVED 2026-09-14 (hello-20 `resourceEvents` wave, ungated): an older artifact answers the same rule over its own pulls

deviation
Next
GET
/api/v4/projects/{id}/merge_requests/{eventable_id}/resource_milestone_events/{event_id}

the by-id twin of the merge request's milestone feed, off the same PullRow.milestone fold and byte-identical to its entry in the list. Addressed by gitlabId of the (pull, milestone) pair; an id this merge request does not hold — which is every id on the twenty-three that carry no milestone — is GitLab's own 404 Resource Not Found, and `%ZZ` is that 404 rather than a 500. Carries the milestone's own deviation, and is ungated, for its list's reasons. SERVED 2026-09-14 (hello-20 `resourceEvents` wave, ungated)

deviation
Next
GET
/api/v4/groups/{id}/epics/{eventable_id}/resource_label_events/{event_id}

refuse — ENTAILED by the empty beside it: the epic's label feed serves `[]` on this same host, so no event id resolves and GitLab's own `404 Resource Not Found` is the whole answer. The EPIC still resolves first, so an epic iid nobody holds is `404 Epic Not Found` and a malformed escape (`%ZZ`) is a 404 rather than the raw URIError 500 PR #481 fixed fleet-wide. RULED 2026-09-14 on the hello-20 GitLab history wave.

deferred
Next
GET
/api/v4/projects/{id}/issues/{eventable_id}/resource_iteration_events/{event_id}

refuse — ENTAILED by the empty beside it: the iteration feed serves `[]` on this same host, so no event id resolves and GitLab's own `404 Resource Not Found` is the whole answer. The project and the issue iid still resolve first, and `%ZZ` is a 404 rather than the raw URIError 500 PR #481 fixed. RULED 2026-09-14 on the hello-20 GitLab history wave.

deferred
Next
GET
/api/v4/projects/{id}/issues/{eventable_id}/resource_weight_events/{event_id}

refuse — ENTAILED by the empty beside it: the weight feed serves `[]` on this same host, so no event id resolves and GitLab's own `404 Resource Not Found` is the whole answer. The project and the issue iid still resolve first, and `%ZZ` is a 404 rather than a 500 (PR #481). RULED 2026-09-14 on the hello-20 GitLab history wave.

deferred
Next
discussions7 of 20 served & verified · 6 deviations
GET
/api/v4/groups/{id}/epics/{noteable_id}/discussions/{discussion_id}/notes

SERVED 2026-09-12 (hello-17): the notes inside one epic discussion, byte-identical to the same notes inside the discussion object. The vendored document types this operation's 200 as APIEntitiesDiscussion - Grape's desc block disagreeing with a route that presents the NOTE collection - so conformance validates against APIEntitiesNote, which is what a client receives; the issue and merge-request twins make the same call

served & verified
Core
GET
/api/v4/groups/{id}/epics/{noteable_id}/discussions/{discussion_id}/notes/{note_id}

SERVED 2026-09-12 (hello-17): one note inside one epic discussion, rendered by the same glEpicNote every other note surface uses

served & verified
Core
GET
/api/v4/projects/{id}/issues/{noteable_id}/discussions/{discussion_id}/notes
served & verified
Core
GET
/api/v4/projects/{id}/issues/{noteable_id}/discussions/{discussion_id}/notes/{note_id}
served & verified
Core
GET
/api/v4/projects/{id}/merge_requests/{noteable_id}/discussions/{discussion_id}/notes
served & verified
Core
GET
/api/v4/projects/{id}/merge_requests/{noteable_id}/discussions/{discussion_id}/notes/{note_id}
served & verified
Core
GET
/api/v4/projects/{id}/repository/commits/{noteable_id}/discussions

no commit in this universe carries a comment — DECISIONS 2026-08-20 [commit-detail] ruled it per endpoint and that ruling stands (the 2026-08-29 entry superseded only its three-verdict bar); GitLab's own answer for a commit with no notes is `[]` at 200. THE LIST HALF: the commit must resolve first (reader.getCommitBySha), so a sha nobody pushed is still GitLab's 404 rather than a cheerful `[]`. UNGATED: packages/canon/src/notes.ts:52 records that hello-17's entity-note domain may not declare a `commit` parent until this ruling is reversed, so no generation of this universe carries a commit note. Falsifiable: the `expectsEmpty` entry reds the row the day one appears. RULED by the founder 2026-09-12

served & verified
Core
GET
/api/v4/groups/{id}/epics/{noteable_id}/discussions

SERVED 2026-09-12 (hello-17): a discussion IS the epic's notes grouped by entity_note.thread_root - not a second entity and not a second query - so /notes and /discussions partition exactly the same set and render each note identically. individual_note is `this thread has one note`; resolvable is false because GitLab makes only DIFF notes resolvable and an epic has no diff. The vendored component types `notes` as a single object where real GitLab sends an array, so the row is a deviation, exactly as its issue and merge-request twins are

deviation
Core
GET
/api/v4/groups/{id}/epics/{noteable_id}/discussions/{discussion_id}

SERVED 2026-09-12 (hello-17): one epic discussion by its 40-hex digest, which is SHA-1 over the canonical id of the thread's root note - the same derivation the issue and merge-request discussions use, so a discussion is addressable by exactly one thread. A discussion that belongs to a different epic 404s here rather than being served under the wrong parent

deviation
Core
GET
/api/v4/projects/{id}/issues/{noteable_id}/discussions
deviation
Core
GET
/api/v4/projects/{id}/issues/{noteable_id}/discussions/{discussion_id}
deviation
Core
GET
/api/v4/projects/{id}/merge_requests/{noteable_id}/discussions
deviation
Core
GET
/api/v4/projects/{id}/merge_requests/{noteable_id}/discussions/{discussion_id}
deviation
Core
GET
/api/v4/projects/{id}/repository/commits/{noteable_id}/discussions/{discussion_id}

refuse — no commit in this universe carries a comment — DECISIONS 2026-08-20 [commit-detail] ruled it per endpoint and that ruling stands (the 2026-08-29 entry superseded only its three-verdict bar); GitLab's own answer for a commit with no notes is `[]` at 200. THE DISCUSSION-ID HALF: with no note on any commit there is no discussion id to resolve, so GitLab's own 404 is the whole answer rather than a 200 over a discussion that does not exist. UNGATED, for the reason the list half states. RULED by the founder 2026-09-12

planned
Core
GET
/api/v4/projects/{id}/repository/commits/{noteable_id}/discussions/{discussion_id}/notes

refuse — no commit in this universe carries a comment — DECISIONS 2026-08-20 [commit-detail] ruled it per endpoint and that ruling stands (the 2026-08-29 entry superseded only its three-verdict bar); GitLab's own answer for a commit with no notes is `[]` at 200. THE DISCUSSION-ID HALF: with no note on any commit there is no discussion id to resolve, so GitLab's own 404 is the whole answer rather than a 200 over a discussion that does not exist. UNGATED, for the reason the list half states. RULED by the founder 2026-09-12

planned
Core
GET
/api/v4/projects/{id}/repository/commits/{noteable_id}/discussions/{discussion_id}/notes/{note_id}

refuse — no commit in this universe carries a comment — DECISIONS 2026-08-20 [commit-detail] ruled it per endpoint and that ruling stands (the 2026-08-29 entry superseded only its three-verdict bar); GitLab's own answer for a commit with no notes is `[]` at 200. THE DISCUSSION-ID HALF: with no note on any commit there is no discussion id to resolve, so GitLab's own 404 is the whole answer rather than a 200 over a discussion that does not exist. UNGATED, for the reason the list half states. RULED by the founder 2026-09-12

planned
Core
GET
/api/v4/projects/{id}/snippets/{noteable_id}/discussions

refuse — no snippet exists in this organization — every shared file in olympus-labs lives in the parthenon repository and no persona has ever posted a snippet; GitLab answers `[]` at 200 for a snippet list and its own 404 for a snippet id that names nothing. THE ID HALF: no snippet id resolves, so this operation never reaches a collection to be empty of and GitLab's own 404 is the whole answer — `[]` on a snippet's award-emoji or note list would assert that the snippet exists and holds nothing. UNGATED: canon has no `Snippet` in any generation, so the 404 is true on every artifact. RULED by the founder 2026-09-12

planned
Core
GET
/api/v4/projects/{id}/snippets/{noteable_id}/discussions/{discussion_id}

refuse — no snippet exists in this organization — every shared file in olympus-labs lives in the parthenon repository and no persona has ever posted a snippet; GitLab answers `[]` at 200 for a snippet list and its own 404 for a snippet id that names nothing. THE ID HALF: no snippet id resolves, so this operation never reaches a collection to be empty of and GitLab's own 404 is the whole answer — `[]` on a snippet's award-emoji or note list would assert that the snippet exists and holds nothing. UNGATED: canon has no `Snippet` in any generation, so the 404 is true on every artifact. RULED by the founder 2026-09-12

planned
Core
GET
/api/v4/projects/{id}/snippets/{noteable_id}/discussions/{discussion_id}/notes

refuse — no snippet exists in this organization — every shared file in olympus-labs lives in the parthenon repository and no persona has ever posted a snippet; GitLab answers `[]` at 200 for a snippet list and its own 404 for a snippet id that names nothing. THE ID HALF: no snippet id resolves, so this operation never reaches a collection to be empty of and GitLab's own 404 is the whole answer — `[]` on a snippet's award-emoji or note list would assert that the snippet exists and holds nothing. UNGATED: canon has no `Snippet` in any generation, so the 404 is true on every artifact. RULED by the founder 2026-09-12

planned
Core
GET
/api/v4/projects/{id}/snippets/{noteable_id}/discussions/{discussion_id}/notes/{note_id}

refuse — no snippet exists in this organization — every shared file in olympus-labs lives in the parthenon repository and no persona has ever posted a snippet; GitLab answers `[]` at 200 for a snippet list and its own 404 for a snippet id that names nothing. THE ID HALF: no snippet id resolves, so this operation never reaches a collection to be empty of and GitLab's own 404 is the whole answer — `[]` on a snippet's award-emoji or note list would assert that the snippet exists and holds nothing. UNGATED: canon has no `Snippet` in any generation, so the 404 is true on every artifact. RULED by the founder 2026-09-12

planned
Core
users6 of 17 served & verified · 7 deviations
GET
/api/v4/user
served & verified
Core
GET
/api/v4/user_counts
served & verified
Core
GET
/api/v4/users
served & verified
Core
GET
/api/v4/users/{id}
served & verified
Core
GET
/api/v4/users/{id}/followers

SERVED 2026-09-12 (hello-17): features.socialGraph derives the follow edges from the review graph - you follow the people whose changes you review most and the people who review yours - in ONE person_follow table both git hosts read, which is the cross-host parity this reason has committed to since the classification run. Ordered by the follower's canonical id, the order both hosts page. On every older artifact it answers the named hello-17 generation gap, because [] would assert that nobody here follows anybody

served & verified
Core
GET
/api/v4/users/{id}/following

SERVED 2026-09-12 (hello-17): the other end of the same directed edge, off the same person_follow table, ordered by the followee's canonical id. Every edge a person's /followers names is visible from that follower's /following, and no edge names the same person twice

served & verified
Core
GET
/api/v4/user/emails

SERVED 2026-09-12 (coverage wave W3): reader.credentialsForPerson(athena, 'email') - athena is the caller GET /api/v4/user reports, and GitHub's /user/emails has been green off the same person_credential rows since the hello-16 credential wave. `confirmed_at` is the credential's own instant and is sent only on an ACTIVE row: GitLab's field records when the address was confirmed to belong to its owner, canon records when the person came to hold it, and a row canon ever revokes loses the field in the same edit rather than keeping a confirmation that is no longer true. `id` is the integer real GitLab sends where the vendored component types it string, so the row is a deviation. Behind the hello-16 credential gate, not the hello-17 caller gate: the caller has held an address since hello-16, which is a different question from whether she holds a key. The old reason - canon's person carries no email list - was falsified by that generation

deviation
Core
GET
/api/v4/user/emails/{email_id}

SERVED 2026-09-12 (coverage wave W3): the by-id twin of /api/v4/user/emails, addressed by gitlabId(credential.id); conformance asserts the list and the by-id row render one address the same way. Same hello-16 gate, same `id` deviation, same falsified reason

deviation
Core
GET
/api/v4/user/preferences

the caller's own four booleans, off the four columns hello-20's F13 appended to `person` - `view_diffs_file_by_file`, `show_whitespace_in_diffs`, `pass_user_identities_to_ci_jwt` and `policy_advanced_editor`, which is exactly the set `APIEntitiesUserPreferences` declares. FOUR AND NOT THREE deliberately: three of four would have left one to be invented at render time, which is the thing the column exists to prevent. The caller is the one identity every token on this replica resolves to, the same person `/api/v4/user` answers as, so the two surfaces cannot describe different people. `id` IS THE PREFERENCE RECORD'S KEY AND NOT THE PERSON'S - on real GitLab a `user_preferences` row has a primary key of its own and it is a different number from `user_id`, so it is minted from the preference's own canonical name rather than from the person, and conformance fails if the two are ever equal. Conformance also fails if every person in the roster carries the same four values, because each is read off something that person did and a constant would be a fold that distinguishes nobody. DEVIATION: the vendored component types all six fields `string`; real GitLab exposes the `user_preferences` columns directly - two integers and four booleans, which is what its own `db/structure.sql` declares - so the row is `deviation` with the validator substring recorded rather than green (invariant #2 mirrors the provider, not its documentation). SERVED 2026-09-14 (hello-20 `providerTails`), gated on hasProviderTails(); an older artifact answers the named hello-20 preference gap, and there is no honest empty because the response is an object of four flags and any default would be a placeholder.

deviation
Core
GET
/api/v4/user/status

nobody in olympus-labs sets a status message — canon's person carries no availability, emoji or status text; GitLab's own answer for a user who has set none is a 200 with every field null. NOT A COLLECTION: `APIEntitiesUserStatus` is an object, so the honest empty is the all-null object rather than `[]`. All five fields are a `deviation` against the vendored component, which types each of them `string` with no `nullable` while GitLab renders `Entities::UserStatus` over a person who has set no status and every field comes out nil. UNGATED: `canon.Person` (packages/canon/src/entities.ts:79) has carried id, archetype, teams, utcOffsetMinutes, isBot and member in every generation and has grown only `aiTool`/`extraAiTools` since, so no artifact of this universe has ever held an availability, an emoji or a status text. RULED by the founder 2026-09-12

deviation
Core
GET
/api/v4/users/{id}/associations_count
deviation
Core
GET
/api/v4/users/{user_id}/memberships
deviation
Core
GET
/api/v4/users/{user_id}/status

nobody in olympus-labs sets a status message — canon's person carries no availability, emoji or status text; GitLab's own answer for a user who has set none is a 200 with every field null. NOT A COLLECTION: `APIEntitiesUserStatus` is an object, so the honest empty is the all-null object rather than `[]`. All five fields are a `deviation` against the vendored component, which types each of them `string` with no `nullable` while GitLab renders `Entities::UserStatus` over a person who has set no status and every field comes out nil. UNGATED: `canon.Person` (packages/canon/src/entities.ts:79) has carried id, archetype, teams, utcOffsetMinutes, isBot and member in every generation and has grown only `aiTool`/`extraAiTools` since, so no artifact of this universe has ever held an availability, an emoji or a status text. RULED by the founder 2026-09-12

deviation
Core
GET
/api/v4/user/activities

refuse — GitLab restricts the instance-wide last-activity index to administrators

planned
Core
GET
/api/v4/user/support_pin

refuse — a support PIN authenticates a human to GitLab Support; SandboxAPIs has no support desk and issuing one would be a credential-shaped lie

planned
Core
GET
/api/v4/users/{id}/emails

refuse — GitLab restricts another user's addresses to instance administrators and this host authenticates as an ordinary member of one group, so GitLab's own 403 is the whole answer. THE OLD REASON WAS FALSIFIED RATHER THAN UPHELD: it said canon's person carries no email list, and hello-16 gave it one — person_credential kind `email`, which /api/v4/user/emails serves today — so the blocker is the TOKEN, not the canon, which is the same reading that puts the whole /api/v4/admin/* family in class 1 of the 2026-09-01 wave. /api/v4/user/emails is deliberately untouched and still served: a caller reading their OWN addresses needs no administrator, and that distinction is what this row turns on. UNGATED: no generation of this universe authenticates as an instance administrator. RULED by the founder 2026-09-12 (round 20)

planned
Core
GET
/api/v4/users/{id}/support_pin

refuse — a support PIN authenticates a human to GitLab Support; SandboxAPIs has no support desk and issuing one would be a credential-shaped lie

planned
Core
award emoji12 of 16 served & verified
GET
/api/v4/groups/{id}/epics/{epic_iid}/award_emoji

content_reaction has carried target_kind 'epic' since hello-15; the block was that this renderer served no epic route, and hello-16's portfolio layer gives the epic one. reactionsFor('epic', id) under a resolvable epic_iid. Two gates in the order a caller meets them: no epic before hello-16 (noEpics), no reactions before hello-15 (noAwardEmoji). SERVED 2026-09-09 (hello-16)

served & verified
Long tail
GET
/api/v4/groups/{id}/epics/{epic_iid}/award_emoji/{award_id}

content_reaction has carried target_kind 'epic' since hello-15; the block was that this renderer served no epic route, and hello-16's portfolio layer gives the epic one. reactionsFor('epic', id) under a resolvable epic_iid. Two gates in the order a caller meets them: no epic before hello-16 (noEpics), no reactions before hello-15 (noAwardEmoji). SERVED 2026-09-09 (hello-16)

served & verified
Long tail
GET
/api/v4/groups/{id}/epics/{epic_iid}/notes/{note_id}/award_emoji

SERVED 2026-10-27 (hello-23 noteReactions, decisions/2026-10-27-2221 and decisions/2026-10-27-2310): the content_reaction rows of target_kind entityNote on this note (reader.noteReactionsFor), as APIEntitiesAwardEmoji with awardable_type Note. The pass ran over every epic note at the issue-comment rate, so a note with none answers [] at 200 as a fact; the 90 d seed-42 recipe draws none and the 30 d recipe six, which is where conformance validates the non-empty case. Older artifacts answer the hello-23 generation gap

served & verified
Long tail
GET
/api/v4/groups/{id}/epics/{epic_iid}/notes/{note_id}/award_emoji/{award_id}

SERVED 2026-10-27 (hello-23 noteReactions, decisions/2026-10-27-2221 and decisions/2026-10-27-2310): one award off the list beside it, byte-identical to its list row; an award id the note does not carry is GitLab's 404 Award Emoji Not Found, which on the 90 d recipe is every id. Older artifacts answer the hello-23 generation gap

served & verified
Long tail
GET
/api/v4/projects/{id}/issues/{issue_iid}/award_emoji

ContentReaction is canon as of hello-15 (features.contentReactions): one row per person per gesture per target, with an id, a discriminated target and an instant. reactionsFor() answers it and renderer-gitlab's glAwardEmoji renders GitLab's AwardEmoji envelope over it - the same rows GitHub serves as reactions and Azure DevOps as comment reactions

served & verified
Long tail
GET
/api/v4/projects/{id}/issues/{issue_iid}/award_emoji/{award_id}

ContentReaction is canon as of hello-15 (features.contentReactions): one row per person per gesture per target, with an id, a discriminated target and an instant. reactionsFor() answers it and renderer-gitlab's glAwardEmoji renders GitLab's AwardEmoji envelope over it - the same rows GitHub serves as reactions and Azure DevOps as comment reactions

served & verified
Long tail
GET
/api/v4/projects/{id}/issues/{issue_iid}/notes/{note_id}/award_emoji

ContentReaction is canon as of hello-15 (features.contentReactions): one row per person per gesture per target, with an id, a discriminated target and an instant. reactionsFor() answers it and renderer-gitlab's glAwardEmoji renders GitLab's AwardEmoji envelope over it - the same rows GitHub serves as reactions and Azure DevOps as comment reactions

served & verified
Long tail
GET
/api/v4/projects/{id}/issues/{issue_iid}/notes/{note_id}/award_emoji/{award_id}

ContentReaction is canon as of hello-15 (features.contentReactions): one row per person per gesture per target, with an id, a discriminated target and an instant. reactionsFor() answers it and renderer-gitlab's glAwardEmoji renders GitLab's AwardEmoji envelope over it - the same rows GitHub serves as reactions and Azure DevOps as comment reactions

served & verified
Long tail
GET
/api/v4/projects/{id}/merge_requests/{merge_request_iid}/award_emoji

ContentReaction is canon as of hello-15 (features.contentReactions): one row per person per gesture per target, with an id, a discriminated target and an instant. reactionsFor() answers it and renderer-gitlab's glAwardEmoji renders GitLab's AwardEmoji envelope over it - the same rows GitHub serves as reactions and Azure DevOps as comment reactions

served & verified
Long tail
GET
/api/v4/projects/{id}/merge_requests/{merge_request_iid}/award_emoji/{award_id}

ContentReaction is canon as of hello-15 (features.contentReactions): one row per person per gesture per target, with an id, a discriminated target and an instant. reactionsFor() answers it and renderer-gitlab's glAwardEmoji renders GitLab's AwardEmoji envelope over it - the same rows GitHub serves as reactions and Azure DevOps as comment reactions

served & verified
Long tail
GET
/api/v4/projects/{id}/merge_requests/{merge_request_iid}/notes/{note_id}/award_emoji

ContentReaction is canon as of hello-15 (features.contentReactions): one row per person per gesture per target, with an id, a discriminated target and an instant. reactionsFor() answers it and renderer-gitlab's glAwardEmoji renders GitLab's AwardEmoji envelope over it - the same rows GitHub serves as reactions and Azure DevOps as comment reactions

served & verified
Long tail
GET
/api/v4/projects/{id}/merge_requests/{merge_request_iid}/notes/{note_id}/award_emoji/{award_id}

ContentReaction is canon as of hello-15 (features.contentReactions): one row per person per gesture per target, with an id, a discriminated target and an instant. reactionsFor() answers it and renderer-gitlab's glAwardEmoji renders GitLab's AwardEmoji envelope over it - the same rows GitHub serves as reactions and Azure DevOps as comment reactions

served & verified
Long tail
GET
/api/v4/projects/{id}/snippets/{snippet_id}/award_emoji

refuse — no snippet exists in this organization — every shared file in olympus-labs lives in the parthenon repository and no persona has ever posted a snippet; GitLab answers `[]` at 200 for a snippet list and its own 404 for a snippet id that names nothing. THE ID HALF: no snippet id resolves, so this operation never reaches a collection to be empty of and GitLab's own 404 is the whole answer — `[]` on a snippet's award-emoji or note list would assert that the snippet exists and holds nothing. UNGATED: canon has no `Snippet` in any generation, so the 404 is true on every artifact. RULED by the founder 2026-09-12

deferred
Long tail
GET
/api/v4/projects/{id}/snippets/{snippet_id}/award_emoji/{award_id}

refuse — no snippet exists in this organization — every shared file in olympus-labs lives in the parthenon repository and no persona has ever posted a snippet; GitLab answers `[]` at 200 for a snippet list and its own 404 for a snippet id that names nothing. THE ID HALF: no snippet id resolves, so this operation never reaches a collection to be empty of and GitLab's own 404 is the whole answer — `[]` on a snippet's award-emoji or note list would assert that the snippet exists and holds nothing. UNGATED: canon has no `Snippet` in any generation, so the 404 is true on every artifact. RULED by the founder 2026-09-12

deferred
Long tail
GET
/api/v4/projects/{id}/snippets/{snippet_id}/notes/{note_id}/award_emoji

refuse — no snippet exists in this organization — every shared file in olympus-labs lives in the parthenon repository and no persona has ever posted a snippet; GitLab answers `[]` at 200 for a snippet list and its own 404 for a snippet id that names nothing. THE ID HALF: no snippet id resolves, so this operation never reaches a collection to be empty of and GitLab's own 404 is the whole answer — `[]` on a snippet's award-emoji or note list would assert that the snippet exists and holds nothing. UNGATED: canon has no `Snippet` in any generation, so the 404 is true on every artifact. RULED by the founder 2026-09-12

deferred
Long tail
GET
/api/v4/projects/{id}/snippets/{snippet_id}/notes/{note_id}/award_emoji/{award_id}

refuse — no snippet exists in this organization — every shared file in olympus-labs lives in the parthenon repository and no persona has ever posted a snippet; GitLab answers `[]` at 200 for a snippet list and its own 404 for a snippet id that names nothing. THE ID HALF: no snippet id resolves, so this operation never reaches a collection to be empty of and GitLab's own 404 is the whole answer — `[]` on a snippet's award-emoji or note list would assert that the snippet exists and holds nothing. UNGATED: canon has no `Snippet` in any generation, so the 404 is true on every artifact. RULED by the founder 2026-09-12

deferred
Long tail
repository-files13 of 15 served & verified
GET
/api/v4/projects/{id}/repository/archive

SERVED 2026-09-12 (coverage wave W7): a real archive of the ref's tree, whose members are `reader.treeFiles(commit)` - the same content-addressed blobs `/repository/files/{path}/raw`, `/repository/blobs/{sha}/raw` and `git clone` serve, so a file cannot have one body here and another there. GITLAB'S OWN NAMING, not GitHub's: the single root directory and the download filename are `{project path}-{ref}-{full sha}` from `archive_prefix` (the project's own path segment, the ref as the caller wrote it with slashes flattened, and the sha appended unconditionally because the API passes `append_sha: true`). FORMATS: `tar.gz` (the default - `format ||= 'tar.gz'`), `tar` and `zip` are served, and the three gzip spellings `tgz`/`gz` and any unrecognised `?format=` collapse onto tar.gz exactly as `archive_file_path` does. The five bzip2 spellings answer the coverage 404 naming the format and the reason - node:zlib carries no bzip2 codec and the shared writer has no dependencies - rather than a gzip wearing a `.tar.bz2` name, which is what falling back to the default would have done. A suffix outside GitLab's own `archive_formats_regex` gets GitLab's plain 404, since its route does not match either. `sha` and `ref_type` are answered; `include_lfs_blobs` is answered too and conformance asserts the two values select byte-identical archives, which is true because the file model holds whole file bodies and no LFS pointer; `path` and `exclude_paths` are refused, because a caller who asked for a subfolder and got the whole repository has been answered a different question. Unblocked by the tar/zip writers moving out of packages/renderer-github into `@sandboxapis/archive`, founder-ordered 2026-09-09

served & verified
Core
GET
/api/v4/projects/{id}/repository/blobs/{sha}
served & verified
Core
GET
/api/v4/projects/{id}/repository/blobs/{sha}/raw
served & verified
Core
GET
/api/v4/projects/{id}/repository/compare
served & verified
Core
GET
/api/v4/projects/{id}/repository/contributors
served & verified
Core
GET
/api/v4/projects/{id}/repository/diverging_commits
served & verified
Core
GET
/api/v4/projects/{id}/repository/files/{file_path}
served & verified
Core
HEAD
/api/v4/projects/{id}/repository/files/{file_path}
served & verified
Core
GET
/api/v4/projects/{id}/repository/files/{file_path}/blame
served & verified
Core
HEAD
/api/v4/projects/{id}/repository/files/{file_path}/blame
served & verified
Core
GET
/api/v4/projects/{id}/repository/files/{file_path}/raw
served & verified
Core
GET
/api/v4/projects/{id}/repository/merge_base
served & verified
Core
GET
/api/v4/projects/{id}/repository/tree
served & verified
Core
GET
/api/v4/projects/{id}/repository/changelog

refuse — GitLab renders its changelog template over the commits carrying a `Changelog:` git trailer. Canon writes no trailers — every commit this host serves reports trailers {} — so no commit is ever selectable and there is no changelog to generate; the documented 400 is the refusal. RULED by the founder 2026-09-09

planned
Core
GET
/api/v4/projects/{id}/repository/health

refuse — this reports Gitaly storage verification for a physical repository; the artifact has no Gitaly behind it

planned
Core
notes6 of 14 served & verified
GET
/api/v4/groups/{id}/epics/{noteable_id}/notes

SERVED 2026-09-12 (hello-17): features.entityNotes gives a plan item the planning conversation the storyline already had - entity_note rows keyed to an epic, authored by the people who commented on the issues underneath it. listEntityNotes() answers this list in canon id order, which is chronological within a parent. On every older artifact it answers the named hello-17 generation gap and never [], because an empty note list asserts that this organization plans in silence

served & verified
Core
GET
/api/v4/groups/{id}/epics/{noteable_id}/notes/{note_id}

SERVED 2026-09-12 (hello-17): one epic note by id, rendered by the same glEpicNote the list and the discussions use, so the three surfaces cannot describe one sentence three ways

served & verified
Core
GET
/api/v4/projects/{id}/issues/{noteable_id}/notes
served & verified
Core
GET
/api/v4/projects/{id}/issues/{noteable_id}/notes/{note_id}
served & verified
Core
GET
/api/v4/projects/{id}/merge_requests/{noteable_id}/notes
served & verified
Core
GET
/api/v4/projects/{id}/merge_requests/{noteable_id}/notes/{note_id}
served & verified
Core
GET
/api/v4/groups/{id}/wiki_pages/{noteable_id}/notes

refuse — olympus-labs documents its services in the parthenon repository, not in a wiki — no WikiPage exists at project or group scope; GitLab answers `[]` at 200 for the wiki list and its own 404 for a slug that names nothing. THE SLUG/PAGE-ID HALF: no slug and no wiki_page_meta_id resolves, so GitLab's own 404 is the whole answer — a 200 `[]` on a wiki page's note list would assert that the page exists and nobody commented on it. UNGATED: canon has no WikiPage in any generation. RULED by the founder 2026-09-12

planned
Core
GET
/api/v4/groups/{id}/wiki_pages/{noteable_id}/notes/{note_id}

refuse — olympus-labs documents its services in the parthenon repository, not in a wiki — no WikiPage exists at project or group scope; GitLab answers `[]` at 200 for the wiki list and its own 404 for a slug that names nothing. THE SLUG/PAGE-ID HALF: no slug and no wiki_page_meta_id resolves, so GitLab's own 404 is the whole answer — a 200 `[]` on a wiki page's note list would assert that the page exists and nobody commented on it. UNGATED: canon has no WikiPage in any generation. RULED by the founder 2026-09-12

planned
Core
GET
/api/v4/projects/{id}/snippets/{noteable_id}/notes

refuse — no snippet exists in this organization — every shared file in olympus-labs lives in the parthenon repository and no persona has ever posted a snippet; GitLab answers `[]` at 200 for a snippet list and its own 404 for a snippet id that names nothing. THE ID HALF: no snippet id resolves, so this operation never reaches a collection to be empty of and GitLab's own 404 is the whole answer — `[]` on a snippet's award-emoji or note list would assert that the snippet exists and holds nothing. UNGATED: canon has no `Snippet` in any generation, so the 404 is true on every artifact. RULED by the founder 2026-09-12

planned
Core
GET
/api/v4/projects/{id}/snippets/{noteable_id}/notes/{note_id}

refuse — no snippet exists in this organization — every shared file in olympus-labs lives in the parthenon repository and no persona has ever posted a snippet; GitLab answers `[]` at 200 for a snippet list and its own 404 for a snippet id that names nothing. THE ID HALF: no snippet id resolves, so this operation never reaches a collection to be empty of and GitLab's own 404 is the whole answer — `[]` on a snippet's award-emoji or note list would assert that the snippet exists and holds nothing. UNGATED: canon has no `Snippet` in any generation, so the 404 is true on every artifact. RULED by the founder 2026-09-12

planned
Core
GET
/api/v4/projects/{id}/vulnerabilities/{noteable_id}/notes

refuse — no vulnerability exists in this organization — canon carries no SecurityFinding, so no vulnerability id resolves, and GitLab's own 404 for an unknown vulnerability is the whole answer. BOTH HALVES REFUSE rather than one of them answering `[]`: the note list hangs off a `{noteable_id}` that names a VULNERABILITY, and a 200 `[]` there would assert the vulnerability exists and nobody commented on it. UNGATED: packages/canon/src/notes.ts:52 records `SecurityFinding` as the entity hello-17's entity-note domain is still missing, so no generation of this universe holds one. RULED by the founder 2026-09-12

planned
Core
GET
/api/v4/projects/{id}/vulnerabilities/{noteable_id}/notes/{note_id}

refuse — no vulnerability exists in this organization — canon carries no SecurityFinding, so no vulnerability id resolves, and GitLab's own 404 for an unknown vulnerability is the whole answer. BOTH HALVES REFUSE rather than one of them answering `[]`: the note list hangs off a `{noteable_id}` that names a VULNERABILITY, and a 200 `[]` there would assert the vulnerability exists and nobody commented on it. UNGATED: packages/canon/src/notes.ts:52 records `SecurityFinding` as the entity hello-17's entity-note domain is still missing, so no generation of this universe holds one. RULED by the founder 2026-09-12

planned
Core
GET
/api/v4/projects/{id}/wiki_pages/{noteable_id}/notes

refuse — olympus-labs documents its services in the parthenon repository, not in a wiki — no WikiPage exists at project or group scope; GitLab answers `[]` at 200 for the wiki list and its own 404 for a slug that names nothing. THE SLUG/PAGE-ID HALF: no slug and no wiki_page_meta_id resolves, so GitLab's own 404 is the whole answer — a 200 `[]` on a wiki page's note list would assert that the page exists and nobody commented on it. UNGATED: canon has no WikiPage in any generation. RULED by the founder 2026-09-12

planned
Core
GET
/api/v4/projects/{id}/wiki_pages/{noteable_id}/notes/{note_id}

refuse — olympus-labs documents its services in the parthenon repository, not in a wiki — no WikiPage exists at project or group scope; GitLab answers `[]` at 200 for the wiki list and its own 404 for a slug that names nothing. THE SLUG/PAGE-ID HALF: no slug and no wiki_page_meta_id resolves, so GitLab's own 404 is the whole answer — a 200 `[]` on a wiki page's note list would assert that the page exists and nobody commented on it. UNGATED: canon has no WikiPage in any generation. RULED by the founder 2026-09-12

planned
Core
epics4 of 13 served & verified · 8 deviations
GET
/api/v4/groups/{id}/epic_boards

reader.boardScopes('epic') and reader.boardColumns('epic', epic) — the same read one scope kind over, and the place the domain's rule is visible: five of the twenty-seven epics have a board at 90 days, because an epic nobody ever filed an issue against has no view to configure. Conformance asserts that equivalence in both directions over every epic. The board's id and name are the EPIC's, hide_backlog_list and hide_closed_list are folds over its own columns (epic-013 and epic-026 hide their backlog; the other three do not), `lists` is `[]` and the board's scope fields are omitted for the project list's reason: nobody configured a label list. The subgroup owns no plan and answers `[]`. SERVED 2026-09-14 (hello-20 `boards`): gated on reader.hasBoards(); an older artifact answers the named hello-20 gap rather than `[]`

served & verified
Next
GET
/api/v4/groups/{id}/epic_boards/{board_id}

the by-id twin of the group's epic-board list, off the same reader.boardScopes('epic') membership test and addressed by gitlabId of the epic the board is. An epic with no issues has no board and its id is GitLab's 404 Board Not Found; so is `%ZZ` (PR #481) rather than a 500. SERVED 2026-09-14 (hello-20 `boards`): gated on reader.hasBoards(); an older artifact answers the named hello-20 gap after the group has resolved

served & verified
Next
GET
/api/v4/groups/{id}/epic_boards/{board_id}/lists

The issue board's ruling on the epic board, and the vendored component makes it no easier: APIEntitiesEpicBoardsList carries `label`, `position` and a `list_type` whose published members are backlog, closed and label — and the operation excludes the first two in its own description, so every servable list is a LABEL list over a label this group publishes, and no label in this universe is a workflow state. Nobody configured one, so `[]` at 200 under an epic board that RESOLVES is GitLab's own answer and matches the `lists: []` the board publishes. An epic nobody filed an issue against has no board at all and its id is GitLab's 404 Board Not Found. RULED 2026-09-14 on the hello-20 GitLab history wave.

served & verified
Next
GET
/api/v4/groups/{id}/related_epic_links

SERVED 2026-10-27 (hello-23 epicLinks, decisions/2026-10-27-2220 and decisions/2026-10-27-2310): every epic_link row as GitLab's documented link object (id, created_at, updated_at, link_type from the source's side, source_epic, target_epic; doc/api/linked_epics.md at the pinned GitLab commit, pinned under gitlab.docs), which shares its top-level members with the vendored APIEntitiesRelatedEpic. The created and updated bounds filter on the link's instant. A subgroup holds no epic and lists none. Older artifacts answer the hello-23 generation gap

served & verified
Next
GET
/api/v4/groups/{id}/-/epics

EpicRow carries the portfolio columns from hello-16 (features.portfolio): author, state, description, start/due/closed epochs and one level of parent/child. reader.hasPortfolio() + listEpics()/epic()/epicChildren()/epicIssues() derive GitLab's Epic entity; older pins answer the noEpics generation-gap 404 rather than an empty plan. SERVED 2026-09-09 (hello-16)

deviation
Next
GET
/api/v4/groups/{id}/-/epics/{epic_iid}

EpicRow carries the portfolio columns from hello-16 (features.portfolio): author, state, description, start/due/closed epochs and one level of parent/child. reader.hasPortfolio() + listEpics()/epic()/epicChildren()/epicIssues() derive GitLab's Epic entity; older pins answer the noEpics generation-gap 404 rather than an empty plan. SERVED 2026-09-09 (hello-16)

deviation
Next
GET
/api/v4/groups/{id}/-/epics/{epic_iid}/issues

EpicRow carries the portfolio columns from hello-16 (features.portfolio): author, state, description, start/due/closed epochs and one level of parent/child. reader.hasPortfolio() + listEpics()/epic()/epicChildren()/epicIssues() derive GitLab's Epic entity; older pins answer the noEpics generation-gap 404 rather than an empty plan. SERVED 2026-09-09 (hello-16)

deviation
Next
GET
/api/v4/groups/{id}/(-/)epics/{epic_iid}/epics

the CHILD epics of an epic - GitLab's Epics::EpicLinks, which the vendored summary labels "related epics" while its POST twin says "relate epic to a PARENT". epic.parent/level are canon from hello-16 (features.portfolio) and reader.epicChildren(id) answers it directly; a leaf reports the empty array and an older pin the noEpics generation-gap 404. The 2026-09-01 reason was right that parentage was not modelled and is no longer true; the SIBLING relation it also names is a different relation, which hello-23 models separately as epic_link and serves at groups/{id}/epics/{epic_iid}/related_epics (re-worded 2026-10-27). SERVED 2026-09-09 (hello-16)

deviation
Next
GET
/api/v4/groups/{id}/epics

EpicRow carries the portfolio columns from hello-16 (features.portfolio): author, state, description, start/due/closed epochs and one level of parent/child. reader.hasPortfolio() + listEpics()/epic()/epicChildren()/epicIssues() derive GitLab's Epic entity; older pins answer the noEpics generation-gap 404 rather than an empty plan. SERVED 2026-09-09 (hello-16)

deviation
Next
GET
/api/v4/groups/{id}/epics/{epic_iid}

EpicRow carries the portfolio columns from hello-16 (features.portfolio): author, state, description, start/due/closed epochs and one level of parent/child. reader.hasPortfolio() + listEpics()/epic()/epicChildren()/epicIssues() derive GitLab's Epic entity; older pins answer the noEpics generation-gap 404 rather than an empty plan. SERVED 2026-09-09 (hello-16)

deviation
Next
GET
/api/v4/groups/{id}/epics/{epic_iid}/issues

EpicRow carries the portfolio columns from hello-16 (features.portfolio): author, state, description, start/due/closed epochs and one level of parent/child. reader.hasPortfolio() + listEpics()/epic()/epicChildren()/epicIssues() derive GitLab's Epic entity; older pins answer the noEpics generation-gap 404 rather than an empty plan. SERVED 2026-09-09 (hello-16)

deviation
Next
GET
/api/v4/groups/{id}/epics/{epic_iid}/related_epics

SERVED 2026-10-27 (hello-23 epicLinks, decisions/2026-10-27-2220 and decisions/2026-10-27-2310): the epic_link rows this epic is either end of (reader.epicLinksFor), each the other epic as APIEntitiesRelatedEpic with related_epic_link_id, link_type read from this epic's side (relates_to, blocks, is_blocked_by) and the link's own instant as link_created_at and link_updated_at. A link joins adjacent sibling features under one parent epic. Older artifacts answer the hello-23 generation gap

deviation
Next
GET
/api/v4/groups/{id}/epic_boards/{board_id}/lists/{list_id}

refuse — ENTAILED by the empty beside it: the epic board has no label list, so no list id resolves and GitLab's own `404 List Not Found` is the whole answer. The GROUP and the BOARD resolve first, and `%ZZ` is a 404 rather than a 500 (PR #481). RULED 2026-09-14 on the hello-20 GitLab history wave.

deferred
Next
pipelines10 of 13 served & verified · 3 deviations
GET
/api/v4/projects/{id}/pipeline_schedules

SERVED 2026-09-12 (coverage wave W8) (hello-15 `ciConfig`): reader.listWorkflowSchedules(repo) over canon.WorkflowSchedule. `description` and `ref` are the schedule's own description and branch, `owner` is the actor it runs as, `created_at` and `updated_at` its two epochs, `cron_timezone` UTC because canon's hours ARE UTC, and `cron` is the (per_hour, hours_of_day, days_of_week) triple re-encoded LOSSLESSLY — every value in the triple appears in the expression and nothing else does, which conformance checks field by field rather than against a literal. `active` is true because every schedule here really starts runs, and an inactive schedule that had started 398 pipelines would contradict its own pipeline list. `next_run_at` is walked forward from THE ARTIFACT'S ANCHOR, never Date.now(): a frozen pin must print the same instant in a year's time. `inputs` and `variables` are omitted — canon carries no schedule variable of any kind. GENERATION-GATED: an older artifact answers the CI-configuration gap rather than `[]`, which would say nothing here runs unattended. `scope=inactive` is a real filter that truthfully matches nothing, the posture `pipelines?status=` takes for a value this generator never produces

served & verified
Next
GET
/api/v4/projects/{id}/pipeline_schedules/{pipeline_schedule_id}/pipelines

SERVED 2026-09-12 (coverage wave W8) (hello-15 `ciConfig`): the runs one schedule started — reader.runsForSchedule(id), reading `workflow_run.schedule` directly. hello-15's schedule starts REAL runs carrying event `schedule`, and they drive the RELEASE workflow while `/projects/{id}/pipelines` lists the `ci` one — so they are deliberately NOT in that feed, and the 2026-09-09 reason that said they were is corrected here. WHAT WOULD HAVE BEEN AN INVARIANT-#5 BREAK IS NAMING A PIPELINE THIS HOST THEN REFUSES TO FETCH, and this wave closed it: `findPipeline` resolves ANY run of the project rather than only a `ci` one, so every id in this list answers at `/pipelines/{pipeline_id}` and its jobs answer too, which conformance drives row by row. The LIST is deliberately left alone — widening it to all 398 runs would move bytes every frozen pin already serves. Generation-gated with the rest of the family

served & verified
Next
GET
/api/v4/projects/{id}/pipeline_schedules/{pipeline_schedule_id}/variables/{key}

the nightly release's publishing credential, off reader.scheduleCiBindings(schedule) — a job that runs unattended cannot be handed its credential by a person, which is why the binding is stored on the schedule rather than pasted into a run. THE SAME BYTES ITS OWN RUNS PUBLISH: conformance compares this response to /pipelines/{pipeline_id}/variables on a run this schedule started, so one fact cannot be answered two ways. GitLab's own `404 Not found` for an unknown schedule or key, `%ZZ` included. SERVED 2026-09-12 (hello-19 `ciBindings`): the 2026-09-09 re-scope asked for a variable table and named the constraint that would make one honest — a masked value is a secret canon stores none of. hello-19's `ci_binding` is that table, built on exactly that constraint: one row, one `masked` boolean, and NO VALUE IN ANY COLUMN on a masked row (`compiler/src/hello19.test.ts` asserts `COUNT(*) WHERE masked = 1 AND value IS NOT NULL` is 0 on every compiled artifact). A MASKED BINDING RENDERS `hidden: true` AND SHIPS NO `value` KEY, which is GitLab's own vocabulary rather than a convenience: `masked` means blanked out of JOB LOGS and `hidden` (created as `masked_and_hidden`) means THE API NEVER RETURNS THE VALUE, so `masked: true, hidden: false` with the key dropped would be a shape real GitLab never sends. An unmasked binding's `value` is the artifact row `ci_binding.value_source` names — the collector's endpoint, the default branch, the orb document's tree path — resolved at compile time, so the setting and the thing it configures cannot drift apart. `variable_type` is `env_var` on every row, which agrees with /api/v4/projects/{id}/secure_files being ruled empty: a `file` variable IS a secure file. `raw` and `description` are OMITTED rather than defaulted — neither is required by APIEntitiesCiVariable and neither is a fact canon holds (the depth-hooks.ts rule, one wave earlier). Conformance drives each row against the vendored schema AND against the reader accessor the renderer must have used, walks the pages with the headers asserted, and fails if a masked row ever carries a value. Gated on hasCiBindings(); an older artifact answers the named hello-19 CI-variable gap, because [] there would say this organization configures its pipelines with nothing while the same artifact publishes a CI document, a nightly schedule and deployments to two environments

served & verified
Next
GET
/api/v4/projects/{id}/pipelines
served & verified
Next
GET
/api/v4/projects/{id}/pipelines/{pipeline_id}
served & verified
Next
GET
/api/v4/projects/{id}/pipelines/{pipeline_id}/bridges

a bridge is a job that triggers a downstream pipeline; this universe's CI is a single project's pipeline with no downstream trigger, so the bridge list is truthfully empty

served & verified
Next
GET
/api/v4/projects/{id}/pipelines/{pipeline_id}/jobs
served & verified
Next
GET
/api/v4/projects/{id}/pipelines/{pipeline_id}/trigger_jobs

GitLab's other name for the same thing the bridges row carries, held to the same fact: this CI triggers no downstream pipeline

served & verified
Next
GET
/api/v4/projects/{id}/pipelines/{pipeline_id}/variables

the variables ONE RUN WAS STARTED WITH — a genuinely two-branch answer, and `workflow_run.schedule` is the column that tells the branches apart without a join. A run a schedule started carries that schedule's bindings (reader.scheduleCiBindings); a run somebody pushed was started with none and answers [] at 200, which is the true answer rather than a gap. 86 of this universe's 350 runs at 90 days carry a schedule, so conformance drives both branches and fails if either becomes unreachable. The pipeline id resolves FIRST, on every generation: an id this project does not have is GitLab's own `404 Not found`. SERVED 2026-09-12 (hello-19 `ciBindings`): the 2026-09-09 re-scope asked for a variable table and named the constraint that would make one honest — a masked value is a secret canon stores none of. hello-19's `ci_binding` is that table, built on exactly that constraint: one row, one `masked` boolean, and NO VALUE IN ANY COLUMN on a masked row (`compiler/src/hello19.test.ts` asserts `COUNT(*) WHERE masked = 1 AND value IS NOT NULL` is 0 on every compiled artifact). A MASKED BINDING RENDERS `hidden: true` AND SHIPS NO `value` KEY, which is GitLab's own vocabulary rather than a convenience: `masked` means blanked out of JOB LOGS and `hidden` (created as `masked_and_hidden`) means THE API NEVER RETURNS THE VALUE, so `masked: true, hidden: false` with the key dropped would be a shape real GitLab never sends. An unmasked binding's `value` is the artifact row `ci_binding.value_source` names — the collector's endpoint, the default branch, the orb document's tree path — resolved at compile time, so the setting and the thing it configures cannot drift apart. `variable_type` is `env_var` on every row, which agrees with /api/v4/projects/{id}/secure_files being ruled empty: a `file` variable IS a secure file. `raw` and `description` are OMITTED rather than defaulted — neither is required by APIEntitiesCiVariable and neither is a fact canon holds (the depth-hooks.ts rule, one wave earlier). Conformance drives each row against the vendored schema AND against the reader accessor the renderer must have used, walks the pages with the headers asserted, and fails if a masked row ever carries a value. Gated on hasCiBindings(); an older artifact answers the named hello-19 CI-variable gap, because [] there would say this organization configures its pipelines with nothing while the same artifact publishes a CI document, a nightly schedule and deployments to two environments

served & verified
Next
GET
/api/v4/projects/{id}/pipelines/latest
served & verified
Next
GET
/api/v4/projects/{id}/pipeline_schedules/{pipeline_schedule_id}

SERVED 2026-09-12 (coverage wave W8) (hello-15 `ciConfig`): reader.listWorkflowSchedules(repo) over canon.WorkflowSchedule. `description` and `ref` are the schedule's own description and branch, `owner` is the actor it runs as, `created_at` and `updated_at` its two epochs, `cron_timezone` UTC because canon's hours ARE UTC, and `cron` is the (per_hour, hours_of_day, days_of_week) triple re-encoded LOSSLESSLY — every value in the triple appears in the expression and nothing else does, which conformance checks field by field rather than against a literal. `active` is true because every schedule here really starts runs, and an inactive schedule that had started 398 pipelines would contradict its own pipeline list. `next_run_at` is walked forward from THE ARTIFACT'S ANCHOR, never Date.now(): a frozen pin must print the same instant in a year's time. `inputs` is omitted — canon carries no schedule input. GENERATION-GATED: an older artifact answers the CI-configuration gap rather than `[]`, which would say nothing here runs unattended. The detail shape adds `last_pipeline`, and it is the newest run THIS SCHEDULE started (reader.runsForSchedule) rather than the project's newest pipeline — conformance asserts every other field is byte-identical to the list row's. VARIABLES SERVED 2026-09-13 (hello-19 `ciBindings`), AND THIS ROW IS A `deviation` BECAUSE OF THEM: the detail shape also publishes the schedule's CI/CD variables, off the SAME glScheduleVariables the by-key row /pipeline_schedules/{id}/variables/{key} reads, so the two surfaces cannot disagree about one fact — conformance compares the block to that URL's bytes element for element. A masked binding renders `masked: true, hidden: true` and ships NO `value` key, here as everywhere: no ci_binding row carries a value when masked = 1 (compiler/src/hello19.test.ts asserts it as SQL over every compiled artifact). THE DEVIATION IS THE VENDORED DOCUMENT'S: APIEntitiesCiPipelineScheduleDetails types `variables` as a SINGLE APIEntitiesCiVariable object while real GitLab sends an ARRAY — `Ci::PipelineSchedule has_many :variables`, `Entities::Ci::PipelineScheduleDetails` exposes them `using: Entities::Ci::Variable` over that association, and GitLab's own published example for this operation shows `"variables": [{...}]`. Invariant #2 mirrors the provider rather than its documentation, so the array is served and the one validator error is declared a spec bug (SCHEDULE_VARIABLES_SPEC_BUGS); the vendored file is upstream-verbatim and sha256-pinned, so it is NOT edited — the correction and the evidence live in conformance and in coverage/provider-pins.yaml. Same class as `approved_by` on the code-review component. The `variables` KEY IS ABSENT rather than `[]` on a pre-hello-19 artifact: [] would say this organization hands its nightly release nothing while the same artifact publishes a CI document, a schedule that starts real runs and deployments to two environments, and a whole-response coverage 404 would break invariant #2 on every frozen pin, which fetches this row and get GitLab's 200. gitlab/hello19.test.ts asserts the row gains exactly that one key and nothing else

deviation
Next
GET
/api/v4/projects/{id}/pipelines/{pipeline_id}/test_report

SERVED 2026-09-12 (coverage wave W8) (hello-15 `ciConfig`): reader.testResultsForJob(job) over canon.TestCase/TestResult, folded PER JOB — GitLab keys a test report's suites by the BUILD that reported them, which is what makes one suite per job the mirror rather than a choice. canon spells a failure "failure" and GitLab spells it "failed"; the other three statuses agree. `system_output` is the reporter's message and is absent on a pass, because canon stores NULL there rather than an empty string. `file`, `stack_trace`, `recent_failures` and `attachment_url` are omitted — canon records none of them. The durations are FLOAT seconds as real GitLab sends them while both vendored components type them `integer`, so the row is a `deviation` rather than rounding canon's millisecond durations away. Generation-gated: an older artifact answers the CI-configuration gap rather than a report of no tests. Conformance recomputes every counter from the artifact and asserts each served case is one canon DECLARES, with canon's own classname — a test name invented here would be a test nobody wrote

deviation
Next
GET
/api/v4/projects/{id}/pipelines/{pipeline_id}/test_report_summary

SERVED 2026-09-12 (coverage wave W8) (hello-15 `ciConfig`): reader.testResultsForJob(job) over canon.TestCase/TestResult, folded PER JOB — GitLab keys a test report's suites by the BUILD that reported them, which is what makes one suite per job the mirror rather than a choice. canon spells a failure "failure" and GitLab spells it "failed"; the other three statuses agree. `system_output` is the reporter's message and is absent on a pass, because canon stores NULL there rather than an empty string. `file`, `stack_trace`, `recent_failures` and `attachment_url` are omitted — canon records none of them. The durations are FLOAT seconds as real GitLab sends them while both vendored components type them `integer`, so the row is a `deviation` rather than rounding canon's millisecond durations away. Generation-gated: an older artifact answers the CI-configuration gap rather than a report of no tests. The SAME fold, totals only: the summary carries `build_ids` and no `test_cases`, which is the whole difference between the two operations and the reason both exist. Conformance asserts the two endpoints agree counter for counter — two different totals for one pipeline would be this host describing two universes of tests. `test_suites` is a `deviation`, the vendored component typing the collection as one object

deviation
Next
graphql12 of 12 served & verified
GQL
branches-tags

SERVED 2026-09-12 (coverage wave W3): Project.repository's rootRef, branchCount, tagCount and branchNames(searchPattern,offset,limit) - which is the WHOLE of GitLab's branch surface in GraphQL: its schema publishes no branch object, and mirroring that rather than inventing a richer type is invariant #2. Tags are reached the way GitLab reaches them, through Release. The counters are asserted equal to what `/repository/branches` and `/repository/tags` list

served & verified
Core
GQL
commits

SERVED 2026-09-12 (coverage wave W3): Project.repository.commit(ref) and commits(ref, first, after). Every field is shapeCommit's own value for the same row, so a client cannot read one sha, title or date here and another from `/repository/commits`; the connection walks the SAME ancestry `/repository/commits?ref_name=` walks rather than every commit in the table. `description` is the message below the title and null for a one-liner, which is GitLab's own reading

served & verified
Core
GQL
groups
served & verified
Core
GQL
issues
served & verified
Core
GQL
labels

SERVED 2026-09-12 (coverage wave W3): Project.label(title) and labels(first, after, title). REST's `name` is GraphQL's `title` and the colours are glLabel's own two strings; `createdAt`/`updatedAt` are OMITTED rather than derived, because canon's label row carries no instant at all and GitLab types both non-null - a subset may omit a field, and inventing a creation date for a label would be a placeholder

served & verified
Core
GQL
merge-requests
served & verified
Core
GQL
milestones

SERVED 2026-09-12 (coverage wave W3): Project.milestones(first, after, state, title), every field glMilestone's own value for the same row - including the two dates ArtifactReader.milestoneTimes derives, so GraphQL and `/milestones` cannot print different ones. `projectMilestone` is true and the two group flags false because canon's `milestone.repo` is never null

served & verified
Core
GQL
projects
served & verified
Core
GQL
releases

SERVED 2026-09-12 (coverage wave W3): Project.release(tagName) and releases(first, after), every field glRelease's own value for the same row, with `commit` resolving to the same Commit type the repository serves so a release's tag commit is fetchable from either dialect

served & verified
Core
GQL
repository-files

SERVED 2026-09-12 (coverage wave W3): Repository.tree(path, recursive, ref) with its Blob and TreeEntry connections, and Repository.blobs(paths, ref) for the CONTENT - the GraphQL twin of `/repository/files/{path}`, asserted byte-equal to it. `flatPath` is computed from the artifact's own trees by git's real rule (a directory whose sub-tree holds exactly one directory collapses into it) rather than copied from `path`. `size`/`rawSize` are omitted: they are GitLab's BigInt scalar, which serialises as a string, and a subset promising Int would hand clients the wrong JSON type. Answers the named hello-4 file generation gap on an artifact without the file model

served & verified
Core
GQL
users
served & verified
Core
GQL
work-items

SERVED 2026-09-12 (coverage wave W3): GitLab's unified issue/epic type: Project.workItems is the project's ISSUES and Group.workItems the group's EPICS, which is what the two roots mean. Every issue field is glIssue's own value including the `closed_epoch ?? created_epoch` derivation of updatedAt, and `confidential: false` is knowable rather than a default - canon models no private issue or epic. On an artifact predating hello-16 the GROUP half answers an explicit generation error in `errors` rather than an empty connection, which would assert that this organization plans nothing (invariant #4's GraphQL clause)

served & verified
Next
snippets4 of 12 served & verified
GET
/api/v4/projects/{id}/snippets

no snippet exists in this organization — every shared file in olympus-labs lives in the parthenon repository and no persona has ever posted a snippet; GitLab answers `[]` at 200 for a snippet list and its own 404 for a snippet id that names nothing. THE LIST HALF, served as GitLab's empty page with the pagination headers the operation declares (`page`/`per_page`). UNGATED: canon has no `Snippet` entity in any generation — packages/canon/src/avatars-reactions.ts:188 names it as the blocker the hello-15 reaction domain could not answer — so every registered pin answers this same `[]`. Falsifiable: the `expectsEmpty` conformance entry reds the row the day canon grows a snippet. RULED by the founder 2026-09-12

served & verified
Long tail
GET
/api/v4/snippets

no snippet exists in this organization — every shared file in olympus-labs lives in the parthenon repository and no persona has ever posted a snippet; GitLab answers `[]` at 200 for a snippet list and its own 404 for a snippet id that names nothing. THE LIST HALF, served as GitLab's empty page with the pagination headers the operation declares (`page`/`per_page`). UNGATED: canon has no `Snippet` entity in any generation — packages/canon/src/avatars-reactions.ts:188 names it as the blocker the hello-15 reaction domain could not answer — so every registered pin answers this same `[]`. Falsifiable: the `expectsEmpty` conformance entry reds the row the day canon grows a snippet. RULED by the founder 2026-09-12

served & verified
Long tail
GET
/api/v4/snippets/all

no snippet exists in this organization — every shared file in olympus-labs lives in the parthenon repository and no persona has ever posted a snippet; GitLab answers `[]` at 200 for a snippet list and its own 404 for a snippet id that names nothing. THE LIST HALF, served as GitLab's empty page with the pagination headers the operation declares (`page`/`per_page`). UNGATED: canon has no `Snippet` entity in any generation — packages/canon/src/avatars-reactions.ts:188 names it as the blocker the hello-15 reaction domain could not answer — so every registered pin answers this same `[]`. Falsifiable: the `expectsEmpty` conformance entry reds the row the day canon grows a snippet. RULED by the founder 2026-09-12

served & verified
Long tail
GET
/api/v4/snippets/public

no snippet exists in this organization — every shared file in olympus-labs lives in the parthenon repository and no persona has ever posted a snippet; GitLab answers `[]` at 200 for a snippet list and its own 404 for a snippet id that names nothing. THE LIST HALF, served as GitLab's empty page with the pagination headers the operation declares (`page`/`per_page`). UNGATED: canon has no `Snippet` entity in any generation — packages/canon/src/avatars-reactions.ts:188 names it as the blocker the hello-15 reaction domain could not answer — so every registered pin answers this same `[]`. Falsifiable: the `expectsEmpty` conformance entry reds the row the day canon grows a snippet. RULED by the founder 2026-09-12

served & verified
Long tail
GET
/api/v4/projects/{id}/snippets/{snippet_id}

refuse — no snippet exists in this organization — every shared file in olympus-labs lives in the parthenon repository and no persona has ever posted a snippet; GitLab answers `[]` at 200 for a snippet list and its own 404 for a snippet id that names nothing. THE ID HALF: no snippet id resolves, so this operation never reaches a collection to be empty of and GitLab's own 404 is the whole answer — `[]` on a snippet's award-emoji or note list would assert that the snippet exists and holds nothing. UNGATED: canon has no `Snippet` in any generation, so the 404 is true on every artifact. RULED by the founder 2026-09-12

deferred
Long tail
GET
/api/v4/projects/{id}/snippets/{snippet_id}/files/{ref}/{file_path}/raw

refuse — no snippet exists in this organization — every shared file in olympus-labs lives in the parthenon repository and no persona has ever posted a snippet; GitLab answers `[]` at 200 for a snippet list and its own 404 for a snippet id that names nothing. THE ID HALF: no snippet id resolves, so this operation never reaches a collection to be empty of and GitLab's own 404 is the whole answer — `[]` on a snippet's award-emoji or note list would assert that the snippet exists and holds nothing. UNGATED: canon has no `Snippet` in any generation, so the 404 is true on every artifact. RULED by the founder 2026-09-12

deferred
Long tail
GET
/api/v4/projects/{id}/snippets/{snippet_id}/raw

refuse — no snippet exists in this organization — every shared file in olympus-labs lives in the parthenon repository and no persona has ever posted a snippet; GitLab answers `[]` at 200 for a snippet list and its own 404 for a snippet id that names nothing. THE ID HALF: no snippet id resolves, so this operation never reaches a collection to be empty of and GitLab's own 404 is the whole answer — `[]` on a snippet's award-emoji or note list would assert that the snippet exists and holds nothing. UNGATED: canon has no `Snippet` in any generation, so the 404 is true on every artifact. RULED by the founder 2026-09-12

deferred
Long tail
GET
/api/v4/projects/{id}/snippets/{snippet_id}/user_agent_detail

refuse — GitLab exposes user-agent detail to instance administrators for abuse reports only; a non-admin token gets 403

deferred
Long tail
GET
/api/v4/snippets/{id}

refuse — no snippet exists in this organization — every shared file in olympus-labs lives in the parthenon repository and no persona has ever posted a snippet; GitLab answers `[]` at 200 for a snippet list and its own 404 for a snippet id that names nothing. THE ID HALF: no snippet id resolves, so this operation never reaches a collection to be empty of and GitLab's own 404 is the whole answer — `[]` on a snippet's award-emoji or note list would assert that the snippet exists and holds nothing. UNGATED: canon has no `Snippet` in any generation, so the 404 is true on every artifact. RULED by the founder 2026-09-12

deferred
Long tail
GET
/api/v4/snippets/{id}/files/{ref}/{file_path}/raw

refuse — no snippet exists in this organization — every shared file in olympus-labs lives in the parthenon repository and no persona has ever posted a snippet; GitLab answers `[]` at 200 for a snippet list and its own 404 for a snippet id that names nothing. THE ID HALF: no snippet id resolves, so this operation never reaches a collection to be empty of and GitLab's own 404 is the whole answer — `[]` on a snippet's award-emoji or note list would assert that the snippet exists and holds nothing. UNGATED: canon has no `Snippet` in any generation, so the 404 is true on every artifact. RULED by the founder 2026-09-12

deferred
Long tail
GET
/api/v4/snippets/{id}/raw

refuse — no snippet exists in this organization — every shared file in olympus-labs lives in the parthenon repository and no persona has ever posted a snippet; GitLab answers `[]` at 200 for a snippet list and its own 404 for a snippet id that names nothing. THE ID HALF: no snippet id resolves, so this operation never reaches a collection to be empty of and GitLab's own 404 is the whole answer — `[]` on a snippet's award-emoji or note list would assert that the snippet exists and holds nothing. UNGATED: canon has no `Snippet` in any generation, so the 404 is true on every artifact. RULED by the founder 2026-09-12

deferred
Long tail
GET
/api/v4/snippets/{id}/user_agent_detail

refuse — GitLab exposes user-agent detail to instance administrators for abuse reports only; a non-admin token gets 403

deferred
Long tail
mlops0 of 11 served & verified
GET
/api/v4/projects/{id}/ml/mlflow/api/2.0/mlflow-artifacts/artifacts

refuse — mirroring GitLab MLflow means simulating a second ML platform with no bearing on the dev-tool story; GitLab 404s these for a project with no experiments

deferred
Long tail
GET
/api/v4/projects/{id}/ml/mlflow/api/2.0/mlflow/experiments/get

refuse — mirroring GitLab MLflow means simulating a second ML platform with no bearing on the dev-tool story; GitLab 404s these for a project with no experiments

deferred
Long tail
GET
/api/v4/projects/{id}/ml/mlflow/api/2.0/mlflow/experiments/get-by-name

refuse — mirroring GitLab MLflow means simulating a second ML platform with no bearing on the dev-tool story; GitLab 404s these for a project with no experiments

deferred
Long tail
GET
/api/v4/projects/{id}/ml/mlflow/api/2.0/mlflow/experiments/list

refuse — mirroring GitLab MLflow means simulating a second ML platform with no bearing on the dev-tool story; GitLab 404s these for a project with no experiments

deferred
Long tail
GET
/api/v4/projects/{id}/ml/mlflow/api/2.0/mlflow/metrics/get-history

refuse — mirroring GitLab MLflow means simulating a second ML platform with no bearing on the dev-tool story; GitLab 404s these for a project with no experiments

deferred
Long tail
GET
/api/v4/projects/{id}/ml/mlflow/api/2.0/mlflow/model-versions/get

refuse — mirroring GitLab MLflow means simulating a second ML platform with no bearing on the dev-tool story; GitLab 404s these for a project with no experiments

deferred
Long tail
GET
/api/v4/projects/{id}/ml/mlflow/api/2.0/mlflow/model-versions/get-download-uri

refuse — mirroring GitLab MLflow means simulating a second ML platform with no bearing on the dev-tool story; GitLab 404s these for a project with no experiments

deferred
Long tail
GET
/api/v4/projects/{id}/ml/mlflow/api/2.0/mlflow/registered-models/alias

refuse — mirroring GitLab MLflow means simulating a second ML platform with no bearing on the dev-tool story; GitLab 404s these for a project with no experiments

deferred
Long tail
GET
/api/v4/projects/{id}/ml/mlflow/api/2.0/mlflow/registered-models/get

refuse — mirroring GitLab MLflow means simulating a second ML platform with no bearing on the dev-tool story; GitLab 404s these for a project with no experiments

deferred
Long tail
GET
/api/v4/projects/{id}/ml/mlflow/api/2.0/mlflow/registered-models/search

refuse — mirroring GitLab MLflow means simulating a second ML platform with no bearing on the dev-tool story; GitLab 404s these for a project with no experiments

deferred
Long tail
GET
/api/v4/projects/{id}/ml/mlflow/api/2.0/mlflow/runs/get

refuse — mirroring GitLab MLflow means simulating a second ML platform with no bearing on the dev-tool story; GitLab 404s these for a project with no experiments

deferred
Long tail
runners5 of 11 served & verified · 1 deviation
GET
/api/v4/groups/{id}/runners

the machines available to one group - the instance pool, on BOTH containers. The operation's own description settles it: it lists the runners available in a specified group AND ANY ANCESTOR GROUPS, including any allowed instance runners. No machine in this universe was registered against a namespace (`RunnerRow` carries a repository or nothing), so neither the organization's root group nor the subgroup has a group runner of its own and what both of them see is the instance pool; conformance asserts the subgroup's answer is byte-identical to the root's, because an [] there would claim a subgroup cannot use the instance machines, which is false about GitLab rather than about olympus-labs. Conformance also asserts no row in this list is a `group_type` runner, which is the claim that keeps the derivation honest. An unknown group is GitLab's own 404 on every generation. SERVED 2026-09-14 (hello-20 `ciRunners`), gated on hasCiRunners(); an older artifact answers the named hello-20 runner gap.

served & verified
Long tail
GET
/api/v4/projects/{id}/runners

the machines available to one project: its OWN first, off reader.runnersForRepo(repo), then the instance pool it inherits, which is what the operation's own description means by including from ancestor groups and any allowed shared runners. The two halves are genuinely different sets - this organization registers self-hosted machines to the repository and also uses the host's own - and conformance fails if the project registers none, because a list whose two halves collapse into one would not be evidence of the ordering it claims. Same `APIEntitiesCiRunner` rendering as `/api/v4/runners`, so a machine reached through either route is described identically. An unknown project is GitLab's own 404 on every generation, because the project resolves before the domain gate is reached. SERVED 2026-09-14 (hello-20 `ciRunners`), gated on hasCiRunners(); an older artifact answers the named hello-20 runner gap.

served & verified
Long tail
GET
/api/v4/runners

the runners AVAILABLE TO THE CALLER, off reader.runnersByOwnership(org, `hosted`) followed by reader.runnersByOwnership(org, `self-hosted`) - the host's own pool first, then the machines this organization keeps, which is mint order and therefore a stable page for a client that walks it. THIS IS NOT THE INSTANCE LIST and the operation says so: `/api/v4/runners` lists what the user can reach, `/api/v4/runners/all` is admin-only and stays armed `refuse` with the other seven runner administration rows, and conformance asserts that `/runners/all` has not quietly become a synonym for this row. `runner_type` is read off WHICH CONTAINER registered the machine (`RunnerRow.repo` NULL = `instance_type`, a repo id = `project_type`), which is GitLab's own meaning for the field; `is_shared` carries the OWNERSHIP (hosted vs self-hosted), which is GitLab's own word for that second fact. `online` and `status` are THIS HOST'S THRESHOLD applied to canon's instant: `RunnerRow.last_seen_epoch` is an instant and the row's own doc block says each host applies its own online window to it, so GitLab's two-hour `Ci::Runner::ONLINE_CONTACT_TIMEOUT` is measured against the ARTIFACT'S OWN ANCHOR rather than against wall-clock now - a frozen pin must answer the same way in a year. `job_execution_status` is `idle` by construction rather than by default: `runner.busy` is 0 on every row of every artifact because the anchor is not mid-build, which is the first leg of buildkite's armed `/agents` ruling, and conformance fails if any artifact ever carries a busy runner. `name` is `gitlab-runner`, the runner SOFTWARE, because that is what GitLab reads off the registration payload; the operator's own word for the machine is `description`, which is where canon's `RunnerRow.name` goes. SERVED 2026-09-14 (hello-20 `ciRunners`): the row's old reason asked for a Runner the job table can point at and hello-20 built both halves - four tables and the `job.runner` column. Gated on hasCiRunners(); an older artifact answers the named hello-20 runner gap, because [] there would claim nothing ever executed a pipeline here while the same artifact records 592 jobs that each started, finished and concluded.

served & verified
Long tail
GET
/api/v4/runners/{id}/jobs

everything one machine ran, off reader.jobsForRunner(runner) - the row the whole `job.runner` column exists for, and the one read in this domain that could not be derived at request time. Which POOL a job ran in follows from its run's trigger, but a pool holds several identical machines and which ONE took this job is the outcome of a queue: a renderer computing it would be inventing an answer and two renderers would invent two, which is why hello-20 appended the column instead. The job objects are `shapeGlJob`'s, the SAME rendering `/projects/{id}/jobs` and `/projects/{id}/pipelines/{pipeline_id}/jobs` publish, with `APIEntitiesProjectIdentity` added - conformance asserts every job here is a job the project's own list carries, so one job cannot be described two ways depending on which route a client walked in through (invariant #5). SERVED 2026-09-14 (hello-20 `ciRunners`), gated on hasCiRunners(); an older artifact answers the named hello-20 runner gap, and on one the `job.runner` column does not exist at all.

served & verified
Long tail
GET
/api/v4/runners/{id}/projects

which projects a machine is available to. An INSTANCE runner (`RunnerRow.repo` NULL) is available to every project on the host; a PROJECT runner to the one that registered it - so the answer is a filter over reader.listRepos() by the machine's own owner rather than a count. Each project is `APIEntitiesBasicProjectDetails`, the same rendering `/api/v4/projects` publishes, and conformance asserts every id here is an id that list carries, so the reference RESOLVES (invariant #5). SERVED 2026-09-14 (hello-20 `ciRunners`), gated on hasCiRunners(); an older artifact answers the named hello-20 runner gap.

served & verified
Long tail
GET
/api/v4/runners/{id}

one machine in full, off reader.runner(id) plus reader.runnerLabels(runner) - the list shape widened with the platform, the architecture, the labels and the instant it last checked in. `tag_list` is the machine's own labels in the artifact's position order, `contacted_at` is `last_seen_epoch`, and `access_level` is `not_protected` on every row for a checkable reason rather than a default: a protected runner may only be picked up by a job on a protected ref, and this organization's jobs run on every branch, which is the same `protected_ref` fact `/projects/{id}/variables` reads from the other end. `maximum_timeout`, `maintenance_note`, `version`, `revision` and `token_expires_at` are OMITTED rather than nulled - none is required by the vendored component, none is a fact canon holds, and a default invented here is the placeholder invariant #4 forbids (the depth-hooks.ts rule). A runner id nobody has is GitLab's own 404, `%ZZ` included, because no path parameter on this host is decoded a second time. DEVIATION: the vendored component types `tag_list`, `run_untagged` and `locked` as `string`; real GitLab sends an array and two booleans, and its own REST documentation for Get details of a runner shows exactly that - invariant #2 mirrors the provider, not its documentation, so the row is `deviation` with the three validator substrings recorded rather than green. SERVED 2026-09-14 (hello-20 `ciRunners`), gated on hasCiRunners(); an older artifact answers the named hello-20 runner gap.

deviation
Long tail
GET
/api/v4/runner_controllers

refuse — runner ADMINISTRATION: managers, controllers, tokens and router discovery are instance-scope operations GitLab restricts to admins, and this replica authenticates as a member of one organization rather than as an instance administrator. RE-WORDED 2026-09-14 (hello-20 gate 1, the falsified-rulings sweep), the mode unmoved: the sentence used to rest on "physical CI machines ... not the org", and hello-20's `ciRunners` domain made that half false - canon now carries runners, their groups and their labels, `job.runner` says which machine ran each of 592 jobs, and `/api/v4/runners` serves the pool this caller can reach. Two legs survive and either is enough alone. THE ACCESS LEG: `/api/v4/runners/all` is the INSTANCE list and `/api/v4/runners` is the caller's, which is GitLab's own distinction rather than ours, and conformance asserts the two have not become synonyms. THE CREDENTIAL LEG: a runner token and a controller token are secrets, and this canon stores secret NAMES and never values (hello-19's `ci_binding` rule, which `/projects/{id}/secure_files` already rests on). A runner existing is what made the first sentence stale; neither of these moves

deferred
Long tail
GET
/api/v4/runner_controllers/{runner_controller_id}/tokens

refuse — runner ADMINISTRATION: managers, controllers, tokens and router discovery are instance-scope operations GitLab restricts to admins, and this replica authenticates as a member of one organization rather than as an instance administrator. RE-WORDED 2026-09-14 (hello-20 gate 1, the falsified-rulings sweep), the mode unmoved: the sentence used to rest on "physical CI machines ... not the org", and hello-20's `ciRunners` domain made that half false - canon now carries runners, their groups and their labels, `job.runner` says which machine ran each of 592 jobs, and `/api/v4/runners` serves the pool this caller can reach. Two legs survive and either is enough alone. THE ACCESS LEG: `/api/v4/runners/all` is the INSTANCE list and `/api/v4/runners` is the caller's, which is GitLab's own distinction rather than ours, and conformance asserts the two have not become synonyms. THE CREDENTIAL LEG: a runner token and a controller token are secrets, and this canon stores secret NAMES and never values (hello-19's `ci_binding` rule, which `/projects/{id}/secure_files` already rests on). A runner existing is what made the first sentence stale; neither of these moves

deferred
Long tail
GET
/api/v4/runners/{id}/managers

refuse — runner ADMINISTRATION: managers, controllers, tokens and router discovery are instance-scope operations GitLab restricts to admins, and this replica authenticates as a member of one organization rather than as an instance administrator. RE-WORDED 2026-09-14 (hello-20 gate 1, the falsified-rulings sweep), the mode unmoved: the sentence used to rest on "physical CI machines ... not the org", and hello-20's `ciRunners` domain made that half false - canon now carries runners, their groups and their labels, `job.runner` says which machine ran each of 592 jobs, and `/api/v4/runners` serves the pool this caller can reach. Two legs survive and either is enough alone. THE ACCESS LEG: `/api/v4/runners/all` is the INSTANCE list and `/api/v4/runners` is the caller's, which is GitLab's own distinction rather than ours, and conformance asserts the two have not become synonyms. THE CREDENTIAL LEG: a runner token and a controller token are secrets, and this canon stores secret NAMES and never values (hello-19's `ci_binding` rule, which `/projects/{id}/secure_files` already rests on). A runner existing is what made the first sentence stale; neither of these moves

deferred
Long tail
GET
/api/v4/runners/all

refuse — runner ADMINISTRATION: managers, controllers, tokens and router discovery are instance-scope operations GitLab restricts to admins, and this replica authenticates as a member of one organization rather than as an instance administrator. RE-WORDED 2026-09-14 (hello-20 gate 1, the falsified-rulings sweep), the mode unmoved: the sentence used to rest on "physical CI machines ... not the org", and hello-20's `ciRunners` domain made that half false - canon now carries runners, their groups and their labels, `job.runner` says which machine ran each of 592 jobs, and `/api/v4/runners` serves the pool this caller can reach. Two legs survive and either is enough alone. THE ACCESS LEG: `/api/v4/runners/all` is the INSTANCE list and `/api/v4/runners` is the caller's, which is GitLab's own distinction rather than ours, and conformance asserts the two have not become synonyms. THE CREDENTIAL LEG: a runner token and a controller token are secrets, and this canon stores secret NAMES and never values (hello-19's `ci_binding` rule, which `/projects/{id}/secure_files` already rests on). A runner existing is what made the first sentence stale; neither of these moves

deferred
Long tail
GET
/api/v4/runners/router/discovery

refuse — runner ADMINISTRATION: managers, controllers, tokens and router discovery are instance-scope operations GitLab restricts to admins, and this replica authenticates as a member of one organization rather than as an instance administrator. RE-WORDED 2026-09-14 (hello-20 gate 1, the falsified-rulings sweep), the mode unmoved: the sentence used to rest on "physical CI machines ... not the org", and hello-20's `ciRunners` domain made that half false - canon now carries runners, their groups and their labels, `job.runner` says which machine ran each of 592 jobs, and `/api/v4/runners` serves the pool this caller can reach. Two legs survive and either is enough alone. THE ACCESS LEG: `/api/v4/runners/all` is the INSTANCE list and `/api/v4/runners` is the caller's, which is GitLab's own distinction rather than ours, and conformance asserts the two have not become synonyms. THE CREDENTIAL LEG: a runner token and a controller token are secrets, and this canon stores secret NAMES and never values (hello-19's `ci_binding` rule, which `/projects/{id}/secure_files` already rests on). A runner existing is what made the first sentence stale; neither of these moves

deferred
Long tail
milestones3 of 10 served & verified · 5 deviations
GET
/api/v4/groups/{id}/milestones/{milestone_id}/issues

the group milestone RESOLVES from hello-16 and nothing is assigned to it: it is the release train the repository's own milestone rolls up into, and all 24 issues that carry a milestone carry the repository's. Served as GitLab's 200 [] under a milestone that exists, rather than the 404 it was when no group milestone could exist - which is still the answer on every older pin. SERVED 2026-09-09 (hello-16)

served & verified
Core
GET
/api/v4/groups/{id}/milestones/{milestone_id}/merge_requests

the merge-request twin of the group milestone's issue list, empty for the same reason: both merge requests that carry a milestone carry the REPOSITORY's, which projects/{id}/milestones/{id}/merge_requests serves with real rows. 200 [] under a milestone that exists from hello-16; the 404 on every older pin. SERVED 2026-09-09 (hello-16)

served & verified
Core
GET
/api/v4/projects/{id}/milestones/{milestone_id}/merge_requests
served & verified
Core
GET
/api/v4/groups/{id}/milestones

features.namespaces (hello-16) puts the release train the repository's milestone rolls up into on the subgroup; reader.milestonesForNamespace(id) derives it with group_id and its own iid. Older pins answer the empty list. SERVED 2026-09-09 (hello-16)

deviation
Core
GET
/api/v4/groups/{id}/milestones/{milestone_id}

the by-id twin of the group milestone list, off the same reader.milestonesForNamespace row; an id nobody holds is still GitLab's 404 Milestone Not Found, and that is the whole answer on every pin older than hello-16. SERVED 2026-09-09 (hello-16)

deviation
Core
GET
/api/v4/projects/{id}/milestones
deviation
Core
GET
/api/v4/projects/{id}/milestones/{milestone_id}
deviation
Core
GET
/api/v4/projects/{id}/milestones/{milestone_id}/issues
deviation
Core
GET
/api/v4/groups/{id}/milestones/{milestone_id}/burndown_events

derive — The project row's reason at the group scope: hello-20's `resource_event` carries the milestone history the old reason said was missing, so the blocker is now the UNOBSERVED WIRE SHAPE — the vendored operation declares a bare `200: description: OK` with no content, no component and no example, and authoring one would be the invented body the Azure DevOps chart-image rows were held back from on 2026-09-03. Deferred on the encoding, not on the canon. NOTE FOR THE COORDINATOR: `empty` would ALSO be defensible on the data here and is not on the project row — this group milestone is the release train the repository's own milestone rolls up into and NOTHING is assigned to it directly, which is exactly why its own /issues and /merge_requests rows were ruled `empty` on 2026-09-09; an empty burndown needs no knowledge of the item shape. It is not taken here because serving one half of a pair whose shape is unobserved, while the tier-1 project row beside it cannot be, is a worse answer than uniform honesty. REASON REPLACED 2026-09-14 on the hello-20 GitLab history wave. RE-CHECKED 2026-10-27 at the pinned commit ea02ef9f: doc/api/milestones.md and doc/api/group_milestones.md give this operation's request line and parameter table and no response example, so the blocker stands (decisions/2026-10-27-2140-documented-shapes-left.md).

planned
Core
GET
/api/v4/projects/{id}/milestones/{milestone_id}/burndown_events

derive — THE DATA EXISTS AND THE WIRE SHAPE DOES NOT. hello-20's `resource_event` table carries the milestone history this row's old reason said was missing (49 adds and 25 removes at 90 days, every one at an instant the event log already records) and `issue_transition` carries the closes, so a burndown over milestone-01 and its 24 issues is a pure fold over rows that exist. What is missing is an OBSERVED BODY: the vendored operation declares a bare `200: description: OK` with no content, no component and no example, and GitLab publishes no response example for this EE endpoint, so there is nothing to mirror and a shape authored here would be the invented body the Azure DevOps chart-image rows were held back from on 2026-09-03 for the identical reason. Deferred on the encoding, not on the canon; the row goes on answering the coverage 404. REASON REPLACED 2026-09-14 on the hello-20 GitLab history wave, because hello-20 falsified the old one. RE-CHECKED 2026-10-27 at the pinned commit ea02ef9f: doc/api/milestones.md and doc/api/group_milestones.md give this operation's request line and parameter table and no response example, so the blocker stands (decisions/2026-10-27-2140-documented-shapes-left.md).

planned
Core
commits7 of 9 served & verified · 1 deviation
GET
/api/v4/projects/{id}/repository/commits
served & verified
Core
GET
/api/v4/projects/{id}/repository/commits/{sha}
served & verified
Core
GET
/api/v4/projects/{id}/repository/commits/{sha}/comments

no commit in this universe carries a comment — DECISIONS 2026-08-20 [commit-detail] ruled it per endpoint and that ruling stands (the 2026-08-29 entry superseded only its three-verdict bar); GitLab's own answer for a commit with no notes is `[]` at 200. THE LIST HALF: the commit must resolve first (reader.getCommitBySha), so a sha nobody pushed is still GitLab's 404 rather than a cheerful `[]`. UNGATED: packages/canon/src/notes.ts:52 records that hello-17's entity-note domain may not declare a `commit` parent until this ruling is reversed, so no generation of this universe carries a commit note. Falsifiable: the `expectsEmpty` entry reds the row the day one appears. RULED by the founder 2026-09-12

served & verified
Core
GET
/api/v4/projects/{id}/repository/commits/{sha}/diff
served & verified
Core
GET
/api/v4/projects/{id}/repository/commits/{sha}/refs
served & verified
Core
GET
/api/v4/projects/{id}/repository/commits/{sha}/sequence
served & verified
Core
GET
/api/v4/projects/{id}/repository/commits/{sha}/statuses
served & verified
Core
GET
/api/v4/projects/{id}/repository/commits/{sha}/merge_requests
deviation
Core
GET
/api/v4/projects/{id}/repository/commits/{sha}/signature

refuse — no commit or tag in this universe is GPG-signed, and GitLab answers its own 404 'GPG Signature Not Found' for an unsigned object

planned
Core
gitlab duo workflows6 of 9 served & verified
GET
/api/v4/ai/duo_workflows/code_review/custom_instructions

NOBODY IN THIS ORGANIZATION WROTE CUSTOM INSTRUCTIONS FOR A CODE REVIEW. Canon records the review CONVERSATION - `review` and `review_comment`, both served on this host - and no per-merge-request instruction to an agent; inventing one would be inventing an agent's PROMPT, the same never-invent rule (2026-08-20, restated by founder ruling F6 of 2026-09-14) that keeps the serialized graph state off a Duo checkpoint and the text off a session turn. THE PARENT IS IN THE QUERY STRING, and the empty is only honest behind it: the operation declares `project_id` and `merge_request_iid` both `required: true`, so a missing one is GitLab's 400 in this host's own shape, a `project_id` that does not resolve is `404 Project Not Found`, and an iid this project has not got is the `404 Not found` the merge-request route itself answers. Unpaged. RULED 2026-09-14 by the hello-20 GitLab serving wave, on the F6 `glDelivery` decision's §5

served & verified
Long tail
GET
/api/v4/ai/duo_workflows/workflows/{id}

the Duo workflow an `agent_session` projects as — `{id}` is minted `gitlabId(session.id)` in the renderer, not in packages/artifact/src/provider-ids.ts, because that module holds the derivations two hosts must AGREE on and no host but this one names a Duo workflow. `project_id`, `user_id` and `created_at` are the session's own; `status` is its `state` in GitLab's own workflow vocabulary (running -> running, completed -> finished, failed -> failed), and GitLab's other statuses (`created`, `paused`, `stopped`, the three approval states) describe a control flow this universe does not record, so none is ever spelled. `updated_at` IS THE LAST INSTANT THE RUN REACHED, read off the same `agentWorkflowSteps` list the checkpoints publish, so the workflow and its trace cannot disagree about when it last did something. `goal`, `workflow_definition`, `agent_privileges` and `pre_approved_agent_privileges` ARE OMITTED: the goal is the prompt somebody typed, the definition is GitLab's own flow catalogue, and the privileges are a grant nobody in this organization made. THE VENDORED DOCUMENT DECLARES NO SCHEMA for this operation — its 200 is a bare `description: OK` — so the row is `schema: null` and the wire contract is asserted directly in conformance. A NON-NUMERIC SEGMENT IS HANDED BACK TO THE MANIFEST: `…/workflows/agent_privileges` matches this route and is an UNCOVERED row, so it answers the coverage 404 with the `X-SandboxAPIs-Coverage` header rather than GitLab's plain one, which would hide the miss (invariant #4; the `/personal_access_tokens/:id` precedent). SERVED 2026-09-14 (hello-20 `glDelivery`): gated on hasGlDelivery(); an older artifact answers the named delivery generation gap

served & verified
Long tail
GET
/api/v4/ai/duo_workflows/workflows/{id}/checkpoints

the instants the run reached, off reader.agentWorkflowSteps(session) in ordinal order — its own start, the merge request it opened, and its own end, EVERY ONE an instant canon already records for that session (a checkpoint log with a drawn instant would be a trace of a run that did not happen), and a session still in flight at the window's now has no terminal step. 163 steps over 78 sessions at 90 d. Each row is `{id, thread_ts, parent_ts}`: the id is `gitlabId(<session>|checkpoint|<ord>)` because `agent_workflow_step` has no id column and is addressed by the pair — THE ORDINAL AND NOT THE INSTANT is what makes it unique, since a session whose merge request opened at the instant it finished carries two steps at one `thread_ts` — and `parent_ts` is the previous step's instant, null on the first because the run had reached nothing before it. NO `checkpoint` AND NO `metadata`: GitLab's checkpoint carries a serialized LangGraph graph state, and this universe does not invent an agent's reasoning any more than it invents its prose (the 2026-08-20 policy, restated by founder ruling F6 of 2026-09-14); `AgentWorkflowStepRow` has four columns and no body, so a blob here would come from the renderer rather than from the universe. The vendored document declares NO SCHEMA, so the row is `schema: null` and conformance asserts the contract plus that every non-clamped instant is one canon records for the session. `accept_compressed` is accepted and changes nothing: it selects an encoding, not a set. SERVED 2026-09-14 (hello-20 `glDelivery`): gated on hasGlDelivery(); an older artifact answers the named delivery generation gap

served & verified
Long tail
GET
/api/v4/ai/duo_workflows/workflows/{id}/checkpoints/{checkpoint_id}

one checkpoint by the id the list hands a caller, decoded back to the `(session, ord)` pair and read through reader.agentWorkflowStep(session, ord) — the same door the list uses. Byte-identical to the list's own row, and conformance reads EVERY checkpoint of the driven run back by its own id rather than only the first. `404 Checkpoint Not Found` for an id nothing has and for `%ZZ`, with no decodeURIComponent. Same omissions as the list (`checkpoint`, `metadata`), same `schema: null` — the vendored document declares none. SERVED 2026-09-14 (hello-20 `glDelivery`): gated on hasGlDelivery(); an older artifact answers the named delivery generation gap

served & verified
Long tail
GET
/api/v4/ai/duo_workflows/workflows/{id}/events

the SAME reader.agentWorkflowSteps(session) list, in GitLab's event shape — `{id, workflow_id, event_type, thread_ts, parent_ts}`, and conformance asserts it is row-for-row the same set in the same order as `…/checkpoints`, which is what `one list read three ways` has to mean on the wire. `event_type` IS THE ONE VALUE IN THIS DOMAIN THAT IS CANON'S WORD RATHER THAN GITLAB'S, disclosed here rather than buried: GitLab's own event vocabulary (`message`, `pause`, `resume`, `stop`, `require_input`, …) names CONTROL events a CLIENT posts, and this universe records no control event — what it records is what the run REACHED (`started`, `pull-opened`, `completed`, `failed`), and THE KIND IS THE STATUS, so there is no second column to put it in. The vendored document declares NO SCHEMA for this operation, so nothing is invalidated by the choice and the wire contract is asserted directly. `message` and `correlation_id` are OMITTED: both are text this universe does not write. A session that produced a merge request reached a `pull-opened` step and that merge request resolves; one that produced none has no such step, and conformance drives both directions. SERVED 2026-09-14 (hello-20 `glDelivery`): gated on hasGlDelivery(); an older artifact answers the named delivery generation gap

served & verified
Long tail
GET
/api/v4/ai/duo_workflows/workflows/{workflow_id}/trace.jsonl

the third reading of the same reader.agentWorkflowSteps(session) list, and the one that is NOT JSON: JSON Lines, as the operation's own description says — `Each line is a JSON object representing one checkpoint event in chronological order` — so it is served as `application/x-ndjson`, the registered media type for that format, one object per line and no trailing blank line. It is also where GitLab itself calls a line a `checkpoint EVENT`, which is what lets one object answer both this and `…/events`. `schema: null` with no vendored schema to validate against, so the row is a `bytes` entry in conformance: the media type, one parseable JSON object per line, and the same ids in the same order as `…/checkpoints` and `…/events`, asserted directly. `full` is accepted and changes nothing: it asks for GitLab's internal channels — conversation history, handover — and this universe records none of them, which is the same never-invent rule that keeps the graph state off the checkpoint. SERVED 2026-09-14 (hello-20 `glDelivery`): gated on hasGlDelivery(); an older artifact answers the named delivery generation gap

served & verified
Long tail
GET
/api/v4/ai/duo_workflows/list_tools

generateheld · data-licensing — static provider catalog, and NOT `derive`: `derive` is defined in this file as computed from artifact rows that already exist, and a vendor catalog is not an artifact row. THE OLD REASON IS FALSE AS OF 2026-09-14 - hello-20's `agent_workflow_step` IS the Duo workflow/checkpoint/trace projection the reason asked for, and the five rows that needed it are served - but this row never did: it takes no project, no group and no user, so it could not be gated on hasGlDelivery() even in principle, because a fact about the HOST must not depend on which snapshot of olympus-labs is being read (the `noRefCatalogs` distinction). LICENCE: NO GRANT, AND NO SOURCE TO CHECK AGAINST. The vendored document declares no schema, no example and no enum for this operation, so every tool id, name and description would come out of memory rather than out of a document, and the contents are GitLab's own MIT-licensed source - verbatim the finding `canon/src/ref-catalogs/gitlab-templates.ts` states at length, and the one that has left GitHub's `/emojis` and `/codes_of_conduct` uncovered since 2026-09-09. THE ASK: a `ref_catalog_entry` catalogue beside the four this fleet already serves (licences, ignore files, Dockerfile templates, CI templates), which is a canon change and was out of scope for the hello-20 serving wave. The row goes on answering the coverage 404 with its `X-SandboxAPIs-Coverage` header and stays in the badge denominator

deferred
Long tail
GET
/api/v4/ai/duo_workflows/workflows/agent_privileges

generateheld · data-licensing — static provider catalog, and NOT `derive`: `derive` is defined in this file as computed from artifact rows that already exist, and a vendor catalog is not an artifact row. THE OLD REASON IS FALSE AS OF 2026-09-14 - hello-20's `agent_workflow_step` IS the Duo workflow/checkpoint/trace projection, and the five rows that needed it are served - but this row never did, and its own description says so: it lists ALL AVAILABLE agent privileges with their IDs, names, descriptions and whether each is enabled by default, and has no supported attributes. It is a fact about GITLAB, like `/api/v4/metadata`, so it could not be gated on hasGlDelivery() even in principle. LICENCE: NO GRANT, AND NO SOURCE TO CHECK AGAINST - the vendored document declares no schema, no example and no enum, so every privilege id, name, description and default flag would come out of memory rather than out of a document, and the contents are GitLab's own MIT-licensed source (the `/emojis` and `/codes_of_conduct` finding of 2026-09-09). THE ASK: a `ref_catalog_entry` catalogue beside the four this fleet already serves. THE PATH MATCHES THE SERVED `…/workflows/{id}` ROUTE, so the renderer hands a non-numeric segment back to `notCovered(c)` rather than letting it answer GitLab's plain 404 - a plain 404 there would hide a coverage miss (invariant #4, the `/personal_access_tokens/:id` precedent) - and `h20-delivery.test.ts` asserts it

deferred
Long tail
GET
/api/v4/ai/duo_workflows/ws

refuse — a WebSocket upgrade is not an HTTP read and the gateway serves no bidirectional channel

deferred
Long tail
issues2 of 9 served & verified · 6 deviations
GET
/api/v4/issues_statistics
served & verified
Core
GET
/api/v4/projects/{id}/issues/{issue_iid}/participants
served & verified
Core
GET
/api/v4/issues
deviation
Core
GET
/api/v4/issues/{id}
deviation
Core
GET
/api/v4/projects/{id}/issues/{issue_iid}/links

SERVED 2026-09-12 (hello-17): features.issueLinks derives the edges from the storyline - issues in one storyline relate, and an issue whose close precedes another's start blocks it. Canon stores the link ONCE, directed; this route renders it relative to the issue you asked about, so one `blocks` row reads `blocks` from the blocker's end and `is_blocked_by` from the blocked one's. The other end is the same issue /issues/{iid} serves, shaped by the same glIssue, plus the four link fields APIEntitiesRelatedIssue adds; sorted by the relationship's own instant ascending, which is the vendored document's own ordering. Answers the named hello-17 generation gap on every older artifact

deviation
Core
GET
/api/v4/projects/{id}/issues/{issue_iid}/links/{issue_link_id}

SERVED 2026-09-12 (hello-17): one link by id, naming both ends outright - so link_type here is the CANONICAL direction rather than the relative one /links prints. Looked up inside this issue's own links, so a link between two other issues 404s rather than being served under a parent it does not touch

deviation
Core
GET
/api/v4/projects/{id}/issues/{issue_iid}/related_merge_requests
deviation
Core
GET
/api/v4/projects/{id}/issues/{issue_iid}/time_stats

no persona in olympus-labs logs time against an issue or a merge request — canon records no TimeEntry; GitLab's own answer for an untracked issue is a 200 carrying `time_estimate: 0`, `total_time_spent: 0` and both human_ fields null. NOT A COLLECTION: the operation returns `APIEntitiesIssuableTimeStats`, an object, so the honest empty is the zeroed object rather than `[]`. It is the SAME object `glIssue` and `glMergeRequest` already embed as `time_stats` (`emptyTimeStats` in packages/renderer-gitlab/src/render2.ts), read from one function so the standalone endpoint and the embedded block can never disagree (invariant #5). canon's `Issue.estimate` is a story-point estimate on a fibonacci scale rather than a duration, and `time_estimate` is documented in seconds, so mapping one onto the other would be an invention. The two human_ fields are a `deviation` against the vendored component, which types them `string` with no `nullable` while real GitLab renders nil for a zero — the same two entries NULLABLE_SPEC_BUGS already carries for the embedded block. UNGATED: no generation of this universe records a time entry. RULED by the founder 2026-09-12

deviation
Core
GET
/api/v4/projects/{id}/issues/{issue_iid}/user_agent_detail

refuse — GitLab exposes user-agent detail to instance administrators for abuse reports only; a non-admin token gets 403

planned
Core
boards6 of 8 served & verified
GET
/api/v4/groups/{id}/boards

the SAME four team boards the project's list serves, through the same reader.boardScopes('team'), with GitLab's group object in place of its project object — one board, two containers, which is GitLab's own model. THE ORGANIZATION'S GROUP ANSWERS AND THE SUBGROUP DOES NOT: platform-delivery owns no repository, so no issue was ever filed in it and there is no view to configure, and its `[]` is the honest answer under a group that resolves — the split every plan-shaped family on this host already makes. hide_backlog_list and hide_closed_list are the same folds over the board's own columns, `lists` is `[]` for the project list's reason, and no WIP limit is emitted anywhere. SERVED 2026-09-14 (hello-20 `boards`): gated on reader.hasBoards(); an older artifact answers the named hello-20 gap rather than `[]`

served & verified
Long tail
GET
/api/v4/groups/{id}/boards/{board_id}

the by-id twin of the group's board list, off the same reader.boardScopes('team') membership test, addressed by gitlabId of the team the board is. A board id under the SUBGROUP is GitLab's 404 Board Not Found, because the subgroup has no board to address; so is a team that has no board, and so is `%ZZ` (PR #481) rather than a 500. SERVED 2026-09-14 (hello-20 `boards`): gated on reader.hasBoards(); an older artifact answers the named hello-20 gap after the group has resolved

served & verified
Long tail
GET
/api/v4/groups/{id}/boards/{board_id}/lists

The project row's ruling at the group scope, over the SAME four team boards: a GitLab board list is a label list, this canon's columns are workflow states, and no label in this project's catalogue is one — so no board in this organization has a label list configured and `[]` at 200 under a board that resolves is GitLab's own answer, matching the `lists: []` the board object publishes. The board's columns reach the surface through hide_backlog_list and hide_closed_list, which the board rows serve. The SUBGROUP owns no repository and therefore no board at all, so a board id under it is GitLab's 404 Board Not Found rather than this empty. RULED 2026-09-14 on the hello-20 GitLab history wave.

served & verified
Long tail
GET
/api/v4/projects/{id}/boards

reader.boardScopes('team') and reader.boardColumns('team', team) — and there is NO board table, which is the domain's whole shape: a board is already a TEAM on GitLab, on Jira and on Azure DevOps, so this reads which teams have a board at all and takes the board's id and name from the team it IS. Four team boards at 90 days, and every team's board answers under the one project because every team files issues against parthenon, which is this organization's only repository. A BOARD EXISTS ONLY WHERE ISSUES DO, so a scope with no issues gets no rows and the honest empty list under a parent that resolves. hide_backlog_list and hide_closed_list are FOLDS over the board's own columns, not toggles: GitLab's backlog list holds the work nobody started and its closed list the work that is finished, and canon's `backlog` column and its terminal columns (done, canceled) are precisely those two — team-platform ran todo > in_progress > done > canceled and hides its backlog while team-qa ran the full six and shows it, which conformance asserts as a DIFFERENCE so a constant published four times cannot pass. `lists` is `[]` and the scope fields (labels, milestone, assignee, weight) are omitted: nobody in this organization configured a label list or scoped a board, which is the WIP-limit argument one level up — canon records the states the issues PASSED THROUGH, an observation, while a GitLab board list is a list somebody ADDED, a policy. max_issue_count, max_issue_weight and limit_metric are never emitted, because BoardColumnRow carries no WIP limit and a peak is not a limit. SERVED 2026-09-14 (hello-20 `boards`): gated on reader.hasBoards(); an older artifact answers the NAMED hello-20 gap (`the column layout of a board`) rather than `[]`, which would claim no board in this project has a single list on it while the same artifact records 71 transitions of issues between states

served & verified
Long tail
GET
/api/v4/projects/{id}/boards/{board_id}

the by-id twin of the project's board list, off the same reader.boardScopes('team') membership test and byte-identical to the board's entry in the list. `{board_id}` is gitlabId of the TEAM the board is, so the id the list publishes is the id this route accepts — and a team that has NO board is not a board id, which is what keeps `a board exists only where issues do` true from both directions: conformance walks every team without a board and asserts GitLab's 404 Board Not Found. `%ZZ` is that 404 rather than a 500 (PR #481). SERVED 2026-09-14 (hello-20 `boards`): gated on reader.hasBoards(); an older artifact answers the named hello-20 gap after the project has resolved

served & verified
Long tail
GET
/api/v4/projects/{id}/boards/{board_id}/lists

NO BOARD IN THIS ORGANIZATION HAS A LABEL LIST CONFIGURED. A GitLab issue-board list is a LABEL list — the vendored APIEntitiesList carries `label`, `milestone`, `iteration` and `assignee` and no `list_type` at all, and both lists operations exclude the built-in open and closed lists — while this canon's board columns are WORKFLOW STATES: /projects/{id}/labels publishes bug, enhancement, documentation, priority: high, good first issue and wontfix, and not one of them is backlog, todo, in_progress, in_review, done or canceled. Minting a label for a column would publish a label that resolves nowhere on this host (invariant #5) and invent a value (invariant #4). It is also the domain's own WIP-limit argument one level up: canon records the states the issues PASSED THROUGH, an observation, while a board list is a list somebody ADDED, a policy — and nobody here added one. The columns are not discarded: they decide hide_backlog_list and hide_closed_list on the board object, which the board rows serve and conformance asserts DIFFER from team to team. `[]` at 200 under a board that RESOLVES, matching the `lists: []` the board itself publishes; a board id nobody holds is GitLab's 404 Board Not Found. RULED 2026-09-14 on the hello-20 GitLab history wave.

served & verified
Long tail
GET
/api/v4/groups/{id}/boards/{board_id}/lists/{list_id}

refuse — ENTAILED by the empty beside it: the board has no label list, so no list id resolves and GitLab's own `404 List Not Found` is the whole answer. The GROUP and the BOARD resolve first, and `%ZZ` is a 404 rather than a 500 (PR #481). RULED 2026-09-14 on the hello-20 GitLab history wave.

deferred
Long tail
GET
/api/v4/projects/{id}/boards/{board_id}/lists/{list_id}

refuse — ENTAILED by the empty beside it: this board has no label list, so no list id resolves and GitLab's own `404 List Not Found` is the whole answer. The PROJECT and the BOARD still resolve first — a board id nobody holds is `404 Board Not Found`, which is what keeps the refusal a statement about configuration rather than about the id — and `%ZZ` is a 404 rather than the raw URIError 500 PR #481 fixed. RULED 2026-09-14 on the hello-20 GitLab history wave.

deferred
Long tail
hooks-config6 of 8 served & verified
GET
/api/v4/groups/{id}/hooks

SERVED 2026-09-12 (hello-18): the hooks a group publishes to, off reader.orgWebhooks for the organization and reader.namespaceWebhooks for the subgroup. BOTH CONTAINER IDS ANSWER IT — the vendored document restricts the operation to no particular group, so the subgroup is not denied a family this host lists it under (invariant #5) — and what each holds is a different fact: the organization's is the account-wide feed with all six delivery events, and the subgroup's is the hook every real account has one of, wired up once and never fired, because the subgroup owns no repository (founder ruling F1 of 2026-09-09). That one renders with every event flag FALSE and `alert_status: disabled`, which is GitLab's own vocabulary for a hook that will not deliver — APIEntitiesGroupHook carries no `active` property — and conformance asserts it rather than testing the family only where it is busy. The event-name translation is the project row's, plus the three flags only a group can publish (subgroup_events, member_events, project_events), all false. Gated on hasWebhookFilters()

served & verified
Next
GET
/api/v4/groups/{id}/hooks/{hook_id}

SERVED 2026-09-12 (hello-18): the same group hook by id, byte-identical to the row the list publishes. The id must belong to the container named in the path: the subgroup's hook id is GitLab's 404 on the organization's path and the other way round, which conformance drives from both sides. Gated on hasWebhookFilters()

served & verified
Next
GET
/api/v4/groups/{id}/hooks/{hook_id}/events

SERVED 2026-10-27 (hello-23 deliveryEvents, founder ruling F4, decisions/2026-10-27-2218 and decisions/2026-10-27-2310): the organization group hook's webhook_delivery rows from the last seven days before the anchor, newest first, in GitLab's documented event shape (doc/api/project_webhooks.md and group_webhooks.md at the pinned GitLab commit, pinned under gitlab.docs; the vendored operation is a bare 200). Each carries trigger, request_headers, request_data (GitLab's push, issue or merge request hook body as of the delivery instant), response_status from last_delivery_code, execution_duration 0 (the whole-second ruling) and null response_headers and response_body, because the receiver's answer was never recorded. A log holding a job hook answers the coverage 404, since its subject is a commit status here. Older artifacts answer the hello-23 generation gap

served & verified
Next
GET
/api/v4/projects/{id}/hooks

SERVED 2026-09-12 (hello-18): the hooks this project publishes to, off reader.repoWebhooks — hello-18's features.webhookFilters widens canon's webhook_subscription from PagerDuty's three account scopes to the three a repository host publishes at, so a project hook and a group hook are ONE object at two scopes rather than a new entity. THE EVENT NAMES ARE THIS RENDERER'S TRANSLATION, not canon's: canon's WebhookEventKind is neutral (ref.pushed, change.opened, change.merged, issue.opened, issue.closed, build.completed) because three renderers read that table, and GitLab publishes a BOOLEAN PER FAMILY instead of a list of names — so ref.pushed becomes push_events, change.opened and change.merged both become merge_requests_events (GitLab has ONE merge-request flag), issue.opened and issue.closed both become issues_events, and build.completed becomes job_events, whose own payload GitLab spells `object_kind: build`. Every other flag the vendored component declares is FALSE, and conformance asserts that in both directions so a flag cannot start defaulting to true. `alert_status` carries canon's `active`, because APIEntitiesProjectHook has no `active` property at all and `executable`/`disabled` is GitLab's own vocabulary for whether a hook will fire. `token_present` and `signing_token_present` are FALSE and are sent, because canon states there is no secret; the knobs canon does not hold (enable_ssl_verification, url_variables, branch filters, custom templates and headers, organization_id, name) are OMITTED rather than defaulted. Gated on hasWebhookFilters(); an older artifact answers the named hello-18 webhook gap, because [] there would say this organization wires up nothing

served & verified
Next
GET
/api/v4/projects/{id}/hooks/{hook_id}

SERVED 2026-09-12 (hello-18): the same hook by id, byte-identical to the row the list publishes — conformance compares them directly. THE ID MUST BELONG TO THIS PROJECT: a hook id from the group's own feed is GitLab's 404 here, which is the same scoping reader.repoWebhooks()/orgWebhooks() enforce one layer down and which conformance drives from both sides. A hook id nobody holds is GitLab's own 404. Gated on hasWebhookFilters()

served & verified
Next
GET
/api/v4/projects/{id}/hooks/{hook_id}/events

SERVED 2026-10-27 (hello-23 deliveryEvents, founder ruling F4, decisions/2026-10-27-2218 and decisions/2026-10-27-2310): the project hook's webhook_delivery rows from the last seven days before the anchor, newest first, in GitLab's documented example shape (doc/api/project_webhooks.md at the pinned GitLab commit, pinned under gitlab.docs; the vendored operation is a bare 200). The project hook is the repository hook GitHub serves, and the two hosts publish one log: the same pushes, the same files changed, the same issues. request_data is GitLab's hook body as of the delivery instant; response_status is last_delivery_code; execution_duration is 0 and response_headers and response_body are null, the receiver's answer never having been recorded. A log holding a job hook answers the coverage 404. Older artifacts answer the hello-23 generation gap

served & verified
Next
GET
/api/v4/hooks

refuse — instance-wide system hooks are admin-only and describe the installation, not the org

deferred
Next
GET
/api/v4/hooks/{hook_id}

refuse — instance-wide system hooks are admin-only and describe the installation, not the org

deferred
Next
jobs7 of 8 served & verified
GET
/api/v4/jobs/{id}/artifacts

SERVED 2026-09-12 (coverage wave W7): the zip of the files the job uploaded, from `reader.jobArtifacts(job)` + `reader.artifactContent()` - the SAME bytes the Buildkite artifact download and GitHub's `actions/artifacts/{id}/zip` serve for the same canonical job (invariant #5), each member named by `ArtifactRow.path`, which is where the job wrote it. Served INLINE at 200 rather than through GitHub's 302: the vendored document declares `'200': OK` and real GitLab streams the file from the API host through workhorse, so a redirect here would be mirroring the other provider. `Content-Disposition: attachment; filename="artifacts.zip"`, which is the name GitLab stores and serves a job's uploaded archive under. A pre-hello-15 snapshot answers the NAMED generation gap (`ArtifactRow.content` ships with `features.ciConfig`) and not a bare 404, which would say this job uploaded nothing - a claim about the job, and a false one. A job that really has no artifacts still gets GitLab's own `404 Not found`.. THE INSTANCE-SCOPED ADDRESS of that same archive, asserted byte-identical to the project-scoped one. Unlike `/api/v4/job` and `/api/v4/job/allowed_agents` - which are `refuse` because they ask what job the CALLER is running right now, and a frozen universe has no live job - this one names a job by id, and the job it names is one this host serves. Unblocked by the tar/zip writers moving out of packages/renderer-github into `@sandboxapis/archive`, founder-ordered 2026-09-09

served & verified
Next
GET
/api/v4/projects/{id}/jobs
served & verified
Next
GET
/api/v4/projects/{id}/jobs/{job_id}
served & verified
Next
GET
/api/v4/projects/{id}/jobs/{job_id}/artifacts

SERVED 2026-09-12 (coverage wave W7): the zip of the files the job uploaded, from `reader.jobArtifacts(job)` + `reader.artifactContent()` - the SAME bytes the Buildkite artifact download and GitHub's `actions/artifacts/{id}/zip` serve for the same canonical job (invariant #5), each member named by `ArtifactRow.path`, which is where the job wrote it. Served INLINE at 200 rather than through GitHub's 302: the vendored document declares `'200': OK` and real GitLab streams the file from the API host through workhorse, so a redirect here would be mirroring the other provider. `Content-Disposition: attachment; filename="artifacts.zip"`, which is the name GitLab stores and serves a job's uploaded archive under. A pre-hello-15 snapshot answers the NAMED generation gap (`ArtifactRow.content` ships with `features.ciConfig`) and not a bare 404, which would say this job uploaded nothing - a claim about the job, and a false one. A job that really has no artifacts still gets GitLab's own `404 Not found`. Unblocked by the tar/zip writers moving out of packages/renderer-github into `@sandboxapis/archive`, founder-ordered 2026-09-09

served & verified
Next
GET
/api/v4/projects/{id}/jobs/{job_id}/artifacts/tree

SERVED 2026-09-12 (coverage wave W8) (hello-15 `ciConfig`): `artifact.path` is where the job wrote the file relative to the workspace root, so the archive's directory tree is a GROUP-BY over those paths and needs no archive format at all — which is what separates this row from the three artifact DOWNLOAD rows. One entry per directory on the way to a file plus one per file, in path order; `?recursive=` and `?path=` narrow it as the vendored operation declares. `mode` is OMITTED throughout: canon records no POSIX mode for an artifact and "100644" would be this renderer inventing one. A JOB WITH NO ARTIFACT IS A 404 rather than an empty list — GitLab's artifact browser has nothing to browse when the job uploaded no archive, and `[]` would advertise an empty archive that does not exist; conformance drives such a job and asserts the 404. Generation-gated: an older artifact has no `artifact.path` column at all

served & verified
Next
GET
/api/v4/projects/{id}/jobs/{job_id}/trace

hello-13's JobLog/JobLogLine ARE the job trace: the endpoint serves reader.jobLogText(job) — the same canonical bytes Bitbucket's step log serves for the same job. Per-test-case results are a different surface (jobs/{id}/artifacts/tree) and stay generate

served & verified
Next
GET
/api/v4/projects/{id}/jobs/artifacts/{ref_name}/download

SERVED 2026-09-12 (coverage wave W7): the zip of the files the job uploaded, from `reader.jobArtifacts(job)` + `reader.artifactContent()` - the SAME bytes the Buildkite artifact download and GitHub's `actions/artifacts/{id}/zip` serve for the same canonical job (invariant #5), each member named by `ArtifactRow.path`, which is where the job wrote it. Served INLINE at 200 rather than through GitHub's 302: the vendored document declares `'200': OK` and real GitLab streams the file from the API host through workhorse, so a redirect here would be mirroring the other provider. `Content-Disposition: attachment; filename="artifacts.zip"`, which is the name GitLab stores and serves a job's uploaded archive under. A pre-hello-15 snapshot answers the NAMED generation gap (`ArtifactRow.content` ships with `features.ciConfig`) and not a bare 404, which would say this job uploaded nothing - a claim about the job, and a false one. A job that really has no artifacts still gets GitLab's own `404 Not found`.. ADDRESSED BY REF AND JOB NAME: the latest successful pipeline on the branch or tag, then the job of that name on it. `ref_name` resolves as a branch or a tag and nothing else, because the document says outright that `HEAD` or `SHA` references are not supported. A missing `job` is Grape's own `{"error": "job is missing"}` at 400 - that envelope and not GitLab's usual `{message}`, because that is what the framework actually sends for a missing required parameter. `search_recent_successful_pipelines` is refused rather than implemented: it widens the search from the latest successful pipeline to `recent` ones, and neither the vendored document nor GitLab's own page says how far back that reaches, so answering it would be answering a different question. Unblocked by the tar/zip writers moving out of packages/renderer-github into `@sandboxapis/archive`, founder-ordered 2026-09-09

served & verified
Next
GET
/api/v4/job

refuse — these answer a running runner presenting a CI job token, not an API caller; a frozen universe has no live job

deferred
Next
search1 of 8 served & verified · 2 deviations
GET
/api/v4/search
served & verified
Next
GET
/api/v4/groups/{id}/(-/)search

scope-dispatched search over the group's projects. THE SUBGROUP RESOLVES HERE as of 2026-09-10 and answers the empty result set for every scope, because a group search is a search over its PROJECTS and it owns none (founder ruling F1). A missing or invalid `scope` is still GitLab's own 400 on either id

deviation
Next
GET
/api/v4/projects/{id}/(-/)search
deviation
Next
GET
/api/v4/admin/search/migrations

refuse — Elasticsearch migrations and Zoekt shards describe the instance search cluster, are admin-only, and say nothing about the org

deferred
Next
GET
/api/v4/admin/search/migrations/{migration_id}

refuse — Elasticsearch migrations and Zoekt shards describe the instance search cluster, are admin-only, and say nothing about the org

deferred
Next
GET
/api/v4/admin/zoekt/shards

refuse — Elasticsearch migrations and Zoekt shards describe the instance search cluster, are admin-only, and say nothing about the org

deferred
Next
GET
/api/v4/admin/zoekt/shards/{node_id}/indexed_namespaces

refuse — Elasticsearch migrations and Zoekt shards describe the instance search cluster, are admin-only, and say nothing about the org

deferred
Next
GET
/api/v4/projects/{id}/(-/)search/semantic

refuse — GitLab restricts semantic search to Duo Enterprise with an embeddings index built over the repository, and olympus-labs holds no Duo subscription — GitLab's own 403 for an unlicensed namespace is the whole answer. 403 RATHER THAN 404, and the status is the ruling: the vendored operation declares both, and the one that matches the condition the reason names is the plan refusal — the project resolves and the caller may read it, what is missing is the subscription. TWO ROUTES, ONE ROW: the manifest template embeds GitLab's dashed and undashed spellings, exactly as /api/v4/projects/{id}/(-/)search does, and both carry the same x-sandboxapis-refusal row key. UNGATED: no generation of this universe holds a Duo subscription or an embeddings index. RULED by the founder 2026-09-12

deferred
Next
templates8 of 8 served & verified
GET
/api/v4/templates/dockerfiles

the image-build starting points this INSTANCE bundles, off reader.refCatalog(`gitlab.dockerfile-templates`) - the half of the template family `gitignore_template` (163 rows) and `license_catalog` (13) have carried since hello-16, which ./depth-catalogs.ts has served at `/api/v4/templates/{gitignores,licenses}` ever since. THIS IS A FACT ABOUT THE HOST, NOT ABOUT olympus-labs: the question is what starting points GitLab offers anybody opening a new project, so an [] here would be a false claim about GitLab rather than about this organization - the same distinction `hasRefCatalogs()` already draws for the licence catalogue. THE NAMES ARE FACTUAL AND THE BODIES ARE OURS, and `canon/src/ref-catalogs/index.ts` draws the line for exactly this case: a catalogue is factual when its content is an identifier a client USES (an MIT licence body, a `.gitignore` source) and fictional when it is content that decorates the story. Nobody compares a build template - it is a starting point a person edits - and GitLab's own bundled templates are MIT-licensed source from its repository, so shipping them inside every artifact would redistribute a third party's code without its notice, which is the licence finding that leaves GitHub's `/emojis` and `/codes_of_conduct` uncovered to this day. WHICH THREE ECOSYSTEMS IS NOT A CHOICE EITHER: parthenon's head tree carries a `package.json`, a `go.mod` and a `requirements.txt`, the same evidence `render-sbom.ts` parses, and conformance fails if a fourth appears. Each entry is `{key, name}` with the two equal, because a build template is its own key exactly as an ignore-file template is (`Gitlab::Template::BaseTemplate` derives both from the file name). SERVED 2026-09-14 (hello-20 `providerTails`), gated on hasProviderTails(); an older artifact answers the named hello-20 template gap rather than [].

served & verified
Long tail
GET
/api/v4/templates/dockerfiles/{name}

one image-build template by the KEY the list operation above hands a caller, off the same `gitlab.dockerfile-templates` catalogue, with the body written in canon for the licence reason that row states. CASE-SENSITIVE, as upstream's own file names are and as `/api/v4/templates/gitignores/{name}` already is on this host: `Node` resolves and `node` does not. A name outside the catalogue is GitLab's own `404 Template Not Found` rather than a coverage miss - the catalogue resolves, this name is simply not in it - and a MALFORMED ESCAPE is the same 404 rather than a 500, because no path parameter on this host is decoded a second time (the `%ZZ` class fixed fleet-wide in #481). Conformance drives the wrong case, an unknown name and `%ZZ`, and asserts the served body is the catalogue's own bytes rather than a re-composition. SERVED 2026-09-14 (hello-20 `providerTails`), gated on hasProviderTails(); an older artifact answers the named hello-20 template gap.

served & verified
Long tail
GET
/api/v4/templates/gitignores

SERVED 2026-09-12 (coverage wave W8) (hello-16 `refCatalogs`): reader.listGitignoreTemplates() over `license_catalog` (13 rows, bodies included) and `gitignore_template` (163 rows, source included) — the SAME two compile-time tables GitHub's /licenses and /gitignore/templates already serve, which is what makes a licence fetched through either host the same licence (invariant #5). GENERATION-GATED and `[]` is not an option: an artifact without the catalogs answers the template-catalog generation gap, because `[]` here would say GitLab bundles no licence templates — a claim about the HOST rather than about this organization, and a false one. licence: FACTUAL/CC0 — the licence texts are the licences themselves and the ignore-file templates are github/gitignore's CC0-1.0 corpus, the same grant coverage/reviews/2026-09-09-hello-15-stale-reasons-git-hosts.md records for the GitHub twins. `Entities::TemplatesList` is `{key, name}`, and an ignore-file template is its own key: GitLab derives both from the file name, which conformance asserts rather than assumes

served & verified
Long tail
GET
/api/v4/templates/gitignores/{name}

SERVED 2026-09-12 (coverage wave W8) (hello-16 `refCatalogs`): reader.gitignoreTemplate(name) over `license_catalog` (13 rows, bodies included) and `gitignore_template` (163 rows, source included) — the SAME two compile-time tables GitHub's /licenses and /gitignore/templates already serve, which is what makes a licence fetched through either host the same licence (invariant #5). GENERATION-GATED and `[]` is not an option: an artifact without the catalogs answers the template-catalog generation gap, because `[]` here would say GitLab bundles no licence templates — a claim about the HOST rather than about this organization, and a false one. licence: FACTUAL/CC0 — the licence texts are the licences themselves and the ignore-file templates are github/gitignore's CC0-1.0 corpus, the same grant coverage/reviews/2026-09-09-hello-15-stale-reasons-git-hosts.md records for the GitHub twins. CASE-SENSITIVE, as upstream's own file names are: `Node` resolves and `node` does not, which is what gitlab.com does and what GitHub's `/gitignore/templates/{name}` does on this same table

served & verified
Long tail
GET
/api/v4/templates/gitlab_ci_ymls

the pipeline starting points this INSTANCE bundles, off reader.refCatalog(`gitlab.ci-templates`) - the twin of `/api/v4/templates/dockerfiles` and served by the same pair of handlers over one table, so the two families cannot page differently from one another. Everything that row says applies unchanged: it is a fact about the HOST rather than about olympus-labs, so [] would be a false claim about GitLab; the NAMES are factual and the BODIES are this project's own, because nobody compares a build template and GitLab's own are MIT-licensed source this project holds no grant to redistribute; and which three ecosystems is read off parthenon's head tree rather than chosen. SERVED 2026-09-14 (hello-20 `providerTails`), gated on hasProviderTails(); an older artifact answers the named hello-20 template gap rather than [].

served & verified
Long tail
GET
/api/v4/templates/gitlab_ci_ymls/{name}

one pipeline template by the KEY the list operation hands a caller, off the same `gitlab.ci-templates` catalogue. Case-sensitive as upstream's own file names are; a name outside the catalogue is GitLab's own `404 Template Not Found` rather than a coverage miss, and `%ZZ` is that same 404 rather than a 500 because no path parameter on this host is decoded a second time (the class fixed fleet-wide in #481). SERVED 2026-09-14 (hello-20 `providerTails`), gated on hasProviderTails(); an older artifact answers the named hello-20 template gap.

served & verified
Long tail
GET
/api/v4/templates/licenses

SERVED 2026-09-12 (coverage wave W8) (hello-16 `refCatalogs`): reader.listLicenses() over `license_catalog` (13 rows, bodies included) and `gitignore_template` (163 rows, source included) — the SAME two compile-time tables GitHub's /licenses and /gitignore/templates already serve, which is what makes a licence fetched through either host the same licence (invariant #5). GENERATION-GATED and `[]` is not an option: an artifact without the catalogs answers the template-catalog generation gap, because `[]` here would say GitLab bundles no licence templates — a claim about the HOST rather than about this organization, and a false one. licence: FACTUAL/CC0 — the licence texts are the licences themselves and the ignore-file templates are github/gitignore's CC0-1.0 corpus, the same grant coverage/reviews/2026-09-09-hello-15-stale-reasons-git-hosts.md records for the GitHub twins. `popular` IS the catalog's own `featured` flag rather than a second judgement, and `?popular=true` narrows on it. `content` is the stored body VERBATIM — upstream's `[fullname]` placeholder included — because only the single-template operation declares the parameters that expand it. `nickname`, `html_url` and `source_url` are OMITTED: canon's licence row deliberately stores no URL and no nickname (a URL is a host's rendering of the row, not a fact about the licence), GitLab points both URLs at choosealicense.com, which is a link out of the sandbox, and this host publishes no licence page to point at instead

served & verified
Long tail
GET
/api/v4/templates/licenses/{name}

SERVED 2026-09-12 (coverage wave W8) (hello-16 `refCatalogs`): reader.license(key) over `license_catalog` (13 rows, bodies included) and `gitignore_template` (163 rows, source included) — the SAME two compile-time tables GitHub's /licenses and /gitignore/templates already serve, which is what makes a licence fetched through either host the same licence (invariant #5). GENERATION-GATED and `[]` is not an option: an artifact without the catalogs answers the template-catalog generation gap, because `[]` here would say GitLab bundles no licence templates — a claim about the HOST rather than about this organization, and a false one. licence: FACTUAL/CC0 — the licence texts are the licences themselves and the ignore-file templates are github/gitignore's CC0-1.0 corpus, the same grant coverage/reviews/2026-09-09-hello-15-stale-reasons-git-hosts.md records for the GitHub twins. `{name}` is the licence KEY the collection's own `key` field hands a caller, and a key outside the thirteen is GitLab's own 404 rather than a coverage miss — the catalog resolves, this key is simply not in it. The two documented parameters are HONOURED rather than ignored: `project` and `fullname` substitute upstream's `[project]` and `[fullname]` the way LicenseTemplate#resolve! does. `[year]` is deliberately NOT substituted — real GitLab fills it from the wall clock, and a replica whose answer changed with the calendar would stop being byte-reproducible across the pins

served & verified
Long tail
variables5 of 8 served & verified
GET
/api/v4/groups/{id}/variables

the group's own CI/CD variables, off reader.orgCiBindings(org, 'pipeline') for the organization's top-level group and reader.namespaceCiBindings(ns) for the subgroup — two scopes, two sets, resolved through the one groupTarget every group family shares. A `selected` org binding is still a group variable here: visibility is GitHub's vocabulary and GitLab has none, a group variable reaching every project in the group — and conformance asserts that the selected set IS every repository this organization has, so the day a second one appears the row reds rather than over-serving. SERVED 2026-09-12 (hello-19 `ciBindings`): the 2026-09-09 re-scope asked for a variable table and named the constraint that would make one honest — a masked value is a secret canon stores none of. hello-19's `ci_binding` is that table, built on exactly that constraint: one row, one `masked` boolean, and NO VALUE IN ANY COLUMN on a masked row (`compiler/src/hello19.test.ts` asserts `COUNT(*) WHERE masked = 1 AND value IS NOT NULL` is 0 on every compiled artifact). A MASKED BINDING RENDERS `hidden: true` AND SHIPS NO `value` KEY, which is GitLab's own vocabulary rather than a convenience: `masked` means blanked out of JOB LOGS and `hidden` (created as `masked_and_hidden`) means THE API NEVER RETURNS THE VALUE, so `masked: true, hidden: false` with the key dropped would be a shape real GitLab never sends. An unmasked binding's `value` is the artifact row `ci_binding.value_source` names — the collector's endpoint, the default branch, the orb document's tree path — resolved at compile time, so the setting and the thing it configures cannot drift apart. `variable_type` is `env_var` on every row, which agrees with /api/v4/projects/{id}/secure_files being ruled empty: a `file` variable IS a secure file. `raw` and `description` are OMITTED rather than defaulted — neither is required by APIEntitiesCiVariable and neither is a fact canon holds (the depth-hooks.ts rule, one wave earlier). Conformance drives each row against the vendored schema AND against the reader accessor the renderer must have used, walks the pages with the headers asserted, and fails if a masked row ever carries a value. Gated on hasCiBindings(); an older artifact answers the named hello-19 CI-variable gap, because [] there would say this organization configures its pipelines with nothing while the same artifact publishes a CI document, a nightly schedule and deployments to two environments

served & verified
Long tail
GET
/api/v4/groups/{id}/variables/{key}

the same group variable by key, byte-identical to the list's own row (conformance compares them), with the operation's own `404 Group Variable Not Found` for a key this scope does not hold — a subgroup's key on the organization's group included, because they are different scopes. NO decodeURIComponent on {key}: Hono has already decoded the segment, so a second decode would mis-match a key containing a percent sign and THROW on a malformed escape — `…/variables/%ZZ` is a key no variable has, answered 404. SERVED 2026-09-12 (hello-19 `ciBindings`): the 2026-09-09 re-scope asked for a variable table and named the constraint that would make one honest — a masked value is a secret canon stores none of. hello-19's `ci_binding` is that table, built on exactly that constraint: one row, one `masked` boolean, and NO VALUE IN ANY COLUMN on a masked row (`compiler/src/hello19.test.ts` asserts `COUNT(*) WHERE masked = 1 AND value IS NOT NULL` is 0 on every compiled artifact). A MASKED BINDING RENDERS `hidden: true` AND SHIPS NO `value` KEY, which is GitLab's own vocabulary rather than a convenience: `masked` means blanked out of JOB LOGS and `hidden` (created as `masked_and_hidden`) means THE API NEVER RETURNS THE VALUE, so `masked: true, hidden: false` with the key dropped would be a shape real GitLab never sends. An unmasked binding's `value` is the artifact row `ci_binding.value_source` names — the collector's endpoint, the default branch, the orb document's tree path — resolved at compile time, so the setting and the thing it configures cannot drift apart. `variable_type` is `env_var` on every row, which agrees with /api/v4/projects/{id}/secure_files being ruled empty: a `file` variable IS a secure file. `raw` and `description` are OMITTED rather than defaulted — neither is required by APIEntitiesCiVariable and neither is a fact canon holds (the depth-hooks.ts rule, one wave earlier). Conformance drives each row against the vendored schema AND against the reader accessor the renderer must have used, walks the pages with the headers asserted, and fails if a masked row ever carries a value. Gated on hasCiBindings(); an older artifact answers the named hello-19 CI-variable gap, because [] there would say this organization configures its pipelines with nothing while the same artifact publishes a CI document, a nightly schedule and deployments to two environments

served & verified
Long tail
GET
/api/v4/projects/{id}/triggers

A PIPELINE TRIGGER IS A TOKEN, and canon stores no token of any kind - the PagerDuty routing-key rule (2026-09-03) and hello-19's masked-binding rule, which is asserted as SQL on every compiled artifact (`COUNT(*) FROM ci_binding WHERE masked = 1 AND value IS NOT NULL` is 0). Nobody in olympus-labs ever created one, so the list is empty: §A-R6 of the hello-20 batch, founder-approved 2026-09-14, and the 2026-09-09 re-scope's own words - the trigger rows are the tokens that start a pipeline from outside, and canon stores none. WHAT THIS ORGANIZATION DOES START PIPELINES WITH IS ALREADY SERVED: hello-15's `workflow_schedule` answers `/api/v4/projects/{id}/pipeline_schedules`, which is the nightly release this universe really runs, so the empty here withholds nothing a caller cannot find. UNGATED: no generation stores a token; conformance drives it against the oldest generation in the suite. A collection under a project that resolves; the row addressed by `{trigger_id}` answers GitLab's own 404.

served & verified
Long tail
GET
/api/v4/projects/{id}/variables

the project's CI/CD variables, off reader.repoCiBindings(repo, 'pipeline') followed by each deployment environment's rows through reader.environmentCiBindings(repo, name), published under `environment_scope`. THE ENVIRONMENT ROWS BELONG IN THIS LIST because GitLab has no per-environment variable endpoint at all — environment scoping is a FIELD on the project variable, and the by-key operation's own description (if there are multiple variables with the same key, use filter to select the correct environment_scope) is the vendored document describing exactly this case. The group's rows are NOT here — GitLab merges those into a JOB's environment, not into this endpoint — and neither is the coding agent's, which is the audience scoping every ci_binding accessor is written to enforce. SERVED 2026-09-12 (hello-19 `ciBindings`): the 2026-09-09 re-scope asked for a variable table and named the constraint that would make one honest — a masked value is a secret canon stores none of. hello-19's `ci_binding` is that table, built on exactly that constraint: one row, one `masked` boolean, and NO VALUE IN ANY COLUMN on a masked row (`compiler/src/hello19.test.ts` asserts `COUNT(*) WHERE masked = 1 AND value IS NOT NULL` is 0 on every compiled artifact). A MASKED BINDING RENDERS `hidden: true` AND SHIPS NO `value` KEY, which is GitLab's own vocabulary rather than a convenience: `masked` means blanked out of JOB LOGS and `hidden` (created as `masked_and_hidden`) means THE API NEVER RETURNS THE VALUE, so `masked: true, hidden: false` with the key dropped would be a shape real GitLab never sends. An unmasked binding's `value` is the artifact row `ci_binding.value_source` names — the collector's endpoint, the default branch, the orb document's tree path — resolved at compile time, so the setting and the thing it configures cannot drift apart. `variable_type` is `env_var` on every row, which agrees with /api/v4/projects/{id}/secure_files being ruled empty: a `file` variable IS a secure file. `raw` and `description` are OMITTED rather than defaulted — neither is required by APIEntitiesCiVariable and neither is a fact canon holds (the depth-hooks.ts rule, one wave earlier). Conformance drives each row against the vendored schema AND against the reader accessor the renderer must have used, walks the pages with the headers asserted, and fails if a masked row ever carries a value. Gated on hasCiBindings(); an older artifact answers the named hello-19 CI-variable gap, because [] there would say this organization configures its pipelines with nothing while the same artifact publishes a CI document, a nightly schedule and deployments to two environments

served & verified
Long tail
GET
/api/v4/projects/{id}/variables/{key}

the same project variable by key, with `filter[environment_scope]` honoured as a real selector — four of this project's variables share a key with another (DEPLOY_ENVIRONMENT and ENVIRONMENT_DEPLOY_TOKEN, once per environment), which is the case the operation's description tells a caller to use the filter for, and conformance drives every duplicated key through every scope it exists in plus one it does not. GitLab's own `404 Variable Not Found` for an unknown key, `%ZZ` included. SERVED 2026-09-12 (hello-19 `ciBindings`): the 2026-09-09 re-scope asked for a variable table and named the constraint that would make one honest — a masked value is a secret canon stores none of. hello-19's `ci_binding` is that table, built on exactly that constraint: one row, one `masked` boolean, and NO VALUE IN ANY COLUMN on a masked row (`compiler/src/hello19.test.ts` asserts `COUNT(*) WHERE masked = 1 AND value IS NOT NULL` is 0 on every compiled artifact). A MASKED BINDING RENDERS `hidden: true` AND SHIPS NO `value` KEY, which is GitLab's own vocabulary rather than a convenience: `masked` means blanked out of JOB LOGS and `hidden` (created as `masked_and_hidden`) means THE API NEVER RETURNS THE VALUE, so `masked: true, hidden: false` with the key dropped would be a shape real GitLab never sends. An unmasked binding's `value` is the artifact row `ci_binding.value_source` names — the collector's endpoint, the default branch, the orb document's tree path — resolved at compile time, so the setting and the thing it configures cannot drift apart. `variable_type` is `env_var` on every row, which agrees with /api/v4/projects/{id}/secure_files being ruled empty: a `file` variable IS a secure file. `raw` and `description` are OMITTED rather than defaulted — neither is required by APIEntitiesCiVariable and neither is a fact canon holds (the depth-hooks.ts rule, one wave earlier). Conformance drives each row against the vendored schema AND against the reader accessor the renderer must have used, walks the pages with the headers asserted, and fails if a masked row ever carries a value. Gated on hasCiBindings(); an older artifact answers the named hello-19 CI-variable gap, because [] there would say this organization configures its pipelines with nothing while the same artifact publishes a CI document, a nightly schedule and deployments to two environments

served & verified
Long tail
GET
/api/v4/admin/ci/variables

refuse — GitLab answers 403 to a non-admin token here (Geo, Sidekiq, storage moves, LDAP, application settings, feature toggles, background migrations); a tenant-shaped mirror has no instance to administer

deferred
Long tail
GET
/api/v4/admin/ci/variables/{key}

refuse — GitLab answers 403 to a non-admin token here (Geo, Sidekiq, storage moves, LDAP, application settings, feature toggles, background migrations); a tenant-shaped mirror has no instance to administer

deferred
Long tail
GET
/api/v4/projects/{id}/triggers/{trigger_id}

refuse — the by-id half of a trigger list that is honestly empty: a pipeline trigger IS a token, canon stores no token of any kind (the PagerDuty routing-key rule, 2026-09-03), and nobody in olympus-labs ever created one - so no `{trigger_id}` can resolve and GitLab's own 404 is the whole answer. §A-R6 of the hello-20 batch, founder-approved 2026-09-14, and the split `decisions/2026-09-12-0348` names: the collection answers `200 []` under a parent that resolves, the row addressed by an id over it answers the provider's 404. UNGATED: no generation stores a token. Driven in conformance through a project that resolves.

deferred
Long tail
vulnerabilities2 of 8 served & verified
GET
/api/v4/projects/{id}/vulnerabilities

NO SECURITY PRODUCT IS ENABLED ON olympus-labs AND NO VULNERABILITY EXISTS - the founder's ruling of 2026-09-12 on `/projects/{id}/vulnerabilities/{noteable_id}/notes`, applied to its own family as §A-R1 of the hello-20 batch and approved 2026-09-14. AND IT CANNOT BE BUILT, which is what makes this permanent rather than a deferral: `render-sbom.ts` parses this repository's REAL manifests, so the components this universe declares are `express@4.18.2`, `github.com/google/uuid v1.3.0` and `httpx==0.24.1` - real packages at real versions - and a generated finding over them would be a false claim about a third party's software. The same fact is why hello-20's F8 derives this namespace's plan rung as `premium` rather than Ultimate, so the money surface and this one agree. UNGATED: no generation of this universe holds a SecurityFinding (`packages/canon/src/notes.ts` records it as the entity hello-17's entity-note domain is still missing), so the empty is true on every artifact and on every frozen pin - conformance drives it against the OLDEST generation in the suite rather than against hello-20, which is what would catch a generation gap wearing a ruling's clothes. A collection under a project that resolves; the six rows in this family addressed by an id or a digest answer GitLab's own 404 instead.

served & verified
Long tail
GET
/api/v4/projects/{id}/vulnerability_findings

the same fact one operation along: a FINDING is what a scanner reports before anybody triages it into a vulnerability, and no scanner ran here. NO SECURITY PRODUCT IS ENABLED ON olympus-labs (§A-R1 of the hello-20 batch, founder-approved 2026-09-14, extending the ruling of 2026-09-12), and it cannot be built without making a false claim about the real third-party packages `render-sbom.ts` publishes from this repository's own manifests. UNGATED: no generation of this universe holds a SecurityFinding, so the empty is true on every artifact; conformance drives it against the oldest generation in the suite. A collection under a project that resolves.

served & verified
Long tail
GET
/api/v4/security/vulnerability_archive_exports/{id}

refuse — the export object can only be created by a POST we refuse, so the by-id read can only ever be the vendor's own 404

deferred
Long tail
GET
/api/v4/security/vulnerability_archive_exports/{id}/download

refuse — the export object can only be created by a POST we refuse, so the by-id read can only ever be the vendor's own 404

deferred
Long tail
GET
/api/v4/security/vulnerability_exports/{id}

refuse — the export object can only be created by a POST we refuse, so the by-id read can only ever be the vendor's own 404

deferred
Long tail
GET
/api/v4/security/vulnerability_exports/{id}/download

refuse — the export object can only be created by a POST we refuse, so the by-id read can only ever be the vendor's own 404

deferred
Long tail
GET
/api/v4/vulnerabilities/{id}

refuse — no vulnerability exists in this organization - canon carries no SecurityFinding, so no vulnerability id resolves, and GitLab's own 404 for an unknown vulnerability is the whole answer. That is the founder's ruling of 2026-09-12, quoted from the two `/projects/{id}/vulnerabilities/{noteable_id}/notes` rows it was written for, applied to the operation those rows hang off as §A-R1 of the hello-20 batch (founder-approved 2026-09-14). It cannot be built: a finding would have to be about the REAL third-party packages this repository's own manifests declare. UNGATED: `packages/canon/src/notes.ts` records `SecurityFinding` as the entity hello-17's entity-note domain is still missing, so no generation of this universe holds one.

deferred
Long tail
GET
/api/v4/vulnerabilities/{id}/issue_links

refuse — the issues linked to a vulnerability, and the vulnerability is what does not exist - so this refuses with its parent rather than answering [], exactly as the two `/vulnerabilities/{noteable_id}/notes` rows the founder ruled on 2026-09-12 do: a 200 [] here would assert the vulnerability exists and nobody linked an issue to it. §A-R1 of the hello-20 batch, founder-approved 2026-09-14. UNGATED: no generation of this universe holds a SecurityFinding. The ISSUE LINKS THIS UNIVERSE DOES HAVE are served: hello-17's `issue_link` answers the four issue-to-issue rows, so the refusal here is about the vulnerability end and nothing else.

deferred
Long tail
clusters0 of 7 served & verified
GET
/api/v4/admin/clusters

refuse — certificate-based cluster integration is disabled on GitLab.com and removed upstream; mirroring it would advertise a surface no current client can reach

deferred
Long tail
GET
/api/v4/admin/clusters/{cluster_id}

refuse — certificate-based cluster integration is disabled on GitLab.com and removed upstream; mirroring it would advertise a surface no current client can reach

deferred
Long tail
GET
/api/v4/discover-cert-based-clusters

refuse — certificate-based cluster integration is disabled on GitLab.com and removed upstream; mirroring it would advertise a surface no current client can reach

deferred
Long tail
GET
/api/v4/groups/{id}/clusters

refuse — certificate-based cluster integration is disabled on GitLab.com and removed upstream; mirroring it would advertise a surface no current client can reach

deferred
Long tail
GET
/api/v4/groups/{id}/clusters/{cluster_id}

refuse — certificate-based cluster integration is disabled on GitLab.com and removed upstream; mirroring it would advertise a surface no current client can reach

deferred
Long tail
GET
/api/v4/projects/{id}/clusters

refuse — certificate-based cluster integration is disabled on GitLab.com and removed upstream; mirroring it would advertise a surface no current client can reach

deferred
Long tail
GET
/api/v4/projects/{id}/clusters/{cluster_id}

refuse — certificate-based cluster integration is disabled on GitLab.com and removed upstream; mirroring it would advertise a surface no current client can reach

deferred
Long tail
badges6 of 6 served & verified
GET
/api/v4/groups/{id}/badges

the group's own badges, off reader.projectBadges('group', org) — ONE row, a `pipeline` badge, and its derivation REPLACES THE FALSE ONE THIS ROW CARRIED. The old reason said `project READMEs in this universe carry pipeline and coverage badges`. THE README CARRIES NO BADGE: `themes/src/greek/files.ts`'s `readme()` writes a title, the repository's description, a `## Layout` list, a `## Development` block and one paragraph of prose — no image, no shield, no link — so a parse would return nothing; and the blob cannot be given one, because it is in `git_blob` on every frozen pin and on live and changing a character of it is a determinism sev-1. THE BADGE IS DERIVED FROM WHAT THIS ORGANIZATION DOES: it runs CI on every push — `job` holds 575 rows at 90 d across three workflows — so the badge sits on the GROUP, dated at the first job, and a group badge is INHERITED by every project under it, which is what makes this one row answer both this endpoint and the inherited half of `/projects/{id}/badges`. ADO's `/_apis/build/status/{definition}` is green in this fleet on exactly this reasoning (2026-09-03). AND THERE IS NO COVERAGE BADGE: nothing in this canon measures test coverage — `job` carries `build` and `test`, and the test job publishes RESULTS (`test_case`, `test_result`) rather than a percentage — so a coverage badge would publish a number nobody computed; `ProjectBadgeKind` has two members and conformance reds any badge whose name or URL says `coverage`. The badge's `name`, `link_url` and `image_url` are GitLab's own vocabulary and GitLab's own templates (`%{project_path}`, `%{default_branch}`), composed by the renderer from the container it is holding, because canon carries none of the three. `rendered_link_url`/`rendered_image_url` EQUAL the templates here and that is the provider's behaviour, not a stub: GitLab presents a group badge with no project and `Badge#rendered_link_url(nil)` returns the url unchanged. A SUBGROUP RESOLVES AND ANSWERS `[]` — `project_badge` is keyed `(scope_kind, scope)`, no row names a namespace, and GitLab's `Group#badges` lists the group's OWN badges without walking ancestors. The `name` filter the operation declares is not implemented and is answered by `filterNotCovered`'s explicit 400 rather than ignored. SERVED 2026-09-14 (hello-20 `glDelivery`): gated on hasGlDelivery(); an older artifact answers the named delivery generation gap

served & verified
Long tail
GET
/api/v4/groups/{id}/badges/{badge_id}

the group's badge by id, over the same reader.projectBadges('group', org) set the list is built from — byte-identical to the list's own row. THE DERIVATION THIS ROW CARRIED WAS FALSE and is replaced: there is no README badge and no coverage badge in this universe; the group's badge is a `pipeline` badge derived from the 575 CI jobs this organization ran, dated at the first of them (see `/api/v4/groups/{id}/badges` for the finding in full). A PROJECT BADGE'S ID DOES NOT RESOLVE HERE, and conformance asserts it: GitLab's `Group#badges` lists the group's own and does not walk down into its projects, which is the opposite direction from the project's union. `404 Badge Not Found` for an id nothing has and for `%ZZ` — no decodeURIComponent on the segment. `rendered_*` echoes the template, because a group badge is presented with no project to render against. SERVED 2026-09-14 (hello-20 `glDelivery`): gated on hasGlDelivery(); an older artifact answers the named delivery generation gap

served & verified
Long tail
GET
/api/v4/groups/{id}/badges/render

GitLab's badge PREVIEW at group scope: it takes the caller's own `link_url` and `image_url` — both `required: true`, and a missing one is GitLab's own 400 `{error: "… is missing"}` — and answers `APIEntitiesBasicBadgeDetails`. THE TWO PAIRS ARE EQUAL HERE AND THAT IS THE PROVIDER, NOT A STUB: `Badges::BuildService` builds the badge and the group routes present it with no project, so `Badge#rendered_link_url(nil)` returns the template unchanged — the project routes are where the placeholders expand. `name` is omitted because the caller supplied none and the component does not require it. THE DERIVATION THIS ROW CARRIED WAS FALSE and is replaced: no README in this universe carries a badge and nothing in this canon measures coverage; the badges that exist are a group `pipeline` badge off 575 CI jobs and a project `release` badge off three published tags (see `/api/v4/groups/{id}/badges`). SERVED 2026-09-14 (hello-20 `glDelivery`): gated on hasGlDelivery(); an older artifact answers the named delivery generation gap

served & verified
Long tail
GET
/api/v4/projects/{id}/badges

the project's badges the way GitLab COMPOSES them — its own rows then the ones it inherits, off reader.projectBadges('project', repo) followed by reader.projectBadges('group', org) and concatenated, because `Project#badges` is `Badge.from_union([project_badges, Badge.where(group: group.self_and_ancestors)])` and the accessor deliberately does not bake the host's composition in. The inherited badge reports `kind: "group"`, which is the only field in the response that says it came from above, and conformance asserts both halves are present. THE DERIVATION THIS ROW CARRIED WAS FALSE and is replaced: it said `project READMEs in this universe carry pipeline and coverage badges`, and the README carries NO badge — `themes/src/greek/files.ts`'s `readme()` writes a title, a description, a layout list, a development block and one paragraph of prose, with no image and no shield — and the blob cannot be given one without moving bytes every frozen pin is pinned to. THE PROJECT'S BADGE IS A `release` BADGE derived from what it publishes: `release` holds three tagged releases, each at a real commit, and the badge is dated at the first. THERE IS NO COVERAGE BADGE — nothing in this canon measures coverage, and a badge is a published number. `name`, `link_url` and `image_url` are GitLab's own vocabulary and templates, composed here; `rendered_*` EXPANDS them against this project, so every rendered URL names the path `/projects/{id}` prints and the branch it reports as `default_branch` (invariant #5). Pagination walked with the headers asserted; the `name` filter is answered by the explicit 400 rather than ignored. SERVED 2026-09-14 (hello-20 `glDelivery`): gated on hasGlDelivery(); an older artifact answers the named delivery generation gap

served & verified
Long tail
GET
/api/v4/projects/{id}/badges/{badge_id}

a project's badge by id, over the SAME two-scope composition the list uses — so the group's inherited badge resolves here too, which is GitLab's own behaviour (`find_badge` searches the union `Project#badges` is) and which conformance drives directly, asserting the inherited row comes back with `kind: "group"` and with its placeholders EXPANDED, because a project is what they resolve against. THE DERIVATION THIS ROW CARRIED WAS FALSE and is replaced: no README badge, no coverage badge; a group `pipeline` badge off 575 CI jobs and a project `release` badge off three published tags (see `/api/v4/projects/{id}/badges`). `404 Badge Not Found` for an id nothing has and for `%ZZ`. SERVED 2026-09-14 (hello-20 `glDelivery`): gated on hasGlDelivery(); an older artifact answers the named delivery generation gap

served & verified
Long tail
GET
/api/v4/projects/{id}/badges/render

GitLab's badge PREVIEW at project scope, and the route where the templates actually expand: the caller's `link_url` and `image_url` (both `required: true`; a missing one is GitLab's own 400) resolved against this project through GitLab's six placeholders — `%{project_path}`, `%{project_name}`, `%{project_title}`, `%{project_id}`, `%{default_branch}`, `%{commit_sha}`, `%{latest_tag}` — every one read off the artifact (the path `/projects/{id}` prints, the default branch's head sha, the newest release's tag), so a rendered URL names something this host itself serves. A placeholder outside the table is LEFT ALONE rather than blanked, which is what GitLab does with one it does not know. THE DERIVATION THIS ROW CARRIED WAS FALSE and is replaced: no README in this universe carries a badge and nothing here measures coverage; the badges that exist are a group `pipeline` badge off 575 CI jobs and a project `release` badge off three published tags (see `/api/v4/projects/{id}/badges`). SERVED 2026-09-14 (hello-20 `glDelivery`): gated on hasGlDelivery(); an older artifact answers the named delivery generation gap

served & verified
Long tail
branches-tags5 of 6 served & verified
GET
/api/v4/projects/{id}/repository/branches
served & verified
Core
GET
/api/v4/projects/{id}/repository/branches/{branch}
served & verified
Core
HEAD
/api/v4/projects/{id}/repository/branches/{branch}
served & verified
Core
GET
/api/v4/projects/{id}/repository/tags
served & verified
Core
GET
/api/v4/projects/{id}/repository/tags/{tag_name}
served & verified
Core
GET
/api/v4/projects/{id}/repository/tags/{tag_name}/signature

refuse — no commit or tag in this universe is GPG-signed, and GitLab answers its own 404 'GPG Signature Not Found' for an unsigned object

planned
Core
cluster agents5 of 6 served & verified
GET
/api/v4/projects/{id}/cluster_agents

the Kubernetes agents that performed this project's deployments, off reader.clusterAgents(repo) in environment order — ONE PER (project, environment) THAT WAS DEPLOYED TO, which is the narrowest claim the rows support: `deployment` says production and staging were, and says nothing about a third; conformance asserts the served set is exactly the set of environments `/projects/{id}/environments` lists. The agent is dated at the FIRST deploy to its environment and was registered by whoever ran it (`created_by_user_id` resolves on `/users/{id}`). ITS NAME IS COMPOSED BY THE RENDERER from `(project, environment)` — canon carries the environment as a closed enum and no name at all, `ProjectBadge`'s rule one entity along — and the pair is the table's primary key, so two agents can never be handed one name. `is_receptive` IS FALSE AND IS LOAD-BEARING: a receptive agent is one GitLab connects OUT to, these connect IN (which is what a deploy run from a pipeline is), and it is the same fact that makes `…/url_configurations` an honest empty. NO TOKEN VALUE ON ANY SHAPE IN THIS DOMAIN (hello-19's masked-binding rule). `config_project` is the seven-field `APIEntitiesProjectIdentity`, every value the one `/projects/{id}` publishes. SERVED 2026-09-14 (hello-20 `glDelivery`): gated on hasGlDelivery(); an older artifact answers the named delivery generation gap, because `[]` there would say nothing performed the deployments the same artifact records against two environments

served & verified
Long tail
GET
/api/v4/projects/{id}/cluster_agents/{agent_id}

the same agent by id, off reader.clusterAgent(repo, id) and byte-identical to the list's own row. The parent resolves FIRST, so an unknown project is GitLab's own `404 Project Not Found` on every generation and an agent id this project does not have is `404 Agent Not Found` — `%ZZ` included, with no decodeURIComponent on the segment. See `/api/v4/projects/{id}/cluster_agents` for the derivation, the composed name and why `is_receptive` is false. SERVED 2026-09-14 (hello-20 `glDelivery`): gated on hasGlDelivery(); an older artifact answers the named delivery generation gap

served & verified
Long tail
GET
/api/v4/projects/{id}/cluster_agents/{agent_id}/tokens

the agent's ACTIVE tokens — exactly one, always, because an agent in this canon is registered with one token and keeps it, which is why the token is four columns on `cluster_agent` rather than a sixth table. A NAME AND TWO DATES AND NO VALUE: GitLab returns an agent token's secret once, in the 201 of a create this replica refuses, and never again, so no shape in this domain carries a `token` key and conformance reds one that does (hello-19's masked-binding rule, the PagerDuty routing-key precedent of 2026-09-03). The token's name is composed from the agent's, its id from the agent's canonical id, and `created_by_user_id` is the person who registered the agent. `last_used_at` IS ABSENT HERE ON PURPOSE: the list declares `APIEntitiesClustersAgentTokenBasic`, which does not carry it, and the by-id read declares `APIEntitiesClustersAgentToken`, which does — GitLab's own split between two entities, mirrored rather than flattened. `description` is omitted: canon holds no text for the token and the field is optional. The agent resolves before the token list, so an unknown agent is GitLab's 404. SERVED 2026-09-14 (hello-20 `glDelivery`): gated on hasGlDelivery(); an older artifact answers the named delivery generation gap

served & verified
Long tail
GET
/api/v4/projects/{id}/cluster_agents/{agent_id}/tokens/{token_id}

the agent's one token by id, `APIEntitiesClustersAgentToken` — the list's row plus `last_used_at`, which is `cluster_agent.token_last_used_epoch`: the MOST RECENT deploy to the environment this agent serves, i.e. the last time it did anything, and conformance recomputes it from `deployment` rather than trusting the column. STILL NO VALUE: the secret is not a stored fact in any generation. An id that is not this agent's one token is the operation's own 404 — its description says a revoked token answers 404 — and so is ANOTHER agent's token id under this agent, which conformance drives. `%ZZ` is the same 404, with no decodeURIComponent. SERVED 2026-09-14 (hello-20 `glDelivery`): gated on hasGlDelivery(); an older artifact answers the named delivery generation gap

served & verified
Long tail
GET
/api/v4/projects/{id}/cluster_agents/{agent_id}/url_configurations

A URL CONFIGURATION BELONGS TO A RECEPTIVE AGENT - one GitLab connects OUT to - and every agent in this universe connects IN, which is what a deploy run from a pipeline is. The served `…/cluster_agents/{agent_id}` rows publish `is_receptive: false`, so this empty and that flag are one fact said twice rather than two claims that could drift. THE AGENT RESOLVES FIRST, which is what makes the empty honest rather than confident: an `{agent_id}` this project has not got answers `404 Agent Not Found`, the same body the by-id route gives, and an unknown project answers `404 Project Not Found`. Unpaged, because the operation declares no `page`/`per_page`. RULED 2026-09-14 by the hello-20 GitLab serving wave, on the F6 `glDelivery` decision's §5

served & verified
Long tail
GET
/api/v4/projects/{id}/cluster_agents/{agent_id}/url_configurations/{url_configuration_id}

refuse — the by-id half of the URL-configuration list, which is empty because a URL configuration belongs to a RECEPTIVE agent and every agent in this universe connects IN - the served `…/cluster_agents/{agent_id}` rows publish `is_receptive: false`. No configuration exists for any agent here, receptive or not, so the refusal does not depend on which agent was named and GitLab's own 404 is the whole answer. The project resolves first. RULED 2026-09-14 by the hello-20 GitLab serving wave, on the F6 `glDelivery` decision's §5

deferred
Long tail
custom attributes0 of 6 served & verified
GET
/api/v4/groups/{id}/custom_attributes

refuse — GitLab restricts custom attributes to instance administrators; a non-admin token gets 403

deferred
Long tail
GET
/api/v4/groups/{id}/custom_attributes/{key}

refuse — GitLab restricts custom attributes to instance administrators; a non-admin token gets 403

deferred
Long tail
GET
/api/v4/projects/{id}/custom_attributes

refuse — GitLab restricts custom attributes to instance administrators; a non-admin token gets 403

deferred
Long tail
GET
/api/v4/projects/{id}/custom_attributes/{key}

refuse — GitLab restricts custom attributes to instance administrators; a non-admin token gets 403

deferred
Long tail
GET
/api/v4/users/{id}/custom_attributes

refuse — GitLab restricts custom attributes to instance administrators; a non-admin token gets 403

deferred
Long tail
GET
/api/v4/users/{id}/custom_attributes/{key}

refuse — GitLab restricts custom attributes to instance administrators; a non-admin token gets 403

deferred
Long tail
deployments4 of 6 served & verified
GET
/api/v4/groups/{id}/protected_environments

olympus-labs protects only branches — canon's protected_ref carries two branch patterns at namespace scope and no environment, tag, merge train, push rule or external status check exists; GitLab answers `[]` at 200 for each of these lists and its own 404 for a named one. THE LIST HALF, served as GitLab's empty page with the pagination headers the operation declares. UNGATED: no generation of this universe has ever protected an environment or a tag, queued a merge train, registered an external status check or set a push rule, so every registered pin answers this same `[]`. Falsifiable: the `expectsEmpty` conformance entry reds the row the day canon grows one. RULED by the founder 2026-09-12

served & verified
Next
GET
/api/v4/projects/{id}/environments
served & verified
Next
GET
/api/v4/projects/{id}/environments/{environment_id}
served & verified
Next
GET
/api/v4/projects/{id}/protected_environments

olympus-labs protects only branches — canon's protected_ref carries two branch patterns at namespace scope and no environment, tag, merge train, push rule or external status check exists; GitLab answers `[]` at 200 for each of these lists and its own 404 for a named one. THE LIST HALF, served as GitLab's empty page with the pagination headers the operation declares. UNGATED: no generation of this universe has ever protected an environment or a tag, queued a merge train, registered an external status check or set a push rule, so every registered pin answers this same `[]`. Falsifiable: the `expectsEmpty` conformance entry reds the row the day canon grows one. RULED by the founder 2026-09-12

served & verified
Next
GET
/api/v4/groups/{id}/protected_environments/{name}

refuse — olympus-labs protects only branches — canon's protected_ref carries two branch patterns at namespace scope and no environment, tag, merge train, push rule or external status check exists; GitLab answers `[]` at 200 for each of these lists and its own 404 for a named one. THE NAMED HALF: no name resolves over a collection that is empty, so GitLab's own 404 is the whole answer — `[]` here would assert that the thing exists and holds nothing. UNGATED: no generation of this universe has ever protected an environment or a tag, queued a merge train, registered an external status check or set a push rule, so the 404 is true on every artifact. RULED by the founder 2026-09-12

deferred
Next
GET
/api/v4/projects/{id}/protected_environments/{name}

refuse — olympus-labs protects only branches — canon's protected_ref carries two branch patterns at namespace scope and no environment, tag, merge train, push rule or external status check exists; GitLab answers `[]` at 200 for each of these lists and its own 404 for a named one. THE NAMED HALF: no name resolves over a collection that is empty, so GitLab's own 404 is the whole answer — `[]` here would assert that the thing exists and holds nothing. UNGATED: no generation of this universe has ever protected an environment or a tag, queued a merge train, registered an external status check or set a push rule, so the 404 is true on every artifact. RULED by the founder 2026-09-12

deferred
Next
imports0 of 6 served & verified
GET
/api/v4/bulk_imports

refuse — each of these reads reports on an export or import that a WRITE would have started, and writes are refused; GitLab answers 404 for an export nobody requested

deferred
Long tail
GET
/api/v4/bulk_imports/{import_id}

refuse — each of these reads reports on an export or import that a WRITE would have started, and writes are refused; GitLab answers 404 for an export nobody requested

deferred
Long tail
GET
/api/v4/bulk_imports/{import_id}/entities

refuse — each of these reads reports on an export or import that a WRITE would have started, and writes are refused; GitLab answers 404 for an export nobody requested

deferred
Long tail
GET
/api/v4/bulk_imports/{import_id}/entities/{entity_id}

refuse — each of these reads reports on an export or import that a WRITE would have started, and writes are refused; GitLab answers 404 for an export nobody requested

deferred
Long tail
GET
/api/v4/bulk_imports/{import_id}/entities/{entity_id}/failures

refuse — each of these reads reports on an export or import that a WRITE would have started, and writes are refused; GitLab answers 404 for an export nobody requested

deferred
Long tail
GET
/api/v4/bulk_imports/entities

refuse — each of these reads reports on an export or import that a WRITE would have started, and writes are refused; GitLab answers 404 for an export nobody requested

deferred
Long tail
integrations5 of 6 served & verified
GET
/api/v4/groups/{id}/integrations

THIS ORGANIZATION WIRES ITS INTEGRATIONS AT THE PROJECT, AND GITLAB DISTINGUISHES THE TWO PLACEMENTS. hello-20's `project_integration` is keyed `(repo, kind)`: canon says the Slack workspace, the Jira project and the error tracker are the PROJECT's, and GitLab publishes a project row that came from the group with `inherited: true`. Every row this host serves at `/projects/{id}/integrations` carries `inherited: false`, so a group-level record would contradict it in the same breath - serving the three project rows here would tell a client this organization set group-wide defaults every project inherits, which is an over-claim rather than a gap. BOTH CONTAINERS ANSWER []: the subgroup owns no project to configure a default for (hello-16's founder ruling F1). Unpaged, because the group operation declares only `id`. THE ADJACENT ROW THAT IS SERVED IS NOT A COUNTER-EXAMPLE: `/api/v4/integrations/jira_forge/subscriptions` answers one row for this group because a GitLab-for-Jira Forge subscription is ALWAYS namespace-scoped and has no project-level counterpart at all, so the group is the only place this universe's Jira link can appear on that endpoint. RULED 2026-09-14 by the hello-20 GitLab serving wave: the F6 `glDelivery` decision counted this row servable, and the placement fact is what it could not be served on

served & verified
Long tail
GET
/api/v4/projects/{id}/integrations

the integrations this project is wired to, off reader.projectIntegrations(repo) — the Slack workspace `channel` rows belong to and the Jira project this fleet renders over the same issues, each dated `max(repo.createdOffset, the target's earliest instant)` so neither is claimed before both ends of it exist. `error-tracking` IS IN THE ARTIFACT AND IS DELIBERATELY NOT IN THIS LIST: GitLab's own `{slug}` enum, vendored in full on both by-slug operations, has no error-tracking member because GitLab models it as an `ErrorTracking::ProjectSetting` rather than as an `Integration` — so that kind drives `…/error_tracking/settings` and nothing else, and conformance reds the row if it ever appears here. `inherited` IS SENT AND IS FALSE because it is the one flag canon settles: `project_integration` is keyed `(repo, kind)`, so every row is the project's own rather than a group default it picked up, which is the same fact that makes `/groups/{id}/integrations` an honest empty. The fifteen event booleans (`push_events`, `issues_events`, `pipeline_events`, …) and the `properties` settings object are OMITTED rather than defaulted — canon records that the integration EXISTS and nothing about what it listens for or is configured with, none is required by the component, and an invented default is the placeholder invariant #4 forbids (the depth-hooks.ts rule). SERVED 2026-09-14 (hello-20 `glDelivery`): gated on hasGlDelivery(); an older artifact answers the named delivery generation gap (`noGlDelivery`), because `[]` there would say this project is wired to nothing while the same artifact carries a Slack workspace, a Jira project and an error tracker

served & verified
Long tail
GET
/api/v4/projects/{id}/integrations/{slug}

the same integration by GitLab's own slug, off reader.projectIntegration(repo, kind) through the `slack`/`jira` mapping the list uses — conformance asserts the by-slug answer is byte-identical to the list's own row, so one integration cannot be described two ways. `APIEntitiesIntegration` adds exactly one field over the list's component, `properties`, and it is OMITTED: an empty settings object is a claim that the integration is configured with nothing. A slug GitLab knows but this project has not turned on (`jenkins`), a slug outside the enum, and `error-tracking` are all the operation's own `404 Service Not Found` — a resolved parent and a missing child rather than a coverage miss. NO decodeURIComponent on `{slug}`: Hono has already decoded the segment, so a second decode would THROW on a malformed escape — `…/integrations/%ZZ` is a slug no integration has, answered 404 (the class PR #481 closed fleet-wide). SERVED 2026-09-14 (hello-20 `glDelivery`): gated on hasGlDelivery(); an older artifact answers the named delivery generation gap

served & verified
Long tail
GET
/api/v4/projects/{id}/services

GitLab's OWN ALIAS for `/projects/{id}/integrations` — same summary, same description, same `APIEntitiesIntegrationBasic` — so it is ONE handler rather than a second rendering that could drift, and conformance asserts BYTE EQUALITY with `/integrations` instead of re-checking the shape. See that row for what is read and what is deliberately omitted. SERVED 2026-09-14 (hello-20 `glDelivery`): gated on hasGlDelivery(); an older artifact answers the named delivery generation gap

served & verified
Long tail
GET
/api/v4/projects/{id}/services/{slug}

the by-slug half of GitLab's `/services` alias, byte-identical to `/projects/{id}/integrations/{slug}` and asserted so in conformance rather than rendered twice. Same `404 Service Not Found` for an unknown slug and for `%ZZ`. SERVED 2026-09-14 (hello-20 `glDelivery`): gated on hasGlDelivery(); an older artifact answers the named delivery generation gap

served & verified
Long tail
GET
/api/v4/groups/{id}/integrations/{slug}

refuse — the by-slug half of the group integration list, which is empty because this organization wires its integrations AT THE PROJECT - `project_integration` is keyed `(repo, kind)` and every served project row carries `inherited: false`. A group that configures no integration has no slug to resolve, for any member of GitLab's slug enum. GitLab's `find_integration!` renders `not_found!('Service')`, so this row NAMES its resource - `404 Service Not Found`, the same body the served `/projects/{id}/integrations/{slug}` gives for a slug this project has not turned on, which is one of the few places a GitLab refusal body differs from the coverage-miss body at all. The group resolves first. RULED 2026-09-14 by the hello-20 GitLab serving wave: the F6 `glDelivery` decision counted this row servable, and the placement fact is what it could not be served on

deferred
Long tail
licenses0 of 6 served & verified
GET
/api/v4/license

refuse — a self-managed license does not exist on a SaaS-shaped mirror and GitLab refuses these to non-admins; managed_licenses is the removed License-Compliance feature

deferred
Long tail
GET
/api/v4/license/{id}

refuse — a self-managed license does not exist on a SaaS-shaped mirror and GitLab refuses these to non-admins; managed_licenses is the removed License-Compliance feature

deferred
Long tail
GET
/api/v4/license/usage_export

refuse — a self-managed license does not exist on a SaaS-shaped mirror and GitLab refuses these to non-admins; managed_licenses is the removed License-Compliance feature

deferred
Long tail
GET
/api/v4/licenses

refuse — this replica mirrors a SaaS-shaped GitLab and holds no instance subscription of its own; GitLab's own 403 for a non-admin token on the licensing surface is the whole answer. THE OLD REASON MISFILED THE ROW: /api/v4/licenses is not the licence TEMPLATE catalog — its 200 is APIEntitiesGitlabLicense (plan, licensee, add_ons, overage, user_limit, historical_max), the same object /api/v4/license and /api/v4/license/{id} return, and both of those have been armed `refuse` at 403 since class 11 of the 2026-09-01 wave. The template catalog is /api/v4/templates/licenses, which this host has served green off license_catalog since wave W8 and which conformance asserts is untouched by this ruling. So this is class 11's own sentence applied to the plural path, and it takes class 11's status. UNGATED: no generation of this universe holds an instance subscription. RULED by the founder 2026-09-12 (round 20)

deferred
Long tail
GET
/api/v4/projects/{id}/managed_licenses

refuse — a self-managed license does not exist on a SaaS-shaped mirror and GitLab refuses these to non-admins; managed_licenses is the removed License-Compliance feature

deferred
Long tail
GET
/api/v4/projects/{id}/managed_licenses/{managed_license_id}

refuse — a self-managed license does not exist on a SaaS-shaped mirror and GitLab refuses these to non-admins; managed_licenses is the removed License-Compliance feature

deferred
Long tail
project import0 of 6 served & verified
GET
/api/v4/projects/{id}/export

refuse — each of these reads reports on an export or import that a WRITE would have started, and writes are refused; GitLab answers 404 for an export nobody requested

deferred
Long tail
GET
/api/v4/projects/{id}/export_relations/download

refuse — each of these reads reports on an export or import that a WRITE would have started, and writes are refused; GitLab answers 404 for an export nobody requested

deferred
Long tail
GET
/api/v4/projects/{id}/export_relations/status

refuse — each of these reads reports on an export or import that a WRITE would have started, and writes are refused; GitLab answers 404 for an export nobody requested

deferred
Long tail
GET
/api/v4/projects/{id}/export/download

refuse — each of these reads reports on an export or import that a WRITE would have started, and writes are refused; GitLab answers 404 for an export nobody requested

deferred
Long tail
GET
/api/v4/projects/{id}/import

refuse — each of these reads reports on an export or import that a WRITE would have started, and writes are refused; GitLab answers 404 for an export nobody requested

deferred
Long tail
GET
/api/v4/projects/{id}/relation-imports

refuse — each of these reads reports on an export or import that a WRITE would have started, and writes are refused; GitLab answers 404 for an export nobody requested

deferred
Long tail
protected-branches5 of 6 served & verified
GET
/api/v4/groups/{id}/protected_branches

features.namespaces (hello-16) adds protected_ref, a rule over a ref PATTERN at namespace scope - two rows, main and release/*. reader.protectedRefs('namespace', id) derives them; older pins answer the empty list. SERVED 2026-09-09 (hello-16)

served & verified
Next
GET
/api/v4/groups/{id}/protected_branches/{name}

the by-name twin of the group protection list, off the same reader.protectedRefs rows and accepting a url-encoded pattern (release%2F*); a pattern nobody holds is still GitLab's 404, and that is the whole answer on every pin older than hello-16. SERVED 2026-09-09 (hello-16)

served & verified
Next
GET
/api/v4/projects/{id}/protected_branches
served & verified
Next
GET
/api/v4/projects/{id}/protected_branches/{name}
served & verified
Next
GET
/api/v4/projects/{id}/protected_tags

olympus-labs protects only branches — canon's protected_ref carries two branch patterns at namespace scope and no environment, tag, merge train, push rule or external status check exists; GitLab answers `[]` at 200 for each of these lists and its own 404 for a named one. THE LIST HALF, served as GitLab's empty page with the pagination headers the operation declares. UNGATED: no generation of this universe has ever protected an environment or a tag, queued a merge train, registered an external status check or set a push rule, so every registered pin answers this same `[]`. Falsifiable: the `expectsEmpty` conformance entry reds the row the day canon grows one. RULED by the founder 2026-09-12

served & verified
Next
GET
/api/v4/projects/{id}/protected_tags/{name}

refuse — olympus-labs protects only branches — canon's protected_ref carries two branch patterns at namespace scope and no environment, tag, merge train, push rule or external status check exists; GitLab answers `[]` at 200 for each of these lists and its own 404 for a named one. THE NAMED HALF: no name resolves over a collection that is empty, so GitLab's own 404 is the whole answer — `[]` here would assert that the thing exists and holds nothing. UNGATED: no generation of this universe has ever protected an environment or a tag, queued a merge train, registered an external status check or set a push rule, so the 404 is true on every artifact. RULED by the founder 2026-09-12

deferred
Next
terraform0 of 6 served & verified
GET
/api/v4/packages/terraform/modules/v1/{module_namespace}/{module_name}/{module_system}

refuse — This address names a TERRAFORM MODULE. `package.kind` is npm, golang, pypi or generic on every row of every artifact - canon's deliberate enum, with `terraform_module` among the eleven GitLab types it does not carry - so no module exists at any namespace, name or system, and GitLab's own 404 is the whole answer. This organization's infrastructure story is CI configuration and a `Dockerfile`, not a published module registry (the latest version's metadata).

deferred
Long tail
GET
/api/v4/packages/terraform/modules/v1/{module_namespace}/{module_name}/{module_system}/download

refuse — This address names a TERRAFORM MODULE. `package.kind` is npm, golang, pypi or generic on every row of every artifact - canon's deliberate enum, with `terraform_module` among the eleven GitLab types it does not carry - so no module exists at any namespace, name or system, and GitLab's own 404 is the whole answer. This organization's infrastructure story is CI configuration and a `Dockerfile`, not a published module registry. Its documented success is a 302 to the archive rather than a 200, so even a module that existed could not be answered by a conformance row that requires 200 - the refusal is the whole answer here twice over.

deferred
Long tail
GET
/api/v4/packages/terraform/modules/v1/{module_namespace}/{module_name}/{module_system}/versions

refuse — This address names a TERRAFORM MODULE. `package.kind` is npm, golang, pypi or generic on every row of every artifact - canon's deliberate enum, with `terraform_module` among the eleven GitLab types it does not carry - so no module exists at any namespace, name or system, and GitLab's own 404 is the whole answer. This organization's infrastructure story is CI configuration and a `Dockerfile`, not a published module registry (the version list).

deferred
Long tail
GET
/api/v4/projects/{id}/packages/terraform/modules/{module_name}/{module_system}

refuse — This address names a TERRAFORM MODULE. `package.kind` is npm, golang, pypi or generic on every row of every artifact - canon's deliberate enum, with `terraform_module` among the eleven GitLab types it does not carry - so no module exists at any namespace, name or system, and GitLab's own 404 is the whole answer. This organization's infrastructure story is CI configuration and a `Dockerfile`, not a published module registry. Its documented success is a 204 carrying `X-Terraform-Get` rather than a 200.

deferred
Long tail
GET
/api/v4/projects/{id}/terraform/state/{name}

refuse — state describes real infrastructure the sandbox does not have; GitLab 404s a state name nobody wrote, and writes are refused

deferred
Long tail
GET
/api/v4/projects/{id}/terraform/state/{name}/versions/{serial}

refuse — state describes real infrastructure the sandbox does not have; GitLab 404s a state name nobody wrote, and writes are refused

deferred
Long tail
vscode0 of 6 served & verified
GET
/api/v4/vscode/settings_sync/{settings_context_hash}/v1/manifest

refuse — this is per-caller editor storage, not a record of the org; it 404s until the caller WRITES settings, and writes are refused

deferred
Long tail
GET
/api/v4/vscode/settings_sync/{settings_context_hash}/v1/resource/{resource_name}

refuse — this is per-caller editor storage, not a record of the org; it 404s until the caller WRITES settings, and writes are refused

deferred
Long tail
GET
/api/v4/vscode/settings_sync/{settings_context_hash}/v1/resource/{resource_name}/{id}

refuse — this is per-caller editor storage, not a record of the org; it 404s until the caller WRITES settings, and writes are refused

deferred
Long tail
GET
/api/v4/vscode/settings_sync/v1/manifest

refuse — this is per-caller editor storage, not a record of the org; it 404s until the caller WRITES settings, and writes are refused

deferred
Long tail
GET
/api/v4/vscode/settings_sync/v1/resource/{resource_name}

refuse — this is per-caller editor storage, not a record of the org; it 404s until the caller WRITES settings, and writes are refused

deferred
Long tail
GET
/api/v4/vscode/settings_sync/v1/resource/{resource_name}/{id}

refuse — this is per-caller editor storage, not a record of the org; it 404s until the caller WRITES settings, and writes are refused

deferred
Long tail
container-registry5 of 5 served & verified
GET
/api/v4/groups/{id}/registry/repositories

`container_repository` and `container_tag` through `containerRepositories()`, `containerTags()` and `containerTag()`. `location` is the image path behind `registry.` + this host's own name, which is how GitLab derives its own (`registry.gitlab.com` beside `gitlab.com`); `name` is the segment after the project's full path, so `""` here, because canon's one image is the ROOT image of the one repository carrying a `Dockerfile`. `delete_api_path`, `status` and `cleanup_policy_started_at` are omitted: nothing is scheduled for deletion and no caller here may delete anything. This route answers every image across the group's projects. The vendored GROUP operation declares neither `tags` nor `tags_count`, so neither is emitted here even though the project-scoped list answers both - mirroring each operation rather than the other one (invariant #2). The subgroup answers `[]`: founder ruling F1 leaves it no repository to keep an image in. SERVED 2026-09-14 (hello-20 `packageRegistry`): gated on `reader.hasPackageRegistry()`, with the project or group resolving FIRST so an unknown one is GitLab's own 404 on every generation. An older artifact - the 127 pins frozen before hello-20, and live until the hello-20 cutover - answers `noPackageRegistry`, the named generation gap, rather than `[]`: an empty package list would claim an organization with three tagged releases, 344 build outputs and a root `Dockerfile` publishes nothing, which the same artifact contradicts

served & verified
Long tail
GET
/api/v4/projects/{id}/registry/repositories

`container_repository` and `container_tag` through `containerRepositories()`, `containerTags()` and `containerTag()`. `location` is the image path behind `registry.` + this host's own name, which is how GitLab derives its own (`registry.gitlab.com` beside `gitlab.com`); `name` is the segment after the project's full path, so `""` here, because canon's one image is the ROOT image of the one repository carrying a `Dockerfile`. `delete_api_path`, `status` and `cleanup_policy_started_at` are omitted: nothing is scheduled for deletion and no caller here may delete anything. This route answers the images one project keeps - one here, the root image of `parthenon` - and answers both declared flags: `tags_count=true` counts `containerTags()` and `tags=true` carries each tag's `{name, path, location}`. Neither is emitted unasked. SERVED 2026-09-14 (hello-20 `packageRegistry`): gated on `reader.hasPackageRegistry()`, with the project or group resolving FIRST so an unknown one is GitLab's own 404 on every generation. An older artifact - the 127 pins frozen before hello-20, and live until the hello-20 cutover - answers `noPackageRegistry`, the named generation gap, rather than `[]`: an empty package list would claim an organization with three tagged releases, 344 build outputs and a root `Dockerfile` publishes nothing, which the same artifact contradicts

served & verified
Long tail
GET
/api/v4/projects/{id}/registry/repositories/{repository_id}/tags

`container_repository` and `container_tag` through `containerRepositories()`, `containerTags()` and `containerTag()`. `location` is the image path behind `registry.` + this host's own name, which is how GitLab derives its own (`registry.gitlab.com` beside `gitlab.com`); `name` is the segment after the project's full path, so `""` here, because canon's one image is the ROOT image of the one repository carrying a `Dockerfile`. `delete_api_path`, `status` and `cleanup_policy_started_at` are omitted: nothing is scheduled for deletion and no caller here may delete anything. This route answers one image's tags in push order with the moving `latest` last, which is the artifact's own `id` order: one tag per release plus `latest`, which shares the newest release tag's digest BY CONSTRUCTION because both are the same commit. A repository id this project does not hold, `%ZZ` included, is GitLab's own 404. SERVED 2026-09-14 (hello-20 `packageRegistry`): gated on `reader.hasPackageRegistry()`, with the project or group resolving FIRST so an unknown one is GitLab's own 404 on every generation. An older artifact - the 127 pins frozen before hello-20, and live until the hello-20 cutover - answers `noPackageRegistry`, the named generation gap, rather than `[]`: an empty package list would claim an organization with three tagged releases, 344 build outputs and a root `Dockerfile` publishes nothing, which the same artifact contradicts

served & verified
Long tail
GET
/api/v4/projects/{id}/registry/repositories/{repository_id}/tags/{tag_name}

`container_repository` and `container_tag` through `containerRepositories()`, `containerTags()` and `containerTag()`. `location` is the image path behind `registry.` + this host's own name, which is how GitLab derives its own (`registry.gitlab.com` beside `gitlab.com`); `name` is the segment after the project's full path, so `""` here, because canon's one image is the ROOT image of the one repository carrying a `Dockerfile`. `delete_api_path`, `status` and `cleanup_policy_started_at` are omitted: nothing is scheduled for deletion and no caller here may delete anything. This route answers ONE tag by name, with what only a single read publishes. `digest` is the artifact's stored `container_tag.digest` - `sha256` of a real OCI image manifest the compiler composed - and `total_size` the layer it measured. `revision` is GitLab's image-CONFIG digest, which the artifact does not store, so it is RECOMPOSED at request time from the same three documents the compiler built (`packageArchive` of the tagged tree under the image path, `ociImageConfig`, `ociImageManifest`) rather than omitted or invented; `short_revision` is its first nine characters, GitLab's own `revision[0..8]`. A tag name this image does not carry is GitLab's own 404, `%ZZ` included. SERVED 2026-09-14 (hello-20 `packageRegistry`): gated on `reader.hasPackageRegistry()`, with the project or group resolving FIRST so an unknown one is GitLab's own 404 on every generation. An older artifact - the 127 pins frozen before hello-20, and live until the hello-20 cutover - answers `noPackageRegistry`, the named generation gap, rather than `[]`: an empty package list would claim an organization with three tagged releases, 344 build outputs and a root `Dockerfile` publishes nothing, which the same artifact contradicts

served & verified
Long tail
GET
/api/v4/registry/repositories/{id}

`container_repository` and `container_tag` through `containerRepositories()`, `containerTags()` and `containerTag()`. `location` is the image path behind `registry.` + this host's own name, which is how GitLab derives its own (`registry.gitlab.com` beside `gitlab.com`); `name` is the segment after the project's full path, so `""` here, because canon's one image is the ROOT image of the one repository carrying a `Dockerfile`. `delete_api_path`, `status` and `cleanup_policy_started_at` are omitted: nothing is scheduled for deletion and no caller here may delete anything. This route is the INSTANCE-scoped address of one image, resolving its project from the image itself, and answers the third flag its own operation declares: `size=true` sums the layers DEDUPLICATED BY DIGEST, which is what a registry stores - `latest` and the newest release tag are the same manifest, and counting that layer twice would report a repository at twice its own size. SERVED 2026-09-14 (hello-20 `packageRegistry`): gated on `reader.hasPackageRegistry()`, with the project or group resolving FIRST so an unknown one is GitLab's own 404 on every generation. An older artifact - the 127 pins frozen before hello-20, and live until the hello-20 cutover - answers `noPackageRegistry`, the named generation gap, rather than `[]`: an empty package list would claim an organization with three tagged releases, 344 build outputs and a root `Dockerfile` publishes nothing, which the same artifact contradicts

served & verified
Long tail
namespaces4 of 5 served & verified
GET
/api/v4/namespaces
served & verified
Long tail
GET
/api/v4/namespaces/{id}
served & verified
Long tail
GET
/api/v4/namespaces/{id}/exists
served & verified
Long tail
GET
/api/v4/namespaces/{id}/gitlab_subscription

this namespace's SEAT subscription, folded out of reader.billingPeriods(`gitlab`, `seats`) - the `gitlab·seats·seat-month` series hello-20's F8 writes, one UTC calendar month per row, each carrying the rate it was priced at. A NAMESPACE SUBSCRIPTION IS NOT AN INSTANCE LICENCE and this host already says so at the other four rows: `/api/v4/license`, `/api/v4/license/{id}` and `/api/v4/licenses` are armed `refuse` at 403 on this replica holding no instance subscription of its own, which is about the file a self-managed administrator uploads. The namespace subscription is the SaaS shape of the question and is the one a gitlab.com customer actually has. `plan.code` is canon's own rung mapped to GitLab's word, and the rung is a FOLD rather than a choice: F8 derives `premium` from this organization holding its merges to an approval rule, which is a GitLab Premium capability, and stops short of Ultimate because nothing here evidences the security product the top rung is bought for - the founder's 2026-09-12 ruling that no security product is enabled on olympus-labs, which is the same fact the ten armed vulnerability rows rest on, so the two surfaces agree. `usage.seats_in_subscription` is the period's OWN allowance divided back out of `included_micro` rather than a seat count copied from the roster, so the subscription and the bill beside it cannot disagree; `seats_in_use` is the roster's non-bot membership, because a bot account is a pipeline identity and a pipeline is billed by the minute; `seats_owed` is GitLab's own arithmetic over the two. `trial` is false (nothing here records a trial end date), `auto_renew` is true (a subscription billed month after month across the window is one that renewed) and `upgradable` is true because `premium` is not the top rung of the ladder canon declares. Both containers answer the same subscription, which is GitLab's own model: a subscription is bought by the root namespace and every group under it is billed on it. SERVED 2026-09-14 (hello-20 `billingPeriods`), gated on hasBillingPeriods(); an older artifact answers the named hello-20 billing gap, and a subscription is an OBJECT with no empty form, so there is nothing honest to fall back on.

served & verified
Long tail
GET
/api/v4/namespaces/storage/limit_exclusions

refuse — namespace storage-limit exclusions are a GitLab Inc. internal admin surface

deferred
Long tail
releases4 of 5 served & verified
GET
/api/v4/groups/{id}/releases

"all releases for PROJECTS IN a specified group" — the same rows each project's own /releases serves. THE SUBGROUP RESOLVES HERE as of 2026-09-10 and answers `200 []`, because it owns no project (founder ruling F1)

served & verified
Core
GET
/api/v4/projects/{id}/releases
served & verified
Core
GET
/api/v4/projects/{id}/releases/{tag_name}
served & verified
Core
GET
/api/v4/projects/{id}/releases/{tag_name}/assets/links

no release in olympus-labs carries an asset link — canon's release names a tag and a description and nothing is attached to it; GitLab answers `[]` at 200 for a release with no links and its own 404 for a link id. THE LIST HALF: the release TAG must resolve first, so a tag nobody cut is still `404 Release Not Found`. Paginated, per the operation's own `page`/`per_page`. UNGATED: `canon.Release` (packages/canon/src/entities.ts:342) has carried the same nine fields in every generation and not one of them is an attachment. Falsifiable: the `expectsEmpty` entry reds the row the day a release grows a link. RULED by the founder 2026-09-12

served & verified
Core
GET
/api/v4/projects/{id}/releases/{tag_name}/assets/links/{link_id}

refuse — no release in olympus-labs carries an asset link — canon's release names a tag and a description and nothing is attached to it; GitLab answers `[]` at 200 for a release with no links and its own 404 for a link id. THE LINK-ID HALF: with no link on any release there is no link id to resolve and GitLab's own 404 is the whole answer. UNGATED, for the reason the list half states. RULED by the founder 2026-09-12

planned
Core
analytics3 of 4 served & verified · 1 deviation
GET
/api/v4/analytics/group_activity/issues_count

SERVED 2026-09-12 (coverage wave W8): `Gitlab::Analytics::GroupActivityCalculator`'s own fold — what was created in the last 90 days, capped at its own RECENT_COUNT_LIMIT of 1000. THE CLOCK IS THE ARTIFACT'S ANCHOR, never Date.now(): the operation's window is relative to "now", and a replica that read the wall clock would answer differently on every request and differently again on a frozen pin a year from now. `group_path` is a REQUIRED parameter and resolves the container the same way `/api/v4/groups/{id}` resolves an id, so both containers this host lists answer and a path nobody has is GitLab's own 404 rather than a count of nothing. The count is over reader.listIssues(repo) across every repository the container owns; conformance recomputes it from the artifact rather than asserting a constant

served & verified
Long tail
GET
/api/v4/analytics/group_activity/merge_requests_count

SERVED 2026-09-12 (coverage wave W8): `Gitlab::Analytics::GroupActivityCalculator`'s own fold — what was created in the last 90 days, capped at its own RECENT_COUNT_LIMIT of 1000. THE CLOCK IS THE ARTIFACT'S ANCHOR, never Date.now(): the operation's window is relative to "now", and a replica that read the wall clock would answer differently on every request and differently again on a frozen pin a year from now. `group_path` is a REQUIRED parameter and resolves the container the same way `/api/v4/groups/{id}` resolves an id, so both containers this host lists answer and a path nobody has is GitLab's own 404 rather than a count of nothing. The count is over reader.listPulls(repo) across every repository the container owns; conformance recomputes it from the artifact rather than asserting a constant

served & verified
Long tail
GET
/api/v4/analytics/group_activity/new_members_count

SERVED 2026-09-12 (coverage wave W8): `Gitlab::Analytics::GroupActivityCalculator`'s own fold — what was created in the last 90 days, capped at its own RECENT_COUNT_LIMIT of 1000. THE CLOCK IS THE ARTIFACT'S ANCHOR, never Date.now(): the operation's window is relative to "now", and a replica that read the wall clock would answer differently on every request and differently again on a frozen pin a year from now. `group_path` is a REQUIRED parameter and resolves the container the same way `/api/v4/groups/{id}` resolves an id, so both containers this host lists answer and a path nobody has is GitLab's own 404 rather than a count of nothing. The one count that needs a JOIN DATE, which is why it alone of the three is generation-gated: `membership.added_epoch` ships with hello-16's access domain, and on an older artifact the row answers the membership generation gap rather than 0 — a zero would say nobody joined this group in the window, and an artifact carrying no membership row says nothing at all about when anybody joined. An invitation nobody accepted is NOT a member, so the pending rows are excluded exactly as `/groups/{id}/members` excludes them, which conformance asserts

served & verified
Long tail
GET
/api/v4/projects/{id}/analytics/deployment_frequency

SERVED 2026-09-12 (coverage wave W8): DORA's PREDECESSOR on this host, and a different shape — `{value, from, to}` per bucket rather than `{date, value}` — folded over the same reader.listDeployments rows, with `environment` and `from` both required as the vendored operation declares. `value` is a `deviation`: the component types it `string` while real GitLab sends the integer count. The clock is the artifact's anchor, for the reason the DORA rows give

deviation
Long tail
ci resource groups1 of 4 served & verified
GET
/api/v4/projects/{id}/resource_groups

no pipeline in olympus-labs serializes on a resource group — every job in canon runs without a concurrency lock; GitLab answers `[]` at 200 for a project with none. THE LIST HALF, served as GitLab's empty page with the pagination headers the operation declares. UNGATED: no generation of this universe takes a concurrency lock on a job, so every registered pin answers this same `[]`. Falsifiable: the `expectsEmpty` conformance entry reds the row the day canon grows one. RULED by the founder 2026-09-12

served & verified
Long tail
GET
/api/v4/projects/{id}/resource_groups/{key}

refuse — no pipeline in olympus-labs serializes on a resource group — every job in canon runs without a concurrency lock; GitLab answers `[]` at 200 for a project with none. THE NAMED HALF: no name resolves over a collection that is empty, so GitLab's own 404 is the whole answer — `[]` here would assert that the thing exists and holds nothing. UNGATED: no generation of this universe takes a concurrency lock on a job, so the 404 is true on every artifact. RULED by the founder 2026-09-12

deferred
Long tail
GET
/api/v4/projects/{id}/resource_groups/{key}/current_job

refuse — no pipeline in olympus-labs serializes on a resource group — every job in canon runs without a concurrency lock; GitLab answers `[]` at 200 for a project with none. THE NAMED HALF: no name resolves over a collection that is empty, so GitLab's own 404 is the whole answer — `[]` here would assert that the thing exists and holds nothing. UNGATED: no generation of this universe takes a concurrency lock on a job, so the 404 is true on every artifact. RULED by the founder 2026-09-12

deferred
Long tail
GET
/api/v4/projects/{id}/resource_groups/{key}/upcoming_jobs

refuse — no pipeline in olympus-labs serializes on a resource group — every job in canon runs without a concurrency lock; GitLab answers `[]` at 200 for a project with none. THE NAMED HALF: no name resolves over a collection that is empty, so GitLab's own 404 is the whole answer — `[]` here would assert that the thing exists and holds nothing. UNGATED: no generation of this universe takes a concurrency lock on a job, so the 404 is true on every artifact. RULED by the founder 2026-09-12

deferred
Long tail
dependency management2 of 4 served & verified
GET
/api/v4/occurrences/vulnerabilities

the vulnerabilities related to one SBOM occurrence, and no occurrence in this universe has any: no security product is enabled on olympus-labs and no vulnerability exists (§A-R1 of the hello-20 batch, founder-approved 2026-09-14). THE OCCURRENCE IS A QUERY PARAMETER AND THE OPERATION DECLARES NO 404 AT ALL - 200 and 400 are its whole response set - so the honest answer for an occurrence nobody scanned is the empty list rather than a status GitLab does not return here, which is why this row is `empty` while the six addressed by a path id or digest refuse. UNGATED: no generation of this universe holds a SecurityFinding; conformance drives it against the oldest generation in the suite.

served & verified
Long tail
GET
/api/v4/projects/{id}/dependencies

GitLab's DEPENDENCY LIST is a security-scanning surface, not a package list, and its own description says so: it partially mirrors the dependency list feature, which is available only for languages and package managers supported by Gemnasium - i.e. it reports what a dependency-SCANNING job found. No such job ran here, because no security product is enabled on olympus-labs (§A-R1 of the hello-20 batch, founder-approved 2026-09-14). THE EMPTY AND THE SBOM AGREE RATHER THAN CONTRADICTING: this universe's dependencies are real and are published where it actually records them - `render-sbom.ts` parses parthenon's own `package.json`, `go.mod` and `requirements.txt` - and what is missing is the SCAN, not the packages. UNGATED: no generation holds a scan result, so the empty is true on every artifact; conformance drives it against the oldest generation in the suite. A collection under a project that resolves.

served & verified
Long tail
GET
/api/v4/dependency_list_exports/{export_id}

refuse — the export object can only be created by a POST we refuse, so the by-id read can only ever be the vendor's own 404

deferred
Long tail
GET
/api/v4/dependency_list_exports/{export_id}/download

refuse — the export object can only be created by a POST we refuse, so the by-id read can only ever be the vendor's own 404

deferred
Long tail
feature flags3 of 4 served & verified
GET
/api/v4/projects/{id}/feature_flags

the gates this organization keeps on each subsystem's new behaviour, off reader.featureFlags(repo) in name order — one flag PER COMPONENT that has feature work (six at 90 d, two at 30 d), which is a uniqueness rule before it is a modelling choice: GitLab reads a flag back BY NAME, and a name pool shorter than the 13 feature storylines would wrap and hand two flags one name, i.e. a by-name route that cannot resolve. `active` is the artifact's own recomputed column — the gate is open exactly when the component's newest feature work started before this organization's most recent production deployment — and three of six are open at 90 d, which is what makes the operation's `scope=enabled|disabled` filter a real selector rather than a no-op; conformance drives BOTH branches and asserts a scope outside the enum is GitLab's 400. `version` is `new_version_flag` on every row and is GitLab's own fact rather than a default: GitLab has had one live flag representation since 13.0 and a client branches on this word to know whether `scopes` or `strategies` describes the rollout. `strategies` AND `scopes` ARE OMITTED because canon carries neither: GitLab's default strategy — everybody, everywhere — is GitLab's REPRESENTATION of a simple on/off flag rather than a fact about this organization, and a percentage rollout or a user list WOULD be a fact and there is none, which is the same finding that makes `/feature_flags_user_lists` an honest empty. (The Unleash routes DO send the default strategy, because there it is not an optional field but the protocol's own encoding of on/off — a client handed no strategy evaluates the flag as off.) Pagination walked to the last page with the five `x-*` headers asserted. SERVED 2026-09-14 (hello-20 `glDelivery`): gated on hasGlDelivery(); an older artifact answers the named delivery generation gap, because `[]` there would say this organization ships behind no gate while the same artifact records thirteen feature storylines over six components

served & verified
Long tail
GET
/api/v4/projects/{id}/feature_flags_user_lists

NOTHING IN THIS CANON RECORDS A COHORT. hello-20's `feature_flag` carries a gate, a component, a description and two instants, and no membership at all - which is the same fact that leaves a flag's `strategies` omitted on the REST rows and at GitLab's everybody-everywhere default on the Unleash wire: a percentage rollout or a user list WOULD be a fact about this organization and there is none. So the list of user lists is empty, under a project that resolves, paginated as the operation declares. THE SERVED ROWS BESIDE THIS ONE ARE WHAT MAKE IT FALSIFIABLE: `/projects/{id}/feature_flags` publishes six gates off `reader.featureFlags(repo)`, so a reader can see that flags exist and cohorts do not, and the `expectsEmpty` conformance entry reds the day canon grows one. The by-iid half answers GitLab's own 404. RULED 2026-09-14 by the hello-20 GitLab serving wave, on the F6 `glDelivery` decision's §5 list of six rows to arm rather than serve

served & verified
Long tail
GET
/api/v4/projects/{id}/feature_flags/{feature_flag_name}

the same flag BY NAME, off reader.featureFlag(repo, name) — the addressing the one-flag-per-component rule exists to make possible, and conformance reads EVERY flag back by its own name rather than only the first. Byte-identical to the list's own row. `404 Feature Flag Not Found` for a name nothing has, `%ZZ` included: NO decodeURIComponent on the segment, because Hono has already decoded it and a second decode THROWS on a malformed escape. Same omissions as the list (`strategies`, `scopes`). SERVED 2026-09-14 (hello-20 `glDelivery`): gated on hasGlDelivery(); an older artifact answers the named delivery generation gap

served & verified
Long tail
GET
/api/v4/projects/{id}/feature_flags_user_lists/{iid}

refuse — the by-iid half of a cohort list that is honestly empty: nothing in this canon records a cohort, so no `{iid}` can resolve and GitLab's own 404 - which this operation declares - is the whole answer. The split is `decisions/2026-09-12-0348`'s: a collection under a parent that resolves answers `200 []`, a row addressed by an id over it answers the provider's 404. RULED 2026-09-14 by the hello-20 GitLab serving wave, on the F6 `glDelivery` decision's §5

deferred
Long tail
gitlab pages0 of 4 served & verified
GET
/api/v4/pages/domains

refuse — the instance-wide Pages domain list is an administrator surface

deferred
Long tail
GET
/api/v4/projects/{id}/pages

refuse — olympus-labs publishes no GitLab Pages site — no project in this universe has a pages deployment or a custom domain; GitLab's own 404 for a project with Pages unused is the whole answer. ALL FOUR ROWS REFUSE, the domain list included: a Pages domain collection is addressable only on a project that HAS Pages, and no project here does — a 200 `[]` there would advertise a Pages site with no custom domain rather than the absence of Pages. The vendored document declares `404 Not Found` on all four operations. UNGATED: canon has no PagesSite in any generation. RULED by the founder 2026-09-12

deferred
Long tail
GET
/api/v4/projects/{id}/pages/domains

refuse — olympus-labs publishes no GitLab Pages site — no project in this universe has a pages deployment or a custom domain; GitLab's own 404 for a project with Pages unused is the whole answer. ALL FOUR ROWS REFUSE, the domain list included: a Pages domain collection is addressable only on a project that HAS Pages, and no project here does — a 200 `[]` there would advertise a Pages site with no custom domain rather than the absence of Pages. The vendored document declares `404 Not Found` on all four operations. UNGATED: canon has no PagesSite in any generation. RULED by the founder 2026-09-12

deferred
Long tail
GET
/api/v4/projects/{id}/pages/domains/{domain}

refuse — olympus-labs publishes no GitLab Pages site — no project in this universe has a pages deployment or a custom domain; GitLab's own 404 for a project with Pages unused is the whole answer. ALL FOUR ROWS REFUSE, the domain list included: a Pages domain collection is addressable only on a project that HAS Pages, and no project here does — a 200 `[]` there would advertise a Pages site with no custom domain rather than the absence of Pages. The vendored document declares `404 Not Found` on all four operations. UNGATED: canon has no PagesSite in any generation. RULED by the founder 2026-09-12

deferred
Long tail
labels4 of 4 served & verified
GET
/api/v4/groups/{id}/labels

features.namespaces (hello-16) puts two cross-cutting labels on the subgroup; reader.labelsForNamespace(id) derives them with is_project_label false. The organization's own list is empty and include_descendant_groups reaches the subgroup's, which is GitLab's own rule; older pins answer the empty list. SERVED 2026-09-09 (hello-16)

served & verified
Core
GET
/api/v4/groups/{id}/labels/{name}

the by-name twin of the group label list, off the same reader.labelsForNamespace rows; a name nobody holds is still GitLab's 404 Label Not Found, and that is the whole answer on every pin older than hello-16. SERVED 2026-09-09 (hello-16)

served & verified
Core
GET
/api/v4/projects/{id}/labels
served & verified
Core
GET
/api/v4/projects/{id}/labels/{name}
served & verified
Core
provider identities0 of 4 served & verified · 4 deviations
GET
/api/v4/groups/{id}/saml/{uid}

SERVED 2026-09-12 (hello-18): one identity by the handle itself — THE ROW THE WHOLE DOMAIN WAS ASKED FOR, since a uid that resolves to a person instead of to nothing is what this clique has named as its blocker since 2026-09-09. Byte-identical to that person's entry in the list, which conformance compares directly; a handle of the right SHAPE that nobody holds is GitLab's own 404, so the refusal is about the value rather than about the path. Same deviation as its list. The subgroup resolves the family — the document restricts it to no particular group — and answers [] for a list and GitLab's 404 for a named read, because a subgroup federates nobody; the organization above it does. Gated on hasExternalUid(); an older artifact answers the named hello-18 identity gap rather than [], because an empty list there would say nobody here was provisioned by a directory while this same host publishes a SAML configuration with SCIM enabled — the replica disagreeing with itself (invariant #5)

deviation
Long tail
GET
/api/v4/groups/{id}/saml/identities

SERVED 2026-09-12 (hello-18): the directory identities themselves — APIEntitiesIdentityDetail over person.external_uid, a 32-character lowercase hex handle (a UUID with its hyphens removed, which is the width Entra and Okta issue) that is a pure function of the root seed and the person's id. DEVIATION RATHER THAN GREEN, and the evidence is the vendored document contradicting itself: APIEntitiesIdentityDetail types `user_id` and `active` as `string`, while GitLab's own published example for these operations is {"extern_uid": "4", "user_id": 48, "active": true} — an integer and a boolean. Invariant #2 mirrors the provider rather than its documentation, so the real types are served and the two errors are declared spec bugs. `active` is true on every row because canon records no deprovisioning. Conformance asserts every handle matches /^[0-9a-f]{32}$/, that no two people share one, and that every user_id resolves to a person this host serves. The subgroup resolves the family — the document restricts it to no particular group — and answers [] for a list and GitLab's 404 for a named read, because a subgroup federates nobody; the organization above it does. Gated on hasExternalUid(); an older artifact answers the named hello-18 identity gap rather than [], because an empty list there would say nobody here was provisioned by a directory while this same host publishes a SAML configuration with SCIM enabled — the replica disagreeing with itself (invariant #5)

deviation
Long tail
GET
/api/v4/groups/{id}/scim/{uid}

SERVED 2026-09-12 (hello-18): one SCIM identity by the handle, byte-identical to the SAML identity for the same handle — one directory, one identity, asserted rather than assumed. Same deviation as its list. The subgroup resolves the family — the document restricts it to no particular group — and answers [] for a list and GitLab's 404 for a named read, because a subgroup federates nobody; the organization above it does. Gated on hasExternalUid(); an older artifact answers the named hello-18 identity gap rather than [], because an empty list there would say nobody here was provisioned by a directory while this same host publishes a SAML configuration with SCIM enabled — the replica disagreeing with itself (invariant #5)

deviation
Long tail
GET
/api/v4/groups/{id}/scim/identities

SERVED 2026-09-12 (hello-18): the directory identities themselves — APIEntitiesIdentityDetail over person.external_uid, a 32-character lowercase hex handle (a UUID with its hyphens removed, which is the width Entra and Okta issue) that is a pure function of the root seed and the person's id. DEVIATION RATHER THAN GREEN, and the evidence is the vendored document contradicting itself: APIEntitiesIdentityDetail types `user_id` and `active` as `string`, while GitLab's own published example for these operations is {"extern_uid": "4", "user_id": 48, "active": true} — an integer and a boolean. Invariant #2 mirrors the provider rather than its documentation, so the real types are served and the two errors are declared spec bugs. `active` is true on every row because canon records no deprovisioning. THE SCIM IDENTITIES ARE THE SAME IDENTITIES, and that is what one directory means: canon holds a single identity_provider_config with scim_enabled = 1, so a second, different handle here would assert a second directory nobody configured. Conformance compares the two lists element for element. The subgroup resolves the family — the document restricts it to no particular group — and answers [] for a list and GitLab's 404 for a named read, because a subgroup federates nobody; the organization above it does. Gated on hasExternalUid(); an older artifact answers the named hello-18 identity gap rather than [], because an empty list there would say nobody here was provisioned by a directory while this same host publishes a SAML configuration with SCIM enabled — the replica disagreeing with itself (invariant #5)

deviation
Long tail
wikis2 of 4 served & verified
GET
/api/v4/groups/{id}/wikis

olympus-labs documents its services in the parthenon repository, not in a wiki — no WikiPage exists at project or group scope; GitLab answers `[]` at 200 for the wiki list and its own 404 for a slug that names nothing. THE LIST HALF, served UNPAGINATED because the vendored operation declares only `id` and `with_content` — printing pagination headers on an operation GitLab does not paginate would break invariant #2 in the name of consistency. Both container ids answer it: the document says "for a group" and restricts the operation to no particular one, so the subgroup is not denied a family this host lists it under (invariant #5). UNGATED: canon has no WikiPage in any generation. RULED by the founder 2026-09-12

served & verified
Long tail
GET
/api/v4/projects/{id}/wikis

olympus-labs documents its services in the parthenon repository, not in a wiki — no WikiPage exists at project or group scope; GitLab answers `[]` at 200 for the wiki list and its own 404 for a slug that names nothing. THE LIST HALF, served UNPAGINATED because the vendored operation declares only `id` and `with_content` — printing pagination headers on an operation GitLab does not paginate would break invariant #2 in the name of consistency. Both container ids answer it: the document says "for a group" and restricts the operation to no particular one, so the subgroup is not denied a family this host lists it under (invariant #5). UNGATED: canon has no WikiPage in any generation. RULED by the founder 2026-09-12

served & verified
Long tail
GET
/api/v4/groups/{id}/wikis/{slug}

refuse — olympus-labs documents its services in the parthenon repository, not in a wiki — no WikiPage exists at project or group scope; GitLab answers `[]` at 200 for the wiki list and its own 404 for a slug that names nothing. THE SLUG/PAGE-ID HALF: no slug and no wiki_page_meta_id resolves, so GitLab's own 404 is the whole answer — a 200 `[]` on a wiki page's note list would assert that the page exists and nobody commented on it. UNGATED: canon has no WikiPage in any generation. RULED by the founder 2026-09-12

deferred
Long tail
GET
/api/v4/projects/{id}/wikis/{slug}

refuse — olympus-labs documents its services in the parthenon repository, not in a wiki — no WikiPage exists at project or group scope; GitLab answers `[]` at 200 for the wiki list and its own 404 for a slug that names nothing. THE SLUG/PAGE-ID HALF: no slug and no wiki_page_meta_id resolves, so GitLab's own 404 is the whole answer — a 200 `[]` on a wiki page's note list would assert that the page exists and nobody commented on it. UNGATED: canon has no WikiPage in any generation. RULED by the founder 2026-09-12

deferred
Long tail
<none>0 of 3 served & verified
GET
/api/v4/internal/gitaly/object_pool_members

refuse — the /api/v4/internal/* family is GitLab-to-Gitaly/agent plumbing outside the public API contract; no client library calls it

deferred
Long tail
GET
/api/v4/swagger_doc

refuse — GitLab mounts its Swagger description only outside production: lib/api/helpers/open_api.rb returns from add_open_api_documentation! before mounting anything when Rails.env.production? (read at the pinned commit ea02ef9f), so no production instance routes this path, and gitlab.com answers 404 {"error":"404 Not Found"} from the catch-all route at the foot of lib/api/api.rb (observed 2026-10-27). This host answers that catch-all body. The vendored OpenAPI document is not served in its place: it is the separate OpenAPI 3.0 file the gitlab:openapi:v3:generate task writes, not the Swagger 2.0 output grape-swagger returns here on a development instance, so serving it would be a body GitLab never sends (decisions/2026-10-27-2135-gitlab-swagger-doc-not-mounted.md)

deferred
Long tail
GET
/api/v4/swagger_doc/{name}

refuse — GitLab mounts its Swagger description only outside production: lib/api/helpers/open_api.rb returns from add_open_api_documentation! before mounting anything when Rails.env.production? (read at the pinned commit ea02ef9f), so no production instance routes the per-API description either, and gitlab.com answers 404 {"error":"404 Not Found"} from the catch-all route at the foot of lib/api/api.rb (observed 2026-10-27 for `projects`). This host answers that catch-all body. The vendored OpenAPI document is not served in its place: it is the separate OpenAPI 3.0 file the gitlab:openapi:v3:generate task writes, not the Swagger 2.0 output grape-swagger returns here on a development instance, so serving it would be a body GitLab never sends (decisions/2026-10-27-2135-gitlab-swagger-doc-not-mounted.md)

deferred
Long tail
applications0 of 3 served & verified
GET
/api/v4/applications

refuse — application registration is a control-plane surface of the vendor's auth system; SandboxAPIs issues its own keys and the instance list is admin-only

deferred
Long tail
GET
/api/v4/user/applications

refuse — application registration is a control-plane surface of the vendor's auth system; SandboxAPIs issues its own keys and the instance list is admin-only

deferred
Long tail
GET
/api/v4/user/applications/{id}

refuse — application registration is a control-plane surface of the vendor's auth system; SandboxAPIs issues its own keys and the instance list is admin-only

deferred
Long tail
audit-events3 of 3 served & verified
GET
/api/v4/audit_events

reader.listPersonAuditEvents() — the PERSON half of `admin_audit_event`, which hello-20 writes: 91 rows at 90 days, one for every administrative act the event log already records (the organization created, the repository created, 44 memberships, 2 protected refs, 38 credentials issued and 1 revoked), every instant an existing event's instant and every actor an existing person. NEVER reader.listAuditEvents(): the bare three accessors default to the SUPPORT DESK's register, and a GitLab surface publishing a Zendesk instance's account audit would be a different organization's register under this one's path — conformance asserts no desk row reaches this surface and that no BOT is ever an actor. THIS HOST PUBLISHES 68 OF THE 91 AND THE 23 IT DOES NOT ALL NAME A TEAM: GitLab has no team object, so the 4 team rows, the 15 memberships scoped to a team and the 4 repository grants whose principal is a team have no GitLab entity, and an audit event naming an object no path here answers would be a dangling reference published as a fact (the same canon reaches GitHub, where a team IS a team). The exclusion is a property of the door rather than a rule to remember — the fold reads listMemberships and protectedRefs, whose own membershipScope() drops a team-principal grant by construction. entity_type/entity_id name the CONTAINER the act happened in (Group, Project or User) and details.target_* the administered object, both resolving to something this host serves; event_name is GitLab's own audit-event-type identifier, composed from (target_kind, action) plus the credential's kind; details.custom_message is the row's own change_description and details.ip_address the address canon drew, always RFC 5737 or RFC 1918 space. entity_type, entity_id, created_after and created_before are IMPLEMENTED rather than ignored — entity_type is not required here although the vendored document declares it so, because real GitLab answers a bare request with the whole register, and Gitlab::Audit::InstanceScope answers `[]` truthfully since nobody here ever changed an instance setting. Oldest first, which is the accessor's order and the order the ids were minted in. SERVED 2026-09-14 (hello-20 `auditActor`): gated on reader.hasAuditActor(); an older artifact answers the NAMED hello-20 gap (`the administrative acts of this organization's own roster`) rather than `[]` — which would claim nobody ever administered this organization — and never the desk's rows

served & verified
Long tail
GET
/api/v4/audit_events/{id}

the by-id twin of the instance register, found by scanning the rows this route may answer and then RE-READ through reader.personAuditEvent(id), which is person-scoped — so a support-desk row could not be published here even if the scan above it were ever widened, which conformance drives directly by asking this route for five desk ids and asserting GitLab's 404 on each. The path carries a gitlabId, which is one-way by construction, so the scan is the door rather than a reverse lookup. An id nobody holds is 404 Audit Event Not Found and `%ZZ` is that same 404 rather than the raw 500 PR #481 fixed. SERVED 2026-09-14 (hello-20 `auditActor`): gated on reader.hasAuditActor(); an older artifact answers the named hello-20 gap

served & verified
Long tail
GET
/api/v4/groups/{id}/audit_events

THE CONTAINMENT FOLD, which the domain deliberately did not provide: personAuditEventsForTarget(kind, target) answers what happened TO one object, and this asks what happened INSIDE this group — the group's own row plus the memberships, the protected refs and the credential scoped to it. A row belongs to a container when the audited object's OWN CANONICAL SCOPE is that container, read through the scoped accessors that already publish it (listMemberships, protectedRefs, credential), so nothing is inferred from a name or an instant. The organization's group answers 18 rows at 90 days (its creation, its 16 memberships and its group access token) and the SUBGROUP KEEPS ITS OWN REGISTER of 6 (its 4 memberships and the 2 refs protected there) — an audit event names ONE entity and a subgroup is an entity of its own, so neither register contains the other's and conformance asserts they do not overlap. The 15 memberships scoped to a TEAM are in neither, because GitLab has no team. SERVED 2026-09-14 (hello-20 `auditActor`): gated on reader.hasAuditActor() after the group has resolved; an older artifact answers the named hello-20 gap rather than `[]`

served & verified
Long tail
avatars3 of 3 served & verified
GET
/api/v4/avatar

THE OLD REASON WAS WRONG (DECISIONS 2026-09-06 [HELLO-15/avatars-reactions] finding a): the vendored spec's own summary is "Return avatar url for a user" and its one required parameter is email. It is a PERSON lookup, not an Attachment - PersonAvatar (hello-13) plus the org-wide login@{org}.dev convention every host joins on answer it. An address naming nobody gets avatar_url null, which is real GitLab with Gravatar disabled; a @users.noreply address deliberately does not resolve, because real GitLab looks up the PUBLIC email

served & verified
Long tail
GET
/api/v4/groups/{id}/avatar

the group's own identicon bytes from reader.orgAvatar("org", org) — one picture per container across every host that serves one. THE SUBGROUP RESOLVES HERE as of 2026-09-10 and answers `404 Avatar Not Found`, GitLab's own answer for a group nobody uploaded an avatar for: `org_avatar` is keyed `org` and `repo` and no row names a namespace, which is also why glSubgroup prints no avatar_url for it. Older pins still answer the noOrgAvatars generation gap

served & verified
Long tail
GET
/api/v4/projects/{id}/avatar

OrgAvatar is canon as of hello-15 (features.orgAvatars): one identicon per repository, subjectKind "repo". orgAvatar("repo", ...) answers it and shapeProject's new avatar_url names the /uploads/-/system/project/avatar/{id}/avatar.png path that serves the same bytes

served & verified
Long tail
group credentials inventory2 of 3 served & verified · 1 deviation
GET
/api/v4/groups/{id}/manage/personal_access_tokens

SERVED 2026-09-12 (hello-18): the credentials of this group's ENTERPRISE USERS, which the vendored document scopes the operation to in as many words. The set is the same sixteen people /enterprise_users answers and the rows are the person_credential rows they already hold — the same rows /users/{id}/keys and /personal_access_tokens serve, re-scoped to the directory's people rather than re-derived, and conformance asserts every row is also on its owner's own list so the inventory and the person cannot disagree. THE MACHINE ACCOUNT'S CREDENTIALS ARE NOT HERE, which is what the exclusion is for: an inventory scoped to enterprise users must not list a principal the enterprise-user surface says does not exist. The vendored operation declares `200: OK` and NO response schema at all — a real GitLab spec gap — so the shape is glPersonalAccessToken, the same object /personal_access_tokens serves, and conformance asserts every id, name, owner and active flag against the credential row rather than against a schema that does not exist. The subgroup gets GitLab's own not_found!, on the vendored description's own top-level scoping. Gated on hasExternalUid() (the set) and hasCredentials() (the rows); an older artifact answers the named generation gap rather than []

served & verified
Long tail
GET
/api/v4/groups/{id}/manage/ssh_keys

SERVED 2026-09-12 (hello-18): the credentials of this group's ENTERPRISE USERS, which the vendored document scopes the operation to in as many words. The set is the same sixteen people /enterprise_users answers and the rows are the person_credential rows they already hold — the same rows /users/{id}/keys and /personal_access_tokens serve, re-scoped to the directory's people rather than re-derived, and conformance asserts every row is also on its owner's own list so the inventory and the person cannot disagree. THE MACHINE ACCOUNT'S CREDENTIALS ARE NOT HERE, which is what the exclusion is for: an inventory scoped to enterprise users must not list a principal the enterprise-user surface says does not exist. AUTHENTICATION KEYS ONLY: the operation's own words are "all SSH public keys associated with enterprise users", and canon's `ssh-signing` rows are excluded on the ruling /users/{selected_user}/ssh-keys already carries one host over — a key authorised to push and a key trusted to attest authorship are different grants, and an inventory is the first place conflating them would matter. APIEntitiesSshKeyWithUserId declares seven properties and the key MATERIAL is not one of them, so it is absent rather than repeated: this route answers WHO HOLDS WHAT and /users/{id}/keys is where the bytes are. The subgroup gets GitLab's own not_found!, on the vendored description's own top-level scoping. Gated on hasExternalUid() (the set) and hasCredentials() (the rows); an older artifact answers the named generation gap rather than []

served & verified
Long tail
GET
/api/v4/groups/{id}/manage/resource_access_tokens

SERVED 2026-09-10 (hello-16): "all group and project access tokens associated with a TOP-LEVEL-GROUP" — the group token plus every project token below it, from the same two reader.listCredentials calls, and conformance asserts the managed list is exactly the UNION of the narrower ones so a token cannot appear here without appearing on its own container. THE SUBGROUP GETS GITLAB'S 404 rather than a list: the document restricts the operation to a top-level group, and an empty list would report a different fact from the one the provider reports. Gated on hasCredentials()

deviation
Long tail
group import and export0 of 3 served & verified
GET
/api/v4/groups/{id}/export_relations/download

refuse — each of these reads reports on an export or import that a WRITE would have started, and writes are refused; GitLab answers 404 for an export nobody requested

deferred
Long tail
GET
/api/v4/groups/{id}/export_relations/status

refuse — each of these reads reports on an export or import that a WRITE would have started, and writes are refused; GitLab answers 404 for an export nobody requested

deferred
Long tail
GET
/api/v4/groups/{id}/export/download

refuse — each of these reads reports on an export or import that a WRITE would have started, and writes are refused; GitLab answers 404 for an export nobody requested

deferred
Long tail
member roles1 of 3 served & verified
GET
/api/v4/groups/{id}/member_roles

olympus-labs defines no custom member role — the five roles in canon's membership_role table are GitLab's own built-ins; GitLab answers `[]` at 200 for a group that has minted none. THE LIST HALF, served UNPAGINATED because the vendored operation declares `id` and nothing else. Both container ids answer it: the document restricts the operation to no particular group, so the subgroup is not denied a family this host lists it under (invariant #5). UNGATED: no generation of this universe mints a custom role — canon's five roles are GitLab's own built-ins. RULED by the founder 2026-09-12

served & verified
Long tail
GET
/api/v4/admin_member_roles

refuse — instance-level custom roles are a self-managed admin surface; GitLab.com answers 404 for them

deferred
Long tail
GET
/api/v4/member_roles

refuse — instance-level custom roles are a self-managed admin surface; GitLab.com answers 404 for them

deferred
Long tail
merge trains1 of 3 served & verified
GET
/api/v4/projects/{id}/merge_trains

olympus-labs protects only branches — canon's protected_ref carries two branch patterns at namespace scope and no environment, tag, merge train, push rule or external status check exists; GitLab answers `[]` at 200 for each of these lists and its own 404 for a named one. THE LIST HALF, served as GitLab's empty page with the pagination headers the operation declares. UNGATED: no generation of this universe has ever protected an environment or a tag, queued a merge train, registered an external status check or set a push rule, so every registered pin answers this same `[]`. Falsifiable: the `expectsEmpty` conformance entry reds the row the day canon grows one. RULED by the founder 2026-09-12

served & verified
Long tail
GET
/api/v4/projects/{id}/merge_trains/{target_branch}

refuse — olympus-labs protects only branches — canon's protected_ref carries two branch patterns at namespace scope and no environment, tag, merge train, push rule or external status check exists; GitLab answers `[]` at 200 for each of these lists and its own 404 for a named one. THE NAMED HALF: no name resolves over a collection that is empty, so GitLab's own 404 is the whole answer — `[]` here would assert that the thing exists and holds nothing. UNGATED: no generation of this universe has ever protected an environment or a tag, queued a merge train, registered an external status check or set a push rule, so the 404 is true on every artifact. RULED by the founder 2026-09-12

deferred
Long tail
GET
/api/v4/projects/{id}/merge_trains/merge_requests/{merge_request_iid}

refuse — olympus-labs protects only branches — canon's protected_ref carries two branch patterns at namespace scope and no environment, tag, merge train, push rule or external status check exists; GitLab answers `[]` at 200 for each of these lists and its own 404 for a named one. THE NAMED HALF: no name resolves over a collection that is empty, so GitLab's own 404 is the whole answer — `[]` here would assert that the thing exists and holds nothing. UNGATED: no generation of this universe has ever protected an environment or a tag, queued a merge train, registered an external status check or set a push rule, so the 404 is true on every artifact. RULED by the founder 2026-09-12

deferred
Long tail
notification settings3 of 3 served & verified
GET
/api/v4/groups/{id}/notification_settings

`global` on both containers, for two independent reasons. First, this replica serves exactly one organization, so the person's `org` row IS the global setting and a group has nothing of its own left to say - hello-20's `notification_preference` therefore mints no group-scoped row at all. Second, the SUBGROUP owns no repository (hello-16's founder ruling F1, the same fact `ci-policy.ts` rests on when it mints no namespace policy), so a preference on it would govern nothing; conformance asserts the two containers answer identically. `global` is GitLab's own inherit value and is a thing this host can print precisely because the persona HAS a setting to inherit. `events` is omitted for the reason the two rows above give. An unknown group is GitLab's own 404 on every generation. SERVED 2026-09-14 (hello-20 `watchGraph`), gated on hasWatchGraph(); an older artifact has no preference to inherit and answers the named hello-20 gap.

served & verified
Long tail
GET
/api/v4/notification_settings

the caller's GLOBAL notification level, which is their `org` row - off reader.notificationLevel(me, `org`, org). This replica serves exactly one organization, so my default across this instance and my default for this organization are the same preference, and naming the container is what makes every row RESOLVE (invariant #5) where an empty-string sentinel would not. Canon's vocabulary is `watch | participating | disabled` and ALL THREE ARE TAKEN in this roster - somebody administers the organization, most people keep the vendor default, and the CI identity hears nothing, which is what makes `disabled` a real answer somewhere rather than a branch no row ever reaches; conformance fails if fewer than three are taken. The level here is never `global`: that value is GitLab's INHERIT SENTINEL and the global setting is the thing being inherited FROM. `notification_email` is the org-wide address convention every host in this universe joins people on (`{login}@{org}.dev`, the same join `/api/v4/avatar` resolves) and deliberately NOT the `@users.noreply.…` address this renderer prints on a commit - that one is the address a person chose to keep private. `events` is OMITTED: GitLab exposes the per-event flags only on a `custom` setting, nobody here is at `custom`, and a block of falses nobody set would be the placeholder invariant #4 forbids. SERVED 2026-09-14 (hello-20 `watchGraph`), gated on hasWatchGraph(); an older artifact answers the named hello-20 gap, and there is no honest empty to fall back on because the response is an OBJECT carrying a level rather than a collection.

served & verified
Long tail
GET
/api/v4/projects/{id}/notification_settings

the caller's own level for one project: their `repo` row where they set one, off reader.notificationLevel(me, `repo`, repo), and otherwise `global`. A SCOPE WITH NO ROW INHERITS, AND THAT IS THE MODEL RATHER THAN A SAVING: hello-20's `notification_preference` carries a repository row only where somebody hears MORE about that repository than about the organization, because GitLab publishes the inheritance as a LEVEL (`global`) and a materialised row per person per repository would make that answer unreachable. `events` is omitted for the reason `/api/v4/notification_settings` gives. An unknown project is GitLab's own 404 on every generation. SERVED 2026-09-14 (hello-20 `watchGraph`), gated on hasWatchGraph(); an older artifact answers the named hello-20 gap rather than `global`, because `global` is a claim about a preference this replica would not have read.

served & verified
Long tail
projects job token scope2 of 3 served & verified
GET
/api/v4/projects/{id}/job_token_scope/allowlist

THIS UNIVERSE HOLDS ONE PROJECT, so a cross-project job-token trust list has nothing to allow - §A-R6 of the hello-20 batch, founder-approved 2026-09-14, and the second half of the 2026-09-09 re-scope's own sentence. The allowlist names the OTHER projects a CI job's token may reach; there is no other project, and `repo` holds exactly one row on every generation. UNGATED: the one-project fact is a property of `template.ts`'s own `repos`, not of a snapshot, and hello-16's founder ruling F1 left the one subgroup owning no repository either - so the empty is true on every artifact and on every frozen pin; conformance drives it against the oldest generation in the suite. A collection under a project that resolves; `…/job_token_scope` itself is a settings object with no empty form and answers GitLab's own 404.

served & verified
Long tail
GET
/api/v4/projects/{id}/job_token_scope/groups_allowlist

the same fact one scope up: the GROUPS a CI job's token may reach. This universe holds one organization and one subgroup, and the subgroup owns no repository (hello-16's founder ruling F1), so there is no other group whose projects a token could be trusted into - §A-R6 of the hello-20 batch, founder-approved 2026-09-14. UNGATED: the containment is a property of the template rather than of a snapshot, so the empty is true on every artifact; conformance drives it against the oldest generation in the suite. A collection under a project that resolves.

served & verified
Long tail
GET
/api/v4/projects/{id}/job_token_scope

refuse — the CI/CD job-token ACCESS SETTINGS - `{inbound_enabled, outbound_enabled}`, which say whether other projects' jobs may reach into this one. It is a SETTINGS OBJECT AND NOT A COLLECTION, so it has no empty form: the three allowlists beside it are honest empties (this universe holds one project and there is nothing to allow), but the two booleans are a configuration canon does not carry, and inventing a pair would be the placeholder invariant #4 forbids. The operation declares the 404. §A-R6 of the hello-20 batch, founder-approved 2026-09-14; UNGATED, because the one-project fact is a property of `template.ts`'s own `repos` rather than of a snapshot. Driven in conformance through a project that resolves.

deferred
Long tail
remote mirrors1 of 3 served & verified
GET
/api/v4/projects/{id}/remote_mirrors

nothing here mirrors to or from another host: a remote mirror exists only because someone configured one, and this universe accepts no writes

served & verified
Long tail
GET
/api/v4/projects/{id}/remote_mirrors/{mirror_id}

refuse — the collection this id would index is empty in this universe, so the only honest answer is the vendor's own 404

deferred
Long tail
GET
/api/v4/projects/{id}/remote_mirrors/{mirror_id}/public_key

refuse — the collection this id would index is empty in this universe, so the only honest answer is the vendor's own 404

deferred
Long tail
secure files1 of 3 served & verified
GET
/api/v4/projects/{id}/secure_files

olympus-labs stores no CI secure file — canon holds no secret VALUE and no uploaded secret FILE; GitLab answers `[]` at 200 for a project with none. THE LIST HALF, served as GitLab's empty page with the pagination headers the operation declares. UNGATED: no generation of this universe stores a secret VALUE or an uploaded secret FILE — hello-19's `ci_binding` stores secret NAMES and dates and has no column that could hold a value, which is asserted as SQL rather than reviewed — so every registered pin answers this same `[]`, and so does every generation after it. Falsifiable: the `expectsEmpty` conformance entry reds the row the day canon grows one. RULED by the founder 2026-09-12; the clause was narrowed on 2026-09-12 in hello-19 wave W-b (founder call F4) from the broader claim that canon holds no secret material of any kind — same conclusion, same mode, same review date, and a narrower fact that stays permanently checkable now that a generation stores secret NAMES

served & verified
Long tail
GET
/api/v4/projects/{id}/secure_files/{secure_file_id}

refuse — olympus-labs stores no CI secure file — canon holds no secret VALUE and no uploaded secret FILE; GitLab answers `[]` at 200 for a project with none. THE NAMED HALF: no name resolves over a collection that is empty, so GitLab's own 404 is the whole answer — `[]` here would assert that the thing exists and holds nothing. UNGATED: no generation of this universe stores a secret VALUE or an uploaded secret FILE — hello-19's `ci_binding` stores secret NAMES and dates and has no column that could hold a value, which is asserted as SQL rather than reviewed — so the 404 is true on every artifact. RULED by the founder 2026-09-12; the clause was narrowed on 2026-09-12 in hello-19 wave W-b (founder call F4) from the broader claim that canon holds no secret material of any kind — same conclusion, same mode, same review date, and a narrower fact that stays permanently checkable now that a generation stores secret NAMES

deferred
Long tail
GET
/api/v4/projects/{id}/secure_files/{secure_file_id}/download

refuse — olympus-labs stores no CI secure file — canon holds no secret VALUE and no uploaded secret FILE; GitLab answers `[]` at 200 for a project with none. THE NAMED HALF: no name resolves over a collection that is empty, so GitLab's own 404 is the whole answer — `[]` here would assert that the thing exists and holds nothing. UNGATED: no generation of this universe stores a secret VALUE or an uploaded secret FILE — hello-19's `ci_binding` stores secret NAMES and dates and has no column that could hold a value, which is asserted as SQL rather than reviewed — so the 404 is true on every artifact. RULED by the founder 2026-09-12; the clause was narrowed on 2026-09-12 in hello-19 wave W-b (founder call F4) from the broader claim that canon holds no secret material of any kind — same conclusion, same mode, same review date, and a narrower fact that stays permanently checkable now that a generation stores secret NAMES

deferred
Long tail
service accounts3 of 3 served & verified
GET
/api/v4/groups/{id}/service_accounts

every actor in olympus-labs is a person on the roster — canon mints no bot principal, and a service account served here would be a member no other host lists (invariant #5); GitLab answers `[]` at 200 for a group with none. THE LIST HALF, served as GitLab's empty page with the pagination headers the operation declares. UNGATED: canon has no `ServiceAccount` in any generation — hello-17 dropped the federation domain that would have introduced one, so every registered pin answers this same `[]`. Falsifiable: the `expectsEmpty` conformance entry reds the row the day canon grows one. RULED by the founder 2026-09-12

served & verified
Long tail
GET
/api/v4/projects/{id}/service_accounts

every actor in olympus-labs is a person on the roster — canon mints no bot principal, and a service account served here would be a member no other host lists (invariant #5); GitLab answers `[]` at 200 for a group with none. THE LIST HALF, served as GitLab's empty page with the pagination headers the operation declares. UNGATED: canon has no `ServiceAccount` in any generation — hello-17 dropped the federation domain that would have introduced one, so every registered pin answers this same `[]`. Falsifiable: the `expectsEmpty` conformance entry reds the row the day canon grows one. RULED by the founder 2026-09-12

served & verified
Long tail
GET
/api/v4/service_accounts

every actor in olympus-labs is a person on the roster — canon mints no bot principal, and a service account served here would be a member no other host lists (invariant #5); GitLab answers `[]` at 200 for a group with none. THE LIST HALF, served as GitLab's empty page with the pagination headers the operation declares. UNGATED: canon has no `ServiceAccount` in any generation — hello-17 dropped the federation domain that would have introduced one, so every registered pin answers this same `[]`. Falsifiable: the `expectsEmpty` conformance entry reds the row the day canon grows one. RULED by the founder 2026-09-12

served & verified
Long tail
unleash3 of 3 served & verified
GET
/api/v4/feature_flags/unleash/{project_id}

the Unleash CLIENT PROTOCOL over the same reader.featureFlags(repo) rows — `API::Entities::UnleashFeatures`, which is `{version, features[]}` where `version` is the PROTOCOL version (1 on every real GitLab) and not the flag's own `version`, a different field with a different vocabulary. Each feature is `{name, description, enabled, strategies}`: `enabled` is GitLab's rename of the artifact's `active` column, and `strategies` is exactly one entry, `{name: "default", parameters: {}}` — GitLab's everybody-everywhere default. THAT STRATEGY IS THE PROTOCOL AND NOT AN INVENTED FACT: an Unleash client handed no strategy evaluates the flag as OFF, so the list is how an on/off flag is spelled on this wire, which is why the REST `/feature_flags` rows omit the field and these do not. A percentage rollout or a user list would be a fact and there is none — `parameters` is empty on every row and conformance reds it the day it is not. The vendored document declares NO SCHEMA for any of the three Unleash operations, so the row is `schema: null` and the envelope is asserted directly. `instance_id` and `app_name` are accepted and ignored: a client sends them so GitLab can record which instance polled, and neither selects anything. SERVED 2026-09-14 (hello-20 `glDelivery`): gated on hasGlDelivery(); an older artifact answers the named delivery generation gap

served & verified
Long tail
GET
/api/v4/feature_flags/unleash/{project_id}/client/features

what a CURRENT unleash-client sends, and the third spelling of one operation — byte-identical to the other two, asserted in conformance rather than rendered again. See `/api/v4/feature_flags/unleash/{project_id}`. SERVED 2026-09-14 (hello-20 `glDelivery`): gated on hasGlDelivery(); an older artifact answers the named delivery generation gap

served & verified
Long tail
GET
/api/v4/feature_flags/unleash/{project_id}/features

the v2 Unleash client's spelling of `/feature_flags/unleash/{project_id}` — deprecated in GitLab 15.6 and still served, because a deprecated operation is one real clients still call. ONE handler answers all three Unleash paths and conformance asserts the three envelopes are byte-identical, so no two of them can drift. See `/api/v4/feature_flags/unleash/{project_id}` for what is read and why the default strategy is on this wire. SERVED 2026-09-14 (hello-20 `glDelivery`): gated on hasGlDelivery(); an older artifact answers the named delivery generation gap

served & verified
Long tail
usage data0 of 3 served & verified
GET
/api/v4/usage_data/non_sql_metrics

refuse — service ping reports what a real installation sends to GitLab Inc.; a mirror has no installation to report on and the endpoints are admin-only

deferred
Long tail
GET
/api/v4/usage_data/queries

refuse — service ping reports what a real installation sends to GitLab Inc.; a mirror has no installation to report on and the endpoints are admin-only

deferred
Long tail
GET
/api/v4/usage_data/service_ping

refuse — service ping reports what a real installation sends to GitLab Inc.; a mirror has no installation to report on and the endpoints are admin-only

deferred
Long tail
access requests2 of 2 served & verified
GET
/api/v4/groups/{id}/access_requests

membership here is granted directly by the group's own scaffolding; nobody has ever REQUESTED access, so the pending-request list is truthfully empty. THE SUBGROUP RESOLVES HERE as of 2026-09-10 and answers the SAME empty list: an access request is an ACTION this canon records none of, which is a fact about the universe rather than about one container

served & verified
Long tail
GET
/api/v4/projects/{id}/access_requests

the project-scoped twin of the group access-request list, held to the same fact: membership is granted directly, so nothing is pending

served & verified
Long tail
attestations0 of 2 served & verified
GET
/api/v4/projects/{id}/attestations/{attestation_iid}/download

refuse — olympus-labs signs no build attestation: no security or supply-chain product is enabled here (§A-R1 of the hello-20 batch, founder-approved 2026-09-14). The operation retrieves an attestation BUNDLE by iid over a set that is empty, and it declares that 404 itself (`Artifact SHA-256 not found`). It is also the one row in this family that would need a SIGNATURE, and canon stores no private key of any kind - the same fact that keeps GitLab's Debian repository `key.asc` refused one domain over. UNGATED: no generation holds an attestation; driven in conformance through a project that resolves.

deferred
Long tail
GET
/api/v4/projects/{id}/attestations/{subject_digest}

refuse — the attestations for one artifact hash, and this project has none for any hash: no supply-chain attestation is produced here, because no security product is enabled on olympus-labs (§A-R1 of the hello-20 batch, founder-approved 2026-09-14). ADDRESSED BY A DIGEST rather than listed under a parent, which is why this is the 404 half of §A-R1 and not the [] half, and the operation declares that 404 in its own words (`Artifact SHA-256 not found`): a 200 for a digest nobody attested would assert the artifact was attested and carries nothing. UNGATED: no generation holds an attestation; driven in conformance through a project that resolves.

deferred
Long tail
data management0 of 2 served & verified
GET
/api/v4/admin/data_management/{model_name}

refuse — GitLab answers 403 to a non-admin token here (Geo, Sidekiq, storage moves, LDAP, application settings, feature toggles, background migrations); a tenant-shaped mirror has no instance to administer

deferred
Long tail
GET
/api/v4/admin/data_management/{model_name}/{record_identifier}

refuse — GitLab answers 403 to a non-admin token here (Geo, Sidekiq, storage moves, LDAP, application settings, feature toggles, background migrations); a tenant-shaped mirror has no instance to administer

deferred
Long tail
dora metrics2 of 2 served & verified
GET
/api/v4/groups/{id}/dora/metrics

SERVED 2026-09-12 (coverage wave W8): the four DORA metrics folded over reader.listDeployments, listIncidents and pullsForCommit — no new fact, and a caller can recompute every value from `/projects/{id}/deployments` and `/projects/{id}/merge_requests`, which is exactly what conformance does. deployment_frequency counts the successful deployments in the requested environment tiers; lead_time_for_changes is the median of (deployment instant − merge instant) over the merge requests each deployment shipped, reached through the same pullsForCommit read `/deployments/{id}/merge_requests` serves so a lead time and that list cannot disagree; time_to_restore_service is the median of (resolved − opened) over the incidents whose canonical issue belongs to the scope; change_failure_rate is those incidents over those deployments. A MEDIAN OF NOTHING IS NULL, never a zero — a lead time of no time is a different claim from no measurement — while a COUNT of nothing is 0, and conformance drives a window with no deployment to hold that line. THE SHAPE IS PINNED HERE because the vendored document declares NO schema for the 200 at all (`"200": {"description": "successful operation"}` and nothing else): an entry per bucket, `{date, value}`, `date` being the bucket's first UTC day. THE CLOCK IS THE ANCHOR: `end_date` defaults to the artifact's own anchor day and `start_date` to 90 UTC days before it, the same window the group-activity counts use. The group folds every repository it owns. This universe has one, so the group series and the project series must AGREE — conformance compares them rather than leaving the coincidence implicit

served & verified
Long tail
GET
/api/v4/projects/{id}/dora/metrics

SERVED 2026-09-12 (coverage wave W8): the four DORA metrics folded over reader.listDeployments, listIncidents and pullsForCommit — no new fact, and a caller can recompute every value from `/projects/{id}/deployments` and `/projects/{id}/merge_requests`, which is exactly what conformance does. deployment_frequency counts the successful deployments in the requested environment tiers; lead_time_for_changes is the median of (deployment instant − merge instant) over the merge requests each deployment shipped, reached through the same pullsForCommit read `/deployments/{id}/merge_requests` serves so a lead time and that list cannot disagree; time_to_restore_service is the median of (resolved − opened) over the incidents whose canonical issue belongs to the scope; change_failure_rate is those incidents over those deployments. A MEDIAN OF NOTHING IS NULL, never a zero — a lead time of no time is a different claim from no measurement — while a COUNT of nothing is 0, and conformance drives a window with no deployment to hold that line. THE SHAPE IS PINNED HERE because the vendored document declares NO schema for the 200 at all (`"200": {"description": "successful operation"}` and nothing else): an entry per bucket, `{date, value}`, `date` being the bucket's first UTC day. THE CLOCK IS THE ANCHOR: `end_date` defaults to the artifact's own anchor day and `start_date` to 90 UTC days before it, the same window the group-activity counts use. `metric` is required and its vocabulary is closed; an unknown one is GitLab's own 400 rather than a silently different answer

served & verified
Long tail
enterprise users2 of 2 served & verified
GET
/api/v4/groups/{id}/enterprise_users

SERVED 2026-09-12 (hello-18): THE SAME SIXTEEN PEOPLE ANSWER saml_users, provisioned_users AND enterprise_users, and that is GitLab's own definition rather than a shortcut: an enterprise user is one the group provisioned through its own SAML/SCIM, and a SAML user and a provisioned user are that sentence read from the other two ends. canon carries ONE identity_provider_config (protocol saml, scim_enabled 1) and one external_uid per person, so the three lists are one set and conformance asserts they are identical rather than leaving it a coincidence. THE MACHINE ACCOUNT IS EXCLUDED and that is the domain's load-bearing ruling (decisions/2026-09-12-1120): canon writes no external_uid for the CI bot, because a directory provisions humans and GitLab's word for a machine principal is a SERVICE ACCOUNT — a different object, a different API family, and one five armed `…/service_accounts` rulings say this universe does not mint. The rows are glUser, byte-identical to what /api/v4/users/{id} serves for each of them, which conformance checks person by person. THE SUBGROUP GETS GITLAB'S OWN not_found!, on the vendored description's own words ("for a specified top-level group"). Gated on hasExternalUid(); an older artifact answers the named hello-18 identity gap rather than [], because an empty list there would say nobody here was provisioned by a directory while this same host publishes a SAML configuration with SCIM enabled — the replica disagreeing with itself (invariant #5)

served & verified
Long tail
GET
/api/v4/groups/{id}/enterprise_users/{user_id}

SERVED 2026-09-12 (hello-18): one enterprise user by numeric id, byte-identical to that person's entry in the list. A PERSON THE ROSTER HAS AND THE DIRECTORY DID NOT PROVISION IS GITLAB'S 404 HERE, not a 200, and that is the whole point of the endpoint: it answers who is an enterprise user rather than who exists. Conformance drives the CI bot's own id, asserts the 404, and asserts the same id DOES resolve at /api/v4/users/{id} — so the refusal is about the family rather than about an id nobody has. The subgroup gets GitLab's own not_found! with its list. Gated on hasExternalUid(); an older artifact answers the named hello-18 identity gap rather than [], because an empty list there would say nobody here was provisioned by a directory while this same host publishes a SAML configuration with SCIM enabled — the replica disagreeing with itself (invariant #5)

served & verified
Long tail
error tracking1 of 2 served & verified
GET
/api/v4/projects/{id}/error_tracking/settings

the project's error-tracking settings, off reader.projectIntegration(repo, 'error-tracking') — the kind canon added for exactly these two rows, behind which sit fourteen `error_group` rows already attributed to this repository's components and to the commits that fixed them. `integrated` IS FALSE AND IT IS THE FIELD THE ROW TURNS ON: GitLab's `integrated` means GitLab's OWN built-in error tracking, and this universe's error groups are rendered by the fleet's Sentry replica — claiming GitLab as the tracker would put two hosts in disagreement about one fact (invariant #5), and it is the same `false` that makes `…/client_keys` a refusal rather than a list, because a DSN is issued by the tracker. `project_name`, `sentry_external_url` AND `api_url` ARE OMITTED, and the reason is arithmetic rather than laziness: all three name ONE Sentry project, while this universe's errors are grouped per COMPONENT and the fleet's Sentry replica renders one project per component (a canonical `Service`, 1:1 with a `Component`) — so any value would name a Sentry project that does not exist. None of the three is required by `APIEntitiesErrorTrackingProjectSetting`. NOT A COLLECTION, so there is no empty: a project with no error tracker is GitLab's own 404 rather than an object full of nulls. SERVED 2026-09-14 (hello-20 `glDelivery`): gated on hasGlDelivery(); an older artifact answers the named delivery generation gap

served & verified
Long tail
GET
/api/v4/projects/{id}/error_tracking/client_keys

refuse — `APIEntitiesErrorTrackingClientKey` IS A CREDENTIAL ISSUED BY GITLAB'S OWN INTEGRATED TRACKER: a `public_key` and a `sentry_dsn`, and GitLab is not this universe's tracker. RE-WORDED 2026-10-27 (hello-23 sweep): the old clause that this universe issues no token of any kind stopped being true when hello-23 `sentryKeys` gave every Sentry project its public client key, served on the fleet's Sentry replica (decisions/2026-10-27-2215); those keys belong to that tracker, not to a GitLab integrated one, so they are not this list's rows. NOT AN EMPTY LIST, and the difference is a claim rather than a shape: `[]` would say GitLab is the error tracker here and has issued nobody a key, while the served `…/error_tracking/settings` reports `integrated: false` because this universe's errors are rendered by the fleet's Sentry replica - two hosts agreeing about one fact (invariant #5). The vendored operation declares its own 404 beside the 200. The parent resolves first, so an unknown project is GitLab's `404 Project Not Found`. RULED 2026-09-14 by the hello-20 GitLab serving wave, on the F6 `glDelivery` decision's §5

deferred
Long tail
experiments0 of 2 served & verified
GET
/api/v4/experiments

refuse — these report GitLab Inc.'s own A/B assignments for the caller, are admin-only, and describe the vendor rather than the org

deferred
Long tail
GET
/api/v4/experiments/{experiment_name}/assignments

refuse — these report GitLab Inc.'s own A/B assignments for the caller, are admin-only, and describe the vendor rather than the org

deferred
Long tail
external status checks2 of 2 served & verified
GET
/api/v4/projects/{id}/external_status_checks

olympus-labs protects only branches — canon's protected_ref carries two branch patterns at namespace scope and no environment, tag, merge train, push rule or external status check exists; GitLab answers `[]` at 200 for each of these lists and its own 404 for a named one. THE LIST HALF, served as GitLab's empty page with the pagination headers the operation declares. UNGATED: no generation of this universe has ever protected an environment or a tag, queued a merge train, registered an external status check or set a push rule, so every registered pin answers this same `[]`. Falsifiable: the `expectsEmpty` conformance entry reds the row the day canon grows one. RULED by the founder 2026-09-12

served & verified
Long tail
GET
/api/v4/projects/{id}/merge_requests/{merge_request_iid}/status_checks

olympus-labs protects only branches — canon's protected_ref carries two branch patterns at namespace scope and no environment, tag, merge train, push rule or external status check exists; GitLab answers `[]` at 200 for each of these lists and its own 404 for a named one. THE LIST HALF, and the merge request RESOLVES — so the empty page is a statement about that merge request rather than about an iid nobody has. Served UNPAGINATED because the vendored operation declares only its two path parameters; printing pagination headers on an operation GitLab does not paginate would break invariant #2 in the name of consistency. UNGATED: no generation of this universe has ever protected an environment or a tag, queued a merge train, registered an external status check or set a push rule. RULED by the founder 2026-09-12

served & verified
Long tail
freeze periods1 of 2 served & verified
GET
/api/v4/projects/{id}/freeze_periods

olympus-labs declares no deploy freeze — canon carries no freeze window; GitLab answers `[]` at 200 for a project with none. THE LIST HALF, served as GitLab's empty page with the pagination headers the operation declares. UNGATED: no generation of this universe opens a freeze window, so every registered pin answers this same `[]`. Falsifiable: the `expectsEmpty` conformance entry reds the row the day canon grows one. RULED by the founder 2026-09-12

served & verified
Long tail
GET
/api/v4/projects/{id}/freeze_periods/{freeze_period_id}

refuse — olympus-labs declares no deploy freeze — canon carries no freeze window; GitLab answers `[]` at 200 for a project with none. THE NAMED HALF: no name resolves over a collection that is empty, so GitLab's own 404 is the whole answer — `[]` here would assert that the thing exists and holds nothing. UNGATED: no generation of this universe opens a freeze window, so the 404 is true on every artifact. RULED by the founder 2026-09-12

deferred
Long tail
iterations2 of 2 served & verified
GET
/api/v4/groups/{id}/iterations

SERVED 2026-09-12 (coverage wave W8): a GitLab iteration IS canon's sprint — reader.listSprints() — which is the same object Jira serves as a Sprint and Azure DevOps as an iteration path, so the 27 windows and their dates are one set read three ways (invariant #5). THE iid IS GROUP-WIDE AND `sequence` IS PER-CADENCE, and that is the one decision this mapping makes: `sprint.idx` counts within a TEAM and four teams each run their own series, so `idx` repeats four times over and cannot be an iid GitLab guarantees unique inside a group — an iteration cadence being exactly one team's series, `sequence` IS `sprint.idx` and the iid is the group-wide rank by start date, ties broken by team and then by index so the numbering is total and stable. `state` is GitLab's INTEGER enum (started 2, closed 3); nothing here is ever upcoming, because the generator closes a sprint whose end falls before the window's end and starts none after it. `title` and `description` are OMITTED, and that is GitLab's own shape rather than a gap: iteration titles were removed in GitLab 15.6 and the field has been nullable since. `created_at`/`updated_at` are omitted because canon's sprint carries no instant beyond its start and end. `state=` is a real narrowing; `upcoming` truthfully matches nothing, the posture `pipelines?status=` already takes for an enum member this generator never produces

served & verified
Long tail
GET
/api/v4/projects/{id}/iterations

SERVED 2026-09-12 (coverage wave W8): a GitLab iteration IS canon's sprint — reader.listSprints() — which is the same object Jira serves as a Sprint and Azure DevOps as an iteration path, so the 27 windows and their dates are one set read three ways (invariant #5). THE iid IS GROUP-WIDE AND `sequence` IS PER-CADENCE, and that is the one decision this mapping makes: `sprint.idx` counts within a TEAM and four teams each run their own series, so `idx` repeats four times over and cannot be an iid GitLab guarantees unique inside a group — an iteration cadence being exactly one team's series, `sequence` IS `sprint.idx` and the iid is the group-wide rank by start date, ties broken by team and then by index so the numbering is total and stable. `state` is GitLab's INTEGER enum (started 2, closed 3); nothing here is ever upcoming, because the generator closes a sprint whose end falls before the window's end and starts none after it. `title` and `description` are OMITTED, and that is GitLab's own shape rather than a gap: iteration titles were removed in GitLab 15.6 and the field has been nullable since. `created_at`/`updated_at` are omitted because canon's sprint carries no instant beyond its start and end. BYTE-IDENTICAL to the group's list, which conformance compares directly: an iteration belongs to the group, and `include_ancestors` — GitLab's own default — is why a project sees them at all. Two different lists would be this host disagreeing with itself about one cadence

served & verified
Long tail
offline transfers0 of 2 served & verified
GET
/api/v4/offline_exports

refuse — each of these reads reports on an export or import that a WRITE would have started, and writes are refused; GitLab answers 404 for an export nobody requested

deferred
Long tail
GET
/api/v4/offline_exports/{id}

refuse — each of these reads reports on an export or import that a WRITE would have started, and writes are refused; GitLab answers 404 for an export nobody requested

deferred
Long tail
project alias0 of 2 served & verified
GET
/api/v4/project_aliases

refuse — project aliases exist only to redirect an in-flight GitHub-import mirror and are admin-only

deferred
Long tail
GET
/api/v4/project_aliases/{name}

refuse — project aliases exist only to redirect an in-flight GitHub-import mirror and are admin-only

deferred
Long tail
project google cloud integration0 of 2 served & verified
GET
/api/v4/projects/{id}/google_cloud/setup/integrations.sh

refuse — these return a shell script that provisions the CALLER's real Google Cloud project; nothing in the response describes the simulated org

deferred
Long tail
GET
/api/v4/projects/{id}/google_cloud/setup/runner_deployment_project.sh

refuse — these return a shell script that provisions the CALLER's real Google Cloud project; nothing in the response describes the simulated org

deferred
Long tail
project templates2 of 2 served & verified
GET
/api/v4/projects/{id}/templates/{type}

SERVED 2026-09-12 (coverage wave W8) (hello-16 `refCatalogs`): reader.listLicenses()/listGitignoreTemplates() over `license_catalog` (13 rows, bodies included) and `gitignore_template` (163 rows, source included) — the SAME two compile-time tables GitHub's /licenses and /gitignore/templates already serve, which is what makes a licence fetched through either host the same licence (invariant #5). GENERATION-GATED and `[]` is not an option: an artifact without the catalogs answers the template-catalog generation gap, because `[]` here would say GitLab bundles no licence templates — a claim about the HOST rather than about this organization, and a false one. licence: FACTUAL/CC0 — the licence texts are the licences themselves and the ignore-file templates are github/gitignore's CC0-1.0 corpus, the same grant coverage/reviews/2026-09-09-hello-15-stale-reasons-git-hosts.md records for the GitHub twins. The project-scoped read is the SAME catalog, and conformance asserts the two lists are identical rather than merely similar. TWO OF THE SIX DECLARED TYPES ANSWER: `licenses` and `gitignores`, the two canon carries a catalog for. `dockerfiles`, `gitlab_ci_ymls`, `issues` and `merge_requests` answer the COVERAGE 404 naming the manifest rather than `[]` — an empty list would be a claim about GitLab's bundled set, or about this project's absent `.gitlab/issue_templates` directory, that nothing here checked (invariant #4). Conformance drives all four and asserts the 404

served & verified
Long tail
GET
/api/v4/projects/{id}/templates/{type}/{name}

SERVED 2026-09-12 (coverage wave W8) (hello-16 `refCatalogs`): reader.license(key)/gitignoreTemplate(name) over `license_catalog` (13 rows, bodies included) and `gitignore_template` (163 rows, source included) — the SAME two compile-time tables GitHub's /licenses and /gitignore/templates already serve, which is what makes a licence fetched through either host the same licence (invariant #5). GENERATION-GATED and `[]` is not an option: an artifact without the catalogs answers the template-catalog generation gap, because `[]` here would say GitLab bundles no licence templates — a claim about the HOST rather than about this organization, and a false one. licence: FACTUAL/CC0 — the licence texts are the licences themselves and the ignore-file templates are github/gitignore's CC0-1.0 corpus, the same grant coverage/reviews/2026-09-09-hello-15-stale-reasons-git-hosts.md records for the GitHub twins. BYTE-IDENTICAL to the instance-scoped read of the same template, which conformance compares directly: one catalog, two addresses. The vendored document types this operation's 200 as `APIEntitiesLicense` for EVERY type, which is true of one of the six — real GitLab presents `Entities::Template` for an ignore file, and invariant #2 mirrors the provider rather than its documentation. The same four types that are not covered on the collection are not covered here

served & verified
Long tail
project topics2 of 2 served & verified
GET
/api/v4/topics

SERVED 2026-09-12 (coverage wave W3): the distinct topics across this instance's projects with the number of projects carrying each, read from repo.topics_json. THE OLD REASON - canon's repo carries no topics - WAS FALSE: the column is real, shapeProject has published it as `topics`/`tag_list` on every project object since wave A, and ?topic= already filters the project list by it. Sorted by project count descending, which is GitLab's own order, tie-broken by name so one artifact always paginates one way. `title`, `description`, `avatar_url` and `organization_id` are omitted: a GitLab topic is an object an administrator creates and titles, canon carries the SLUG and nothing else, and title-casing the slug would be a string this universe never wrote. `search` narrows and is asserted to; `without_projects=true` is empty because every topic here IS a project's own tag; `organization_id` is an explicit error because this universe models no GitLab organization

served & verified
Long tail
GET
/api/v4/topics/{id}

SERVED 2026-09-12 (coverage wave W3): the by-id twin of /api/v4/topics, addressed by the id minted from the topic's own name - two projects tagged `platform` are tagged the same topic. Conformance asserts the list and the by-id row render one topic the same way and that an unknown id is GitLab's 404. Same falsified reason as the list

served & verified
Long tail
push rules0 of 2 served & verified
GET
/api/v4/groups/{id}/push_rule

refuse — olympus-labs protects only branches — canon's protected_ref carries two branch patterns at namespace scope and no environment, tag, merge train, push rule or external status check exists; GitLab answers `[]` at 200 for each of these lists and its own 404 for a named one. NOT A NAMED HALF AND NOT A COLLECTION: `push_rule` is a SINGULAR sub-resource that takes no name, so `[]` is not a shape it can take at all — there is simply no rule object to render, and the vendored operation declares exactly two responses, a 200 with the rule and a 404 without one. UNGATED: no generation of this universe has ever protected an environment or a tag, queued a merge train, registered an external status check or set a push rule. RULED by the founder 2026-09-12

deferred
Long tail
GET
/api/v4/projects/{id}/push_rule

refuse — olympus-labs protects only branches — canon's protected_ref carries two branch patterns at namespace scope and no environment, tag, merge train, push rule or external status check exists; GitLab answers `[]` at 200 for each of these lists and its own 404 for a named one. NOT A NAMED HALF AND NOT A COLLECTION: `push_rule` is a SINGULAR sub-resource that takes no name, so `[]` is not a shape it can take at all — there is simply no rule object to render, and the vendored operation declares exactly two responses, a 200 with the rule and a 404 without one. UNGATED: no generation of this universe has ever protected an environment or a tag, queued a merge train, registered an external status check or set a push rule. RULED by the founder 2026-09-12

deferred
Long tail
runner controllers0 of 2 served & verified
GET
/api/v4/runner_controllers/{id}

refuse — runner ADMINISTRATION: managers, controllers, tokens and router discovery are instance-scope operations GitLab restricts to admins, and this replica authenticates as a member of one organization rather than as an instance administrator. RE-WORDED 2026-09-14 (hello-20 gate 1, the falsified-rulings sweep), the mode unmoved: the sentence used to rest on "physical CI machines ... not the org", and hello-20's `ciRunners` domain made that half false - canon now carries runners, their groups and their labels, `job.runner` says which machine ran each of 592 jobs, and `/api/v4/runners` serves the pool this caller can reach. Two legs survive and either is enough alone. THE ACCESS LEG: `/api/v4/runners/all` is the INSTANCE list and `/api/v4/runners` is the caller's, which is GitLab's own distinction rather than ours, and conformance asserts the two have not become synonyms. THE CREDENTIAL LEG: a runner token and a controller token are secrets, and this canon stores secret NAMES and never values (hello-19's `ci_binding` rule, which `/projects/{id}/secure_files` already rests on). A runner existing is what made the first sentence stale; neither of these moves

deferred
Long tail
GET
/api/v4/runner_controllers/{id}/scopes

refuse — runner ADMINISTRATION: managers, controllers, tokens and router discovery are instance-scope operations GitLab restricts to admins, and this replica authenticates as a member of one organization rather than as an instance administrator. RE-WORDED 2026-09-14 (hello-20 gate 1, the falsified-rulings sweep), the mode unmoved: the sentence used to rest on "physical CI machines ... not the org", and hello-20's `ciRunners` domain made that half false - canon now carries runners, their groups and their labels, `job.runner` says which machine ran each of 592 jobs, and `/api/v4/runners` serves the pool this caller can reach. Two legs survive and either is enough alone. THE ACCESS LEG: `/api/v4/runners/all` is the INSTANCE list and `/api/v4/runners` is the caller's, which is GitLab's own distinction rather than ours, and conformance asserts the two have not become synonyms. THE CREDENTIAL LEG: a runner token and a controller token are secrets, and this canon stores secret NAMES and never values (hello-19's `ci_binding` rule, which `/projects/{id}/secure_files` already rests on). A runner existing is what made the first sentence stale; neither of these moves

deferred
Long tail
saml group links2 of 2 served & verified
GET
/api/v4/groups/{id}/saml_group_links

SERVED 2026-09-12 (hello-17): the groups this organization's directory asserts and what each may do here — APIEntitiesSamlGroupLink over canon's identity_group (the asserted group, hello-15) joined to its one membership row at scope_kind = 'group' (hello-17). `access_level` maps canon's five role keys onto GitLab's own five constants by what the role's permissions column actually grants: owner 50, admin 40, maintainer 40, member 30, reader 10. `provider` is the directory's own protocol rather than a literal, so a universe that ever ran OIDC would say so. `member_role_id` IS OMITTED and that is a statement: it names a CUSTOM member role, olympus-labs mints none — /groups/{id}/member_roles is armed `empty` on exactly that fact — and an id here would address a role this host says does not exist. A group with no role is not linked at all rather than linked at an invented level. GATED ON THE DIRECTORY AND ON hasGrants(), NOT ON hasExternalUid(), and that is the wave's one deliberate asymmetry: a SAML group link names a GROUP, not a person, so it needs identity_group (hello-15) and the group's own role (hello-17 features.grants, reader.groupRole) and no per-person handle at all. Both halves are already on the LIVE artifact, so these two rows answer TODAY rather than waiting for hello-18's cutover — gating them on a uid they do not read would have reported a gap for a fact that is present. The subgroup answers [] for the list and GitLab's 404 for a named link: the directory hangs off the organization

served & verified
Long tail
GET
/api/v4/groups/{id}/saml_group_links/{saml_group_name}

SERVED 2026-09-12 (hello-17): one link by the directory group's name, byte-identical to its entry in the list. `?provider=` IS HONOURED RATHER THAN IGNORED: naming this directory's own protocol narrows to the same link and naming one nobody runs is GitLab's 404, which conformance drives both ways — a silently-ignored filter that still answered 200 is the wrong answer this renderer refuses to give. A name the directory does not assert is GitLab's own 404. GATED ON THE DIRECTORY AND ON hasGrants(), NOT ON hasExternalUid(), and that is the wave's one deliberate asymmetry: a SAML group link names a GROUP, not a person, so it needs identity_group (hello-15) and the group's own role (hello-17 features.grants, reader.groupRole) and no per-person handle at all. Both halves are already on the LIVE artifact, so these two rows answer TODAY rather than waiting for hello-18's cutover — gating them on a uid they do not read would have reported a gap for a fact that is present. The subgroup answers [] for the list and GitLab's 404 for a named link: the directory hangs off the organization

served & verified
Long tail
workspaces0 of 2 served & verified
GET
/api/v4/internal/agents/agentw/agent_info

refuse — the /api/v4/internal/* family is GitLab-to-Gitaly/agent plumbing outside the public API contract; no client library calls it

deferred
Long tail
GET
/api/v4/internal/agents/agentw/authorize_user_access

refuse — the /api/v4/internal/* family is GitLab-to-Gitaly/agent plumbing outside the public API contract; no client library calls it

deferred
Long tail
active context0 of 1 served & verified
GET
/api/v4/admin/active_context/connections

refuse — GitLab answers 403 to a non-admin token here (Geo, Sidekiq, storage moves, LDAP, application settings, feature toggles, background migrations); a tenant-shaped mirror has no instance to administer

deferred
Long tail
add on purchases0 of 1 served & verified
GET
/api/v4/namespaces/{id}/subscription_add_on_purchase/{add_on_name}

refuse — THIS NAMESPACE BOUGHT NO ADD-ON. hello-20's F8 `billingPeriods` unblocked two GitLab rows and only one of them is a serve: `/namespaces/{id}/gitlab_subscription` is this namespace's SEAT subscription and is green off `billing_period`, but that table carries exactly ONE GitLab series - `gitlab·seats·seat-month`, the only GitLab entry in `canon/src/billing-periods.ts`'s `BILLING_RATE_CARD` - and no add-on purchase anywhere. The three AI assistants this organization licenses belong to other vendors (`ai_seat.tool` is `copilot`, `claude_code` or `cursor`), so publishing them here as a GitLab Duo purchase would be a false claim about who was paid. So this is the by-NAME half of a set that is empty, which is `decisions/2026-09-12-0348`'s own class, and the operation declares the 404. NOT PART OF THE FOUNDER'S 2026-09-14 BATCH: this is the one ruling the hello-20 GitLab wave armed on its own, flagged in its decision entry and in its pull request for review. UNGATED: no generation of this universe records a GitLab add-on; driven in conformance through a namespace `/api/v4/namespaces/{id}` serves.

deferred
Long tail
agents0 of 1 served & verified
GET
/api/v4/job/allowed_agents

refuse — these answer a running runner presenting a CI job token, not an API caller; a frozen universe has no live job

deferred
Long tail
alert management1 of 1 served & verified
GET
/api/v4/projects/{id}/alert_management_alerts/{alert_iid}/metric_images

SERVED 2026-09-12 (hello-18): APIEntitiesMetricImage over the picture the responder took of each alert the incident opened — one screenshot-svg per alert, dated to the moment the series fell back under the threshold, because a picture of a breach is taken once the breach has a shape. WHICH PROJECT AN ALERT BELONGS TO IS DERIVED rather than assumed from there being one: an alert names the incident it opened, an incident names the issue the git canon filed, and that issue names its project — so an alert that opened no incident belongs to no project and is addressable nowhere, which is the truthful answer rather than a default. `alert_iid` is its 1-based position in that project's own alert list, in listAlerts() id order: mint order, therefore never renumbered, the same rule the merge-request and issue iid sequences follow. `file_path` is GitLab's own system-store template with this file's id and name in it — the vendored component's own example path — so it is reproducible rather than decorative; `url` and `url_text` are OMITTED because they are the dashboard link a responder typed beside the image and canon records none. Conformance asserts every image is an image by its content type, that none predates the alert it pictures, and that an alert iid nobody has is GitLab's own 404. Gated on hasAttachmentParents()

served & verified
Long tail
ci lint1 of 1 served & verified
GET
/api/v4/projects/{id}/ci/lint

the parthenon repository has no `.gitlab-ci.yml` — its pipelines are defined in `.circleci/` (ci_config_document, 3 rows, all under .circleci/) and `.github/workflows/`, and no tree entry names a GitLab CI file; GitLab's own answer for a project with no config is a 200 carrying `valid: false` and its own "Please provide content of .gitlab-ci.yml" error. NOT A COLLECTION: the operation returns `APIEntitiesCiLintResult`, an object, so the honest empty is GitLab's own failed lint rather than `[]`. `merged_yaml`, `includes` and `jobs` are OMITTED rather than nulled — there is no configuration to merge, nothing to include and no job to describe, all three are optional in the vendored component, and a null typed `string` would buy a deviation for nothing. UNGATED: no generation of this repository's tree carries a `.gitlab-ci.yml`. Falsifiable: the `expectsEmpty` conformance entry (`ciLintEmptyErrors`) names every field and reds the row the day one appears. RULED by the founder 2026-09-12

served & verified
Long tail
code review analytics0 of 1 served & verified · 1 deviation
GET
/api/v4/analytics/code_review

SERVED 2026-09-12 (coverage wave W8): GitLab's own definition of the review queue, in its own words — the OPEN merge requests that have at least one non-author comment, ordered by review time with the longest first. reader.listPulls + pullReviewComments + listReviews; a merge request nobody has reviewed is absent rather than listed with a null time, which is what makes the queue a queue. `review_time` is whole hours from the first non-author note to the NEWEST INSTANT this universe records against that merge request (`mrUpdatedEpoch`, the same value `updated_at` publishes, widened by the conversation's own last note and last review): a snapshot has a last recorded event rather than a wall clock, canon carries review comments that postdate the anchor, and anchoring the subtraction would print a negative duration for a merge request that is plainly under review. `approved_by` is the approving reviews' authors and is a `deviation`, the vendored component typing a collection as one object. `diff_stats` is OMITTED: canon's `pull` carries no line counts at all and inventing them would be a placeholder

deviation
Long tail
compliance policy settings0 of 1 served & verified
GET
/api/v4/admin/security/compliance_policy_settings

refuse — GitLab answers 403 to a non-admin token here (Geo, Sidekiq, storage moves, LDAP, application settings, feature toggles, background migrations); a tenant-shaped mirror has no instance to administer

deferred
Long tail
jira forge subscriptions1 of 1 served & verified
GET
/api/v4/integrations/jira_forge/subscriptions

the GitLab namespaces subscribed to the Jira Forge installation — ONE row, for this organization's group, dated at the earliest instant any of its projects was linked to Jira (reader.projectIntegration(repo, 'jira')). A FORGE SUBSCRIPTION IS ALWAYS NAMESPACE-SCOPED and has no project-level counterpart at all, which is what makes this row servable while `/groups/{id}/integrations` is an honest empty: the group list answers integration RECORDS placed at the group, and canon places none there, whereas the only place this universe's Jira link can appear on THIS endpoint is the group. The development data the subscription describes syncing is real and checkable — the fleet renders the same 61 issues as a Jira project. `JiraConnectGroupEntity`'s four fields are read off the SAME shaper `/api/v4/groups/{id}` answers with (`glGroup`), so the group named here cannot describe itself differently from the group endpoint (invariant #5), and `avatar_url` is emitted only where `/uploads/-/system/group/avatar/{id}/avatar.png` answers. `unlink_path` is GitLab's own path shape for the record; it names a WRITE, which this replica refuses like every other. No project and no group in the request, so the gate is the whole guard. SERVED 2026-09-14 (hello-20 `glDelivery`): gated on hasGlDelivery(); an older artifact answers the named delivery generation gap

served & verified
Long tail
knowledge graph0 of 1 served & verified
GET
/api/v4/admin/knowledge_graph/namespaces

refuse — GitLab answers 403 to a non-admin token here (Geo, Sidekiq, storage moves, LDAP, application settings, feature toggles, background migrations); a tenant-shaped mirror has no instance to administer

deferred
Long tail
mcp0 of 1 served & verified
GET
/api/v4/mcp

refuse — /api/v4/mcp is a JSON-RPC/SSE agent channel, not a read of org data; SandboxAPIs serves its own MCP server and two tool surfaces would conflict

deferred
Long tail
metric images1 of 1 served & verified
GET
/api/v4/projects/{id}/issues/{issue_iid}/metric_images

RE-RULED 2026-09-12 by the founder, replacing the `generate` this row carried since the hello-18 upload wave. THE FACTS DID NOT MOVE, THE VERDICT DID. GitLab scopes this operation to INCIDENTS and this universe HAS incident issues, so the collection is addressable and real; what it holds is nothing. The responder's picture IS in this universe - it is attached to the ALERT, and /projects/{id}/alert_management_alerts/{alert_iid}/metric_images has published it since hello-18 - so rendering the same canonical object under the issue as well would publish one upload twice (invariant #5). The one file canon hangs off the incident issue is a har-summary (requests.har), a capture rather than a picture of a metric. `generate` said the opposite - that the row was waiting on canon - and a row waiting forever for a file nobody would attach is a backlog entry rather than a plan. NO GENERATION GATE, unlike the alert half: `[]` is true on every artifact this fleet has ever published, because no generation has hung an image attachment off the incident issue. Both parents still resolve. RULED by the founder 2026-09-12, reason as given in decisions/2026-09-12-2026-post-h18-serve-pass.md.

served & verified
Long tail
metrics0 of 1 served & verified
GET
/api/v4/usage_data/metric_definitions

refuse — service ping reports what a real installation sends to GitLab Inc.; a mirror has no installation to report on and the endpoints are admin-only

deferred
Long tail
migrations0 of 1 served & verified
GET
/api/v4/admin/migrations/pending

refuse — GitLab answers 403 to a non-admin token here (Geo, Sidekiq, storage moves, LDAP, application settings, feature toggles, background migrations); a tenant-shaped mirror has no instance to administer

deferred
Long tail
oauth applications0 of 1 served & verified
GET
/api/v4/internal/agents/agentw/server_config

refuse — the /api/v4/internal/* family is GitLab-to-Gitaly/agent plumbing outside the public API contract; no client library calls it

deferred
Long tail
project mirrors0 of 1 served & verified
GET
/api/v4/projects/{id}/mirror/pull

refuse — the collection this id would index is empty in this universe, so the only honest answer is the vendor's own 404

deferred
Long tail
project snapshots0 of 1 served & verified
GET
/api/v4/projects/{id}/snapshot

refuse — each of these reads reports on an export or import that a WRITE would have started, and writes are refused; GitLab answers 404 for an export nobody requested

deferred
Long tail
runner controller tokens0 of 1 served & verified
GET
/api/v4/runner_controllers/{runner_controller_id}/tokens/{id}

refuse — runner ADMINISTRATION: managers, controllers, tokens and router discovery are instance-scope operations GitLab restricts to admins, and this replica authenticates as a member of one organization rather than as an instance administrator. RE-WORDED 2026-09-14 (hello-20 gate 1, the falsified-rulings sweep), the mode unmoved: the sentence used to rest on "physical CI machines ... not the org", and hello-20's `ciRunners` domain made that half false - canon now carries runners, their groups and their labels, `job.runner` says which machine ran each of 592 jobs, and `/api/v4/runners` serves the pool this caller can reach. Two legs survive and either is enough alone. THE ACCESS LEG: `/api/v4/runners/all` is the INSTANCE list and `/api/v4/runners` is the caller's, which is GitLab's own distinction rather than ours, and conformance asserts the two have not become synonyms. THE CREDENTIAL LEG: a runner token and a controller token are secrets, and this canon stores secret NAMES and never values (hello-19's `ci_binding` rule, which `/projects/{id}/secure_files` already rests on). A runner existing is what made the first sentence stale; neither of these moves

deferred
Long tail
software composition analysis0 of 1 served & verified
GET
/api/v4/jobs/{id}/sbom_scans/{sbom_digest}

refuse — no SBOM SCAN result exists for any job, because no security product is enabled on olympus-labs (§A-R1 of the hello-20 batch, founder-approved 2026-09-14, extending the founder's ruling of 2026-09-12). The operation downloads a scan result addressed BY DIGEST over a set that is empty, so it is the 404 half of §A-R1 rather than the [] half, and the operation declares that 404 itself. It cannot be built either: the components a scan would report are the REAL third-party packages `render-sbom.ts` parses out of this repository's own manifests, and a generated result over them would be a false claim about somebody else's software. UNGATED: no generation of this universe holds a SecurityFinding, so the refusal is true on every artifact and on every frozen pin. Driven in conformance through a job that RESOLVES, so the 404 is about the scan rather than about a parent nobody has.

deferred
Long tail
todos1 of 1 served & verified
GET
/api/v4/todos

the calling persona has no pending to-do — canon carries no per-person work queue and every review request it would echo is already served at /merge_requests; GitLab answers `[]` at 200 for a caller with an empty queue. THE LIST HALF, served as GitLab's empty page with the pagination headers the operation declares. UNGATED: canon carries no per-person work queue in any generation, so every registered pin answers this same `[]`. Falsifiable: the `expectsEmpty` conformance entry reds the row the day canon grows one. RULED by the founder 2026-09-12

served & verified
Next
web commits0 of 1 served & verified
GET
/api/v4/web_commits/public_key

refuse — no commit or tag in this universe is GPG-signed, and GitLab answers its own 404 'GPG Signature Not Found' for an unsigned object

deferred
Long tail

03 / What's simulated

One data set, rendered in GitLab's dialect.

Every GitLab call resolves against the same simulated data set every other provider serves. Counted from the gl-v4-g12 artifact (universe generation 12):

What another provider has to agree with, and the test that makes it

Each line below is one assertion in the conformance suite named beside it — run on every build, over one artifact, through the real renderers. Nothing is claimed here that no expect checks.

04 / Known deviations

83 rows are served but do not validate.

These answer with real data and match what GitLab actually returns, but fail the spec we vendor from GitLab — nearly always a bug in that published description. They are listed rather than counted so you can see whether one of them is the endpoint your client calls, and they are never counted as verified.

05 / Pinned snapshots

Universes that never change, on their own hostnames.

Each pin regenerates byte-identically on every request, so a test written against one never drifts. Generations are DIFFERENT universes, not versions of one — never swap a suffix expecting the same data.

PinHostUniverse generationGitLab API versionRepository files
gl-v4gl-v4.snap.sandboxapis.devgeneration 1v4not in this generation
gl-v4-g10gl-v4-g10.snap.sandboxapis.devgeneration 10v4served
gl-v4-g11gl-v4-g11.snap.sandboxapis.devgeneration 11v4served
gl-v4-g12gl-v4-g12.snap.sandboxapis.devgeneration 12v4served
gl-v4-g2gl-v4-g2.snap.sandboxapis.devgeneration 2v4served
gl-v4-g3gl-v4-g3.snap.sandboxapis.devgeneration 3v4served
gl-v4-g5gl-v4-g5.snap.sandboxapis.devgeneration 5v4served
gl-v4-g6gl-v4-g6.snap.sandboxapis.devgeneration 6v4served
gl-v4-g8gl-v4-g8.snap.sandboxapis.devgeneration 8v4served
gl-v4-g9gl-v4-g9.snap.sandboxapis.devgeneration 9v4served

Need an endpoint that is not served yet?

Every row GitLab's manifest carries is on this page, so “not here” is an answer rather than a gap in the rendering. Tell us which path and which client, and it moves up the queue — the order is set by what people ask for.

Request coverage