Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 71 additions & 0 deletions .github/workflows/pages.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
name: GitHub Pages

on:
push:
branches: [main]
workflow_dispatch:

permissions:
contents: read

concurrency:
group: pages
cancel-in-progress: false

jobs:
build:
name: Pages / Build
runs-on: ubuntu-24.04
timeout-minutes: 15
permissions:
contents: read
steps:
- name: Check out the independent repository
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803
with:
persist-credentials: false

- name: Set up pnpm
uses: pnpm/action-setup@f40ffcd9367d9f12939873eb1018b921a783ffaa
with:
run_install: false

- name: Set up Node.js
uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38
with:
node-version-file: .node-version
cache: pnpm

- name: Configure GitHub Pages
uses: actions/configure-pages@983d7736d9b0ae728b81ab479565c72886d7745b

- name: Install dependencies from the lockfile
run: pnpm install --frozen-lockfile

- name: Build the static site
run: pnpm build

- name: Verify the static artifact
run: pnpm check:routes

- name: Upload the static site artifact
uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9
with:
path: build

deploy:
name: Pages / Deploy
needs: build
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-24.04
timeout-minutes: 10
permissions:
pages: write
id-token: write
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Deploy GitHub Pages
id: deployment
uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128
14 changes: 9 additions & 5 deletions PLAN.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Repository visibility: Public
- **Purpose:** Publish and maintain the public Link42 company website independently of the private platform monorepo.
- **Audience:** Security practitioners, learners, customers, contributors, and the public.
- **Stack:** TypeScript, Svelte 5, SvelteKit, pnpm, Node 22, Vite, Vitest, and Biome.
- **Runtime target:** SvelteKit `adapter-node`; hosting and production cutover are outside this plan.
- **Runtime target:** SvelteKit `adapter-static` output for GitHub Pages at the `link42.app` domain root; repository Pages settings, custom-domain APIs, DNS, and production cutover remain separate.
- **Content:** Git-backed Markdown for Learn and Blog.
- **Software licence:** AGPL-3.0-only.
- **Original editorial content licence:** CC BY-NC 4.0.
Expand Down Expand Up @@ -102,7 +102,7 @@ All package manifests, lockfiles, configuration, documentation, licences, CI, an
- Move the required footer, theme, and public navigation into website-owned source; remove `@link42/ui`.
- Remove `@link42/auth-client`, `src/lib/api.ts`, auth locals, session resolution, sign-out logic, and auth environment variables.
- Remove Cytoscape and every dependency used only by investigations.
- Retain only Svelte, SvelteKit, `adapter-node`, and Marked as runtime dependencies.
- Retain only Svelte, SvelteKit, `adapter-static`, and Marked as runtime dependencies.
- Retain Vite, TypeScript, Vitest, Biome, and the Svelte Vite plugin for development.
- Add Playwright and Axe coverage only after plan and dependency approval.
- Replace the Google Fonts runtime import with either locally vendored, provenance-recorded Geist assets under their original licence or an approved system-font fallback.
Expand All @@ -119,6 +119,7 @@ All package manifests, lockfiles, configuration, documentation, licences, CI, an
| 5 | Public company pages | Port home, about, changelog, and licence after editorial and public-safety review; remove or neutralise links to excluded API, reports, and investigations routes and publish the selected licensing policy accurately. | 3, 4 | done |
| 6 | Verification and private CI | Add one-command verification, route and link tests, Playwright and Axe checks, secret and privacy scans, dependency audit, deterministic generation checks, and hosted CI in the private repository. | 3, 4, 5 | done |
| 7 | Public release | Publish the independent repository only after private hosted CI succeeds; configure public default-branch protections and security features; verify a credential-free fresh clone, full history, tree, receipts, routes, browsers, accessibility, and final public hosted CI. Hosting and production remain excluded. | 6 | done |
| 8 | GitHub Pages and domain preparation | Produce strict static output for every included route and asset, add a pinned least-privilege Pages workflow for protected `main`, and prepare domain-root metadata for `link42.app`. Repository Pages settings, custom-domain APIs, DNS, DigitalOcean, and production verification remain excluded. | 7 | done |

One completed feature equals one tested commit and push. Do not start the next feature with uncommitted work.

Expand All @@ -136,6 +137,8 @@ One completed feature equals one tested commit and push. Do not start the next f
- `pnpm audit --prod --audit-level=high` has no high or critical finding.
- Secret scans pass on the repository tree and complete independent history.
- Hosted CI must complete successfully; local results cannot substitute for an unavailable hosted run.
- The static artifact contains Home, About, Changelog, Licence, all 15 Learn endpoints, Blog index and article, RSS, 404 handling, local fonts, and all referenced assets at domain-root paths.
- The Pages workflow builds on protected `main` or manual dispatch only, deploys no pull request, uses pinned actions, and separates read-only build permission from the Pages and OIDC deploy permissions.

## Commands

Expand All @@ -155,8 +158,8 @@ pnpm verify

