Skip to content

Record the project and hostname decisions (D2, D3) and the runbook for the hostname (C2) and the move (C5) - #21

Merged
anth-volk merged 32 commits into
mainfrom
claude/project-and-domain-memo
Oct 27, 2026
Merged

anth-volk merged 32 commits into
mainfrom
claude/project-and-domain-memo

Conversation

@anth-volk

@anth-volk anth-volk commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #17

Summary

Docs only. The memo now records decisions D2 and D3 and the choices that follow from them, made by Anthony on 2026-10-27, and the runbook is rewritten for the chosen order: the hostname first (C2), then the move behind it (C5). Nothing in Google Cloud, DNS, GitHub settings or Vercel was created or changed.

No concrete values. Neither document names a hostname, domain, IP address, project ID or number, organization or billing account ID, Cloud SQL instance, database or user name, connection name, service-account email or secret value. Each uses a named placeholder, such as <production hostname> or <new project ID>, and says where the value is kept (a named GitHub variable or secret, a value read from the current project with gcloud, or "recorded by the operator").

The decisions (2026-10-27)

Memo choice Decision
D2, the project B: a dedicated project, moved in one database copy per environment. Same organization and billing account as the current project. One owner.
D3, the address C: a global external Application Load Balancer with a Google-managed certificate from Certificate Manager. One load balancer and one hostname per environment, both under a PolicyEngine-owned domain.
DNS Managed outside Google Cloud; Anthony adds the records by hand. Per environment: an A record for the load balancer's static IP, and the Certificate Manager DNS-authorization CNAME.
Order Hostname first. Each environment's load balancer is built in the new project from the start, and sends requests across projects to today's service through a backend service and serverless network endpoint group left in the current project. Clients switch to the hostname; each move is then a URL map change.
Windows No scheduled maintenance windows. Each environment's database copy still puts its service in maintenance mode while it runs.
Staging data Copied with a dump and restore, as the rehearsal.
The dashboard's Google identity Stays in the current project, with read access granted to the new project's read secrets; removed when the dashboard switches to Vercel OIDC tokens (C8).
microcosm-runs Its address is unchanged until the emitter's direct upload (O7) and microcosm-runs' upload API (O8) are removed.
The old address Maintenance mode after production's copy. Deleted once no build uses emitter 0.2.1 and it has had no requests for seven days.

Plan codes follow the 2026-10-27 renumbering: C2 the hostname, C3 the emitter's default, C4 builds running that release, C5 the move, C6 read API v2, C7 ES256 signing, C8 the dashboard's Vercel OIDC token.

