Founding keys are opena year free at 6,000 req/hour, for the first 1,000 developersClaim yours

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

Deep554/755 · 73%

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
9 — generation 1, 2, 3, 5, 6, 8, 9, 10, 11
Serving since
2026-07-31
Lifecycle
Live

Served & verified

329 / 44%

Answering with real universe data, each response checked against GitLab's published spec by the conformance suite on this build. 73 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

426

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.39.2

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

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.

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

packages0 of 79 served & verified
GET
/api/v4/group/{id}/-/packages/composer/p/{sha}

generate — canon has no Package/PackageFile; a simulated org publishes packages and container images

deferred
Long tail
GET
/api/v4/group/{id}/-/packages/composer/packages

generate — canon has no Package/PackageFile; a simulated org publishes packages and container images

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

generate — canon has no Package/PackageFile; a simulated org publishes packages and container images

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

generate — canon has no Package/PackageFile; a simulated org publishes packages and container images

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

generate — canon has no Package/PackageFile; a simulated org publishes packages and container images

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

generate — canon has no Package/PackageFile; a simulated org publishes packages and container images

deferred
Long tail
GET
/api/v4/groups/{id}/-/packages/nuget/index

generate — canon has no Package/PackageFile; a simulated org publishes packages and container images

deferred
Long tail
GET
/api/v4/groups/{id}/-/packages/nuget/query

generate — canon has no Package/PackageFile; a simulated org publishes packages and container images

deferred
Long tail
GET
/api/v4/groups/{id}/-/packages/nuget/v2

generate — canon has no Package/PackageFile; a simulated org publishes packages and container images

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

generate — canon has no Package/PackageFile; a simulated org publishes packages and container images

deferred
Long tail
GET
/api/v4/groups/{id}/-/packages/pypi/simple

generate — canon has no Package/PackageFile; a simulated org publishes packages and container images

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

generate — canon has no Package/PackageFile; a simulated org publishes packages and container images

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

generate — canon has no Package/PackageFile; a simulated org publishes packages and container images

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

generate — canon has no Package/PackageFile; a simulated org publishes packages and container images

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

generate — canon has no Package/PackageFile; a simulated org publishes packages and container images

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

generate — canon has no Package/PackageFile; a simulated org publishes packages and container images

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

generate — canon has no Package/PackageFile; a simulated org publishes packages and container images

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

generate — canon has no Package/PackageFile; a simulated org publishes packages and container images

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

generate — canon has no Package/PackageFile; a simulated org publishes packages and container images

deferred
Long tail
GET
/api/v4/packages/conan/v1/conans/search

generate — canon has no Package/PackageFile; a simulated org publishes packages and container images

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

generate — canon has no Package/PackageFile; a simulated org publishes packages and container images

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}

generate — canon has no Package/PackageFile; a simulated org publishes packages and container images

deferred
Long tail
GET
/api/v4/packages/conan/v1/ping

generate — canon has no Package/PackageFile; a simulated org publishes packages and container images

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

generate — canon has no Package/PackageFile; a simulated org publishes packages and container images

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

generate — canon has no Package/PackageFile; a simulated org publishes packages and container images

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

generate — canon has no Package/PackageFile; a simulated org publishes packages and container images

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

generate — canon has no Package/PackageFile; a simulated org publishes packages and container images

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

generate — canon has no Package/PackageFile; a simulated org publishes packages and container images

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

generate — canon has no Package/PackageFile; a simulated org publishes packages and container images

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

generate — canon has no Package/PackageFile; a simulated org publishes packages and container images

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

generate — canon has no Package/PackageFile; a simulated org publishes packages and container images

deferred
Long tail
GET
/api/v4/projects/{id}/packages/{package_id}/package_files/{package_file_id}/download

generate — canon has no Package/PackageFile; a simulated org publishes packages and container images

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

generate — canon has no Package/PackageFile; a simulated org publishes packages and container images

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

generate — canon has no Package/PackageFile; a simulated org publishes packages and container images

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

generate — canon has no Package/PackageFile; a simulated org publishes packages and container images

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

generate — canon has no Package/PackageFile; a simulated org publishes packages and container images

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

generate — canon has no Package/PackageFile; a simulated org publishes packages and container images

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

generate — canon has no Package/PackageFile; a simulated org publishes packages and container images

deferred
Long tail
GET
/api/v4/projects/{id}/packages/cargo/config.json

generate — canon has no Package/PackageFile; a simulated org publishes packages and container images

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

generate — canon has no Package/PackageFile; a simulated org publishes packages and container images

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