- The private monorepo remains in place and private.
- The website repository is public after completing Feature 7's explicit publication gates.
- No public deployment specification, credential, private app identifier, or DNS mutation script is copied.
- Hosting, DNS, staging, and production cutover are explicitly excluded from this plan.
- No credential, private app identifier, or DNS mutation script is copied.
- GitHub Pages source preparation is included in Feature 8; repository settings, custom-domain APIs, DNS, DigitalOcean, staging, and production cutover are explicitly excluded.
- Keep website links pointed at the durable `https://rule1.link42.app` product URL, not either GitHub Pages owner URL.

## Known Risks
Expand All @@ -166,7 +169,7 @@ pnpm verify
- The current changelog links to `/reports/**` and `/api`; those links would be broken or imply deferred functionality is present.
- The current About page publishes infrastructure, costs, private-repository references, and broad authorship claims. It requires an editorial accuracy review.
- Code, editorial text, brand assets, and third-party content require explicit file-level licensing boundaries; a root AGPL licence alone must not imply a trademark grant for logos.
- A future deployment must be separately planned and must not expose private platform components or configuration.
- Enabling Pages, attaching the custom domain, changing DNS, and verifying production require a separate controlled step after Feature 8 succeeds.

## Discovered Dependencies

Expand All @@ -176,3 +179,4 @@ pnpm verify
| D2 | Approve Playwright and Axe development dependencies. **Decision:** approved for the verification feature. | 6 | resolved |
| D3 | Choose locally vendored Geist fonts or the system-font fallback. **Decision:** locally vendored Geist assets with provenance, checksums, and their original licence. | 2 | resolved |
| D4 | Permit literal SvelteKit bracketed route directories in concrete provenance paths without permitting `*` or `?` wildcards. | 4 | resolved |
| D5 | Replace `adapter-node` with `adapter-static` and add immutable official GitHub Pages actions. **Decision:** approved for Feature 8 with exact action SHAs and least-privilege jobs. | 8 | resolved |
16 changes: 13 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

This repository is the independent, public source for the Link42 company website at [link42.app](https://link42.app). The website is being separated from Link42's private platform monorepo so that its public source, content provenance, licensing boundaries, and release history can be reviewed without exposing private services or operational configuration.

The repository contains the public-only website shell and the publication safeguards used to keep it independent of the private platform. Content is being added in small, tested features recorded in [PLAN.md](PLAN.md). Until the public release and production cutover gates are complete, this repository is source-in-progress rather than the canonical production deployment.
The repository contains the public-only website shell and the publication safeguards used to keep it independent of the private platform. Work is recorded in small, tested features in [PLAN.md](PLAN.md). The repository is public and produces a GitHub Pages-compatible artifact, but it is not the canonical production deployment until the separately controlled Pages, custom-domain, DNS, and live-verification gates are complete.

## Development

Expand All @@ -28,10 +28,20 @@ history for secrets, formatting, types, unit tests, the production build, every
included and excluded route, internal links, desktop/mobile accessibility in
light and dark themes, and high-severity production dependency advisories.

Route and internal-link verification starts the built website only on loopback.
It validates external HTTPS link syntax but deliberately does not fetch external
Route and internal-link verification inspects the static `build/` artifact
directly. Browser and accessibility checks serve that artifact only on loopback.
They validate external HTTPS link syntax but deliberately do not fetch external
URLs. CI uses the same command with read-only repository access and no secrets.

## GitHub Pages artifact

`pnpm build` writes the complete domain-root site to `build/`. The pinned Pages
workflow builds and verifies that directory on protected `main` pushes or an
explicit manual run, then grants only its deploy job Pages and OIDC write access.
The source intentionally contains no `CNAME`; Pages enablement, the `link42.app`
custom-domain setting, `www.link42.app` redirect DNS, and production verification
are separate operational steps.

The source manifest is pinned to a single private-source commit. A path not explicitly listed in that manifest is rejected by default. See [provenance/SOURCE_PROVENANCE.md](provenance/SOURCE_PROVENANCE.md) before importing any material.

## Publication boundary
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
"verify": "pnpm check && pnpm check:gitleaks && pnpm test && pnpm build && pnpm check:routes && pnpm test:e2e && pnpm audit --prod --audit-level=high"
},
"dependencies": {
"@sveltejs/adapter-node": "5.5.7",
"@sveltejs/adapter-static": "3.0.10",
"@sveltejs/kit": "2.70.3",
"marked": "18.0.11",
"svelte": "5.56.10"
Expand Down
6 changes: 3 additions & 3 deletions playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ export default defineConfig({
retries: 0,
reporter: "line",
use: {
baseURL: "http://127.0.0.1:4173",
baseURL: "http://127.0.0.1:4174",
trace: "retain-on-failure",
},
webServer: {
command: "pnpm build && pnpm preview --host 127.0.0.1 --port 4173",
url: "http://127.0.0.1:4173",
command: "pnpm build && pnpm preview --host 127.0.0.1 --port 4174",
url: "http://127.0.0.1:4174",
reuseExistingServer: false,
timeout: 120_000,
},
Expand Down
Loading
Loading