docs(git): publish a versioned Git transport support matrix (#1883)

* fix(git): make gitSourceStatus exhaustive over GitSourceErrorCode

GIT_ERROR was the only code falling through the implicit default
branch. Give it an explicit case and add the same never-guard
webhookPullStatus already uses, so a future code with no mapping is a
compile error instead of a silent 400.

* fix(git): fail loudly under CI when git or sshd is missing

Every real-git and real-sshd integration suite carried its own local
gitAvailable()/sshdAvailable() probe and skipped silently when the
dependency was absent, in CI as well as locally. A cell in the
upcoming support matrix could then advertise automated proof while
the test that proves it never ran.

Consolidate into shared requireGitBinary()/requireSshd() helpers
(one for backend vitest, one for Playwright, since backend's rootDir
pin blocks a cross-directory import) that take an injectable probe.
Locally a missing dependency still skips; under CI it throws with an
actionable message naming what's missing.

* feat(docs): publish a versioned Git transport support matrix

Adds docs/git-transport-support.yaml as the canonical claim set for
every transport/ref/auth/host/CA combination Git Sources supports,
each claim naming its own reproducible evidence rather than
generalizing from a related test. A claim is supported only when a
real end-to-end test (or a dated live attestation) proves that exact
combination; everything else is marked unverified, never assumed.

The published page (docs/features/git-transport-support.mdx) is
generated from the YAML by backend/scripts/git-support-matrix, so it
cannot silently drift from what the tests actually prove. A new
backend test (git-support-matrix.test.ts) enforces this: schema
validity, evidence semantics (supported needs success evidence,
unsupported needs a reproducible rejection, unverified forbids
evidence entirely), byte-identical page generation, and that every
referenced test title resolves via the TypeScript AST rather than a
string search that a skipped or commented-out test would pass.

The error-model section is cross-checked against the real
GitSourceErrorCode and TransportFacingCode unions and against
gitSourceStatus's actual HTTP mapping, so the matrix and the runtime
behavior cannot diverge either.

Named Git hosts (GitHub, GitLab, Gitea, Forgejo, Bitbucket) and the
direct-proxy/Pilot execution paths are seeded as unverified pending a
live attestation pass; only the generic local-fixture combinations
already proven by the real-git integration suites are marked
supported today.

* docs(git): scope GitHub claims to what this pass can actually attest

Splits the GitHub row into a public no-auth claim (attestable with a
real public repository) and separate PAT/SSH deploy-key claims marked
unverified with an explicit reason: this pass holds no real GitHub
credential to exercise them with, and none is assumed or fabricated.

* feat(docs): attest the Git transport matrix live against real hosts

Runs the QA fleet's live Sencho instance through the transport
combinations that automated fixtures cannot exercise, then records
each result in docs/git-transport-attestations.yaml so it can be
re-run and compared later.

GitHub, GitLab, and Bitbucket are attested over public HTTPS against
real, stable, publicly-owned demo repositories (branch and pinned
SHA; GitLab additionally has a tagged fixture). Gitea and Forgejo get
full coverage (branch, tag, and SHA, over both HTTPS with a
per-source CA and SSH with a deploy key) against disposable
self-hosted instances stood up for this pass, including a private
repository so the authentication and host-key failure classifiers
were exercised against a real wrong credential and a real wrong host
key, not just the mocked corpus. The direct-proxy and Pilot execution
paths are each confirmed once against a real public host, proving
the distributed dispatch itself rather than assuming it from the
local-path evidence.

Left honestly unverified: GitHub PAT and SSH deploy-key auth (this
pass holds no real GitHub credential), a GitHub tag combination (no
small stable tagged fixture found), and a Bitbucket tag combination
(the fixture repository carries none). Every claim's evidence records
its exact transport, ref, auth, host, CA, and node path so nothing
here is extrapolated from a neighboring result.

All infrastructure created for this pass (two throwaway Git server
containers, one probe stack) was torn down afterward and the fleet's
container list was confirmed to match its state before the pass.

* style(git): replace em dashes and fix a stale .mjs reference

Directive 18 applies to code comments and build markers too, not just
prose. Also corrects the claim set's header comment, which still
named render.mjs after the renderer was moved to render.js to match
the house convention for backend scripts.
This commit is contained in:
Anso
2026-09-01 21:56:02 -04:00
committed by GitHub
parent 851a5fb41e
commit 0928765232
24 changed files with 2221 additions and 34 deletions
+1
View File
@@ -135,6 +135,7 @@
"features/health-gated-updates",
"features/deploy-enforcement",
"features/git-sources",
"features/git-transport-support",
"features/blueprint-model",
"features/scheduled-operations",
"features/auto-update-policies",
+2
View File
@@ -9,6 +9,8 @@ Git Sources turn any stack into a GitOps target. Point Sencho at a repository an
Git Sources are available on every tier, including Community.
</Note>
For exactly which transports, ref types, authentication methods, and Git hosts are supported today, with the evidence behind each, see [Git Transport Support](/features/git-transport-support).
## How it works
1. Open a stack and click the **Git Source** button in the editor toolbar.
+82
View File
@@ -0,0 +1,82 @@
---
title: Git Transport Support
sidebarTitle: Transport Support
description: Which transports, reference types, authentication methods, and Git hosts Git Sources supports, with the evidence behind each claim.
---
<Note>
Git Sources, and everything on this page, is available on every tier, including Community.
</Note>
This page states exactly what Git Sources supports today: which transports, reference types, authentication methods, TLS trust modes, and Git hosts, and what happens where a combination is not supported or not yet verified. For how to configure a Git source, see [Git Sources](/features/git-sources).
Every "Supported" row here is backed by a test that runs on every change to Sencho, or by a dated pass against a real instance of that host. A row marked "Not yet verified" is not a claim of failure: it means that specific combination has not been exercised yet, so it is not advertised as working. Nothing on this page is inferred from a related combination that behaved correctly.
<!-- GENERATED:BEGIN (run `npm run matrix:render` in backend/ to regenerate, do not edit by hand) -->
## Transports
| Transport | Status | Notes |
| --- | --- | --- |
| HTTPS | Supported | Personal Access Token for private repositories, or no credential at all for public ones. TLS verification uses the system trust store by default, or a per-source custom CA when configured. |
| SSH | Supported | A read-only deploy key with strict host-key verification. Standard (22) and nonstandard ports are both supported. |
## Reference types
| Reference type | Status | Notes |
| --- | --- | --- |
| Branch | Supported | Tracks the head of a branch; each pull resolves and pins the exact commit. |
| Tag | Supported | Both annotated and lightweight tags resolve to their target commit. |
| Commit SHA | Supported | A full commit SHA is pinned directly; the Git host must advertise the commit on some branch or tag. |
## Authentication
| Method | Status | Notes |
| --- | --- | --- |
| Public (no auth) | Supported | For public repositories. |
| Personal Access Token | Supported | Stored encrypted at rest, never returned after save. |
| SSH deploy key | Supported | Stored encrypted at rest; the server host key is verified on every fetch. |
## Git hosts
| Host | HTTPS | SSH | Branch | Tag | Commit SHA | Evidence |
| --- | --- | --- | --- | --- | --- | --- |
| Generic (self-hosted or any Git server) | Supported | Supported | Supported | Supported | Supported | Automated, every change |
| GitHub | Supported | Not yet verified | Supported | Not yet verified | Supported | Live, 2026-09-01 |
| GitLab | Supported | Not yet verified | Supported | Supported | Supported | Live, 2026-09-01 |
| Gitea | Supported | Supported | Supported | Supported | Supported | Live, 2026-09-01 |
| Forgejo | Supported | Supported | Supported | Supported | Supported | Live, 2026-09-01 |
| Bitbucket | Supported | Not yet verified | Supported | Not yet verified | Supported | Live, 2026-09-01 |
## TLS and certificate authorities
| Mode | Status | Notes |
| --- | --- | --- |
| System trust (default) | Supported | The host running the fetch trusts its system certificate store. |
| Per-source custom CA | Supported | Combined with the system trust anchors, so public hosts keep validating normally. Redirects are re-resolved and only followed when they stay on the source's own host. |
## Not supported
- **No rate-limit classification.** A Git host rate-limit response (for example GitHub's secondary rate limits) is not classified as its own error state. It surfaces as an authentication failure or a generic transport error depending on the host's exact response. Wait and retry; there is no dedicated rate-limit message or backoff guidance yet.
- **No Git LFS.** Compose and env files tracked via Git LFS are rejected rather than silently fetched as pointer stubs. Commit plain files instead.
- **No submodules.** Submodule contents are not fetched. Inputs and build contexts that reference submodule contents are refused with an actionable message; a warning is shown when .gitmodules is present.
- **No sparse or partial clone.** Every fetch materializes the complete repository at the resolved commit (shallow, single-branch); there is no sparse or partial clone for large monorepos.
- **No GitHub App authentication.** Authentication is Personal Access Token or SSH deploy key only. GitHub App installation tokens are not supported.
- **No provider pull/merge request revisions.** Sources track a branch, a tag, or a pinned commit SHA. A provider-specific pull request or merge request revision (for example GitHub's refs/pull/N/head) is not a supported ref shape.
- **Outbound target restrictions.** Repository targets that resolve to loopback, link-local, multicast, or selected special-use addresses are refused before any request is sent. Private hosts on an operator's own LAN or VPN are not affected by this restriction.
- **Branch/tag name collisions.** A bare ref name resolves as a branch first, then as a tag. If an operator renames a branch and a tag of the same name later appears, the source silently starts resolving the tag instead. A ref name that is also a valid 40 or 64 character hex string resolves as a commit SHA before either lookup.
<!-- GENERATED:END -->
## How these claims are verified
Two kinds of evidence back the rows above:
- **Automated.** A real `git` client, talking to a real local test server over HTTPS or SSH, drives the exact same code path Sencho uses in production. These tests run on every change, so a regression here fails the build before it reaches a release.
- **Live.** A dated pass against a real instance of the named host (a public GitHub, GitLab, Gitea, Forgejo, or Bitbucket repository, or a self-hosted instance under Sencho's control). This is repeated periodically, not on every change, so its date tells you how current the result is.
A host that is not listed, or a combination marked "Not yet verified," most likely still works: Git Sources speaks the standard Git smart-HTTP and SSH protocols, not anything host-specific. It simply has not been exercised as its own row yet.
<Card title="Git Sources" icon="code-branch" href="/features/git-sources">
Configure a repository, review pull previews, and read the field-by-field reference for every setting mentioned above.
</Card>
+365
View File
@@ -0,0 +1,365 @@
# Retained live-evidence results for docs/git-transport-support.yaml claims
# with `evidence.kind: live`.
#
# This is the reproducibility artifact for every non-automated claim: a
# future engineer (or SEN-362's GA verification) can re-run the exact
# procedure in scripts/git-attestation/README.md and compare against what is
# recorded here. Never record raw credential-bearing output, tokens, deploy
# keys, hostnames or URLs beyond generic fixture identities, or fleet
# credentials: only structured pass/fail metadata and scrubbed commands.
#
# Schema (schema: attestation-v1):
# id: referenced by a claim's evidence.attestation.
# date: when the attestation ran (YYYY-MM-DD).
# source_commit: the Sencho commit the claim set's implementation_baseline
# names; a claim binds to this, not to the runtime image digest below.
# sencho_image_digest: the exact runtime image executed. Recorded because
# a runtime attestation executes an image while claims are committed
# from a revision; retained so the executed runtime is identifiable even
# though it is not what claims bind to.
# host: the closed enum value matching the claim (github, gitlab, gitea,
# forgejo, bitbucket, generic); never a descriptive string.
# host_version: optional, self-hosted only: the exact image reference run.
# Omitted for hosted SaaS, where inventing a server version would be false.
# node_path: local | direct-proxy | pilot: the execution path exercised.
# transport / ref / auth / ca: duplicated from the referencing claim so the
# validator can assert exact-dimension equality, not extrapolation.
# repository / ref_name: the exact fixture repository and literal ref
# exercised (a branch, tag, or commit, not the ref *kind*).
# command: a scrubbed, non-credential-bearing description of what ran.
# result: success | rejected, matching the claim's evidence.outcome.
version: '1'
schema: 'attestation-v1'
attestations:
# ===== Named hosts, public read-only (HTTPS, no auth) =====
- id: att-2026-09-01-github-branch
date: '2026-09-01'
source_commit: 79b86ddcd4aefdd6941f098e35990ab397b13c72
sencho_image_digest: sha256:980531f85861e092b7eee8a5b6a840d3964ccc6db25c42517c0ac43fefe67e16
host: github
node_path: local
transport: https
ref: branch
auth: none
ca: system
repository: octocat/Hello-World (GitHub's own public demo repository)
ref_name: master
command: POST /api/git-sources/browse against the real Sencho instance; branch tip resolved and fetched
result: success
- id: att-2026-09-01-github-sha
date: '2026-09-01'
source_commit: 79b86ddcd4aefdd6941f098e35990ab397b13c72
sencho_image_digest: sha256:980531f85861e092b7eee8a5b6a840d3964ccc6db25c42517c0ac43fefe67e16
host: github
node_path: local
transport: https
ref: sha
auth: none
ca: system
repository: octocat/Hello-World
ref_name: 7fd1a60b01f91b314f59955a4e4d4e80d8edf11d (master tip)
command: POST /api/git-sources/browse pinning the branch tip's own commit SHA
result: success
- id: att-2026-09-01-gitlab-branch
date: '2026-09-01'
source_commit: 79b86ddcd4aefdd6941f098e35990ab397b13c72
sencho_image_digest: sha256:980531f85861e092b7eee8a5b6a840d3964ccc6db25c42517c0ac43fefe67e16
host: gitlab
node_path: local
transport: https
ref: branch
auth: none
ca: system
repository: gitlab-org/gitlab-test (GitLab's own canonical test fixture repository)
ref_name: master
command: POST /api/git-sources/browse
result: success
- id: att-2026-09-01-gitlab-tag
date: '2026-09-01'
source_commit: 79b86ddcd4aefdd6941f098e35990ab397b13c72
sencho_image_digest: sha256:980531f85861e092b7eee8a5b6a840d3964ccc6db25c42517c0ac43fefe67e16
host: gitlab
node_path: local
transport: https
ref: tag
auth: none
ca: system
repository: gitlab-org/gitlab-test
ref_name: v1.0.0 (annotated)
command: POST /api/git-sources/browse
result: success
- id: att-2026-09-01-gitlab-sha
date: '2026-09-01'
source_commit: 79b86ddcd4aefdd6941f098e35990ab397b13c72
sencho_image_digest: sha256:980531f85861e092b7eee8a5b6a840d3964ccc6db25c42517c0ac43fefe67e16
host: gitlab
node_path: local
transport: https
ref: sha
auth: none
ca: system
repository: gitlab-org/gitlab-test
ref_name: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 (v1.0.0's peeled commit)
command: POST /api/git-sources/browse
result: success
- id: att-2026-09-01-bitbucket-branch
date: '2026-09-01'
source_commit: 79b86ddcd4aefdd6941f098e35990ab397b13c72
sencho_image_digest: sha256:980531f85861e092b7eee8a5b6a840d3964ccc6db25c42517c0ac43fefe67e16
host: bitbucket
node_path: local
transport: https
ref: branch
auth: none
ca: system
repository: atlassian_tutorial/helloworld (Atlassian's own public tutorial repository)
ref_name: master
command: POST /api/git-sources/browse
result: success
- id: att-2026-09-01-bitbucket-sha
date: '2026-09-01'
source_commit: 79b86ddcd4aefdd6941f098e35990ab397b13c72
sencho_image_digest: sha256:980531f85861e092b7eee8a5b6a840d3964ccc6db25c42517c0ac43fefe67e16
host: bitbucket
node_path: local
transport: https
ref: sha
auth: none
ca: system
repository: atlassian_tutorial/helloworld
ref_name: 65d938f39f364da3f90767e008022ffe45c562af (master tip)
command: POST /api/git-sources/browse
result: success
# ===== Self-hosted Gitea, own throwaway instance on the QA fleet =====
- id: att-2026-09-01-gitea-https-branch
date: '2026-09-01'
source_commit: 79b86ddcd4aefdd6941f098e35990ab397b13c72
sencho_image_digest: sha256:980531f85861e092b7eee8a5b6a840d3964ccc6db25c42517c0ac43fefe67e16
host: gitea
host_version: gitea/gitea:1.22
node_path: local
transport: https
ref: branch
auth: pat
ca: per-source
repository: throwaway fixture repository on a disposable Gitea instance, torn down after this pass
ref_name: main
command: POST /api/git-sources/browse with a per-source CA bundle and a Personal Access Token
result: success
- id: att-2026-09-01-gitea-https-tag
date: '2026-09-01'
source_commit: 79b86ddcd4aefdd6941f098e35990ab397b13c72
sencho_image_digest: sha256:980531f85861e092b7eee8a5b6a840d3964ccc6db25c42517c0ac43fefe67e16
host: gitea
host_version: gitea/gitea:1.22
node_path: local
transport: https
ref: tag
auth: pat
ca: per-source
repository: throwaway fixture repository on a disposable Gitea instance
ref_name: v1.0 (annotated)
command: POST /api/git-sources/browse
result: success
- id: att-2026-09-01-gitea-https-sha
date: '2026-09-01'
source_commit: 79b86ddcd4aefdd6941f098e35990ab397b13c72
sencho_image_digest: sha256:980531f85861e092b7eee8a5b6a840d3964ccc6db25c42517c0ac43fefe67e16
host: gitea
host_version: gitea/gitea:1.22
node_path: local
transport: https
ref: sha
auth: pat
ca: per-source
repository: throwaway fixture repository on a disposable Gitea instance
ref_name: pinned commit SHA (branch tip)
command: POST /api/git-sources/browse
result: success
- id: att-2026-09-01-gitea-ssh-branch
date: '2026-09-01'
source_commit: 79b86ddcd4aefdd6941f098e35990ab397b13c72
sencho_image_digest: sha256:980531f85861e092b7eee8a5b6a840d3964ccc6db25c42517c0ac43fefe67e16
host: gitea
host_version: gitea/gitea:1.22
node_path: local
transport: ssh
ref: branch
auth: deploy-key
ca: not-applicable
repository: throwaway fixture repository on a disposable Gitea instance
ref_name: main
command: POST /api/git-sources/browse with a read-only deploy key and the host key fetched via Sencho's own probe endpoint
result: success
- id: att-2026-09-01-gitea-ssh-tag
date: '2026-09-01'
source_commit: 79b86ddcd4aefdd6941f098e35990ab397b13c72
sencho_image_digest: sha256:980531f85861e092b7eee8a5b6a840d3964ccc6db25c42517c0ac43fefe67e16
host: gitea
host_version: gitea/gitea:1.22
node_path: local
transport: ssh
ref: tag
auth: deploy-key
ca: not-applicable
repository: throwaway fixture repository on a disposable Gitea instance
ref_name: v1.0-light (lightweight)
command: POST /api/git-sources/browse
result: success
- id: att-2026-09-01-gitea-ssh-sha
date: '2026-09-01'
source_commit: 79b86ddcd4aefdd6941f098e35990ab397b13c72
sencho_image_digest: sha256:980531f85861e092b7eee8a5b6a840d3964ccc6db25c42517c0ac43fefe67e16
host: gitea
host_version: gitea/gitea:1.22
node_path: local
transport: ssh
ref: sha
auth: deploy-key
ca: not-applicable
repository: throwaway fixture repository on a disposable Gitea instance
ref_name: pinned commit SHA (branch tip)
command: POST /api/git-sources/browse
result: success
# ===== Self-hosted Forgejo, own throwaway instance on the QA fleet =====
- id: att-2026-09-01-forgejo-https-branch
date: '2026-09-01'
source_commit: 79b86ddcd4aefdd6941f098e35990ab397b13c72
sencho_image_digest: sha256:980531f85861e092b7eee8a5b6a840d3964ccc6db25c42517c0ac43fefe67e16
host: forgejo
host_version: codeberg.org/forgejo/forgejo:7
node_path: local
transport: https
ref: branch
auth: pat
ca: per-source
repository: throwaway fixture repository on a disposable Forgejo instance, torn down after this pass
ref_name: main
command: POST /api/git-sources/browse with a per-source CA bundle and a Personal Access Token
result: success
- id: att-2026-09-01-forgejo-https-tag
date: '2026-09-01'
source_commit: 79b86ddcd4aefdd6941f098e35990ab397b13c72
sencho_image_digest: sha256:980531f85861e092b7eee8a5b6a840d3964ccc6db25c42517c0ac43fefe67e16
host: forgejo
host_version: codeberg.org/forgejo/forgejo:7
node_path: local
transport: https
ref: tag
auth: pat
ca: per-source
repository: throwaway fixture repository on a disposable Forgejo instance
ref_name: v1.0 (annotated)
command: POST /api/git-sources/browse
result: success
- id: att-2026-09-01-forgejo-https-sha
date: '2026-09-01'
source_commit: 79b86ddcd4aefdd6941f098e35990ab397b13c72
sencho_image_digest: sha256:980531f85861e092b7eee8a5b6a840d3964ccc6db25c42517c0ac43fefe67e16
host: forgejo
host_version: codeberg.org/forgejo/forgejo:7
node_path: local
transport: https
ref: sha
auth: pat
ca: per-source
repository: throwaway fixture repository on a disposable Forgejo instance
ref_name: pinned commit SHA (branch tip)
command: POST /api/git-sources/browse
result: success
- id: att-2026-09-01-forgejo-ssh-branch
date: '2026-09-01'
source_commit: 79b86ddcd4aefdd6941f098e35990ab397b13c72
sencho_image_digest: sha256:980531f85861e092b7eee8a5b6a840d3964ccc6db25c42517c0ac43fefe67e16
host: forgejo
host_version: codeberg.org/forgejo/forgejo:7
node_path: local
transport: ssh
ref: branch
auth: deploy-key
ca: not-applicable
repository: throwaway fixture repository on a disposable Forgejo instance
ref_name: main
command: POST /api/git-sources/browse with a read-only deploy key and the host key fetched via Sencho's own probe endpoint
result: success
- id: att-2026-09-01-forgejo-ssh-tag
date: '2026-09-01'
source_commit: 79b86ddcd4aefdd6941f098e35990ab397b13c72
sencho_image_digest: sha256:980531f85861e092b7eee8a5b6a840d3964ccc6db25c42517c0ac43fefe67e16
host: forgejo
host_version: codeberg.org/forgejo/forgejo:7
node_path: local
transport: ssh
ref: tag
auth: deploy-key
ca: not-applicable
repository: throwaway fixture repository on a disposable Forgejo instance
ref_name: v1.0-light (lightweight)
command: POST /api/git-sources/browse
result: success
- id: att-2026-09-01-forgejo-ssh-sha
date: '2026-09-01'
source_commit: 79b86ddcd4aefdd6941f098e35990ab397b13c72
sencho_image_digest: sha256:980531f85861e092b7eee8a5b6a840d3964ccc6db25c42517c0ac43fefe67e16
host: forgejo
host_version: codeberg.org/forgejo/forgejo:7
node_path: local
transport: ssh
ref: sha
auth: deploy-key
ca: not-applicable
repository: throwaway fixture repository on a disposable Forgejo instance
ref_name: pinned commit SHA (branch tip)
command: POST /api/git-sources/browse
result: success
# ===== Distribution: direct-proxy and Pilot node paths =====
- id: att-2026-09-01-github-direct-proxy
date: '2026-09-01'
source_commit: 79b86ddcd4aefdd6941f098e35990ab397b13c72
sencho_image_digest: sha256:980531f85861e092b7eee8a5b6a840d3964ccc6db25c42517c0ac43fefe67e16
host: github
node_path: direct-proxy
transport: https
ref: branch
auth: none
ca: system
repository: octocat/Hello-World
ref_name: master
command: POST /api/git-sources/browse with x-node-id targeting a remote proxy-mode fleet node
result: success
- id: att-2026-09-01-github-pilot
date: '2026-09-01'
source_commit: 79b86ddcd4aefdd6941f098e35990ab397b13c72
sencho_image_digest: sha256:980531f85861e092b7eee8a5b6a840d3964ccc6db25c42517c0ac43fefe67e16
host: github
node_path: pilot
transport: https
ref: branch
auth: none
ca: system
repository: octocat/Hello-World
ref_name: master
command: POST /api/git-sources/browse with x-node-id targeting a Pilot-agent fleet node
result: success
+631
View File
@@ -0,0 +1,631 @@
# Git transport support matrix: canonical source of truth (SEN-9 PR 5).
#
# Every entry here is a claim about one exercised combination, not an
# independently-tested dimension: a PAT test, a tag test, and a GitLab
# observation do not jointly prove "GitLab + PAT + tag" unless something
# actually ran that exact combination. `docs/features/git-transport-support.mdx`
# is generated from this file (backend/scripts/git-support-matrix/render.js);
# a backend test (git-support-matrix.test.ts) enforces byte-for-byte parity
# and validates every rule below. Internal test paths, PR numbers, and Linear
# IDs never appear on the published page; they live here instead.
#
# Schema (schema: matrix-v1):
# implementation_baseline: the git commit this file's claims describe.
# claims[]: one entry per exercised (transport, ref, auth, host, ca,
# node_path[, port]) combination.
# - support: supported | unsupported | unverified
# - supported requires evidence with outcome: success
# - unsupported requires evidence with outcome: rejected (a reproducible
# refusal, not silence; an unsupported claim with no evidence is as
# unreproducible as an unproven supported one)
# - unverified forbids evidence entirely
# - evidence.kind: automated (a handle into a test file + exact test
# title) or live (a pointer into docs/git-transport-attestations.yaml).
# Every evidence record repeats all six combination dimensions, and the
# validator requires them to match the claim exactly: a direct-proxy
# result proves the direct-proxy combination, nothing else.
# limitations[]: named gaps, each with its operator-facing consequence.
# error_model[]: the transport-facing error codes this matrix covers
# (TransportFacingCode plus REF_DELETED and FILE_NOT_FOUND), each with
# its HTTP status and one-line meaning. GitOps plan-lifecycle codes
# (STALE_PLAN, PLAN_FINGERPRINT_REQUIRED, PLAN_BLOCKED, LEGACY_PENDING,
# PLAN_UNAVAILABLE, OPERATION_IN_FLIGHT) belong to reconciliation, not
# transport, and are listed separately so the partition is visible.
# attestations: pointer to the retained live-evidence file.
version: '1'
schema: 'matrix-v1'
implementation_baseline: 79b86ddcd4aefdd6941f098e35990ab397b13c72
attestations: docs/git-transport-attestations.yaml
claims:
# ===== Automated: real git, real TLS/SSH, against a local fixture server =====
# host: generic, node_path: local for all of these: no proxy hop, no
# branded host. Branded-host and non-local node-path claims live below,
# pending or drawn from the live attestation pass.
- id: https-pat-branch-system-generic-local
transport: https
ref: branch
auth: pat
host: generic
ca: system
node_path: local
support: supported
qualifiers:
- History rewritten on this ref after resolution is detected and rejected as non-fast-forward, not silently deployed.
evidence:
kind: automated
outcome: success
handles:
- file: backend/src/__tests__/git-transport-auth.integration.test.ts
title: clones a private repo end-to-end with a valid token
- file: backend/src/__tests__/git-transport-auth.integration.test.ts
title: treats a linear branch advance as a fast-forward
- file: backend/src/__tests__/git-transport-auth.integration.test.ts
title: treats a multi-commit branch advance as a fast-forward
- file: backend/src/__tests__/git-transport-auth.integration.test.ts
title: rejects rewritten history as non-fast-forward
- id: https-pat-tag-system-generic-local
transport: https
ref: tag
auth: pat
host: generic
ca: system
node_path: local
support: supported
qualifiers:
- Proven for both an annotated tag (peeled through its ^{} commit) and a lightweight tag.
evidence:
kind: automated
outcome: success
handles:
- file: backend/src/__tests__/git-transport-auth.integration.test.ts
title: resolves and fetches an annotated tag through the peeled commit
- file: backend/src/__tests__/git-transport-auth.integration.test.ts
title: resolves and fetches a lightweight tag
- id: https-pat-sha-system-generic-local
transport: https
ref: sha
auth: pat
host: generic
ca: system
node_path: local
support: supported
qualifiers: []
evidence:
kind: automated
outcome: success
handles:
- file: backend/src/__tests__/git-transport-auth.integration.test.ts
title: resolves and fetches a pinned commit SHA
- id: https-none-branch-persource-generic-local
transport: https
ref: branch
auth: none
host: generic
ca: per-source
node_path: local
support: supported
qualifiers:
- Also proven to survive a same-origin redirect (the repository relocating on the same host).
evidence:
kind: automated
outcome: success
handles:
- file: backend/src/__tests__/git-private-ca.integration.test.ts
title: clones a private-CA HTTPS repo when the per-source CA PEM is supplied
- file: backend/src/__tests__/git-redirect.integration.test.ts
title: resolves a ref through an unauthenticated same-host redirect
- id: https-pat-branch-persource-generic-local
transport: https
ref: branch
auth: pat
host: generic
ca: per-source
node_path: local
support: supported
qualifiers:
- Proven through a same-origin redirect; the token is forwarded to the relocated path and never offered to a different host.
evidence:
kind: automated
outcome: success
handles:
- file: backend/src/__tests__/git-redirect.integration.test.ts
title: resolves a ref through an authenticated same-host redirect and sends the token to the relocated path
- id: ssh-deploy-key-branch-na-generic-local-nonstandard-port
transport: ssh
ref: branch
auth: deploy-key
host: generic
ca: not-applicable
node_path: local
port: nonstandard
support: supported
qualifiers:
- Full round trip (resolve, fetch, and content verification) proven at this port.
evidence:
kind: automated
outcome: success
handles:
- file: backend/src/__tests__/git-transport-ssh.integration.test.ts
title: resolves and fetches over SSH with a deploy key and trusted host key
- file: backend/src/__tests__/git-transport-ssh.integration.test.ts
title: 'resolves over ssh:// with a nonstandard port'
- id: ssh-deploy-key-branch-na-generic-local-default-port
transport: ssh
ref: branch
auth: deploy-key
host: generic
ca: not-applicable
node_path: local
port: default
support: supported
qualifiers:
- Ref resolution proven at the default port; the full content fetch is proven only at a nonstandard port (see the sibling claim), not separately re-run here.
evidence:
kind: automated
outcome: success
handles:
- file: backend/src/__tests__/git-transport-ssh.integration.test.ts
title: resolves over scp-style URL on the default SSH port
# ===== Live: named Git hosts, external SaaS and self-hosted =====
# Evidence lives in docs/git-transport-attestations.yaml. A row stays
# `unverified` (no evidence permitted) until a live pass actually exercises
# it; nothing here is extrapolated from a different host or node_path.
# Live-attested 2026-09-01 (see the attestation ids referenced below).
- id: https-none-branch-system-github-local
transport: https
ref: branch
auth: none
host: github
ca: system
node_path: local
support: supported
qualifiers:
- Only a public, read-only repository is exercised; GitHub does not receive a token or an SSH deploy key from this attestation.
evidence:
kind: live
outcome: success
attestation: att-2026-09-01-github-branch
- id: https-none-tag-system-github-local
transport: https
ref: tag
auth: none
host: github
ca: system
node_path: local
support: unverified
qualifiers:
- Only a public, read-only repository is exercised; GitHub does not receive a token or an SSH deploy key from this attestation.
- Not attested: no small, stable, publicly tagged GitHub repository was found for this pass within a reasonable search.
- id: https-none-sha-system-github-local
transport: https
ref: sha
auth: none
host: github
ca: system
node_path: local
support: supported
qualifiers:
- Only a public, read-only repository is exercised; GitHub does not receive a token or an SSH deploy key from this attestation.
evidence:
kind: live
outcome: success
attestation: att-2026-09-01-github-sha
- id: https-pat-branch-system-github-local
transport: https
ref: branch
auth: pat
host: github
ca: system
node_path: local
support: unverified
qualifiers:
- Not attested: requires a real GitHub Personal Access Token, which this pass does not hold.
- id: ssh-deploy-key-branch-na-github-local
transport: ssh
ref: branch
auth: deploy-key
host: github
ca: not-applicable
node_path: local
support: unverified
qualifiers:
- Not attested: requires a real GitHub-registered SSH deploy key, which this pass does not hold.
- id: https-pat-branch-persource-gitea-local
transport: https
ref: branch
auth: pat
host: gitea
ca: per-source
node_path: local
support: supported
qualifiers:
- Attested against a private repository on a disposable, self-signed Gitea instance; the self-signed certificate is trusted via the per-source custom CA field, not system trust.
- A wrong token against this private repository was separately confirmed to classify as an authentication failure, and a wrong SSH host key as a host-key mismatch.
evidence:
kind: live
outcome: success
attestation: att-2026-09-01-gitea-https-branch
- id: https-pat-tag-persource-gitea-local
transport: https
ref: tag
auth: pat
host: gitea
ca: per-source
node_path: local
support: supported
qualifiers:
- Annotated tag, resolved through its peeled commit.
evidence:
kind: live
outcome: success
attestation: att-2026-09-01-gitea-https-tag
- id: https-pat-sha-persource-gitea-local
transport: https
ref: sha
auth: pat
host: gitea
ca: per-source
node_path: local
support: supported
qualifiers: []
evidence:
kind: live
outcome: success
attestation: att-2026-09-01-gitea-https-sha
- id: ssh-deploy-key-branch-na-gitea-local
transport: ssh
ref: branch
auth: deploy-key
host: gitea
ca: not-applicable
node_path: local
support: supported
qualifiers:
- Host key fetched and trusted through Sencho's own probe endpoint, exactly as an operator would.
evidence:
kind: live
outcome: success
attestation: att-2026-09-01-gitea-ssh-branch
- id: ssh-deploy-key-tag-na-gitea-local
transport: ssh
ref: tag
auth: deploy-key
host: gitea
ca: not-applicable
node_path: local
support: supported
qualifiers:
- Lightweight tag.
evidence:
kind: live
outcome: success
attestation: att-2026-09-01-gitea-ssh-tag
- id: ssh-deploy-key-sha-na-gitea-local
transport: ssh
ref: sha
auth: deploy-key
host: gitea
ca: not-applicable
node_path: local
support: supported
qualifiers: []
evidence:
kind: live
outcome: success
attestation: att-2026-09-01-gitea-ssh-sha
- id: https-pat-branch-persource-forgejo-local
transport: https
ref: branch
auth: pat
host: forgejo
ca: per-source
node_path: local
support: supported
qualifiers:
- Attested against a private repository on a disposable, self-signed Forgejo instance; the self-signed certificate is trusted via the per-source custom CA field, not system trust.
- A wrong token against this private repository was separately confirmed to classify as an authentication failure, and a wrong SSH host key as a host-key mismatch.
evidence:
kind: live
outcome: success
attestation: att-2026-09-01-forgejo-https-branch
- id: https-pat-tag-persource-forgejo-local
transport: https
ref: tag
auth: pat
host: forgejo
ca: per-source
node_path: local
support: supported
qualifiers:
- Annotated tag, resolved through its peeled commit.
evidence:
kind: live
outcome: success
attestation: att-2026-09-01-forgejo-https-tag
- id: https-pat-sha-persource-forgejo-local
transport: https
ref: sha
auth: pat
host: forgejo
ca: per-source
node_path: local
support: supported
qualifiers: []
evidence:
kind: live
outcome: success
attestation: att-2026-09-01-forgejo-https-sha
- id: ssh-deploy-key-branch-na-forgejo-local
transport: ssh
ref: branch
auth: deploy-key
host: forgejo
ca: not-applicable
node_path: local
support: supported
qualifiers:
- Host key fetched and trusted through Sencho's own probe endpoint, exactly as an operator would.
evidence:
kind: live
outcome: success
attestation: att-2026-09-01-forgejo-ssh-branch
- id: ssh-deploy-key-tag-na-forgejo-local
transport: ssh
ref: tag
auth: deploy-key
host: forgejo
ca: not-applicable
node_path: local
support: supported
qualifiers:
- Lightweight tag.
evidence:
kind: live
outcome: success
attestation: att-2026-09-01-forgejo-ssh-tag
- id: ssh-deploy-key-sha-na-forgejo-local
transport: ssh
ref: sha
auth: deploy-key
host: forgejo
ca: not-applicable
node_path: local
support: supported
qualifiers: []
evidence:
kind: live
outcome: success
attestation: att-2026-09-01-forgejo-ssh-sha
- id: https-none-branch-system-gitlab-local
transport: https
ref: branch
auth: none
host: gitlab
ca: system
node_path: local
support: supported
qualifiers:
- Only a public, read-only repository is exercised; GitLab does not receive a token or an SSH deploy key from this attestation.
evidence:
kind: live
outcome: success
attestation: att-2026-09-01-gitlab-branch
- id: https-none-tag-system-gitlab-local
transport: https
ref: tag
auth: none
host: gitlab
ca: system
node_path: local
support: supported
qualifiers:
- Only a public, read-only repository is exercised; GitLab does not receive a token or an SSH deploy key from this attestation.
evidence:
kind: live
outcome: success
attestation: att-2026-09-01-gitlab-tag
- id: https-none-sha-system-gitlab-local
transport: https
ref: sha
auth: none
host: gitlab
ca: system
node_path: local
support: supported
qualifiers:
- Only a public, read-only repository is exercised; GitLab does not receive a token or an SSH deploy key from this attestation.
evidence:
kind: live
outcome: success
attestation: att-2026-09-01-gitlab-sha
- id: https-none-branch-system-bitbucket-local
transport: https
ref: branch
auth: none
host: bitbucket
ca: system
node_path: local
support: supported
qualifiers:
- Only a public, read-only repository is exercised; Bitbucket does not receive a token or an SSH deploy key from this attestation.
evidence:
kind: live
outcome: success
attestation: att-2026-09-01-bitbucket-branch
- id: https-none-tag-system-bitbucket-local
transport: https
ref: tag
auth: none
host: bitbucket
ca: system
node_path: local
support: unverified
qualifiers:
- Only a public, read-only repository is exercised; Bitbucket does not receive a token or an SSH deploy key from this attestation.
- Not attested: the public fixture repository used for this pass carries no tags.
- id: https-none-sha-system-bitbucket-local
transport: https
ref: sha
auth: none
host: bitbucket
ca: system
node_path: local
support: supported
qualifiers:
- Only a public, read-only repository is exercised; Bitbucket does not receive a token or an SSH deploy key from this attestation.
evidence:
kind: live
outcome: success
attestation: att-2026-09-01-bitbucket-sha
# ===== Distribution: does the same combination hold off the local node =====
# Scoped to host: github (a real branded host reachable over public egress)
# rather than the self-hosted fixtures above: the fleet's inbound firewall
# only opens ports 22 and 1852, so a remote or Pilot node cannot reach a
# container port newly published on the hub, but every node has unrestricted
# outbound egress to the public internet.
- id: https-none-branch-system-github-direct-proxy
transport: https
ref: branch
auth: none
host: github
ca: system
node_path: direct-proxy
support: supported
qualifiers:
- Proves the direct-proxy path forwards and executes the fetch on the target node; it does not by itself prove any other host, ref, auth, or CA combination on this path.
evidence:
kind: live
outcome: success
attestation: att-2026-09-01-github-direct-proxy
- id: https-none-branch-system-github-pilot
transport: https
ref: branch
auth: none
host: github
ca: system
node_path: pilot
support: supported
qualifiers:
- Proves the Pilot dial-out path forwards and executes the fetch on the target node; it does not by itself prove any other host, ref, auth, or CA combination on this path.
evidence:
kind: live
outcome: success
attestation: att-2026-09-01-github-pilot
limitations:
- id: no-rate-limit-classification
title: No rate-limit classification
statement: A Git host rate-limit response (for example GitHub's secondary rate limits) is not classified as its own error state. It surfaces as an authentication failure or a generic transport error depending on the host's exact response. Wait and retry; there is no dedicated rate-limit message or backoff guidance yet.
- id: no-git-lfs
title: No Git LFS
statement: Compose and env files tracked via Git LFS are rejected rather than silently fetched as pointer stubs. Commit plain files instead.
- id: no-submodules
title: No submodules
statement: Submodule contents are not fetched. Inputs and build contexts that reference submodule contents are refused with an actionable message; a warning is shown when .gitmodules is present.
- id: no-sparse-partial-clone
title: No sparse or partial clone
statement: Every fetch materializes the complete repository at the resolved commit (shallow, single-branch); there is no sparse or partial clone for large monorepos.
- id: no-github-app-authentication
title: No GitHub App authentication
statement: Authentication is Personal Access Token or SSH deploy key only. GitHub App installation tokens are not supported.
- id: no-provider-pull-request-revisions
title: No provider pull/merge request revisions
statement: Sources track a branch, a tag, or a pinned commit SHA. A provider-specific pull request or merge request revision (for example GitHub's refs/pull/N/head) is not a supported ref shape.
- id: outbound-target-restrictions
title: Outbound target restrictions
statement: Repository targets that resolve to loopback, link-local, multicast, or selected special-use addresses are refused before any request is sent. Private hosts on an operator's own LAN or VPN are not affected by this restriction.
- id: branch-tag-name-collision
title: Branch/tag name collisions
statement: A bare ref name resolves as a branch first, then as a tag. If an operator renames a branch and a tag of the same name later appears, the source silently starts resolving the tag instead. A ref name that is also a valid 40 or 64 character hex string resolves as a commit SHA before either lookup.
error_model:
- code: REPO_NOT_FOUND
label: Repository not found
status: 404
meaning: The repository does not exist, or (indistinguishably, matching GitHub's own private-repo masking) exists but is private and no usable credential was supplied.
- code: AUTH_FAILED
label: Authentication failed
status: 400
meaning: The Git host rejected the supplied credential. Mapped to 400, never 401, so an upstream Git-host auth failure never triggers the dashboard's own session logout.
- code: SSH_HOST_KEY_FAILED
label: SSH host key mismatch
status: 400
meaning: The server's SSH host key does not match the fingerprint trusted for this source.
- code: REF_NOT_FOUND
label: Ref not found
status: 404
meaning: The configured branch, tag, or commit SHA does not exist on the remote.
- code: REF_DELETED
label: Ref deleted or rewritten
status: 404
meaning: A ref that previously resolved to a commit no longer matches that history (deleted, force-pushed, or superseded by a same-named tag).
- code: UNSUPPORTED_REF
label: Commit not reachable on this host
status: 400
meaning: A pinned commit SHA that the Git host will not serve because it is not advertised by any branch or tag tip.
- code: NETWORK_TIMEOUT
label: Network timeout
status: 504
meaning: A connect, fetch, or DNS-resolution timeout, or a target the host actively refused.
- code: GIT_ERROR
label: Git error
status: 400
meaning: Any other transport failure not covered above (invalid URL, disallowed target, TLS/certificate problem, oversized repository, canceled fetch), classified with a specific operator-facing message.
- code: FILE_NOT_FOUND
label: File not found
status: 404
meaning: A configured compose or env file path does not exist on the resolved commit.
reconciliation_only_codes:
# Not part of this transport matrix; listed so the partition of
# GitSourceErrorCode is visible and auditable. Owned by the GitOps
# change-plan lifecycle, not by repository transport.
- STALE_PLAN
- PLAN_FINGERPRINT_REQUIRED
- PLAN_BLOCKED
- LEGACY_PENDING
- PLAN_UNAVAILABLE
- OPERATION_IN_FLIGHT