docs/project-and-domain-decision.md

  • Status is now "decided", with a table of the decisions, what follows from them (C2 to C5, then S1 and S2, intake, C6 and C7), and why: the same organization is what lets the new project's URL map use the current project's backend service; a serverless endpoint group must be in its Cloud Run service's project; the certificate is issued before any client uses the hostname, and the DNS records never change during the move.
  • The options tables stay as the record of what was weighed, with the chosen options marked and the D2 B row corrected for the chosen design (the dashboard's reads recover at the URL map change, with no dashboard deployment).
  • The consumer sections say when each setting changes: the dashboard's MICROCOSM_TELEMETRY_COLLECTOR_URL once, in C2; MICROCOSM_TELEMETRY_READ_SECRET_VERSION, TELEMETRY_GCP_PROJECT_ID and TELEMETRY_READ_SECRET_NAME after each copy; the emitter's constants in C3.
  • "Order and timing" lists the five stages in order, and the constraints: telemetry-service.yml is never run after the move, Accept artifact submissions into private storage and run worker jobs #12 merged with intake off, and the default privileges are set again on each new instance.

docs/project-move-runbook.md: what changed

  • Sections 1 to 4: the access table drops intake, scheduler and monitoring roles and adds the load-balancer roles in the current project. The organization and billing account are read from the current project, so neither is typed. The "agree the windows" step is gone.
  • Section 5, C2 (new): per environment, a serverless endpoint group and backend service for today's service in the current project, with roles/compute.loadBalancerServiceUser granted on that backend service to the operator; then the static IP, per-project DNS authorization, certificate, certificate map, URL map (default service: the current project's backend service), HTTPS proxy and forwarding rule in the new project. Step 5.3 lists the four DNS records Anthony adds (per environment, the authorization CNAME and the A record) and how to print their values. Then the hostname checks, and the dashboard's switch to the hostname.
  • Section 6: what C3 and C4 must achieve before production's copy, and how to check it.
  • Section 7, preparing each environment: only the migration and runtime users are recreated (the third user was dropped), the admin user is a placeholder, and the new service's backend service is created in advance. The dashboard's Vercel identities are granted read access on the new read secrets. S1, S2, intake, C6 and C7 must not merge before production's copy.
  • Section 8, each copy: maintenance mode checked through the hostname; export and import; step 8.4 sets the migration user's default privileges (ALTER DEFAULT PRIVILEGES FOR ROLE <migration user> IN SCHEMA public GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO <runtime user>), with the admin user granting itself INHERIT on the migration user first and revoking it after, before any migration runs, and checks \ddp; the GitHub environment switch; the deployment; then step 8.8 changes the URL map to the new backend service. Intake pausing and the intake-bucket handling are gone, since intake is off.
  • Section 9: the dashboard moves to the new project's read secret in an ordinary deployment after each copy.
  • Section 11, rollback: before the URL map change, route the old service back; after it, the reverse URL map change plus the reverse data copy (including the default privileges). Never run telemetry-service.yml after the move.
  • Section 12 (new): after the move, in order: S1 and S2, switching intake on, C6, C7, each created once in the new project.
  • Section 13: retirement waits for no build on emitter 0.2.1 and seven days with no requests to the old service, and now removes the current project's backend services, endpoint groups and the role grant.
  • Sections 14 to 16: the settings table, the unverified list (now led by the cross-project backend service with a Cloud Run backend, which staging's load balancer tests first) and the code changes, updated to match. Domain mapping and the "both together" order are removed throughout.

Values check

Before pushing, I searched all text the PR adds, in its diff against main, for hostnames, domains, IP addresses, long numbers, emails, project IDs, instance, connection and user names. None of PolicyEngine's remain. What matches are file names, IAM role and permission names, and Google's and GitHub's public endpoint names that the commands need (*.googleapis.com, the GitHub OIDC issuer, the Artifact Registry and service-account address formats built from placeholders, and Google's public sample image).

What a merge does

Merging releases 0.6.0 (an added fragment). With deployment enabled, that release redeploys staging and production with unchanged runtime code, including the usual short maintenance phase.

Testing

Docs only, so there is no failing-test-first step. tests/test_deployment_workflow.py (its vocabulary test scans every Markdown file) and tests/test_packaging.py: 18 passed. The full suite: 361 passed, 51 skipped. tools/check-quality.sh (Ruff check and format, script syntax) passes, and tools/bump-version.py --check gives 0.5.0 to 0.6.0. main was merged in with a merge commit.

Earlier review rounds (2026-10-20 to 2026-10-22) are in the commit history; their fixes are kept where they still apply to the chosen design.

🤖 Generated with Claude Code

docs/project-and-domain-decision.md sets out decisions D2 (a dedicated
Google Cloud project) and D3 (a custom hostname): the options, their costs
and risks, what changes for the emitter, the calibration dashboard and
microcosm-runs, and the choices to make. Neither decision is made.