generate — canon has no Package/PackageFile; a simulated org publishes packages and container images

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

generate — canon has no Package/PackageFile; a simulated org publishes packages and container images

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

generate — canon has no Package/PackageFile; a simulated org publishes packages and container images

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

generate — canon has no Package/PackageFile; a simulated org publishes packages and container images

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

generate — canon has no Package/PackageFile; a simulated org publishes packages and container images

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

generate — canon has no Package/PackageFile; a simulated org publishes packages and container images

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

generate — canon has no Package/PackageFile; a simulated org publishes packages and container images

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}

generate — canon has no Package/PackageFile; a simulated org publishes packages and container images

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}

generate — canon has no Package/PackageFile; a simulated org publishes packages and container images

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

generate — canon has no Package/PackageFile; a simulated org publishes packages and container images

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

generate — canon has no Package/PackageFile; a simulated org publishes packages and container images

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

generate — canon has no Package/PackageFile; a simulated org publishes packages and container images

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

generate — canon has no Package/PackageFile; a simulated org publishes packages and container images

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

generate — canon has no Package/PackageFile; a simulated org publishes packages and container images

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

generate — canon has no Package/PackageFile; a simulated org publishes packages and container images

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}

generate — canon has no Package/PackageFile; a simulated org publishes packages and container images

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

generate — canon has no Package/PackageFile; a simulated org publishes packages and container images

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

generate — canon has no Package/PackageFile; a simulated org publishes packages and container images

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

generate — canon has no Package/PackageFile; a simulated org publishes packages and container images

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}

generate — canon has no Package/PackageFile; a simulated org publishes packages and container images

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

generate — canon has no Package/PackageFile; a simulated org publishes packages and container images

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

generate — canon has no Package/PackageFile; a simulated org publishes packages and container images

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

generate — canon has no Package/PackageFile; a simulated org publishes packages and container images

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

generate — canon has no Package/PackageFile; a simulated org publishes packages and container images

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

generate — canon has no Package/PackageFile; a simulated org publishes packages and container images

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

generate — canon has no Package/PackageFile; a simulated org publishes packages and container images

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

generate — canon has no Package/PackageFile; a simulated org publishes packages and container images

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

generate — canon has no Package/PackageFile; a simulated org publishes packages and container images

deferred
Long tail
GET
/api/v4/projects/{id}/packages/nuget/index

generate — canon has no Package/PackageFile; a simulated org publishes packages and container images

deferred
Long tail
GET
/api/v4/projects/{id}/packages/nuget/query

generate — canon has no Package/PackageFile; a simulated org publishes packages and container images

deferred
Long tail
GET
/api/v4/projects/{id}/packages/nuget/v2

generate — canon has no Package/PackageFile; a simulated org publishes packages and container images

deferred
Long tail
GET
/api/v4/projects/{id}/packages/nuget/v2/$metadata

generate — canon has no Package/PackageFile; a simulated org publishes packages and container images

deferred
Long tail
GET
/api/v4/projects/{id}/packages/pypi/simple

generate — canon has no Package/PackageFile; a simulated org publishes packages and container images

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

generate — canon has no Package/PackageFile; a simulated org publishes packages and container images

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

generate — canon has no Package/PackageFile; a simulated org publishes packages and container images

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

generate — canon has no Package/PackageFile; a simulated org publishes packages and container images

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

generate — canon has no Package/PackageFile; a simulated org publishes packages and container images

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

generate — canon has no Package/PackageFile; a simulated org publishes packages and container images

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

generate — canon has no Package/PackageFile; a simulated org publishes packages and container images

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 all 127 registered pins 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
groups25 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}/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 [] all 127 registered pins 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
GET
/api/v4/groups/{id}/audit_events/{audit_event_id}

generate — A WIDENING, NOT A BUILD: canon.AdminAuditEvent exists (packages/canon/src/support-desk.ts:690) and its own doc comment calls it "GENERIC by construction". The action vocabulary (AuditAction, :602), the change vocabulary (AuditChangeSlot, :633), the target vocabulary (AuditTargetKind, :611) and the RFC 5737 source-address rule (AuditSourceBlock, :652) all shipped, and Zendesk serves the table today (reader.listAuditEvents(), ruled 2026-09-07). TWO things are missing and neither is the entity: git-host members on AuditTargetKind, and a generation that WRITES personActor — the field is declared for exactly this wave and "this generation writes none" (:695-697)

planned
Core
projects20 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}/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}/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/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}/audit_events

