Skip to content

fix: the scheduled sync runs in ops, not production - #189

Merged
danjamk merged 2 commits into
mainfrom
feature/186-ops-environment
Aug 9, 2026
Merged

fix: the scheduled sync runs in ops, not production#189
danjamk merged 2 commits into
mainfrom
feature/186-ops-environment

Conversation

@danjamk

@danjamk danjamk commented Aug 9, 2026

Copy link
Copy Markdown
Owner

fix: the scheduled sync runs in ops, not production

Closes: #186

Overview

The first dispatch of the scheduled sync prompted for approval, because production carries a
required-reviewer gate. That gate is right for deploys — deploy-prod.yml says "you click Run when
you mean it"
and means it — and fatal for a job whose entire value is running unattended. A 03:17
run would sit waiting for an approval nobody is awake to give, every night.

Summary of Changes

A separate ops environment, which must have no reviewer gate.

Splitting beats loosening production, because this job needs a strictly narrower credential
than a deploy. It reads counts. It does not deploy, destroy, or touch KV:

Secret production ops
CLOUDFLARE_API_TOKEN (broad, provisioning) yes no
CLOUDFLARE_ANALYTICS_TOKEN (Account Analytics · Read only) no yes
CF_RUNTIME_TOKEN yes no
PAGEVAULT_API_TOKEN yes yes
PAGEVAULT_PROD_CONFIG yes yes
Slack webhooks no optional

That is ADR-002's blast-radius argument about the Worker, pointed at CI: an unattended nightly job
should hold the least credential that works. Today it holds one that can deploy and destroy
infrastructure, to read some counts.

The analytics token is a different secret NAME, not the same name in a different environment.
Same-name-different-scope is how the wrong credential ends up in the wrong job, and it reads as
interchangeable in review when it is not. The workflow maps it to the CLOUDFLARE_API_TOKEN variable
the CLI expects, explicitly and with the reason stated at the mapping.

Testing

make check-docs ✓
YAML parses; environment resolves to `ops`, secret mapping confirmed

Not covered, and it cannot be from here: whether a scheduled run fires unattended. A manual
dispatch proves the secrets resolve; only a real 03:17 run proves the gate is gone. That is the one
acceptance criterion in #186 that needs a night to pass.

Deployment Instructions

Before this can run, create the ops environment — Settings → Environments → New environment:

  1. Name it ops. Do not add a required reviewer.
  2. Add: PAGEVAULT_PROD_CONFIG and PAGEVAULT_API_TOKEN (same values as production),
    SLACK_HEARTBEAT_WEBHOOK, SLACK_ALERT_WEBHOOK.
  3. Add CLOUDFLARE_ANALYTICS_TOKEN — a new token, My Profile → API Tokens → Create Token →
    Custom, with exactly Account · Account Analytics · Read on the production account. Not the
    deploy token.
  4. Dispatch the workflow by hand once. It should run with no approval prompt.

production and deploy-prod.yml are untouched.

Next Steps

danjamk added 2 commits August 9, 2026 06:49
`production` carries a required-reviewer gate. That is right for deploys —
deploy-prod.yml says you click Run when you mean it — and fatal for a job
whose entire value is running unattended. The first dispatch prompted for
approval, and a 03:17 scheduled run would have done the same with nobody
awake to answer it.

Splitting beats loosening `production`, because this job needs a strictly
narrower credential than a deploy. It reads counts. It does not deploy,
destroy, or touch KV, so it gets a Cloudflare token scoped to Account
Analytics Read and nothing else. That is ADR-002's blast-radius argument
about the Worker, pointed at CI: an unattended nightly job should hold the
least credential that works.

The analytics token is a different secret NAME, not the same name in a
different environment. Same-name-different-scope is how the wrong
credential ends up in the wrong job, and it reads as interchangeable in
review when it is not.
@danjamk
danjamk merged commit 2c646c7 into main Aug 9, 2026
2 checks passed
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.

The scheduled sync needs an environment without a reviewer gate

1 participant