docs/project-move-runbook.md is plan item C2: creating the project, the
Artifact Registry store, the Cloud Run service and migration job, Cloud SQL
with an export and import inside a maintenance window, the Secret Manager
secrets (including S1's Hugging Face read token), Workload Identity
Federation for this repo, the hostname and DNS, the GitHub environment and
dashboard settings, verification and rollback. It applies only after the
deploy switch-over (C1) is verified.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
anth-volk and others added 27 commits October 20, 2026 22:47
From staging's window on, staging builds the image in the new project
and production deploys that same image, still in the old project, so the
old deployment account and the old project's Cloud Run service agent
need Artifact Registry Reader on the new project. Without it, production's
deployment fails in staging's window and on every merge until production
moves. The runbook now grants the role before staging's window and removes
it when the old resources are retired.

Production's window had no merge to deploy it: the C2 code PR merges in
staging's window. The runbook now names a second PR for production's
window.

The stated downtime left out the release pipeline, which sets most of a
window's length. The memo and the runbook now give the measured range.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Production's old instance lists a third user, telemetry_app, besides the
migration and runtime users. Google's import guide says every user that
owns objects or holds privileges in the dumped database must exist on the
target, so the runbook now creates every user the record lists.

Re-creating the database after the timing rehearsal drops the
database-level privileges granted in step 5.2; the runbook now grants
them again, and the copy check compares the database listing too.

The command that shows the database URL's form now hides everything up to
the last "@", so a password containing one isn't printed in part.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The runbook listed only part of what #12, #16 and #19 need in the new
project, so following it after any of them merges would stop a
deployment or lose writes:

- #12's worker job and recovery job write to the database while the
  service is in maintenance mode, so the export could miss their writes.
  The runbook now pauses the queue and the recovery schedule before the
  export, resumes them on rollback, and lists the recovery job, its
  schedule and the Cloud Scheduler API.
- #16's signing key is a fifth secret that the deployment checks and
  mounts. The runbook now creates it, grants it like the other four and
  lists it with the settings.
- #19's daily check wasn't mentioned. The runbook now lists its job,
  schedule, alert policies, permissions and three production variables,
  one of which names the project.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The dashboard's identity-provider setting is one Vercel entry for
  Production and Preview (since 2026-10-07), not Production only, and
  Preview deployments can read staging's collector.
- Only Cloud Run domain mapping needs Search Console verification;
  Certificate Manager's DNS authorization needs only a DNS record.
- Events are not always kept through a window: a machine discarded when
  its build ends, such as a Modal container, loses the events not sent by
  then.
- The emitter's Orrery uploads do get their credential from the
  collector's token exchange; they already wait today, so the window
  changes nothing for them.
- #23 adds the emitter's Hugging Face module; #27 makes it send
  submissions to the collector.
- A new emitter release reaches builds once it is installed on the
  machine or in the build image.
- The backup check needs a format that shows the description.
- Whether a re-run of the dashboard's deployment reads a changed GitHub
  variable is unchecked, so the documents now point to a merge.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Step 6.2 now pauses #12's queue and recovery schedule together and
lists unfinished executions of both the worker and the recovery job.
It gives the possible wait (three attempts of up to 1 hour each), how
to cancel a worker execution and what recovery then does with its job,
and why the queue must never be paused while recovery runs.

The intake bucket stays in the old project. Each accepted submission
records its files' Cloud Storage generation numbers, and the worker
reads every file at that generation; a copy into a new bucket gets new
generation numbers, so the copy step is removed. Uploads still in
flight need nothing, because the bucket doesn't change.

Step 6.5 and section 12 now point the queue, worker job, signing
account and invoker account settings at the new project. A new step
6.7 creates the recovery schedule only after the deployment.

Documentation only, so there is no failing test to add first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A new part of step 6.7 creates #19's schedule in the new project once
production's deployment has created the check job, then runs the job
once by hand: the README says the absence policy can report only after
a run that follows its creation. It then disables the old project's two
alert policies and pauses its schedule, in that order, before section 11
removes the old check job or revokes S1's old token, so the old project
sends no false alerts. Rollback reverses this, and section 9 checks it.

Step 5.3's #19 bullet now names what the deployment needs: S1's secret,
the worker account, and the deployment account's viewer and
service-account-user roles. The channel and the policies don't affect it.

Documentation only, so there is no failing test to add first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Step 3.2 now reads the access policy of every secret the environment
uses, including #16's signing key and S1's token if merged, and records
#12's queue, worker and recovery jobs, schedule, bucket roles and
invoker account, and #19's check job, schedule, alert policies and
notification channel.

Section 11 now retires those resources: every recorded secret, the old
worker account, #12's queue, jobs, schedule and accounts (but not the
intake bucket, which stays in use), and #19's job, schedule, account,
policies and channel. S1's old token is revoked only after step 6.7 has
paused the old check that uses it.