generate — A WIDENING, NOT A BUILD: canon.AdminAuditEvent exists (packages/canon/src/support-desk.ts:690) and its own doc comment calls it "GENERIC by construction". The action vocabulary (AuditAction, :602), the change vocabulary (AuditChangeSlot, :633), the target vocabulary (AuditTargetKind, :611) and the RFC 5737 source-address rule (AuditSourceBlock, :652) all shipped, and Zendesk serves the table today (reader.listAuditEvents(), ruled 2026-09-07). TWO things are missing and neither is the entity: git-host members on AuditTargetKind, and a generation that WRITES personActor — the field is declared for exactly this wave and "this generation writes none" (:695-697)

planned
Core
GET
/api/v4/projects/{id}/audit_events/{audit_event_id}

generate — A WIDENING, NOT A BUILD: canon.AdminAuditEvent exists (packages/canon/src/support-desk.ts:690) and its own doc comment calls it "GENERIC by construction". The action vocabulary (AuditAction, :602), the change vocabulary (AuditChangeSlot, :633), the target vocabulary (AuditTargetKind, :611) and the RFC 5737 source-address rule (AuditSourceBlock, :652) all shipped, and Zendesk serves the table today (reader.listAuditEvents(), ruled 2026-09-07). TWO things are missing and neither is the entity: git-host members on AuditTargetKind, and a generation that WRITES personActor — the field is declared for exactly this wave and "this generation writes none" (:695-697)

planned
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

generate — canon has no SecurityFinding/SbomComponent/Attestation; a scanning org has findings

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

generate — canon has no Star; the founder ruled stars generated on both hosts so a starred repo is the same repo on GitHub and GitLab

planned
Core
GET
/api/v4/users/{user_id}/starred_projects

generate — canon has no Star; the founder ruled stars generated on both hosts so a starred repo is the same repo on GitHub and GitLab

planned
Core
events7 of 23 served & verified
GET
/api/v4/events
served & verified
Next
GET
/api/v4/projects/{id}/events
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}/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/groups/{id}/epics/{eventable_id}/resource_label_events

generate — canon keeps current label/milestone state, not its history; needs a ResourceEvent log

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

generate — canon keeps current label/milestone state, not its history; needs a ResourceEvent log

deferred
Next
GET
/api/v4/groups/{id}/epics/{eventable_id}/resource_state_events

generate — canon keeps current label/milestone state, not its history; needs a ResourceEvent log

deferred
Next
GET
/api/v4/groups/{id}/epics/{eventable_id}/resource_state_events/{event_id}

generate — canon keeps current label/milestone state, not its history; needs a ResourceEvent log

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

generate — canon keeps current label/milestone state, not its history; needs a ResourceEvent log

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

generate — canon keeps current label/milestone state, not its history; needs a ResourceEvent log

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

generate — canon keeps current label/milestone state, not its history; needs a ResourceEvent log

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

generate — canon keeps current label/milestone state, not its history; needs a ResourceEvent log

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

generate — canon keeps current label/milestone state, not its history; needs a ResourceEvent log

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

generate — canon keeps current label/milestone state, not its history; needs a ResourceEvent log

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

generate — canon keeps current label/milestone state, not its history; needs a ResourceEvent log

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

generate — canon keeps current label/milestone state, not its history; needs a ResourceEvent log

deferred
Next
GET
/api/v4/projects/{id}/merge_requests/{eventable_id}/resource_label_events

generate — canon keeps current label/milestone state, not its history; needs a ResourceEvent log

deferred
Next
GET
/api/v4/projects/{id}/merge_requests/{eventable_id}/resource_label_events/{event_id}

generate — canon keeps current label/milestone state, not its history; needs a ResourceEvent log

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

generate — canon keeps current label/milestone state, not its history; needs a ResourceEvent log

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

generate — canon keeps current label/milestone state, not its history; needs a ResourceEvent log

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 · 6 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/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/preferences

generate — canon's person carries no email list, preferences or status message

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 emoji10 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/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/groups/{id}/epics/{epic_iid}/notes/{note_id}/award_emoji

generate — HALF UNBLOCKED 2026-09-12 (hello-17): the epic-COMMENT entity this reason used to name EXISTS now (features.entityNotes) and this renderer serves all six epic note and discussion routes, so a note_id resolves. What is still missing is the AWARDABLE: content_reaction carries target_kind values for issues, comments, pulls, review comments, releases and epics, and nothing in the reaction pass attaches a reaction to an epic NOTE. [] here would assert that nobody reacted to this sentence, which is a claim about the people rather than about the artifact, so the row keeps the coverage 404. THE ASK: an `entityNote` awardable kind in the reaction pass, and this row plus its by-id twin go green with no new entity

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

