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