* 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.
* feat(tier-reconcile): seed tier-catalog with validated inventory
Verified current-state catalog (29 entries) with cross-field invariant
(tier: internal iff availability: internal). No internal Linear IDs
in committed file; publicRoadmapKey slugs used instead.
Canonical validator (scripts/website-catalog/canonical-validate.mjs) passes.
Refs: SEN-549
* feat(tier-reconcile): add canonical catalog, sync scripts, and CI drift check
Add canonical feature catalog (29 entries, no SEN-NNN identifiers) with
cross-field invariant (tier:internal iff availability:internal).
Sencho-owned scripts:
- canonical-validate.mjs: schema + invariant validation
- sync-feature-catalog.mjs: builds sanitized public projection
- check-website-drift.mjs: checksum-based drift detection
- test-drift-detection.mjs: unit tests for drift logic
- test-catalog-no-leak.mjs: no prohibited identifiers
GitHub Actions catalog-drift.yml: pull_request required check + push safeguard.
Refs: SEN-549
* fix(tier-reconcile): correct relative paths in scripts for standalone runs
Use fileURLToPath to resolve paths relative to script directory rather
than cwd. Fixes PA-01/PA-02 script execution from any directory.
Also removes SEN-NNN references from docs/feature-catalog.yaml entries
and updates limitation text per audit.
* ci(catalog-drift): authenticate the cross-repo website checkout
The drift check reads the website repository, which is private, so the
ambient workflow token cannot see it and the checkout failed with a
not-found error before any validation ran. Mint a GitHub App
installation token scoped to that one repository with read-only contents
access, matching the pattern the docs sync workflow already uses.
Also declare contents: read at the workflow level so the job stops
inheriting the repository default token permissions.
* fix(catalog-drift): make the drift check able to fail
The job reported success no matter what the website repository contained,
for two compounding reasons.
The root checkout ran after the website checkout. actions/checkout cleans
its destination, so it deleted website-checkout before any script ran.
Reorder so the root checkout comes first.
The verify step then regenerated the snapshot into that directory before
comparing against it, so the comparison only ever read back what it had
just written, recreating the deleted tree along the way. Drop the sync
call and compare against what the website has actually committed.
The comparison also trusted the checksum recorded in the snapshot
metadata without checking that it described the snapshot file sitting
next to it, so a hand-edited or stale snapshot passed beside fresh
metadata. Require both to agree.
Round out the surrounding tooling: a catalog with no entries array now
fails validation instead of reporting zero entries, the unused clone
branch no longer calls require from an ES module, and the failure output
names the regeneration command, which is now reachable as an npm script.
* ci(catalog-drift): check for website-side drift on a daily schedule
The path filters only fire on changes inside this repository, so an
edited or reverted snapshot in the website repository left the check
green while the two were genuinely out of sync. A daily run closes that
window without waiting for someone to touch the canonical catalog.
* fix(catalog-scripts): check every prohibited key and drop an inert test
The leak check listed five prohibited keys but only tested three by
hand, so an entry carrying route or service would have reached the
public catalog unnoticed. Drive the loop from the list instead.
Remove test-drift-detection.mjs. Nothing invoked it, and it asserted
against a reimplemented normalizer rather than the drift script it named,
so it reported coverage it did not provide.
* docs: relicense Sencho to AGPLv3 and reframe Community positioning
Replace BSL with AGPLv3 for the public Community product, update contributor
and licensing copy for Community-focused contributions, and surface source
and license links in Settings About.
* docs: clarify CLA scope and Community contribution framing
* fix(ui): split About link constants and harden AboutSection test selectors
* ci: harden CI and supply-chain pipeline
* Add frontend Vitest step to ci.yml so the 241 existing frontend tests run on
every PR (mirrors the backend build/test/lint/audit order).
* Pin Node 26 as a single source of truth: new .node-version, node-version-file
on all setup-node calls, engines.node ">=26.0.0" in all three package.json
files. Matches the Dockerfile's node:26-alpine.
* SHA-pin remaining mutable actions in the start-app composite
(actions/setup-node v6, actions/cache v4.3.0).
* Pin Dockerfile supply-chain inputs: golang:1.26.3-alpine by sha256 digest in
both builder stages; replace mutable-tag git clone with commit-SHA fetch for
docker/cli (v29.4.1) and docker/compose (v5.1.3). LDFLAGS version strings
and otel patch preserved unchanged.
* Ref-scope docker-publish concurrency so two different release tags cannot
cancel each other; same-ref reruns still cancel as before.
* Harden CLA workflow: drop actions:write from permissions; tighten the
issue_comment trigger to PRs only (github.event.issue.pull_request != null)
matching the two documented CLA phrases. No PR code is checked out.
* Drop trivy-version: latest from both Trivy scans so the SHA-pinned
aquasecurity/trivy-action governs the bundled binary version. The
HIGH/CRITICAL gate, severity filter, and trivy.yaml (OpenVEX) are unchanged.
* Restructure Dependabot: add applies-to: security-updates groups for npm
(root/backend/frontend), docker, and github-actions; switch github-actions
to directories so the local composite action is monitored alongside the
top-level workflows.
* Add a daily scheduled SARIF security scan (security-scan.yml): two parallel
jobs scanning saelix/sencho:latest and a fresh main HEAD build, uploading to
GitHub code scanning. Least-privilege (contents: read, security-events:
write). Visibility only; existing PR-blocking and release-blocking Trivy
gates are not weakened.
Validation: backend tsc clean; frontend tsc clean; frontend npm test 27 files
/ 241 tests pass; npm audit --audit-level=high passes at root, backend, and
frontend; docker buildx build --check passes with no warnings (all pinned
digests resolve from the registry).
* ci(frontend): set explicit jsdom URL so localStorage initializes in CI
jsdom does not instantiate window.localStorage / sessionStorage when the
document has the opaque about:blank origin. Five frontend test files that
call localStorage.clear() in beforeEach started failing once the new
frontend Vitest step in this PR began running them on Linux CI runners.
Configuring environmentOptions.jsdom.url with a real same-origin URL is
the documented Vitest 4.x workaround and is a config-only change. All 27
test files (241 tests) pass locally with the fix applied.