Part of the StackToIT package, published unchanged. A document of a proposal: nothing here is approved or applied.

Manual steps and what is not migrated

Generated by StackToIT from execution-plan.json. Nothing in this folder has been run: StackToIT reads and writes files and creates, changes and deletes nothing in any cloud. Every step is carried out by a person, by OpenTofu or by a script of this folder that a person runs. Paths are relative to the output folder.

Manual steps

The 64 manual steps of the plan in 24 work packages, in the order of the Work packages table of execution/README.md. Each package says who carries it out, what it touches, what it waits for, how far it is and when it is done; its steps follow it in full, folded. Steps that say the same thing on different resources are one entry that gives the text once, with {resource} for the resource, and lists each step by its name beside its resource; what every step of an entry comes after is said once, and so is the text a line of each step begins and ends with, with its field in braces for what each step says there.

WP-01 — Set up the identities and access (prepare)

  • Owner: Identity Architect
  • Steps: 4 (4 a person can start now)
  • Affected resources: google_cloud_run_v2_job.migrate, google_cloud_run_v2_service.server, google_service_account.automation, google_service_account.server
  • State: a person can start it now
  • Done when: each step meets the condition stated with it below (4 different conditions).

<details> <summary>The 4 manual steps of WP-01</summary>

The step "Decide how google_cloud_run_v2_job.migrate gets its application identity on…" (MAN/identity/facts:google_cloud_run_v2_job.migrate:google_cloud_run_v2_job.migrate.template.0.template.0.service_account-8dc3e1)
  • Phase: Prepare
  • What: Decide how google_cloud_run_v2_job.migrate gets its application identity on STACKIT (a service account of its own, workload federation): the draft does not write it
  • Why: the workload is a container task, not a server an account is attached to (template[0].template[0].service_account of google_cloud_run_v2_job.migrate names google_service_account.automation). no service account is attached to a workload of this source: an account attaches, but it can do nothing without its roles, and writing a role assignment from a draft has not been shown on STACKIT (it needs a right the key that tried it did not hold), so the draft grants no role and attaches no account
  • Who: Identity Architect
  • When: before pilot
  • Needs: No input is named for this step
  • Done when: the identity fact of google_cloud_run_v2_job.migrate (template[0].template[0].service_account of google_cloud_run_v2_job.migrate names google_service_account.automation) is decided on STACKIT and recorded, or recorded as not needed
  • Source: google_cloud_run_v2_job.migrate
  • State: executable: a person can start it now
  • Rollback (manual): the plan does not know how this step is undone: the person who carries it out records how
The step "Decide how google_cloud_run_v2_service.server gets its application identity on…" (MAN/identity/facts:google_cloud_run_v2_service.server:google_cloud_run_v2_service.server.template.0.service_account-5ccce9)
  • Phase: Prepare
  • What: Decide how google_cloud_run_v2_service.server gets its application identity on STACKIT (a service account of its own, workload federation): the draft does not write it
  • Why: the workload is a container service, not a server an account is attached to (template[0].service_account of google_cloud_run_v2_service.server names google_service_account.server). no service account is attached to a workload of this source: an account attaches, but it can do nothing without its roles, and writing a role assignment from a draft has not been shown on STACKIT (it needs a right the key that tried it did not hold), so the draft grants no role and attaches no account
  • Who: Identity Architect
  • When: before pilot
  • Needs: No input is named for this step
  • Done when: the identity fact of google_cloud_run_v2_service.server (template[0].service_account of google_cloud_run_v2_service.server names google_service_account.server) is decided on STACKIT and recorded, or recorded as not needed
  • Source: google_cloud_run_v2_service.server
  • State: executable: a person can start it now
  • Rollback (manual): the plan does not know how this step is undone: the person who carries it out records how
Alike steps on 2 resources (google_service_account) — the step "Decide who may use the STACKIT service account written for…" (MAN/identity/held:principal:{resource})
  • Phase: Prepare
  • What: Decide who may use the STACKIT service account written for {resource}, what it may do and which workloads run as it: the draft attaches it to nothing
  • Who: Identity Architect
  • When: before pilot
  • Needs: No input is named for this step
  • Done when: the people and the workloads that may use the service account written for {resource} are named, and the roles it holds are stated
  • State: executable: a person can start it now
  • Rollback (manual): the plan does not know how this step is undone: the person who carries it out records how