generate — HALF UNBLOCKED 2026-09-12 (hello-17): the by-id twin of the epic-note award list, and blocked by the same single fact - the note exists and resolves as of this wave, the reaction on it does not, because no content_reaction row carries an epic note as its target. THE ASK is the list row's

deferred
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
epics0 of 13 served & verified · 7 deviations
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 still is not, which is why groups/{id}/epics/{epic_iid}/related_epics stays generate. 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}/epic_boards

generate — canon has sprint/epic/workflow state but no Board; the board layout itself must be generated

deferred
Next
GET
/api/v4/groups/{id}/epic_boards/{board_id}

generate — canon has sprint/epic/workflow state but no Board; the board layout itself must be generated

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

generate — canon has sprint/epic/workflow state but no Board; the board layout itself must be generated

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

generate — canon has sprint/epic/workflow state but no Board; the board layout itself must be generated

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

generate — STILL BLOCKED 2026-09-12 (hello-17): IssueLink is canon now and the four issue-to-issue rows serve, but this operation is EPIC-to-EPIC and issue_link's source and target are both issue ids. The one epic relation canon carries is PARENTAGE (features.portfolio's level and parent), which GitLab serves at /epics/{epic_iid}/epics and is a different relation from the sibling `related_epics` set. THE ASK: an epic-to-epic link kind, or a widening of issue_link's ends to a discriminated parent

deferred
Next
GET
/api/v4/groups/{id}/related_epic_links

generate — STILL BLOCKED 2026-09-12 (hello-17): the group-wide list of the same epic-to-epic relation /epics/{epic_iid}/related_epics enumerates per epic, and blocked by the same single fact - issue_link links two ISSUES, and canon's only epic-to-epic relation is parentage. THE ASK is that row's

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 all 127 frozen pins, which fetch 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
runners0 of 11 served & verified
GET
/api/v4/groups/{id}/runners

generate — canon's jobs ran on nothing; needs a Runner the job table can point at

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

generate — canon's jobs ran on nothing; needs a Runner the job table can point at

deferred
Long tail
GET
/api/v4/runner_controllers

refuse — runner managers, controllers, tokens and router discovery describe physical CI machines and their credentials, not the org; GitLab restricts them to admins

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

refuse — runner managers, controllers, tokens and router discovery describe physical CI machines and their credentials, not the org; GitLab restricts them to admins

deferred
Long tail
GET
/api/v4/runners

generate — canon's jobs ran on nothing; needs a Runner the job table can point at

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

generate — canon's jobs ran on nothing; needs a Runner the job table can point at

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

generate — canon's jobs ran on nothing; needs a Runner the job table can point at

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

refuse — runner managers, controllers, tokens and router discovery describe physical CI machines and their credentials, not the org; GitLab restricts them to admins

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

generate — canon's jobs ran on nothing; needs a Runner the job table can point at

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

refuse — runner managers, controllers, tokens and router discovery describe physical CI machines and their credentials, not the org; GitLab restricts them to admins

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

refuse — runner managers, controllers, tokens and router discovery describe physical CI machines and their credentials, not the org; GitLab restricts them to admins

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

generate — canon keeps current label/milestone state, not its history; needs a ResourceEvent log

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

generate — canon keeps current label/milestone state, not its history; needs a ResourceEvent log

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 workflows0 of 9 served & verified
GET
/api/v4/ai/duo_workflows/code_review/custom_instructions

generate — canon has AgentSession but no Duo-shaped workflow, checkpoint or trace projection

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

generate — canon has AgentSession but no Duo-shaped workflow, checkpoint or trace projection

deferred
Long tail
GET
/api/v4/ai/duo_workflows/workflows/{id}

generate — canon has AgentSession but no Duo-shaped workflow, checkpoint or trace projection

deferred
Long tail
GET
/api/v4/ai/duo_workflows/workflows/{id}/checkpoints

generate — canon has AgentSession but no Duo-shaped workflow, checkpoint or trace projection

deferred
Long tail
GET
/api/v4/ai/duo_workflows/workflows/{id}/checkpoints/{checkpoint_id}

generate — canon has AgentSession but no Duo-shaped workflow, checkpoint or trace projection

deferred
Long tail
GET
/api/v4/ai/duo_workflows/workflows/{id}/events

generate — canon has AgentSession but no Duo-shaped workflow, checkpoint or trace projection

deferred
Long tail
GET
/api/v4/ai/duo_workflows/workflows/{workflow_id}/trace.jsonl

