Skip to content
Closed
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
2 changes: 1 addition & 1 deletion .github/workflows/publish-cli.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ on:
workflow_dispatch:
inputs:
version:
description: 'Version to publish (e.g. 0.0.2). Leave blank to publish the current one.'
description: 'Version to publish (e.g. 0.0.4). Leave blank to publish the current one.'
required: false
dry_run:
description: Pack and inspect without publishing
Expand Down
27 changes: 24 additions & 3 deletions .railway/railway.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,30 @@ export default defineRailway(() => {
restartPolicyMaxRetries: 5,
},
replicas: { 'europe-west4-drams3a': 1 },
// preserve() keeps the value already set on Railway without writing a
// credential into source.
env: { DATABASE_URL: preserve() },
/*
* `env` is reconciled, not merged: a variable set in the Railway dashboard
* and absent here is *removed* on apply. So every variable the service
* reads is declared, whether or not it is set today — a name listed with
* `preserve()` that does not exist yet is a no-op, while a name omitted is
* a deletion waiting for the next `railway config apply`.
*
* `preserve()` keeps whatever value Railway already holds without writing
* a credential into source. That is not a convenience for the secrets: it
* is the only reason they can be declared here at all.
*/
env: {
DATABASE_URL: preserve(),
// Required before any tracker credential can be stored: 32 random bytes
// encrypting them at rest. Losing it makes every stored token
// unreadable, so it must survive an apply.
SENTINEL0_SECRET_KEY: preserve(),
DASHBOARD_URL: preserve(),
CORS_ORIGINS: preserve(),
LOG_LEVEL: preserve(),
DATABASE_POOL_MAX: preserve(),
DATABASE_SSL: preserve(),
SLACK_WEBHOOK_HOST: preserve(),
},
})

// The dashboard. Its own Dockerfile, and no pre-deploy command — it owns no
Expand Down
8 changes: 8 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,14 @@ deprecated: services could no longer opt in from 2026-08-28, and it retires on
unrepresentable — with no root `railway.json`, a new service cannot inherit another's
builder and silently deploy the wrong image.

A service's `env` block is **reconciled, not merged**: a variable set in the Railway
dashboard and absent from `railway.ts` is *removed* on apply. So every variable a
service reads is declared there with `preserve()`, whether or not it is set today — a
declared name that does not exist yet is a no-op, an undeclared one is a deletion
waiting for the next apply. `SENTINEL0_SECRET_KEY` is the case that made this
concrete: it is required, it can only be set outside source, and losing it makes every
stored credential unreadable. `PORT` stays undeclared because Railway injects it.

`pnpm railway:plan` previews; `pnpm railway:apply` applies after review. Neither
deploys — `pnpm railway:deploy:api` and `pnpm railway:deploy:dashboard` do that, and
they reconcile no configuration, so a change to `.railway/railway.ts` needs an apply
Expand Down
4 changes: 2 additions & 2 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ GET /health
Unauthenticated, because Railway's health check runs before any key exists.

```json
{ "status": "ok", "version": "0.0.1" }
{ "status": "ok", "version": "0.0.3" }
```

---
Expand Down Expand Up @@ -507,7 +507,7 @@ API; the source of truth is Hermes itself.
{
"name": "cerebro",
"hostname": "cerebro.local",
"version": "0.0.1",
"version": "0.0.3",
"started_at": "2026-09-01T20:00:00.000Z",
"last_seen_at": "2026-09-02T08:14:31.000Z",
"hermes_ok": true,
Expand Down
7 changes: 6 additions & 1 deletion docs/deploy-cloud.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,11 @@ exactly like a working one.
**Losing or rotating this key makes every stored credential unreadable.** They are not
recoverable; re-add them under Settings → Integrations.

It is declared in `.railway/railway.ts` as `preserve()`, which is what keeps
`railway config apply` from removing it. A variable set only in the dashboard and not
named in that file is deleted on the next apply — so anything you add there by hand
needs a line there too.

Optional variables:

| Variable | Default | Purpose |
Expand Down Expand Up @@ -187,7 +192,7 @@ Confirm:

```bash
curl https://<your-service>.up.railway.app/health
# {"status":"ok","version":"0.0.1"}
# {"status":"ok","version":"0.0.3"}
```

`/health` is unauthenticated on purpose: the health check has to pass before any key
Expand Down
4 changes: 2 additions & 2 deletions docs/releasing.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ things that only fail on a user's machine:
### Doing it by hand

```bash
pnpm version:set 0.0.2 # every package, and the cli's internal pins
pnpm version:set 0.0.4 # every package, and the cli's internal pins
pnpm install --lockfile-only # the lockfile records those pins
pnpm lint && pnpm test && pnpm build

Expand All @@ -215,7 +215,7 @@ To test a tarball without publishing:

```bash
mkdir /tmp/t && cd /tmp/t && npm init -y
npm install /path/to/sentinel0-0.0.1.tgz
npm install /path/to/sentinel0-0.0.3.tgz
./node_modules/.bin/sentinel0 --version
```

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "sentinel0-monorepo",
"version": "0.0.2",
"version": "0.0.3",
"private": true,
"type": "module",
"scripts": {
Expand Down
6 changes: 3 additions & 3 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "sentinel0",
"version": "0.0.2",
"version": "0.0.3",
"description": "Trigger Hermes agents from your tickets and pull requests.",
"type": "module",
"keywords": [
Expand Down Expand Up @@ -35,8 +35,8 @@
"dependencies": {
"@clack/prompts": "1.4.0",
"@fastify/cors": "11.2.0",
"@sentinel0/common": "0.0.2",
"@sentinel0/orchestrator": "0.0.2",
"@sentinel0/common": "0.0.3",
"@sentinel0/orchestrator": "0.0.3",
"chalk": "4",
"fastify": "5.7.4",
"p-limit": "6.1.0",
Expand Down
2 changes: 1 addition & 1 deletion packages/cloud-api/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@sentinel0/cloud-api",
"version": "0.0.2",
"version": "0.0.3",
"private": true,
"type": "module",
"main": "dist/index.js",
Expand Down
2 changes: 1 addition & 1 deletion packages/cloud-dashboard/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@sentinel0/cloud-dashboard",
"version": "0.0.2",
"version": "0.0.3",
"private": true,
"type": "module",
"scripts": {
Expand Down
2 changes: 1 addition & 1 deletion packages/common/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@sentinel0/common",
"version": "0.0.2",
"version": "0.0.3",
"private": true,
"type": "module",
"main": "./dist/index.js",
Expand Down
2 changes: 1 addition & 1 deletion packages/orchestrator/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@sentinel0/orchestrator",
"version": "0.0.2",
"version": "0.0.3",
"private": true,
"type": "module",
"scripts": {
Expand Down
4 changes: 2 additions & 2 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading