Skip to content

feat: the changelog workflow tags and publishes releases without recording them in the hub - #11

Merged
AmrMsCLL merged 1 commit into
mainfrom
feat/changelog-without-hub-records
Nov 5, 2026
Merged

AmrMsCLL merged 1 commit into
mainfrom
feat/changelog-without-hub-records

Conversation

@AmrMsCLL

@AmrMsCLL AmrMsCLL commented Nov 5, 2026

Copy link
Copy Markdown
Member

The changelog workflow stops writing YAML into graphite-hub. The hub now reads GitHub Releases directly (Project-Graphite/graphite-hub#29).

  • Removed the node scripts/compile.mjs and Record releases steps. Tagging the next version and publishing its GitHub Release are unchanged.
  • Added platform to the App token's repositories. The hub's new scripts/sync.mjs reads the listed projects from platform/projects/*.yml instead of the hub's registry.json.

Merge order: merge this before graphite-hub#29. Until #29 merges, the hub's current sync.mjs still reads registry.json and works without the removed steps. If #29 merges first, every project's changelog run fails at compile.mjs, which no longer exists, after its tags and releases have already been published.

Stale docs: README.md lines 61 and 172–179 still say the workflow records entries under releases/ in graphite-hub. That's phase 8 documentation and needs permission.

@AmrMsCLL
AmrMsCLL requested a review from a team as a code owner November 5, 2026 13:22
@AmrMsCLL
AmrMsCLL merged commit d67386c into main Nov 5, 2026
2 checks passed
@AmrMsCLL
AmrMsCLL deleted the feat/changelog-without-hub-records branch November 5, 2026 13:37
AmrMsCLL added a commit to Project-Graphite/graphite-hub that referenced this pull request Nov 5, 2026
…gn-in, live stream (#29)

Phase 2 of the hub rebuild
(`.claude/.Plans/as2026-11-04-graphite-hub-server.md`), steps 1 to 6:
the hub becomes one Node 24 server, React Router framework mode on
Express, with SQLite for records and Redis for the bus, the stream,
sessions and rate limits. Every current public route keeps its markup.
Step 7, the deploy folder and the cutover, is a separate platform PR and
owner work, listed below.

## Commits

1. **build**: React Router framework mode, Express, one tsconfig with
typegen, Dockerfile (non-root, `/data`), local `compose.yaml` with
`DEV_LOGIN` and Redis.
2. **storage**: `node:sqlite` with ordered SQL migrations; the five
current profiles are seeded from `registry.json`. Redis client; the bus
writes the activity row first, then the 7-day `events` stream (its id is
the row's `seq`), then publishes per topic. The snapshot is rebuilt when
the bus says something changed.
3. **sign-in**: `?graphite` shows the control. GitHub OAuth
(`read:org`), organisation membership required, owner from `role:
admin`. Sessions in Redis for 7 days with the GitHub token sealed;
membership re-checked at most once a minute. Sign-out checks CSRF and
Origin. `CF-Connecting-IP` is trusted only from Cloudflare's ranges,
refreshed daily. CSP with a per-request nonce, HSTS and the rest.
4. **errors**: fingerprinted by normalised message and top frame,
upserted into `errors`, and the owner is emailed on a new or reopened
fingerprint, at most once a day.
5. **registry and GitHub**: `platform/projects/*.yml` validated with
platform's own schema through Ajv 2020-12. Org members, repository
stats, and the webhook: HMAC check, idempotent by delivery id, 202 at
once, processed afterwards. A `.graphite.yml` push dispatches the
platform sync. Hourly full sync as the fallback.
6. **changelog**: every project gets 1.0.0 once, on import day; after
that, GitHub Releases (webhook plus hourly fallback), with generated
notes classified by `rules.mjs`. `releases/` (YAML history) is removed,
and `scripts/sync.mjs` only cuts tags, reading listed projects from
platform.
7. **deploys and uptime**: `/internal/deploys` uses the agent's signing
scheme (HMAC over `stamp.body`, 5-minute skew, replay set) and refuses
anything that came through Traefik. Uptime pings every 30 s.
`toPublicEvent` allow-lists fields per kind. `/internal/metrics` reports
error rate, p95 and live connections.
8. **live**: `GET /live?topics=` as SSE, with replay after
`Last-Event-ID`, 25 s heartbeat, 12-topic limit and 100 ms coalescing.
Visitors get only public projections. The client hook revalidates the
loaders. Also the nightly lifecycle job (uptime roll-ups, retention).
9. **routes**: all 13 current URL forms plus `sitemap.xml`, `llms.txt`,
the old `_redirects` as 301s, and a real 404 status.
10. **ci**: one `pipeline.yml` on the shared pipeline (image
`ghcr.io/project-graphite/graphite-hub/site:sha-…`), plus types, format
and bundle size.

## Verified

- `npm run lint`, `format:check`, `typecheck`: clean. `npm test`: 50
pass (the new `server/*.test.ts` specs and the existing
`scripts/rules.test.mjs`).
- The real platform schema validates all 13 real `projects/*.yml`. The
listed set gives the same 9 projects, paths and URLs as the current
site.
- Ran against a local Redis with real GitHub data. Every route answered
200, unknown handles and slugs 404, redirects 301. Signed deploy reports
reached an open live stream without the host. Webhook signature,
redelivery, CSRF, origin and open-redirect checks behaved. Production
build: all 5 inline scripts carry the CSP nonce, assets are immutable,
and Chromium hydrated with no console errors. A published `releases`
event made an open tab revalidate.

## Bundle budget

150 KB of compressed JavaScript per page (owner decision). Pages load
116–120 KB today, about 107 KB of it React 19 and React Router.

## Cutover steps

The PRs, in merge order: Project-Graphite/actions#11 →
Project-Graphite/platform#49 → Project-Graphite/console#30 → this PR →
Project-Graphite/platform#50.

**A. Before merging anything**
1. Create an OAuth App: homepage `https://project-graphite.com`,
callback `https://project-graphite.com/auth/callback`.
2. Give the platform GitHub App **Organisation → Members: read** and
accept the new permission on the installation. Without it the hub cannot
list members.
3. Generate four values: `SESSION_SECRET`, `GITHUB_WEBHOOK_SECRET`,
`DEPLOY_REPORT_SECRET` and `REDIS_PASSWORD`, each with `node -e
"console.log(require('crypto').randomBytes(32).toString('base64url'))"`.
4. Enter them in the console's Secrets page:
- `/graphite-hub/`: `GITHUB_CLIENT_ID`, `GITHUB_CLIENT_SECRET`,
`GITHUB_WEBHOOK_SECRET`, `SESSION_SECRET`, `DEPLOY_REPORT_SECRET`,
`REDIS_PASSWORD`, `PLATFORM_APP_CLIENT_ID`, `PLATFORM_APP_PRIVATE_KEY`;
optionally `SMTP_URL`, `ALERTS_EMAIL`, `ALERTS_FROM`.
   - `/agent/`: `DEPLOY_REPORT_SECRET` (the same value).

5. Add `DEPLOY_REPORT_SECRET=<the same value>` to
`/etc/graphite/console.env` on the VPS over SSH. The Secrets page
refuses `/console/`; the console's own secrets are placed by hand.

**B. Merge, in order**
6. actions#11. The changelog workflow stops writing YAML into the hub.
7. platform#49. The hub's deploy folder lands disabled; the console
redeploys with the new variable.
8. On the VPS, install the host files (they only travel through
`bootstrap.sh`):
   ```sh
   cd /srv/graphite/bin
sudo curl -fsSL -o graphite-up
https://raw.githubusercontent.com/project-graphite/platform/main/host/graphite-up
sudo curl -fsSL -o graphite-report
https://raw.githubusercontent.com/project-graphite/platform/main/host/graphite-report
sudo chmod 755 graphite-up graphite-report && sudo chown deploy:deploy
graphite-up graphite-report
   ```
9. console#30. The console gains the two hub alert rules.
10. This PR. The pipeline pushes
`ghcr.io/project-graphite/graphite-hub/site:sha-…` and records it in
platform's `values`; Deploy still holds the hub disabled. Then make the
new `graphite-hub/site` package **public** in the organisation's
packages, matching `console/app` and `strata/app`, so the VPS can pull
it.

**C. Cutover**
11. platform#50 retargets to `main` once #49's branch is deleted. Mark
it ready and merge it. Deploy starts the hub, waits for readiness,
routes the apex and points DNS at the VPS through Cloudflare.
12. Check:
    - `https://project-graphite.com/health/ready` answers `ready`.
- The response headers carry `Content-Security-Policy` and
`X-Request-Id`.
    - `/?graphite` shows *sign in*, and signing in works.
- The console shows `graphite-hub-app-1` and `graphite-hub-redis-1`
healthy, with no hub alerts.
13. Create the organisation webhook: payload
`https://project-graphite.com/internal/github`, content type JSON,
secret `GITHUB_WEBHOOK_SECRET`. Events: push, release, star, fork,
member, membership, organization, repository, meta. The ping under
Recent Deliveries should answer 202.
14. Once the site has run cleanly for a day, delete the `graphite-hub`
Pages project in Cloudflare.

**Rollback before step 14:** in Cloudflare DNS, set the apex back to a
proxied CNAME to `graphite-hub.pages.dev`. **After step 14:** set
`IMAGE_TAG` in `deploy/graphite-hub/values` back to the last good
`sha-`.
AmrMsCLL added a commit that referenced this pull request Nov 7, 2026
…and there is no registry workflow (#12)

Phase 8 of the hub server plan: the README matches the workflows that
exist.

## Changes

- `reusable-sync-registry.yml` is gone from the table and its section; a
note says the Hub's webhook now dispatches `platform`'s **Sync
registry**, and a project's `registry.yml` is dead.
- `reusable-deploy`: writes `IMAGE_TAG=<tag>` into
`deploy/<slug>/values` and commits `chore(<slug>): deploy <tag>`, only
when `deploy/<slug>/` exists; no `tags/<slug>.json`.
- `reusable-changelog`: tags and publishes a GitHub Release (#11); no
release YAML in `graphite-hub`. Token scope lists the three repositories
the workflow asks for.
- "Calling them" names the one file in `project-template`,
`pipeline.yml`.

No workflow changes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant