@rtrentjones/greenlight 0.4.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,45 +1,46 @@
1
1
  ---
2
2
  name: provider-cloudflare
3
- description: How Cloudflare works in a Greenlight setup — the zone/DNS provider for every tool, Workers as the keepalive/blog target, token scoping (Workers Scripts:Edit + Zone DNS:Edit), and the Cloudflare MCP. Use when wiring DNS, the keepalive worker, a workers-target tool, or debugging a Cloudflare apply/token.
3
+ description: Cloudflare in a Greenlight setup — the always-on DNS/zone provider, the keepalive Worker host, the workers runtime. Use when wiring DNS, the keepalive Worker, a workers-target tool, or debugging a Cloudflare apply or token scope.
4
4
  ---
5
5
 
6
6
  # provider-cloudflare
7
7
 
8
- Cloudflare is the **always-on** provider in Greenlight: it owns the DNS zone for the
9
- domain (every tool's `<name>.<domain>` CNAME), hosts the **keepalive** Worker (a Cron
10
- Trigger, immune to repo-inactivity disable), and is the `target: workers` runtime for the
11
- blog and throwaway MCP dev targets.
8
+ Cloudflare is the **always-on** provider: it owns the DNS zone for the domain (every tool's
9
+ `<name>.<domain>` CNAME), hosts the **keepalive** Worker (a Cron Trigger, immune to
10
+ repo-inactivity disable), and is the `target: workers` runtime for the blog and throwaway MCP
11
+ dev targets.
12
12
 
13
13
  ## Token — `CLOUDFLARE_API_TOKEN`
14
14
 
15
- One token, these scopes (a missing scope took down a live apply more than once):
16
- - **Account · Workers Scripts · Edit** — deploy the keepalive worker / workers-target tools.
17
- - **Zone · DNS · Edit** the subdomain CNAMEs.
18
- - **Account · Account Settings · Read** read account id.
19
- - **Account · Cloudflare Tunnel · Edit** only if a tool uses `target: oci` (the cloudflared
20
- tunnel). Without it, the tunnel resource fails with **403 Forbidden** on `cfd_tunnel` at apply.
21
-
22
- Create at dash → My Profile → API Tokens → Custom Token. Push it straight to GitHub Actions
23
- with `greenlight secrets gather` (or `gh secret set CLOUDFLARE_API_TOKEN`) — Greenlight keeps
24
- no local secret file. `greenlight add` verifies it against `/user/tokens/verify` (status must
25
- be `active`) before you commit.
15
+ Scopes, creation, and the verify command live in
16
+ [tokens-reference.md](https://github.com/RTrentJones/greenlight/blob/main/docs/tokens-reference.md).
17
+ The short of it: **Account · Workers Scripts:Edit + Zone · DNS:Edit + Account · Account
18
+ Settings:Read** (+ **Account · Cloudflare Tunnel:Edit** only if any tool is `target: oci`).
19
+ Single store: **GitHub Actions secrets** (no local secret file). `greenlight add` fails fast on a
20
+ mis-scoped token beyond `status: active` it probes `/accounts`, so a Zone-DNS-only token (which
21
+ can't see the account it needs) is rejected before you commit.
26
22
 
27
23
  ## Terraform modules
28
24
 
29
- - `infra/modules/tool` — the subdomain DNS record. `proxied = target != "vercel"` (Vercel
30
- needs an unproxied CNAME to `cname.vercel-dns.com`; everything else is proxied).
25
+ - `infra/modules/tool` — the subdomain DNS record. `proxied = target != "vercel"` (Vercel needs an
26
+ unproxied CNAME to `cname.vercel-dns.com`; everything else is proxied).
31
27
  - `infra/modules/keepalive` — `cloudflare_workers_script` + `cloudflare_workers_cron_trigger`,
32
- self-contained (ships its own bundled `worker.js`). One worker aggregates all targets via
33
- `targets_json`; do **not** emit a worker per tool. Needs a workers.dev subdomain registered
34
- on the account once (error 10063 if missing).
28
+ self-contained (bundled `worker.js`). **One** worker aggregates all targets via `targets_json`;
29
+ do not emit a worker per tool.
35
30
 
36
31
  ## MCP
37
32
 
38
33
  `.mcp.json` wires `cloudflare` (Workers/DNS/R2/KV/D1/builds/observability) + `cloudflare-docs`.
39
- Run `/mcp` to authenticate. For richer help: the `cloudflare@cloudflare` plugin skill.
34
+ Run `/mcp` to authenticate.
40
35
 
41
36
  ## Gotchas
42
- - A DNS record for the apex managed by **wrangler** (Workers custom domain) collides with a
43
- Terraform `cloudflare_dns_record` for the same name pick one owner per record.
44
- - The `observability` block on `cloudflare_workers_script` has a provider bug
45
- (propagation_policy conversion error)leave it off.
37
+ - **Account id from a scoped token (`/memberships` 403).** A scoped API token can't call
38
+ `/memberships` to discover the account id, so Workers/wrangler deploys that need it fail. Resolve
39
+ the account id **from the zone** in CI (the agent-lane deploy workflow does exactly this) or write
40
+ it into `wrangler.toml` at `add` time never rely on `/memberships`.
41
+ - **workers.dev subdomain (error 10063).** A first Workers deploy needs a workers.dev subdomain
42
+ registered on the account once.
43
+ - **Wrangler vs Terraform DNS ownership.** A record managed by wrangler (a Workers custom domain)
44
+ collides with a Terraform `cloudflare_dns_record` of the same name — one owner per record.
45
+ - **`observability` block on `cloudflare_workers_script`** trips a provider bug (propagation_policy
46
+ conversion error) — leave it off.
@@ -1,78 +1,64 @@
1
1
  ---
2
2
  name: provider-gemini
3
- description: How the `agent` lane works in Greenlight — an autonomous cron-triggered Cloudflare Worker backed by Google Gemini (free tier). Covers the GEMINI_API_KEY (Google AI Studio, no billing), the gemini-2.5-flash generateContent call, the wrangler deploy (cron + KV + secret + custom_domain), the /, /status, /run surface, api-mode verify, and the free-tier safety envelope. Use when building, deploying, or verifying an agent tool.
3
+ description: The `agent` lane in Greenlight — an autonomous cron-triggered Cloudflare Worker backed by Google Gemini (free tier). Use when building, deploying, or verifying an agent tool, or debugging its KV / account-id / seed wiring.
4
4
  ---
5
5
 
6
6
  # provider-gemini
7
7
 
8
8
  The `agent` lane is an **autonomous tool**: a Cloudflare Worker that wakes on a **cron trigger**,
9
- calls **Gemini** (Google's LLM, free tier), does low-stakes work, stores the result in KV, and
10
- exposes a tiny HTTP surface. It's the keepalive Worker pattern promoted to a user tool — free,
11
- always-available, immune to repo-inactivity, no OCI box, no new paid account.
12
-
13
- `agent` → target **workers**, data **none | kv** (kv holds the last output + run metadata).
9
+ calls **Gemini** (free tier), does low-stakes work, stores the result in KV, and exposes a tiny
10
+ HTTP surface. It's the keepalive-Worker pattern promoted to a user tool — free, always-available,
11
+ immune to repo-inactivity, no OCI box, no paid account. `agent` → target **workers**, data
12
+ **none | kv** (kv holds the last output + run metadata).
14
13
 
15
14
  ## Token — `GEMINI_API_KEY`
16
15
 
17
- Create it at **Google AI Studio** (https://aistudio.google.com/apikey) — **free tier, no billing,
18
- no card**. `greenlight add` verifies it against `…/v1beta/models?key=…` (HTTP 200). One key serves
19
- every agent (shared, not per-tool). It is a **Cloudflare Worker secret** (`wrangler secret put
20
- GEMINI_API_KEY`) never in the repo.
16
+ Creation + verify live in
17
+ [tokens-reference.md](https://github.com/RTrentJones/greenlight/blob/main/docs/tokens-reference.md).
18
+ **Free tier, no billing / no card** (Google AI Studio). One key serves every agent (shared, not
19
+ per-tool); stored as a **Cloudflare Worker secret** (`wrangler secret put`), never in the repo.
20
+ `RUN_TOKEN` (bearer-gates `POST /run`) is the second Worker secret.
21
21
 
22
22
  ## The model + call
23
23
 
24
- `gemini-2.5-flash` (fast; generous free limits — ~15 RPM / 1500 req/day, so a daily cron is ~1/day).
24
+ `gemini-2.5-flash` (fast; ~15 RPM / 1500 req/day free, so a daily cron is ~1/day).
25
25
 
26
26
  ```
27
27
  POST https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key=${GEMINI_API_KEY}
28
- { "contents": [{ "parts": [{ "text": "<prompt>" }] }] }
29
- → candidates[0].content.parts[0].text
28
+ { "contents": [{ "parts": [{ "text": "<prompt>" }] }] } → candidates[0].content.parts[0].text
30
29
  ```
31
30
 
32
31
  ## Deploy — emitted CI (push to main)
33
32
 
34
- `greenlight add` resolves the Cloudflare **account id** into `wrangler.toml` (so wrangler skips the
35
- `/memberships` call a scoped token can't do) and emits **`.github/workflows/deploy-<name>.yml`**. On
36
- a push to main that touches `tools/<name>`, that workflow: **creates the KV namespace** (find-or-create
37
- in-CI no manual step, the id stays a placeholder), deploys the Worker (cron + `custom_domain` from
38
- `wrangler.toml`), sets the `GEMINI_API_KEY` + `RUN_TOKEN` **Worker secrets** from GitHub secrets, seeds
39
- the first run, and verifies. So the only setup is **adding those two GitHub secrets**. (Local instead:
40
- `pnpm exec wrangler deploy --env prod`.)
41
-
42
- `wrangler.toml` carries the cron + KV binding + the per-env `custom_domain` route; no Terraform.
33
+ `greenlight add` emits `.github/workflows/deploy-<name>.yml`. On a push to main that touches
34
+ `tools/<name>` it: creates the KV namespace (find-or-create in CI), deploys the Worker (cron +
35
+ `custom_domain`), sets `GEMINI_API_KEY` + `RUN_TOKEN` Worker secrets from GitHub secrets, seeds the
36
+ first run, and verifies. The only manual setup is **adding those two GitHub secrets**. `wrangler.toml`
37
+ carries the cron + KV binding + per-env `custom_domain`; no Terraform. (Local: `wrangler deploy --env prod`.)
43
38
 
44
- ```toml
45
- [triggers]
46
- crons = ["0 13 * * *"] # daily; stays far under the free-tier quota
47
- [[kv_namespaces]]
48
- binding = "STATE"
49
- [[routes]]
50
- pattern = "<name>.<domain>"
51
- custom_domain = true
52
- ```
53
-
54
- ## Surface
39
+ ## Surface + verify
55
40
 
56
41
  | route | purpose |
57
42
  |---|---|
58
43
  | `scheduled()` | the cron: prompt Gemini → `STATE.put(today, text + metadata)` |
59
- | `GET /` | the latest output (public, read-only) |
44
+ | `GET /` | latest output (public, read-only) |
60
45
  | `GET /status` | `{ ok, lastRun, model, preview }` — the **api-mode verify** target |
61
- | `POST /run` | force a run — **bearer-gated** (a `RUN_TOKEN` secret) so randoms can't burn the Gemini quota; lets deploy/verify seed the first output |
62
-
63
- ## Verify `api` mode on `/status`
64
-
65
- `verify.config.ts` hits `/status` and asserts `ok: true` + a recent run. (Output *quality* is a
66
- future `eval` mode — LLM-judged.) Because the first cron may not have fired at deploy time, the
67
- deploy step `POST /run`s once to seed, then verifies.
46
+ | `POST /run` | force a run — **bearer-gated** (`RUN_TOKEN`); lets deploy/verify seed the first output |
47
+
48
+ `verify.config.ts` hits `/status` and asserts `ok` + a recent run. (Output *quality* is a future
49
+ `eval` mode — LLM-judged.)
50
+
51
+ ## Gotchas
52
+ - **KV namespace as code (no manual id).** The deploy workflow does a **find-or-create** on the KV
53
+ namespace in CI and binds it — the `wrangler.toml` id stays a placeholder, never hand-filled.
54
+ - **Account id from a scoped token.** `add` resolves the Cloudflare account id into `wrangler.toml`
55
+ so wrangler skips the `/memberships` call a scoped token can't make (see provider-cloudflare).
56
+ - **Seed before verify.** The first cron may not have fired at deploy time, so `/status` would
57
+ (correctly) report no run — the deploy step `POST /run`s once to seed *before* verifying. Don't
58
+ reorder these.
59
+ - **No keepalive.** The cron *is* the heartbeat and the edge Worker is always-available — never add
60
+ an agent to `module.keepalive.targets_json`.
68
61
 
69
62
  ## Safety envelope
70
-
71
- - **Low-stakes / read-only** first agents (generate store serve; no destructive external actions).
72
- - **Bearer on `/run`**; the cron frequency stays far under the free-tier daily limit.
73
- - Key is **secret-only** (a Worker secret), never committed or echoed.
74
-
75
- ## No keepalive
76
-
77
- An agent needs no keepalive — the cron *is* its heartbeat and the Worker is always-available
78
- (Cloudflare's edge, not a reclaimable box). Don't add it to `module.keepalive.targets_json`.
63
+ Low-stakes / read-only first agents (generate → store → serve, no destructive external actions);
64
+ bearer on `/run`; cron frequency far under the free-tier daily limit; key secret-only, never echoed.
@@ -1,43 +1,42 @@
1
1
  ---
2
2
  name: provider-github
3
- description: How GitHub works in a Greenlight setup — the single secret store (Actions secrets/environments), the repo/branch/protection Terraform module, the develop→main flow, and OIDC-over-PAT preference. Use when setting tokens, wiring branch protection/environments, or debugging gh/secrets/CI auth.
3
+ description: GitHub in a Greenlight setup — the single secret store (Actions secrets/environments), the repo/branch/protection module, the develop→main flow, OIDC-over-PAT. Use when setting tokens, wiring branch protection/environments, or debugging gh / secrets / CI auth.
4
4
  ---
5
5
 
6
6
  # provider-github
7
7
 
8
- GitHub is the **always-on** control plane: it holds the Actions **secrets** (provider tokens),
9
- the **environments** (beta/prod), branch protection, and runs the deploy/promote/infra
10
- workflows. The `develop → main` flow is standardized (PR → preview, `develop` → beta, `main`
11
- → prod; promote is a gated fast-forward).
8
+ GitHub is the **always-on** control plane: it holds the Actions **secrets** (provider tokens), the
9
+ **environments** (beta/prod), branch protection, and runs the deploy/promote/infra workflows. The
10
+ `develop → main` flow is standardized (PR → preview, `develop` → beta, `main` → prod; promote is a
11
+ gated fast-forward).
12
12
 
13
- ## Token — `GITHUB_TOKEN` (usually you don't set one)
13
+ ## Token — `GITHUB_TOKEN` (you usually don't set one)
14
14
 
15
- - In **CI**, Actions provides `github.token` automatically — the infra.yml maps it to the
16
- `github` provider. No PAT needed for single-repo infra.
17
- - For **cross-repo** ops (managing another repo's settings, syncing secrets to a tool repo),
18
- use a fine-grained **PAT** with the minimal scopes (e.g. `Secrets: write`, `Administration`
19
- for protection). Prefer **GitHub OIDC → cloud** over long-lived cloud tokens where supported.
15
+ In **CI**, Actions provides `github.token` automatically — `infra.yml` maps it to the `github`
16
+ provider; no PAT for single-repo infra. For **cross-repo** ops, use a fine-grained PAT with minimal
17
+ scopes; prefer **GitHub OIDC cloud** over long-lived cloud tokens where supported. Full token table:
18
+ [tokens-reference.md](https://github.com/RTrentJones/greenlight/blob/main/docs/tokens-reference.md).
20
19
 
21
20
  ### Poly-repo deploy loop — the two option-B PATs
22
21
 
23
22
  An adopted tool (submodule) and its wrapper hand off via two fine-grained PATs (`secrets gather`
24
- pushes each to the right repo; see docs/provider-tokens.md):
25
-
26
- - **`GREENLIGHT_DISPATCH_TOKEN`** — on the **tool** repo, scoped **Contents: write** on the
23
+ pushes each to the right repo):
24
+ - **`GREENLIGHT_DISPATCH_TOKEN`** — on the **tool** repo, scoped **Contents:write** on the
27
25
  **wrapper** → the tool's build fires `repository_dispatch` so the wrapper deploys.
28
- - **`GREENLIGHT_STATUS_TOKEN_<TOOL>`** — on the **wrapper** repo, scoped **Commit statuses: write**
29
- on the **tool** → the wrapper posts deploy/verify status back to the tool's commit. **Per-tool
30
- suffix** (e.g. `…_BAMCP`) because it lives on the shared wrapper alongside other tools' tokens.
26
+ - **`GREENLIGHT_STATUS_TOKEN_<TOOL>`** — on the **wrapper** repo, scoped **Commit statuses:write** on
27
+ the **tool** → the wrapper posts deploy/verify status back. **Per-tool suffix** (it lives on the
28
+ shared wrapper alongside other tools' tokens).
31
29
 
32
30
  Provider creds (OCI/Cloudflare/…) live **only in the wrapper**; the tool repo holds just the
33
- dispatch PAT (its build pushes to GHCR with the built-in `github.token`).
31
+ dispatch PAT (its build pushes to GHCR with the built-in `github.token`). Optimal end state: a tool
32
+ repo holds **only** `GREENLIGHT_DISPATCH_TOKEN`.
34
33
 
35
34
  ## Setting secrets
36
35
 
37
- GitHub Actions secrets are the **single** secret store — Greenlight keeps no local secret file.
38
- `greenlight secrets gather <tool> [--repo o/r] [--env <env>]` prompts the tool's tokens (and the
39
- always-on base tokens) with hidden input and pipes them straight to `gh secret set` (never on
40
- disk, never in argv or logs). Run `gh auth login` first. `gh secret set` is the manual alternative.
36
+ GitHub Actions secrets are the **single** store (no local secret file). `greenlight secrets gather
37
+ <tool> [--repo o/r] [--env <env>]` prompts with hidden input and pipes straight to `gh secret set`
38
+ (never on disk, in argv, or logs); `greenlight secrets check [<tool>]` lists the secrets a tool's
39
+ deploy needs and flags missing ones. Run `gh auth login` first.
41
40
 
42
41
  ## Terraform module — `infra/modules/repo`
43
42
 
@@ -45,6 +44,8 @@ Branches + protection + required checks (+ optional environments via the `tool`
45
44
  `manage_github_environments`). For an **external** tool (code in its own repo), set
46
45
  `manage_github_environments = false` so the wrapper's CI stays single-repo (no PAT needed).
47
46
 
48
- ## Gotcha
49
- Direct pushes/merges to a protected `main` are blocked use `gh pr create` + merge, or the
50
- gated `greenlight promote` fast-forward. Keep to `develop` (not `development`) for the branch.
47
+ ## Gotchas
48
+ - **IDs are repo variables, not secrets.** Enumerable IDs (`CLOUDFLARE_ZONE_ID`, account ids) carry
49
+ no authority store them as `vars.*` (referenced `${{ vars.X }}`), not secrets.
50
+ - **Protected `main`** blocks direct pushes/merges — use `gh pr create` + merge, or the gated
51
+ `greenlight promote` fast-forward. Keep to `develop` (not `development`) for the branch.
@@ -1,25 +1,25 @@
1
1
  ---
2
2
  name: provider-hcp
3
- description: How HCP Terraform works in a Greenlight setup — the remote-state backend (free tier, no credit card), local execution mode (HCP stores state + locks; runs use local/CI creds), the cloud{} block, and TF_API_TOKEN auth. Use when setting up remote state, debugging a backend/init/locking issue, or CI apply-on-push.
3
+ description: HCP Terraform in a Greenlight setup — the remote-state backend (free tier, no card) in Local execution mode (HCP stores state + locks; runs use CI creds). Use when setting up remote state, debugging a backend/init/locking issue, or CI apply-on-push.
4
4
  ---
5
5
 
6
6
  # provider-hcp
7
7
 
8
- HCP Terraform (app.terraform.io) is the **remote-state backend** for the wrapper's infra —
9
- free tier, **no credit card**. It replaces local state so CI can `terraform apply` on push
10
- with state locking (no two applies racing).
8
+ HCP Terraform (app.terraform.io) is the **remote-state backend** for the wrapper's infra — free
9
+ tier, **no credit card**. It replaces local state so CI can `terraform apply` on push with state
10
+ locking (no two applies racing).
11
11
 
12
12
  ## Execution mode — **Local**, deliberately
13
13
 
14
14
  The workspace is set to **Local execution mode**: HCP **stores state + does locking only**;
15
- `terraform` runs here / in CI with **our own provider creds** (Cloudflare/Vercel/Supabase
16
- tokens from GitHub Actions secrets). This avoids uploading every provider token to HCP.
15
+ `terraform` runs here / in CI with **our own provider creds** (from GitHub Actions secrets). This
16
+ avoids uploading every provider token to HCP.
17
17
 
18
18
  ## Token — `TF_API_TOKEN`
19
19
 
20
- HCP → Account Settings → Tokens (a **user** API token). In CI it maps to the backend-auth env
21
- var **`TF_TOKEN_app_terraform_io`** (the infra.yml does this mapping). `greenlight add`
22
- verifies it against `/api/v2/organizations` (HTTP 200).
20
+ A **user** API token (HCP → Account Settings → Tokens). Verify command + table:
21
+ [tokens-reference.md](https://github.com/RTrentJones/greenlight/blob/main/docs/tokens-reference.md).
22
+ In CI it maps to the backend-auth env var **`TF_TOKEN_app_terraform_io`** (`infra.yml` does this).
23
23
 
24
24
  ## The `cloud{}` block
25
25
 
@@ -32,15 +32,14 @@ terraform {
32
32
  }
33
33
  ```
34
34
 
35
- Migrate local → HCP with a plain `terraform init` (answer `yes` to copy state). The
36
- `-migrate-state` / `-force-copy` flags are **rejected** for the cloud backend — don't pass them.
37
-
38
35
  ## CI apply-on-push
39
36
 
40
- `infra.yml` (on push to `main`, paths `infra/**`): map GH secrets → `TF_TOKEN_app_terraform_io`
41
- + the provider tokens + `TF_VAR_*`, then setup-terraform (`terraform_wrapper: false`) → init →
42
- plan -out → apply. This is the deploy half — the CLI only edits the `.tf`; CI applies.
37
+ `infra.yml` (on push to `main`, paths `infra/**`): map GH secrets → `TF_TOKEN_app_terraform_io` + the
38
+ provider tokens + `TF_VAR_*`, then setup-terraform (`terraform_wrapper: false`) → init → plan -out →
39
+ apply. The CLI only edits the `.tf`; CI applies.
43
40
 
44
- ## Alternatives
45
- See `docs/terraform-state.md` for the full backend chooser (HCP no-CC · OCI S3-compat ·
46
- R2 card-required · AWS · local).
41
+ ## Gotchas
42
+ - **Migrate local HCP** with a plain `terraform init` (answer `yes` to copy state). The
43
+ `-migrate-state` / `-force-copy` flags are **rejected** for the cloud backend — don't pass them.
44
+ - **Alternatives:** `docs/terraform-state.md` has the full backend chooser (HCP no-CC · OCI
45
+ S3-compat · R2 card-required · AWS · local).
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: provider-neon
3
- description: How Neon works in a Greenlight setup — the `data: neon` store (serverless Postgres), git-style branch-per-env, scale-to-zero + auto-resume (so NO keepalive, unlike Supabase), pooled vs direct connection strings, the NEON_API_KEY, and migrations on a branch. Use when wiring a tool's database, choosing Neon vs Supabase, or a Neon apply.
3
+ description: Neon in a Greenlight setup — the `data: neon` store (serverless Postgres, branch-per-env, auto-resume no keepalive). Use when wiring a tool's database, choosing Neon vs Supabase, or debugging a Neon apply or connection string.
4
4
  ---
5
5
 
6
6
  # provider-neon
@@ -9,15 +9,16 @@ description: How Neon works in a Greenlight setup — the `data: neon` store (se
9
9
  bundled. One Neon **project** per tool, **a branch per env** (git-style copy-on-write): `prod` is
10
10
  the project's default branch; `beta` is a child branch (separate data, instant to create). Compute
11
11
  **autosuspends and auto-resumes on the next connection**, so a Neon tool needs **no keepalive** —
12
- that's the whole reason Neon is preferred over Supabase, which pauses for 7 days and needs a manual
13
- unpause. Choose `supabase` only when you need bundled auth + storage + realtime together.
12
+ that's the whole reason Neon is preferred over Supabase (which pauses for 7 days and needs a manual
13
+ unpause). Choose `supabase` only when you need bundled auth + storage + realtime together.
14
14
 
15
15
  ## Token — `NEON_API_KEY`
16
16
 
17
- Console Account settings → API keys. Account-level (configures the `neon` provider for every Neon
18
- tool, like `CLOUDFLARE_API_TOKEN`) — **not** per-tool. `greenlight add` verifies it against
19
- `/api/v2/projects` (HTTP 200). There is **no per-tool secret**: the role/password/connection strings
20
- are module OUTPUTS, not inputs.
17
+ Creation + verify live in
18
+ [tokens-reference.md](https://github.com/RTrentJones/greenlight/blob/main/docs/tokens-reference.md).
19
+ Account-level (configures the `neon` provider for every Neon tool, like `CLOUDFLARE_API_TOKEN`)
20
+ **not** per-tool, and there is **no per-tool secret**: the role/password/connection strings are
21
+ module OUTPUTS, not inputs.
21
22
 
22
23
  ## Terraform module — `infra/modules/neon`
23
24
 
@@ -29,37 +30,32 @@ which is ephemeral/per-PR — created by CI, not Terraform). Outputs two per-env
29
30
  The emitted `<name>.tf` wires `database_url["prod"]`/`["beta"]` into the Vercel env per target, so
30
31
  prod and beta hit **different branches**. Pin the provider `kislerdm/neon ~> 0.13`.
31
32
 
32
- ## No keepalive
33
-
34
- Do **not** add a Neon tool to `module.keepalive.targets_json`. Neon resumes on connect — a request
35
- just wakes it. (`doctor` does not flag `data: neon` for keepalive; that exemption is intentional.)
36
-
37
33
  ## Schema as code / migrations
38
34
 
39
- **Greenlight does NOT run migrations — by design.** The split:
40
- - **Schema** lives in the tool (an ORM Drizzle/Prisma or plain `.sql` migrations).
41
- - **Branch-per-env**: the TF module owns stable `prod`/`beta`; the **native Neon↔Vercel integration**
42
- owns ephemeral per-PR preview branches (+ auto-injects `DATABASE_URL`). Don't put ephemeral branches
43
- in Terraform.
44
- - **Execution**: the app's own build runs its migrate (`drizzle-kit migrate` / `prisma migrate deploy`)
45
- against the wired **`DIRECT_URL`** — prod build → prod branch, preview build → preview branch. A
46
- failed migrate fails the build = a natural gate.
47
- - **Greenlight's role**: the **dangerous-SQL gate**. Run `greenlight migrations scan` (no `<dir>` →
48
- it auto-detects `supabase/migrations | migrations | drizzle/migrations | …`) in CI before the migrate.
49
-
50
- See [docs/migrations.md](../../../docs/migrations.md).
35
+ **Greenlight does NOT run migrations — by design.** Schema lives in the tool (Drizzle/Prisma or
36
+ plain `.sql`); the app's own build runs its migrate against the wired **`DIRECT_URL`** (prod build →
37
+ prod branch, preview build preview branch; a failed migrate fails the build = a natural gate). The
38
+ **native Neon↔Vercel integration** owns ephemeral per-PR preview branches (don't put those in
39
+ Terraform). Greenlight's only role is the **dangerous-SQL gate**: run `greenlight migrations scan`
40
+ (auto-detects `supabase/migrations | migrations | drizzle/migrations | …`) in CI before the migrate.
41
+ See [migrations.md](https://github.com/RTrentJones/greenlight/blob/main/docs/migrations.md).
51
42
 
52
43
  ## Sharing one DB + multi-account
53
-
54
- - **One DB, many services**: a second tool sets `dataShareWith: '<owner>'` (or `add … --share <owner>`)
44
+ - **One DB, many services:** a second tool sets `dataShareWith: '<owner>'` (or `add … --share <owner>`)
55
45
  — it creates no project and wires the owner's connection strings.
56
- - **A second Neon account**: `tokenOverrides: { NEON_API_KEY: 'NEON_API_KEY_X' }` → an aliased `neon`
57
- provider authenticates that account. (A sharer can't also override — it uses the owner's account.)
46
+ - **A second Neon account:** `tokenOverrides: { NEON_API_KEY: 'NEON_API_KEY_X' }` → an aliased `neon`
47
+ provider authenticates that account. (A sharer can't also override.)
58
48
 
59
49
  ## MCP
60
-
61
50
  `.mcp.json` wires `neon` (hosted) with `Authorization: Bearer ${NEON_API_KEY}`. Run `/mcp` to auth.
62
51
 
63
- ## Rule
64
- The **blog must never use a database** that can pause — but Neon's auto-resume makes it safe for
65
- *tools*; still, the apex blog stays `data: none` (D1/KV/external only). Neon is per-tool Postgres.
52
+ ## Gotchas
53
+ - **Free-tier `history_retention_seconds` cap (21600).** Neon's free plan caps history retention at
54
+ **21600s (6h)** the module must not request more or the apply 400s. Point-in-time restore is
55
+ bounded to that window on free.
56
+ - **`pooler_enabled` is a non-issue.** Both connection strings are module outputs regardless — wire
57
+ `database_url` (pooled) to the app and `direct_url` to migrations; there's no pooler flag to toggle.
58
+ - **No keepalive.** Don't add a Neon tool to `module.keepalive.targets_json` — it resumes on connect,
59
+ and `doctor` intentionally does not flag `data: neon` for keepalive.
60
+ - **The blog stays `data: none`.** Neon's auto-resume makes it safe for *tools*, but the apex blog
61
+ uses D1/KV/external only (it must never depend on a store that can pause or error).
@@ -1,74 +1,63 @@
1
1
  ---
2
2
  name: provider-oci
3
- description: How Oracle Cloud (OCI) works in a Greenlight setup — the `target: oci` runtime for stateful MCP servers (BAMCP) on a free-tier Ampere A1 Container Instance, the provider-agnostic build-via-GitHub→GHCR model, Greenlight-owned compute + tunnel Terraform, the OCI token CLI, deploy = restart, and staying on the free tier (no PAYG; recover-on-alert). Use when wiring/debugging an oci-target tool.
3
+ description: Oracle Cloud (OCI) in a Greenlight setup — the `target: oci` runtime for stateful MCP servers on a free-tier Ampere A1 Container Instance (build-via-GitHub→GHCR; Greenlight-owned compute + tunnel). Use when wiring or debugging an oci-target tool.
4
4
  ---
5
5
 
6
6
  # provider-oci
7
7
 
8
8
  OCI is the `target: oci` runtime for **stateful** services that don't fit serverless — the
9
- canonical case is **BAMCP** (a stateful MCP server). The split: **the tool is provider-agnostic
10
- and just builds a container via GitHub; Greenlight owns the OCI infra** (compute + tunnel + DNS),
9
+ canonical case is **BAMCP** (a stateful MCP server). The split: **the tool is provider-agnostic and
10
+ just builds a container via GitHub; Greenlight owns the OCI infra** (compute + tunnel + DNS),
11
11
  configured in the wrapper.
12
12
 
13
13
  ## Free tier — A1 Container Instance + GHCR
14
14
 
15
- The Always-Free path is an **OCI Container Instance** on **Ampere A1** (the A1 allotment 2 OCPU
16
- / 12 GB as of 2026-06-15 — is shared across VM / Bare-Metal / Container-Instances). No VM to
17
- provision, no cloud-init, no SSH. The image comes from **GHCR** (free); **OCI's own registry
18
- (OCIR) is paid** that was the trap in the old BAMCP pipeline. The container instance runs the
19
- tool container + a **cloudflared sidecar** (shared netns localhost), exposed at `<name>.<domain>`.
20
-
21
- ## Provider-agnostic tool → GHCR
22
-
23
- The tool repo (BAMCP) has ONE job: a GitHub Actions workflow that **builds + pushes the container
24
- to GHCR**. No OCI, no SSH, no deploy logic — portable to any provider's infra.
15
+ The Always-Free path is an **OCI Container Instance** on **Ampere A1** (2 OCPU / 12 GB, shared
16
+ across VM / Bare-Metal / Container-Instances). No VM, no cloud-init, no SSH. The image comes from
17
+ **GHCR** (free); **OCI's own registry (OCIR) is paid** — that was the old-pipeline trap. The
18
+ instance runs the tool container + a **cloudflared sidecar** (shared netns localhost), exposed at
19
+ `<name>.<domain>`. The tool repo's only job: a GitHub Actions workflow that builds + pushes to GHCR.
25
20
 
26
21
  ## Greenlight OCI infra (Terraform, in the wrapper)
27
22
 
28
23
  `greenlight add/adopt` emits, per oci tool:
29
- - **`oci-container-instance`** module — the container instance (tool image from GHCR + cloudflared
30
- sidecar with the tunnel token), `CI.Standard.A1.Flex` within the free allotment, restart ALWAYS.
31
- - **`tunnel`** module — cloudflared tunnel + ingress `<name>.<domain> → http://localhost:8000` + token.
32
- - **`tool`** DNS module — CNAME → the tunnel.
33
- The `oci` provider (auth below) is added to `infra/main.tf`.
34
-
35
- ## OCI token CLI
36
-
37
- `greenlight secrets gather <tool> --repo <o/r>` pushes the OCI creds straight to GitHub secrets
38
- (hidden prompts, no disk/logs). **The only manual OCI inputs are the API-key auth values**
39
- `TF_VAR_OCI_TENANCY_OCID`, `TF_VAR_OCI_USER_OCID`, `TF_VAR_OCI_FINGERPRINT`, `TF_VAR_OCI_PRIVATE_KEY`
40
- (PEM), `TF_VAR_OCI_REGION`. `TF_VAR_OCI_COMPARTMENT_ID` is **optional** (blank the tenancy/root
41
- compartment). Auth is API-key request signing — no bearer, so no fetch-verify. The container
42
- instance OCID is **not** a manual input the deploy workflow resolves it at deploy time from OCI
43
- by the instance's display name (= the tool name), so it's abstracted from the developer.
44
-
45
- **The VCN, subnet, and availability domain are NOT manual** they're Terraform: the `oci-network`
46
- module creates the VCN + a public (egress-only) subnet, and the container-instance module looks the
47
- AD up via an `oci_identity_availability_domains` data source. So the bootstrap is just "create one
48
- API key" — Terraform can't create the credential it needs to authenticate, but it owns everything
49
- after that. (Out-of-A1-capacity in one AD? set `availability_domain` on the instance module to pin
50
- another — the only time you touch it.)
51
-
52
- **Shortcut — feed the API-key config preview directly.** After *Add API key*, OCI shows a
53
- "Configuration file preview" (the `[DEFAULT]` block) and you download the `.pem`. Pass both:
54
- `greenlight secrets gather <tool> --repo <o/r> --oci-config ~/path/config [--oci-key ~/path/key.pem]`
55
- — it auto-fills the 5 auth secrets (incl. the multi-line PEM, read from the file so it's never
56
- pasted) and only prompts for the 3 placement values + the option-B deploy PATs.
24
+ - **`oci-container-instance`** — the container instance (tool image from GHCR + cloudflared sidecar
25
+ with the tunnel token), `CI.Standard.A1.Flex` within the free allotment, restart ALWAYS.
26
+ - **`tunnel`** — cloudflared tunnel + ingress `<name>.<domain> → http://localhost:8000` + token.
27
+ - **`tool`** DNS — CNAME → the tunnel.
28
+ - The `oci-network` module owns the VCN + public subnet; the instance module looks the AD up via a
29
+ data source. The `oci` provider is added to `infra/main.tf`.
30
+
31
+ ## Token — OCI API-key auth
32
+
33
+ Auth values + the gather flow live in
34
+ [tokens-reference.md](https://github.com/RTrentJones/greenlight/blob/main/docs/tokens-reference.md).
35
+ The **only manual OCI inputs are the API-key auth values** (`TF_VAR_OCI_TENANCY_OCID`,
36
+ `TF_VAR_OCI_USER_OCID`, `TF_VAR_OCI_FINGERPRINT`, `TF_VAR_OCI_PRIVATE_KEY` PEM, `TF_VAR_OCI_REGION`;
37
+ `TF_VAR_OCI_COMPARTMENT_ID` optional root). Auth is API-key request signing (no bearer no
38
+ fetch-verify). **Shortcut:** `greenlight secrets gather <tool> --repo <o/r> --oci-config ~/config
39
+ [--oci-key ~/key.pem]` auto-fills the 5 auth secrets from OCI's "Configuration file preview" + the
40
+ `.pem` (the multi-line key is read from the file, never pasted).
57
41
 
58
42
  ## Deploy = restart (re-pull)
59
43
 
60
- `greenlight deploy <tool>` (oci) just runs `oci container-instances container-instance restart
61
- --container-instance-id <OCID>` — the instance re-pulls the latest GHCR image. The tool's CI
62
- builds; an event trigger (the chosen deploy option) fires the restart. The adapter does NOT build.
63
-
64
- ## Idle-reclaim — stay free, recover on alert
65
-
66
- OCI Always-Free can reclaim idle compute. We **stay on the free tier** and accept that: the
67
- instance runs restart-policy ALWAYS, keepalive health-checks it, and if it's ever stopped/reclaimed
68
- the alert fires and a **re-apply / redeploy** restores it. **PAYG is NOT used** — it's an optional
69
- last resort (see `docs/oci-payg-runbook.md`) only if reclaim ever becomes a recurring problem.
44
+ `greenlight deploy <tool>` (oci) runs `oci container-instances container-instance restart
45
+ --container-instance-id <OCID>` — the instance re-pulls the latest GHCR image. The tool's CI builds;
46
+ the deploy event fires the restart. The adapter does **not** build. The instance OCID is **not** a
47
+ manual input — the deploy workflow resolves it from OCI by display name (= the tool name).
70
48
 
71
49
  ## Verify
72
50
  The tool is typically an **MCP server**: verify with `mode: mcp`, connect at `<name>.<domain>/mcp`
73
- (FastMCP's `streamable_http_app()` serves `/mcp` by default — the convention). If auth gates
74
- `initialize`, supply a token or use an `api`-mode 401 check. Keepalive health-checks `target: oci`.
51
+ (FastMCP's `streamable_http_app()` serves `/mcp` by default). If auth gates `initialize`, supply a
52
+ token or use an `api`-mode 401 check. Keepalive health-checks `target: oci`.
53
+
54
+ ## Gotchas
55
+ - **OCIR is paid — use GHCR.** Pulling from OCI's own registry bills; the free path is GHCR.
56
+ - **Idle-reclaim → stay free, recover on alert.** Always-Free can reclaim idle compute. We **stay on
57
+ the free tier**: restart-policy ALWAYS + keepalive health-check + alert → re-apply/redeploy
58
+ restores it. **PAYG is NOT used** (optional last resort; see `docs/oci-payg-runbook.md`). Never
59
+ imply the harness *prevents* reclaim — it only nags and recovers.
60
+ - **Out-of-A1-capacity in one AD** → set `availability_domain` on the instance module to pin another
61
+ AD (the only time you touch it).
62
+ - **Rotation.** The API signing key is the one long-lived credential — rotate it on a schedule (see
63
+ docs/security.md); nothing automated rotates it.
@@ -1,28 +1,29 @@
1
1
  ---
2
2
  name: provider-supabase
3
- description: How Supabase works in a Greenlight setup — the `data: supabase` store (Postgres + auth + storage + realtime), single-project-schema-per-env model, the pause trap solved by keepalive, Management API token, and the read-only Supabase MCP. Use when wiring a tool's database, debugging a paused project, or a Supabase apply.
3
+ description: Supabase in a Greenlight setup — the `data: supabase` store (Postgres + auth + storage + realtime), schema-per-env, the 7-day pause trap solved by keepalive. Use when wiring a tool's database, debugging a paused project, or a Supabase apply.
4
4
  ---
5
5
 
6
6
  # provider-supabase
7
7
 
8
8
  `data: supabase` is for tools that need bundled **auth + storage + realtime** together
9
- (HeistMind). One Supabase **project**, **schema-per-env** (beta/prod share the project —
10
- NOT project-per-env; branching is paid). The project is **imported** (not recreated):
11
- name/region are replace-forcing, so the module sets `ignore_changes` to protect the live DB.
9
+ (HeistMind). One Supabase **project**, **schema-per-env** (beta/prod share the project — NOT
10
+ project-per-env; branching is paid). The project is **imported**, not recreated: name/region are
11
+ replace-forcing, so the module sets `ignore_changes` to protect the live DB.
12
12
 
13
13
  ## Token — `SUPABASE_ACCESS_TOKEN`
14
14
 
15
- Dashboard Account Access Tokens (Management API). Push it straight to GitHub Actions with
16
- `greenlight secrets gather` (or `gh secret set`) — Greenlight keeps no local secret file.
17
- `greenlight add` verifies it against `/v1/projects` (HTTP 200). The DB password
18
- (`TF_VAR_supabase_database_password`) is only used if the project is recreated ignored on
19
- import, so `import-placeholder` is fine for an existing project.
15
+ Creation + verify live in
16
+ [tokens-reference.md](https://github.com/RTrentJones/greenlight/blob/main/docs/tokens-reference.md).
17
+ Management API PAT, **account-scoped** (it manages every project in the account — keep it tight).
18
+ Single store: GitHub Actions secrets. The DB password (`TF_VAR_supabase_database_password`) is only
19
+ used if the project is *recreated* — ignored on import, so `import-placeholder` is fine for an
20
+ existing project.
20
21
 
21
22
  ## Terraform module — `infra/modules/supabase`
22
23
 
23
- Single project, import-safe. Outputs `url`, `anon_key`, `service_role_key`, `project_ref` —
24
- feed these straight into the consumer (e.g. the Vercel env block). To re-apply from fresh
25
- state, import first: `terraform import module.<name>_supabase.supabase_project.this <ref>`.
24
+ Single project, import-safe. Outputs `url`, `anon_key`, `service_role_key`, `project_ref` — feed
25
+ these straight into the consumer (e.g. the Vercel env block). To re-apply from fresh state, import
26
+ first: `terraform import module.<name>_supabase.supabase_project.this <ref>`.
26
27
 
27
28
  ## Keepalive — non-negotiable
28
29
 
@@ -32,10 +33,14 @@ Supabase **pauses a free project after ~7 days idle** — this is what takes too
32
33
  The ping counts any HTTP response (even 401 on `/rest/v1/`) as alive.
33
34
 
34
35
  ## MCP
35
-
36
36
  `.mcp.json` wires `supabase` (hosted, **read-only**): needs `SUPABASE_ACCESS_TOKEN` +
37
37
  `SUPABASE_PROJECT_REF` in the env (Claude Code expands `${VAR}` in the url/header). Run `/mcp`.
38
38
 
39
- ## Rule
40
- The **blog must never use Supabase** for state (it must stay up unattended) — use D1/KV or
41
- external services. Supabase is per-tool, only when the bundled features are needed together.
39
+ ## Gotchas
40
+ - **The blog must never use Supabase** for state (it must stay up unattended) — use D1/KV or
41
+ external services. Supabase is per-tool, only when the bundled features are needed together.
42
+ - **Migrations gate.** A supabase tool that owns `supabase/migrations` must run `greenlight
43
+ migrations scan` in the CI that applies them (before `supabase db push`) — `doctor` flags a
44
+ migrations dir whose workflows don't.
45
+ - **Forgetting keepalive** is the #1 silent-death cause for a supabase tool — `doctor` does not yet
46
+ assert it, so wire `targets_json` when you add the tool.