generate — canon has AgentSession but no Duo-shaped workflow, checkpoint or trace projection

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

generate — canon has AgentSession but no Duo-shaped workflow, checkpoint or trace projection

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
boards0 of 8 served & verified
GET
/api/v4/groups/{id}/boards

generate — canon has sprint/epic/workflow state but no Board; the board layout itself must be generated

deferred
Long tail
GET
/api/v4/groups/{id}/boards/{board_id}

generate — canon has sprint/epic/workflow state but no Board; the board layout itself must be generated

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

generate — canon has sprint/epic/workflow state but no Board; the board layout itself must be generated

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

generate — canon has sprint/epic/workflow state but no Board; the board layout itself must be generated

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

generate — canon has sprint/epic/workflow state but no Board; the board layout itself must be generated

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

generate — canon has sprint/epic/workflow state but no Board; the board layout itself must be generated

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

generate — canon has sprint/epic/workflow state but no Board; the board layout itself must be generated

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

generate — canon has sprint/epic/workflow state but no Board; the board layout itself must be generated

deferred
Long tail
hooks-config4 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/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/groups/{id}/hooks/{hook_id}/events

generate — RE-SCOPED 2026-09-12 by the hello-18 webhook wave, which served the four hook rows beside this one. The old reason ("canon has no Webhook") is now FALSE: canon carries webhook_subscription with the git hosts' three scopes as of hello-18, and this host serves the subscriptions. What is missing is a DELIVERY LOG — one row per attempted POST, with the request it sent and the response it got — which is a second entity hello-18 deliberately did not invent (decisions/2026-09-12-1125). The subscription's last_delivery_epoch/code/status are a fact about the SUBSCRIPTION, published on the subscription by every host in the fleet, and one row is not a log to page. THE ASK: a canon DeliveryAttempt hanging off webhook_subscription, and this row goes green off it

deferred
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
GET
/api/v4/projects/{id}/hooks/{hook_id}/events

generate — RE-SCOPED 2026-09-12 by the hello-18 webhook wave, which served the four hook rows beside this one. The old reason ("canon has no Webhook") is now FALSE: canon carries webhook_subscription with the git hosts' three scopes as of hello-18, and this host serves the subscriptions. What is missing is a DELIVERY LOG — one row per attempted POST, with the request it sent and the response it got — which is a second entity hello-18 deliberately did not invent (decisions/2026-09-12-1125). The subscription's last_delivery_epoch/code/status are a fact about the SUBSCRIPTION, published on the subscription by every host in the fleet, and one row is not a log to page. THE ASK: a canon DeliveryAttempt hanging off webhook_subscription, and this row goes green off it

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
templates4 of 8 served & verified
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/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
GET
/api/v4/templates/dockerfiles

generate — canon has no TemplateCatalog; GitLab's bundled Dockerfile/gitignore/CI/license templates are vendor content canon must carry

deferred
Long tail
GET
/api/v4/templates/dockerfiles/{name}

generate — canon has no TemplateCatalog; GitLab's bundled Dockerfile/gitignore/CI/license templates are vendor content canon must carry

deferred
Long tail
GET
/api/v4/templates/gitlab_ci_ymls

generate — canon has no TemplateCatalog; GitLab's bundled Dockerfile/gitignore/CI/license templates are vendor content canon must carry

deferred
Long tail
GET
/api/v4/templates/gitlab_ci_ymls/{name}

generate — canon has no TemplateCatalog; GitLab's bundled Dockerfile/gitignore/CI/license templates are vendor content canon must carry

deferred
Long tail
variables4 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}/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

generate — RE-SCOPED 2026-09-09: the 23-row CiVariable+PipelineSchedule+ResourceGroup+SecureFile+FreezePeriod clique was hiding a solved fifth — hello-15's canon.WorkflowSchedule answers the PIPELINE SCHEDULES (see /api/v4/projects/{id}/pipeline_schedules). A JOB TOKEN is not: the scope rows are the cross-project trust list a CI job's token may reach, and the trigger rows are the tokens that start a pipeline from outside. canon stores no token (the PagerDuty routing-key rule, DECISIONS 2026-09-03) and this universe holds one project, so neither the allowlist nor the trigger has anything to point at. New canon entities JobTokenScope and PipelineTrigger

deferred
Long tail
GET
/api/v4/projects/{id}/triggers/{trigger_id}