The steps, each by its name, with the resource that stands for {resource} above, its target objects, the steps it comes after besides those above and the lines that are its own (for a line above with a placeholder in braces, what stands for it):

  • The step "Decide who may use the STACKIT service account written for…" for google_service_account.automation (MAN/identity/held:principal:google_service_account.automation) — {resource} = google_service_account.automation - Why: google_service_account.automation is used by the workloads that run as it, by a reading that is KNOWN: google_cloud_run_v2_job.migrate (google_cloud_run_v2_job.migrate#template[0].template[0].service_account); the other places that refer to it: google_cloudbuild_trigger.init, google_project_iam_member.automation_permissions, google_secret_manager_secret_iam_binding.django_admin_password, google_secret_manager_secret_iam_binding.django_settings, google_storage_bucket_iam_member.automation. Its service account is written by the draft's converter of google_service_account, not by this plan, and the plan attaches it to nothing.
  • The step "Decide who may use the STACKIT service account written for…" for google_service_account.server (MAN/identity/held:principal:google_service_account.server) — {resource} = google_service_account.server - Why: google_service_account.server is used by the workloads that run as it, by a reading that is KNOWN: google_cloud_run_v2_service.server (google_cloud_run_v2_service.server#template[0].service_account); the other places that refer to it: google_project_iam_member.server_permissions, google_secret_manager_secret_iam_binding.django_settings, google_storage_bucket_iam_member.server. Its service account is written by the draft's converter of google_service_account, not by this plan, and the plan attaches it to nothing.

</details>

WP-02 — Decide and set the critical settings (security)

  • Owner: Identity Architect
  • Steps: 6 (6 a person can start now)
  • Affected resources: google_secret_manager_secret.django_admin_password, google_secret_manager_secret.django_settings, google_service_account.automation, google_service_account.client, google_service_account.init, google_service_account.server
  • State: a person can start it now
  • Done when: each step meets the condition stated with it below (2 different conditions).

<details> <summary>The 6 manual steps of WP-02</summary>

Alike steps on 2 resources (google_secret_manager_secret) — the setting secret-settings of {resource} (MAN/semantic/{resource}/secret-settings)
  • Phase: Security
  • What: Decide what the STACKIT target of {resource} (google_secret_manager_secret) does for its critical setting secret-settings, which the draft does not carry, and set it up by hand, or record that production does not need it
  • Why: The mapping rule for this kind of unit recognises the unit but only passes this setting to a person; it does not decide its value. The settings of the secret beside its value (secret-value): STACKIT replicates it itself (no replica locations or per-replica keys), tags and version states have no counterpart, and IAM members and policy map to Secrets Manager users (read-only or read-write for a whole instance) or to experimental role bindings on a secret group — REVIEW.
  • Who: Identity Architect
  • When: Timing to be scheduled by the owner
  • Done when: A person recreates the secret in the Secrets Manager instance, since its data is not written into the draft, and decides for each IAM member whether it gets a user of the whole instance, a role binding on a secret group, or a separate instance.
  • State: executable: a person can start it now
  • Risk: the assessment leaves the critical setting secret-settings of {resource} open (REVIEW: a person decides what the target does for it): the migration of {resource} is not complete until this step is done
  • Rollback (manual): the plan does not know how this step is undone: the person who carries it out records how

The steps, each by its name, with the resource that stands for {resource} above, its target objects, the steps it comes after besides those above and the lines that are its own (for a line above with a placeholder in braces, what stands for it):

  • The setting secret-settings of google_secret_manager_secret.django_admin_password (MAN/semantic/google_secret_manager_secret.django_admin_password/secret-settings) — {resource} = google_secret_manager_secret.django_admin_password - Needs: the answer to the decision register record DEC-IAM-GOOGLE-SECRET-MANAGER-SECRET-DJANGO-ADMIN-PASSWORD (decisions.json): what the STACKIT target does for the critical setting secret-settings, or the accepted difference
  • The setting secret-settings of google_secret_manager_secret.django_settings (MAN/semantic/google_secret_manager_secret.django_settings/secret-settings) — {resource} = google_secret_manager_secret.django_settings - Needs: the answer to the decision register record DEC-IAM-GOOGLE-SECRET-MANAGER-SECRET-DJANGO-SETTINGS (decisions.json): what the STACKIT target does for the critical setting secret-settings, or the accepted difference
Alike steps on 4 resources (google_service_account) — the review of the open settings of {resource} (MAN/semantic/{resource}/open-settings)
  • Phase: Security
  • What: Review the 2 open critical settings of {resource} (google_service_account) that the draft does not carry, and decide for each what the STACKIT target does: set it up by hand, or record that production does not need it (role-bindings, account-name)
  • Why: The assessment leaves 2 critical settings of {resource} open and the draft does not carry them: a person decides what the target does for each. Each setting states its own reason with its item.
  • Who: Identity Architect
  • Needs: the answer to each decision register record (decisions.json): what the STACKIT target does for the setting, or the accepted difference - role-bindings {needs}
  • Done when: every one of the 2 settings is closed: each is set up on its STACKIT target and checked, or the decision that production does not need it is recorded (the condition of each is stated with its item, and assurance/critical-semantics.json says what closes it)
  • Settings (2, the step is done when every one is closed; each step says below what is its own, and what every step says the same is written here once): - role-bindings (Identity Architect) - Why: The mapping rule for this kind of unit recognises the unit but only passes this setting to a person; it does not decide its value. The grants of the estate that name the account: a grant on a project, a folder or an organisation is written as a STACKIT role assignment of the account when its role and members are visible statically and it has no condition, and a grant that lets another account act as it is written as an Act-As assignment. A Google role is written as a STACKIT role only where the mapping shows the STACKIT role holds the same permissions or fewer on a project; every other role is an input that names the candidates, and a person confirms each. A binding or a policy is authoritative (it removes the members of its role that it does not list) and a role assignment only adds, which the draft says on each: members that hold the role outside the configuration keep it on STACKIT until a person removes them. - Done when: A person confirms the value against the target, or the knowledge base gains a rule that decides this setting. - account-name (Identity Architect) - Why: A check of the knowledge base (sa account id not visible) fired on this unit and leaves the setting to a person: the target does not settle it by itself (a finding of this unit describes it). The account ID becomes the stackit_service_account name (shortened to the STACKIT name rules; pinned provider) — TRANSFORMED; the e-mail address changes. An ID not visible statically (or a repeated account) is named NOT CARRIED — REVIEW (check sa-account-id-not-visible). - Done when: A person decides what this finding asks, and records the evidence or the decision. The finding says: The account ID is not visible statically; the STACKIT account is named after the source address. — a variable's default, not a fact.
  • State: executable: a person can start it now
  • Risk: the assessment leaves 2 critical settings of {resource} open (the draft does not carry them): the migration of {resource} is not complete until every one is closed; this step is the one step of them (assurance/critical-semantics.json says why each is open)
  • Rollback (manual): the plan does not know how this step is undone: the person who carries it out records how

The steps, each by its name, with the resource that stands for {resource} above, its target objects, the steps it comes after besides those above and the lines that are its own (for a line above with a placeholder in braces, what stands for it):

  • The review of the open settings of google_service_account.automation (MAN/semantic/google_service_account.automation/open-settings) — {resource} = google_service_account.automation - When: Timing to be scheduled by the owner - Needs ({needs}): (record DEC-IAM-GOOGLE-SERVICE-ACCOUNT-AUTOMATION, Identity Architect); account-name (record DEC-IAM-GOOGLE-SERVICE-ACCOUNT-AUTOMATION, Identity Architect) - Settings, the part of each that is this step's own: - role-bindings (decision register record DEC-IAM-GOOGLE-SERVICE-ACCOUNT-AUTOMATION) - account-name (decision register record DEC-IAM-GOOGLE-SERVICE-ACCOUNT-AUTOMATION)
  • The review of the open settings of google_service_account.client (MAN/semantic/google_service_account.client/open-settings) — {resource} = google_service_account.client - When: Timing to be scheduled by the owner - Needs ({needs}): (record DEC-IAM-GOOGLE-SERVICE-ACCOUNT-CLIENT, Identity Architect); account-name (record DEC-IAM-GOOGLE-SERVICE-ACCOUNT-CLIENT, Identity Architect) - Settings, the part of each that is this step's own: - role-bindings (decision register record DEC-IAM-GOOGLE-SERVICE-ACCOUNT-CLIENT) - account-name (decision register record DEC-IAM-GOOGLE-SERVICE-ACCOUNT-CLIENT)
  • The review of the open settings of google_service_account.init (MAN/semantic/google_service_account.init/open-settings) — {resource} = google_service_account.init - When: applies only if its count (var.init ? 1 : 0) holds when OpenTofu plans — present with the defaults of var.init (1 instance); a variable file (-var-file), -var or TF_VAR_init can change that - Needs ({needs}): (record DEC-IAM-GOOGLE-SERVICE-ACCOUNT-INIT, Identity Architect); account-name (record DEC-IAM-GOOGLE-SERVICE-ACCOUNT-INIT, Identity Architect) - Settings, the part of each that is this step's own: - role-bindings (decision register record DEC-IAM-GOOGLE-SERVICE-ACCOUNT-INIT) - When: applies only if its count (var.init ? 1 : 0) holds when OpenTofu plans — present with the defaults of var.init (1 instance); a variable file (-var-file), -var or TF_VAR_init can change that - account-name (decision register record DEC-IAM-GOOGLE-SERVICE-ACCOUNT-INIT) - When: applies only if its count (var.init ? 1 : 0) holds when OpenTofu plans — present with the defaults of var.init (1 instance); a variable file (-var-file), -var or TF_VAR_init can change that
  • The review of the open settings of google_service_account.server (MAN/semantic/google_service_account.server/open-settings) — {resource} = google_service_account.server - When: Timing to be scheduled by the owner - Needs ({needs}): (record DEC-IAM-GOOGLE-SERVICE-ACCOUNT-SERVER, Identity Architect); account-name (record DEC-IAM-GOOGLE-SERVICE-ACCOUNT-SERVER, Identity Architect) - Settings, the part of each that is this step's own: - role-bindings (decision register record DEC-IAM-GOOGLE-SERVICE-ACCOUNT-SERVER) - account-name (decision register record DEC-IAM-GOOGLE-SERVICE-ACCOUNT-SERVER)

</details>

WP-03 — Decide and set the critical settings (security)

  • Owner: Security Architect
  • Steps: 2 (2 a person can start now)
  • Affected resources: google_secret_manager_secret.django_admin_password, google_secret_manager_secret.django_settings
  • State: a person can start it now
  • Done when: every one of the 2 settings is closed: each is set up on its STACKIT target and checked, or the decision that production does not need it is recorded (the condition of each is stated with its item, and assurance/critical-semantics.json says what closes it), for each of its steps.

<details> <summary>The 2 manual steps of WP-03</summary>

Alike steps on 2 resources (google_secret_manager_secret) — the review of the open settings of {resource} (MAN/semantic/{resource}/open-settings-security-architect)
  • Phase: Security
  • What: Review the 2 open critical settings of {resource} (google_secret_manager_secret) that the draft does not carry, and decide for each what the STACKIT target does: set it up by hand, or record that production does not need it (secret-value, tcr-secret-id)
  • Why: The assessment leaves 2 critical settings of {resource} open and the draft does not carry them: a person decides what the target does for each. Each setting states its own reason with its item.
  • Who: Security Architect
  • When: Timing to be scheduled by the owner
  • Done when: every one of the 2 settings is closed: each is set up on its STACKIT target and checked, or the decision that production does not need it is recorded (the condition of each is stated with its item, and assurance/critical-semantics.json says what closes it)
  • State: executable: a person can start it now
  • Risk: the assessment leaves 2 critical settings of {resource} open (the draft does not carry them): the migration of {resource} is not complete until every one is closed; this step is the one step of them (assurance/critical-semantics.json says why each is open)
  • Rollback (manual): the plan does not know how this step is undone: the person who carries it out records how

The steps, each by its name, with the resource that stands for {resource} above, its target objects, the steps it comes after besides those above and the lines that are its own (for a line above with a placeholder in braces, what stands for it):

  • The review of the open settings of google_secret_manager_secret.django_admin_password (MAN/semantic/google_secret_manager_secret.django_admin_password/open-settings-security-architect) — {resource} = google_secret_manager_secret.django_admin_password - Needs: the answer to each decision register record (decisions.json): what the STACKIT target does for the setting, or the accepted difference - secret-value (record DEC-SEC-GOOGLE-SECRET-MANAGER-SECRET-DJANGO-ADMIN-PASSWORD, Security Architect); tcr-secret-id (record DEC-SEC-GOOGLE-SECRET-MANAGER-SECRET-DJANGO-ADMIN-PASSWORD, Security Architect) - Settings (2, the step is done when every one is closed): - secret-value (Security Architect; decision register record DEC-SEC-GOOGLE-SECRET-MANAGER-SECRET-DJANGO-ADMIN-PASSWORD) - Why: The value of the secret is the document you supply, not something the plan carries: StackToIT never reads it from the source (state, variables, code) and writes it nowhere itself, so this critical setting stays open for a person to decide where the values come from. The plan holds the write as the STACKIT API step WriteSecret of google_secret_manager_secret.django_admin_password, which sends the document named in STACKIT_INPUT_SECRET_DOCUMENT_DJANGO_ADMIN_PASSWORD to the secret at the path STACKIT_INPUT_SECRET_NAME_DJANGO_ADMIN_PASSWORD names of the drafted Secrets Manager instance, after the migration user the draft creates, and which waits for the opt-in to the Vault-compatible API. - Done when: A person decides where the values of the document come from (the same value from a secure source, or a new one: moving is the moment to rotate), supplies it through STACKIT_INPUT_SECRET_DOCUMENT_DJANGO_ADMIN_PASSWORD, and the STACKIT API step WriteSecret of google_secret_manager_secret.django_admin_password has run. The person also names the Vault path the applications read, in STACKIT_INPUT_SECRET_NAME_DJANGO_ADMIN_PASSWORD. - tcr-secret-id (Security Architect; decision register record DEC-SEC-GOOGLE-SECRET-MANAGER-SECRET-DJANGO-ADMIN-PASSWORD) - Why: The plan holds the write of the secret as the STACKIT API step WriteSecret of google_secret_manager_secret.django_admin_password, which takes its path from STACKIT_INPUT_SECRET_NAME_DJANGO_ADMIN_PASSWORD and does not carry the name of the source: the source builds the name of the secret from a value the scan cannot know, so it does not state the name as a literal the plan can rely on. The plan states no path for it, so the name stays open for a person to decide. - Done when: A person names the Vault path in STACKIT_INPUT_SECRET_NAME_DJANGO_ADMIN_PASSWORD, the STACKIT API step WriteSecret of google_secret_manager_secret.django_admin_password has run, and the applications look the secret up at that path.
  • The review of the open settings of google_secret_manager_secret.django_settings (MAN/semantic/google_secret_manager_secret.django_settings/open-settings-security-architect) — {resource} = google_secret_manager_secret.django_settings - Needs: the answer to each decision register record (decisions.json): what the STACKIT target does for the setting, or the accepted difference - secret-value (record DEC-SEC-GOOGLE-SECRET-MANAGER-SECRET-DJANGO-SETTINGS, Security Architect); tcr-secret-id (record DEC-SEC-GOOGLE-SECRET-MANAGER-SECRET-DJANGO-SETTINGS, Security Architect) - Settings (2, the step is done when every one is closed): - secret-value (Security Architect; decision register record DEC-SEC-GOOGLE-SECRET-MANAGER-SECRET-DJANGO-SETTINGS) - Why: The value of the secret is the document you supply, not something the plan carries: StackToIT never reads it from the source (state, variables, code) and writes it nowhere itself, so this critical setting stays open for a person to decide where the values come from. The plan holds the write as the STACKIT API step WriteSecret of google_secret_manager_secret.django_settings, which sends the document named in STACKIT_INPUT_SECRET_DOCUMENT_DJANGO_SETTINGS to the secret at the path STACKIT_INPUT_SECRET_NAME_DJANGO_SETTINGS names of the drafted Secrets Manager instance, after the migration user the draft creates, and which waits for the opt-in to the Vault-compatible API. - Done when: A person decides where the values of the document come from (the same value from a secure source, or a new one: moving is the moment to rotate), supplies it through STACKIT_INPUT_SECRET_DOCUMENT_DJANGO_SETTINGS, and the STACKIT API step WriteSecret of google_secret_manager_secret.django_settings has run. The person also names the Vault path the applications read, in STACKIT_INPUT_SECRET_NAME_DJANGO_SETTINGS. - tcr-secret-id (Security Architect; decision register record DEC-SEC-GOOGLE-SECRET-MANAGER-SECRET-DJANGO-SETTINGS) - Why: The plan holds the write of the secret as the STACKIT API step WriteSecret of google_secret_manager_secret.django_settings, which takes its path from STACKIT_INPUT_SECRET_NAME_DJANGO_SETTINGS and does not carry the name of the source: the source builds the name of the secret from a value the scan cannot know, so it does not state the name as a literal the plan can rely on. The plan states no path for it, so the name stays open for a person to decide. - Done when: A person names the Vault path in STACKIT_INPUT_SECRET_NAME_DJANGO_SETTINGS, the STACKIT API step WriteSecret of google_secret_manager_secret.django_settings has run, and the applications look the secret up at that path.

</details>

WP-04 — Secrets migration user retire (security)

  • Owner: Security Architect
  • Steps: 1 (1 requires a design decision)
  • Affected resources: google_secret_manager_secret.django_admin_password, google_secret_manager_secret.django_settings
  • Waits for: DEC-GEN-CONVERT-API-OPT-IN/google_secret_manager_secret.django_admin_password
  • State: waits for decisions
  • Done when: the block and the two outputs of stackit_secretsmanager_user.migration are gone from the draft, the state lists no such user, and the instance lists no user created for the migration

<details> <summary>The 1 manual step of WP-04</summary>

The step "Remove the migration user stackit_secretsmanager_user.migration of the Secrets…" (MAN/path/secrets-migration/retire-user)
  • Phase: Security
  • What: Remove the migration user stackit_secretsmanager_user.migration of the Secrets Manager instance stackit_secretsmanager_instance.migrated_secrets once the last secret is written and read back: delete its block and its two outputs (stackit_secretsmanager_user_migration_password and stackit_secretsmanager_user_migration_username) from the draft and run tofu apply, so the provider deletes the user. Afterwards the state must not list it (tofu state list shows no stackit_secretsmanager_user.migration) and the instance must list no user of that name; give a workload that reads a secret a user of its own with write_enabled = false, or the instance's role binding.
  • Why: The user has write access to the secrets engine of the whole instance, not to one secret, and STACKIT generated its password into the state in plain text (the sensitive output stackit_secretsmanager_user_migration_password). Setting write_enabled = false is no removal: the provider updates that argument in place and has no flag that disables a user, so the user and its password stay. The provider's delete returns without an error when the API answers 403 (terraform-provider-stackit 0.119.0, secretsmanager user resource), so a successful apply alone does not prove that the user is gone: look at the instance's users in the portal or with the ListUsers request. Take the entry out of the state by hand (tofu state rm stackit_secretsmanager_user.migration) only AFTER the user is deleted in the instance, for example when the instance is already gone: removing it from the state alone leaves a user with write access behind. The earlier versions of the state (a versioned backend, backups) still hold the password; once the user is deleted it opens nothing, and while it is kept those versions are secrets.
  • Who: Security Architect
  • When: Timing to be scheduled by the owner
  • Needs: the draft, the state of the draft and the Secrets Manager instance's list of users
  • Done when: the block and the two outputs of stackit_secretsmanager_user.migration are gone from the draft, the state lists no such user, and the instance lists no user created for the migration
  • Source: google_secret_manager_secret.django_admin_password, google_secret_manager_secret.django_settings
  • After: the step "Read each migrated secret"
  • State: not executable — requires a design decision — blocked by DEC-GEN-CONVERT-API-OPT-IN/google_secret_manager_secret.django_admin_password; it also comes after the step "Read each migrated secret", a manual prerequisite; and, as the plan states: until this step is done a credential with write access to every secret of the instance exists and its password is in the state
  • Risk: until this step is done a credential with write access to every secret of the instance exists and its password is in the state
  • Rollback (manual): the plan does not know how this step is undone: the person who carries it out records how

</details>

WP-05 — Secrets read back (security)

  • Owner: Security Architect
  • Steps: 1 (1 requires a design decision)
  • Affected resources: google_secret_manager_secret.django_admin_password, google_secret_manager_secret.django_settings
  • Waits for: DEC-GEN-CONVERT-API-OPT-IN/google_secret_manager_secret.django_admin_password
  • State: waits for decisions
  • Done when: every migrated secret has a current version whose keys and values equal those of its document, or the difference is corrected with the write step and read back again

<details> <summary>The 1 manual step of WP-05</summary>

The step "Read each migrated secret" (MAN/path/secrets-migration/read-back)
  • Phase: Security
  • What: Read each migrated secret (google_secret_manager_secret.django_admin_password and google_secret_manager_secret.django_settings) back from the Secrets Manager instance stackit_secretsmanager_instance.migrated_secrets before the migration user is removed: with its login, send the ReadSecret request of the write step (GET /v1/\<instance id>/data/\<path>, the path the write step names) and check that the secret has a current version, that its keys are the keys of the document you supplied, and that its values equal the document's (compare a hash of each value on your side; print none).
  • Why: The write steps stop on any answer but 200 and check that a current version exists, but they never compare what the instance holds with what the person supplied. The migration user is the login they write with: once it is removed a correction needs a new user, and the source secrets are what the later steps retire.
  • Who: Security Architect
  • When: Timing to be scheduled by the owner
  • Needs: the documents named in the write steps' inputs (the STACKIT API step WriteSecret of google_secret_manager_secret.django_admin_password, the STACKIT API step WriteSecret of google_secret_manager_secret.django_settings) and the login of stackit_secretsmanager_user.migration (its outputs stackit_secretsmanager_user_migration_password and stackit_secretsmanager_user_migration_username)
  • Done when: every migrated secret has a current version whose keys and values equal those of its document, or the difference is corrected with the write step and read back again
  • Source: google_secret_manager_secret.django_admin_password, google_secret_manager_secret.django_settings
  • After: the STACKIT API step WriteSecret of google_secret_manager_secret.django_admin_password, the STACKIT API step WriteSecret of google_secret_manager_secret.django_settings
  • State: not executable — requires a design decision — blocked by DEC-GEN-CONVERT-API-OPT-IN/google_secret_manager_secret.django_admin_password; and, as the plan states: a value printed while comparing is a leaked secret, so compare hashes and never the values themselves
  • Risk: a value printed while comparing is a leaked secret, so compare hashes and never the values themselves
  • Rollback (manual): the plan does not know how this step is undone: the person who carries it out records how

</details>

WP-06 — Decide and set the critical settings (data services)

  • Owner: Identity Architect
  • Steps: 2 (2 a person can start now)
  • Affected resources: google_sql_database_instance.postgres, google_storage_bucket.media
  • State: a person can start it now
  • Done when: each step meets the condition stated with it below (2 different conditions).

<details> <summary>The 2 manual steps of WP-06</summary>

The setting database-users of google_sql_database_instance.postgres (MAN/semantic/google_sql_database_instance.postgres/database-users)
  • Phase: Data services
  • What: Decide what the STACKIT target of google_sql_database_instance.postgres (google_sql_database_instance) does for its critical setting database-users, which the draft does not carry, and set it up by hand, or record that production does not need it
  • Why: A check of the knowledge base (sql user not generated) needs a value the scan cannot read statically, so it could not decide. A built-in user of a PostgreSQL instance becomes a stackit_postgresflex_user with the same username (pinned provider) — TRANSFORMED. IAM users (and a name or type not visible statically) are not generated — REVIEW (check sql-user-not-generated); users of the other engines are named NOT CARRIED — REVIEW.
  • Who: Identity Architect
  • When: Timing to be scheduled by the owner
  • Needs: the answer to the decision register record DEC-IAM-GOOGLE-SQL-DATABASE-INSTANCE-POSTGRES (decisions.json): what the STACKIT target does for the critical setting database-users, or the accepted difference
  • Done when: A literal value or a resolved reference in the source lets the check decide, or a person verifies the value.
  • Source: google_sql_database_instance.postgres
  • State: executable: a person can start it now
  • Risk: the assessment leaves the critical setting database-users of google_sql_database_instance.postgres open (REVIEW: a person decides what the target does for it): the migration of google_sql_database_instance.postgres is not complete until this step is done
  • Rollback (manual): the plan does not know how this step is undone: the person who carries it out records how
The review of the open settings of google_storage_bucket.media (MAN/semantic/google_storage_bucket.media/open-settings-identity-architect)
  • Phase: Data services
  • What: Review the 2 open critical settings of google_storage_bucket.media (google_storage_bucket) that the draft does not carry, and decide for each what the STACKIT target does: set it up by hand, or record that production does not need it (iam-grant, public-access)
  • Why: The assessment leaves 2 critical settings of google_storage_bucket.media open and the draft does not carry them: a person decides what the target does for each. Each setting states its own reason with its item.
  • Who: Identity Architect
  • When: Timing to be scheduled by the owner
  • Needs: the answer to each decision register record (decisions.json): what the STACKIT target does for the setting, or the accepted difference - iam-grant (record DEC-IAM-GOOGLE-STORAGE-BUCKET-MEDIA, Identity Architect); public-access (record DEC-IAM-GOOGLE-STORAGE-BUCKET-MEDIA, Identity Architect)
  • Done when: every one of the 2 settings is closed: each is set up on its STACKIT target and checked, or the decision that production does not need it is recorded (the condition of each is stated with its item, and assurance/critical-semantics.json says what closes it)
  • Settings (2, the step is done when every one is closed): - iam-grant (Identity Architect; decision register record DEC-IAM-GOOGLE-STORAGE-BUCKET-MEDIA) - Why: A check of the knowledge base (gcs iam) fired on this unit and leaves the setting to a person: the target does not settle it by itself (a finding of this unit describes it). The value-level reader of who gets which role (iam-access above is the facet as a whole): role, member(s) and condition. A GCP principal (user:, group:, serviceAccount:, allUsers) and a predefined role have no STACKIT counterpart in a bucket policy, whose statements name principals and S3 actions (supported-permissions-in-bucket-policies docs; the draft writes no policy) — a person translates each grant — REVIEW for every value, allUsers included (that one is also public-access, TRANSFORMED). - Done when: A person decides what this finding asks, and records the evidence or the decision. The finding says: Bucket access is granted with IAM bindings or ACL entries; on STACKIT it becomes bucket policies and Object Storage credentials. - public-access (Identity Architect; decision register record DEC-IAM-GOOGLE-STORAGE-BUCKET-MEDIA) - Why: A check of the knowledge base (gcs public) needs a value the scan cannot read statically, so it could not decide. The counterpart is a bucket policy with Principal "*" and s3:GetObject (documented sample), but stackit_objectstorage_bucket has no policy attribute and the provider no bucket-policy resource, so the draft writes none: REVIEW, as for the IP rules. - Done when: A literal value or a resolved reference in the source lets the check decide, or a person verifies the value.
  • Source: google_storage_bucket.media
  • State: executable: a person can start it now
  • Risk: the assessment leaves 2 critical settings of google_storage_bucket.media open (the draft does not carry them): the migration of google_storage_bucket.media is not complete until every one is closed; this step is the one step of them (assurance/critical-semantics.json says why each is open)
  • Rollback (manual): the plan does not know how this step is undone: the person who carries it out records how

</details>

WP-07 — Decide and set the critical settings (data services)

  • Owner: Network Architect
  • Steps: 1 (1 a person can start now)
  • Affected resources: google_sql_database_instance.postgres
  • State: a person can start it now
  • Done when: A person confirms the value against the target, or the knowledge base gains a rule that decides this setting.

<details> <summary>The 1 manual step of WP-07</summary>

The setting user-and-database-settings of google_sql_database_instance.postgres (MAN/semantic/google_sql_database_instance.postgres/user-and-database-settings)
  • Phase: Data services
  • What: Decide what the STACKIT target of google_sql_database_instance.postgres (google_sql_database_instance) does for its critical setting user-and-database-settings, which the draft does not carry, and set it up by hand, or record that production does not need it
  • Why: The mapping rule for this kind of unit recognises the unit but only passes this setting to a person; it does not decide its value. The source password is not copied (STACKIT generates it: stackit_postgresflex_user.password is computed), a host restriction and ABANDON deletion policies have no counterpart (pinned provider) — the draft names them NOT CARRIED — REVIEW.
  • Who: Network Architect
  • When: Timing to be scheduled by the owner
  • Needs: the answer to the decision register record DEC-NET-GOOGLE-SQL-DATABASE-INSTANCE-POSTGRES (decisions.json): what the STACKIT target does for the critical setting user-and-database-settings, or the accepted difference
  • Done when: A person confirms the value against the target, or the knowledge base gains a rule that decides this setting.
  • Source: google_sql_database_instance.postgres
  • State: executable: a person can start it now
  • Risk: the assessment leaves the critical setting user-and-database-settings of google_sql_database_instance.postgres open (REVIEW: a person decides what the target does for it): the migration of google_sql_database_instance.postgres is not complete until this step is done
  • Rollback (manual): the plan does not know how this step is undone: the person who carries it out records how

</details>

WP-08 — Decide and set the critical settings (data services)

  • Owner: Platform Architect
  • Steps: 4 (2 a person can start now, 2 require a design decision)
  • Affected resources: google_cloud_run_v2_job.migrate, google_cloud_run_v2_service.server, google_sql_database_instance.postgres, google_storage_bucket.media
  • Rests on: DEC-GEN-CONVERT-SOURCE-REGION-MAPPING, DEC-GEN-CONVERT-SOURCE-PROJECT-MAPPING
  • Waits for: DEC-GEN-CONVERT-SOURCE-REGION-MAPPING, DEC-GEN-CONVERT-SOURCE-PROJECT-MAPPING
  • State: waits for decisions (2 of 4 manual steps)
  • Done when: each step meets the condition stated with it below (3 different conditions).

<details> <summary>The 4 manual steps of WP-08</summary>

The review of the open settings of google_sql_database_instance.postgres "SOURCE_PROJECT_MAPPING" (MAN/estate/SOURCE_PROJECT_MAPPING)
  • Phase: Data services
  • What: Answer the estate's question once for 1 unit: it holds 1 open critical setting, and no unit has a step of its own for it
  • Why: One question of the estate, asked of unit after unit, is answered once. Each setting states its own reason with its item, and each stays open until the answer closes it.
  • Who: Platform Architect
  • When: Timing to be scheduled by the owner
  • Needs: the answer of record DEC-GEN-CONVERT-SOURCE-PROJECT-MAPPING, answered once for each source project in the inputs file, topic target.project.source_project: the answer closes the setting and does not change the draft, which keeps its own project resolver
  • Done when: an answer of the inputs file closes every item it covers; this step closes nothing by itself, and assurance/critical-semantics.json lists what is still open
  • Settings (1, the step is done when every one is closed): - project-scope (Platform Architect; decision register record DEC-GEN-CONVERT-SOURCE-PROJECT-MAPPING) - Why: The mapping rule for this kind of unit recognises the unit but only passes this setting to a person; it does not decide its value. The project of the instance (google 8.4.0 project: "The ID of the project in which the resource belongs. If it is not provided, the provider project is used"). A STACKIT project is the scope for ownership, resource consumption and access control (docs.stackit.cloud/platform/resource-manager/basics/projects/): whether the database's source project becomes the project of the rest of the estate is a design decision no rule evidences — a person confirms it — REVIEW (critical). - Done when: A person confirms the value against the target, or the knowledge base gains a rule that decides this setting.
  • Source: google_sql_database_instance.postgres
  • State: not executable — requires a design decision — blocked by DEC-GEN-CONVERT-SOURCE-PROJECT-MAPPING; and, as the plan states: the migration is not complete until the 1 setting of this step are closed
  • Risk: the migration is not complete until the 1 setting of this step are closed
  • Rollback (manual): the plan does not know how this step is undone: the person who carries it out records how
The review of the open settings of google_cloud_run_v2_job.migrate, google_cloud_run_v2_service.server, google_sql_database_instance.postgres and 1 more (MAN/estate/SOURCE_REGION_MAPPING)
  • Phase: Data services
  • What: Answer the estate's question once for 4 units: it holds 4 open critical settings, and no unit has a step of its own for it
  • Why: One question of the estate, asked of unit after unit, is answered once. Each setting states its own reason with its item, and each stays open until the answer closes it.
  • Who: Platform Architect
  • When: Timing to be scheduled by the owner
  • Needs: the answer of record DEC-GEN-CONVERT-SOURCE-REGION-MAPPING, answered once for each source location in the inputs file, topic target.region.source_location: the region the draft builds in closes the setting, and the answer does not change the draft
  • Done when: an answer of the inputs file closes every item it covers; this step closes nothing by itself, and assurance/critical-semantics.json lists what is still open
  • Settings (4, the step is done when every one is closed): - region-residency (Platform Architect; decision register record DEC-GEN-CONVERT-SOURCE-REGION-MAPPING) - Why: The mapping rule for this kind of unit recognises the unit but only passes this setting to a person; it does not decide its value. The region of the instance (google 8.4.0 region; unset means the provider's region). A person confirms the region — REVIEW (critical, the value is not verified against the target). - Done when: A person confirms the STACKIT region of the instance (and of a read replica, decided with its primary) holds the residency the source region gave. - bucket-region (Platform Architect; decision register record DEC-GEN-CONVERT-SOURCE-REGION-MAPPING) - Why: The mapping rule for this kind of unit recognises the unit but only passes this setting to a person; it does not decide its value. A single-region location (us-central1, europe-west3, ...), or one not visible statically: the draft sets no region on stackit_objectstorage_bucket (region: "if not defined, the provider region is used", pinned provider), so the bucket lands in the STACKIT region the user sets, not in the source's — a person confirms the residency — REVIEW. - Done when: A person confirms the value against the target, or the knowledge base gains a rule that decides this setting. - location-residency (Platform Architect; decision register record DEC-GEN-CONVERT-SOURCE-REGION-MAPPING) - Why: The mapping rule for this kind of unit recognises the unit but only passes this setting to a person; it does not decide its value. The region the service, job or worker pool runs in (google 8.4.0 location). A person confirms the region — REVIEW (critical, the value is not verified against the target). - Done when: A person confirms the STACKIT region of the SKE cluster the workload runs on holds the residency the source region gave, since no mapping between Google and STACKIT regions is evidenced. - location-residency (Platform Architect; decision register record DEC-GEN-CONVERT-SOURCE-REGION-MAPPING) - Why: The mapping rule for this kind of unit recognises the unit but only passes this setting to a person; it does not decide its value. The region the service, job or worker pool runs in (google 8.4.0 location). A person confirms the region — REVIEW (critical, the value is not verified against the target). - Done when: A person confirms the STACKIT region of the SKE cluster the workload runs on holds the residency the source region gave, since no mapping between Google and STACKIT regions is evidenced.
  • Source: google_cloud_run_v2_job.migrate, google_cloud_run_v2_service.server, google_sql_database_instance.postgres, google_storage_bucket.media
  • State: not executable — requires a design decision — blocked by DEC-GEN-CONVERT-SOURCE-REGION-MAPPING; and, as the plan states: the migration is not complete until the 4 settings of this step are closed
  • Risk: the migration is not complete until the 4 settings of this step are closed
  • Rollback (manual): the plan does not know how this step is undone: the person who carries it out records how
The review of the open settings of google_sql_database_instance.postgres "open-settings-platform-architect" (MAN/semantic/google_sql_database_instance.postgres/open-settings-platform-architect)
  • Phase: Data services
  • What: Review the 2 open critical settings of google_sql_database_instance.postgres (google_sql_database_instance) that the draft does not carry, and decide for each what the STACKIT target does: set it up by hand, or record that production does not need it (tc-name, tcf-sql-database--name)
  • Why: The assessment leaves 2 critical settings of google_sql_database_instance.postgres open and the draft does not carry them: a person decides what the target does for each. Each setting states its own reason with its item. The mapping rule for this kind of unit recognises the unit but only passes this setting to a person; it does not decide its value.
  • Who: Platform Architect
  • When: Timing to be scheduled by the owner
  • Needs: the answer to each decision register record (decisions.json): what the STACKIT target does for the setting, or the accepted difference - tc-name (record DEC-OPS-GOOGLE-SQL-DATABASE-INSTANCE-POSTGRES, Platform Architect); tcf-sql-database--name (record DEC-OPS-GOOGLE-SQL-DATABASE-INSTANCE-POSTGRES, Platform Architect)
  • Done when: every one of the 2 settings is closed: each is set up on its STACKIT target and checked, or the decision that production does not need it is recorded (the condition of each is stated with its item, and assurance/critical-semantics.json says what closes it)
  • Settings (2, the step is done when every one is closed): - tc-name (Platform Architect; decision register record DEC-OPS-GOOGLE-SQL-DATABASE-INSTANCE-POSTGRES) - Why: The mapping rule for this kind of unit recognises the unit but only passes this setting to a person; it does not decide its value. google_sql_database_instance.name: The instance's name/label; does not itself grant access, encrypt data, or control backups/availability/residency. - Done when: A person confirms the instance name the STACKIT database carries against what refers to the source instance name, or accepts the new name. - tcf-sql-database--name (Platform Architect; decision register record DEC-OPS-GOOGLE-SQL-DATABASE-INSTANCE-POSTGRES) - Why: The mapping rule for this kind of unit recognises the unit but only passes this setting to a person; it does not decide its value. google_sql_database.name (a facet of the unit): The literal database name; determines the connection target's label but does not itself grant or restrict access, encrypt data, or affect backups/availability/residency. - Done when: A person confirms the value against the target, or the knowledge base gains a rule that decides this setting.
  • Source: google_sql_database_instance.postgres
  • State: executable: a person can start it now
  • Risk: the assessment leaves 2 critical settings of google_sql_database_instance.postgres open (the draft does not carry them): the migration of google_sql_database_instance.postgres is not complete until every one is closed; this step is the one step of them (assurance/critical-semantics.json says why each is open)
  • Rollback (manual): the plan does not know how this step is undone: the person who carries it out records how
The setting "name" of google_storage_bucket.media (MAN/semantic/google_storage_bucket.media/tc-storage-bucket-name)
  • Phase: Data services
  • What: Decide what the STACKIT target of google_storage_bucket.media (google_storage_bucket) does for its critical setting tc-storage-bucket-name, which the draft does not carry, and set it up by hand, or record that production does not need it
  • Why: The mapping rule for this kind of unit recognises the unit but only passes this setting to a person; it does not decide its value. The name of the Cloud Storage bucket, in lowercase and at most 63 characters per the provider documentation, by which applications address it.
  • Who: Platform Architect
  • When: Timing to be scheduled by the owner
  • Needs: the answer to the decision register record DEC-OPS-GOOGLE-STORAGE-BUCKET-MEDIA (decisions.json): what the STACKIT target does for the critical setting tc-storage-bucket-name, or the accepted difference
  • Done when: A person confirms the Object Storage bucket name against the applications and tools that use the source bucket name, or accepts the new name.
  • Source: google_storage_bucket.media
  • State: executable: a person can start it now
  • Risk: the assessment leaves the critical setting tc-storage-bucket-name of google_storage_bucket.media open (REVIEW: a person decides what the target does for it): the migration of google_storage_bucket.media is not complete until this step is done
  • Rollback (manual): the plan does not know how this step is undone: the person who carries it out records how

</details>

WP-09 — Decide and set the critical settings (data services)

  • Owner: Security Architect
  • Steps: 1 (1 a person can start now)
  • Affected resources: google_storage_bucket.media
  • State: a person can start it now
  • Done when: The value is set where the module is called (a literal), the variable is given its value in a variable file of the scan (--var-file), or a person confirms the value production uses.

<details> <summary>The 1 manual step of WP-09</summary>

The setting geo-replication of google_storage_bucket.media (MAN/semantic/google_storage_bucket.media/geo-replication)
  • Phase: Data services
  • What: Decide what the STACKIT target of google_storage_bucket.media (google_storage_bucket) does for its critical setting geo-replication, which the draft does not carry, and set it up by hand, or record that production does not need it
  • Why: The value is read from a variable's default; that is not a fact of the source. A custom_placement_config not visible statically (a dynamic block over an unknown for_each, a whole value from a variable) may be absent: the facts state no replication, the ledger writes no line and a person decides (REVIEW by the case; 2026-09-25 unknown-value audit — it used to be claimed as replicated).
  • Who: Security Architect
  • When: Timing to be scheduled by the owner
  • Needs: the answer to the decision register record DEC-SEC-GOOGLE-STORAGE-BUCKET-MEDIA (decisions.json): what the STACKIT target does for the critical setting geo-replication, or the accepted difference
  • Done when: The value is set where the module is called (a literal), the variable is given its value in a variable file of the scan (--var-file), or a person confirms the value production uses.
  • Source: google_storage_bucket.media
  • State: executable: a person can start it now
  • Risk: the assessment leaves the critical setting geo-replication of google_storage_bucket.media open (REVIEW: a person decides what the target does for it): the migration of google_storage_bucket.media is not complete until this step is done
  • Rollback (manual): the plan does not know how this step is undone: the person who carries it out records how

</details>

WP-10 — Decide and set the critical settings (data services)

  • Owner: Storage Architect
  • Steps: 1 (1 a person can start now)
  • Affected resources: google_storage_bucket.media
  • State: a person can start it now
  • Done when: A person decides it as that finding asks, and the finding's closing evidence or decision is recorded. A person creates the bucket policies and Object Storage credentials that give the same access on STACKIT.

<details> <summary>The 1 manual step of WP-10</summary>

The setting iam-access of google_storage_bucket.media (MAN/semantic/google_storage_bucket.media/iam-access)
  • Phase: Data services
  • What: Decide what the STACKIT target of google_storage_bucket.media (google_storage_bucket) does for its critical setting iam-access, which the draft does not carry, and set it up by hand, or record that production does not need it
  • Why: A check of the knowledge base (gcs iam) fired on this unit and leaves the setting to a person: the target does not settle it by itself (a finding of this unit describes it). The setting states who may access the bucket, granted with IAM bindings or ACL entries.
  • Who: Storage Architect
  • When: Timing to be scheduled by the owner
  • Needs: the answer to the decision register record DEC-GEN-GOOGLE-STORAGE-BUCKET-MEDIA (decisions.json): what the STACKIT target does for the critical setting iam-access, or the accepted difference
  • Done when: A person decides it as that finding asks, and the finding's closing evidence or decision is recorded. A person creates the bucket policies and Object Storage credentials that give the same access on STACKIT.
  • Source: google_storage_bucket.media
  • State: executable: a person can start it now
  • Risk: the assessment leaves the critical setting iam-access of google_storage_bucket.media open (REVIEW: a person decides what the target does for it): the migration of google_storage_bucket.media is not complete until this step is done
  • Rollback (manual): the plan does not know how this step is undone: the person who carries it out records how

</details>

WP-11 — Decide on the switched-off modules that hold a STACKIT alternative of what has no equal (data services)

  • Owner: Network Architect
  • Steps: 1 (1 requires a design decision)
  • Affected resources: google_firebase_hosting_site.client
  • Rests on: DEC-GEN-CONVERT-PROPOSED-ALTERNATIVE/google_firebase_hosting_site.client
  • Waits for: DEC-GEN-CONVERT-PROPOSED-ALTERNATIVE/google_firebase_hosting_site.client
  • State: waits for decisions
  • Done when: the module terraform/proposals/proposal_client is switched on with its values and checked, or the design that replaces it is set up and checked; the decision records which

<details> <summary>The 1 manual step of WP-11</summary>

The proposed alternative of google_firebase_hosting_site.client (MAN/unit/google_firebase_hosting_site.client)
  • Phase: Data services
  • What: Decide whether the STACKIT alternative in the switched-off module terraform/proposals/proposal_client replaces google_firebase_hosting_site.client (google_firebase_hosting_site): read the module, accept the stated difference, give it its values and set proposal_client_enabled to true, or set the target up another way by hand
  • Why: STACKIT has no web hosting service of this kind (0 hits for firebase in the resource names, descriptions and attributes of the 115 resource types of the pinned STACKIT provider, checked 2026-11-05; the product list at docs.stackit.cloud/products/ read 2026-11-05 names none), but it has the parts for static content: Object Storage buckets, and a CDN whose distribution can use a bucket as its origin and takes redirect rules, TLS settings and a WAF (the stackit_cdn_distribution resource of the pinned provider is a beta resource), with custom domains.
  • Who: Network Architect
  • When: applies only if its count (var.random_suffix ? 1 : 0) holds when OpenTofu plans — present with the defaults of var.random_suffix (1 instance); a variable file (-var-file), -var or TF_VAR_random_suffix can change that
  • Needs: what the customer decides: Which bucket is the origin, which domain the distribution serves, and how rewrites and previews are replaced. The CDN resources are beta. the review the assessment asks for: Check the rewrites, headers and clean-URL settings of firebase.json: redirects have a counterpart in the CDN, rewrites to a backend do not; Plan the DNS change for the custom domain and the certificate that the CDN issues for it.; whether google_firebase_hosting_site.client exists in production, and with how many instances: the value production gives var.random_suffix
  • Done when: the module terraform/proposals/proposal_client is switched on with its values and checked, or the design that replaces it is set up and checked; the decision records which
  • Source: google_firebase_hosting_site.client
  • State: not executable — requires a design decision — blocked by DEC-GEN-CONVERT-PROPOSED-ALTERNATIVE/google_firebase_hosting_site.client; and, as the plan states: the assessment says: A Firebase Hosting site: the unit of hosting, whose identifier builds the Firebase-provided sub-domains, optionally associated with a Firebase web app. Firebase Hosting is Google's web hosting with a global CDN: a site holds versions (the files with the rewrites, redirects and headers of firebase.json), releases publish a version on a channel, preview channels expire by themselves, and custom domains get a certificate. STACKIT has no web hosting service of this kind (0 hits for firebase in the resource names, descriptions and attributes of the 115 resource types of the pinned STACKIT provider, checked 2026-11-05; the product list at docs.stackit.cloud/products/ read 2026-11-05 names none), but it has the parts for static content: Object Storage buckets, and a CDN whose distribution can use a bucket as its origin and takes redirect rules, TLS settings and a WAF (the stackit_cdn_distribution resource of the pinned provider is a beta resource), with custom domains. Rewrites to Cloud Run services or Cloud Functions, preview channels and the release history of a site have no counterpart there. Static files are published into an Object Storage bucket by a pipeline; a CDN distribution with the bucket as origin serves them on a custom domain, and the redirects of firebase.json become redirect rules of the distribution. Preview channels and release history are rebuilt in the pipeline (one bucket path per preview). Rewrites to dynamic backends need a load balancer or another origin, which a person decides; the assessment classifies google_firebase_hosting_site.client as redesign: only its switched-off module holds a target, and that is a proposal; 2 critical settings of google_firebase_hosting_site.client are open and part of this step: tc-firebase-hosting-site-project, tcr-firebase-hosting-site-site-id (assurance/critical-semantics.json says why each is open and what closes it); google_firebase_hosting_site.client exists only under a condition of the source (applies only if its count (var.random_suffix ? 1 : 0) holds when OpenTofu plans — present with the defaults of var.random_suffix (1 instance); a variable file (-var-file), -var or TF_VAR_random_suffix can change that)
  • Risk: the assessment says: A Firebase Hosting site: the unit of hosting, whose identifier builds the Firebase-provided sub-domains, optionally associated with a Firebase web app. Firebase Hosting is Google's web hosting with a global CDN: a site holds versions (the files with the rewrites, redirects and headers of firebase.json), releases publish a version on a channel, preview channels expire by themselves, and custom domains get a certificate. STACKIT has no web hosting service of this kind (0 hits for firebase in the resource names, descriptions and attributes of the 115 resource types of the pinned STACKIT provider, checked 2026-11-05; the product list at docs.stackit.cloud/products/ read 2026-11-05 names none), but it has the parts for static content: Object Storage buckets, and a CDN whose distribution can use a bucket as its origin and takes redirect rules, TLS settings and a WAF (the stackit_cdn_distribution resource of the pinned provider is a beta resource), with custom domains. Rewrites to Cloud Run services or Cloud Functions, preview channels and the release history of a site have no counterpart there. Static files are published into an Object Storage bucket by a pipeline; a CDN distribution with the bucket as origin serves them on a custom domain, and the redirects of firebase.json become redirect rules of the distribution. Preview channels and release history are rebuilt in the pipeline (one bucket path per preview). Rewrites to dynamic backends need a load balancer or another origin, which a person decides.
  • Risk: the assessment classifies google_firebase_hosting_site.client as redesign: only its switched-off module holds a target, and that is a proposal
  • Risk: 2 critical settings of google_firebase_hosting_site.client are open and part of this step: tc-firebase-hosting-site-project, tcr-firebase-hosting-site-site-id (assurance/critical-semantics.json says why each is open and what closes it)
  • Risk: google_firebase_hosting_site.client exists only under a condition of the source (applies only if its count (var.random_suffix ? 1 : 0) holds when OpenTofu plans — present with the defaults of var.random_suffix (1 instance); a variable file (-var-file), -var or TF_VAR_random_suffix can change that)
  • Rollback (manual): the plan does not know how this step is undone: the person who carries it out records how

</details>

WP-12 — Move the data and the objects to the target

  • Owner: Data Architect
  • Steps: 8 (4 wait for a manual prerequisite, 2 wait for an automated step, 2 require a design decision)
  • Affected resources: google_sql_database_instance.postgres, google_storage_bucket.media
  • Rests on: DEC-GEN-CONVERT-DATA-MOVEMENT
  • Waits for: DEC-GEN-CONVERT-DATA-MOVEMENT
  • State: waits for decisions (2 of 8 manual steps)
  • Done when: each step meets the condition stated with it below (8 different conditions).

<details> <summary>The 8 manual steps of WP-12</summary>

Alike steps on 2 resources (google_sql_database_instance, google_storage_bucket) — the data preflight of {resource} (MAN/data/{resource}/preflight)
  • Phase: Data services
  • Who: Data Architect
  • When: Timing to be scheduled by the owner

The steps, each by its name, with the resource that stands for {resource} above, its target objects, the steps it comes after besides those above and the lines that are its own (for a line above with a placeholder in braces, what stands for it):

  • The data preflight of google_storage_bucket.media (MAN/data/google_storage_bucket.media/preflight) — {resource} = google_storage_bucket.media; After: the OpenTofu step for stackit_objectstorage_bucket.media - What: Close what the plan lists as UNKNOWN, from the move host, before any object moves. The credentials of the target are the environment names TARGET_ACCESS_KEY_ID and TARGET_SECRET_ACCESS_KEY, set in the shell of the move host at run time from the credential of a credentials group; the source uses the Google identity of that shell (the service account the host runs as, or a login you made for the move; rclone's env_auth takes it from the environment). No value is written into a file, a ticket or this plan. In the shell of the move host configure the two remotes from the environment: export RCLONE_CONFIG_SRC_TYPE="google cloud storage" RCLONE_CONFIG_SRC_ENV_AUTH=true RCLONE_CONFIG_DST_TYPE=s3 RCLONE_CONFIG_DST_PROVIDER=Other RCLONE_CONFIG_DST_ENDPOINT=https://object.storage.eu01.onstackit.cloud RCLONE_CONFIG_DST_ACCESS_KEY_ID="$TARGET_ACCESS_KEY_ID" RCLONE_CONFIG_DST_SECRET_ACCESS_KEY="$TARGET_SECRET_ACCESS_KEY" RCLONE_CONFIG_DST_ACL=private. Source: rclone lsd SRC:, rclone size SRC:\<source-bucket> --json (the count and the bytes), gcloud storage buckets describe gs://\<source-bucket> (versioning, retention policy, default key, requester pays, soft delete policy, storage class), and for the objects the owner names rclone lsjson --metadata SRC:\<source-bucket> --files-only -R --max-depth 1. Target (after the draft is applied, the bucket exists and a key of a credentials group is issued): rclone lsd DST: and rclone size DST:\<target-bucket> --json: the bucket is empty. Record the number of objects and the bytes beside the size the customer stated (the size of the data is not provided); the plan has no input for the number of objects. Check that the move host reaches both endpoints. Runbook section: terraform/handover/bucket-media/data-copy.md#preflight. - Why: the checks of the plan that are UNKNOWN are closed by these calls before any object moves - Document: terraform/handover/bucket-media/data-copy.md - Needs: the checks that are UNKNOWN and what closes each: target-bucket-and-credentials (closed by: preflight on the target: rclone lsd DST: and rclone size DST:\<target-bucket> --json with that key (the bucket exists and is empty)); source-read-access (closed by: preflight on the source: rclone lsd SRC: and rclone size SRC:\<source-bucket> --json with the identity the move host uses, and a read of a sample of the objects the owner names); object-count-and-size (closed by: preflight on the source: rclone size SRC:\<source-bucket> --json for the count and the bytes; the customer states the size under source.dataset.size); encryption (closed by: preflight on the source: gcloud storage buckets describe gs://\<source-bucket> --format='value(default_kms_key)' and the key of a sample of the objects); object-metadata (closed by: the rehearsal: rclone lsjson --metadata of a sample on both sides, compared field by field); requester-pays (closed by: preflight on the source: gcloud storage buckets describe gs://\<source-bucket> --format='value(billing.requester_pays)'); soft-deleted-objects (closed by: preflight on the source: whether the owner needs deleted objects restored on the target (they are restored in the source first, then copied)); copy-cannot-catch-up (closed by: the customer states source.dataset.size, source.dataset.bandwidth and source.dataset.write_rate; the rehearsal measures the real throughput); the open fields of the move: - (1) copy volume: UNKNOWN — supplied by Data Architect (the amount of data of google_storage_bucket.media to copy: the used space measured on the source; the provisioned capacity of the source is not the amount of data; closed when the measured amount and the date of the measurement are recorded) - (2) change rate: UNKNOWN — supplied by Data Architect (how much data the writers of google_storage_bucket.media change per hour; closed when the rate measured over a representative period is recorded, with the time a final pass needs at that rate) - (3) write-freeze window: UNKNOWN — supplied by Data Architect (the window in which the writers of google_storage_bucket.media stop for the final pass; closed when the owner of the application agrees the start and the end of the window and it is recorded) - (4) last sync: UNKNOWN — supplied by Data Architect (the time the final pass ends and its checks hold; closed when the time of the final pass and the result of its checks are recorded) - (5) return point: UNKNOWN — supplied by Data Architect (the state of google_storage_bucket.media the migration returns to on a rollback: the source as it was at the write freeze, or a named snapshot or backup; closed when the return point is recorded with its time and how it is restored) - the checks of the copy: - (1) consistency check: UNKNOWN — supplied by Data Architect (how the copy of google_storage_bucket.media is shown complete and unchanged after the final pass: the used space and a checksum of the data (of the files, or the row counts of the tables) agree with the source; closed when the method is agreed and its result after the final pass is recorded) - (2) validation on the target: UNKNOWN — supplied by Migration Owner (the test the application that uses google_storage_bucket.media passes on STACKIT with the copied data before the cutover, agreed with the owner of the application; closed when the test passes on the target and its result is recorded) - Done when: every UNKNOWN check of the plan is HOLDS or FAILS, the number of objects and the bytes are recorded, and both buckets answer - State: executable once the automated step it comes after has run: it waits for the OpenTofu step for stackit_objectstorage_bucket.media - Risk: the key for the move reaches every bucket of the project until it is removed at the cleanup - Risk: check acl-and-policy FAILS: the source states IAM bindings on the bucket: bucket configuration, not object data, so a copy of the objects brings none of it; it is settled where the draft names it, and the consumers' keys reach every bucket of the project until a policy narrows them - Rollback (manual): nothing changed on either side: read-only calls and, on the target, an empty bucket. The way back: - (1) source retention: UNKNOWN — supplied by Migration Owner (google_storage_bucket.media is not retired until the owner decides to retire it; how long it and its final backup are kept after that is the owner's; closed when the date until which google_storage_bucket.media is kept is recorded) - (2) rollback trigger: UNKNOWN — supplied by Migration Owner (the owner's decision to go back; the criteria that call it; closed when the criteria (which check fails, which metric passes which limit, by when) are recorded before the cutover) - (3) data written to the target: UNKNOWN — supplied by Data Architect (what is written on STACKIT after the switch is not in Google Cloud: it is copied back to google_storage_bucket.media before Google Cloud serves again, or accepted as lost; closed when the choice is recorded before the cutover)
  • The data preflight of google_sql_database_instance.postgres (MAN/data/google_sql_database_instance.postgres/preflight) — {resource} = google_sql_database_instance.postgres; After: the OpenTofu step for stackit_postgresflex_instance.postgres - What: Close what the plan lists as UNKNOWN, from the move host, with read-only queries. The move host reaches the source through the connection method you already use for Cloud SQL for PostgreSQL (an authorized network of a public address, a private address inside the VPC network, or a proxy you run). Source: PGPASSWORD="$SOURCE_PGPASSWORD" PGSSLMODE=require psql --host=\<source-host> --port=\<source-port> --username=\<source-user> --dbname=\<source-db> --command='SHOW server_version;', SELECT extname, extversion FROM pg_extension ORDER BY 1;, SELECT pg_size_pretty(pg_database_size(current_database)); against the free storage of the instance (at least 25 percent of the database size free before a large export), \du and \dp for the roles and grants, and the users that sign in with an IAM identity. Target (after the draft is applied and the instance, user and database exist): PGPASSWORD="$TARGET_PGPASSWORD" PGSSLMODE=require psql --host=\<target-host> --port=\<target-port> --username=\<target-user> --dbname=\<target-db> --command='SHOW extwlist.extensions;' and SELECT version;. Check the move host is in network.acl of the instance, reaches both ends and speaks TLS 1.3. Logical replication: on the source SHOW wal_level;, SHOW max_replication_slots;, SHOW max_wal_senders; and the tables without a primary key (SELECT n.nspname, c.relname FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace WHERE c.relkind = 'r' AND n.nspname NOT IN ('pg_catalog', 'information_schema') AND NOT EXISTS (SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary);); on the empty target the probe CREATE SUBSCRIPTION stacktoit_probe CONNECTION 'host=localhost dbname=x' PUBLICATION p WITH (connect = false); then ALTER SUBSCRIPTION stacktoit_probe SET (slot_name = NONE); and DROP SUBSCRIPTION stacktoit_probe;. Runbook section: terraform/handover/database-postgres/data-move.md#preflight. - Why: the checks of the plan that are UNKNOWN are closed by these queries before any data moves - Document: terraform/handover/database-postgres/data-move.md - Needs: the checks that are UNKNOWN and what closes each: extensions (closed by: preflight: on the source SELECT extname, extversion FROM pg_extension ORDER BY 1; on the target SHOW extwlist.extensions; and trust the target); roles (closed by: preflight on the source, the roles: SELECT rolname, rolsuper, rolcanlogin, rolcreatedb, rolcreaterole, rolreplication, rolconnlimit FROM pg_roles WHERE rolname !~ '^(pg_|cloudsql)' ORDER BY 1; (psql: \du); the memberships: SELECT r.rolname AS role, m.rolname AS member, a.admin_option FROM pg_auth_members a JOIN pg_roles r ON r.oid = a.roleid JOIN pg_roles m ON m.oid = a.member ORDER BY 1, 2;; and in every database to move: \dp, \ddp, \dn+, \l and \drds; each role the application uses is created as a stackit_postgresflex_user resource and its grants are issued again); network-tls (closed by: preflight from the move host: psql to the source and to the target with PGSSLMODE=require); database-users-and-iam (closed by: preflight on the source: the users of the instance (gcloud sql users list --instance=\<instance> for Cloud SQL, the pg_roles listing for both) and which of the applications sign in with a token); source-headroom (closed by: preflight on the source: SELECT pg_size_pretty(pg_database_size(current_database)); and the free storage of the instance in Cloud Monitoring, against the size the customer states under source.dataset.size); lr-enabled (closed by: preflight on the source: SHOW wal_level; and the flag cloudsql.logical_decoding of the instance (the owner of the source sets it and restarts the instance)); lr-replica-identity (closed by: preflight on the source: list the tables without a primary key (pg_class / pg_index, relkind 'r', outside pg_catalog and information_schema)); lr-subscription-privilege (closed by: preflight on the empty target: CREATE SUBSCRIPTION stacktoit_probe CONNECTION 'host=localhost dbname=x' PUBLICATION p WITH (connect = false); then ALTER SUBSCRIPTION stacktoit_probe SET (slot_name = NONE); DROP SUBSCRIPTION stacktoit_probe; if the master user cannot, the method is a design); source-reach (closed by: the rehearsal: a subscription from a scratch database of the target to a copy of the source, and psql from the move host to both ends); replication-cannot-catch-up (closed by: the customer states source.dataset.size, source.dataset.bandwidth and source.dataset.write_rate; the rehearsal measures the real throughput); the open fields of the move: - (1) copy volume: UNKNOWN — supplied by Data Architect (the amount of data of google_sql_database_instance.postgres to copy: the used space measured on the source; the provisioned capacity of the source is not the amount of data; closed when the measured amount and the date of the measurement are recorded) - (2) change rate: UNKNOWN — supplied by Data Architect (how much data the writers of google_sql_database_instance.postgres change per hour; closed when the rate measured over a representative period is recorded, with the time a final pass needs at that rate) - (3) write-freeze window: UNKNOWN — supplied by Data Architect (the window in which the writers of google_sql_database_instance.postgres stop for the final pass; closed when the owner of the application agrees the start and the end of the window and it is recorded) - (4) last sync: UNKNOWN — supplied by Data Architect (the time the final pass ends and its checks hold; closed when the time of the final pass and the result of its checks are recorded) - (5) return point: UNKNOWN — supplied by Data Architect (the state of google_sql_database_instance.postgres the migration returns to on a rollback: the source as it was at the write freeze, or a named snapshot or backup; closed when the return point is recorded with its time and how it is restored) - the checks of the copy: - (1) consistency check: UNKNOWN — supplied by Data Architect (how the copy of google_sql_database_instance.postgres is shown complete and unchanged after the final pass: the used space and a checksum of the data (of the files, or the row counts of the tables) agree with the source; closed when the method is agreed and its result after the final pass is recorded) - (2) validation on the target: UNKNOWN — supplied by Migration Owner (the test the application that uses google_sql_database_instance.postgres passes on STACKIT with the copied data before the cutover, agreed with the owner of the application; closed when the test passes on the target and its result is recorded) - Done when: every UNKNOWN check of the plan is HOLDS or FAILS; the extension list of the source is compared with SHOW extwlist.extensions; of the target and each difference is settled - State: executable once the automated step it comes after has run: it waits for the OpenTofu step for stackit_postgresflex_instance.postgres - Risk: the move host in network.acl is a hole until it is removed at cleanup - Risk: a public address of the source with an authorized network for the move host is a hole until the entry is removed - Risk: check version-deprecated FAILS: PostgreSQL Flex 14, the major the draft writes, is deprecated: end of support 2026-11-12; an instance on it must be updated to a newer major - Rollback (manual): nothing changed on either side: read-only queries and, on the target, an empty database. The way back: - (1) source retention: UNKNOWN — supplied by Migration Owner (google_sql_database_instance.postgres is not retired until the owner decides to retire it; how long it and its final backup are kept after that is the owner's; closed when the date until which google_sql_database_instance.postgres is kept is recorded) - (2) rollback trigger: UNKNOWN — supplied by Migration Owner (the owner's decision to go back; the criteria that call it; closed when the criteria (which check fails, which metric passes which limit, by when) are recorded before the cutover) - (3) data written to the target: UNKNOWN — supplied by Data Architect (what is written on STACKIT after the switch is not in Google Cloud: it is copied back to google_sql_database_instance.postgres before Google Cloud serves again, or accepted as lost; closed when the choice is recorded before the cutover)
Alike steps on 2 resources (google_sql_database_instance, google_storage_bucket) — the data rehearsal of {resource} (MAN/data/{resource}/rehearsal)
  • Phase: Data services
  • Who: Data Architect
  • When: Timing to be scheduled by the owner
  • Needs: the customer's statements in inputs.yaml: source.dataset.size, source.dataset.bandwidth
  • State: executable once its manual prerequisite is done: it waits for the data preflight of {resource}, a manual prerequisite

The steps, each by its name, with the resource that stands for {resource} above, its target objects, the steps it comes after besides those above and the lines that are its own (for a line above with a placeholder in braces, what stands for it):

  • The data rehearsal of google_storage_bucket.media (MAN/data/google_storage_bucket.media/rehearsal) — {resource} = google_storage_bucket.media; After: the data preflight of google_storage_bucket.media - What: Rehearse the copy with a representative part of the bucket: the objects of one prefix and the largest and the smallest objects, into the prefix stacktoit-rehearsal/ of the target bucket. Note the elapsed time and the bytes a second for small and for large objects, compare rclone lsjson --metadata of a sample on both sides (content type, custom metadata, storage class, modification time), and delete the prefix stacktoit-rehearsal/ when the rehearsal is done. Runbook section: terraform/handover/bucket-media/data-copy.md#rehearsal. - Why: the transfer lower bound is the wire time only; the cost of each request for small objects, the retries and the throughput towards STACKIT are not computed, only a rehearsal measures them - Done when: the elapsed times are recorded, the metadata of the sample is compared and the window of the final pass is agreed from them - Rollback (manual): delete what the rehearsal wrote; the real bucket and the source are untouched
  • The data rehearsal of google_sql_database_instance.postgres (MAN/data/google_sql_database_instance.postgres/rehearsal) — {resource} = google_sql_database_instance.postgres; After: the data preflight of google_sql_database_instance.postgres - What: Rehearse the chosen method into a scratch database of the target (a second stackit_postgresflex_database or a clone of the instance) from a copy of the source (a clone or a restored backup of the Cloud SQL for PostgreSQL instance, so that production is not loaded). Note the elapsed time of the dump, the restore, the index build and the verification. Drop the scratch database and the copy afterwards. Runbook section: terraform/handover/database-postgres/data-move.md#rehearsal. - Why: the transfer lower bound leaves out restore, index build and verification, and the throughput is not verified on STACKIT; only a rehearsal measures them - Done when: the elapsed times of each part are recorded and the write-freeze window is agreed from them - Rollback (manual): drop the scratch database and the copy; the draft's database and the source are untouched
The initial copy of the data of google_storage_bucket.media with rclone (MAN/data/google_storage_bucket.media/initial-copy/rclone-copy)
  • Phase: Data services
  • What: While the writers keep writing to the source, copy every live object to the target. The credentials of the target are the environment names TARGET_ACCESS_KEY_ID and TARGET_SECRET_ACCESS_KEY, set in the shell of the move host at run time from the credential of a credentials group; the source uses the Google identity of that shell (the service account the host runs as, or a login you made for the move; rclone's env_auth takes it from the environment). No value is written into a file, a ticket or this plan. With the remotes of the preflight: rclone copy SRC:\<source-bucket> DST:\<target-bucket> --s3-upload-cutoff 0 --progress --log-file=\<log-file> (the form STACKIT's tutorial uses). Read the log for every error before the next step. Runbook section: terraform/handover/bucket-media/data-copy.md#initial-copy-rclone-copy.
  • Why: the copy runs while the application is live, so the freeze later only has to cover the objects that changed since the last pass
  • Who: Data Architect
  • When: Timing to be scheduled by the owner
  • Needs: No input is named for this step
  • Done when: the command ends without an error and rclone size SRC:\<source-bucket> --json and rclone size DST:\<target-bucket> --json differ only by what was written after the copy started
  • Source: google_storage_bucket.media
  • After: the data rehearsal of google_storage_bucket.media
  • State: executable once its manual prerequisite is done: it waits for the data rehearsal of google_storage_bucket.media, a manual prerequisite
  • Risk: a copy of the live objects carries no noncurrent version
  • Risk: the log names every object key
  • Rollback (manual): delete the copied objects from the target bucket; the source is untouched
The replication watch of google_storage_bucket.media with rclone (MAN/data/google_storage_bucket.media/delta-sync/rclone-copy)
  • Phase: Data services
  • What: Run the same rclone copy again, as often as the business needs the target to be close to the source: each pass skips the objects that are identical (rclone compares size and modification time; --checksum compares size and hash where there is one, and Cloud Storage stores the MD5 natively) and copies the ones that are new or changed. It deletes nothing on the target, so an object deleted in the source stays there until the final pass. Note how many objects and bytes each pass transferred. Runbook section: terraform/handover/bucket-media/data-copy.md#delta-sync-rclone-copy.
  • Why: each pass is shorter than the one before while the write rate stays below the bandwidth; the last pass is the one that runs inside the freeze
  • Who: Data Architect
  • When: Timing to be scheduled by the owner
  • Needs: the customer's statements in inputs.yaml: source.dataset.write_rate
  • Done when: the passes get shorter until a pass is as short as the freeze window the business accepts
  • Source: google_storage_bucket.media
  • After: the initial copy of the data of google_storage_bucket.media with rclone
  • State: executable once its manual prerequisite is done: it waits for the initial copy of the data of google_storage_bucket.media with rclone, a manual prerequisite
  • Risk: a pass that does not get shorter means the copy does not catch up at this write rate
  • Rollback (manual): as the initial copy: delete the copied objects from the target; the source is untouched
The initial copy of the data of google_sql_database_instance.postgres by logical replication (MAN/data/google_sql_database_instance.postgres/initial-copy/logical-replication)
  • Phase: Data services
  • What: Source: turn logical decoding on: set the flag cloudsql.logical_decoding to on on the instance (a restart of the instance), create a user for the publication with the REPLICATION attribute that holds CREATE on the database (CREATE ROLE stacktoit_repl WITH LOGIN REPLICATION PASSWORD '\<replication-password>';, typed in an interactive session). Every table needs a primary key or a replica identity. Copy the schema (no later DDL on either side): PGPASSWORD="$SOURCE_PGPASSWORD" pg_dump --schema-only --no-owner --no-acl --host=\<source-host> --port=\<source-port> --username=\<source-user> --dbname=\<source-db> | PGPASSWORD="$TARGET_PGPASSWORD" PGSSLMODE=require psql --host=\<target-host> --port=\<target-port> --username=\<target-user> --dbname=\<target-db>. Publish on the source: CREATE PUBLICATION stacktoit_move FOR ALL TABLES;. Subscribe on the target in an interactive psql session so that no password is stored in a file or the shell history: CREATE SUBSCRIPTION stacktoit_move CONNECTION 'host=\<source-host> port=\<source-port> dbname=\<source-db> user=stacktoit_repl password=\<replication-password> sslmode=require' PUBLICATION stacktoit_move;. Wait until SELECT srrelid::regclass, srsubstate FROM pg_subscription_rel; shows r for every table. Runbook section: terraform/handover/database-postgres/data-move.md#initial-copy-logical-replication.
  • Why: the subscription copies the existing rows and then follows the changes, so the freeze later only has to wait for the lag to reach 0
  • Who: Data Architect
  • When: Timing to be scheduled by the owner
  • Needs: the answer of DEC-GEN-CONVERT-DATA-MOVEMENT in decisions.yaml, which names the path this step belongs to
  • Done when: every table of the target is in state r and the replication slot of the source is active
  • Source: google_sql_database_instance.postgres
  • After: the data rehearsal of google_sql_database_instance.postgres
  • State: not executable — requires a design decision — blocked by DEC-GEN-CONVERT-DATA-MOVEMENT; it also comes after the data rehearsal of google_sql_database_instance.postgres, a manual prerequisite; and, as the plan states: a change and a restart of the source; a slot nobody reads keeps WAL on the disk of the source; schema changes freeze from here on; this step holds only once DEC-GEN-CONVERT-DATA-MOVEMENT is answered: it is of the path replicate-with-the-engine-replication of google_sql_database_instance.postgres, not of the path the draft carries out by default
  • Risk: a change and a restart of the source
  • Risk: a slot nobody reads keeps WAL on the disk of the source
  • Risk: schema changes freeze from here on
  • Risk: this step holds only once DEC-GEN-CONVERT-DATA-MOVEMENT is answered: it is of the path replicate-with-the-engine-replication of google_sql_database_instance.postgres, not of the path the draft carries out by default
  • Rollback (manual): drop the subscription on the target (DROP SUBSCRIPTION stacktoit_move;), the publication and the slot on the source, drop the user stacktoit_repl, and set the flag back with a second restart of the source; the source is otherwise untouched
The replication watch of google_sql_database_instance.postgres by logical replication (MAN/data/google_sql_database_instance.postgres/delta-sync/logical-replication)
  • Phase: Data services
  • What: Keep the target apart: the application does not write to it and no DDL runs on either side. Watch the lag and the free storage of the source: SELECT slot_name, active, pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), confirmed_flush_lsn)) AS lag FROM pg_replication_slots;. Runbook section: terraform/handover/database-postgres/data-move.md#delta-sync-logical-replication.
  • Why: replication keeps running until the freeze; counts on the changing source differ by the changes in flight, so the exact comparison is made inside the freeze
  • Who: Data Architect
  • When: Timing to be scheduled by the owner
  • Needs: the customer's statements in inputs.yaml: source.dataset.write_rate
  • Done when: the lag stays bounded and the free storage of the source does not fall
  • Source: google_sql_database_instance.postgres
  • After: the initial copy of the data of google_sql_database_instance.postgres by logical replication
  • State: not executable — requires a design decision — blocked by DEC-GEN-CONVERT-DATA-MOVEMENT; it also comes after the initial copy of the data of google_sql_database_instance.postgres by logical replication, a manual prerequisite; and, as the plan states: a growing lag means the copy does not catch up at this write rate; this step holds only once DEC-GEN-CONVERT-DATA-MOVEMENT is answered: it is of the path replicate-with-the-engine-replication of google_sql_database_instance.postgres, not of the path the draft carries out by default
  • Risk: a growing lag means the copy does not catch up at this write rate
  • Risk: this step holds only once DEC-GEN-CONVERT-DATA-MOVEMENT is answered: it is of the path replicate-with-the-engine-replication of google_sql_database_instance.postgres, not of the path the draft carries out by default
  • Rollback (manual): as the initial copy: drop the subscription, the publication and the slot

</details>

WP-13 — Decide and set the critical settings (platform services)

  • Owner: Identity Architect
  • Steps: 2 (2 a person can start now)
  • Affected resources: google_cloud_run_v2_job.migrate, google_cloud_run_v2_service.server
  • State: a person can start it now
  • Done when: each step meets the condition stated with it below (2 different conditions).

<details> <summary>The 2 manual steps of WP-13</summary>

The setting service-identity of google_cloud_run_v2_job.migrate (MAN/semantic/google_cloud_run_v2_job.migrate/service-identity)
  • Phase: Platform services
  • What: Decide what the STACKIT target of google_cloud_run_v2_job.migrate (google_cloud_run_v2_job) does for its critical setting service-identity, which the draft does not carry, and set it up by hand, or record that production does not need it
  • Why: Whether the draft carries this setting is decided by its semantic ledger entry "workload identity", and that entry does not state it as carried (it reads review or unsupported, is missing, or rests on a variable's default). The Google service account the workload runs as. target.workload.identity asks the customer which model the pods use.
  • Who: Identity Architect
  • When: Timing to be scheduled by the owner
  • Needs: the answer to the decision register record DEC-IAM-GOOGLE-CLOUD-RUN-V2-JOB-MIGRATE (decisions.json): what the STACKIT target does for the critical setting service-identity, or the accepted difference
  • Done when: The ledger entry reads preserved or transformed with a value that is a fact of the source, or a person accepts the difference.
  • Source: google_cloud_run_v2_job.migrate
  • State: executable: a person can start it now
  • Risk: the assessment leaves the critical setting service-identity of google_cloud_run_v2_job.migrate open (the draft does not carry its value): the migration of google_cloud_run_v2_job.migrate is not complete until this step is done
  • Rollback (manual): the plan does not know how this step is undone: the person who carries it out records how
The review of the open settings of google_cloud_run_v2_service.server by the Identity Architect (MAN/semantic/google_cloud_run_v2_service.server/open-settings-identity-architect)
  • Phase: Platform services
  • What: Review the 2 open critical settings of google_cloud_run_v2_service.server (google_cloud_run_v2_service) that the draft does not carry, and decide for each what the STACKIT target does: set it up by hand, or record that production does not need it (iam-invocation, service-identity)
  • Why: The assessment leaves 2 critical settings of google_cloud_run_v2_service.server open and the draft does not carry them: a person decides what the target does for each. Each setting states its own reason with its item.
  • Who: Identity Architect
  • When: Timing to be scheduled by the owner
  • Needs: the answer to each decision register record (decisions.json): what the STACKIT target does for the setting, or the accepted difference - iam-invocation (record DEC-IAM-GOOGLE-CLOUD-RUN-V2-SERVICE-SERVER, Identity Architect); service-identity (record DEC-IAM-GOOGLE-CLOUD-RUN-V2-SERVICE-SERVER, Identity Architect)
  • Done when: every one of the 2 settings is closed: each is set up on its STACKIT target and checked, or the decision that production does not need it is recorded (the condition of each is stated with its item, and assurance/critical-semantics.json says what closes it)
  • Settings (2, the step is done when every one is closed): - iam-invocation (Identity Architect; decision register record DEC-IAM-GOOGLE-CLOUD-RUN-V2-SERVICE-SERVER) - Why: The mapping rule for this kind of unit recognises the unit but only passes this setting to a person; it does not decide its value. Cloud Run lets only callers with the invoker role through unless the service is made public. The draft carries no invocation check; the SKE exposure and the authentication of callers (application, authenticating proxy, or no exposure at all) are decided by a person. - Done when: A person decides how callers of the workload are authenticated (in the application, behind an authenticating proxy, or by no exposure at all) and how the SKE exposure is set, since the draft carries no invocation check. - service-identity (Identity Architect; decision register record DEC-IAM-GOOGLE-CLOUD-RUN-V2-SERVICE-SERVER) - Why: Whether the draft carries this setting is decided by its semantic ledger entry "workload identity", and that entry does not state it as carried (it reads review or unsupported, is missing, or rests on a variable's default). The Google service account the workload runs as. target.workload.identity asks the customer which model the pods use. - Done when: The ledger entry reads preserved or transformed with a value that is a fact of the source, or a person accepts the difference.
  • Source: google_cloud_run_v2_service.server
  • State: executable: a person can start it now
  • Risk: the assessment leaves 2 critical settings of google_cloud_run_v2_service.server open (the draft does not carry them): the migration of google_cloud_run_v2_service.server is not complete until every one is closed; this step is the one step of them (assurance/critical-semantics.json says why each is open)
  • Rollback (manual): the plan does not know how this step is undone: the person who carries it out records how

</details>

WP-14 — Decide and set the critical settings (platform services)

  • Owner: Network Architect
  • Steps: 2 (2 a person can start now)
  • Affected resources: google_cloud_run_v2_job.migrate, google_cloud_run_v2_service.server
  • State: a person can start it now
  • Done when: each step meets the condition stated with it below (2 different conditions).

<details> <summary>The 2 manual steps of WP-14</summary>

The setting cloud-run-v2-job-network-exposure of google_cloud_run_v2_job.migrate (MAN/semantic/google_cloud_run_v2_job.migrate/cloud-run-v2-job-network-exposure)
  • Phase: Platform services
  • What: Decide what the STACKIT target of google_cloud_run_v2_job.migrate (google_cloud_run_v2_job) does for its critical setting cloud-run-v2-job-network-exposure, which the draft does not carry, and set it up by hand, or record that production does not need it
  • Why: The mapping rule for this kind of unit recognises the unit but only passes this setting to a person; it does not decide its value. template.template.vpc_access.egress, template.template.vpc_access.network_interfaces.network, template.template.vpc_access.network_interfaces.subnetwork, template.template.volumes.cloud_sql_instance.instances (network exposure) — what the resource is reachable from and what it can reach. The draft names these settings NOT CARRIED: which database the job mounts and which network its egress goes through are re-created on the target (a database connection, the network of the workload) by a person. A person decides: REVIEW.
  • Who: Network Architect
  • When: Timing to be scheduled by the owner
  • Needs: the answer to the decision register record DEC-NET-GOOGLE-CLOUD-RUN-V2-JOB-MIGRATE (decisions.json): what the STACKIT target does for the critical setting cloud-run-v2-job-network-exposure, or the accepted difference
  • Done when: A person confirms the value against the target, or the knowledge base gains a rule that decides this setting.
  • Source: google_cloud_run_v2_job.migrate
  • State: executable: a person can start it now
  • Risk: the assessment leaves the critical setting cloud-run-v2-job-network-exposure of google_cloud_run_v2_job.migrate open (REVIEW: a person decides what the target does for it): the migration of google_cloud_run_v2_job.migrate is not complete until this step is done
  • Rollback (manual): the plan does not know how this step is undone: the person who carries it out records how
The setting cloud-sql-connection of google_cloud_run_v2_service.server (MAN/semantic/google_cloud_run_v2_service.server/cloud-sql-connection)
  • Phase: Platform services
  • What: Decide what the STACKIT target of google_cloud_run_v2_service.server (google_cloud_run_v2_service) does for its critical setting cloud-sql-connection, which the draft does not carry, and set it up by hand, or record that production does not need it
  • Why: The mapping rule for this kind of unit recognises the unit but only passes this setting to a person; it does not decide its value. The Cloud SQL connection volume (a connector socket) has no counterpart: the workload connects to the migrated Flex instance endpoint over TLS.
  • Who: Network Architect
  • When: Timing to be scheduled by the owner
  • Needs: the answer to the decision register record DEC-NET-GOOGLE-CLOUD-RUN-V2-SERVICE-SERVER (decisions.json): what the STACKIT target does for the critical setting cloud-sql-connection, or the accepted difference
  • Done when: A person points the workload at the endpoint of the migrated database instance (a Flex or MariaDB instance) over TLS, because the Cloud SQL connector socket has no counterpart.
  • Source: google_cloud_run_v2_service.server
  • State: executable: a person can start it now
  • Risk: the assessment leaves the critical setting cloud-sql-connection of google_cloud_run_v2_service.server open (REVIEW: a person decides what the target does for it): the migration of google_cloud_run_v2_service.server is not complete until this step is done
  • Rollback (manual): the plan does not know how this step is undone: the person who carries it out records how

</details>

WP-15 — Decide and set the critical settings (platform services)

  • Owner: Platform Architect
  • Steps: 3 (3 a person can start now)
  • Affected resources: google_cloud_run_v2_job.migrate, google_cloud_run_v2_service.server, google_pubsub_topic.faux
  • State: a person can start it now
  • Done when: each step meets the condition stated with it below (2 different conditions).

<details> <summary>The 3 manual steps of WP-15</summary>

Alike steps on 2 resources (google_cloud_run_v2_job, google_cloud_run_v2_service) — the review of the open settings of {resource} (MAN/semantic/{resource}/open-settings-platform-architect)
  • Phase: Platform services
  • Who: Platform Architect
  • When: Timing to be scheduled by the owner
  • Done when: every one of the 6 settings is closed: each is set up on its STACKIT target and checked, or the decision that production does not need it is recorded (the condition of each is stated with its item, and assurance/critical-semantics.json says what closes it)
  • State: executable: a person can start it now
  • Risk: the assessment leaves 6 critical settings of {resource} open (the draft does not carry them): the migration of {resource} is not complete until every one is closed; this step is the one step of them (assurance/critical-semantics.json says why each is open)
  • Rollback (manual): the plan does not know how this step is undone: the person who carries it out records how

The steps, each by its name, with the resource that stands for {resource} above, its target objects, the steps it comes after besides those above and the lines that are its own (for a line above with a placeholder in braces, what stands for it):

  • The review of the open settings of google_cloud_run_v2_job.migrate (MAN/semantic/google_cloud_run_v2_job.migrate/open-settings-platform-architect) — {resource} = google_cloud_run_v2_job.migrate - What: Review the 6 open critical settings of google_cloud_run_v2_job.migrate (google_cloud_run_v2_job) that the draft does not carry, and decide for each what the STACKIT target does: set it up by hand, or record that production does not need it (k4-cloud-run-volume-mount-cloud-run-v2-job, terraform-destroy-guard, tc-cloud-run-v2-job-name, tc-cloud-run-v2-job-template--template--volumes--name, deployment-artifact, entry-point) - Why: The assessment leaves 6 critical settings of google_cloud_run_v2_job.migrate open and the draft does not carry them: a person decides what the target does for each. Each setting states its own reason with its item. The mapping rule for this kind of unit recognises the unit but only passes this setting to a person; it does not decide its value. - Needs: the answer to each decision register record (decisions.json): what the STACKIT target does for the setting, or the accepted difference - k4-cloud-run-volume-mount-cloud-run-v2-job (record DEC-OPS-GOOGLE-CLOUD-RUN-V2-JOB-MIGRATE, Platform Architect); terraform-destroy-guard (record DEC-OPS-GOOGLE-CLOUD-RUN-V2-JOB-MIGRATE, Platform Architect); tc-cloud-run-v2-job-name (record DEC-OPS-GOOGLE-CLOUD-RUN-V2-JOB-MIGRATE, Platform Architect); tc-cloud-run-v2-job-template--template--volumes--name (record DEC-OPS-GOOGLE-CLOUD-RUN-V2-JOB-MIGRATE, Platform Architect); deployment-artifact (record DEC-OPS-GOOGLE-CLOUD-RUN-V2-JOB-MIGRATE, Platform Architect); entry-point (record DEC-OPS-GOOGLE-CLOUD-RUN-V2-JOB-MIGRATE, Platform Architect) - Settings (6, the step is done when every one is closed): - k4-cloud-run-volume-mount-cloud-run-v2-job (Platform Architect; decision register record DEC-OPS-GOOGLE-CLOUD-RUN-V2-JOB-MIGRATE) - Why: The mapping rule for this kind of unit recognises the unit but only passes this setting to a person; it does not decide its value. Where a volume appears in the container, which part of it is mounted there and the gcsfuse flags of a bucket mount (volume_mounts, mount_path and sub_path; volumes, gcs, mount_options). The application opens the bucket or share as a local directory, and the STACKIT draft mounts no volume into a container. - Done when: A person provides the data in the target workload at the path the application expects, with an owner and mode it can use, and checks that it finds its files. - terraform-destroy-guard (Platform Architect; decision register record DEC-OPS-GOOGLE-CLOUD-RUN-V2-JOB-MIGRATE) - Why: The mapping rule for this kind of unit recognises the unit but only passes this setting to a person; it does not decide its value. deletion_protection of a Cloud Run service, job or worker pool (on unless the source switches it off, which an explicit false states) only stops Terraform from destroying the resource; it is not an API protection. - Done when: A person decides whether the replacement keeps the guard with lifecycle { prevent_destroy = true } in the Terraform code. - tc-cloud-run-v2-job-name (Platform Architect; decision register record DEC-OPS-GOOGLE-CLOUD-RUN-V2-JOB-MIGRATE) - Why: The mapping rule for this kind of unit recognises the unit but only passes this setting to a person; it does not decide its value. google_cloud_run_v2_job.name: The job's own name, used for identification. - Done when: A person confirms the value against the target, or the knowledge base gains a rule that decides this setting. - tc-cloud-run-v2-job-template--template--volumes--name (Platform Architect; decision register record DEC-OPS-GOOGLE-CLOUD-RUN-V2-JOB-MIGRATE) - Why: The mapping rule for this kind of unit recognises the unit but only passes this setting to a person; it does not decide its value. google_cloud_run_v2_job.template.template.volumes.name: The volume's own name, used only to cross-reference it from volume_mounts.name; an internal wiring identifier with no security meaning of its own. - Done when: A person confirms the value against the target, or the knowledge base gains a rule that decides this setting. - deployment-artifact (Platform Architect; decision register record DEC-OPS-GOOGLE-CLOUD-RUN-V2-JOB-MIGRATE) - Why: The mapping rule for this kind of unit recognises the unit but only passes this setting to a person; it does not decide its value. google_cloud_run_service.template.spec.containers.image; google_cloud_run_v2_service.template.containers.image, build_config.base_image, build_config.image_uri and 5 more; google_cloud_run_v2_job.template.template.containers.image; google_cloud_run_v2_worker_pool.template.containers.image, artifact or image reference (workload compatibility: a setting that binds the code to its runtime stays CRITICAL). - Done when: A person confirms the executable artifact or image for the STACKIT target: where it is built or pulled from and that the same build runs there. - entry-point (Platform Architect; decision register record DEC-OPS-GOOGLE-CLOUD-RUN-V2-JOB-MIGRATE) - Why: The mapping rule for this kind of unit recognises the unit but only passes this setting to a person; it does not decide its value. google_cloud_run_service.template.spec.containers.command, template.spec.containers.args, template.spec.containers.working_dir; google_cloud_run_v2_service.template.containers.command, template.containers.args, template.containers.working_dir and 4 more. - Done when: A person confirms the value against the target, or the knowledge base gains a rule that decides this setting.
  • The review of the open settings of google_cloud_run_v2_service.server by the Platform Architect (MAN/semantic/google_cloud_run_v2_service.server/open-settings-platform-architect) — {resource} = google_cloud_run_v2_service.server - What: Review the 6 open critical settings of google_cloud_run_v2_service.server (google_cloud_run_v2_service) that the draft does not carry, and decide for each what the STACKIT target does: set it up by hand, or record that production does not need it (k4-cloud-run-volume-mount-cloud-run-v2-service, iam-policy-grant, terraform-destroy-guard, tc-cloud-run-v2-service-name, tc-cloud-run-v2-service-template--volumes--name, deployment-artifact) - Why: The assessment leaves 6 critical settings of google_cloud_run_v2_service.server open and the draft does not carry them: a person decides what the target does for each. Each setting states its own reason with its item. - Needs: the answer to each decision register record (decisions.json): what the STACKIT target does for the setting, or the accepted difference - k4-cloud-run-volume-mount-cloud-run-v2-service (record DEC-OPS-GOOGLE-CLOUD-RUN-V2-SERVICE-SERVER, Platform Architect); iam-policy-grant (record DEC-GEN-GOOGLE-CLOUD-RUN-V2-SERVICE-SERVER, no owner role stated); terraform-destroy-guard (record DEC-OPS-GOOGLE-CLOUD-RUN-V2-SERVICE-SERVER, Platform Architect); tc-cloud-run-v2-service-name (record DEC-OPS-GOOGLE-CLOUD-RUN-V2-SERVICE-SERVER, Platform Architect); tc-cloud-run-v2-service-template--volumes--name (record DEC-OPS-GOOGLE-CLOUD-RUN-V2-SERVICE-SERVER, Platform Architect); deployment-artifact (record DEC-OPS-GOOGLE-CLOUD-RUN-V2-SERVICE-SERVER, Platform Architect) - Settings (6, the step is done when every one is closed): - k4-cloud-run-volume-mount-cloud-run-v2-service (Platform Architect; decision register record DEC-OPS-GOOGLE-CLOUD-RUN-V2-SERVICE-SERVER) - Why: The mapping rule for this kind of unit recognises the unit but only passes this setting to a person; it does not decide its value. Where a volume appears in the container, which part of it is mounted there and the gcsfuse flags of a bucket mount (volume_mounts, mount_path and sub_path; volumes, gcs, mount_options). The application opens the bucket or share as a local directory, and the STACKIT draft mounts no volume into a container. - Done when: A person provides the data in the target workload at the path the application expects, with an owner and mode it can use, and checks that it finds its files. - iam-policy-grant (Platform Architect; decision register record DEC-GEN-GOOGLE-CLOUD-RUN-V2-SERVICE-SERVER) - Why: A check of the knowledge base (run iam policy) fired on this unit and leaves the setting to a person: the target does not settle it by itself (a finding of this unit describes it). A full IAM policy (member/role bindings) is set directly on the service via policy_data (google 8.4.0); STACKIT has no per-service IAM policy resource, so who may invoke the migrated SKE workload is decided by a person — REVIEW. - Done when: A person decides what this finding asks, and records the evidence or the decision. The finding says: A full IAM policy (member/role bindings) is set directly on the service via policy_data; STACKIT has no per-service IAM policy resource, so who may invoke the migrated SKE workload is decided by a person — the draft carries no invocation check. - terraform-destroy-guard (Platform Architect; decision register record DEC-OPS-GOOGLE-CLOUD-RUN-V2-SERVICE-SERVER) - Why: The mapping rule for this kind of unit recognises the unit but only passes this setting to a person; it does not decide its value. deletion_protection of a Cloud Run service, job or worker pool (on unless the source switches it off, which an explicit false states) only stops Terraform from destroying the resource; it is not an API protection. - Done when: A person decides whether the replacement keeps the guard with lifecycle { prevent_destroy = true } in the Terraform code. - tc-cloud-run-v2-service-name (Platform Architect; decision register record DEC-OPS-GOOGLE-CLOUD-RUN-V2-SERVICE-SERVER) - Why: The mapping rule for this kind of unit recognises the unit but only passes this setting to a person; it does not decide its value. google_cloud_run_v2_service.name: The Service's own name; an identifier used for addressing/reference, not an access-control or safety property in itself. - Done when: A person confirms the workload name on the cluster against what addresses or refers to the Cloud Run service name, or accepts the new name. - tc-cloud-run-v2-service-template--volumes--name (Platform Architect; decision register record DEC-OPS-GOOGLE-CLOUD-RUN-V2-SERVICE-SERVER) - Why: The mapping rule for this kind of unit recognises the unit but only passes this setting to a person; it does not decide its value. google_cloud_run_v2_service.template.volumes.name: The volume's own name, used only to cross-reference it from a volume_mounts.name entry; internal wiring identifier. - Done when: A person keeps each volume name matched with the volume mount that refers to it when the volumes are rebuilt as workload volumes on the cluster. - deployment-artifact (Platform Architect; decision register record DEC-OPS-GOOGLE-CLOUD-RUN-V2-SERVICE-SERVER) - Why: The mapping rule for this kind of unit recognises the unit but only passes this setting to a person; it does not decide its value. google_cloud_run_service.template.spec.containers.image; google_cloud_run_v2_service.template.containers.image, build_config.base_image, build_config.image_uri and 5 more; google_cloud_run_v2_job.template.template.containers.image; google_cloud_run_v2_worker_pool.template.containers.image, artifact or image reference (workload compatibility: a setting that binds the code to its runtime stays CRITICAL). - Done when: A person confirms the executable artifact or image for the STACKIT target: where it is built or pulled from and that the same build runs there.
The setting "name" of google_pubsub_topic.faux (MAN/semantic/google_pubsub_topic.faux/tc-name)
  • Phase: Platform services
  • What: Decide what the STACKIT target of google_pubsub_topic.faux (google_pubsub_topic) does for its critical setting tc-name, which the draft does not carry, and set it up by hand, or record that production does not need it
  • Why: The mapping rule for this kind of unit recognises the unit but only passes this setting to a person; it does not decide its value. google_pubsub_topic.name: The topic's own resource name, which publishers and subscribers address it by; access to it is controlled by IAM, not by the name string itself, and a changed name breaks every client that uses the old one.
  • Who: Platform Architect
  • When: Timing to be scheduled by the owner
  • Needs: the answer to the decision register record DEC-OPS-GOOGLE-PUBSUB-TOPIC-FAUX (decisions.json): what the STACKIT target does for the critical setting tc-name, or the accepted difference
  • Done when: A person confirms the topic name the replacement messaging service carries against the publishers and subscribers that use it, or accepts the new name.
  • Source: google_pubsub_topic.faux
  • State: executable: a person can start it now
  • Risk: the assessment leaves the critical setting tc-name of google_pubsub_topic.faux open (REVIEW: a person decides what the target does for it): the migration of google_pubsub_topic.faux is not complete until this step is done
  • Rollback (manual): the plan does not know how this step is undone: the person who carries it out records how

</details>

WP-16 — Decide and set the critical settings (platform services)

  • Owner: Security Architect
  • Steps: 2 (2 a person can start now)
  • Affected resources: google_cloud_run_v2_job.migrate, google_cloud_run_v2_service.server
  • State: a person can start it now
  • Done when: each step meets the condition stated with it below (2 different conditions).

<details> <summary>The 2 manual steps of WP-16</summary>

The setting secrets of google_cloud_run_v2_job.migrate (MAN/semantic/google_cloud_run_v2_job.migrate/secrets)
  • Phase: Platform services
  • What: Decide what the STACKIT target of google_cloud_run_v2_job.migrate (google_cloud_run_v2_job) does for its critical setting secrets, which the draft does not carry, and set it up by hand, or record that production does not need it
  • Why: The mapping rule for this kind of unit recognises the unit but only passes this setting to a person; it does not decide its value. A Secret Manager reference in the environment becomes a secretKeyRef of the Deployment or Job and a secret volume a Kubernetes secret volume, and the head of the manifest lists the Secrets to create. The draft writes no Secret, because no value of a secret reaches an output.
  • Who: Security Architect
  • When: Timing to be scheduled by the owner
  • Needs: the answer to the decision register record DEC-SEC-GOOGLE-CLOUD-RUN-V2-JOB-MIGRATE (decisions.json): what the STACKIT target does for the critical setting secrets, or the accepted difference
  • Done when: A person creates the Kubernetes Secrets the manifest lists, filled from STACKIT Secrets Manager (for example with an External Secrets Operator).
  • Source: google_cloud_run_v2_job.migrate
  • State: executable: a person can start it now
  • Risk: the assessment leaves the critical setting secrets of google_cloud_run_v2_job.migrate open (REVIEW: a person decides what the target does for it): the migration of google_cloud_run_v2_job.migrate is not complete until this step is done
  • Rollback (manual): the plan does not know how this step is undone: the person who carries it out records how
The review of the open settings of google_cloud_run_v2_service.server by the Security Architect (MAN/semantic/google_cloud_run_v2_service.server/open-settings-security-architect)
  • Phase: Platform services
  • What: Review the 2 open critical settings of google_cloud_run_v2_service.server (google_cloud_run_v2_service) that the draft does not carry, and decide for each what the STACKIT target does: set it up by hand, or record that production does not need it (environment, secrets)
  • Why: The assessment leaves 2 critical settings of google_cloud_run_v2_service.server open and the draft does not carry them: a person decides what the target does for each. Each setting states its own reason with its item. The mapping rule for this kind of unit recognises the unit but only passes this setting to a person; it does not decide its value.
  • Who: Security Architect
  • When: Timing to be scheduled by the owner
  • Needs: the answer to each decision register record (decisions.json): what the STACKIT target does for the setting, or the accepted difference - environment (record DEC-SEC-GOOGLE-CLOUD-RUN-V2-SERVICE-SERVER, Security Architect); secrets (record DEC-SEC-GOOGLE-CLOUD-RUN-V2-SERVICE-SERVER, Security Architect)
  • Done when: every one of the 2 settings is closed: each is set up on its STACKIT target and checked, or the decision that production does not need it is recorded (the condition of each is stated with its item, and assurance/critical-semantics.json says what closes it)
  • Settings (2, the step is done when every one is closed): - environment (Security Architect; decision register record DEC-SEC-GOOGLE-CLOUD-RUN-V2-SERVICE-SERVER) - Why: The mapping rule for this kind of unit recognises the unit but only passes this setting to a person; it does not decide its value. The draft writes the environment keys into the ConfigMap and the env of the Deployment or Job. A value is copied only when it is a quantity under a name that says so or a boolean, because any other value can hold a secret, so the rest of the values are a person's. - Done when: A person fills in the values the manifest lists as not copied, in the ConfigMap on the cluster. - secrets (Security Architect; decision register record DEC-SEC-GOOGLE-CLOUD-RUN-V2-SERVICE-SERVER) - Why: The mapping rule for this kind of unit recognises the unit but only passes this setting to a person; it does not decide its value. A Secret Manager reference in the environment becomes a secretKeyRef of the Deployment or Job and a secret volume a Kubernetes secret volume, and the head of the manifest lists the Secrets to create. The draft writes no Secret, because no value of a secret reaches an output. - Done when: A person creates the Kubernetes Secrets the manifest lists, filled from STACKIT Secrets Manager (for example with an External Secrets Operator).
  • Source: google_cloud_run_v2_service.server
  • State: executable: a person can start it now
  • Risk: the assessment leaves 2 critical settings of google_cloud_run_v2_service.server open (the draft does not carry them): the migration of google_cloud_run_v2_service.server is not complete until every one is closed; this step is the one step of them (assurance/critical-semantics.json says why each is open)
  • Rollback (manual): the plan does not know how this step is undone: the person who carries it out records how

</details>

WP-17 — Carry by hand what the STACKIT service for pubsub topic does not take from the draft (platform services)

  • Owner: Platform Architect
  • Steps: 1 (1 requires a design decision)
  • Affected resources: google_pubsub_topic.faux
  • Rests on: DEC-GEN-CONVERT-API-OPT-IN/google_pubsub_topic.faux
  • Waits for: DEC-GEN-CONVERT-API-OPT-IN/google_pubsub_topic.faux
  • State: waits for decisions
  • Done when: The object exists in STACKIT PubSub under a name the API takes, or the decision that the source stays on the RabbitMQ path is recorded.

<details> <summary>The 1 manual step of WP-17</summary>

The step "Create the topic under a displayName of 1 to 36 letters, digits, spaces and…" (MAN/pubsub.topic/name/google_pubsub_topic.faux)
  • Phase: Platform services
  • What: Create the topic under a displayName of 1 to 36 letters, digits, spaces and hyphens and change its publishers to it, or keep the topic on the RabbitMQ path.
  • Why: the name reads a variable that can be set when the source is planned (its default is not a fact of the source; a variable file the scan reads (assess --var-file, terraform.tfvars) makes it a fact), so no create-topic call is made for google_pubsub_topic.faux
  • Who: Platform Architect
  • When: Timing to be scheduled by the owner
  • Needs: the name the PubSub topic gets, or the decision to stay on the RabbitMQ path
  • Done when: The object exists in STACKIT PubSub under a name the API takes, or the decision that the source stays on the RabbitMQ path is recorded.
  • Source: google_pubsub_topic.faux
  • State: not executable — requires a design decision — blocked by DEC-GEN-CONVERT-API-OPT-IN/google_pubsub_topic.faux; and, as the plan states: this step holds only once DEC-GEN-CONVERT-API-OPT-IN is answered: it is of the candidate path pubsub.topic of google_pubsub_topic.faux, not of the path the draft carries out by default
  • Risk: this step holds only once DEC-GEN-CONVERT-API-OPT-IN is answered: it is of the candidate path pubsub.topic of google_pubsub_topic.faux, not of the path the draft carries out by default
  • Rollback (manual): a person undoes the step by hand

</details>

WP-18 — Cluster access (platform services)

  • Owner: Platform Architect
  • Steps: 1 (1 waits for an automated step)
  • Affected resources: google_cloud_run_v2_job.migrate, google_cloud_run_v2_service.server
  • State: a person can start it after the automated step it comes after has run
  • Done when: the kubeconfig of each cluster of terraform/handover/cluster-access/kubeconfig.md is fetched, each cluster passes its health verification, the namespace exists and the secrets the headers of the manifest files list are created (steps 1 to 5 of the document); applying the manifest files and their cutover (steps 6 and 7) are the steps of those files in the plan

<details> <summary>The 1 manual step of WP-18</summary>

The step "Fetch the kubeconfig, check the health, create the namespace and the secrets of…" (MAN/draft/handover/cluster-access/kubeconfig-md)
  • Phase: Platform services
  • What: Fetch the kubeconfig, check the health, create the namespace and the secrets of the SKE cluster(s) of the draft: the namespace is one you choose for all manifest files, the secrets are migrate-secrets, server-secrets (the keys of each are listed under SECRETS TO CREATE in the header of its manifest file)
  • Why: the draft writes no kubeconfig (it would keep an admin credential in the Terraform state): a person fetches one when it is needed
  • Document: terraform/handover/cluster-access/kubeconfig.md
  • Who: Platform Architect
  • When: Timing to be scheduled by the owner
  • Needs: the STACKIT project id and a login of the STACKIT CLI for that project, and the name of each cluster (the table of handover/cluster-access/kubeconfig.md); the name of the namespace, which the source does not state, so you choose it once for all manifest files (they are applied into it with -n); the value of every key of the secrets migrate-secrets, server-secrets, from the place its line under SECRETS TO CREATE in the header of the manifest file names (a Secret Manager secret, or a registry login), and where that line says the value is not visible statically, from the running Cloud Run service; the values are read from the environment of the person who runs the command and are in no file of the draft
  • Done when: the kubeconfig of each cluster of terraform/handover/cluster-access/kubeconfig.md is fetched, each cluster passes its health verification, the namespace exists and the secrets the headers of the manifest files list are created (steps 1 to 5 of the document); applying the manifest files and their cutover (steps 6 and 7) are the steps of those files in the plan
  • Source: google_cloud_run_v2_job.migrate, google_cloud_run_v2_service.server
  • After: the OpenTofu step for stackit_ske_cluster.containers
  • State: executable once the automated step it comes after has run: it waits for the OpenTofu step for stackit_ske_cluster.containers
  • Rollback (manual): the plan does not know how this step is undone: terraform/handover/cluster-access/kubeconfig.md says how where it has a rollback step, else the person who carries it out records how

</details>

WP-19 — Messaging topology (platform services)

  • Owner: Platform Engineer
  • Steps: 1 (1 a person can start now)
  • Affected resources: google_pubsub_topic.faux
  • State: a person can start it now
  • Done when: the script ends with status 0 after reading every queue back

<details> <summary>The 1 manual step of WP-19</summary>

The step "Declare the topics and subscriptions of the source on the RabbitMQ instance" (MAN/rabbitmq/topology)
  • Phase: Platform services
  • What: Declare the topics and subscriptions of the source on the RabbitMQ instance (exchanges: 1, queues: 0): run rabbitmq/apply-topology.sh, which writes rabbitmq/topology.json through the HTTP API of the broker. Nothing of this was run on STACKIT, and whether the credential may declare them is not stated: the script asks first and stops with the answer
  • Why: The provider has no resource for an exchange or a queue, so the draft writes them from the source and a person runs the script. The settings of the source stay open on each unit until that is done and read back
  • Who: Platform Engineer
  • When: Timing to be scheduled by the owner
  • Needs: a credential of the instance (stackit_rabbitmq_credential): its http_api_uri (RABBITMQ_HTTP_API_URI), username and password (RABBITMQ_USERNAME, RABBITMQ_PASSWORD), and the virtual host of its uri (RABBITMQ_VHOST)
  • Done when: the script ends with status 0 after reading every queue back
  • Source: google_pubsub_topic.faux
  • State: executable: a person can start it now
  • Rollback (manual): the plan does not know how this step is undone: the person who carries it out records how

</details>

WP-20 — Decide on the switched-off modules that hold a STACKIT alternative of what has no equal (workloads)

  • Owner: Platform Architect
  • Steps: 3 (3 require a design decision)
  • Affected resources: google_cloudbuild_trigger.activategcb, google_cloudbuild_trigger.init, google_cloudbuild_trigger.placeholder
  • Rests on: DEC-GEN-CONVERT-PROPOSED-ALTERNATIVE/google_cloudbuild_trigger.activategcb, DEC-GEN-CONVERT-PROPOSED-ALTERNATIVE/google_cloudbuild_trigger.init, DEC-GEN-CONVERT-PROPOSED-ALTERNATIVE/google_cloudbuild_trigger.placeholder
  • Waits for: DEC-GEN-CONVERT-PROPOSED-ALTERNATIVE/google_cloudbuild_trigger.activategcb, DEC-GEN-CONVERT-PROPOSED-ALTERNATIVE/google_cloudbuild_trigger.init, DEC-GEN-CONVERT-PROPOSED-ALTERNATIVE/google_cloudbuild_trigger.placeholder
  • State: waits for decisions
  • Done when: each step meets the condition stated with it below (3 different conditions).

<details> <summary>The 3 manual steps of WP-20</summary>

The proposed alternative of google_cloudbuild_trigger.activategcb (MAN/unit/google_cloudbuild_trigger.activategcb)
  • Phase: Workloads
  • What: Decide whether the STACKIT alternative in the switched-off module terraform/proposals/proposal_activategcb replaces google_cloudbuild_trigger.activategcb (google_cloudbuild_trigger): read the module, accept the stated difference, give it its values and set proposal_activategcb_enabled to true, or set the target up another way by hand
  • Why: STACKIT has no trigger or build resource.
  • Who: Platform Architect
  • When: applies only if its count (var.init ? 1 : 0) holds when OpenTofu plans — present with the defaults of var.init (1 instance); a variable file (-var-file), -var or TF_VAR_init can change that
  • Needs: what the customer decides: Which pipelines are still needed, where they run (STACKIT Git Pipelines or the customer's own CI/CD), how each trigger, step, secret and approval is written again, and who owns the workflow files. the review the assessment asks for: Write the workflow in the repository: one job (or step) per build step, with the same container images or their STACKIT equivalents; Recreate every secret the build reads in STACKIT Secrets Manager (read with the vault action) or as a repository secret; the values of the source are never copied; Write the trigger conditions again as the on: section of the workflow and verify each event kind against the Forgejo Actions reference; Decide where images and artifacts go: the package registry of STACKIT Git takes container images from any OCI client, or keep your own registry, and change the image names in the deploy steps.; whether google_cloudbuild_trigger.activategcb exists in production, and with how many instances: the value production gives var.init
  • Done when: the module terraform/proposals/proposal_activategcb is switched on with its values and checked, or the design that replaces it is set up and checked; the decision records which
  • Source: google_cloudbuild_trigger.activategcb
  • State: not executable — requires a design decision — blocked by DEC-GEN-CONVERT-PROPOSED-ALTERNATIVE/google_cloudbuild_trigger.activategcb; and, as the plan states: the assessment says: A Cloud Build trigger starts a build when a source event happens (a push or a pull request on GitHub, Bitbucket Server, a repository of the 2nd generation, Developer Connect or Cloud Source Repositories), when a Pub/Sub message arrives, when a webhook is called or when a person runs it. The build is a list of container steps (a build block, or a file in the repository) with substitutions, images and artifacts to push, secrets from Secret Manager, a service account, a log destination and optionally a manual approval. STACKIT has no trigger or build resource. The same delivery is a workflow of STACKIT Git Pipelines (workflows written for GitHub Actions; STACKIT-managed or custom runners; secrets from STACKIT Secrets Manager or the repository; a manual-approval action) or the customer's own CI/CD. Nothing is converted: the trigger conditions (branch and tag expressions, file filters, the event kind), the steps (a container step becomes a step or a job with a container), the secrets (created again, never copied), the service account (replaced by the credentials the workflow uses against STACKIT), the log destination and the approval are written again by a person. The pages read describe pipelines that run when changes are pushed; other events (pull requests, schedules, manual runs) are verified against the Forgejo Actions reference; the assessment classifies google_cloudbuild_trigger.activategcb as redesign: only its switched-off module holds a target, and that is a proposal; 6 critical settings of google_cloudbuild_trigger.activategcb are open and part of this step: project-residency, build-identity, log-destination, deployment-artifact, entry-point, dep-references (assurance/critical-semantics.json says why each is open and what closes it); google_cloudbuild_trigger.activategcb exists only under a condition of the source (applies only if its count (var.init ? 1 : 0) holds when OpenTofu plans — present with the defaults of var.init (1 instance); a variable file (-var-file), -var or TF_VAR_init can change that)
  • Risk: the assessment says: A Cloud Build trigger starts a build when a source event happens (a push or a pull request on GitHub, Bitbucket Server, a repository of the 2nd generation, Developer Connect or Cloud Source Repositories), when a Pub/Sub message arrives, when a webhook is called or when a person runs it. The build is a list of container steps (a build block, or a file in the repository) with substitutions, images and artifacts to push, secrets from Secret Manager, a service account, a log destination and optionally a manual approval. STACKIT has no trigger or build resource. The same delivery is a workflow of STACKIT Git Pipelines (workflows written for GitHub Actions; STACKIT-managed or custom runners; secrets from STACKIT Secrets Manager or the repository; a manual-approval action) or the customer's own CI/CD. Nothing is converted: the trigger conditions (branch and tag expressions, file filters, the event kind), the steps (a container step becomes a step or a job with a container), the secrets (created again, never copied), the service account (replaced by the credentials the workflow uses against STACKIT), the log destination and the approval are written again by a person. The pages read describe pipelines that run when changes are pushed; other events (pull requests, schedules, manual runs) are verified against the Forgejo Actions reference.
  • Risk: the assessment classifies google_cloudbuild_trigger.activategcb as redesign: only its switched-off module holds a target, and that is a proposal
  • Risk: 6 critical settings of google_cloudbuild_trigger.activategcb are open and part of this step: project-residency, build-identity, log-destination, deployment-artifact, entry-point, dep-references (assurance/critical-semantics.json says why each is open and what closes it)
  • Risk: google_cloudbuild_trigger.activategcb exists only under a condition of the source (applies only if its count (var.init ? 1 : 0) holds when OpenTofu plans — present with the defaults of var.init (1 instance); a variable file (-var-file), -var or TF_VAR_init can change that)
  • Rollback (manual): the plan does not know how this step is undone: the person who carries it out records how
The proposed alternative of google_cloudbuild_trigger.init (MAN/unit/google_cloudbuild_trigger.init)
  • Phase: Workloads
  • What: Decide whether the STACKIT alternative in the switched-off module terraform/proposals/proposal_init replaces google_cloudbuild_trigger.init (google_cloudbuild_trigger): read the module, accept the stated difference, give it its values and set proposal_init_enabled to true, or set the target up another way by hand
  • Why: STACKIT has no trigger or build resource.
  • Who: Platform Architect
  • When: applies only if its count (var.init ? 1 : 0) holds when OpenTofu plans — present with the defaults of var.init (1 instance); a variable file (-var-file), -var or TF_VAR_init can change that
  • Needs: what the customer decides: Which pipelines are still needed, where they run (STACKIT Git Pipelines or the customer's own CI/CD), how each trigger, step, secret and approval is written again, and who owns the workflow files. the review the assessment asks for: Write the workflow in the repository: one job (or step) per build step, with the same container images or their STACKIT equivalents; Recreate every secret the build reads in STACKIT Secrets Manager (read with the vault action) or as a repository secret; the values of the source are never copied; Write the trigger conditions again as the on: section of the workflow and verify each event kind against the Forgejo Actions reference; Decide where images and artifacts go: the package registry of STACKIT Git takes container images from any OCI client, or keep your own registry, and change the image names in the deploy steps.; whether google_cloudbuild_trigger.init exists in production, and with how many instances: the value production gives var.init
  • Done when: the module terraform/proposals/proposal_init is switched on with its values and checked, or the design that replaces it is set up and checked; the decision records which
  • Source: google_cloudbuild_trigger.init
  • State: not executable — requires a design decision — blocked by DEC-GEN-CONVERT-PROPOSED-ALTERNATIVE/google_cloudbuild_trigger.init; and, as the plan states: the assessment says: A Cloud Build trigger starts a build when a source event happens (a push or a pull request on GitHub, Bitbucket Server, a repository of the 2nd generation, Developer Connect or Cloud Source Repositories), when a Pub/Sub message arrives, when a webhook is called or when a person runs it. The build is a list of container steps (a build block, or a file in the repository) with substitutions, images and artifacts to push, secrets from Secret Manager, a service account, a log destination and optionally a manual approval. STACKIT has no trigger or build resource. The same delivery is a workflow of STACKIT Git Pipelines (workflows written for GitHub Actions; STACKIT-managed or custom runners; secrets from STACKIT Secrets Manager or the repository; a manual-approval action) or the customer's own CI/CD. Nothing is converted: the trigger conditions (branch and tag expressions, file filters, the event kind), the steps (a container step becomes a step or a job with a container), the secrets (created again, never copied), the service account (replaced by the credentials the workflow uses against STACKIT), the log destination and the approval are written again by a person. The pages read describe pipelines that run when changes are pushed; other events (pull requests, schedules, manual runs) are verified against the Forgejo Actions reference; the assessment classifies google_cloudbuild_trigger.init as redesign: only its switched-off module holds a target, and that is a proposal; 6 critical settings of google_cloudbuild_trigger.init are open and part of this step: project-residency, build-identity, log-destination, deployment-artifact, entry-point, dep-references (assurance/critical-semantics.json says why each is open and what closes it); google_cloudbuild_trigger.init exists only under a condition of the source (applies only if its count (var.init ? 1 : 0) holds when OpenTofu plans — present with the defaults of var.init (1 instance); a variable file (-var-file), -var or TF_VAR_init can change that)
  • Risk: the assessment says: A Cloud Build trigger starts a build when a source event happens (a push or a pull request on GitHub, Bitbucket Server, a repository of the 2nd generation, Developer Connect or Cloud Source Repositories), when a Pub/Sub message arrives, when a webhook is called or when a person runs it. The build is a list of container steps (a build block, or a file in the repository) with substitutions, images and artifacts to push, secrets from Secret Manager, a service account, a log destination and optionally a manual approval. STACKIT has no trigger or build resource. The same delivery is a workflow of STACKIT Git Pipelines (workflows written for GitHub Actions; STACKIT-managed or custom runners; secrets from STACKIT Secrets Manager or the repository; a manual-approval action) or the customer's own CI/CD. Nothing is converted: the trigger conditions (branch and tag expressions, file filters, the event kind), the steps (a container step becomes a step or a job with a container), the secrets (created again, never copied), the service account (replaced by the credentials the workflow uses against STACKIT), the log destination and the approval are written again by a person. The pages read describe pipelines that run when changes are pushed; other events (pull requests, schedules, manual runs) are verified against the Forgejo Actions reference.
  • Risk: the assessment classifies google_cloudbuild_trigger.init as redesign: only its switched-off module holds a target, and that is a proposal
  • Risk: 6 critical settings of google_cloudbuild_trigger.init are open and part of this step: project-residency, build-identity, log-destination, deployment-artifact, entry-point, dep-references (assurance/critical-semantics.json says why each is open and what closes it)
  • Risk: google_cloudbuild_trigger.init exists only under a condition of the source (applies only if its count (var.init ? 1 : 0) holds when OpenTofu plans — present with the defaults of var.init (1 instance); a variable file (-var-file), -var or TF_VAR_init can change that)
  • Rollback (manual): the plan does not know how this step is undone: the person who carries it out records how
The proposed alternative of google_cloudbuild_trigger.placeholder (MAN/unit/google_cloudbuild_trigger.placeholder)
  • Phase: Workloads
  • What: Decide whether the STACKIT alternative in the switched-off module terraform/proposals/proposal_placeholder replaces google_cloudbuild_trigger.placeholder (google_cloudbuild_trigger): read the module, accept the stated difference, give it its values and set proposal_placeholder_enabled to true, or set the target up another way by hand
  • Why: STACKIT has no trigger or build resource.
  • Who: Platform Architect
  • When: applies only if its count (var.init ? 1 : 0) holds when OpenTofu plans — present with the defaults of var.init (1 instance); a variable file (-var-file), -var or TF_VAR_init can change that
  • Needs: what the customer decides: Which pipelines are still needed, where they run (STACKIT Git Pipelines or the customer's own CI/CD), how each trigger, step, secret and approval is written again, and who owns the workflow files. the review the assessment asks for: Write the workflow in the repository: one job (or step) per build step, with the same container images or their STACKIT equivalents; Recreate every secret the build reads in STACKIT Secrets Manager (read with the vault action) or as a repository secret; the values of the source are never copied; Write the trigger conditions again as the on: section of the workflow and verify each event kind against the Forgejo Actions reference; Decide where images and artifacts go: the package registry of STACKIT Git takes container images from any OCI client, or keep your own registry, and change the image names in the deploy steps.; whether google_cloudbuild_trigger.placeholder exists in production, and with how many instances: the value production gives var.init
  • Done when: the module terraform/proposals/proposal_placeholder is switched on with its values and checked, or the design that replaces it is set up and checked; the decision records which
  • Source: google_cloudbuild_trigger.placeholder
  • State: not executable — requires a design decision — blocked by DEC-GEN-CONVERT-PROPOSED-ALTERNATIVE/google_cloudbuild_trigger.placeholder; and, as the plan states: the assessment says: A Cloud Build trigger starts a build when a source event happens (a push or a pull request on GitHub, Bitbucket Server, a repository of the 2nd generation, Developer Connect or Cloud Source Repositories), when a Pub/Sub message arrives, when a webhook is called or when a person runs it. The build is a list of container steps (a build block, or a file in the repository) with substitutions, images and artifacts to push, secrets from Secret Manager, a service account, a log destination and optionally a manual approval. STACKIT has no trigger or build resource. The same delivery is a workflow of STACKIT Git Pipelines (workflows written for GitHub Actions; STACKIT-managed or custom runners; secrets from STACKIT Secrets Manager or the repository; a manual-approval action) or the customer's own CI/CD. Nothing is converted: the trigger conditions (branch and tag expressions, file filters, the event kind), the steps (a container step becomes a step or a job with a container), the secrets (created again, never copied), the service account (replaced by the credentials the workflow uses against STACKIT), the log destination and the approval are written again by a person. The pages read describe pipelines that run when changes are pushed; other events (pull requests, schedules, manual runs) are verified against the Forgejo Actions reference; the assessment classifies google_cloudbuild_trigger.placeholder as redesign: only its switched-off module holds a target, and that is a proposal; 6 critical settings of google_cloudbuild_trigger.placeholder are open and part of this step: project-residency, build-identity, log-destination, deployment-artifact, dep-references, linked-dependency:google_firebase_hosting_site (assurance/critical-semantics.json says why each is open and what closes it); google_cloudbuild_trigger.placeholder exists only under a condition of the source (applies only if its count (var.init ? 1 : 0) holds when OpenTofu plans — present with the defaults of var.init (1 instance); a variable file (-var-file), -var or TF_VAR_init can change that)
  • Risk: the assessment says: A Cloud Build trigger starts a build when a source event happens (a push or a pull request on GitHub, Bitbucket Server, a repository of the 2nd generation, Developer Connect or Cloud Source Repositories), when a Pub/Sub message arrives, when a webhook is called or when a person runs it. The build is a list of container steps (a build block, or a file in the repository) with substitutions, images and artifacts to push, secrets from Secret Manager, a service account, a log destination and optionally a manual approval. STACKIT has no trigger or build resource. The same delivery is a workflow of STACKIT Git Pipelines (workflows written for GitHub Actions; STACKIT-managed or custom runners; secrets from STACKIT Secrets Manager or the repository; a manual-approval action) or the customer's own CI/CD. Nothing is converted: the trigger conditions (branch and tag expressions, file filters, the event kind), the steps (a container step becomes a step or a job with a container), the secrets (created again, never copied), the service account (replaced by the credentials the workflow uses against STACKIT), the log destination and the approval are written again by a person. The pages read describe pipelines that run when changes are pushed; other events (pull requests, schedules, manual runs) are verified against the Forgejo Actions reference.
  • Risk: the assessment classifies google_cloudbuild_trigger.placeholder as redesign: only its switched-off module holds a target, and that is a proposal
  • Risk: 6 critical settings of google_cloudbuild_trigger.placeholder are open and part of this step: project-residency, build-identity, log-destination, deployment-artifact, dep-references, linked-dependency:google_firebase_hosting_site (assurance/critical-semantics.json says why each is open and what closes it)
  • Risk: google_cloudbuild_trigger.placeholder exists only under a condition of the source (applies only if its count (var.init ? 1 : 0) holds when OpenTofu plans — present with the defaults of var.init (1 instance); a variable file (-var-file), -var or TF_VAR_init can change that)
  • Rollback (manual): the plan does not know how this step is undone: the person who carries it out records how

</details>

WP-21 — Kubernetes apply (workloads)

  • Owner: Platform Architect
  • Steps: 2 (2 wait for a manual prerequisite)
  • Affected resources: google_cloud_run_v2_job.migrate, google_cloud_run_v2_service.server
  • State: a person can start after WP-18 is done
  • Done when: each step meets the condition stated with it below (2 different conditions).

<details> <summary>The 2 manual steps of WP-21</summary>

Alike steps on 2 resources (google_cloud_run_v2_job, google_cloud_run_v2_service) — MAN/draft/kubernetes/{resource}/manifests-yaml
  • Phase: Workloads
  • Why: the draft writes Kubernetes manifests for a workload of the source, not Terraform: a person applies them to the cluster
  • Who: Platform Architect
  • When: Timing to be scheduled by the owner
  • Needs: No input is named for this step
  • State: executable once its manual prerequisite is done: it waits for the step "Fetch the kubeconfig, check the health, create the namespace and the secrets of…", a manual prerequisite
  • After, for every step: the step "Fetch the kubeconfig, check the health, create the namespace and the secrets of…", the OpenTofu step for stackit_ske_cluster.containers

The steps, each by its name, with the resource that stands for {resource} above, its target objects, the steps it comes after besides those above and the lines that are its own (for a line above with a placeholder in braces, what stands for it):

  • The step "Fill in the required inputs of kubernetes/migrate/manifests.yaml and apply it…" (MAN/draft/kubernetes/migrate/manifests-yaml) — {resource} = google_cloud_run_v2_job.migrate - What: Fill in the required inputs of kubernetes/migrate/manifests.yaml and apply it to the cluster stackit_ske_cluster.containers: kubectl apply -n \<namespace> -f \<manifest file>; the Job is written suspended and runs when you unsuspend it - Document: terraform/kubernetes/migrate/manifests.yaml - Done when: the Job of terraform/kubernetes/migrate/manifests.yaml is applied to the cluster with its inputs filled in and suspended, and the Cloud Run job still exists - Risk: this step carries 1 critical setting of google_cloud_run_v2_job.migrate that the assessment passes to a person and the final artifact shows - tc-cloud-run-v2-job-template--template--containers--env--name: the 1 environment keys of the source (DJANGO_ENV) are the env entries of terraform/kubernetes/migrate/manifests.yaml: the keys are carried, and the values stay the person's; no manual step stands for it - Rollback (manual): the plan does not know how this step is undone: terraform/kubernetes/migrate/manifests.yaml says how where it has a rollback step, else the person who carries it out records how
  • The step "Fill in the required inputs of kubernetes/server/manifests.yaml and apply it to…" (MAN/draft/kubernetes/server/manifests-yaml) — {resource} = google_cloud_run_v2_service.server - What: Fill in the required inputs of kubernetes/server/manifests.yaml and apply it to the cluster stackit_ske_cluster.containers (put 0 in the number of pods): kubectl apply -n \<namespace> -f \<manifest file>; no pod starts. The pods start when the data they connect to is moved, by the person who runs the cutover (step 2 of the header) - Document: terraform/kubernetes/server/manifests.yaml - Done when: every manifest of terraform/kubernetes/server/manifests.yaml is applied to the cluster with its inputs filled in and no pod running, and the Cloud Run resource still runs - Rollback (manual): the plan does not know how this step is undone: terraform/kubernetes/server/manifests.yaml says how where it has a rollback step, else the person who carries it out records how

</details>

WP-22 — Move the data and the objects to the target

  • Owner: Data Architect
  • Steps: 8 (2 wait for a manual prerequisite, 6 require a design decision)
  • Affected resources: google_sql_database_instance.postgres, google_storage_bucket.media
  • Rests on: DEC-GEN-CONVERT-DATA-MOVEMENT
  • Waits for: DEC-GEN-CONVERT-DATA-MOVEMENT
  • State: waits for decisions (6 of 8 manual steps)
  • Done when: each step meets the condition stated with it below (6 different conditions).

<details> <summary>The 8 manual steps of WP-22</summary>

The final copy of the data of google_sql_database_instance.postgres by dump and restore (MAN/data/google_sql_database_instance.postgres/final-sync/dump-restore)
  • Phase: Cutover
  • What: Dump inside the freeze: PGPASSWORD="$SOURCE_PGPASSWORD" PGSSLMODE=require pg_dump --host=\<source-host> --port=\<source-port> --username=\<source-user> --dbname=\<source-db> --format=directory --jobs=4 --no-owner --no-acl --file=\<dump-dir> (Google's page names --no-owner and --no-acl for a dump of one database and the directory format for a parallel dump; dump one database at a time, since pg_dumpall fails for databases with several owners). Restore as the database owner into the empty database: PGPASSWORD="$TARGET_PGPASSWORD" PGSSLMODE=require pg_restore --host=\<target-host> --port=\<target-port> --username=\<target-user> --dbname=\<target-db> --jobs=4 --no-owner --no-acl \<dump-dir> 2> restore.log, then read restore.log line by line; create the extensions of the source that the target lists and settle the ones it does not. Runbook section: terraform/handover/database-postgres/data-move.md#final-sync-dump-restore.
  • Why: the dump is a consistent snapshot of the moment it starts; inside the freeze it is the final state of the source
  • Who: Data Architect
  • When: Timing to be scheduled by the owner
  • Needs: the customer's statements in inputs.yaml: source.dataset.downtime; the maintenance window is not given: the plan states none and invents none; state the window, the blackout days and the longest downtime of one switch with ops.maintenance_window in the inputs file (inputs-template.yaml shows the empty answer), then run again
  • Done when: pg_restore finished and every error in restore.log is settled
  • Source: google_sql_database_instance.postgres
  • After: the write freeze of google_sql_database_instance.postgres by dump and restore
  • State: not executable — requires a design decision — blocked by DEC-GEN-CONVERT-DATA-MOVEMENT; it also comes after the write freeze of google_sql_database_instance.postgres by dump and restore, a manual prerequisite; and, as the plan states: the freeze lasts as long as the dump and the restore; this step holds only once DEC-GEN-CONVERT-DATA-MOVEMENT is answered: it is of the path export-and-import-with-the-engine-clients of google_sql_database_instance.postgres, not of the path the draft carries out by default
  • Risk: the freeze lasts as long as the dump and the restore
  • Risk: this step holds only once DEC-GEN-CONVERT-DATA-MOVEMENT is answered: it is of the path export-and-import-with-the-engine-clients of google_sql_database_instance.postgres, not of the path the draft carries out by default
  • Rollback (manual): the source is current to the freeze and untouched: put the writers back on it; drop and recreate the target database for another attempt
The data reconciliation of google_sql_database_instance.postgres by dump and restore (MAN/data/google_sql_database_instance.postgres/reconcile/dump-restore)
  • Phase: Cutover
  • What: Both sides, with the writers stopped: per-table counts (SELECT format('SELECT %L AS tbl, count(*) FROM %I.%I;', schemaname || '.' || tablename, schemaname, tablename) FROM pg_tables WHERE schemaname NOT IN ('pg_catalog', 'information_schema'); run on each side), an md5 of every keyed table (SELECT count(*), md5(string_agg(t::text, ',' ORDER BY t.\<key-column>)) FROM \<schema>.\<table> t;, a key range for a large table, DateStyle, TimeZone and extra_float_digits equal in both sessions), the sequences, and one read query the application itself runs on its most important data, \<application-query>: the query and the answer are the customer's, the source does not state them. Record GO or NO-GO. Runbook section: terraform/handover/database-postgres/data-move.md#reconcile-dump-restore.
  • Why: the move is accepted only when the numbers equal on both sides; a mismatch is a NO-GO
  • Who: Data Architect
  • When: Timing to be scheduled by the owner
  • Needs: the answer of DEC-GEN-CONVERT-DATA-MOVEMENT in decisions.yaml, which names the path this step belongs to
  • Done when: counts, checksums, sequences and the application query agree: GO
  • Checks after the transfer (8, the owner records GO only when every one passes): written once, in terraform/handover/database-postgres/data-move.md, the section "Verification: the checks after the transfer" under "Reconcile (dump-restore)", with the id, the command, the expected result and what is not provided of each; execution-plan.json holds the same checks as data
  • Source: google_sql_database_instance.postgres
  • After: the final copy of the data of google_sql_database_instance.postgres by dump and restore
  • State: not executable — requires a design decision — blocked by DEC-GEN-CONVERT-DATA-MOVEMENT; it also comes after the final copy of the data of google_sql_database_instance.postgres by dump and restore, a manual prerequisite; and, as the plan states: this step holds only once DEC-GEN-CONVERT-DATA-MOVEMENT is answered: it is of the path export-and-import-with-the-engine-clients of google_sql_database_instance.postgres, not of the path the draft carries out by default
  • Risk: this step holds only once DEC-GEN-CONVERT-DATA-MOVEMENT is answered: it is of the path export-and-import-with-the-engine-clients of google_sql_database_instance.postgres, not of the path the draft carries out by default
  • Rollback (manual): on NO-GO take the rollback without switching anything: the clients still point at the source, which is current to the freeze
The final copy of the data of google_sql_database_instance.postgres by logical replication (MAN/data/google_sql_database_instance.postgres/final-sync/logical-replication)
  • Phase: Cutover
  • What: Wait until the lag is 0. Copy the sequences: on the source SELECT format('SELECT setval(%L, %s);', schemaname || '.' || sequencename, last_value) FROM pg_sequences WHERE last_value IS NOT NULL; with psql --tuples-only --no-align, and run the output on the target. Then disable the subscription on the target: ALTER SUBSCRIPTION stacktoit_move DISABLE; (the slot stays on the source, so the way back stays open). Runbook section: terraform/handover/database-postgres/data-move.md#final-sync-logical-replication.
  • Why: sequences are not replicated: without setval the first insert on the target repeats a key
  • Who: Data Architect
  • When: Timing to be scheduled by the owner
  • Needs: the customer's statements in inputs.yaml: source.dataset.downtime; the maintenance window is not given: the plan states none and invents none; state the window, the blackout days and the longest downtime of one switch with ops.maintenance_window in the inputs file (inputs-template.yaml shows the empty answer), then run again
  • Done when: the lag was 0, the sequences on the target are not lower than the source's, the subscription is disabled
  • Source: google_sql_database_instance.postgres
  • After: the write freeze of google_sql_database_instance.postgres by logical replication
  • State: not executable — requires a design decision — blocked by DEC-GEN-CONVERT-DATA-MOVEMENT; it also comes after the write freeze of google_sql_database_instance.postgres by logical replication, a manual prerequisite; and, as the plan states: this step holds only once DEC-GEN-CONVERT-DATA-MOVEMENT is answered: it is of the path replicate-with-the-engine-replication of google_sql_database_instance.postgres, not of the path the draft carries out by default
  • Risk: this step holds only once DEC-GEN-CONVERT-DATA-MOVEMENT is answered: it is of the path replicate-with-the-engine-replication of google_sql_database_instance.postgres, not of the path the draft carries out by default
  • Rollback (manual): the slot is still on the source and the source untouched: put the writers back on the source
The data reconciliation of google_sql_database_instance.postgres by logical replication (MAN/data/google_sql_database_instance.postgres/reconcile/logical-replication)
  • Phase: Cutover
  • What: Both sides, with the writers stopped: per-table counts (SELECT format('SELECT %L AS tbl, count(*) FROM %I.%I;', schemaname || '.' || tablename, schemaname, tablename) FROM pg_tables WHERE schemaname NOT IN ('pg_catalog', 'information_schema'); run on each side), an md5 of every keyed table (SELECT count(*), md5(string_agg(t::text, ',' ORDER BY t.\<key-column>)) FROM \<schema>.\<table> t;, a key range for a large table, DateStyle, TimeZone and extra_float_digits equal in both sessions), the sequences, and one read query the application itself runs on its most important data, \<application-query>: the query and the answer are the customer's, the source does not state them. Record GO or NO-GO. Runbook section: terraform/handover/database-postgres/data-move.md#reconcile-logical-replication.
  • Why: the move is accepted only when the numbers equal on both sides; a mismatch is a NO-GO
  • Who: Data Architect
  • When: Timing to be scheduled by the owner
  • Needs: the answer of DEC-GEN-CONVERT-DATA-MOVEMENT in decisions.yaml, which names the path this step belongs to
  • Done when: counts, checksums, sequences and the application query agree: GO
  • Checks after the transfer (10, the owner records GO only when every one passes): written once, in terraform/handover/database-postgres/data-move.md, the section "Verification: the checks after the transfer" under "Reconcile (logical-replication)", with the id, the command, the expected result and what is not provided of each; execution-plan.json holds the same checks as data
  • Source: google_sql_database_instance.postgres
  • After: the final copy of the data of google_sql_database_instance.postgres by logical replication
  • State: not executable — requires a design decision — blocked by DEC-GEN-CONVERT-DATA-MOVEMENT; it also comes after the final copy of the data of google_sql_database_instance.postgres by logical replication, a manual prerequisite; and, as the plan states: this step holds only once DEC-GEN-CONVERT-DATA-MOVEMENT is answered: it is of the path replicate-with-the-engine-replication of google_sql_database_instance.postgres, not of the path the draft carries out by default
  • Risk: this step holds only once DEC-GEN-CONVERT-DATA-MOVEMENT is answered: it is of the path replicate-with-the-engine-replication of google_sql_database_instance.postgres, not of the path the draft carries out by default
  • Rollback (manual): on NO-GO take the rollback without switching anything: the clients still point at the source, which is current to the freeze
The final copy of the data of google_sql_database_instance.postgres with a tool of your own (MAN/data/google_sql_database_instance.postgres/final-sync/own-tool)
  • Phase: Cutover
  • What: Copy the data of google_sql_database_instance.postgres to stackit_postgresflex_instance.postgres with the migration tool or the replication you chose: write its steps, the checks before the move, the freeze window it needs and the way back. Runbook section: terraform/handover/database-postgres/data-move.md#final-sync-own-tool.
  • Why: the draft creates the instance empty and the plan knows nothing of a tool of your own, so it holds the step and the person who chose the tool writes the steps
  • Who: Data Architect
  • When: Timing to be scheduled by the owner
  • Needs: the tool, its steps, the checks before the move, the freeze window and the way back, from the person who runs it; the maintenance window is not given: the plan states none and invents none; state the window, the blackout days and the longest downtime of one switch with ops.maintenance_window in the inputs file (inputs-template.yaml shows the empty answer), then run again
  • Done when: the data of google_sql_database_instance.postgres is in stackit_postgresflex_instance.postgres and the checks of the tool hold
  • Source: google_sql_database_instance.postgres
  • After: the write freeze of google_sql_database_instance.postgres with a tool of your own
  • State: not executable — requires a design decision — blocked by DEC-GEN-CONVERT-DATA-MOVEMENT; it also comes after the write freeze of google_sql_database_instance.postgres with a tool of your own, a manual prerequisite; and, as the plan states: this step holds only once DEC-GEN-CONVERT-DATA-MOVEMENT is answered: it is of the path migrate-with-a-tool-of-my-own of google_sql_database_instance.postgres, not of the path the draft carries out by default
  • Risk: this step holds only once DEC-GEN-CONVERT-DATA-MOVEMENT is answered: it is of the path migrate-with-a-tool-of-my-own of google_sql_database_instance.postgres, not of the path the draft carries out by default
  • Rollback (manual): the writers are stopped, not removed, and nothing the plan does changes the source database, so the source database is the way back until the reconcile records GO: put the writers back on it; before another attempt drop what the tool wrote into the databases of stackit_postgresflex_instance.postgres (the draft created them empty); check that the tool you chose does not change the source database, since the plan knows nothing of it
The data reconciliation of google_sql_database_instance.postgres with a tool of your own (MAN/data/google_sql_database_instance.postgres/reconcile/own-tool)
  • Phase: Cutover
  • What: Both sides, with the writers stopped: per-table counts (SELECT format('SELECT %L AS tbl, count(*) FROM %I.%I;', schemaname || '.' || tablename, schemaname, tablename) FROM pg_tables WHERE schemaname NOT IN ('pg_catalog', 'information_schema'); run on each side), an md5 of every keyed table (SELECT count(*), md5(string_agg(t::text, ',' ORDER BY t.\<key-column>)) FROM \<schema>.\<table> t;, a key range for a large table, DateStyle, TimeZone and extra_float_digits equal in both sessions), the sequences, and one read query the application itself runs on its most important data, \<application-query>: the query and the answer are the customer's, the source does not state them. Record GO or NO-GO. Runbook section: terraform/handover/database-postgres/data-move.md#reconcile-own-tool.
  • Why: the move is accepted only when the numbers equal on both sides; a mismatch is a NO-GO
  • Who: Data Architect
  • When: Timing to be scheduled by the owner
  • Needs: the answer of DEC-GEN-CONVERT-DATA-MOVEMENT in decisions.yaml, which names the path this step belongs to
  • Done when: counts, checksums, sequences and the application query agree: GO
  • Checks after the transfer (7, the owner records GO only when every one passes): written once, in terraform/handover/database-postgres/data-move.md, the section "Verification: the checks after the transfer" under "Reconcile (own-tool)", with the id, the command, the expected result and what is not provided of each; execution-plan.json holds the same checks as data
  • Source: google_sql_database_instance.postgres
  • After: the final copy of the data of google_sql_database_instance.postgres with a tool of your own
  • State: not executable — requires a design decision — blocked by DEC-GEN-CONVERT-DATA-MOVEMENT; it also comes after the final copy of the data of google_sql_database_instance.postgres with a tool of your own, a manual prerequisite; and, as the plan states: this step holds only once DEC-GEN-CONVERT-DATA-MOVEMENT is answered: it is of the path migrate-with-a-tool-of-my-own of google_sql_database_instance.postgres, not of the path the draft carries out by default
  • Risk: this step holds only once DEC-GEN-CONVERT-DATA-MOVEMENT is answered: it is of the path migrate-with-a-tool-of-my-own of google_sql_database_instance.postgres, not of the path the draft carries out by default
  • Rollback (manual): on NO-GO take the rollback without switching anything: the clients still point at the source, which is current to the freeze
The final copy of the data of google_storage_bucket.media with rclone (MAN/data/google_storage_bucket.media/final-sync/rclone-copy)
  • Phase: Cutover
  • What: Inside the freeze run the rclone copy of the initial copy once more and read how many objects it transferred; run it a second time: it transfers nothing. Then see what the target holds that the source no longer has: rclone sync SRC:\<source-bucket> DST:\<target-bucket> --dry-run prints what a sync would delete; read the list, and only a person who has read it runs the sync without --dry-run (the target must equal the source at the cutover, and an object deleted in the source after the initial copy is otherwise still served). Runbook section: terraform/handover/bucket-media/data-copy.md#final-sync-rclone-copy.
  • Why: the pass inside the freeze copies the last changes; the dry run is the only place where the deletions of the source after the initial copy are seen
  • Who: Data Architect
  • When: Timing to be scheduled by the owner
  • Needs: the customer's statements in inputs.yaml: source.dataset.downtime; the maintenance window is not given: the plan states none and invents none; state the window, the blackout days and the longest downtime of one switch with ops.maintenance_window in the inputs file (inputs-template.yaml shows the empty answer), then run again
  • Done when: the second pass transfers nothing and every object that exists only on the target is settled
  • Source: google_storage_bucket.media
  • After: the write freeze of google_storage_bucket.media with rclone
  • State: executable once its manual prerequisite is done: it waits for the write freeze of google_storage_bucket.media with rclone, a manual prerequisite
  • Risk: the freeze lasts as long as the last pass and the settlement of the deletions
  • Rollback (manual): the source is current to the freeze and untouched: put the writers back on it
The data reconciliation of google_storage_bucket.media with rclone (MAN/data/google_storage_bucket.media/reconcile/rclone-copy)
  • Phase: Cutover
  • What: Both sides, with the writers stopped: the number of objects and the bytes (rclone size SRC:\<source-bucket> --json, rclone size DST:\<target-bucket> --json); the list of keys (rclone lsf -R --files-only SRC:\<source-bucket> | sort > source-keys.txt, the same for the target, diff source-keys.txt target-keys.txt is empty); and the content: rclone check SRC:\<source-bucket> DST:\<target-bucket> --one-way --combined=\<check-file> compares size and hash where both sides have one and marks every difference, so read the file and the log; an object for which the two sides share no hash (the target's ETag of a multipart upload is not an MD5) is at best compared by size, which is not its content, so run rclone check ... --download for the prefixes that matter and for a sample of the rest. One read that the application itself does on its most important objects through the STACKIT endpoint with a key of a credentials group: \<application-read>, the query and the answer are the customer's, the source does not state them. Record GO or NO-GO. Runbook section: terraform/handover/bucket-media/data-copy.md#reconcile-rclone-copy.
  • Why: the move is accepted only when the inventory and the content agree on both sides; a mismatch is a NO-GO
  • Who: Data Architect
  • When: Timing to be scheduled by the owner
  • Needs: No input is named for this step
  • Done when: the counts, the key lists and the checks agree, the application read works: GO
  • Checks after the transfer (5, the owner records GO only when every one passes): written once, in terraform/handover/bucket-media/data-copy.md, the section "Verification: the checks after the transfer" under "Reconcile (rclone-copy)", with the id, the command, the expected result and what is not provided of each; execution-plan.json holds the same checks as data
  • Source: google_storage_bucket.media
  • After: the final copy of the data of google_storage_bucket.media with rclone
  • State: executable once its manual prerequisite is done: it waits for the final copy of the data of google_storage_bucket.media with rclone, a manual prerequisite
  • Risk: a hash that rclone does not have for an object leaves that object compared by size only
  • Rollback (manual): on NO-GO take the rollback without switching anything: the clients still point at the source, which is current to the freeze

</details>

WP-23 — Move the data and the objects to the target

  • Owner: Migration Owner
  • Steps: 4 (1 waits for a manual prerequisite, 3 require a design decision)
  • Affected resources: google_sql_database_instance.postgres, google_storage_bucket.media
  • Rests on: DEC-GEN-CONVERT-DATA-MOVEMENT
  • Waits for: DEC-GEN-CONVERT-DATA-MOVEMENT
  • State: waits for decisions (3 of 4 manual steps)
  • Done when: no client writes to the source, for each of its steps.

<details> <summary>The 4 manual steps of WP-23</summary>

The write freeze of google_sql_database_instance.postgres by dump and restore (MAN/data/google_sql_database_instance.postgres/write-freeze/dump-restore)
  • Phase: Cutover
  • What: List who writes (SELECT usename, application_name, client_addr, count(*) FROM pg_stat_activity WHERE datname = '\<source-db>' GROUP BY 1, 2, 3 ORDER BY 4 DESC;) and record the connection settings of every client where they can be restored from. Then stop the writers at the application (services, jobs, migrations, cron). Do not make the source read-only and do not change it. Check: SELECT count(*) FROM pg_stat_activity WHERE datname = '\<source-db>' AND state <> 'idle' AND pid <> pg_backend_pid(); is 0. Runbook section: terraform/handover/database-postgres/data-move.md#write-freeze-dump-restore.
  • Why: the source stays read-write and untouched, so the way back needs nothing on the source
  • Who: Migration Owner
  • When: Timing to be scheduled by the owner
  • Needs: the customer's statements in inputs.yaml: source.dataset.downtime; the maintenance window is not given: the plan states none and invents none; state the window, the blackout days and the longest downtime of one switch with ops.maintenance_window in the inputs file (inputs-template.yaml shows the empty answer), then run again
  • Done when: no client writes to the source
  • Source: google_sql_database_instance.postgres
  • After: the data rehearsal of google_sql_database_instance.postgres
  • State: not executable — requires a design decision — blocked by DEC-GEN-CONVERT-DATA-MOVEMENT; it also comes after the data rehearsal of google_sql_database_instance.postgres, a manual prerequisite; and, as the plan states: this step holds only once DEC-GEN-CONVERT-DATA-MOVEMENT is answered: it is of the path export-and-import-with-the-engine-clients of google_sql_database_instance.postgres, not of the path the draft carries out by default
  • Risk: this step holds only once DEC-GEN-CONVERT-DATA-MOVEMENT is answered: it is of the path export-and-import-with-the-engine-clients of google_sql_database_instance.postgres, not of the path the draft carries out by default
  • Rollback (manual): start the writers again against the source: nothing was changed
The write freeze of google_sql_database_instance.postgres by logical replication (MAN/data/google_sql_database_instance.postgres/write-freeze/logical-replication)
  • Phase: Cutover
  • What: List who writes (SELECT usename, application_name, client_addr, count(*) FROM pg_stat_activity WHERE datname = '\<source-db>' GROUP BY 1, 2, 3 ORDER BY 4 DESC;) and record the connection settings of every client where they can be restored from. Then stop the writers at the application (services, jobs, migrations, cron). Do not make the source read-only and do not change it. Check: SELECT count(*) FROM pg_stat_activity WHERE datname = '\<source-db>' AND state <> 'idle' AND pid <> pg_backend_pid(); is 0. Runbook section: terraform/handover/database-postgres/data-move.md#write-freeze-logical-replication.
  • Why: the source stays read-write and untouched, so the way back needs nothing on the source
  • Who: Migration Owner
  • When: Timing to be scheduled by the owner
  • Needs: the customer's statements in inputs.yaml: source.dataset.downtime; the maintenance window is not given: the plan states none and invents none; state the window, the blackout days and the longest downtime of one switch with ops.maintenance_window in the inputs file (inputs-template.yaml shows the empty answer), then run again
  • Done when: no client writes to the source
  • Source: google_sql_database_instance.postgres
  • After: the replication watch of google_sql_database_instance.postgres by logical replication
  • State: not executable — requires a design decision — blocked by DEC-GEN-CONVERT-DATA-MOVEMENT; it also comes after the replication watch of google_sql_database_instance.postgres by logical replication, a manual prerequisite; and, as the plan states: this step holds only once DEC-GEN-CONVERT-DATA-MOVEMENT is answered: it is of the path replicate-with-the-engine-replication of google_sql_database_instance.postgres, not of the path the draft carries out by default
  • Risk: this step holds only once DEC-GEN-CONVERT-DATA-MOVEMENT is answered: it is of the path replicate-with-the-engine-replication of google_sql_database_instance.postgres, not of the path the draft carries out by default
  • Rollback (manual): start the writers again against the source: nothing was changed
The write freeze of google_sql_database_instance.postgres with a tool of your own (MAN/data/google_sql_database_instance.postgres/write-freeze/own-tool)
  • Phase: Cutover
  • What: List who writes (SELECT usename, application_name, client_addr, count(*) FROM pg_stat_activity WHERE datname = '\<source-db>' GROUP BY 1, 2, 3 ORDER BY 4 DESC;) and record the connection settings of every client where they can be restored from. Then stop the writers at the application (services, jobs, migrations, cron). Do not make the source read-only and do not change it. Check: SELECT count(*) FROM pg_stat_activity WHERE datname = '\<source-db>' AND state <> 'idle' AND pid <> pg_backend_pid(); is 0. Runbook section: terraform/handover/database-postgres/data-move.md#write-freeze-own-tool.
  • Why: the source stays read-write and untouched, so the way back needs nothing on the source
  • Who: Migration Owner
  • When: Timing to be scheduled by the owner
  • Needs: the customer's statements in inputs.yaml: source.dataset.downtime; the maintenance window is not given: the plan states none and invents none; state the window, the blackout days and the longest downtime of one switch with ops.maintenance_window in the inputs file (inputs-template.yaml shows the empty answer), then run again
  • Done when: no client writes to the source
  • Source: google_sql_database_instance.postgres
  • After: the data rehearsal of google_sql_database_instance.postgres
  • State: not executable — requires a design decision — blocked by DEC-GEN-CONVERT-DATA-MOVEMENT; it also comes after the data rehearsal of google_sql_database_instance.postgres, a manual prerequisite; and, as the plan states: this step holds only once DEC-GEN-CONVERT-DATA-MOVEMENT is answered: it is of the path migrate-with-a-tool-of-my-own of google_sql_database_instance.postgres, not of the path the draft carries out by default
  • Risk: this step holds only once DEC-GEN-CONVERT-DATA-MOVEMENT is answered: it is of the path migrate-with-a-tool-of-my-own of google_sql_database_instance.postgres, not of the path the draft carries out by default
  • Rollback (manual): start the writers again against the source: nothing was changed
The write freeze of google_storage_bucket.media with rclone (MAN/data/google_storage_bucket.media/write-freeze/rclone-copy)
  • Phase: Cutover
  • What: List who writes to the bucket: the units the README lists for it, and every job, partner and signed URL outside the scan (if Cloud Audit Logs record data writes for the bucket, its object writes and deletes name the principals; the Terraform does not show whether they are). Record the endpoint, the bucket name and the credential settings of every client where they can be restored from. Then stop the writers at the application (services, jobs, uploads). Do not change the source bucket (no read-only policy, no retention lock): it stays read-write, so the way back needs nothing on the source. Runbook section: terraform/handover/bucket-media/data-copy.md#write-freeze-rclone-copy.
  • Why: the source stays untouched, so the way back needs nothing on the source
  • Who: Migration Owner
  • When: Timing to be scheduled by the owner
  • Needs: the customer's statements in inputs.yaml: source.dataset.downtime; the maintenance window is not given: the plan states none and invents none; state the window, the blackout days and the longest downtime of one switch with ops.maintenance_window in the inputs file (inputs-template.yaml shows the empty answer), then run again
  • Done when: no client writes to the source
  • Source: google_storage_bucket.media
  • After: the replication watch of google_storage_bucket.media with rclone
  • State: executable once its manual prerequisite is done: it waits for the replication watch of google_storage_bucket.media with rclone, a manual prerequisite
  • Rollback (manual): start the writers again against the source: nothing was changed

</details>

WP-24 — Retire the source resources

  • Owner: Data Architect
  • Steps: 3 (1 waits for a manual prerequisite, 2 require a design decision)
  • Affected resources: google_sql_database_instance.postgres, google_storage_bucket.media
  • Rests on: DEC-GEN-CONVERT-DATA-MOVEMENT
  • Waits for: DEC-GEN-CONVERT-DATA-MOVEMENT
  • State: waits for decisions (2 of 3 manual steps)
  • Done when: each step meets the condition stated with it below (2 different conditions).

<details> <summary>The 3 manual steps of WP-24</summary>

The cleanup of the data move of google_sql_database_instance.postgres by dump and restore (MAN/data/google_sql_database_instance.postgres/cleanup/dump-restore)
  • Phase: Decommission
  • What: Only after the target has run in production for the period the customer decides and the way back is no longer wanted: remove the move host from network.acl of the instance and from the authorized networks of the source if it was added; delete the dump files and restore.log; keep a final backup of the source for the retention the customer decides before anything is deleted; replace the credentials of the target user if they were ever on a ticket or in a file. Runbook section: terraform/handover/database-postgres/data-move.md#clean-up-dump-restore.
  • Why: the move leaves a publication, a slot, a flag, an access entry and copies of the data behind; none of them stays
  • Who: Data Architect
  • When: after the owner records GO at the end of the verification, and once the way back is no longer wanted
  • Needs: the answer of DEC-GEN-CONVERT-DATA-MOVEMENT in decisions.yaml, which names the path this step belongs to
  • Done when: no slot, subscription, publication or dump file is left, the move host is out of every access list, the credentials are replaced
  • Source: google_sql_database_instance.postgres
  • After: the data reconciliation of google_sql_database_instance.postgres by dump and restore
  • State: not executable — requires a design decision — blocked by DEC-GEN-CONVERT-DATA-MOVEMENT; it also comes after the data reconciliation of google_sql_database_instance.postgres by dump and restore, a manual prerequisite; and, as the plan states: the source is not deleted in the same hour as the cutover; this step holds only once DEC-GEN-CONVERT-DATA-MOVEMENT is answered: it is of the path export-and-import-with-the-engine-clients of google_sql_database_instance.postgres, not of the path the draft carries out by default; the plan holds no point at which the owner records GO or ROLLBACK, so nothing in it orders this cleanup after the GO: do not run it before the owner has decided that the way back is no longer wanted
  • Risk: the source is not deleted in the same hour as the cutover
  • Risk: this step holds only once DEC-GEN-CONVERT-DATA-MOVEMENT is answered: it is of the path export-and-import-with-the-engine-clients of google_sql_database_instance.postgres, not of the path the draft carries out by default
  • Risk: the plan holds no point at which the owner records GO or ROLLBACK, so nothing in it orders this cleanup after the GO: do not run it before the owner has decided that the way back is no longer wanted
  • Rollback (manual): after the cleanup the way back is the final backup of the source; there is no other
The cleanup of the data move of google_sql_database_instance.postgres by logical replication (MAN/data/google_sql_database_instance.postgres/cleanup/logical-replication)
  • Phase: Decommission
  • What: Only after the target has run in production for the period the customer decides and the way back is no longer wanted: DROP SUBSCRIPTION stacktoit_move; on the target (it drops the slot on the source if the source is reachable; check SELECT * FROM pg_replication_slots;), DROP PUBLICATION stacktoit_move; and DROP ROLE stacktoit_repl; on the source, set cloudsql.logical_decoding back with a second restart if it was off before the move; remove the move host from network.acl of the instance and from the authorized networks of the source if it was added; delete the dump files and restore.log; keep a final backup of the source for the retention the customer decides before anything is deleted; replace the credentials of the target user if they were ever on a ticket or in a file. Runbook section: terraform/handover/database-postgres/data-move.md#clean-up-logical-replication.
  • Why: the move leaves a publication, a slot, a flag, an access entry and copies of the data behind; none of them stays
  • Who: Data Architect
  • When: after the owner records GO at the end of the verification, and once the way back is no longer wanted
  • Needs: the answer of DEC-GEN-CONVERT-DATA-MOVEMENT in decisions.yaml, which names the path this step belongs to
  • Done when: no slot, subscription, publication or dump file is left, the move host is out of every access list, the credentials are replaced
  • Source: google_sql_database_instance.postgres
  • After: the data reconciliation of google_sql_database_instance.postgres by logical replication
  • State: not executable — requires a design decision — blocked by DEC-GEN-CONVERT-DATA-MOVEMENT; it also comes after the data reconciliation of google_sql_database_instance.postgres by logical replication, a manual prerequisite; and, as the plan states: the source is not deleted in the same hour as the cutover; this step holds only once DEC-GEN-CONVERT-DATA-MOVEMENT is answered: it is of the path replicate-with-the-engine-replication of google_sql_database_instance.postgres, not of the path the draft carries out by default; the plan holds no point at which the owner records GO or ROLLBACK, so nothing in it orders this cleanup after the GO: do not run it before the owner has decided that the way back is no longer wanted
  • Risk: the source is not deleted in the same hour as the cutover
  • Risk: this step holds only once DEC-GEN-CONVERT-DATA-MOVEMENT is answered: it is of the path replicate-with-the-engine-replication of google_sql_database_instance.postgres, not of the path the draft carries out by default
  • Risk: the plan holds no point at which the owner records GO or ROLLBACK, so nothing in it orders this cleanup after the GO: do not run it before the owner has decided that the way back is no longer wanted
  • Rollback (manual): after the cleanup the way back is the final backup of the source; there is no other
The cleanup of the data move of google_storage_bucket.media with rclone (MAN/data/google_storage_bucket.media/cleanup/rclone-copy)
  • Phase: Decommission
  • What: Only after the target has run in production for the period the customer decides and the way back is no longer wanted: delete the key lists and the logs of the move (they name every object key); delete the credential made for the move, the Google login or key made for it, and take the environment names out of the shell history; keep the source bucket, with its versions, for the retention the customer decides before any object of it is deleted, and do not delete it in the same hour as the cutover. Runbook section: terraform/handover/bucket-media/data-copy.md#clean-up-rclone-copy.
  • Why: the move leaves key lists, logs, two credentials and an environment behind; none of them stays
  • Who: Data Architect
  • When: after the owner records GO at the end of the verification, and once the way back is no longer wanted
  • Needs: No input is named for this step
  • Done when: no key list or log is left, the credentials of the move are removed, and the retention of the source is recorded
  • Source: google_storage_bucket.media
  • After: the data reconciliation of google_storage_bucket.media with rclone
  • State: executable once its manual prerequisite is done: it waits for the data reconciliation of google_storage_bucket.media with rclone, a manual prerequisite
  • Risk: the source is not deleted in the same hour as the cutover
  • Risk: the plan holds no point at which the owner records GO or ROLLBACK, so nothing in it orders this cleanup after the GO: do not run it before the owner has decided that the way back is no longer wanted
  • Rollback (manual): after the cleanup the way back is the retained source bucket; there is none once it is deleted

</details>

Not migrated

The refusal of the setting force-destroy of google_storage_bucket.media (REF/semantic/google_storage_bucket.media/force-destroy)

  • Source capability: the critical setting force-destroy of google_storage_bucket.media
  • Reason: the target has no counterpart that carries this setting of the source (TARGET_SETTING_UNSUPPORTED)
  • Missing on the target: counterpart that carries the setting force-destroy of google_storage_bucket (the knowledge base marks it UNSUPPORTED)
  • Why: A check of the knowledge base (gcs force destroy) found a setting the target has no counterpart for (a finding of this unit describes it). stackit_objectstorage_bucket (pinned provider) "cannot be destroyed if the bucket contains objects" (schema description): force_destroy = false — a destroy that stops at a non-empty bucket — is what STACKIT does, PRESERVED; true has no counterpart, UNSUPPORTED (check gcs-force-destroy).
  • Redesign direction: To be defined by the architect
  • Source: google_storage_bucket.media
  • State: permanently unsupported: refused, not migrated (not executable)
  • Risk: what would close it, as the assessment says: A person accepts the difference or the design changes: nothing in the source can make the target carry it.
  • Risk: the migration of google_storage_bucket.media is not complete: STACKIT does not carry its critical setting force-destroy, which a redesign or an accepted difference has to settle
  • Rollback (available): nothing is done: a refused step creates nothing

The refusal of google_firebase_project.default (REF/unit/google_firebase_project.default)

  • Source capability: google_firebase_project.default (google_firebase_project)
  • Reason: no STACKIT service or resource carries this source resource (TARGET_RESOURCE_UNSUPPORTED)
  • Missing on the target: enabling of Firebase on a project
  • Why: Nothing on STACKIT holds the registration of an app or the enabling of Firebase on a project. It stays with Google for as long as any Firebase service (push messaging, for example) is used, and a person decides, app by app, which backend replaces the Firebase services the app calls. Adding Firebase to a project cannot be undone.
  • Redesign direction: List the Firebase services each app uses (authentication, database, storage, messaging, remote config); each needs its own replacement; The signing keys and store listings stay with Google Play and the Apple App Store; only the backend the app talks to moves.
  • Source: google_firebase_project.default
  • State: permanently unsupported: refused, not migrated (not executable)
  • Risk: google_firebase_project.default (google_firebase_project) has no execution phase of its own; the step is placed in the platform services phase
  • Risk: 1 critical setting of google_firebase_project.default is open and part of this step: tc-firebase-project-project (assurance/critical-semantics.json says why each is open and what closes it)
  • Rollback (available): nothing is done: a refused step creates nothing

The draft writes no resource for these source resources; each is a manual step above, carried out by a person: google_firebase_hosting_site.client, google_cloudbuild_trigger.activategcb, google_cloudbuild_trigger.init, google_cloudbuild_trigger.placeholder.

STACKIT API scripts

The plan holds 2 STACKIT API steps. Each is a script of this package that a person runs; StackToIT has run none of them. The state says how far the step has come, and execution/README.md says what each state means.

Operator checks before running a script that is not verified live: the endpoint it calls, the project it acts in and the read before the write (what the read before the write finds, or that the call reads nothing before it writes). The head of each script names its own values.

  • The STACKIT API step WriteSecret of google_secret_manager_secret.django_admin_password (API/secrets-manager-vault/WriteSecret/google_secret_manager_secret.django_admin_password) — execution/api/secrets-manager-vault/001-writesecret.sh: generated, tested offline
  • The STACKIT API step WriteSecret of google_secret_manager_secret.django_settings (API/secrets-manager-vault/WriteSecret/google_secret_manager_secret.django_settings) — execution/api/secrets-manager-vault/002-writesecret.sh: generated, tested offline