Documentation only, so there is no failing test to add first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Production redeploys in staging's window and on every merge until its
own window, possibly with migrations, so the revision step 3.2 records
can be older than the database schema by the time of a rollback. Step
6.1 now records the revision the old service serves just before step
6.2, and rollback routes traffic to that one.

Rollback also says which GitHub values it restores: only those step 6.5
changed in that window. In production's window that is production's
values and TELEMETRY_PRODUCTION_CLOUD_SQL_CONNECTION in staging's
environment; staging stays on the new project. The rollback after the
emitter release notes when the recorded revision is too old for the
imported schema.

Documentation only, so there is no failing test to add first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Cloud SQL's "About PostgreSQL users and roles" and "Create and manage
users" pages say a user created with gcloud sql users create is a member
of cloudsqlsuperuser with CREATEROLE, CREATEDB and LOGIN. An old user
created in SQL may have less, and an export carries no roles.

Step 3.2 now records every role's attributes and memberships with one
SQL query, which works in any client (psql 16 and later no longer show
memberships in \du). Step 5.2 compares each new user with that record
and removes what it doesn't show: the membership with gcloud sql users
assign-roles --revoke-existing-roles, the attributes with ALTER ROLE as
postgres. Step 6.4 compares the query's rows on both instances. The
extra users are now a list set from the record, so pasting the block
for staging no longer creates telemetry_app.

Documentation only, so there is no failing test to add first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#16 adds the signing-key secret to both environments' deployments, so
without it that environment's deployment stops, not only production's.

The quiet-day figure counted merge to tag only (25 minutes on
2026-10-15). The two deploy jobs add about 10 minutes, and the export
and import come on top, so the runbook's section 6 and the memo's D2 B
now say about 40 minutes.

Documentation only, so there is no failing test to add first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The memo's D2 B sentence put the quiet-day figure on the redeploy
alone; the 40 minutes includes the export and import, so it now says
"in all". Two paragraphs added in this round are rewrapped, and one
sentence in step 5.2 reads in order.