generate — RE-SCOPED 2026-09-09: the 23-row CiVariable+PipelineSchedule+ResourceGroup+SecureFile+FreezePeriod clique was hiding a solved fifth — hello-15's canon.WorkflowSchedule answers the PIPELINE SCHEDULES (see /api/v4/projects/{id}/pipeline_schedules). A JOB TOKEN is not: the scope rows are the cross-project trust list a CI job's token may reach, and the trigger rows are the tokens that start a pipeline from outside. canon stores no token (the PagerDuty routing-key rule, DECISIONS 2026-09-03) and this universe holds one project, so neither the allowlist nor the trigger has anything to point at. New canon entities JobTokenScope and PipelineTrigger

deferred
Long tail
vulnerabilities0 of 8 served & verified
GET
/api/v4/projects/{id}/vulnerabilities

generate — canon has no SecurityFinding/SbomComponent/Attestation; a scanning org has findings

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

generate — canon has no SecurityFinding/SbomComponent/Attestation; a scanning org has findings

deferred
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}

generate — canon has no SecurityFinding/SbomComponent/Attestation; a scanning org has findings

deferred
Long tail
GET
/api/v4/vulnerabilities/{id}/issue_links

generate — canon has no SecurityFinding/SbomComponent/Attestation; a scanning org has findings

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
badges0 of 6 served & verified
GET
/api/v4/groups/{id}/badges

generate — canon has no Badge; project READMEs in this universe carry pipeline and coverage badges

deferred
Long tail
GET
/api/v4/groups/{id}/badges/{badge_id}

generate — canon has no Badge; project READMEs in this universe carry pipeline and coverage badges

deferred
Long tail
GET
/api/v4/groups/{id}/badges/render

generate — canon has no Badge; project READMEs in this universe carry pipeline and coverage badges

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

generate — canon has no Badge; project READMEs in this universe carry pipeline and coverage badges

deferred
Long tail
GET
/api/v4/projects/{id}/badges/{badge_id}

generate — canon has no Badge; project READMEs in this universe carry pipeline and coverage badges

deferred
Long tail
GET
/api/v4/projects/{id}/badges/render

generate — canon has no Badge; project READMEs in this universe carry pipeline and coverage badges

deferred
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 agents0 of 6 served & verified
GET
/api/v4/projects/{id}/cluster_agents

generate — canon has deployments but no ClusterAgent that performed them

deferred
Long tail
GET
/api/v4/projects/{id}/cluster_agents/{agent_id}

generate — canon has deployments but no ClusterAgent that performed them

deferred
Long tail
GET
/api/v4/projects/{id}/cluster_agents/{agent_id}/tokens

generate — canon has deployments but no ClusterAgent that performed them

deferred
Long tail
GET
/api/v4/projects/{id}/cluster_agents/{agent_id}/tokens/{token_id}

generate — canon has deployments but no ClusterAgent that performed them

deferred
Long tail
GET
/api/v4/projects/{id}/cluster_agents/{agent_id}/url_configurations

generate — canon has deployments but no ClusterAgent that performed them

deferred
Long tail
GET
/api/v4/projects/{id}/cluster_agents/{agent_id}/url_configurations/{url_configuration_id}

generate — canon has deployments but no ClusterAgent that performed them

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
integrations0 of 6 served & verified
GET
/api/v4/groups/{id}/integrations

generate — canon has no Integration; this universe literally has a Jira and a Slack to be integrated with

deferred
Long tail
GET
/api/v4/groups/{id}/integrations/{slug}

generate — canon has no Integration; this universe literally has a Jira and a Slack to be integrated with

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

generate — canon has no Integration; this universe literally has a Jira and a Slack to be integrated with

deferred
Long tail
GET
/api/v4/projects/{id}/integrations/{slug}

generate — canon has no Integration; this universe literally has a Jira and a Slack to be integrated with

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

generate — canon has no Integration; this universe literally has a Jira and a Slack to be integrated with

deferred
Long tail
GET
/api/v4/projects/{id}/services/{slug}

generate — canon has no Integration; this universe literally has a Jira and a Slack to be integrated with

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}

generate — canon has no Package/PackageFile; a simulated org publishes packages and container images

deferred
Long tail
GET
/api/v4/packages/terraform/modules/v1/{module_namespace}/{module_name}/{module_system}/download

generate — canon has no Package/PackageFile; a simulated org publishes packages and container images

deferred
Long tail
GET
/api/v4/packages/terraform/modules/v1/{module_namespace}/{module_name}/{module_system}/versions

generate — canon has no Package/PackageFile; a simulated org publishes packages and container images

deferred
Long tail
GET
/api/v4/projects/{id}/packages/terraform/modules/{module_name}/{module_system}

generate — canon has no Package/PackageFile; a simulated org publishes packages and container images

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-registry0 of 5 served & verified
GET
/api/v4/groups/{id}/registry/repositories

generate — canon has no Package/PackageFile; a simulated org publishes packages and container images

deferred
Long tail
GET
/api/v4/projects/{id}/registry/repositories

generate — canon has no Package/PackageFile; a simulated org publishes packages and container images

deferred
Long tail
GET
/api/v4/projects/{id}/registry/repositories/{repository_id}/tags

generate — canon has no Package/PackageFile; a simulated org publishes packages and container images

deferred
Long tail
GET
/api/v4/projects/{id}/registry/repositories/{repository_id}/tags/{tag_name}

generate — canon has no Package/PackageFile; a simulated org publishes packages and container images

deferred
Long tail
GET
/api/v4/registry/repositories/{id}

generate — canon has no Package/PackageFile; a simulated org publishes packages and container images

deferred
Long tail
namespaces3 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

generate — canon has no Subscription/BillingPeriod; decision 7 of 2026-08-29 puts billing in scope with sandbox-issued ids

deferred
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 management0 of 4 served & verified
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
GET
/api/v4/occurrences/vulnerabilities

generate — canon has no SecurityFinding/SbomComponent/Attestation; a scanning org has findings

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

generate — canon has no SecurityFinding/SbomComponent/Attestation; a scanning org has findings

deferred
Long tail
feature flags0 of 4 served & verified
GET
/api/v4/projects/{id}/feature_flags

generate — canon has no FeatureFlag; a shipping org gates releases behind flags

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

generate — canon has no FeatureFlag; a shipping org gates releases behind flags

deferred
Long tail
GET
/api/v4/projects/{id}/feature_flags_user_lists/{iid}

generate — canon has no FeatureFlag; a shipping org gates releases behind flags

deferred
Long tail
GET
/api/v4/projects/{id}/feature_flags/{feature_flag_name}

generate — canon has no FeatureFlag; a shipping org gates releases behind flags

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

derive

deferred
Long tail
GET
/api/v4/swagger_doc/{name}

derive

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-events0 of 3 served & verified
GET
/api/v4/audit_events

generate — A WIDENING, NOT A BUILD: canon.AdminAuditEvent exists (packages/canon/src/support-desk.ts:690) and its own doc comment calls it "GENERIC by construction". The action vocabulary (AuditAction, :602), the change vocabulary (AuditChangeSlot, :633), the target vocabulary (AuditTargetKind, :611) and the RFC 5737 source-address rule (AuditSourceBlock, :652) all shipped, and Zendesk serves the table today (reader.listAuditEvents(), ruled 2026-09-07). TWO things are missing and neither is the entity: git-host members on AuditTargetKind, and a generation that WRITES personActor — the field is declared for exactly this wave and "this generation writes none" (:695-697)

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

generate — A WIDENING, NOT A BUILD: canon.AdminAuditEvent exists (packages/canon/src/support-desk.ts:690) and its own doc comment calls it "GENERIC by construction". The action vocabulary (AuditAction, :602), the change vocabulary (AuditChangeSlot, :633), the target vocabulary (AuditTargetKind, :611) and the RFC 5737 source-address rule (AuditSourceBlock, :652) all shipped, and Zendesk serves the table today (reader.listAuditEvents(), ruled 2026-09-07). TWO things are missing and neither is the entity: git-host members on AuditTargetKind, and a generation that WRITES personActor — the field is declared for exactly this wave and "this generation writes none" (:695-697)

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

generate — A WIDENING, NOT A BUILD: canon.AdminAuditEvent exists (packages/canon/src/support-desk.ts:690) and its own doc comment calls it "GENERIC by construction". The action vocabulary (AuditAction, :602), the change vocabulary (AuditChangeSlot, :633), the target vocabulary (AuditTargetKind, :611) and the RFC 5737 source-address rule (AuditSourceBlock, :652) all shipped, and Zendesk serves the table today (reader.listAuditEvents(), ruled 2026-09-07). TWO things are missing and neither is the entity: git-host members on AuditTargetKind, and a generation that WRITES personActor — the field is declared for exactly this wave and "this generation writes none" (:695-697)

deferred
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 settings0 of 3 served & verified
GET
/api/v4/groups/{id}/notification_settings

generate — canon has no NotificationSetting for a persona

deferred
Long tail
GET
/api/v4/notification_settings

generate — canon has no NotificationSetting for a persona

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

generate — canon has no NotificationSetting for a persona

deferred
Long tail
projects job token scope0 of 3 served & verified
GET
/api/v4/projects/{id}/job_token_scope