Documentation only, so there is no failing test to add first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Section 1 asked only for read access and Cloud SQL admin in the old
project, but the steps also read secret values, change the intake
bucket's IAM policy, update the old Cloud Run service, pause and resume
Cloud Tasks and Cloud Scheduler, change alert policies and delete
resources there. A table now names the role for each step.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
If #12's intake is switched on before the move, its bucket stays in
policyengine-api, so its storage cost, data-access logs and
administration stay there, and shutting that project down or removing
its billing would make every submission's files unreadable. The memo's
D2 B row, "What a dedicated project gives" and "Order and timing" now
say so, recommend switching intake on only after the move, and record
that intake is off today (read-only check: the Cloud Tasks and Cloud
Scheduler APIs aren't enabled, and no intake bucket exists).

In the runbook, step 3.2 also records the old accounts' project-level
roles, because #12's unwritten deployment wiring may grant storage
access on the project. Step 5.3 names the permissions the code needs on
the bucket (create and list for the runtime account, get for the
worker's) and grants them on the bucket. Moving the bucket later is a
one-off data change that rewrites the recorded generation numbers, not
necessarily a code change. Section 9 checks the bucket's policy and one
test submission, and section 11 says the old project must stay active
while the collector reads the files.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Step 5.2 still set other_database_users=(telemetry_app) whatever the
environment, so pasting the block for staging created a user staging
doesn't have, and step 6.4's roles comparison would then stop the
window. Step 3.2 now records each environment's users as listed on
2026-10-20 (staging: postgres, telemetry_migrator, telemetry_runtime;
production: the same plus telemetry_app) and sets OTHER_DATABASE_USERS
per environment, and step 5.2 creates that list, which is empty for
staging.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The roles comparison in step 6.4 would differ on the postgres row if
the old users were made in SQL by postgres: PostgreSQL 16 grants each
role a non-superuser creates back to its creator with ADMIN TRUE, SET
FALSE, INHERIT FALSE, and users made by gcloud sql users create don't
get that grant. Step 3.2's roles query now shows each membership's
admin, inherit and set options. Step 5.2 creates a user whose row lists
cloudsqlsuperuser with gcloud sql users create, and any other user in
SQL as the role that holds it as "(admin)", normally postgres, with
exactly the recorded attributes, so both rows match without removing
anything. Step 5.2 compares the roles query before the window, and
step 6.4 accepts only the differences step 5.2 names.

Whether postgres may ALTER ROLE a user Cloud SQL made is not documented:
PostgreSQL 16 requires ADMIN OPTION on the target, and Cloud SQL's pages
don't say whether postgres holds it. Step 5.2 now checks it once on
staging's new instance, with a throwaway user, before production relies
on it, and gives the fallback: create that user in SQL as postgres,
which then holds ADMIN OPTION, and accept the two resulting differences.
The local check of this behaviour on PostgreSQL 16.4, with a
non-superuser postgres, refused ALTER ROLE on a role it hadn't created.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On PostgreSQL 15 and later only the database's owner may create objects
in schema public unless granted CREATE there. If the old migration user
isn't a cloudsqlsuperuser member, it got that by owning the database or
public, or by a grant, and none of these exists on the new database
before the import. The dump's own grants on public come after its
tables and run as the importing user, so the import's first CREATE
TABLE would fail.

Step 5.2 now compares the new database's \l and \dn+ public with the
record before any import (step 5.5's rehearsal and step 6.3) and
applies what differs: privileges first, then owners. A Cloud SQL-made
owner makes itself the owner; for a role postgres made, postgres grants
itself SET on it for the change and revokes that afterwards, which
PostgreSQL 16 allows and which leaves the automatic grant in place
(checked on a local PostgreSQL 16.4 emulation). Step 5.5 repeats these
steps after re-creating the database, and step 6.4 also compares
\dn+ public. The example loop's comment now says a placeholder makes
each call fail rather than stopping the loop.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The first rollback row re-enabled the old project's alert policies but
didn't run the old check job, so after a rollback the old "no success
for 23 hours" policy couldn't report a stopped schedule: #19's README
says a metric-absence condition needs a successful run after the
policy is created or changed. The row now runs the old check job once
and waits for it, and pauses the new queue along with the new
schedules.

The "after the emitter release" row paused only the new project's
queue and recovery schedule. It now also pauses the new check schedule
and disables the new alert policies, and in the old project resumes
the queue, the recovery schedule and the check schedule, enables the
old alert policies and runs the old check job once.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Step 6.2 said every reprocess run is a recovery-job execution, so the
loop covers it. #12's README also allows running reprocess locally
through the Cloud SQL Auth Proxy, which writes to the database without
a Cloud Run execution. The step now says so and asks that nobody runs
it that way during the window.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Section 12's closing paragraph now lists the open questions plainly:
whether postgres may ALTER ROLE a user Cloud SQL made (step 5.2's
staging check settles it); who owns a database gcloud sql databases
create makes, and the old databases' owners and grants; whether either
project is inside a VPC Service Controls perimeter; whether
policyengine-api has a project lien; and #12's unwritten deployment
wiring.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The memo said the bucket "can't move" and that the files depend on
policyengine-api "for good", while the same text says a one-off change
could move them. It now says the files depend on that project until
such a change, not yet written, moves them. Section 12 no longer says
nothing has been run: some read-only commands have been. One sentence
in step 5.5 is rewrapped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The memo offers a hostname-first order, but the runbook had no step that
points the hostname at today's service, releases the emitter with it, and
waits for builds to leave the default address before the move; section 7
assumed the hostname was "already mapped", and section 8.2 placed the
emitter release after the move. Step 3.4 now does that phase against
policyengine-api, says why emitter 0.2.1 decides the wait, and section 11
retires the mapping or load balancer it created there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The runbook created the new project's Certificate Manager DNS authorization
with the default fixed record name, `_acme-challenge.HOSTNAME`. In the
hostname-first order the old project's authorization already holds that
name, and DNS allows one CNAME per name, so the new certificate couldn't be
issued before the A record moved, as the memo says it is. The command now
passes `--type=PER_PROJECT_RECORD`, whose record name carries a suffix for
the project, and the text says why, quoting Certificate Manager's page.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
For the hostname-first order with domain mapping, the runbook moved the
hostname by creating the new project's mapping with --force-override and
said this "moves the hostname at once". gcloud's help describes the flag as
mapping a domain "even if it is already mapped to another service", and
nothing says it reaches a mapping in another project. The runbook now
deletes the old project's mapping and then creates the new one, and the
rollback row does the reverse; section 11 no longer lists that mapping.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A read-only check of policyengine-api on 2026-10-22 found a fifth
production collector secret, `microcosm-telemetry-database-url`, made four
days before the runtime database URL secret. No GitHub variable names it and
no service or job mounts it, but production's runtime account may read it.
Step 3.2 recorded secrets only from the deployment's settings, so section 11
would have left it in policyengine-api. Step 3.2 now lists the old project's
secrets and notes unused ones, section 11 deletes them, and the memo says
what the 2026-10-22 check found.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Step 5.2 said to create each instance with the recorded settings and to
"keep" deletion protection on, but on 2026-10-22 production's instance had
deletion protection off, and its command always passed
--backup-start-time, which turns on automated backups that staging's
instance doesn't have. It now says deletion protection is on for both new
instances, and to use --no-backup where the record shows backups off.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The new project inherits its parent's organization policies. A policy such
as domain-restricted sharing would refuse the allUsers invoker binding that
--allow-unauthenticated adds, and the deployment's maintenance check would
then fail. Whether one applies couldn't be read: the Organization Policy API
isn't enabled in policyengine-api.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Second review pass over this round's fixes: the memo's hostname-first
paragraph had overlong lines and still said the D3 B mapping is
"re-created" rather than deleted and created again; the organization
policy note implied the new project shares policyengine-api's
organization, which choice 2 decides, and now names the checked invoker
binding.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
anth-volk added a commit that referenced this pull request Oct 27, 2026
The README's "Database access" said each environment sets the migration
user's default privileges before 0002_artifact_intake is applied. A
read-only check through each environment's migration job on 2026-10-27
found that staging and production both have them, so before a merge
there is only a \ddp check, and nothing to run.

It now also says when the statement applies again: when the database
users are recreated, as the project move in #21 does. A default-privileges
entry belongs to the role, not its name, so the new database needs a \ddp
check before a migration job runs there.

The intake checklist now lists all four privileges the default
privileges grant, not three.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
anth-volk and others added 3 commits October 27, 2026 18:16
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The memo now records Anthony's decisions: a dedicated project in the
same organization and billing account (D2 B), a global external load
balancer with one hostname per environment (D3 C), DNS records added by
hand outside Google Cloud, hostname first with a cross-project backend
service until each move, no scheduled windows, staging copied as the
rehearsal, the dashboard's identity kept in the current project, and
microcosm-runs left until O7 and O8. Plan codes follow the 2026-10-27
renumbering (C2 to C8). Concrete values, such as project IDs, domains and
database user names, are replaced by named placeholders.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
C2 builds each environment's load balancer in the new project, sending
requests to today's service through a backend service left in the
current project, and lists the four DNS records to add. C5 copies each
database with the service in maintenance mode, sets the migration user's
default privileges before any migration runs, deploys into the new
project and changes the URL map. Rollback is the reverse URL map change.
Intake, S1, S2, C6 and C7 are created once in the new project after the
move. The third database user, domain mapping and the "both together"
order are removed, and no concrete values remain.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@anth-volk anth-volk changed the title Document the project and hostname decision (D2, D3) and the move runbook (C2) Record the project and hostname decisions (D2, D3) and the runbook for the hostname (C2) and the move (C5) Oct 27, 2026
…vision

The memo says where the hostnames and the domain are kept, and states that
the copy recreates only the migration and runtime users. The runbook sets
the new project's number right after creating the project (step 4.2's
binding needs it), reads the operator's account from gcloud's configuration,
and in step 8.1 checks that the serving revision is the latest created one,
since step 8.2's update builds the maintenance revision from the latest
created revision's settings.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@anth-volk
anth-volk merged commit 357c824 into main Oct 27, 2026
4 checks passed
@anth-volk
anth-volk deleted the claude/project-and-domain-memo branch October 27, 2026 17:02
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.

Decide and plan the collector's own Google Cloud project and custom domain

1 participant