generate — RE-SCOPED 2026-09-09: the 23-row CiVariable+PipelineSchedule+ResourceGroup+SecureFile+FreezePeriod clique was hiding a solved fifth — hello-15's canon.WorkflowSchedule answers the PIPELINE SCHEDULES (see /api/v4/projects/{id}/pipeline_schedules). A JOB TOKEN is not: the scope rows are the cross-project trust list a CI job's token may reach, and the trigger rows are the tokens that start a pipeline from outside. canon stores no token (the PagerDuty routing-key rule, DECISIONS 2026-09-03) and this universe holds one project, so neither the allowlist nor the trigger has anything to point at. New canon entities JobTokenScope and PipelineTrigger

deferred
Long tail
GET
/api/v4/projects/{id}/job_token_scope/allowlist

generate — RE-SCOPED 2026-09-09: the 23-row CiVariable+PipelineSchedule+ResourceGroup+SecureFile+FreezePeriod clique was hiding a solved fifth — hello-15's canon.WorkflowSchedule answers the PIPELINE SCHEDULES (see /api/v4/projects/{id}/pipeline_schedules). A JOB TOKEN is not: the scope rows are the cross-project trust list a CI job's token may reach, and the trigger rows are the tokens that start a pipeline from outside. canon stores no token (the PagerDuty routing-key rule, DECISIONS 2026-09-03) and this universe holds one project, so neither the allowlist nor the trigger has anything to point at. New canon entities JobTokenScope and PipelineTrigger

deferred
Long tail
GET
/api/v4/projects/{id}/job_token_scope/groups_allowlist

generate — RE-SCOPED 2026-09-09: the 23-row CiVariable+PipelineSchedule+ResourceGroup+SecureFile+FreezePeriod clique was hiding a solved fifth — hello-15's canon.WorkflowSchedule answers the PIPELINE SCHEDULES (see /api/v4/projects/{id}/pipeline_schedules). A JOB TOKEN is not: the scope rows are the cross-project trust list a CI job's token may reach, and the trigger rows are the tokens that start a pipeline from outside. canon stores no token (the PagerDuty routing-key rule, DECISIONS 2026-09-03) and this universe holds one project, so neither the allowlist nor the trigger has anything to point at. New canon entities JobTokenScope and PipelineTrigger

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
unleash0 of 3 served & verified
GET
/api/v4/feature_flags/unleash/{project_id}

generate — canon has no FeatureFlag; a shipping org gates releases behind flags

deferred
Long tail
GET
/api/v4/feature_flags/unleash/{project_id}/client/features

generate — canon has no FeatureFlag; a shipping org gates releases behind flags

deferred
Long tail
GET
/api/v4/feature_flags/unleash/{project_id}/features

generate — canon has no FeatureFlag; a shipping org gates releases behind flags

deferred
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

generate — canon has no SecurityFinding/SbomComponent/Attestation; a scanning org has findings

deferred
Long tail
GET
/api/v4/projects/{id}/attestations/{subject_digest}

generate — canon has no SecurityFinding/SbomComponent/Attestation; a scanning org has findings

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 tracking0 of 2 served & verified
GET
/api/v4/projects/{id}/error_tracking/client_keys

generate — canon has no Integration; this universe literally has a Jira and a Slack to be integrated with

deferred
Long tail
GET
/api/v4/projects/{id}/error_tracking/settings

generate — canon has no Integration; this universe literally has a Jira and a Slack to be integrated with

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 managers, controllers, tokens and router discovery describe physical CI machines and their credentials, not the org; GitLab restricts them to admins

deferred
Long tail
GET
/api/v4/runner_controllers/{id}/scopes

refuse — runner managers, controllers, tokens and router discovery describe physical CI machines and their credentials, not the org; GitLab restricts them to admins

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}

generate — canon has no Subscription/BillingPeriod; decision 7 of 2026-08-29 puts billing in scope with sandbox-issued ids

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 subscriptions0 of 1 served & verified
GET
/api/v4/integrations/jira_forge/subscriptions

generate — canon has no Integration; this universe literally has a Jira and a Slack to be integrated with

deferred
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 managers, controllers, tokens and router discovery describe physical CI machines and their credentials, not the org; GitLab restricts them to admins

deferred
Long tail
software composition analysis0 of 1 served & verified
GET
/api/v4/jobs/{id}/sbom_scans/{sbom_digest}

generate — canon has no SecurityFinding/SbomComponent/Attestation; a scanning org has findings

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-g11 artifact (universe generation 11):

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

73 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

Frozen universes, 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-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