diff --git a/dag/extdeps/cloud/gcp/iam.dag b/dag/extdeps/cloud/gcp/iam.dag index 855089fa753..4a57e7b7150 100644 --- a/dag/extdeps/cloud/gcp/iam.dag +++ b/dag/extdeps/cloud/gcp/iam.dag @@ -307,6 +307,8 @@ fn reconcile_faithful_policy( // There is deliberately no hand-authored permission roster in this module: a // role->permission table written from memory is authored data wearing a citation, // and it decays silently as Google edits the role. +type GcpPermissionProbeWire { permissions: List? } + type GcpPredefinedRole { name: String title: String @@ -493,6 +495,7 @@ fn gcp_project_lifecycle_state_from_wire(raw: String) -> GcpProjectLifecycleStat } type GcpProjectResourceV3 { + parent: String? project_id: GcpProjectId from "projectId" state: String resource_name: String from "name" @@ -505,13 +508,77 @@ type GcpProjectResourceV3 { // which for a converge keyed on project_id is the ordinary already-converged answer rather than a // failure, and is left for the caller to classify because this module does not know the caller's // intent for an id it did not choose. +type GcpFolderResourceV3 { name: String, parent: String, state: String } + service gcp.ResourceManager { config { endpoint: "https://cloudresourcemanager.googleapis.com", auth: Bearer, auth_input: access_token } + operation GetFolder { + requires Network + input { access_token: Secret, folder: NonEmptyStr } + output { name: String, parent: String, state: String, outcome: RestOutcome } + readonly + transport rest { method: GET, path: "/v3/\{folder\}" } + response { + 200 => GcpFolderResourceV3 + 401 => GcpErrorShape + 403 => GcpErrorShape + 404 => GcpErrorShape + 5xx => GcpErrorShape + } + } + + operation GetOrganizationIamPolicy { + requires Network + input { access_token: Secret, organization: NonEmptyStr } + output { bindings: List?, etag: String, version: Int?, outcome: RestOutcome } + readonly + transport rest { method: POST, path: "/v3/\{organization\}:getIamPolicy", body: { options: { requestedPolicyVersion: 3 } } } + response { + 200 => GcpPolicy + 401 => GcpErrorShape + 403 => GcpErrorShape + 404 => GcpErrorShape + 5xx => GcpErrorShape + } + } + + operation SetOrganizationIamPolicy { + requires Network + input { access_token: Secret, organization: NonEmptyStr, policy: GcpPolicy } + output { bindings: List?, etag: String, version: Int?, outcome: RestOutcome } + transport rest { method: POST, path: "/v3/\{organization\}:setIamPolicy", body: { policy: policy, updateMask: "bindings,etag" } } + response { + 200 => GcpPolicy + 400 => GcpErrorShape + 401 => GcpErrorShape + 403 => GcpErrorShape + 404 => GcpErrorShape + 409 => GcpErrorShape + 412 => GcpErrorShape + 5xx => GcpErrorShape + } + } + + operation TestIamPermissions { + requires Network + input { access_token: Secret, project_id: GcpProjectId, permissions: List } + output { permissions: List?, outcome: RestOutcome } + readonly + transport rest { method: POST, path: "/v1/projects/\{project_id\}:testIamPermissions", body: { permissions: permissions } } + response { + 200 => GcpPermissionProbeWire + 401 => GcpErrorShape + 403 => GcpErrorShape + 404 => GcpErrorShape + 5xx => GcpErrorShape + } + } + operation GetProject { requires Network input { access_token: Secret, project_id: GcpProjectId } - output { project_id: GcpProjectId from "projectId", state: String, resource_name: String from "name", outcome: RestOutcome } + output { project_id: GcpProjectId from "projectId", state: String, resource_name: String from "name", parent: String?, outcome: RestOutcome } readonly transport rest { method: GET, path: "/v3/projects/\{project_id\}" } response { @@ -552,9 +619,9 @@ service gcp.ResourceManager { operation GetIamPolicy { requires Network input { access_token: Secret, project_id: GcpProjectId } - output { bindings: Json, etag: String } + output { bindings: List?, etag: String, version: Int?, outcome: RestOutcome } readonly - transport rest { method: POST, path: "/v1/projects/\{project_id\}:getIamPolicy" } + transport rest { method: POST, path: "/v1/projects/\{project_id\}:getIamPolicy", body: { options: { requestedPolicyVersion: 3 } } } response { 200 => GcpPolicy 401 => GcpErrorShape @@ -569,11 +636,10 @@ service gcp.ResourceManager { input { access_token: Secret project_id: GcpProjectId - bindings: Json - etag: NonEmptyStr + policy: GcpPolicy } - output { ok: Bool } - transport rest { method: POST, path: "/v1/projects/\{project_id\}:setIamPolicy" } + output { bindings: List?, etag: String, version: Int?, outcome: RestOutcome } + transport rest { method: POST, path: "/v1/projects/\{project_id\}:setIamPolicy", body: { policy: policy, updateMask: "bindings,etag" } } response { 200 => GcpPolicy 401 => GcpErrorShape diff --git a/dag/extdeps/cloud/gcp/iam_admin.dag b/dag/extdeps/cloud/gcp/iam_admin.dag index b8c208c881d..075737ee37e 100644 --- a/dag/extdeps/cloud/gcp/iam_admin.dag +++ b/dag/extdeps/cloud/gcp/iam_admin.dag @@ -4,11 +4,11 @@ module extdeps.cloud.gcp.iam_admin import extdeps.external_authority { ExternalAuthority } import extdeps.uri { Uri, Https } import extdeps.cloud.gcp.gcp { GcpProjectId, ServiceAccountEmail, WifPoolWire, WifProviderWire, WifProviderListWire } -import extdeps.cloud.gcp.iam { GcpPolicy, GcpBinding } +import extdeps.cloud.gcp.iam { GcpPolicy, GcpBinding, GcpPermissionProbeWire } import std.types { Bool, Int, List, Map, NonEmptyStr, Secret, String } import extdeps.transports.rest { RestOutcome } import extdeps.transports.rest { RestOutcome } -import extdeps.ietf.http_semantics { POST } +import extdeps.ietf.http_semantics { GET, POST } import std.resources { Network } data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { @@ -61,6 +61,29 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { // classification that could not see it would call a pool single-provider while a second admission // rule waits one call away. GetWorkloadIdentityPool and it are what a provisioning run reads before // it writes anything through an existing pool (gunbc.auth.heal_publisher_provision). + +// Custom roles are observed as exact permission sets, including disabled/deleted standing. +type GcpCustomRoleWire { + name: String + title: String + included_permissions: List? from "includedPermissions" + stage: String? + deleted: Bool? +} + +// A newly observed pool may return HTTP 200 with an entirely empty policy. Missing +// etag is a valid wire observation, not authority for an unconditional replacement. +// Only presence is consumed: pool policies with audit configuration are refused +// by this bootstrap rather than reconstructed without their logging settings. +type GcpPoolAuditConfigWire { service: String? } + +type GcpPoolPolicyWire { + audit_configs: List? from "auditConfigs" + bindings: List? + etag: String? + version: Int? +} + service gcp.IamAdmin { config { endpoint: "https://iam.googleapis.com" @@ -68,6 +91,102 @@ service gcp.IamAdmin { auth_input: access_token } + operation GetProjectRole { + requires Network + input { access_token: Secret, project_id: GcpProjectId, role_id: NonEmptyStr } + output { name: String, included_permissions: List? from "includedPermissions", stage: String?, deleted: Bool?, outcome: RestOutcome } + readonly + transport rest { method: GET, path: "/v1/projects/\{project_id\}/roles/\{role_id\}" } + response { + 200 => GcpCustomRoleWire + 401 => GcpErrorShape + 403 => GcpErrorShape + 404 => GcpErrorShape + 429 => GcpErrorShape + 5xx => GcpErrorShape + } + } + + operation CreateProjectRole { + requires Network + input { access_token: Secret, project_id: GcpProjectId, role_id: NonEmptyStr, title: NonEmptyStr, permissions: List } + output { name: String, included_permissions: List? from "includedPermissions", stage: String?, deleted: Bool?, outcome: RestOutcome } + transport rest { method: POST, path: "/v1/projects/\{project_id\}/roles", + body: { roleId: role_id, role: { title: title, includedPermissions: permissions, stage: "GA" } } } + response { + 200 => GcpCustomRoleWire + 401 => GcpErrorShape + 403 => GcpErrorShape + 409 => GcpErrorShape + 429 => GcpErrorShape + 5xx => GcpErrorShape + } + } + + operation GetWorkloadIdentityPoolIamPolicy { + requires Network + input { access_token: Secret, project_id: GcpProjectId, pool_id: NonEmptyStr } + output { audit_configs: List? from "auditConfigs", bindings: List?, etag: String?, version: Int?, outcome: RestOutcome } + readonly + transport rest { method: POST, path: "/v1/projects/\{project_id\}/locations/global/workloadIdentityPools/\{pool_id\}:getIamPolicy", + body: { options: { requestedPolicyVersion: 3 } } } + response { + 200 => GcpPoolPolicyWire + 401 => GcpErrorShape + 403 => GcpErrorShape + 404 => GcpErrorShape + 429 => GcpErrorShape + 5xx => GcpErrorShape + } + } + + operation SetWorkloadIdentityPoolIamPolicy { + requires Network + input { access_token: Secret, project_id: GcpProjectId, pool_id: NonEmptyStr, policy: GcpPolicy } + output { audit_configs: List? from "auditConfigs", bindings: List?, etag: String?, version: Int?, outcome: RestOutcome } + transport rest { method: POST, path: "/v1/projects/\{project_id\}/locations/global/workloadIdentityPools/\{pool_id\}:setIamPolicy", body: { policy: policy } } + response { + 200 => GcpPoolPolicyWire + 401 => GcpErrorShape + 403 => GcpErrorShape + 404 => GcpErrorShape + 409 => GcpErrorShape + 412 => GcpErrorShape + 429 => GcpErrorShape + 5xx => GcpErrorShape + } + } + + operation TestServiceAccountIamPermissions { + requires Network + input { access_token: Secret, project_id: GcpProjectId, service_account: ServiceAccountEmail, permissions: List } + output { permissions: List?, outcome: RestOutcome } + readonly + transport rest { method: POST, path: "/v1/projects/\{project_id\}/serviceAccounts/\{service_account\}:testIamPermissions", body: { permissions: permissions } } + response { + 200 => GcpPermissionProbeWire + 401 => GcpErrorShape + 403 => GcpErrorShape + 404 => GcpErrorShape + 5xx => GcpErrorShape + } + } + + operation TestWorkloadIdentityPoolIamPermissions { + requires Network + input { access_token: Secret, project_id: GcpProjectId, pool_id: NonEmptyStr, permissions: List } + output { permissions: List?, outcome: RestOutcome } + readonly + transport rest { method: POST, path: "/v1/projects/\{project_id\}/locations/global/workloadIdentityPools/\{pool_id\}:testIamPermissions", body: { permissions: permissions } } + response { + 200 => GcpPermissionProbeWire + 401 => GcpErrorShape + 403 => GcpErrorShape + 404 => GcpErrorShape + 5xx => GcpErrorShape + } + } + operation CreateServiceAccount { requires Network input { diff --git a/dag/extdeps/cloud/gcp/iam_deny.dag b/dag/extdeps/cloud/gcp/iam_deny.dag new file mode 100644 index 00000000000..ae0a56eaf7b --- /dev/null +++ b/dag/extdeps/cloud/gcp/iam_deny.dag @@ -0,0 +1,66 @@ +module extdeps.cloud.gcp.iam_deny + +import extdeps.external_authority { ExternalAuthority } +import extdeps.uri { Uri, Https } +import std.types { String, NonEmptyStr, Secret, List } +import extdeps.cloud.gcp.iam { IamCondition } +import extdeps.cloud.gcp.gcp { GcpOperation } +import extdeps.cloud.gcp.errors { GcpErrorShape } +import extdeps.transports.rest { RestOutcome } +import extdeps.ietf.http_semantics { GET, POST } +import std.resources { Network } + +data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { + uri: Uri { scheme: Https, locator: "cloud.google.com/iam/docs/reference/rest/v2/policies" } +} + +// IAM v2 policies: attachment_point is one URL-encoded resource-name segment. +// https://docs.cloud.google.com/iam/docs/reference/rest/v2/policies/createPolicy +// https://docs.cloud.google.com/iam/docs/reference/rest/v2/policies +type GcpDenyRule { + denied_principals: List from "deniedPrincipals" + denied_permissions: List from "deniedPermissions" + exception_principals: List? from "exceptionPrincipals" + exception_permissions: List? from "exceptionPermissions" + denial_condition: IamCondition? from "denialCondition" +} + +type GcpDenyPolicyRule { deny_rule: GcpDenyRule? from "denyRule" } + +type GcpDenyPolicyWire { name: String, etag: String, rules: List? } + +service gcp.IamDeny { + config { endpoint: "https://iam.googleapis.com", auth: Bearer, auth_input: access_token } + + operation GetPolicy { + requires Network + input { access_token: Secret, attachment_point: NonEmptyStr, policy_id: NonEmptyStr } + output { name: String, etag: String, rules: List?, outcome: RestOutcome } + readonly + transport rest { method: GET, path: "/v2/policies/\{attachment_point\}/denypolicies/\{policy_id\}" } + response { + 200 => GcpDenyPolicyWire + 401 => GcpErrorShape + 403 => GcpErrorShape + 404 => GcpErrorShape + 429 => GcpErrorShape + 5xx => GcpErrorShape + } + } + + operation CreatePolicy { + requires Network + input { access_token: Secret, attachment_point: NonEmptyStr, policy_id: NonEmptyStr, rules: List } + output { operation_name: String from "name", outcome: RestOutcome } + transport rest { method: POST, path: "/v2/policies/\{attachment_point\}/denypolicies", + query: { policyId: policy_id }, body: { rules: rules } } + response { + 200 => GcpOperation + 401 => GcpErrorShape + 403 => GcpErrorShape + 409 => GcpErrorShape + 429 => GcpErrorShape + 5xx => GcpErrorShape + } + } +} diff --git a/dag/extdeps/cloud/gcp/secret_manager.dag b/dag/extdeps/cloud/gcp/secret_manager.dag index 03c430b7240..aefd4aadeed 100644 --- a/dag/extdeps/cloud/gcp/secret_manager.dag +++ b/dag/extdeps/cloud/gcp/secret_manager.dag @@ -126,7 +126,7 @@ type SmResolvedVersionIdentityDecodeOutcome type GcpEmptyResponse {} type SmTestPermissionsResponse { - permissions: List + permissions: List? } // The decoded reading of a testIamPermissions response, at one-permission grain. @@ -707,7 +707,7 @@ service gcp.SecretManager { operation TestSecretIamPermissions { requires Network input { access_token: Secret, project_id: GcpProjectId, secret: NonEmptyStr, permissions: List } - output { permissions: List } + output { permissions: List?, outcome: RestOutcome } readonly transport rest { method: POST, diff --git a/dag/extdeps/google/userinfo.dag b/dag/extdeps/google/userinfo.dag new file mode 100644 index 00000000000..66dbe7c4690 --- /dev/null +++ b/dag/extdeps/google/userinfo.dag @@ -0,0 +1,38 @@ +module extdeps.google.userinfo + +import std.types { String, Email, Bool, Secret } +import extdeps.external_authority { ExternalAuthority } +import extdeps.uri { Uri, Https } +import extdeps.transports.rest { RestOutcome } +import extdeps.ietf.http_semantics { GET } +import extdeps.cloud.gcp.errors { GcpOAuth2ErrorShape } +import std.resources { Network } + +// Flat wire projection of OIDC standard claims. Claim semantics remain in extdeps.auth.oidc. +data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { + uri: Uri { scheme: Https, locator: "developers.google.com/identity/openid-connect/reference" } +} + +type GoogleUserInfoWire { + subject: String from "sub" + email: Email? + email_verified: Bool? +} + +service google.OidcUserInfo { + config { endpoint: "https://openidconnect.googleapis.com", auth: Bearer, auth_input: access_token } + operation Get { + requires Network + input { access_token: Secret } + output { subject: String from "sub", email: Email?, email_verified: Bool?, outcome: RestOutcome } + readonly + transport rest { method: GET, path: "/v1/userinfo" } + response { + 200 => GoogleUserInfoWire + 400 => GcpOAuth2ErrorShape + 401 => GcpOAuth2ErrorShape + 403 => GcpOAuth2ErrorShape + 5xx => GcpOAuth2ErrorShape + } + } +} diff --git a/dag/gunbc/auth/gcp_iam_bootstrap.dag b/dag/gunbc/auth/gcp_iam_bootstrap.dag new file mode 100644 index 00000000000..2cd4fe83c6e --- /dev/null +++ b/dag/gunbc/auth/gcp_iam_bootstrap.dag @@ -0,0 +1,823 @@ +module gunbc.auth.gcp_iam_bootstrap + +import std.types { String, NonEmptyStr, Secret, List, Bool, Int, path_segment_is_safe, Timestamp } +import std.process { ProcessExit, ExitSuccess, ExitFailure, exit_failure } +import extdeps.filesystem.filesystem_io { Filesystem, filesystem_listing_names_entry } +import extdeps.google.userinfo +import extdeps.auth.oidc { OidcStandardClaims } +import extdeps.cloud.gcp.gcp { ServiceAccountEmail } +import extdeps.cloud.gcp.iam_admin +import extdeps.cloud.gcp.iam_deny +import extdeps.cloud.gcp.serviceusage { gcp_service_usage_state_from_wire, ServiceUsageStateEnabled, ServiceUsageStateDisabled } +import extdeps.cloud.gcp.secret_manager +import extdeps.cloud.gcp.iam { + GcpPolicy, GcpBinding, IamCondition, GcpIamBindingCell, GcpIamOwnedCellUniverse, + gcp_cells_bindings, gcp_policy_from_wire, gcp_policy_cells, gcp_cell_in, reconcile_gcp_policy, + GcpPolicyAlreadyConverged, GcpPolicyDeltaRequired, GcpPolicyRepresentationLossy, +} +import extdeps.transports.rest { RestOutcome, RestStatusRefused } +import gunbc.cli_wire { CliWireResponse, CliWirePrintable } +import gunbc.actions_run_binding { actions_variable_trimmed } +import gunbc.auth.access_token_source { read_supplied_access_token, SuppliedTokenReady, SuppliedTokenUnavailable } +import gunbc.auth.github_gcp_federation { fleet_secrets_project_id, fleet_secrets_project_number } +import gunbc.auth.gcp_iam_converge { gcp_iam_observe_dedicated_federation, gcp_iam_converge_targets, iam_converge_service_account, iam_observe_service_account, iam_converge_denied_permissions, same_key_set, cell_preimage, observe_and_plan_estate, GcpIamEstateRefused, GcpIamEstateNoop, GcpIamEstateApply, PolicyReading, PolicyObserved, PolicyResourceAbsent, PolicyUnreadable } +import gunbc.auth.heal_publisher_provision { + DedicatedFederation, observe_and_classify, create_absent_subjects, federation_fully_present, + FederationClassified, FederationUnclassified, rest_ok, rest_refusal_text, outcome_is_not_found, +} +import gunbc.auth.gcp_iam_bootstrap_model { + IamBootstrapRole, BootstrapProbeToken, iam_bootstrap_roles, iam_bootstrap_role_name, iam_bootstrap_role_id, iam_bootstrap_role_permissions, iam_bootstrap_role_matches, + IamBootstrapResource, BootstrapProject, BootstrapPool, BootstrapAccount, BootstrapSecret, IamBootstrapBinding, + iam_bootstrap_resource_key, iam_bootstrap_capability_bindings, iam_bootstrap_trust_bindings, + iam_bootstrap_apply_federation, iam_bootstrap_deny_policy_id, iam_bootstrap_attachment_point, + iam_bootstrap_deny_rules, iam_bootstrap_deny_matches, iam_bootstrap_model_admitted, +} + +import gunbc.auth.gcp_iam_bootstrap_authority { + bootstrap_operator_matches, iam_bootstrap_operator_member, iam_bootstrap_operator_subject, + iam_bootstrap_authority_organization, bootstrap_parent_name_valid, bootstrap_authority_publication_refused, bootstrap_deny_retry_admitted, bootstrap_authority_dependency_plan, + BootstrapAuthorityIntent, bootstrap_authority_intent_valid, bootstrap_authority_may_grant, + bootstrap_authority_cell, bootstrap_authority_intent_wire, bootstrap_authority_action, + AuthorityAbsent, AuthorityPresent, AuthorityUnreadable, PublishAuthority, UseAuthority, + RemoveAuthority, AuthoritySettled, AuthorityRefused, +} +import gunbc.auth.approval_gate { approval_instant_now } +import std.measure { second } +import extdeps.tools.sleep { sleep_delay_seconds_second_carrier_projection } + +// Operator-authorized initial setup. This entry does not forge an app approval or workflow +// identity. Its credential is never a fallback in the ongoing approval-gated job. +// Trust is last. Existing mismatched roles, providers and deny policies refuse instead of being +// adopted or patched; existing foreign policy cells are preserved through the shared reconciler. +type IamBootstrapStage = BootstrapServices | BootstrapIdentities | BootstrapRoles | BootstrapDeny | BootstrapAuthorityCleanup | BootstrapCapabilities | BootstrapProbeAuthority | BootstrapPrivilegeReadback | BootstrapProbeCleanup | BootstrapWorkflowTrust + +data iam_bootstrap_stages: List = [BootstrapServices, BootstrapIdentities, BootstrapRoles, BootstrapDeny, BootstrapAuthorityCleanup, BootstrapCapabilities, BootstrapProbeAuthority, BootstrapPrivilegeReadback, BootstrapProbeCleanup, BootstrapWorkflowTrust] + +type IamBootstrapProgress { completed: List, refusal: String } + +fn iam_bootstrap_stage_admitted(stage: IamBootstrapStage, completed: List) -> Bool { + let required = match stage { + BootstrapServices => [] + BootstrapIdentities => [BootstrapServices] + BootstrapRoles => [BootstrapIdentities] + BootstrapDeny => [BootstrapIdentities] + BootstrapAuthorityCleanup => [BootstrapDeny] + BootstrapCapabilities => [BootstrapRoles, BootstrapDeny, BootstrapAuthorityCleanup] + BootstrapProbeAuthority => [BootstrapCapabilities] + BootstrapPrivilegeReadback => [BootstrapCapabilities, BootstrapProbeAuthority] + BootstrapProbeCleanup => [BootstrapProbeAuthority] + BootstrapWorkflowTrust => [BootstrapDeny, BootstrapAuthorityCleanup, BootstrapPrivilegeReadback, BootstrapProbeCleanup] + } + all(required, prerequisite => any(completed, done => done == prerequisite)) +} + +fn iam_bootstrap_stage_wire(stage: IamBootstrapStage) -> String { + match stage { BootstrapServices => "services" BootstrapIdentities => "identities" BootstrapRoles => "roles" BootstrapDeny => "deny" BootstrapAuthorityCleanup => "authority-cleanup" BootstrapCapabilities => "capabilities" BootstrapProbeAuthority => "probe-authority" BootstrapProbeCleanup => "probe-cleanup" BootstrapPrivilegeReadback => "privilege-readback" BootstrapWorkflowTrust => "workflow-trust" } +} + +fn iam_bootstrap_plan() -> CliWireResponse { + let federation = gcp_iam_observe_dedicated_federation() + let boundary = [ + concat("project ", fleet_secrets_project_id as String), + concat("workflow provider ", federation.provider_id as String), + concat("workflow condition ", federation.attribute_condition), + concat("workflow principal set ", federation.principal_set), + concat("deny policy ", iam_bootstrap_deny_policy_id as String), + concat("deny principal serviceAccount:", iam_converge_service_account as String), + concat("deny permissions ", join(iam_converge_denied_permissions, ",")), + "Target bootstrap creates only bare pools/accounts; target providers and workload trust remain approval-gated.", + bootstrap_authority_dependency_plan(), + concat("Empty pool initialization requires the separately confirmed exclusive window: GUNBC_IAM_BOOTSTRAP_POOL_WINDOW=", iam_bootstrap_pool_window), + "Initializer: declared empty pool only -> one journaled publication -> exact independent readback with etag -> normal CAS updates. Uncertain publication is never replayed.", + "Temporary authority intent: GUNBC_IAM_BOOTSTRAP_AUTHORITY_ID + GUNBC_IAM_BOOTSTRAP_AUTHORITY_EXPIRES_AT (canonical UTC, at most one hour).", + concat("The grant targets ", iam_bootstrap_operator_member, " after verified Google subject ", iam_bootstrap_operator_subject, "; retry cannot renew or republish it."), + ] + let roles = map(iam_bootstrap_roles, r => join(["role ", iam_bootstrap_role_name(role: r) as String, " permissions=", join(iam_bootstrap_role_permissions(role: r), ",")], "")) + let cells = map(iam_bootstrap_capability_bindings() + iam_bootstrap_trust_bindings(), b => join([ + "binding ", iam_bootstrap_resource_key(resource: b.resource), " ", cell_preimage(c: b.cell), + ], "")) + CliWirePrintable { bytes: join(["Bootstrap stages: ", join(map(iam_bootstrap_stages, s => iam_bootstrap_stage_wire(stage: s)), " -> "), "\n", join(boundary + roles + cells, "\n")], ""), + exit: if iam_bootstrap_model_admitted() { ExitSuccess } else { exit_failure(reason: "bootstrap model does not preserve the declared trust boundary") } } +} + +fn iam_bootstrap_ensure_identity(f: DedicatedFederation, own_trust: Bool, token: Secret) -> String { + let before = observe_and_classify(f: f, token: token) + match before { + FederationUnclassified { reasons } => join(reasons, "; ") + FederationClassified { create_pool, create_provider, create_account } => { + let created = create_absent_subjects(f: f, token: token, c: FederationClassified { + create_pool: create_pool, create_provider: own_trust && create_provider, create_account: create_account }) + if created != "" { created } else { + let after = observe_and_classify(f: f, token: token) + match after { + FederationUnclassified { reasons } => join(reasons, "; ") + FederationClassified { create_pool: pool_missing, create_provider: _, create_account: account_missing } => + if pool_missing || account_missing || (own_trust && !federation_fully_present(c: after)) { + concat(f.label, ": named creation is not yet established by readback; no rights granted") + } else { "" } + } + } + } + } +} + +fn iam_bootstrap_ensure_role(role: IamBootstrapRole, token: Secret) -> String { + let seen = gcp.IamAdmin.GetProjectRole(access_token: token, project_id: fleet_secrets_project_id, role_id: iam_bootstrap_role_id(role: role)) + if rest_ok(o: seen.outcome) { + if iam_bootstrap_role_matches(role: role, name: seen.name, permissions: seen.included_permissions, stage: seen.stage, deleted: seen.deleted) { "" } + else { concat(iam_bootstrap_role_name(role: role) as String, ": existing role differs; no automatic privilege expansion or adoption") } + } else if !outcome_is_not_found(o: seen.outcome) { rest_refusal_text(o: seen.outcome) } + else { + let made = gcp.IamAdmin.CreateProjectRole(access_token: token, project_id: fleet_secrets_project_id, + role_id: iam_bootstrap_role_id(role: role), title: iam_bootstrap_role_id(role: role), permissions: iam_bootstrap_role_permissions(role: role)) + if !rest_ok(o: made.outcome) { rest_refusal_text(o: made.outcome) } + else { + let check = gcp.IamAdmin.GetProjectRole(access_token: token, project_id: fleet_secrets_project_id, role_id: iam_bootstrap_role_id(role: role)) + if rest_ok(o: check.outcome) && iam_bootstrap_role_matches(role: role, name: check.name, permissions: check.included_permissions, stage: check.stage, deleted: check.deleted) { "" } + else { concat(iam_bootstrap_role_name(role: role) as String, ": created role did not read back exactly; no rights granted") } + } + } +} + +fn iam_bootstrap_ensure_deny(token: Secret) -> String { + iam_bootstrap_ensure_deny_bounded(token: token, remaining: 0) +} + +fn iam_bootstrap_ensure_deny_bounded(token: Secret, remaining: Int) -> String { + let seen = gcp.IamDeny.GetPolicy(access_token: token, attachment_point: iam_bootstrap_attachment_point(), policy_id: iam_bootstrap_deny_policy_id) + if rest_ok(o: seen.outcome) { + if iam_bootstrap_deny_matches(rules: seen.rules) { "" } else { "existing bootstrap deny policy differs; refusing to weaken or adopt it" } + } else if !outcome_is_not_found(o: seen.outcome) { rest_refusal_text(o: seen.outcome) } + else { + if remaining > 0 && !iam_bootstrap_current_authority_live() { "authority deadline or clock does not admit another deny installation attempt" } + else { + let made = gcp.IamDeny.CreatePolicy(access_token: token, attachment_point: iam_bootstrap_attachment_point(), policy_id: iam_bootstrap_deny_policy_id, rules: iam_bootstrap_deny_rules()) + if !rest_ok(o: made.outcome) { + if bootstrap_deny_retry_admitted(outcome: made.outcome, remaining: remaining, live: iam_bootstrap_current_authority_live()) { + if sleep_delay_seconds_second_carrier_projection(duration: second(count: 10)) { + iam_bootstrap_ensure_deny_bounded(token: token, remaining: remaining - 1) + } else { "deny authority propagation wait failed" } + } else { rest_refusal_text(o: made.outcome) } + } else { iam_bootstrap_deny_creation_readback(token: token, remaining: 6) } + } + } +} + +fn iam_bootstrap_policy_reading(outcome: RestOutcome, bindings: List?, etag: String, version: Int?) -> PolicyReading { + if rest_ok(o: outcome) { PolicyObserved { policy: gcp_policy_from_wire(bindings: bindings, etag: etag, version: version) } } + else if outcome_is_not_found(o: outcome) { PolicyResourceAbsent } + else { PolicyUnreadable { cause: rest_refusal_text(o: outcome) } } +} + +// Preserve a successful but unfenced pool read as an explicit unmet dependency. +// Transport failure and resource absence keep their original standing. +fn iam_bootstrap_pool_policy_reading(outcome: RestOutcome, bindings: List?, etag: String?, version: Int?) -> PolicyReading { + if !rest_ok(o: outcome) { iam_bootstrap_policy_reading(outcome: outcome, bindings: bindings, etag: "", version: version) } + else { match etag { + Present { value: revision } => if revision != "" { + iam_bootstrap_policy_reading(outcome: outcome, bindings: bindings, etag: revision, version: version) + } else { PolicyUnreadable { cause: "pool policy publication prerequisite unmet: successful read has no concurrency token; unconditional replacement is not admitted" } } + Absent => PolicyUnreadable { cause: "pool policy publication prerequisite unmet: successful read has no concurrency token; unconditional replacement is not admitted" } + } } +} + +fn iam_bootstrap_read_policy(resource: IamBootstrapResource, token: Secret) -> PolicyReading { + match resource { + BootstrapProject => { + let r = gcp.ResourceManager.GetIamPolicy(access_token: token, project_id: fleet_secrets_project_id) + iam_bootstrap_policy_reading(outcome: r.outcome, bindings: r.bindings, etag: r.etag, version: r.version) + } + BootstrapPool { pool } => { + let r = gcp.IamAdmin.GetWorkloadIdentityPoolIamPolicy(access_token: token, project_id: fleet_secrets_project_id, pool_id: pool) + if (match r.audit_configs { Absent => false Present { value: rows } => count(rows) != 0 }) { + PolicyUnreadable { cause: "pool policy audit configuration is outside bootstrap representation; no replacement" } + } else { iam_bootstrap_pool_policy_reading(outcome: r.outcome, bindings: r.bindings, etag: r.etag, version: r.version) } + } + BootstrapAccount { account } => { + let r = gcp.IamAdmin.GetServiceAccountIamPolicy(access_token: token, project_id: fleet_secrets_project_id, service_account: account) + iam_bootstrap_policy_reading(outcome: r.outcome, bindings: r.bindings, etag: r.etag, version: r.version) + } + BootstrapSecret { secret } => { + let r = gcp.SecretManager.GetSecretIamPolicy(access_token: token, project_id: fleet_secrets_project_id, secret: secret) + iam_bootstrap_policy_reading(outcome: r.outcome, bindings: r.bindings, etag: r.etag, version: r.version) + } + } +} + +fn iam_bootstrap_write_policy(resource: IamBootstrapResource, policy: GcpPolicy, token: Secret) -> RestOutcome { + match resource { + BootstrapProject => gcp.ResourceManager.SetIamPolicy(access_token: token, project_id: fleet_secrets_project_id, policy: policy).outcome + BootstrapPool { pool } => gcp.IamAdmin.SetWorkloadIdentityPoolIamPolicy(access_token: token, project_id: fleet_secrets_project_id, pool_id: pool, policy: policy).outcome + BootstrapAccount { account } => gcp.IamAdmin.SetServiceAccountIamPolicy(access_token: token, project_id: fleet_secrets_project_id, service_account: account, policy: policy).outcome + BootstrapSecret { secret } => gcp.SecretManager.SetSecretIamPolicy(access_token: token, project_id: fleet_secrets_project_id, secret: secret, policy: policy).outcome + } +} + +fn iam_bootstrap_ensure_binding(binding: IamBootstrapBinding, token: Secret) -> String { + let prerequisite = match binding.resource { + BootstrapPool { pool } => iam_bootstrap_initialize_pool_policy(pool: pool, token: token) + BootstrapProject => "" + BootstrapAccount { account: _ } => "" + BootstrapSecret { secret: _ } => "" + } + if prerequisite != "" { prerequisite } else { iam_bootstrap_ensure_fenced_binding(binding: binding, token: token) } +} + +fn iam_bootstrap_ensure_fenced_binding(binding: IamBootstrapBinding, token: Secret) -> String { + match iam_bootstrap_read_policy(resource: binding.resource, token: token) { + PolicyResourceAbsent => concat(iam_bootstrap_resource_key(resource: binding.resource), ": binding subject absent") + PolicyUnreadable { cause } => cause + PolicyObserved { policy: before } => match reconcile_gcp_policy(observed: before, desired_owned: [binding.cell], owned_universe: GcpIamOwnedCellUniverse { cells: [binding.cell] }) { + GcpPolicyRepresentationLossy { conditional_roles: _ } => "policy representation loses conditions; refusing write" + GcpPolicyAlreadyConverged { policy: _ } => "" + GcpPolicyDeltaRequired { policy: after, added: _, removed, preserved: _ } => if count(removed) != 0 || before.etag == "" { + "bootstrap policy addition needs a nonempty etag and may remove no cells" + } else { + let changed = iam_bootstrap_write_policy(resource: binding.resource, policy: after, token: token) + if !rest_ok(o: changed) { rest_refusal_text(o: changed) } + else { match iam_bootstrap_read_policy(resource: binding.resource, token: token) { + PolicyObserved { policy: checked } => if gcp_cell_in(c: binding.cell, xs: gcp_policy_cells(p: checked)) { "" } else { "policy write answered but the desired binding is absent on readback" } + PolicyResourceAbsent => "policy write answered but independent readback is unavailable" + PolicyUnreadable { cause: _ } => "policy write answered but independent readback is unavailable" + } } + } + } + } +} + +fn iam_bootstrap_execute_stage(stage: IamBootstrapStage, token: Secret) -> String { + match stage { + BootstrapServices => fold(iam_bootstrap_required_services, init: "", f: (prior, api) => if prior != "" { prior } else { iam_bootstrap_ensure_service(service: api, token: token) }) + BootstrapIdentities => { + let own = fold([gcp_iam_observe_dedicated_federation(), iam_bootstrap_apply_federation()], init: "", f: (prior, f) => + if prior != "" { prior } else { iam_bootstrap_ensure_identity(f: f, own_trust: true, token: token) }) + if own != "" { own } else { fold(gcp_iam_converge_targets, init: "", f: (prior, f) => + if prior != "" { prior } else { iam_bootstrap_ensure_identity(f: f, own_trust: false, token: token) }) } + } + BootstrapRoles => fold(iam_bootstrap_roles, init: "", f: (prior, role) => if prior != "" { prior } else { iam_bootstrap_ensure_role(role: role, token: token) }) + BootstrapDeny => { + let seen = gcp.IamDeny.GetPolicy(access_token: token, attachment_point: iam_bootstrap_attachment_point(), policy_id: iam_bootstrap_deny_policy_id) + if rest_ok(o: seen.outcome) { + if iam_bootstrap_deny_matches(rules: seen.rules) { "" } else { "existing deny policy differs; no authority granted" } + } else if !outcome_is_not_found(o: seen.outcome) { rest_refusal_text(o: seen.outcome) } + else { + let authority = iam_bootstrap_authority_converge(token: token, withdrawing: false) + if authority != "" { authority } else { iam_bootstrap_ensure_deny_bounded(token: token, remaining: 30) } + } + } + BootstrapAuthorityCleanup => iam_bootstrap_authority_converge(token: token, withdrawing: true) + BootstrapCapabilities => fold(iam_bootstrap_capability_bindings(), init: "", f: (prior, binding) => if prior != "" { prior } else { iam_bootstrap_ensure_binding(binding: binding, token: token) }) + BootstrapProbeAuthority => iam_bootstrap_probe_authority_converge(token: token, withdrawing: false) + BootstrapPrivilegeReadback => iam_bootstrap_privilege_readback(token: token) + BootstrapProbeCleanup => iam_bootstrap_probe_authority_converge(token: token, withdrawing: true) + BootstrapWorkflowTrust => { + let guarded = iam_bootstrap_ensure_deny(token: token) + if guarded != "" { guarded } else { fold(iam_bootstrap_trust_bindings(), init: "", f: (prior, binding) => if prior != "" { prior } else { iam_bootstrap_ensure_binding(binding: binding, token: token) }) } + } + } +} + +// A create-only local receipt starts before cloud effects. Each stage has its own receipt; a +// process loss leaves its started record visible. Named creates and etag-fenced additions can be +// re-observed under a new receipt identity, never silently counted as completed from an exit code. +fn iam_bootstrap_apply() -> ProcessExit { + let receipt = actions_variable_trimmed(name: "GUNBC_IAM_BOOTSTRAP_RECEIPT") + if !path_segment_is_safe(raw: receipt) || !iam_bootstrap_model_admitted() { exit_failure(reason: "bootstrap requires a safe receipt identity and an admitted model") } + else { match read_supplied_access_token() { + SuppliedTokenUnavailable { cause } => exit_failure(reason: cause as String) + SuppliedTokenReady { token } => { + let started = Filesystem.WriteCreateNew(path: concat("target/gcp-iam-bootstrap-", receipt, "-started.txt"), content: "operator bootstrap started; not a convergence receipt\n") + if !started.success { exit_failure(reason: "bootstrap receipt already exists or cannot be recorded; nothing applied") } + else { + let result = fold(iam_bootstrap_stages, init: IamBootstrapProgress { completed: [], refusal: "" }, f: (prior, stage) => + if prior.refusal != "" { prior } + else if !iam_bootstrap_stage_admitted(stage: stage, completed: prior.completed) { + IamBootstrapProgress { completed: prior.completed, refusal: "bootstrap stage prerequisites are not established; no further effects" } + } else { + let outcome = iam_bootstrap_execute_stage(stage: stage, token: token) + let saved = Filesystem.WriteCreateNew(path: concat("target/gcp-iam-bootstrap-", receipt, "-", iam_bootstrap_stage_wire(stage: stage), ".txt"), + content: if outcome == "" { "stage checks completed; see the stage authority for configuration/probe scope\n" } else { concat("refused: ", outcome) }) + if !saved.success { IamBootstrapProgress { completed: prior.completed, refusal: "stage receipt failed; inspect cloud state before another attempt" } } + else if outcome != "" { IamBootstrapProgress { completed: prior.completed, refusal: outcome } } + else { IamBootstrapProgress { completed: prior.completed + [stage], refusal: "" } } + }) + let probe_cleanup = iam_bootstrap_probe_authority_converge(token: token, withdrawing: true) + let deny_cleanup = iam_bootstrap_authority_converge(token: token, withdrawing: true) + let cleanup = concat(probe_cleanup, if probe_cleanup != "" && deny_cleanup != "" { "; " } else { "" }, deny_cleanup) + if result.refusal == "" && cleanup == "" { ExitSuccess } + else { exit_failure(reason: concat(result.refusal, if cleanup == "" { "" } else { concat("; authority cleanup outstanding: ", cleanup) })) } + } + } + } } +} + +// Permission-test answers are diagnostic, not an authorization oracle. Missing permissions do +// not prove absence. These probes run only after the named resources and policies read back; +// both positive and negative expectations must match, and real authenticated reads follow. +type IamBootstrapProbe = IamBootstrapProbeRead { permissions: List } | IamBootstrapProbeUnreadable { reason: String } + +fn iam_bootstrap_probe_result(outcome: RestOutcome, permissions: List?) -> IamBootstrapProbe { + if rest_ok(o: outcome) { IamBootstrapProbeRead { permissions: match permissions { Present { value } => value Absent => [] } } } + else { IamBootstrapProbeUnreadable { reason: rest_refusal_text(o: outcome) } } +} + +fn iam_bootstrap_probe(resource: IamBootstrapResource, permissions: List, token: Secret) -> IamBootstrapProbe { + match resource { + BootstrapProject => { + let r = gcp.ResourceManager.TestIamPermissions(access_token: token, project_id: fleet_secrets_project_id, permissions: permissions) + iam_bootstrap_probe_result(outcome: r.outcome, permissions: r.permissions) + } + BootstrapPool { pool } => { + let r = gcp.IamAdmin.TestWorkloadIdentityPoolIamPermissions(access_token: token, project_id: fleet_secrets_project_id, pool_id: pool, permissions: permissions) + iam_bootstrap_probe_result(outcome: r.outcome, permissions: r.permissions) + } + BootstrapAccount { account } => { + let r = gcp.IamAdmin.TestServiceAccountIamPermissions(access_token: token, project_id: fleet_secrets_project_id, service_account: account, permissions: permissions) + iam_bootstrap_probe_result(outcome: r.outcome, permissions: r.permissions) + } + BootstrapSecret { secret } => { + let r = gcp.SecretManager.TestSecretIamPermissions(access_token: token, project_id: fleet_secrets_project_id, secret: secret, permissions: permissions) + iam_bootstrap_probe_result(outcome: r.outcome, permissions: r.permissions) + } + } +} + +fn iam_bootstrap_check_probe(resource: IamBootstrapResource, requested: List, expected: List, token: Secret) -> String { + match iam_bootstrap_probe(resource: resource, permissions: requested, token: token) { + IamBootstrapProbeUnreadable { reason } => reason + IamBootstrapProbeRead { permissions } => if same_key_set(a: permissions, b: expected) { "" } + else { concat(iam_bootstrap_resource_key(resource: resource), ": permission probe differs from the declared expectation") } + } +} + +fn iam_bootstrap_positive_probes(token: Secret) -> String { + fold(filter(iam_bootstrap_capability_bindings(), b => b.cell.member == concat("serviceAccount:", iam_converge_service_account as String)), init: "", f: (prior, binding) => { + if prior != "" { prior } else { match filter(iam_bootstrap_roles, role => (iam_bootstrap_role_name(role: role) as String) == binding.cell.role).first() { + Absent => "apply binding has no declared custom role" + Present { value: role } => iam_bootstrap_check_probe(resource: binding.resource, requested: iam_bootstrap_role_permissions(role: role), expected: iam_bootstrap_role_permissions(role: role), token: token) + } } + }) +} + +fn iam_bootstrap_negative_probes(token: Secret) -> String { + let accounts = fold([iam_observe_service_account, iam_converge_service_account], init: "", f: (prior, account) => + if prior != "" { prior } else { iam_bootstrap_check_probe(resource: BootstrapAccount { account: account }, requested: ["iam.serviceAccounts.setIamPolicy"], expected: [], token: token) }) + if accounts != "" { accounts } else { + iam_bootstrap_check_probe(resource: BootstrapPool { pool: gcp_iam_observe_dedicated_federation().pool_id }, requested: ["iam.workloadIdentityPoolProviders.create"], expected: [], token: token) + } +} + +fn iam_bootstrap_is_forbidden(outcome: RestOutcome) -> Bool { + match outcome { RestStatusRefused { status, body: _ } => (status as Int) == 403 RestOk => false RestTransportRefused { cause: _ } => false RestBodyUndecodable { status: _, cause: _ } => false } +} + +// Readback uses a temporary, exact-account token permission for the verified operator. +// Its grant is retired before workflow trust; no signing permission or account key is added. +fn iam_bootstrap_privilege_readback(token: Secret) -> String { + match iam_bootstrap_probe_token(token: token, account: iam_converge_service_account, remaining: 85) { + BootstrapProbeTokenRefused { reason } => concat("apply-account readback credential unavailable; workflow trust withheld: ", reason) + BootstrapProbeTokenReady { access_token: apply_token } => { + let positive = iam_bootstrap_positive_probes(token: apply_token) + if positive != "" { positive } else { + let negative = iam_bootstrap_negative_probes(token: apply_token) + if negative != "" { negative } else { + match iam_bootstrap_probe_token(token: token, account: iam_observe_service_account, remaining: 85) { + BootstrapProbeTokenRefused { reason } => concat("observer readback credential unavailable; workflow trust withheld: ", reason) + BootstrapProbeTokenReady { access_token: observer_token } => match observe_and_plan_estate(token: observer_token) { + GcpIamEstateRefused { reasons } => concat("observer cannot establish the declared estate: ", join(reasons, "; ")) + GcpIamEstateNoop => iam_bootstrap_observer_secret_readback(observer_token: observer_token, apply_token: apply_token) + GcpIamEstateApply { diff: _ } => iam_bootstrap_observer_secret_readback(observer_token: observer_token, apply_token: apply_token) + } + } + } + } + } + } +} + +// The observer reads its pinned approval-submission credential and the apply account is refused it. +fn iam_bootstrap_observer_secret_readback(observer_token: Secret, apply_token: Secret) -> String { + fold(gcp_iam_observe_dedicated_federation().secret_grants, init: "", f: (prior, grant) => if prior != "" { prior } else { + let permitted = gcp.SecretManager.AccessVersion(access_token: observer_token, project_id: grant.target.project, secret: grant.target.secret, version: grant.target.version) + if !rest_ok(o: permitted.outcome) { "observer cannot read its pinned approval-submission credential" } + else { + let denied = gcp.SecretManager.AccessVersion(access_token: apply_token, project_id: grant.target.project, secret: grant.target.secret, version: grant.target.version) + if iam_bootstrap_is_forbidden(outcome: denied.outcome) { "" } else { "apply account did not receive exact HTTP 403 for the known readable approval credential; workflow trust withheld" } + } + }) +} + +type IamBootstrapProbeToken = BootstrapProbeTokenReady { access_token: Secret } | BootstrapProbeTokenRefused { reason: String } + +// Permission propagation may lag readback. Retry only explicit permission refusals, +// within both a bounded count and the original absolute authority deadline. +fn iam_bootstrap_probe_token(token: Secret, account: ServiceAccountEmail, remaining: Int) -> IamBootstrapProbeToken { + let generated = gcp.IAM.GenerateAccessToken(access_token: token, target_sa: account, lifetime: "600s") + if rest_ok(o: generated.outcome) { BootstrapProbeTokenReady { access_token: generated.access_token } } + else { + let intent = BootstrapAuthorityIntent { operation: actions_variable_trimmed(name: "GUNBC_IAM_BOOTSTRAP_PROBE_ID"), + expires_at: actions_variable_trimmed(name: "GUNBC_IAM_BOOTSTRAP_PROBE_EXPIRES_AT") as Timestamp } + let live = match approval_instant_now() { Absent => false Present { value: now } => bootstrap_authority_may_grant(intent: intent, now: now) } + if bootstrap_deny_retry_admitted(outcome: generated.outcome, remaining: remaining, live: live) { + if sleep_delay_seconds_second_carrier_projection(duration: second(count: 5)) { + iam_bootstrap_probe_token(token: token, account: account, remaining: remaining - 1) + } else { BootstrapProbeTokenRefused { reason: "probe permission propagation wait failed" } } + } else { BootstrapProbeTokenRefused { reason: rest_refusal_text(o: generated.outcome) } } + } +} + +// These local bootstrap records are durable before effects and stay outside the cloud authority +// they establish. They must travel with recovery. Read failure is never treated as absence. +type IamAuthorityRecord = AuthorityRecordAbsent | AuthorityRecordPresent { bytes: String } | AuthorityRecordRefused + +fn iam_authority_record(name: String) -> IamAuthorityRecord { + let listed = Filesystem.List(path: "target") + if !listed.success { AuthorityRecordRefused } else { + let read = Filesystem.Read(path: concat("target/", name)) + let present = filesystem_listing_names_entry(listing: listed.entries, name: name) + if present && read.success { AuthorityRecordPresent { bytes: read.content } } + else if !present && !read.success { AuthorityRecordAbsent } + else { AuthorityRecordRefused } + } +} + +fn iam_authority_record_matches(record: IamAuthorityRecord, wire: String) -> Bool { + match record { AuthorityRecordAbsent => true AuthorityRecordPresent { bytes } => bytes == wire AuthorityRecordRefused => false } +} + +// A reading that is not an observed policy carries no revision; absent and unreadable are one case here. +fn iam_bootstrap_observed_policy(reading: PolicyReading) -> GcpPolicy? { + match reading { PolicyObserved { policy } => Present { value: policy } PolicyResourceAbsent => none PolicyUnreadable { cause: _ } => none } +} + +fn iam_authority_record_present(record: IamAuthorityRecord) -> Bool { + match record { AuthorityRecordPresent { bytes: _ } => true AuthorityRecordAbsent => false AuthorityRecordRefused => false } +} + +fn iam_authority_pin(name: String, wire: String) -> Bool { + let saved = Filesystem.WriteCreateNew(path: concat("target/", name), content: wire) + if saved.success { true } else { match iam_authority_record(name: name) { + AuthorityRecordPresent { bytes } => bytes == wire + AuthorityRecordAbsent => false + AuthorityRecordRefused => false + } } +} + +// The fixed subject comes from the operator's explicit authorization; only operation identity +// and absolute deadline are inputs. They are pinned before publication and cannot drift on retry. +fn iam_bootstrap_authority_converge(token: Secret, withdrawing: Bool) -> String { + let operation = actions_variable_trimmed(name: "GUNBC_IAM_BOOTSTRAP_AUTHORITY_ID") + let expiry = actions_variable_trimmed(name: "GUNBC_IAM_BOOTSTRAP_AUTHORITY_EXPIRES_AT") + if operation == "" && expiry == "" { + if withdrawing { "" } else { "dependency unsatisfied: temporary deny administration needs an authority operation ID and absolute expiry; verified organization IAM write authority is its prerequisite" } + } else { + let intent = BootstrapAuthorityIntent { operation: operation, expires_at: expiry as Timestamp } + if !bootstrap_authority_intent_valid(intent: intent) { "authority intent has an invalid operation identity or UTC deadline" } + else { + let actor = if withdrawing { "" } else { iam_bootstrap_operator_readback(token: token) } + if actor != "" { actor } else { + let ancestry = if withdrawing { "" } else { iam_bootstrap_authority_ancestry(token: token) } + if ancestry != "" { ancestry } else { iam_bootstrap_authority_reconcile(intent: intent, token: token, withdrawing: withdrawing) } + } + } + } +} + +fn iam_bootstrap_authority_reconcile(intent: BootstrapAuthorityIntent, token: Secret, withdrawing: Bool) -> String { + iam_bootstrap_cell_authority_reconcile(intent: intent, stem: concat("gcp-iam-authority-", intent.operation), + wire: bootstrap_authority_intent_wire(intent: intent), cell: bootstrap_authority_cell(intent: intent), + resource: BootstrapOrganizationAuthority, token: token, withdrawing: withdrawing) +} + +type IamBootstrapAuthorityResource = BootstrapOrganizationAuthority | BootstrapAccountAuthority { resource: IamBootstrapResource } + +fn iam_bootstrap_authority_resource_read(resource: IamBootstrapAuthorityResource, token: Secret) -> PolicyReading { + match resource { + BootstrapOrganizationAuthority => iam_bootstrap_organization_policy_read(token: token) + BootstrapAccountAuthority { resource: account } => iam_bootstrap_read_policy(resource: account, token: token) + } +} + +fn iam_bootstrap_authority_resource_write(resource: IamBootstrapAuthorityResource, policy: GcpPolicy, token: Secret) -> RestOutcome { + match resource { + BootstrapOrganizationAuthority => gcp.ResourceManager.SetOrganizationIamPolicy(access_token: token, organization: iam_bootstrap_authority_organization, policy: policy).outcome + BootstrapAccountAuthority { resource: account } => iam_bootstrap_write_policy(resource: account, policy: policy, token: token) + } +} + +// Deny administration and probe access share one election, deadline, CAS and cleanup protocol. +fn iam_bootstrap_cell_authority_reconcile(intent: BootstrapAuthorityIntent, stem: String, wire: String, + cell: GcpIamBindingCell, resource: IamBootstrapAuthorityResource, token: Secret, withdrawing: Bool) -> String { + let intent_name = concat(stem, "-intent.txt") + let elected_name = concat(stem, "-publication-elected.txt") + let closed_name = concat(stem, "-publication-closed.txt") + let retired_name = concat(stem, "-retired.txt") + let intent_record = iam_authority_record(name: intent_name) + let elected_record = iam_authority_record(name: elected_name) + let closed_record = iam_authority_record(name: closed_name) + let retired_record = iam_authority_record(name: retired_name) + if !all([intent_record, elected_record, closed_record, retired_record], r => iam_authority_record_matches(record: r, wire: wire)) { + "authority journal unreadable or intent changed; no authority effects" + } else { + let owned = iam_authority_record_present(record: intent_record) + let elected = iam_authority_record_present(record: elected_record) + let closed = iam_authority_record_present(record: closed_record) + let retired = iam_authority_record_present(record: retired_record) + if !owned && (elected || closed || retired) { "authority intent missing beside publication history; recovery evidence unavailable" } + else { match iam_bootstrap_authority_resource_read(resource: resource, token: token) { + PolicyObserved { policy } => { + let present = gcp_cell_in(c: cell, xs: gcp_policy_cells(p: policy)) + if !owned && present { "existing authority cell has no ownership journal; refusing adoption or removal" } + else if !owned && withdrawing { "" } + else if !owned && !iam_authority_pin(name: intent_name, wire: wire) { "authority intent could not be durably pinned" } + else { + let live = match approval_instant_now() { Present { value: now } => bootstrap_authority_may_grant(intent: intent, now: now) Absent => false } + let action = bootstrap_authority_action(observed: if present { AuthorityPresent } else { AuthorityAbsent }, + publication_elected: elected, publication_closed: closed, retired: retired, withdrawing: withdrawing, live: live) + match action { + AuthorityRefused => "authority dependency unresolved: expired, unowned, or publication may still commit; no grant retry or renewal" + AuthoritySettled => if withdrawing && iam_authority_pin(name: retired_name, wire: wire) { "" } + else { "authority operation is retired; a new authorized intent is required to grant again" } + UseAuthority => if iam_authority_pin(name: closed_name, wire: wire) { "" } else { "authority publication readback could not be recorded" } + PublishAuthority => iam_bootstrap_authority_policy_change(cell: cell, wire: wire, resource: resource, before: policy, removing: false, token: token, elected_name: elected_name, closed_name: closed_name, retired_name: retired_name) + RemoveAuthority => if !iam_authority_pin(name: closed_name, wire: wire) { "authority readback could not be recorded before cleanup" } + else { + let removed = iam_bootstrap_authority_policy_change(cell: cell, wire: wire, resource: resource, before: policy, removing: true, token: token, elected_name: elected_name, closed_name: closed_name, retired_name: retired_name) + if removed != "" || withdrawing { removed } else { "expired or retired authority cleaned up; no use admitted" } + } + } + } + } + PolicyUnreadable { cause } => concat("authority IAM read dependency unresolved: ", cause) + PolicyResourceAbsent => "authority policy resource absent; no authority change attempted" + } } + } +} + +// Both publication and retirement use the existing cell reconciler and its etag. The owned +// universe is exactly this conditioned cell; pre-existing unconditional or unrelated grants survive. +fn iam_bootstrap_authority_policy_change(cell: GcpIamBindingCell, wire: String, resource: IamBootstrapAuthorityResource, before: GcpPolicy, removing: Bool, token: Secret, elected_name: String, closed_name: String, retired_name: String) -> String { + match reconcile_gcp_policy(observed: before, desired_owned: if removing { [] } else { [cell] }, owned_universe: GcpIamOwnedCellUniverse { cells: [cell] }) { + GcpPolicyRepresentationLossy { conditional_roles: _ } => "authority policy loses conditions; authority change refused" + GcpPolicyAlreadyConverged { policy: _ } => "authority policy moved before the intended change; reconverge" + GcpPolicyDeltaRequired { policy: after, added: _, removed: _, preserved: _ } => if before.etag == "" { "authority mutation requires an etag" } + else { + let elected = if removing { true } else { Filesystem.WriteCreateNew(path: concat("target/", elected_name), content: wire).success } + if !elected { "authority publication already elected or election unreadable; reread, never issue a second grant" } + else { + let changed = iam_bootstrap_authority_resource_write(resource: resource, policy: after, token: token) + if !rest_ok(o: changed) { + let terminal_refusal = if !removing && bootstrap_authority_publication_refused(outcome: changed) { iam_authority_pin(name: closed_name, wire: wire) } else { false } + concat(if terminal_refusal { "authority IAM writer dependency refused without publication: " } else { "authority mutation not established; recovery remains owed: " }, rest_refusal_text(o: changed)) + } else if !removing && !iam_authority_pin(name: closed_name, wire: wire) { "acknowledged authority publication could not be recorded; recovery remains owed" } + else { match iam_bootstrap_authority_resource_read(resource: resource, token: token) { + PolicyObserved { policy: checked } => { + let present = gcp_cell_in(c: cell, xs: gcp_policy_cells(p: checked)) + if present == removing { "authority mutation lacks the required independent readback" } + else if !iam_authority_pin(name: if removing { retired_name } else { closed_name }, wire: wire) { "authority readback receipt failed; reconverge from journal" } + else { "" } + } + PolicyResourceAbsent => "authority mutation answered but policy readback is unavailable; recovery remains owed" + PolicyUnreadable { cause: _ } => "authority mutation answered but policy readback is unavailable; recovery remains owed" + } } + } + } + } +} + +// Recovery can withdraw the exact owned grant without rerunning identities, deny installation, +// or workload provisioning. Expired grants still require this physical policy cleanup. +fn iam_bootstrap_authority_cleanup() -> ProcessExit { + match read_supplied_access_token() { + SuppliedTokenUnavailable { cause } => exit_failure(reason: cause as String) + SuppliedTokenReady { token } => { + let result = iam_bootstrap_authority_converge(token: token, withdrawing: true) + if result == "" { ExitSuccess } else { exit_failure(reason: result) } + } + } +} + + +fn iam_bootstrap_organization_policy_read(token: Secret) -> PolicyReading { + let r = gcp.ResourceManager.GetOrganizationIamPolicy(access_token: token, organization: iam_bootstrap_authority_organization) + iam_bootstrap_policy_reading(outcome: r.outcome, bindings: r.bindings, etag: r.etag, version: r.version) +} + +fn iam_bootstrap_authority_ancestry(token: Secret) -> String { + let p = gcp.ResourceManager.GetProject(access_token: token, project_id: fleet_secrets_project_id) + if !rest_ok(o: p.outcome) { concat("project ancestry unreadable: ", rest_refusal_text(o: p.outcome)) } + else if p.project_id != fleet_secrets_project_id || p.resource_name != concat("projects/", fleet_secrets_project_number) || p.state != "ACTIVE" { + "project ancestry subject or active state differs; no authority grant" + } else { match p.parent { + Absent => "project has no organization ancestor; organization attachment is an unresolved dependency, not an instruction to move it" + Present { value: parent } => iam_bootstrap_follow_parent(parent: parent, visited: [], remaining: 12, token: token) + } } +} + +fn iam_bootstrap_follow_parent(parent: String, visited: List, remaining: Int, token: Secret) -> String { + if remaining <= 0 || any(visited, p => p == parent) { "ancestry traversal incomplete or cyclic; no authority grant" } + else if bootstrap_parent_name_valid(name: parent, kind: "organizations") { + if parent == (iam_bootstrap_authority_organization as String) { "" } else { "project organization differs from the pinned authority subject" } + } else if bootstrap_parent_name_valid(name: parent, kind: "folders") { + let f = gcp.ResourceManager.GetFolder(access_token: token, folder: parent as NonEmptyStr) + if !rest_ok(o: f.outcome) { concat("folder ancestry unreadable: ", rest_refusal_text(o: f.outcome)) } + else if f.name != parent || f.state != "ACTIVE" { "folder ancestry subject or active state differs" } + else { iam_bootstrap_follow_parent(parent: f.parent, visited: visited + [parent], remaining: remaining - 1, token: token) } + } else { "invalid ancestry resource name; no authority grant" } +} + + +fn iam_bootstrap_current_authority_live() -> Bool { + let intent = BootstrapAuthorityIntent { + operation: actions_variable_trimmed(name: "GUNBC_IAM_BOOTSTRAP_AUTHORITY_ID"), + expires_at: actions_variable_trimmed(name: "GUNBC_IAM_BOOTSTRAP_AUTHORITY_EXPIRES_AT") as Timestamp, + } + match approval_instant_now() { Present { value: now } => bootstrap_authority_may_grant(intent: intent, now: now) Absent => false } +} + +// An accepted asynchronous create is followed only by bounded reads, never another create. +fn iam_bootstrap_deny_creation_readback(token: Secret, remaining: Int) -> String { + let check = gcp.IamDeny.GetPolicy(access_token: token, attachment_point: iam_bootstrap_attachment_point(), policy_id: iam_bootstrap_deny_policy_id) + if rest_ok(o: check.outcome) { + if iam_bootstrap_deny_matches(rules: check.rules) { "" } else { "created deny policy differs; no capabilities granted" } + } else if outcome_is_not_found(o: check.outcome) && remaining > 0 { + if sleep_delay_seconds_second_carrier_projection(duration: second(count: 10)) { + iam_bootstrap_deny_creation_readback(token: token, remaining: remaining - 1) + } else { "deny creation readback wait failed" } + } else { concat("deny creation accepted but exact readback is not established: ", rest_refusal_text(o: check.outcome)) } +} + + +fn iam_bootstrap_operator_readback(token: Secret) -> String { + let actor = google.OidcUserInfo.Get(access_token: token) + if !rest_ok(o: actor.outcome) { concat("bootstrap operator identity unreadable: ", rest_refusal_text(o: actor.outcome)) } + else if bootstrap_operator_matches(subject: actor.subject, claims: OidcStandardClaims { email: actor.email, email_verified: actor.email_verified, name: none }) { "" } + else { "bootstrap credential is not the admitted Google subject with its pinned verified IAM email; no grant" } +} + + +// The operator confirmed exclusive pool-policy writers on 2026-09-29. This one-off +// initialization is not CAS. Its journal must stay in this same recovery workspace; +// ambiguous outcomes never authorize another unconditional request. +data iam_bootstrap_pool_window: String = "exclusive-pool-policy-initialization-20260929" + +fn iam_bootstrap_pool_initial_cells(pool: NonEmptyStr) -> List { + map(filter(iam_bootstrap_capability_bindings(), binding => binding.resource == BootstrapPool { pool: pool }), binding => binding.cell) +} + +fn iam_bootstrap_pool_seedable(audit_configured: Bool, bindings: List?, etag: String?, version: Int?) -> Bool { + !audit_configured && (match bindings { Absent => true Present { value: rows } => count(rows) == 0 }) + && (match etag { Absent => true Present { value: revision } => revision == "" }) + && (match version { Absent => true Present { value: number } => number == 0 || number == 1 || number == 3 }) +} + +fn iam_bootstrap_pool_initialized(policy: GcpPolicy, cells: List) -> Bool { + policy.etag != "" && count(cells) > 0 + && count(gcp_policy_cells(p: policy)) == count(cells) + && all(cells, cell => gcp_cell_in(c: cell, xs: gcp_policy_cells(p: policy))) +} + +fn iam_bootstrap_initialize_pool_policy(pool: NonEmptyStr, token: Secret) -> String { + let cells = iam_bootstrap_pool_initial_cells(pool: pool) + let name = concat("gcp-iam-pool-initialization-", pool as String) + let wire = concat(iam_bootstrap_pool_window, "\n", iam_bootstrap_resource_key(resource: BootstrapPool { pool: pool }), "\n", join(map(cells, cell => cell_preimage(c: cell)), "\n")) + let elected = iam_authority_record(name: concat(name, "-elected.txt")) + let closed = iam_authority_record(name: concat(name, "-closed.txt")) + if !path_segment_is_safe(raw: pool as String) || count(cells) == 0 { "pool is outside the declared bootstrap capability roster" } + else if !iam_authority_record_matches(record: elected, wire: wire) || !iam_authority_record_matches(record: closed, wire: wire) { + "pool initialization journal is unreadable or differs; retain the recovery obligation" + } else { + let read = gcp.IamAdmin.GetWorkloadIdentityPoolIamPolicy(access_token: token, project_id: fleet_secrets_project_id, pool_id: pool) + if !rest_ok(o: read.outcome) { rest_refusal_text(o: read.outcome) } + else if (match read.audit_configs { Absent => false Present { value: rows } => count(rows) != 0 }) { + "pool policy audit configuration is outside bootstrap representation; no replacement" + } else { match iam_bootstrap_observed_policy(reading: iam_bootstrap_pool_policy_reading(outcome: read.outcome, bindings: read.bindings, etag: read.etag, version: read.version)) { + Present { value: policy } => if !iam_authority_record_present(record: elected) || iam_authority_record_present(record: closed) { "" } + else if iam_bootstrap_pool_initialized(policy: policy, cells: cells) { + if iam_authority_pin(name: concat(name, "-closed.txt"), wire: wire) { "" } else { "pool initialized but completion journal failed; recover before continuing" } + } else { "unresolved initial pool write differs from readback; do not apply later policy changes" } + Absent => if iam_authority_record_present(record: elected) || iam_authority_record_present(record: closed) { + "initial pool publication is unresolved or its revision disappeared; no unconditional retry" + } else if !iam_bootstrap_pool_seedable(audit_configured: false, bindings: read.bindings, etag: read.etag, version: read.version) { + "unfenced pool policy is not empty; no initialization admitted" + } else if actions_variable_trimmed(name: "GUNBC_IAM_BOOTSTRAP_POOL_WINDOW") != iam_bootstrap_pool_window { + "empty pool initialization requires the operator-confirmed exclusive maintenance window" + } else { + let actor = iam_bootstrap_operator_readback(token: token) + if actor != "" { actor } else { + let election = Filesystem.WriteCreateNew(path: concat("target/", name, "-elected.txt"), content: wire) + if !election.success { "pool initialization election lost or unreadable; recover without another write" } + else { + let changed = gcp.IamAdmin.SetWorkloadIdentityPoolIamPolicy(access_token: token, project_id: fleet_secrets_project_id, pool_id: pool, + policy: GcpPolicy { bindings: gcp_cells_bindings(cs: cells), etag: "", version: 3 }) + if !rest_ok(o: changed.outcome) { concat("initial pool publication requires readback recovery; no unconditional retry: ", rest_refusal_text(o: changed.outcome)) } + else { + let checked = gcp.IamAdmin.GetWorkloadIdentityPoolIamPolicy(access_token: token, project_id: fleet_secrets_project_id, pool_id: pool) + match iam_bootstrap_pool_policy_reading(outcome: checked.outcome, bindings: checked.bindings, etag: checked.etag, version: checked.version) { + PolicyObserved { policy } => if iam_bootstrap_pool_initialized(policy: policy, cells: cells) { + if iam_authority_pin(name: concat(name, "-closed.txt"), wire: wire) { "" } else { "pool initialized but completion journal failed" } + } else { "pool initialization readback differs; workflow trust withheld" } + PolicyResourceAbsent => "pool initialization readback lacks its policy revision; workflow trust withheld" + PolicyUnreadable { cause: _ } => "pool initialization readback lacks its policy revision; workflow trust withheld" + } + } + } + } + } + } } + } +} + + +fn iam_bootstrap_probe_cell(intent: BootstrapAuthorityIntent) -> GcpIamBindingCell { + GcpIamBindingCell { role: iam_bootstrap_role_name(role: BootstrapProbeToken) as String, + member: iam_bootstrap_operator_member, condition: Present { value: IamCondition { + title: concat("gunbc-bootstrap-probe-", intent.operation), + description: Present { value: "Temporary exact-account bootstrap readback; physical cleanup required after expiry." }, + expression: concat("request.time < timestamp('", intent.expires_at as String, "')"), + } } } +} + +fn iam_bootstrap_probe_authority_converge(token: Secret, withdrawing: Bool) -> String { + let intent = BootstrapAuthorityIntent { + operation: actions_variable_trimmed(name: "GUNBC_IAM_BOOTSTRAP_PROBE_ID"), + expires_at: actions_variable_trimmed(name: "GUNBC_IAM_BOOTSTRAP_PROBE_EXPIRES_AT") as Timestamp, + } + if intent.operation == "" && intent.expires_at == "" { + if withdrawing { "" } else { "probe token authority requires a pinned operation identity and absolute expiry" } + } else if !bootstrap_authority_intent_valid(intent: intent) { "probe authority intent is invalid" } + else { + let actor = if withdrawing { "" } else { iam_bootstrap_operator_readback(token: token) } + if actor != "" { actor } else { + fold([iam_converge_service_account, iam_observe_service_account], init: "", f: (prior, account) => { + let cell = iam_bootstrap_probe_cell(intent: intent) + let subject = BootstrapAccount { account: account } + let result = if prior != "" && !withdrawing { prior } else { + iam_bootstrap_cell_authority_reconcile(intent: intent, + stem: concat("gcp-iam-probe-", intent.operation, "-", account as String), + wire: concat(iam_bootstrap_resource_key(resource: subject), ";", cell_preimage(c: cell)), + cell: cell, resource: BootstrapAccountAuthority { resource: subject }, token: token, withdrawing: withdrawing) + } + if prior == "" { result } else if result == "" || result == prior { prior } else { concat(prior, "; ", result) } + }) + } + } +} + +fn iam_bootstrap_probe_cleanup() -> ProcessExit { + match read_supplied_access_token() { + SuppliedTokenUnavailable { cause } => exit_failure(reason: cause as String) + SuppliedTokenReady { token } => { + let result = iam_bootstrap_probe_authority_converge(token: token, withdrawing: true) + if result == "" { ExitSuccess } else { exit_failure(reason: result) } + } + } +} + + +// These API consumers are the existing bootstrap/approval operations, not every GCP API. +data iam_bootstrap_required_services: List = [ + "iam.googleapis.com", "iamcredentials.googleapis.com", "cloudresourcemanager.googleapis.com", + "secretmanager.googleapis.com", "sts.googleapis.com", +] + +fn iam_bootstrap_service_enabled(name: String, state: String, service: String) -> Bool { + name == concat("projects/", fleet_secrets_project_number, "/services/", service) + && (match gcp_service_usage_state_from_wire(raw: state) { Present { value: st } => match st { ServiceUsageStateEnabled => true ServiceUsageStateDisabled => false ServiceUsageStateUnspecified => false } Absent => false }) +} + +fn iam_bootstrap_ensure_service(service: String, token: Secret) -> String { + if !any(iam_bootstrap_required_services, declared => declared == service) { "service is outside bootstrap dependencies" } + else { + let seen = gcp.ServiceUsage.GetService(access_token: token, project_id: fleet_secrets_project_id, service_id: service) + if !rest_ok(o: seen.outcome) { rest_refusal_text(o: seen.outcome) } + else if iam_bootstrap_service_enabled(name: seen.name, state: seen.state, service: service) { "" } + else if seen.name != concat("projects/", fleet_secrets_project_number, "/services/", service) { "service observation belongs to another project or API" } + else { + let unknown = "API enablement observation is unknown; no mutation admitted" + match gcp_service_usage_state_from_wire(raw: seen.state) { + Present { value: st } => match st { + ServiceUsageStateDisabled => { + let accepted = gcp.ServiceUsage.BatchEnableServices(access_token: token, project_id: fleet_secrets_project_id, service_ids: [service]) + if !rest_ok(o: accepted.outcome) { rest_refusal_text(o: accepted.outcome) } + else { iam_bootstrap_service_readback(service: service, token: token, remaining: 25) } + } + ServiceUsageStateEnabled => unknown + ServiceUsageStateUnspecified => unknown + } + Absent => unknown + } + } + } +} + +// An accepted long-running operation is not readiness. Poll only GetService. +fn iam_bootstrap_service_readback(service: String, token: Secret, remaining: Int) -> String { + let seen = gcp.ServiceUsage.GetService(access_token: token, project_id: fleet_secrets_project_id, service_id: service) + if !rest_ok(o: seen.outcome) { rest_refusal_text(o: seen.outcome) } + else if iam_bootstrap_service_enabled(name: seen.name, state: seen.state, service: service) { "" } + else if remaining <= 1 { concat("API enablement accepted but readiness remains unestablished: ", service) } + else if sleep_delay_seconds_second_carrier_projection(duration: second(count: 5)) { + iam_bootstrap_service_readback(service: service, token: token, remaining: remaining - 1) + } else { "API enablement readback wait failed" } +} diff --git a/dag/gunbc/auth/gcp_iam_bootstrap_authority.dag b/dag/gunbc/auth/gcp_iam_bootstrap_authority.dag new file mode 100644 index 00000000000..b92595233c3 --- /dev/null +++ b/dag/gunbc/auth/gcp_iam_bootstrap_authority.dag @@ -0,0 +1,123 @@ +module gunbc.auth.gcp_iam_bootstrap_authority + +import std.types { String, Timestamp, Bool, List, NonEmptyStr, Int, path_segment_is_safe } +import extdeps.transports.rest { RestOutcome, RestStatusRefused, mutation_status_is_commit_ambiguous } +import extdeps.auth.oidc { OidcStandardClaims } +import std.decimal { decimal_digits_only } +import gunbc.auth.github_gcp_federation { fleet_secrets_project_id } +import extdeps.cloud.gcp.iam { GcpIamBindingCell, IamCondition } +import gunbc.auth.approval_capability { utc_instant_is_canonical, utc_instant_before, utc_instant_seconds_after } +import gunbc.auth.gcp_iam_converge { cell_preimage } + +// The operator authorized this named human principal. No caller-controlled member, role, or +// project is accepted. Ongoing iam-converge is never the beneficiary of bootstrap authority. +// Observed from Resource Manager v3 on 2026-09-28. Publication revalidates ancestry; +// cleanup uses this pinned subject even if the project subsequently moves. +// Deny Admin is grantable only on an organization, per Google's role definition. +data iam_bootstrap_authority_organization: NonEmptyStr = "organizations/266638272282" + +data iam_bootstrap_operator_subject: String = "116084671989231734979" +data iam_bootstrap_operator_email: String = "brian@gunb.ai" +data iam_bootstrap_operator_member: String = concat("user:", iam_bootstrap_operator_email) + +fn bootstrap_operator_matches(subject: String, claims: OidcStandardClaims) -> Bool { + subject == iam_bootstrap_operator_subject + && match claims.email_verified { Present { value: v } => v Absent => false } + && match claims.email { Present { value: email } => (email as String) == iam_bootstrap_operator_email Absent => false } +} + +type BootstrapAuthorityIntent { operation: String, expires_at: Timestamp } + +type BootstrapAuthorityDependency = OrganizationPolicyRead | OrganizationPolicyWrite | TemporaryDenyAdministration | DenyPolicyReadback | TemporaryAuthorityRetired + +fn bootstrap_authority_requires(subject: BootstrapAuthorityDependency) -> List { + match subject { + OrganizationPolicyRead => [] + OrganizationPolicyWrite => [OrganizationPolicyRead] + TemporaryDenyAdministration => [OrganizationPolicyWrite] + DenyPolicyReadback => [TemporaryDenyAdministration] + TemporaryAuthorityRetired => [OrganizationPolicyRead, OrganizationPolicyWrite] + } +} + +fn bootstrap_authority_intent_valid(intent: BootstrapAuthorityIntent) -> Bool { + path_segment_is_safe(raw: intent.operation) && string_length(s: intent.operation) <= 48 && utc_instant_is_canonical(t: intent.expires_at) +} + +// The absolute deadline is immutable in the durable intent. Reading it on retry never renews it. +fn bootstrap_authority_may_grant(intent: BootstrapAuthorityIntent, now: Timestamp) -> Bool { + bootstrap_authority_intent_valid(intent: intent) && utc_instant_is_canonical(t: now) + && utc_instant_before(a: now, b: intent.expires_at) + && utc_instant_seconds_after(later: intent.expires_at, earlier: now) <= 3600 +} + +fn bootstrap_authority_cell(intent: BootstrapAuthorityIntent) -> GcpIamBindingCell { + GcpIamBindingCell { role: "roles/iam.denyAdmin", member: iam_bootstrap_operator_member, + condition: Present { value: IamCondition { + title: concat("gunbc-bootstrap-deny-", intent.operation), + description: Present { value: "Temporary dependency for gunbc IAM deny-policy installation; cleanup remains owed after expiry." }, + expression: join(["request.time < timestamp('", intent.expires_at as String, "')"], ""), + } } } +} + +fn bootstrap_authority_intent_wire(intent: BootstrapAuthorityIntent) -> String { + join(["target-project:", fleet_secrets_project_id as String, ";authority-resource:", iam_bootstrap_authority_organization as String, ";", cell_preimage(c: bootstrap_authority_cell(intent: intent))], "") +} + +type BootstrapAuthorityObservation = AuthorityAbsent | AuthorityPresent | AuthorityUnreadable + +type BootstrapAuthorityAction = PublishAuthority | UseAuthority | RemoveAuthority | AuthoritySettled | AuthorityRefused + +// One durable publication election prevents a retired grant from being reissued. An absent +// policy cell after an unresolved publication is NOT evidence that a queued write cannot land. +fn bootstrap_authority_action(observed: BootstrapAuthorityObservation, publication_elected: Bool, publication_closed: Bool, retired: Bool, withdrawing: Bool, live: Bool) -> BootstrapAuthorityAction { + match observed { + AuthorityUnreadable => AuthorityRefused + AuthorityPresent => if !publication_elected { AuthorityRefused } + else if withdrawing || retired || !live { RemoveAuthority } else { UseAuthority } + AuthorityAbsent => if retired { AuthoritySettled } + else if publication_elected { if publication_closed && withdrawing { AuthoritySettled } else { AuthorityRefused } } + else if withdrawing { AuthoritySettled } + else if live { PublishAuthority } else { AuthorityRefused } + } +} + + +// Resource names are validated before interpolating a parent read; never follow arbitrary URLs. +fn bootstrap_parent_name_valid(name: String, kind: String) -> Bool { + let prefix = concat(kind, "/") + starts_with(s: name, prefix: prefix) && string_length(s: name) > string_length(s: prefix) + && decimal_digits_only(s: substring(s: name, start: string_length(s: prefix), end: string_length(s: name))) +} + +// Reuse the transport authority's distinction: a received 4xx rejects the mutation; +// a transport fault, undecodable success, or server error does not establish non-commit. +fn bootstrap_authority_publication_refused(outcome: RestOutcome) -> Bool { + match outcome { + RestStatusRefused { status, body: _ } => (status as Int) >= 400 && !mutation_status_is_commit_ambiguous(status: status) + RestOk => false + RestTransportRefused { cause: _ } => false + RestBodyUndecodable { status: _, cause: _ } => false + } +} + + +fn bootstrap_deny_retry_admitted(outcome: RestOutcome, remaining: Int, live: Bool) -> Bool { + remaining > 1 && live && match outcome { RestStatusRefused { status, body: _ } => (status as Int) == 403 RestOk => false RestTransportRefused { cause: _ } => false RestBodyUndecodable { status: _, cause: _ } => false } +} + + +fn bootstrap_authority_dependency_name(subject: BootstrapAuthorityDependency) -> String { + match subject { + OrganizationPolicyRead => "organization-policy-read" + OrganizationPolicyWrite => "organization-policy-write" + TemporaryDenyAdministration => "temporary-deny-administration" + DenyPolicyReadback => "deny-policy-readback" + TemporaryAuthorityRetired => "temporary-authority-retired" + } +} + +fn bootstrap_authority_dependency_plan() -> String { + join(map([OrganizationPolicyRead, OrganizationPolicyWrite, TemporaryDenyAdministration, DenyPolicyReadback, TemporaryAuthorityRetired], subject => + concat(bootstrap_authority_dependency_name(subject: subject), " requires [", join(map(bootstrap_authority_requires(subject: subject), required => bootstrap_authority_dependency_name(subject: required)), ","), "]")), "\n") +} diff --git a/dag/gunbc/auth/gcp_iam_bootstrap_model.dag b/dag/gunbc/auth/gcp_iam_bootstrap_model.dag new file mode 100644 index 00000000000..53b839261f5 --- /dev/null +++ b/dag/gunbc/auth/gcp_iam_bootstrap_model.dag @@ -0,0 +1,147 @@ +module gunbc.auth.gcp_iam_bootstrap_model + +import std.types { String, NonEmptyStr, List, Bool } +import std.list { distinct_by_key } +import extdeps.cloud.gcp.gcp { ServiceAccountEmail } +import extdeps.cloud.gcp.iam { GcpIamBindingCell } +import extdeps.cloud.gcp.iam_deny { GcpDenyRule, GcpDenyPolicyRule } +import gunbc.auth.github_gcp_federation { fleet_secrets_project_id, fleet_secrets_project_number, federated_service_account_member } +import gunbc.auth.gcp_iam_converge { + IamConvergeRole, ConvergeCreateRole, ConvergeProviderRole, ConvergeAccountPolicyRole, ConvergeSecretPolicyRole, + iam_converge_role_id, iam_converge_role_permissions, iam_observe_role_permissions, iam_converge_denied_permissions, + iam_observe_service_account, iam_converge_service_account, iam_converge_service_account_id, + gcp_iam_observe_dedicated_federation, gcp_iam_converge_targets, iam_converge_reach_excludes_its_own_trust, same_key_set, cell_preimage, +} +import gunbc.auth.heal_publisher_provision { DedicatedFederation, impersonation_cell } +import gunbc.auth.gcp_secret_access { secret_access_desired_cells } + +// Bootstrap owns the job's trust. Ongoing iam-converge never receives authority over these subjects. +type IamBootstrapRole = BootstrapApplyRole { role: IamConvergeRole } | BootstrapObservePool | BootstrapObserveAccount | BootstrapObserveSecret | BootstrapProbeToken + +type IamBootstrapResource = BootstrapProject | BootstrapPool { pool: NonEmptyStr } | BootstrapAccount { account: ServiceAccountEmail } | BootstrapSecret { secret: NonEmptyStr } + +type IamBootstrapBinding { resource: IamBootstrapResource, cell: GcpIamBindingCell } + +data iam_bootstrap_roles: List = [ + BootstrapApplyRole { role: ConvergeCreateRole }, BootstrapApplyRole { role: ConvergeProviderRole }, + BootstrapApplyRole { role: ConvergeAccountPolicyRole }, BootstrapApplyRole { role: ConvergeSecretPolicyRole }, + BootstrapObservePool, BootstrapObserveAccount, BootstrapObserveSecret, BootstrapProbeToken, +] + +fn iam_bootstrap_project_role_name(role_id: String) -> NonEmptyStr { + join(["projects/", fleet_secrets_project_id as String, "/roles/", role_id], "") as NonEmptyStr +} + +fn iam_bootstrap_role_name(role: IamBootstrapRole) -> NonEmptyStr { + match role { + BootstrapApplyRole { role: r } => iam_converge_role_id(r: r) as NonEmptyStr + BootstrapObservePool => iam_bootstrap_project_role_name(role_id: "gunbcIamObservePool") + BootstrapObserveAccount => iam_bootstrap_project_role_name(role_id: "gunbcIamObserveAccount") + BootstrapObserveSecret => iam_bootstrap_project_role_name(role_id: "gunbcIamObserveSecret") + BootstrapProbeToken => iam_bootstrap_project_role_name(role_id: "gunbcIamBootstrapProbeToken") + } +} + +fn iam_bootstrap_role_id(role: IamBootstrapRole) -> NonEmptyStr { + let name = iam_bootstrap_role_name(role: role) as String + let prefix = join(["projects/", fleet_secrets_project_id as String, "/roles/"], "") + substring(s: name, start: string_length(s: prefix), end: string_length(s: name)) as NonEmptyStr +} + +fn iam_bootstrap_role_permissions(role: IamBootstrapRole) -> List { + match role { + BootstrapApplyRole { role: r } => iam_converge_role_permissions(r: r) + BootstrapObservePool => filter(iam_observe_role_permissions, p => starts_with(s: p, prefix: "iam.workloadIdentity")) + BootstrapObserveAccount => filter(iam_observe_role_permissions, p => starts_with(s: p, prefix: "iam.serviceAccounts")) + BootstrapObserveSecret => filter(iam_observe_role_permissions, p => starts_with(s: p, prefix: "secretmanager.")) + BootstrapProbeToken => ["iam.serviceAccounts.getAccessToken"] + } +} + +fn iam_bootstrap_role_matches(role: IamBootstrapRole, name: String, permissions: List?, stage: String?, deleted: Bool?) -> Bool { + name == (iam_bootstrap_role_name(role: role) as String) + && match deleted { Present { value: d } => !d Absent => true } + && match stage { Present { value: s } => s == "GA" Absent => false } + && match permissions { Present { value: ps } => same_key_set(a: ps, b: iam_bootstrap_role_permissions(role: role)) Absent => false } +} + +fn iam_bootstrap_apply_federation() -> DedicatedFederation { + let base = gcp_iam_observe_dedicated_federation() + DedicatedFederation { label: "gcp iam converge apply bootstrap", pool_id: base.pool_id, pool_display_name: base.pool_display_name, + provider_id: base.provider_id, attribute_mapping: base.attribute_mapping, attribute_condition: base.attribute_condition, + principal_set: base.principal_set, service_account: iam_converge_service_account, service_account_id: iam_converge_service_account_id, + service_account_display_name: "GCP IAM converge apply (WIF-only, approval-gated)", + service_account_member: federated_service_account_member(email: iam_converge_service_account), secret_grants: [] } +} + +fn iam_bootstrap_resource_key(resource: IamBootstrapResource) -> String { + match resource { + BootstrapProject => join(["projects/", fleet_secrets_project_id as String], "") + BootstrapPool { pool } => join(["projects/", fleet_secrets_project_number, "/locations/global/workloadIdentityPools/", pool as String], "") + BootstrapAccount { account } => join(["projects/", fleet_secrets_project_id as String, "/serviceAccounts/", account as String], "") + BootstrapSecret { secret } => join(["projects/", fleet_secrets_project_number, "/secrets/", secret as String], "") + } +} + +fn iam_bootstrap_binding(resource: IamBootstrapResource, role: IamBootstrapRole, account: ServiceAccountEmail) -> IamBootstrapBinding { + IamBootstrapBinding { resource: resource, cell: GcpIamBindingCell { role: iam_bootstrap_role_name(role: role) as String, + member: federated_service_account_member(email: account), condition: none } } +} + +// Apply rights are projected from the same target roster as ongoing convergence. Observer rights +// use the same subjects plus the job's own trust; no project-wide metadata reader is introduced. +fn iam_bootstrap_capability_bindings() -> List { + let apply = [iam_bootstrap_binding(resource: BootstrapProject, role: BootstrapApplyRole { role: ConvergeCreateRole }, account: iam_converge_service_account)] + + flat_map(gcp_iam_converge_targets, f => [ + iam_bootstrap_binding(resource: BootstrapPool { pool: f.pool_id }, role: BootstrapApplyRole { role: ConvergeProviderRole }, account: iam_converge_service_account), + iam_bootstrap_binding(resource: BootstrapAccount { account: f.service_account }, role: BootstrapApplyRole { role: ConvergeAccountPolicyRole }, account: iam_converge_service_account), + ] + map(f.secret_grants, g => iam_bootstrap_binding(resource: BootstrapSecret { secret: g.target.secret }, role: BootstrapApplyRole { role: ConvergeSecretPolicyRole }, account: iam_converge_service_account))) + let observe = flat_map(gcp_iam_converge_targets + [gcp_iam_observe_dedicated_federation(), iam_bootstrap_apply_federation()], f => [ + iam_bootstrap_binding(resource: BootstrapPool { pool: f.pool_id }, role: BootstrapObservePool, account: iam_observe_service_account), + iam_bootstrap_binding(resource: BootstrapAccount { account: f.service_account }, role: BootstrapObserveAccount, account: iam_observe_service_account), + ] + map(f.secret_grants, g => iam_bootstrap_binding(resource: BootstrapSecret { secret: g.target.secret }, role: BootstrapObserveSecret, account: iam_observe_service_account))) + let submission = flat_map(gcp_iam_observe_dedicated_federation().secret_grants, g => map( + secret_access_desired_cells(member: federated_service_account_member(email: iam_observe_service_account), grant: g), c => + IamBootstrapBinding { resource: BootstrapSecret { secret: g.target.secret }, cell: c })) + distinct_by_key(xs: apply + observe + submission, key: b => concat(iam_bootstrap_resource_key(resource: b.resource), cell_preimage(c: b.cell))) +} + +// Workload trust is a separate final stage, after deny and resource-local permission readbacks. +fn iam_bootstrap_trust_bindings() -> List { + map([gcp_iam_observe_dedicated_federation(), iam_bootstrap_apply_federation()], f => + IamBootstrapBinding { resource: BootstrapAccount { account: f.service_account }, cell: impersonation_cell(f: f) }) +} + +data iam_bootstrap_deny_policy_id: NonEmptyStr = "gunbc-iam-converge-boundary" + +fn iam_bootstrap_attachment_point() -> NonEmptyStr { + concat("cloudresourcemanager.googleapis.com%2Fprojects%2F", fleet_secrets_project_number) as NonEmptyStr +} + +fn iam_bootstrap_deny_rules() -> List { + [GcpDenyPolicyRule { deny_rule: Present { value: GcpDenyRule { + denied_principals: [concat("principal://iam.googleapis.com/projects/-/serviceAccounts/", iam_converge_service_account as String)], + denied_permissions: iam_converge_denied_permissions, exception_principals: none, exception_permissions: none, denial_condition: none, + } } }] +} + +fn iam_bootstrap_deny_matches(rules: List?) -> Bool { + match rules { + Absent => false + Present { value: rows } => count(rows) == 1 && all(rows, row => match row.deny_rule { + Absent => false + Present { value: rule } => same_key_set(a: rule.denied_principals, + b: [concat("principal://iam.googleapis.com/projects/-/serviceAccounts/", iam_converge_service_account as String)]) + && same_key_set(a: rule.denied_permissions, b: iam_converge_denied_permissions) + && match rule.exception_principals { Absent => true Present { value: xs } => count(xs) == 0 } + && match rule.exception_permissions { Absent => true Present { value: xs } => count(xs) == 0 } + && match rule.denial_condition { Absent => true Present { value: _ } => false } + }) + } +} + +fn iam_bootstrap_model_admitted() -> Bool { + iam_converge_reach_excludes_its_own_trust() + && all(iam_bootstrap_roles, r => count(iam_bootstrap_role_permissions(role: r)) > 0) + && count(iam_bootstrap_trust_bindings()) == 2 +} diff --git a/dag/test/claim/auth/gcp_iam_bootstrap_witness_test.dag b/dag/test/claim/auth/gcp_iam_bootstrap_witness_test.dag new file mode 100644 index 00000000000..3fbe7c439bd --- /dev/null +++ b/dag/test/claim/auth/gcp_iam_bootstrap_witness_test.dag @@ -0,0 +1,206 @@ +module test.claim.auth.gcp_iam_bootstrap_witness_test + +import std.types { Bool } +import extdeps.auth.oidc { OidcStandardClaims } +import extdeps.transports.rest { RestOk, RestStatusRefused, RestTransportRefused, RestBodyUndecodable } +import extdeps.cloud.gcp.iam_deny { GcpDenyRule, GcpDenyPolicyRule } +import gunbc.auth.gcp_iam_bootstrap_model { + BootstrapProbeToken, iam_bootstrap_roles, iam_bootstrap_role_matches, iam_bootstrap_role_name, iam_bootstrap_role_permissions, + iam_bootstrap_deny_rules, iam_bootstrap_deny_matches, iam_bootstrap_model_admitted, +} +import gunbc.auth.gcp_iam_bootstrap { + iam_bootstrap_service_enabled, iam_bootstrap_probe_cell, iam_bootstrap_pool_seedable, iam_bootstrap_pool_initialized, iam_bootstrap_pool_initial_cells, iam_bootstrap_pool_policy_reading, iam_bootstrap_stage_admitted, BootstrapServices, BootstrapIdentities, BootstrapRoles, BootstrapDeny, + BootstrapCapabilities, BootstrapProbeAuthority, BootstrapProbeCleanup, BootstrapPrivilegeReadback, BootstrapWorkflowTrust, BootstrapAuthorityCleanup, +} + +import gunbc.auth.gcp_iam_bootstrap_authority { + bootstrap_operator_matches, iam_bootstrap_operator_subject, + iam_bootstrap_authority_organization, bootstrap_parent_name_valid, bootstrap_authority_publication_refused, bootstrap_deny_retry_admitted, bootstrap_authority_requires, TemporaryAuthorityRetired, DenyPolicyReadback, + BootstrapAuthorityIntent, bootstrap_authority_cell, bootstrap_authority_intent_wire, bootstrap_authority_may_grant, + bootstrap_authority_action, AuthorityAbsent, AuthorityPresent, AuthorityUnreadable, + AuthorityRefused, AuthoritySettled, RemoveAuthority, PublishAuthority, +} +import extdeps.cloud.gcp.iam { + GcpPolicy, GcpIamBindingCell, GcpIamOwnedCellUniverse, gcp_cells_bindings, gcp_policy_cells, + reconcile_gcp_policy, GcpPolicyDeltaRequired, gcp_cell_in, +} + +import gunbc.auth.gcp_iam_converge { PolicyUnreadable, PolicyObserved, PolicyResourceAbsent } + +test fn bootstrap_role_drift_never_authorizes_adoption() -> Bool { + all(iam_bootstrap_roles, role => { + let name = iam_bootstrap_role_name(role: role) + let permissions = iam_bootstrap_role_permissions(role: role) + iam_bootstrap_role_matches(role: role, name: name, permissions: Present { value: permissions }, stage: Present { value: "GA" }, deleted: none) + && !iam_bootstrap_role_matches(role: role, name: name, permissions: Present { value: permissions + ["resourcemanager.projects.setIamPolicy"] }, stage: Present { value: "GA" }, deleted: none) + && !iam_bootstrap_role_matches(role: role, name: name, permissions: Absent, stage: Present { value: "GA" }, deleted: none) + && !iam_bootstrap_role_matches(role: role, name: name, permissions: Present { value: permissions }, stage: Present { value: "GA" }, deleted: Present { value: true }) + && !iam_bootstrap_role_matches(role: role, name: "projects/other/roles/foreign", permissions: Present { value: permissions }, stage: Present { value: "GA" }, deleted: none) + }) +} + +test fn deny_exceptions_and_incomplete_readback_withhold_trust() -> Bool { + let rows = iam_bootstrap_deny_rules() + iam_bootstrap_deny_matches(rules: Present { value: rows }) + && !iam_bootstrap_deny_matches(rules: Absent) + && !iam_bootstrap_deny_matches(rules: Present { value: [] }) + && !iam_bootstrap_deny_matches(rules: Present { value: rows + rows }) + && all(rows, row => match row.deny_rule { + Absent => false + Present { value: r } => !iam_bootstrap_deny_matches(rules: Present { value: [GcpDenyPolicyRule { deny_rule: Present { value: GcpDenyRule { + denied_principals: r.denied_principals, denied_permissions: r.denied_permissions, + exception_principals: Present { value: r.denied_principals }, exception_permissions: none, denial_condition: none, + } } }] }) + }) +} + +test fn workflow_trust_requires_completed_deny_and_privilege_readback() -> Bool { + iam_bootstrap_model_admitted() + && iam_bootstrap_stage_admitted(stage: BootstrapServices, completed: []) + && !iam_bootstrap_stage_admitted(stage: BootstrapIdentities, completed: []) + && iam_bootstrap_stage_admitted(stage: BootstrapIdentities, completed: [BootstrapServices]) + && !iam_bootstrap_stage_admitted(stage: BootstrapCapabilities, completed: [BootstrapIdentities, BootstrapRoles]) + && !iam_bootstrap_stage_admitted(stage: BootstrapWorkflowTrust, completed: [BootstrapIdentities, BootstrapRoles, BootstrapDeny, BootstrapCapabilities]) + && !iam_bootstrap_stage_admitted(stage: BootstrapWorkflowTrust, completed: [BootstrapPrivilegeReadback]) + && iam_bootstrap_stage_admitted(stage: BootstrapWorkflowTrust, completed: [BootstrapIdentities, BootstrapRoles, BootstrapDeny, BootstrapAuthorityCleanup, BootstrapCapabilities, BootstrapProbeAuthority, BootstrapPrivilegeReadback, BootstrapProbeCleanup]) +} + + +data authority_specimen: BootstrapAuthorityIntent = BootstrapAuthorityIntent { operation: "control-1", expires_at: "2026-09-28T23:00:00Z" } + +test fn authority_deadline_is_bounded_and_retry_does_not_renew_it() -> Bool { + bootstrap_authority_may_grant(intent: authority_specimen, now: "2026-09-28T22:30:00Z") + && !bootstrap_authority_may_grant(intent: authority_specimen, now: "2026-09-28T21:00:00Z") + && !bootstrap_authority_may_grant(intent: authority_specimen, now: "2026-09-28T23:00:00Z") + && !bootstrap_authority_may_grant(intent: authority_specimen, now: "unreadable") + && bootstrap_authority_intent_wire(intent: authority_specimen) != bootstrap_authority_intent_wire(intent: BootstrapAuthorityIntent { operation: "control-1", expires_at: "2026-09-29T00:00:00Z" }) +} + +test fn uncertain_publication_cannot_be_settled_from_an_absent_cell() -> Bool { + bootstrap_authority_action(observed: AuthorityAbsent, publication_elected: true, publication_closed: false, retired: false, withdrawing: true, live: false) == AuthorityRefused + && bootstrap_authority_action(observed: AuthorityAbsent, publication_elected: true, publication_closed: true, retired: false, withdrawing: true, live: false) == AuthoritySettled + && bootstrap_authority_action(observed: AuthorityAbsent, publication_elected: true, publication_closed: true, retired: true, withdrawing: false, live: true) != PublishAuthority + && bootstrap_authority_action(observed: AuthorityPresent, publication_elected: true, publication_closed: false, retired: false, withdrawing: true, live: false) == RemoveAuthority +} + +test fn unknown_policy_and_unowned_grants_never_authorize_changes() -> Bool { + bootstrap_authority_action(observed: AuthorityUnreadable, publication_elected: false, publication_closed: false, retired: false, withdrawing: false, live: true) == AuthorityRefused + && bootstrap_authority_action(observed: AuthorityPresent, publication_elected: false, publication_closed: false, retired: false, withdrawing: true, live: true) == AuthorityRefused + && !iam_bootstrap_stage_admitted(stage: BootstrapCapabilities, completed: [BootstrapIdentities, BootstrapRoles, BootstrapDeny]) + && !iam_bootstrap_stage_admitted(stage: BootstrapWorkflowTrust, completed: [BootstrapDeny, BootstrapPrivilegeReadback]) +} + +test fn authority_retirement_preserves_existing_human_and_foreign_access() -> Bool { + let owned = bootstrap_authority_cell(intent: authority_specimen) + let foreign = GcpIamBindingCell { role: "roles/iam.denyAdmin", member: "user:other@example.com", condition: none } + let original = GcpIamBindingCell { role: "roles/iam.denyAdmin", member: owned.member, condition: none } + let before = GcpPolicy { bindings: gcp_cells_bindings(cs: [foreign, original, owned]), etag: "observed-generation", version: 3 } + match reconcile_gcp_policy(observed: before, desired_owned: [], owned_universe: GcpIamOwnedCellUniverse { cells: [owned] }) { + GcpPolicyDeltaRequired { policy, added, removed, preserved: _ } => + count(added) == 0 && count(removed) == 1 && policy.etag == "observed-generation" + && gcp_cell_in(c: foreign, xs: gcp_policy_cells(p: policy)) + && gcp_cell_in(c: original, xs: gcp_policy_cells(p: policy)) + && !gcp_cell_in(c: owned, xs: gcp_policy_cells(p: policy)) + _ => false + } +} + + +test fn bootstrap_authority_requires_an_organization_resource() -> Bool { + bootstrap_parent_name_valid(name: iam_bootstrap_authority_organization, kind: "organizations") + && !bootstrap_parent_name_valid(name: "projects/582015116396", kind: "organizations") + && !bootstrap_parent_name_valid(name: "organizations/123?alt=other", kind: "organizations") + && !bootstrap_parent_name_valid(name: "folders/../../organizations/123", kind: "folders") + && !bootstrap_parent_name_valid(name: "organizations/", kind: "organizations") + && bootstrap_parent_name_valid(name: "folders/123", kind: "folders") +} + +test fn definite_publication_rejection_is_distinct_from_unknown_commit() -> Bool { + bootstrap_authority_publication_refused(outcome: RestStatusRefused { status: 400, body: "unsupported role scope" }) + && bootstrap_authority_publication_refused(outcome: RestStatusRefused { status: 403, body: "permission denied" }) + && bootstrap_authority_publication_refused(outcome: RestStatusRefused { status: 409, body: "etag conflict" }) + && !bootstrap_authority_publication_refused(outcome: RestStatusRefused { status: 500, body: "unknown" }) + && !bootstrap_authority_publication_refused(outcome: RestTransportRefused { cause: "connection lost" }) + && !bootstrap_authority_publication_refused(outcome: RestBodyUndecodable { status: 200, cause: "lost success body" }) +} + + +test fn deny_propagation_retry_requires_live_authority_and_a_remaining_budget() -> Bool { + let refused = RestStatusRefused { status: 403, body: "permission not established" } + bootstrap_deny_retry_admitted(outcome: refused, remaining: 2, live: true) + && !bootstrap_deny_retry_admitted(outcome: refused, remaining: 1, live: true) + && !bootstrap_deny_retry_admitted(outcome: refused, remaining: 30, live: false) + && !bootstrap_deny_retry_admitted(outcome: RestTransportRefused { cause: "unknown commit" }, remaining: 30, live: true) + && !bootstrap_deny_retry_admitted(outcome: RestStatusRefused { status: 500, body: "unknown commit" }, remaining: 30, live: true) +} + + +test fn cleanup_dependency_does_not_wait_for_successful_deny_installation() -> Bool { + !any(bootstrap_authority_requires(subject: TemporaryAuthorityRetired), prerequisite => prerequisite == DenyPolicyReadback) +} + + +test fn authority_beneficiary_requires_the_verified_stable_google_subject() -> Bool { + let claims = OidcStandardClaims { email: Present { value: "brian@gunb.ai" }, email_verified: Present { value: true }, name: none } + bootstrap_operator_matches(subject: iam_bootstrap_operator_subject, claims: claims) + && !bootstrap_operator_matches(subject: "another-subject", claims: claims) + && !bootstrap_operator_matches(subject: iam_bootstrap_operator_subject, claims: OidcStandardClaims { email: claims.email, email_verified: Present { value: false }, name: none }) + && !bootstrap_operator_matches(subject: iam_bootstrap_operator_subject, claims: OidcStandardClaims { email: none, email_verified: Present { value: true }, name: none }) + && !bootstrap_operator_matches(subject: iam_bootstrap_operator_subject, claims: OidcStandardClaims { email: Present { value: "briansrls@gunb.ai" }, email_verified: Present { value: true }, name: none }) +} + + +test fn empty_pool_policy_does_not_authorize_unconditional_publication() -> Bool { + let missing = iam_bootstrap_pool_policy_reading(outcome: RestOk, bindings: Absent, etag: Absent, version: Absent) + let empty = iam_bootstrap_pool_policy_reading(outcome: RestOk, bindings: Absent, etag: Present { value: "" }, version: Absent) + let observed = iam_bootstrap_pool_policy_reading(outcome: RestOk, bindings: Absent, etag: Present { value: "observed-revision" }, version: Absent) + (match missing { PolicyUnreadable { cause: reason } => reason != "" _ => false }) + && (match empty { PolicyUnreadable { cause: _ } => true _ => false }) + && (match observed { PolicyObserved { policy } => policy.etag == "observed-revision" _ => false }) +} + + +test fn pool_initialization_rejects_existing_unfenced_bindings() -> Bool { + let cells = iam_bootstrap_pool_initial_cells(pool: "github-heal-publisher") + count(cells) > 0 + && iam_bootstrap_pool_seedable(audit_configured: false, bindings: Absent, etag: Absent, version: Absent) + && !iam_bootstrap_pool_seedable(audit_configured: false, bindings: Present { value: gcp_cells_bindings(cs: cells) }, etag: Absent, version: Absent) + && !iam_bootstrap_pool_seedable(audit_configured: false, bindings: Absent, etag: Present { value: "revision" }, version: Absent) + && !iam_bootstrap_pool_seedable(audit_configured: false, bindings: Absent, etag: Absent, version: Present { value: 99 }) + && !iam_bootstrap_pool_seedable(audit_configured: true, bindings: Absent, etag: Absent, version: Absent) + && count(iam_bootstrap_pool_initial_cells(pool: "undeclared-pool")) == 0 +} + +test fn pool_initialization_readback_requires_exact_cells_and_a_revision() -> Bool { + let cells = iam_bootstrap_pool_initial_cells(pool: "github-heal-publisher") + let policy = GcpPolicy { bindings: gcp_cells_bindings(cs: cells), etag: "revision", version: 3 } + iam_bootstrap_pool_initialized(policy: policy, cells: cells) + && !iam_bootstrap_pool_initialized(policy: GcpPolicy { bindings: policy.bindings, etag: "", version: 3 }, cells: cells) + && !iam_bootstrap_pool_initialized(policy: GcpPolicy { bindings: [], etag: "revision", version: 3 }, cells: cells) + && !iam_bootstrap_pool_initialized(policy: policy, cells: []) +} + + +test fn probe_authority_has_no_signing_or_policy_permission() -> Bool { + iam_bootstrap_role_permissions(role: BootstrapProbeToken) == ["iam.serviceAccounts.getAccessToken"] + && iam_bootstrap_probe_cell(intent: authority_specimen).member == "user:brian@gunb.ai" + && (match iam_bootstrap_probe_cell(intent: authority_specimen).condition { + Absent => false + Present { value: condition } => condition.expression == "request.time < timestamp('2026-09-28T23:00:00Z')" + }) +} + +test fn probe_cleanup_is_required_for_trust_but_not_successful_probes() -> Bool { + iam_bootstrap_stage_admitted(stage: BootstrapProbeCleanup, completed: [BootstrapProbeAuthority]) + && !iam_bootstrap_stage_admitted(stage: BootstrapWorkflowTrust, + completed: [BootstrapDeny, BootstrapAuthorityCleanup, BootstrapPrivilegeReadback]) + && !iam_bootstrap_stage_admitted(stage: BootstrapPrivilegeReadback, completed: [BootstrapCapabilities]) +} + + +test fn service_enablement_requires_enabled_state_on_the_exact_project() -> Bool { + iam_bootstrap_service_enabled(name: "projects/582015116396/services/cloudresourcemanager.googleapis.com", state: "ENABLED", service: "cloudresourcemanager.googleapis.com") + && !iam_bootstrap_service_enabled(name: "projects/other/services/cloudresourcemanager.googleapis.com", state: "ENABLED", service: "cloudresourcemanager.googleapis.com") + && !iam_bootstrap_service_enabled(name: "projects/582015116396/services/cloudresourcemanager.googleapis.com", state: "DISABLED", service: "cloudresourcemanager.googleapis.com") + && !iam_bootstrap_service_enabled(name: "projects/582015116396/services/cloudresourcemanager.googleapis.com", state: "ENABLING", service: "cloudresourcemanager.googleapis.com") +} diff --git a/dag/test/claim/gcp_iam_converge_witness_test.dag b/dag/test/claim/gcp_iam_converge_witness_test.dag index 75625050612..58817f4f9d5 100644 --- a/dag/test/claim/gcp_iam_converge_witness_test.dag +++ b/dag/test/claim/gcp_iam_converge_witness_test.dag @@ -36,7 +36,7 @@ import gunbc.fleet_converge_workflow { fleet_converge_gcp_iam_request_step, fleet_converge_gcp_iam_actuate_auth_step, fleet_converge_gcp_iam_apply_step, fleet_converge_gcp_iam_approved_if, } -import gunbc.auth.gcp_iam_converge_run { iam_converge_token_env, gcp_iam_converge_admission, attempt_identity_of } +import gunbc.auth.gcp_iam_converge_run { iam_converge_token_env, gcp_iam_converge_admission } import gunbc.auth.approval_request_client { ApprovalPollStanding, PollApproved } import gunbc.auth.approval_gate { ApprovalGate, GateAdmitted, GateRefused } import extdeps.cloud.gcp.iam { gcp_cell_in, gcp_policy_cells } @@ -384,19 +384,19 @@ test fn only_a_live_approval_admits_the_mint_gate_or_the_apply() -> Bool { // the run attempt, so the two requests have different escalation ids -- attempt 2 reads its own slot, // never attempt 1's -- while consenting to the same revision. A missing or non-decimal part refuses. test fn a_rerun_attempt_files_afresh_and_malformed_attempts_refuse() -> Bool { - match attempt_identity_of(run_id: "36235907538", run_attempt: "1") { + match gunbc.auth.gcp_iam_converge_run.attempt_identity_of(run_id: "36235907538", run_attempt: "1") { Absent => false Present { value: a1 } => - match attempt_identity_of(run_id: "36235907538", run_attempt: "2") { + match gunbc.auth.gcp_iam_converge_run.attempt_identity_of(run_id: "36235907538", run_attempt: "2") { Absent => false Present { value: a2 } => { let q1 = gcp_iam_converge_request_over(d: one_effect_estate(etag: "e1"), g: digest_a, attempt: a1) let q2 = gcp_iam_converge_request_over(d: one_effect_estate(etag: "e1"), g: digest_a, attempt: a2) q1.escalation_id != q2.escalation_id && request_revision_of(request: q1) == request_revision_of(request: q2) - && match attempt_identity_of(run_id: "36235907538", run_attempt: "") { Absent => true Present { value: _ } => false } - && match attempt_identity_of(run_id: "36235907538", run_attempt: "2x") { Absent => true Present { value: _ } => false } - && match attempt_identity_of(run_id: "", run_attempt: "1") { Absent => true Present { value: _ } => false } + && match gunbc.auth.gcp_iam_converge_run.attempt_identity_of(run_id: "36235907538", run_attempt: "") { Absent => true Present { value: _ } => false } + && match gunbc.auth.gcp_iam_converge_run.attempt_identity_of(run_id: "36235907538", run_attempt: "2x") { Absent => true Present { value: _ } => false } + && match gunbc.auth.gcp_iam_converge_run.attempt_identity_of(run_id: "", run_attempt: "1") { Absent => true Present { value: _ } => false } } } } diff --git a/docs/plans/gcp-iam-bootstrap.md b/docs/plans/gcp-iam-bootstrap.md new file mode 100644 index 00000000000..f860430a5d1 --- /dev/null +++ b/docs/plans/gcp-iam-bootstrap.md @@ -0,0 +1,264 @@ +# Commission the approval-gated GCP IAM workflow + +The existing `gcp_iam_converge` fleet workflow needs its own cloud identities +before it can request approval and configure other workflows. This bootstrap +commissions that foundation from the existing IAM declarations. It does not +depend on the workspace allocation branch. + +## Entry points + +`gunbc.auth.gcp_iam_bootstrap.iam_bootstrap_plan` prints the exact roles, +permissions, bindings, deny permissions, and workflow admission condition without +cloud effects. `iam_bootstrap_apply` uses an explicitly supplied operator token +file and a fresh receipt identity: + +```bash +export GUNBC_GCP_ACCESS_TOKEN_FILE=/path/to/private/operator-token +export GUNBC_IAM_BOOTSTRAP_RECEIPT=operator-bootstrap-unique-attempt +export GUNBC_IAM_BOOTSTRAP_AUTHORITY_ID=stable-authority-operation +export GUNBC_IAM_BOOTSTRAP_AUTHORITY_EXPIRES_AT=2026-09-28T23:00:00Z # illustrative; set once for the intended operation +systemd-run --user --scope -p MemoryMax=6G -p MemorySwapMax=0 --quiet \ + ./target/release/gunbc run --source-root target/iam-bootstrap-controls \ + --entry target/iam-bootstrap-controls/gunbc.auth.gcp_iam_bootstrap.dag \ + --function iam_bootstrap_apply +``` + +The token file must be private and short-lived. No token belongs in a command +argument, source file, receipt, or GitHub variable. The ongoing workflow never +falls back to this operator token. No service-account key is created. + +The missing deny-policy permission is a modeled dependency, not a console task. +Under the operator's explicit authorization, the organization IAM writer can publish a +time-limited `roles/iam.denyAdmin` grant to `user:brian@gunb.ai`, install the +protection policy, then remove exactly that grant. GCP documents +[Deny Admin](https://docs.cloud.google.com/iam/docs/deny-access) and +[time-limited conditional bindings](https://docs.cloud.google.com/iam/docs/managing-conditional-role-bindings). +The account used for continuing automation never receives this grant. + +The operation ID and absolute UTC expiry identify one desired authority lease. +The maximum remaining duration is one hour. Retries retain both values and the +journal; they do not recalculate the expiry. A new lease requires a new explicit +intent. The current credential must be able to read/write organization IAM; if it +cannot, convergence reports that upstream dependency. This is an observed +boundary, not a declaration that organization IAM authority must forever be manual. + +## What bootstrap owns + +1. Observe and enable the declared APIs with independent readiness readback, then + create or exactly read back the dedicated IAM workflow pool/provider and its + observer/apply accounts. Create only bare pools and accounts for declared + target federations, so resource-local administrative roles can be attached. +2. Create or exactly read back the existing four apply role definitions and + three resource-specific observer roles plus one single-permission bootstrap probe role. Existing drift refuses adoption. +3. For an absent deny policy, converge the temporary operator authority, install + and read back the policy, and retire the exact temporary grant. Cleanup is + attempted on ordinary failure too; later capabilities and workflow trust require + successful cleanup. Existing exact deny policies need no new authority grant. +4. Add declared capability bindings with policy etags, preserving foreign cells. +5. Converge a time-bounded probe grant for the verified operator on only the + observer/apply accounts. The custom role contains only + `iam.serviceAccounts.getAccessToken`. Check access using 600-second tokens: + observer reads must work, and the apply account must receive HTTP 403 reading + a credential that the observer just successfully read. +6. Remove and independently verify absence of both temporary probe grants. Cleanup + also runs after ordinary failure and has a standalone recovery entry. Issued + tokens retain their original 600-second lifetime; deleting a grant is not + revocation of an already minted token. +7. Only after these checks and cleanup, add workload-identity impersonation + bindings for the existing, pinned IAM workflow. + +Target providers, workload impersonation, and target secret-access grants remain +in the existing approval-gated convergence plan. Bare target containers do not +activate those workflows. The target roster comes from `gcp_iam_converge_targets`, +including its current private-publisher and Namecheap entries; it is not copied +from the older allocation branch. + +Permission probes are diagnostics, not an authorization oracle. Configuration +readback and the real observer/apply secret-read control are separate checks. +Successful bootstrap still requires a real GitHub OIDC/approval/apply run before +the ongoing workflow can be called operational. + +## Recovery and evidence + +Each invocation creates `target/gcp-iam-bootstrap--started.txt` before +effects, then a separate receipt for every attempted stage. A refusal stops +later stages. A process loss or asynchronous creation may require a new attempt +identity; it rereads named resources and their current policies. A started +receipt is not completion evidence. Existing policy drift never authorizes +automatic privilege expansion. + +The existing `fleet_converge_workflow.dag` job already performs observer WIF, +request/approval, apply WIF, live approval recheck, and independent readback. +Bootstrap supplies its missing cloud prerequisites rather than adding another +approval workflow or a pasted-token path to secret consumers. + +## Validation + +The witness `test.claim.auth.gcp_iam_bootstrap_witness_test` is discovered and +executed by the required floor lane (`claim_executor --required-ci --required-lane +witnesses`) on every pull request and merge group that touches its closure; no +hand-assembled source root is needed. Qualification must also record +the binary hash, source revision, pure plan, missing-credential refusal, cloud +stage receipts, and a real approval-workflow run when available. + +API contracts follow Google Cloud's official references for +[custom roles](https://docs.cloud.google.com/iam/docs/reference/rest/v1/projects.roles/create), +[project policy updates](https://docs.cloud.google.com/resource-manager/reference/rest/v1/projects/setIamPolicy), +[workload identity pools](https://docs.cloud.google.com/iam/docs/reference/rest/v1/projects.locations.workloadIdentityPools), +and [deny policy creation](https://docs.cloud.google.com/iam/docs/reference/rest/v2/policies/createPolicy). + +## Current commissioning standing + +Bootstrap completed successfully at source `85bdb4e19` on 2026-09-29. All ten +stages passed: required API readiness, identities, roles, deny protection, +temporary deny-authority cleanup, capabilities, temporary probe authority, +effective access checks, probe-authority cleanup, and workflow trust. The observer +read the pinned approval credential and the writer received exact HTTP 403 for it. +Both temporary probe grants and the earlier Deny Admin grant were verified absent. +The operator token file was removed. See +`receipts/gcp-iam-services-2026-09-29/README.md` for evidence and limitations. + +The existing approval-gated workflow was then dispatched on main as run +`36505895733`. Bootstrap completion does not establish that workflow's approval +or apply acceptance, nor VM commissioning. Earlier refusal receipts remain as +historical evidence of the dependencies repaired along the way. + +## Temporary-authority dependency and recovery + +```mermaid +flowchart TD + P[Read project ancestry; verify pinned organization] --> R[Read organization IAM] + R --> W[Organization IAM write authority] + W --> I[Persist fixed operator lease intent] + I --> E[Elect one publication] + E --> G[CAS grant and read back exact conditioned cell] + G --> D[Install and read back deny policy] + D --> C[CAS remove owned cell and read back absence] + G --> F[Deny installation refuses or lease expires] + F --> C + C --> T[Persist retirement; continue bootstrap] + E --> U[Interrupted or unreadable publication] + U --> O[Reobserve the same intent and cell] + O --> C + U --> Q[Absent cell with unresolved write: keep obligation outstanding] +``` + +`target/gcp-iam-authority--*.txt` records the exact project, organization, principal, +role, condition, and expiry before effects. Keep these records with the operator +recovery workspace; they are local bootstrap state, not yet protected fabric +storage. Intent drift or unreadable records refuse. A create-only election means +at most one grant request is issued for this operation, even across retries. +An acknowledged successful publication or a grant observed present closes that publication; cleanup can recover a crash +between removal and recording retirement. A definitive publication HTTP 4xx also +closes the no-effect attempt. Other unresolved write outcomes remain outstanding +when a read sees absence. Neither a timeout nor expiry proves physical cleanup. +The cloud condition independently bounds effective access after process loss. + +The shared policy reconciler preserves unrelated grants and conditions, uses +version 3, and requires etags. Cleanup owns only the exact conditioned cell; +unconditional pre-existing administrator access survives. A retired operation +cannot publish again. `iam_bootstrap_authority_cleanup` drives withdrawal alone, +using the same authority ID, expiry, journal, and operator token-file input. + +The downstream account-impersonation readback still requires its actual authority. +That is a separate dependency to classify when reached, not permission to invent +an approval or give the continuing workflow self-elevation rights. + + +### Role grant scope correction + +The first dependency attempt was rejected with HTTP 400 because Deny Admin cannot +be granted on a project. The current model uses the role's documented +[organization-only grant scope](https://docs.cloud.google.com/iam/docs/roles-permissions/iam#iam.denyAdmin). +A read-only Resource Manager observation established that active +`projects/582015116396` (`gunbai-secrets`) belongs to +`organizations/266638272282`. The authority declaration pins that organization; +publication rereads the project and follows validated folder parents, refusing +unknown, changed, cyclic, or incomplete ancestry. No project move or organization +creation is inferred from missing ancestry. + +This temporary role is organization-scoped and time-limited; it is not represented +as project-scoped access. Only the named human operator receives it. The modeled +actuator still installs only the exact project deny policy. Cleanup addresses the +pinned organization even if the project later moves. These local journals support +one shared operator recovery workspace; they are not a distributed election across +independent workspaces or hosts. The original project-level attempt was rejected, +not an effective grant, and its historical receipt remains preserved. + + +A newly read-back authority binding may not yet be effective. Deny installation +permits at most 30 attempts separated by ten seconds, only after an explicit HTTP +403 and while the original lease remains live. Transport/server faults are not +retried as if nothing committed. An accepted create is followed by at most seven +readbacks separated by ten seconds, with no additional creation request. Exhaustion +or clock/deadline refusal enters the same authority cleanup obligation. + + +Before publishing temporary authority, the native Google UserInfo operation must +establish subject `116084671989231734979` (previously confirmed by the operator), +verified email, and the pinned IAM member `user:brian@gunb.ai`. Live UserInfo readback +identified this primary email; the earlier `briansrls@gunb.ai` spelling is not used +as the authority identity. A different subject with the same email, missing claims, +or changed transport email refuses publication. Cleanup does not require the old +operator credential's identity: an authorized recovery credential may remove the +already-pinned exact cell. No access token is put in a URL or command argument. + +## Exclusive initialization of an empty pool policy + +On 2026-09-29 the operator confirmed that no other administrator will update +these pool policies during bootstrap. This supplies an operational exclusivity +assumption, not a provider-enforced CAS token. Normal policy updates still require +an observed nonempty etag. + +The one-off initializer is limited to pool resources already derived by +`iam_bootstrap_capability_bindings`: `github-heal-publisher`, +`github-heal-publisher-private`, `github-mtcollins1-boot`, +`github-namecheap-dns`, and `github-gcp-iam-converge`. Each initial policy contains +all and only that pool's declared capability cells. The last pool receives +observer capability only; the apply principal cannot administer its own trust. +It does not install target OIDC providers or account impersonation grants. + +An invocation must explicitly select the confirmed window using +`GUNBC_IAM_BOOTSTRAP_POOL_WINDOW=exclusive-pool-policy-initialization-20260929`. +The native initializer verifies the same stable Google administrator identity, +requires an empty policy without a revision, elects one publication through a +create-only local journal, and performs independent readback. Completion requires +exact declared cells and a returned revision before later CAS updates are allowed. +A failed or ambiguous write retains the journal and cannot issue another +unconditional request. Recovery can close it only after matching readback. +Keep the same journal workspace across retries; do not delete election records or +run independent copies. The operator's exclusive window must still be in force +at actual execution. This is not a standing permission for future initializations. + +This slice has not been applied to Google yet. An approval receipt does not +create an administrator credential. The normal `gcp-iam-converge` workflow first +federates as `iam-observe`, files its request, and only after approval federates as +`iam-converge`. Both trust grants are deliberately the final bootstrap stage. +Consequently that workflow cannot bootstrap its own initial trust. An existing +administrator credential is a remaining explicit dependency; this environment +has neither gcloud nor a configured readable GCP token file. No credential or +refresh token is stored in these sources or receipts. + + +## Temporary probe authority + +The live pool run completed all capability grants, then the administrator token +received HTTP 403 for `iam.serviceAccounts.getAccessToken` on `iam-converge`. +That is now another explicit bootstrap dependency, not a console instruction. +`GUNBC_IAM_BOOTSTRAP_PROBE_ID` and `GUNBC_IAM_BOOTSTRAP_PROBE_EXPIRES_AT` pin its +operation and deadline, at most one hour away. The two exact-account conditional +grants name the verified human identity. They share the deny authority's journal, +publication election, etag update and cleanup machinery. No signing, key-creation, +policy-write or project-wide impersonation permission is included. + +Google documents the [access-token permission](https://docs.cloud.google.com/iam/docs/service-account-permissions). +Permission propagation retries accept only explicit HTTP 403, at most 85 attempts +with five-second waits (seven minutes of waiting), and stop at the original deadline. Other failures are not +converted to propagation. `iam_bootstrap_probe_cleanup` can recover both exact +grants without rerunning bootstrap; keep its journal and original environment. +Trust requires cleanup success. A retired probe operation cannot grant again. + +The first probe lease was removed after 30 token-mint refusals over roughly one +minute. That did not establish a permission-design defect: Google documents policy +propagation as typically two minutes, potentially seven or longer. The bounded +retry now permits seven minutes within the original lease; no role is widened. diff --git a/docs/plans/receipts/gcp-iam-bootstrap-2026-09-28/README.md b/docs/plans/receipts/gcp-iam-bootstrap-2026-09-28/README.md new file mode 100644 index 00000000000..2f40d3fcd70 --- /dev/null +++ b/docs/plans/receipts/gcp-iam-bootstrap-2026-09-28/README.md @@ -0,0 +1,56 @@ +# IAM bootstrap qualification and partial commissioning + +Source: `a23a0f99cbfe3f39da52cd40638eb44c85e76c6c`, based on main +`2886b9e396d5ca6026c54212f27c52ea4113fba4`. Independent draft: #12565. + +The branch's own release build succeeded. Its binary passed all three focused +controls, printed the exact bootstrap plan, and refused the missing-credential +control with the expected reason before creating a started receipt. All commands +ran under `MemoryMax=6G`, `MemorySwapMax=0`. `receipt.json` records binary and +closure-manifest hashes. The runbook reproduces the byte-identical closure; this +is not a full landing-suite result. + +## Cloud result: stopped before permissions were granted + +Attempt `20260928-a23a0f99c-1` submitted creation of the IAM job's pool, provider, +and observer account, but immediate readback did not establish their readiness. +It refused without reaching role or permission stages. + +Attempt `20260928-a23a0f99c-2` reread the same named resources, created the apply +account and missing bare Namecheap pool/account, and completed the identity +stage. Existing target resources were read, not replaced. It then created all +seven exact role definitions and independently reread them. + +GCP refused the next stage's deny-policy creation with HTTP 403: +`Permission iam.googleapis.com/denypolicies.create denied on resource +cloudresourcemanager.googleapis.com/projects/582015116396`. + +No capability bindings or workflow impersonation bindings were applied by this +bootstrap. No target provider was created. The privilege probes and real +OIDC/approval workflow acceptance have not run. Existing unrelated identities and +access were not removed. Roles are definitions, not grants to the new accounts. + +The operator-supplied token was read through a private temporary file, never a +command argument. The launcher removed that file after each attempt. Receipt +copies redact the temporary pathname and contain no token. The refusal is a +missing permission, not an observed token-expiry error. + +## Resume + +An existing administrator credential or approved administrative workflow must +supply the deny-policy authority on `gunbai-secrets`. The bootstrap never grants +that authority to itself. Run the same modeled entry with a new receipt identity; +completed named subjects and role definitions are reread before further effects. +Later capability and impersonation checks may expose additional missing +permissions; they have not been qualified by this partial run. + +Successful deny readback, capability reconciliation, observer/apply checks, and +final workload trust must precede the real approval-workflow acceptance run. + +## Fresh-token retry + +Attempt `20260928-a23a0f99c-3` used a newly supplied operator token and reread +the existing identities and seven role definitions. GCP again refused deny-policy +creation with HTTP 403 for `iam.denypolicies.create`. Capability and workflow +trust stages were not reached. Temporary credential removal was independently +checked. Refreshing the token did not supply the missing IAM permission. diff --git a/docs/plans/receipts/gcp-iam-bootstrap-2026-09-28/build.log b/docs/plans/receipts/gcp-iam-bootstrap-2026-09-28/build.log new file mode 100644 index 00000000000..41e6e0db3b0 --- /dev/null +++ b/docs/plans/receipts/gcp-iam-bootstrap-2026-09-28/build.log @@ -0,0 +1,114 @@ + Compiling proc-macro2 v1.0.106 + Compiling quote v1.0.45 + Compiling unicode-ident v1.0.24 + Compiling syn v2.0.117 + Compiling shlex v1.3.0 + Compiling find-msvc-tools v0.1.9 + Compiling cc v1.2.62 + Compiling synstructure v0.13.2 + Compiling version_check v0.9.5 + Compiling serde_core v1.0.228 + Compiling libc v0.2.186 + Compiling zerofrom-derive v0.1.7 + Compiling typenum v1.20.1 + Compiling cfg-if v1.0.4 + Compiling zerofrom v0.1.8 + Compiling yoke-derive v0.8.2 + Compiling serde v1.0.228 + Compiling stable_deref_trait v1.2.1 + Compiling yoke v0.8.2 + Compiling serde_derive v1.0.228 + Compiling object v0.37.3 + Compiling zerovec-derive v0.11.3 + Compiling memchr v2.8.0 + Compiling zerovec v0.11.6 + Compiling displaydoc v0.2.5 + Compiling ar_archive_writer v0.5.1 + Compiling zmij v1.0.21 + Compiling psm v0.1.31 + Compiling serde_json v1.0.149 + Compiling tinystr v0.8.3 + Compiling bitmaps v2.1.0 + Compiling im v15.1.0 + Compiling stacker v0.1.24 + Compiling rand_core v0.6.4 + Compiling litemap v0.8.2 + Compiling writeable v0.6.3 + Compiling itoa v1.0.18 + Compiling icu_locale_core v2.2.0 + Compiling rand_xoshiro v0.6.0 + Compiling sized-chunks v0.6.5 + Compiling zerotrie v0.2.4 + Compiling potential_utf v0.1.5 + Compiling generic-array v0.14.7 + Compiling icu_properties_data v2.2.0 + Compiling icu_normalizer_data v2.2.0 + Compiling utf8_iter v1.0.4 + Compiling icu_collections v2.2.0 + Compiling icu_provider v2.2.0 + Compiling ring v0.17.14 + Compiling unicode-properties v0.1.4 + Compiling zeroize v1.8.2 + Compiling v1-stage0-runtime v0.1.0 (/home/briansrls/scratch-wt/gcp-iam-bootstrap/src/v1/stage0_runtime) + Compiling rustls-pki-types v1.14.1 + Compiling subtle v2.6.1 + Compiling smallvec v1.15.1 + Compiling icu_normalizer v2.2.0 + Compiling icu_properties v2.2.0 + Compiling v1-stage0-std-core v0.1.0 (/home/briansrls/scratch-wt/gcp-iam-bootstrap/src/v1/stage0_std_core) + Compiling getrandom v0.2.17 + Compiling untrusted v0.9.0 + Compiling utf8parse v0.2.2 + Compiling crc32fast v1.5.0 + Compiling anstyle-parse v1.0.0 + Compiling v1-stage0-std-surface v0.1.0 (/home/briansrls/scratch-wt/gcp-iam-bootstrap/src/v1/stage0_std_surface) + Compiling idna_adapter v1.2.2 + Compiling crypto-common v0.1.7 + Compiling block-buffer v0.10.4 + Compiling adler2 v2.0.1 + Compiling colorchoice v1.0.5 + Compiling equivalent v1.0.2 + Compiling percent-encoding v2.3.2 + Compiling simd-adler32 v0.3.9 + Compiling rustls v0.23.40 + Compiling anstyle-query v1.1.5 + Compiling hashbrown v0.17.1 + Compiling is_terminal_polyfill v1.70.2 + Compiling anstyle v1.0.14 + Compiling anstream v1.0.0 + Compiling indexmap v2.14.0 + Compiling miniz_oxide v0.8.9 + Compiling form_urlencoded v1.2.2 + Compiling digest v0.10.7 + Compiling idna v1.1.0 + Compiling v1-stage0-extdeps-languages v0.1.0 (/home/briansrls/scratch-wt/gcp-iam-bootstrap/src/v1/stage0_extdeps_languages) + Compiling rustls-webpki v0.103.13 + Compiling webpki-roots v1.0.7 + Compiling toml_datetime v0.6.11 + Compiling serde_spanned v0.6.9 + Compiling strsim v0.11.1 + Compiling winnow v0.7.15 + Compiling heck v0.5.0 + Compiling log v0.4.29 + Compiling clap_lex v1.1.0 + Compiling toml_write v0.1.2 + Compiling once_cell v1.21.4 + Compiling toml_edit v0.22.27 + Compiling clap_builder v4.6.0 + Compiling clap_derive v4.6.1 + Compiling webpki-roots v0.26.11 + Compiling flate2 v1.1.9 + Compiling v1-stage0-v1-artifact v0.1.0 (/home/briansrls/scratch-wt/gcp-iam-bootstrap/src/v1/stage0_v1_artifact) + Compiling url v2.5.8 + Compiling cpufeatures v0.2.17 + Compiling base64 v0.22.1 + Compiling v1-compiler v0.1.0 (/home/briansrls/scratch-wt/gcp-iam-bootstrap/src/v1/stage0) + Compiling ureq v2.12.1 + Compiling sha2 v0.10.9 + Compiling v1-stage0-v1-infer v0.1.0 (/home/briansrls/scratch-wt/gcp-iam-bootstrap/src/v1/stage0_v1_infer) + Compiling clap v4.6.1 + Compiling toml v0.8.23 + Compiling hmac v0.12.1 + Compiling lazy_static v1.5.0 + Compiling hex v0.4.3 + Finished `release` profile [optimized] target(s) in 15m 29s diff --git a/docs/plans/receipts/gcp-iam-bootstrap-2026-09-28/cloud-attempt-1.log b/docs/plans/receipts/gcp-iam-bootstrap-2026-09-28/cloud-attempt-1.log new file mode 100644 index 00000000000..820189fcf42 --- /dev/null +++ b/docs/plans/receipts/gcp-iam-bootstrap-2026-09-28/cloud-attempt-1.log @@ -0,0 +1,17 @@ +[workspace-root] discovered /home/briansrls/scratch-wt/gcp-iam-bootstrap +✓ typecheck v2.std.compilers.target_model done in 20 seconds +✓ typecheck v2.compiler.target_serialize done in 2 seconds +✓ typecheck v2.compiler.translate done in 3 seconds +[file] read +[file] write_create_new target/gcp-iam-bootstrap-20260928-a23a0f99c-1-started.txt (54 bytes) +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-gcp-iam-converge +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-gcp-iam-converge/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/iam-observe@gunbai-secrets.iam.gserviceaccount.com +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-gcp-iam-converge/providers +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-gcp-iam-converge +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-gcp-iam-converge/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/iam-observe@gunbai-secrets.iam.gserviceaccount.com +[file] write_create_new target/gcp-iam-bootstrap-20260928-a23a0f99c-1-identities.txt (114 bytes) +gcp iam converge observe federation: named creation is not yet established by readback; no rights granted diff --git a/docs/plans/receipts/gcp-iam-bootstrap-2026-09-28/cloud-attempt-2.log b/docs/plans/receipts/gcp-iam-bootstrap-2026-09-28/cloud-attempt-2.log new file mode 100644 index 00000000000..dc9ecc69f38 --- /dev/null +++ b/docs/plans/receipts/gcp-iam-bootstrap-2026-09-28/cloud-attempt-2.log @@ -0,0 +1,91 @@ +[workspace-root] discovered /home/briansrls/scratch-wt/gcp-iam-bootstrap +✓ typecheck v2.std.compilers.target_model done in 23 seconds +✓ typecheck v2.compiler.target_serialize done in 2 seconds +✓ typecheck v2.compiler.translate done in 3 seconds +[file] read +[file] write_create_new target/gcp-iam-bootstrap-20260928-a23a0f99c-2-started.txt (54 bytes) +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-gcp-iam-converge +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-gcp-iam-converge/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/iam-observe@gunbai-secrets.iam.gserviceaccount.com +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-gcp-iam-converge +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-gcp-iam-converge/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/iam-observe@gunbai-secrets.iam.gserviceaccount.com +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-gcp-iam-converge +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-gcp-iam-converge/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-gcp-iam-converge +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-gcp-iam-converge/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/heal-publisher@gunbai-secrets.iam.gserviceaccount.com +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/heal-publisher@gunbai-secrets.iam.gserviceaccount.com +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher-private +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher-private/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/heal-publisher-private@gunbai-secrets.iam.gserviceaccount.com +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher-private +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher-private/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/heal-publisher-private@gunbai-secrets.iam.gserviceaccount.com +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-mtcollins1-boot +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-mtcollins1-boot/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/mtcollins1-boot@gunbai-secrets.iam.gserviceaccount.com +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-mtcollins1-boot +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-mtcollins1-boot/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/mtcollins1-boot@gunbai-secrets.iam.gserviceaccount.com +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-namecheap-dns +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-namecheap-dns/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/namecheap-dns@gunbai-secrets.iam.gserviceaccount.com +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-namecheap-dns +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-namecheap-dns/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/namecheap-dns@gunbai-secrets.iam.gserviceaccount.com +[file] write_create_new target/gcp-iam-bootstrap-20260928-a23a0f99c-2-identities.txt (78 bytes) +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/roles/gunbcIamConvergeCreate +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/roles +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/roles/gunbcIamConvergeCreate +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/roles/gunbcIamConvergeProvider +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/roles +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/roles/gunbcIamConvergeProvider +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/roles/gunbcIamConvergeAccountPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/roles +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/roles/gunbcIamConvergeAccountPolicy +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/roles/gunbcIamConvergeSecretPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/roles +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/roles/gunbcIamConvergeSecretPolicy +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/roles/gunbcIamObservePool +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/roles +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/roles/gunbcIamObservePool +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/roles/gunbcIamObserveAccount +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/roles +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/roles/gunbcIamObserveAccount +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/roles/gunbcIamObserveSecret +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/roles +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/roles/gunbcIamObserveSecret +[file] write_create_new target/gcp-iam-bootstrap-20260928-a23a0f99c-2-roles.txt (78 bytes) +[rest] GET https://iam.googleapis.com/v2/policies/cloudresourcemanager.googleapis.com%2Fprojects%2F582015116396/denypolicies/gunbc-iam-converge-boundary +[rest] POST https://iam.googleapis.com/v2/policies/cloudresourcemanager.googleapis.com%2Fprojects%2F582015116396/denypolicies +[file] write_create_new target/gcp-iam-bootstrap-20260928-a23a0f99c-2-deny.txt (879 bytes) +status 403: { + "error": { + "code": 403, + "message": "Permission iam.googleapis.com/denypolicies.create denied on resource cloudresourcemanager.googleapis.com/projects/582015116396", + "status": "PERMISSION_DENIED", + "details": [ + { + "@type": "type.googleapis.com/google.rpc.ErrorInfo", + "reason": "IAM_PERMISSION_DENIED", + "domain": "iam.googleapis.com", + "metadata": { + "permission": "iam.googleapis.com/denypolicies.create", + "resource": "projects/582015116396", + "error_info_id": "CiQwMWEwZWEwOS1lY2U1LTdmYTgtOGUyNi03NGQyNmM0MjRkODQSFXByb2plY3RzLzU4MjAxNTExNjM5Ng==", + "troubleshooter_url": "https://console.cloud.google.com/iam-admin/troubleshooter/summary;errorId=CiQwMWEwZWEwOS1lY2U1LTdmYTgtOGUyNi03NGQyNmM0MjRkODQSFXByb2plY3RzLzU4MjAxNTExNjM5Ng%3D%3D" + } + } + ] + } +} diff --git a/docs/plans/receipts/gcp-iam-bootstrap-2026-09-28/cloud-attempt-3.log b/docs/plans/receipts/gcp-iam-bootstrap-2026-09-28/cloud-attempt-3.log new file mode 100644 index 00000000000..0d7ec5449d0 --- /dev/null +++ b/docs/plans/receipts/gcp-iam-bootstrap-2026-09-28/cloud-attempt-3.log @@ -0,0 +1,74 @@ +[workspace-root] discovered /home/briansrls/scratch-wt/gcp-iam-bootstrap +✓ typecheck v2.std.compilers.target_model done in 20 seconds +✓ typecheck v2.compiler.target_serialize done in 2 seconds +✓ typecheck v2.compiler.translate done in 3 seconds +[file] read +[file] write_create_new target/gcp-iam-bootstrap-20260928-a23a0f99c-3-started.txt (54 bytes) +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-gcp-iam-converge +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-gcp-iam-converge/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/iam-observe@gunbai-secrets.iam.gserviceaccount.com +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-gcp-iam-converge +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-gcp-iam-converge/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/iam-observe@gunbai-secrets.iam.gserviceaccount.com +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-gcp-iam-converge +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-gcp-iam-converge/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-gcp-iam-converge +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-gcp-iam-converge/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/heal-publisher@gunbai-secrets.iam.gserviceaccount.com +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/heal-publisher@gunbai-secrets.iam.gserviceaccount.com +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher-private +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher-private/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/heal-publisher-private@gunbai-secrets.iam.gserviceaccount.com +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher-private +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher-private/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/heal-publisher-private@gunbai-secrets.iam.gserviceaccount.com +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-mtcollins1-boot +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-mtcollins1-boot/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/mtcollins1-boot@gunbai-secrets.iam.gserviceaccount.com +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-mtcollins1-boot +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-mtcollins1-boot/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/mtcollins1-boot@gunbai-secrets.iam.gserviceaccount.com +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-namecheap-dns +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-namecheap-dns/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/namecheap-dns@gunbai-secrets.iam.gserviceaccount.com +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-namecheap-dns +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-namecheap-dns/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/namecheap-dns@gunbai-secrets.iam.gserviceaccount.com +[file] write_create_new target/gcp-iam-bootstrap-20260928-a23a0f99c-3-identities.txt (78 bytes) +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/roles/gunbcIamConvergeCreate +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/roles/gunbcIamConvergeProvider +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/roles/gunbcIamConvergeAccountPolicy +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/roles/gunbcIamConvergeSecretPolicy +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/roles/gunbcIamObservePool +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/roles/gunbcIamObserveAccount +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/roles/gunbcIamObserveSecret +[file] write_create_new target/gcp-iam-bootstrap-20260928-a23a0f99c-3-roles.txt (78 bytes) +[rest] GET https://iam.googleapis.com/v2/policies/cloudresourcemanager.googleapis.com%2Fprojects%2F582015116396/denypolicies/gunbc-iam-converge-boundary +[rest] POST https://iam.googleapis.com/v2/policies/cloudresourcemanager.googleapis.com%2Fprojects%2F582015116396/denypolicies +[file] write_create_new target/gcp-iam-bootstrap-20260928-a23a0f99c-3-deny.txt (879 bytes) +status 403: { + "error": { + "code": 403, + "message": "Permission iam.googleapis.com/denypolicies.create denied on resource cloudresourcemanager.googleapis.com/projects/582015116396", + "status": "PERMISSION_DENIED", + "details": [ + { + "@type": "type.googleapis.com/google.rpc.ErrorInfo", + "reason": "IAM_PERMISSION_DENIED", + "domain": "iam.googleapis.com", + "metadata": { + "permission": "iam.googleapis.com/denypolicies.create", + "error_info_id": "CiQwMWEwZWExMi1mMzBkLTdkMjUtOWYwZC1mMjNhNGJmYmFhNmQSFXByb2plY3RzLzU4MjAxNTExNjM5Ng==", + "resource": "projects/582015116396", + "troubleshooter_url": "https://console.cloud.google.com/iam-admin/troubleshooter/summary;errorId=CiQwMWEwZWExMi1mMzBkLTdkMjUtOWYwZC1mMjNhNGJmYmFhNmQSFXByb2plY3RzLzU4MjAxNTExNjM5Ng%3D%3D" + } + } + ] + } +} diff --git a/docs/plans/receipts/gcp-iam-bootstrap-2026-09-28/controls.log b/docs/plans/receipts/gcp-iam-bootstrap-2026-09-28/controls.log new file mode 100644 index 00000000000..7bff137e61b --- /dev/null +++ b/docs/plans/receipts/gcp-iam-bootstrap-2026-09-28/controls.log @@ -0,0 +1,7 @@ +[workspace-root] discovered /home/briansrls/scratch-wt/gcp-iam-bootstrap +✓ typecheck v2.std.compilers.target_model done in 20 seconds +✓ typecheck v2.compiler.target_serialize done in 2 seconds +✓ typecheck v2.compiler.translate done in 3 seconds +PASS bootstrap_role_drift_never_authorizes_adoption +PASS deny_exceptions_and_incomplete_readback_withhold_trust +PASS workflow_trust_requires_completed_deny_and_privilege_readback diff --git a/docs/plans/receipts/gcp-iam-bootstrap-2026-09-28/gcp-iam-bootstrap-20260928-a23a0f99c-1-identities.txt b/docs/plans/receipts/gcp-iam-bootstrap-2026-09-28/gcp-iam-bootstrap-20260928-a23a0f99c-1-identities.txt new file mode 100644 index 00000000000..2522b49ad07 --- /dev/null +++ b/docs/plans/receipts/gcp-iam-bootstrap-2026-09-28/gcp-iam-bootstrap-20260928-a23a0f99c-1-identities.txt @@ -0,0 +1 @@ +refused: gcp iam converge observe federation: named creation is not yet established by readback; no rights granted \ No newline at end of file diff --git a/docs/plans/receipts/gcp-iam-bootstrap-2026-09-28/gcp-iam-bootstrap-20260928-a23a0f99c-1-started.txt b/docs/plans/receipts/gcp-iam-bootstrap-2026-09-28/gcp-iam-bootstrap-20260928-a23a0f99c-1-started.txt new file mode 100644 index 00000000000..b286fc75b3e --- /dev/null +++ b/docs/plans/receipts/gcp-iam-bootstrap-2026-09-28/gcp-iam-bootstrap-20260928-a23a0f99c-1-started.txt @@ -0,0 +1 @@ +operator bootstrap started; not a convergence receipt diff --git a/docs/plans/receipts/gcp-iam-bootstrap-2026-09-28/gcp-iam-bootstrap-20260928-a23a0f99c-2-deny.txt b/docs/plans/receipts/gcp-iam-bootstrap-2026-09-28/gcp-iam-bootstrap-20260928-a23a0f99c-2-deny.txt new file mode 100644 index 00000000000..bb00c5c4118 --- /dev/null +++ b/docs/plans/receipts/gcp-iam-bootstrap-2026-09-28/gcp-iam-bootstrap-20260928-a23a0f99c-2-deny.txt @@ -0,0 +1,20 @@ +refused: status 403: { + "error": { + "code": 403, + "message": "Permission iam.googleapis.com/denypolicies.create denied on resource cloudresourcemanager.googleapis.com/projects/582015116396", + "status": "PERMISSION_DENIED", + "details": [ + { + "@type": "type.googleapis.com/google.rpc.ErrorInfo", + "reason": "IAM_PERMISSION_DENIED", + "domain": "iam.googleapis.com", + "metadata": { + "permission": "iam.googleapis.com/denypolicies.create", + "resource": "projects/582015116396", + "error_info_id": "CiQwMWEwZWEwOS1lY2U1LTdmYTgtOGUyNi03NGQyNmM0MjRkODQSFXByb2plY3RzLzU4MjAxNTExNjM5Ng==", + "troubleshooter_url": "https://console.cloud.google.com/iam-admin/troubleshooter/summary;errorId=CiQwMWEwZWEwOS1lY2U1LTdmYTgtOGUyNi03NGQyNmM0MjRkODQSFXByb2plY3RzLzU4MjAxNTExNjM5Ng%3D%3D" + } + } + ] + } +} diff --git a/docs/plans/receipts/gcp-iam-bootstrap-2026-09-28/gcp-iam-bootstrap-20260928-a23a0f99c-2-identities.txt b/docs/plans/receipts/gcp-iam-bootstrap-2026-09-28/gcp-iam-bootstrap-20260928-a23a0f99c-2-identities.txt new file mode 100644 index 00000000000..480da8f3ea6 --- /dev/null +++ b/docs/plans/receipts/gcp-iam-bootstrap-2026-09-28/gcp-iam-bootstrap-20260928-a23a0f99c-2-identities.txt @@ -0,0 +1 @@ +stage checks completed; see the stage authority for configuration/probe scope diff --git a/docs/plans/receipts/gcp-iam-bootstrap-2026-09-28/gcp-iam-bootstrap-20260928-a23a0f99c-2-roles.txt b/docs/plans/receipts/gcp-iam-bootstrap-2026-09-28/gcp-iam-bootstrap-20260928-a23a0f99c-2-roles.txt new file mode 100644 index 00000000000..480da8f3ea6 --- /dev/null +++ b/docs/plans/receipts/gcp-iam-bootstrap-2026-09-28/gcp-iam-bootstrap-20260928-a23a0f99c-2-roles.txt @@ -0,0 +1 @@ +stage checks completed; see the stage authority for configuration/probe scope diff --git a/docs/plans/receipts/gcp-iam-bootstrap-2026-09-28/gcp-iam-bootstrap-20260928-a23a0f99c-2-started.txt b/docs/plans/receipts/gcp-iam-bootstrap-2026-09-28/gcp-iam-bootstrap-20260928-a23a0f99c-2-started.txt new file mode 100644 index 00000000000..b286fc75b3e --- /dev/null +++ b/docs/plans/receipts/gcp-iam-bootstrap-2026-09-28/gcp-iam-bootstrap-20260928-a23a0f99c-2-started.txt @@ -0,0 +1 @@ +operator bootstrap started; not a convergence receipt diff --git a/docs/plans/receipts/gcp-iam-bootstrap-2026-09-28/gcp-iam-bootstrap-20260928-a23a0f99c-3-deny.txt b/docs/plans/receipts/gcp-iam-bootstrap-2026-09-28/gcp-iam-bootstrap-20260928-a23a0f99c-3-deny.txt new file mode 100644 index 00000000000..02019ed4fe0 --- /dev/null +++ b/docs/plans/receipts/gcp-iam-bootstrap-2026-09-28/gcp-iam-bootstrap-20260928-a23a0f99c-3-deny.txt @@ -0,0 +1,20 @@ +refused: status 403: { + "error": { + "code": 403, + "message": "Permission iam.googleapis.com/denypolicies.create denied on resource cloudresourcemanager.googleapis.com/projects/582015116396", + "status": "PERMISSION_DENIED", + "details": [ + { + "@type": "type.googleapis.com/google.rpc.ErrorInfo", + "reason": "IAM_PERMISSION_DENIED", + "domain": "iam.googleapis.com", + "metadata": { + "permission": "iam.googleapis.com/denypolicies.create", + "error_info_id": "CiQwMWEwZWExMi1mMzBkLTdkMjUtOWYwZC1mMjNhNGJmYmFhNmQSFXByb2plY3RzLzU4MjAxNTExNjM5Ng==", + "resource": "projects/582015116396", + "troubleshooter_url": "https://console.cloud.google.com/iam-admin/troubleshooter/summary;errorId=CiQwMWEwZWExMi1mMzBkLTdkMjUtOWYwZC1mMjNhNGJmYmFhNmQSFXByb2plY3RzLzU4MjAxNTExNjM5Ng%3D%3D" + } + } + ] + } +} diff --git a/docs/plans/receipts/gcp-iam-bootstrap-2026-09-28/gcp-iam-bootstrap-20260928-a23a0f99c-3-identities.txt b/docs/plans/receipts/gcp-iam-bootstrap-2026-09-28/gcp-iam-bootstrap-20260928-a23a0f99c-3-identities.txt new file mode 100644 index 00000000000..480da8f3ea6 --- /dev/null +++ b/docs/plans/receipts/gcp-iam-bootstrap-2026-09-28/gcp-iam-bootstrap-20260928-a23a0f99c-3-identities.txt @@ -0,0 +1 @@ +stage checks completed; see the stage authority for configuration/probe scope diff --git a/docs/plans/receipts/gcp-iam-bootstrap-2026-09-28/gcp-iam-bootstrap-20260928-a23a0f99c-3-roles.txt b/docs/plans/receipts/gcp-iam-bootstrap-2026-09-28/gcp-iam-bootstrap-20260928-a23a0f99c-3-roles.txt new file mode 100644 index 00000000000..480da8f3ea6 --- /dev/null +++ b/docs/plans/receipts/gcp-iam-bootstrap-2026-09-28/gcp-iam-bootstrap-20260928-a23a0f99c-3-roles.txt @@ -0,0 +1 @@ +stage checks completed; see the stage authority for configuration/probe scope diff --git a/docs/plans/receipts/gcp-iam-bootstrap-2026-09-28/gcp-iam-bootstrap-20260928-a23a0f99c-3-started.txt b/docs/plans/receipts/gcp-iam-bootstrap-2026-09-28/gcp-iam-bootstrap-20260928-a23a0f99c-3-started.txt new file mode 100644 index 00000000000..b286fc75b3e --- /dev/null +++ b/docs/plans/receipts/gcp-iam-bootstrap-2026-09-28/gcp-iam-bootstrap-20260928-a23a0f99c-3-started.txt @@ -0,0 +1 @@ +operator bootstrap started; not a convergence receipt diff --git a/docs/plans/receipts/gcp-iam-bootstrap-2026-09-28/no-credential.log b/docs/plans/receipts/gcp-iam-bootstrap-2026-09-28/no-credential.log new file mode 100644 index 00000000000..9a75d5f411d --- /dev/null +++ b/docs/plans/receipts/gcp-iam-bootstrap-2026-09-28/no-credential.log @@ -0,0 +1,5 @@ +[workspace-root] discovered /home/briansrls/scratch-wt/gcp-iam-bootstrap +✓ typecheck v2.std.compilers.target_model done in 24 seconds +✓ typecheck v2.compiler.target_serialize done in 2 seconds +✓ typecheck v2.compiler.translate done in 3 seconds +no GUNBC_GCP_ACCESS_TOKEN_FILE in the environment: this entry needs a path to a file holding an access token diff --git a/docs/plans/receipts/gcp-iam-bootstrap-2026-09-28/plan.log b/docs/plans/receipts/gcp-iam-bootstrap-2026-09-28/plan.log new file mode 100644 index 00000000000..35135a13db7 --- /dev/null +++ b/docs/plans/receipts/gcp-iam-bootstrap-2026-09-28/plan.log @@ -0,0 +1,52 @@ +[workspace-root] discovered /home/briansrls/scratch-wt/gcp-iam-bootstrap +✓ typecheck v2.std.compilers.target_model done in 23 seconds +✓ typecheck v2.compiler.target_serialize done in 2 seconds +✓ typecheck v2.compiler.translate done in 3 seconds +Bootstrap stages: identities -> roles -> deny -> capabilities -> privilege-readback -> workflow-trust +project gunbai-secrets +workflow provider github-gcp-iam-converge-oidc +workflow condition assertion.repository_id == '1143718943' && assertion.repository_owner_id == '244123794' && assertion.workflow_ref == 'gunb-ai/gunbc/.github/workflows/fleet-converge.yml@refs/heads/main' && assertion.ref == 'refs/heads/main' && assertion.event_name == 'workflow_dispatch' && assertion.environment == 'gcp-iam-converge' +workflow principal set principalSet://iam.googleapis.com/projects/582015116396/locations/global/workloadIdentityPools/github-gcp-iam-converge/* +deny policy gunbc-iam-converge-boundary +deny principal serviceAccount:iam-converge@gunbai-secrets.iam.gserviceaccount.com +deny permissions secretmanager.googleapis.com/versions.access,secretmanager.googleapis.com/versions.add,iam.googleapis.com/serviceAccounts.actAs,iam.googleapis.com/serviceAccounts.getAccessToken,iam.googleapis.com/serviceAccounts.getOpenIdToken,iam.googleapis.com/serviceAccounts.implicitDelegation,iam.googleapis.com/serviceAccounts.signBlob,iam.googleapis.com/serviceAccounts.signJwt +Target bootstrap creates only bare pools/accounts; target providers and workload trust remain approval-gated. +role projects/gunbai-secrets/roles/gunbcIamConvergeCreate permissions=iam.workloadIdentityPools.create,iam.workloadIdentityPools.get,iam.serviceAccounts.create,iam.serviceAccounts.get +role projects/gunbai-secrets/roles/gunbcIamConvergeProvider permissions=iam.workloadIdentityPoolProviders.create,iam.workloadIdentityPoolProviders.get,iam.workloadIdentityPoolProviders.list +role projects/gunbai-secrets/roles/gunbcIamConvergeAccountPolicy permissions=iam.serviceAccounts.get,iam.serviceAccounts.getIamPolicy,iam.serviceAccounts.setIamPolicy +role projects/gunbai-secrets/roles/gunbcIamConvergeSecretPolicy permissions=secretmanager.secrets.get,secretmanager.secrets.getIamPolicy,secretmanager.secrets.setIamPolicy +role projects/gunbai-secrets/roles/gunbcIamObservePool permissions=iam.workloadIdentityPools.get,iam.workloadIdentityPoolProviders.get,iam.workloadIdentityPoolProviders.list +role projects/gunbai-secrets/roles/gunbcIamObserveAccount permissions=iam.serviceAccounts.get,iam.serviceAccounts.getIamPolicy +role projects/gunbai-secrets/roles/gunbcIamObserveSecret permissions=secretmanager.secrets.get,secretmanager.secrets.getIamPolicy +binding projects/gunbai-secrets role:52:projects/gunbai-secrets/roles/gunbcIamConvergeCreate;member:66:serviceAccount:iam-converge@gunbai-secrets.iam.gserviceaccount.com;condition:4:none; +binding projects/582015116396/locations/global/workloadIdentityPools/github-heal-publisher role:54:projects/gunbai-secrets/roles/gunbcIamConvergeProvider;member:66:serviceAccount:iam-converge@gunbai-secrets.iam.gserviceaccount.com;condition:4:none; +binding projects/gunbai-secrets/serviceAccounts/heal-publisher@gunbai-secrets.iam.gserviceaccount.com role:59:projects/gunbai-secrets/roles/gunbcIamConvergeAccountPolicy;member:66:serviceAccount:iam-converge@gunbai-secrets.iam.gserviceaccount.com;condition:4:none; +binding projects/582015116396/secrets/agent-github-app-private-key role:58:projects/gunbai-secrets/roles/gunbcIamConvergeSecretPolicy;member:66:serviceAccount:iam-converge@gunbai-secrets.iam.gserviceaccount.com;condition:4:none; +binding projects/582015116396/locations/global/workloadIdentityPools/github-heal-publisher-private role:54:projects/gunbai-secrets/roles/gunbcIamConvergeProvider;member:66:serviceAccount:iam-converge@gunbai-secrets.iam.gserviceaccount.com;condition:4:none; +binding projects/gunbai-secrets/serviceAccounts/heal-publisher-private@gunbai-secrets.iam.gserviceaccount.com role:59:projects/gunbai-secrets/roles/gunbcIamConvergeAccountPolicy;member:66:serviceAccount:iam-converge@gunbai-secrets.iam.gserviceaccount.com;condition:4:none; +binding projects/582015116396/locations/global/workloadIdentityPools/github-mtcollins1-boot role:54:projects/gunbai-secrets/roles/gunbcIamConvergeProvider;member:66:serviceAccount:iam-converge@gunbai-secrets.iam.gserviceaccount.com;condition:4:none; +binding projects/gunbai-secrets/serviceAccounts/mtcollins1-boot@gunbai-secrets.iam.gserviceaccount.com role:59:projects/gunbai-secrets/roles/gunbcIamConvergeAccountPolicy;member:66:serviceAccount:iam-converge@gunbai-secrets.iam.gserviceaccount.com;condition:4:none; +binding projects/582015116396/secrets/bmc-mtcollins1-gunbc role:58:projects/gunbai-secrets/roles/gunbcIamConvergeSecretPolicy;member:66:serviceAccount:iam-converge@gunbai-secrets.iam.gserviceaccount.com;condition:4:none; +binding projects/582015116396/secrets/fleet-automation-ssh-key role:58:projects/gunbai-secrets/roles/gunbcIamConvergeSecretPolicy;member:66:serviceAccount:iam-converge@gunbai-secrets.iam.gserviceaccount.com;condition:4:none; +binding projects/582015116396/locations/global/workloadIdentityPools/github-namecheap-dns role:54:projects/gunbai-secrets/roles/gunbcIamConvergeProvider;member:66:serviceAccount:iam-converge@gunbai-secrets.iam.gserviceaccount.com;condition:4:none; +binding projects/gunbai-secrets/serviceAccounts/namecheap-dns@gunbai-secrets.iam.gserviceaccount.com role:59:projects/gunbai-secrets/roles/gunbcIamConvergeAccountPolicy;member:66:serviceAccount:iam-converge@gunbai-secrets.iam.gserviceaccount.com;condition:4:none; +binding projects/582015116396/secrets/namecheap-api-key role:58:projects/gunbai-secrets/roles/gunbcIamConvergeSecretPolicy;member:66:serviceAccount:iam-converge@gunbai-secrets.iam.gserviceaccount.com;condition:4:none; +binding projects/582015116396/locations/global/workloadIdentityPools/github-heal-publisher role:49:projects/gunbai-secrets/roles/gunbcIamObservePool;member:65:serviceAccount:iam-observe@gunbai-secrets.iam.gserviceaccount.com;condition:4:none; +binding projects/gunbai-secrets/serviceAccounts/heal-publisher@gunbai-secrets.iam.gserviceaccount.com role:52:projects/gunbai-secrets/roles/gunbcIamObserveAccount;member:65:serviceAccount:iam-observe@gunbai-secrets.iam.gserviceaccount.com;condition:4:none; +binding projects/582015116396/secrets/agent-github-app-private-key role:51:projects/gunbai-secrets/roles/gunbcIamObserveSecret;member:65:serviceAccount:iam-observe@gunbai-secrets.iam.gserviceaccount.com;condition:4:none; +binding projects/582015116396/locations/global/workloadIdentityPools/github-heal-publisher-private role:49:projects/gunbai-secrets/roles/gunbcIamObservePool;member:65:serviceAccount:iam-observe@gunbai-secrets.iam.gserviceaccount.com;condition:4:none; +binding projects/gunbai-secrets/serviceAccounts/heal-publisher-private@gunbai-secrets.iam.gserviceaccount.com role:52:projects/gunbai-secrets/roles/gunbcIamObserveAccount;member:65:serviceAccount:iam-observe@gunbai-secrets.iam.gserviceaccount.com;condition:4:none; +binding projects/582015116396/locations/global/workloadIdentityPools/github-mtcollins1-boot role:49:projects/gunbai-secrets/roles/gunbcIamObservePool;member:65:serviceAccount:iam-observe@gunbai-secrets.iam.gserviceaccount.com;condition:4:none; +binding projects/gunbai-secrets/serviceAccounts/mtcollins1-boot@gunbai-secrets.iam.gserviceaccount.com role:52:projects/gunbai-secrets/roles/gunbcIamObserveAccount;member:65:serviceAccount:iam-observe@gunbai-secrets.iam.gserviceaccount.com;condition:4:none; +binding projects/582015116396/secrets/bmc-mtcollins1-gunbc role:51:projects/gunbai-secrets/roles/gunbcIamObserveSecret;member:65:serviceAccount:iam-observe@gunbai-secrets.iam.gserviceaccount.com;condition:4:none; +binding projects/582015116396/secrets/fleet-automation-ssh-key role:51:projects/gunbai-secrets/roles/gunbcIamObserveSecret;member:65:serviceAccount:iam-observe@gunbai-secrets.iam.gserviceaccount.com;condition:4:none; +binding projects/582015116396/locations/global/workloadIdentityPools/github-namecheap-dns role:49:projects/gunbai-secrets/roles/gunbcIamObservePool;member:65:serviceAccount:iam-observe@gunbai-secrets.iam.gserviceaccount.com;condition:4:none; +binding projects/gunbai-secrets/serviceAccounts/namecheap-dns@gunbai-secrets.iam.gserviceaccount.com role:52:projects/gunbai-secrets/roles/gunbcIamObserveAccount;member:65:serviceAccount:iam-observe@gunbai-secrets.iam.gserviceaccount.com;condition:4:none; +binding projects/582015116396/secrets/namecheap-api-key role:51:projects/gunbai-secrets/roles/gunbcIamObserveSecret;member:65:serviceAccount:iam-observe@gunbai-secrets.iam.gserviceaccount.com;condition:4:none; +binding projects/582015116396/locations/global/workloadIdentityPools/github-gcp-iam-converge role:49:projects/gunbai-secrets/roles/gunbcIamObservePool;member:65:serviceAccount:iam-observe@gunbai-secrets.iam.gserviceaccount.com;condition:4:none; +binding projects/gunbai-secrets/serviceAccounts/iam-observe@gunbai-secrets.iam.gserviceaccount.com role:52:projects/gunbai-secrets/roles/gunbcIamObserveAccount;member:65:serviceAccount:iam-observe@gunbai-secrets.iam.gserviceaccount.com;condition:4:none; +binding projects/582015116396/secrets/approval-submission-mac-key role:51:projects/gunbai-secrets/roles/gunbcIamObserveSecret;member:65:serviceAccount:iam-observe@gunbai-secrets.iam.gserviceaccount.com;condition:4:none; +binding projects/gunbai-secrets/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com role:52:projects/gunbai-secrets/roles/gunbcIamObserveAccount;member:65:serviceAccount:iam-observe@gunbai-secrets.iam.gserviceaccount.com;condition:4:none; +binding projects/582015116396/secrets/approval-submission-mac-key role:34:roles/secretmanager.secretAccessor;member:65:serviceAccount:iam-observe@gunbai-secrets.iam.gserviceaccount.com;condition-title:54:gcp-iam-converge pinned approval-submission-mac-key v1;condition-expression:87:resource.name == "projects/582015116396/secrets/approval-submission-mac-key/versions/1";condition-description:96:gunbc.auth.gcp_iam_converge: the one version the gcp iam converge job reads, to sign its request; +binding projects/gunbai-secrets/serviceAccounts/iam-observe@gunbai-secrets.iam.gserviceaccount.com role:30:roles/iam.workloadIdentityUser;member:120:principalSet://iam.googleapis.com/projects/582015116396/locations/global/workloadIdentityPools/github-gcp-iam-converge/*;condition:4:none; +binding projects/gunbai-secrets/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com role:30:roles/iam.workloadIdentityUser;member:120:principalSet://iam.googleapis.com/projects/582015116396/locations/global/workloadIdentityPools/github-gcp-iam-converge/*;condition:4:none; \ No newline at end of file diff --git a/docs/plans/receipts/gcp-iam-bootstrap-2026-09-28/receipt.json b/docs/plans/receipts/gcp-iam-bootstrap-2026-09-28/receipt.json new file mode 100644 index 00000000000..192c58d5a66 --- /dev/null +++ b/docs/plans/receipts/gcp-iam-bootstrap-2026-09-28/receipt.json @@ -0,0 +1,35 @@ +{ + "source_revision": "a23a0f99cbfe3f39da52cd40638eb44c85e76c6c", + "base_revision": "2886b9e396d5ca6026c54212f27c52ea4113fba4", + "binary_sha256": "179882133a4c03958a1bed8a8b444ea06e48c63a6c42263ff83694508825b415", + "closure_modules": 837, + "closure_manifest_sha256": "8b62fc4c64f5d76a879ae2c2fb7802e3a2aa5d53684431d7fa3276cd70d8d298", + "memory_max": "6G", + "memory_swap_max": 0, + "release_build_exit": 0, + "focused_controls": { + "passed": 3, + "failed": 0 + }, + "plan_exit": 0, + "missing_credential_exit": 1, + "missing_credential_exact_reason_verified": true, + "missing_credential_created_started_receipt": false, + "cloud_bootstrap": { + "status": "refused", + "attempts": 3, + "completed_stages": [ + "identities", + "roles" + ], + "refused_stage": "deny", + "http_status": 403, + "missing_permission": "iam.denypolicies.create", + "capability_bindings_applied": false, + "workflow_trust_applied": false, + "temporary_credentials_removed": true, + "fresh_token_retry": "same HTTP 403 at deny-policy creation" + }, + "full_landing_suite": "not run", + "oidc_approval_workflow_acceptance": "not run" +} diff --git a/docs/plans/receipts/gcp-iam-bootstrap-authority-2026-09-28/README.md b/docs/plans/receipts/gcp-iam-bootstrap-authority-2026-09-28/README.md new file mode 100644 index 00000000000..10cf532a6c9 --- /dev/null +++ b/docs/plans/receipts/gcp-iam-bootstrap-authority-2026-09-28/README.md @@ -0,0 +1,35 @@ +# Temporary bootstrap authority convergence receipt + +Source: `70009ad9d2c8f184153976576f5a1ec2211da1b3` on draft PR #12565. +The branch binary passed 12 focused controls over 843 byte-identical modules, +within 6 GiB with swap disabled. The full landing suite was not run. + +The organization-scoped run verified the operator subject and primary IAM email, +verified project ancestry, granted the time-bounded Deny Admin cell, installed +and read back the project deny policy, then removed the exact temporary grant +and verified its absence. The retirement journal and sanitized execution log +are included. Existing unrelated grants were outside the owned cell. + +The capabilities stage subsequently applied the project create-role binding, +then stopped on a successful pool getIamPolicy response containing `{}` with +neither a body etag nor an HTTP ETag. No pool policy write followed. Account +privilege probes and workflow trust were not reached. The bootstrap as a whole +is incomplete; no VM or fleet deployment acceptance is claimed. + +The earlier project-scoped attempt was rejected by GCP with HTTP 400: Deny Admin +is grantable at organization scope. Its log and initial seven-control receipt +are historical, not qualification of the final source. The new run also binds +the verified stable subject to the actual primary email `brian@gunb.ai`. + +## Remaining dependency + +The pool's initial IAM policy needs an established safe publication contract. +A successful empty read does not supply an optimistic-concurrency token. Do not +replace this with an unconditional setIamPolicy or fabricate an etag. Google's +[Policy contract](https://docs.cloud.google.com/iam/docs/reference/rest/v1/Policy) +describes etag as the concurrency protection; the current required-etag wire +shape reports this boundary as a decode refusal. A follow-up should model the +missing publication prerequisite explicitly, including provider-supported +initialization or established exclusive ownership, before enabling the write. + +The temporary credential file was removed by the launcher. No token is included. diff --git a/docs/plans/receipts/gcp-iam-bootstrap-authority-2026-09-28/controls.log b/docs/plans/receipts/gcp-iam-bootstrap-authority-2026-09-28/controls.log new file mode 100644 index 00000000000..afefd6cdcf9 --- /dev/null +++ b/docs/plans/receipts/gcp-iam-bootstrap-authority-2026-09-28/controls.log @@ -0,0 +1,11 @@ +[workspace-root] discovered /home/briansrls/scratch-wt/gcp-iam-bootstrap +✓ typecheck v2.std.compilers.target_model done in 23 seconds +✓ typecheck v2.compiler.target_serialize done in 2 seconds +✓ typecheck v2.compiler.translate done in 3 seconds +PASS bootstrap_role_drift_never_authorizes_adoption +PASS deny_exceptions_and_incomplete_readback_withhold_trust +PASS workflow_trust_requires_completed_deny_and_privilege_readback +PASS authority_deadline_is_bounded_and_retry_does_not_renew_it +PASS uncertain_publication_cannot_be_settled_from_an_absent_cell +PASS unknown_policy_and_unowned_grants_never_authorize_changes +PASS authority_retirement_preserves_existing_human_and_foreign_access diff --git a/docs/plans/receipts/gcp-iam-bootstrap-authority-2026-09-28/gcp-iam-authority-deny-bootstrap-org-20260928-1-intent.txt b/docs/plans/receipts/gcp-iam-bootstrap-authority-2026-09-28/gcp-iam-authority-deny-bootstrap-org-20260928-1-intent.txt new file mode 100644 index 00000000000..96c40eedfff --- /dev/null +++ b/docs/plans/receipts/gcp-iam-bootstrap-authority-2026-09-28/gcp-iam-authority-deny-bootstrap-org-20260928-1-intent.txt @@ -0,0 +1 @@ +target-project:gunbai-secrets;authority-resource:organizations/266638272282;role:19:roles/iam.denyAdmin;member:18:user:brian@gunb.ai;condition-title:50:gunbc-bootstrap-deny-deny-bootstrap-org-20260928-1;condition-expression:48:request.time < timestamp('2026-09-28T23:15:00Z');condition-description:95:Temporary dependency for gunbc IAM deny-policy installation; cleanup remains owed after expiry.; \ No newline at end of file diff --git a/docs/plans/receipts/gcp-iam-bootstrap-authority-2026-09-28/gcp-iam-authority-deny-bootstrap-org-20260928-1-publication-closed.txt b/docs/plans/receipts/gcp-iam-bootstrap-authority-2026-09-28/gcp-iam-authority-deny-bootstrap-org-20260928-1-publication-closed.txt new file mode 100644 index 00000000000..96c40eedfff --- /dev/null +++ b/docs/plans/receipts/gcp-iam-bootstrap-authority-2026-09-28/gcp-iam-authority-deny-bootstrap-org-20260928-1-publication-closed.txt @@ -0,0 +1 @@ +target-project:gunbai-secrets;authority-resource:organizations/266638272282;role:19:roles/iam.denyAdmin;member:18:user:brian@gunb.ai;condition-title:50:gunbc-bootstrap-deny-deny-bootstrap-org-20260928-1;condition-expression:48:request.time < timestamp('2026-09-28T23:15:00Z');condition-description:95:Temporary dependency for gunbc IAM deny-policy installation; cleanup remains owed after expiry.; \ No newline at end of file diff --git a/docs/plans/receipts/gcp-iam-bootstrap-authority-2026-09-28/gcp-iam-authority-deny-bootstrap-org-20260928-1-publication-elected.txt b/docs/plans/receipts/gcp-iam-bootstrap-authority-2026-09-28/gcp-iam-authority-deny-bootstrap-org-20260928-1-publication-elected.txt new file mode 100644 index 00000000000..96c40eedfff --- /dev/null +++ b/docs/plans/receipts/gcp-iam-bootstrap-authority-2026-09-28/gcp-iam-authority-deny-bootstrap-org-20260928-1-publication-elected.txt @@ -0,0 +1 @@ +target-project:gunbai-secrets;authority-resource:organizations/266638272282;role:19:roles/iam.denyAdmin;member:18:user:brian@gunb.ai;condition-title:50:gunbc-bootstrap-deny-deny-bootstrap-org-20260928-1;condition-expression:48:request.time < timestamp('2026-09-28T23:15:00Z');condition-description:95:Temporary dependency for gunbc IAM deny-policy installation; cleanup remains owed after expiry.; \ No newline at end of file diff --git a/docs/plans/receipts/gcp-iam-bootstrap-authority-2026-09-28/gcp-iam-authority-deny-bootstrap-org-20260928-1-retired.txt b/docs/plans/receipts/gcp-iam-bootstrap-authority-2026-09-28/gcp-iam-authority-deny-bootstrap-org-20260928-1-retired.txt new file mode 100644 index 00000000000..96c40eedfff --- /dev/null +++ b/docs/plans/receipts/gcp-iam-bootstrap-authority-2026-09-28/gcp-iam-authority-deny-bootstrap-org-20260928-1-retired.txt @@ -0,0 +1 @@ +target-project:gunbai-secrets;authority-resource:organizations/266638272282;role:19:roles/iam.denyAdmin;member:18:user:brian@gunb.ai;condition-title:50:gunbc-bootstrap-deny-deny-bootstrap-org-20260928-1;condition-expression:48:request.time < timestamp('2026-09-28T23:15:00Z');condition-description:95:Temporary dependency for gunbc IAM deny-policy installation; cleanup remains owed after expiry.; \ No newline at end of file diff --git a/docs/plans/receipts/gcp-iam-bootstrap-authority-2026-09-28/gcp-iam-bootstrap-20260928-organization-authority-1-authority-cleanup.txt b/docs/plans/receipts/gcp-iam-bootstrap-authority-2026-09-28/gcp-iam-bootstrap-20260928-organization-authority-1-authority-cleanup.txt new file mode 100644 index 00000000000..480da8f3ea6 --- /dev/null +++ b/docs/plans/receipts/gcp-iam-bootstrap-authority-2026-09-28/gcp-iam-bootstrap-20260928-organization-authority-1-authority-cleanup.txt @@ -0,0 +1 @@ +stage checks completed; see the stage authority for configuration/probe scope diff --git a/docs/plans/receipts/gcp-iam-bootstrap-authority-2026-09-28/gcp-iam-bootstrap-20260928-organization-authority-1-capabilities.txt b/docs/plans/receipts/gcp-iam-bootstrap-authority-2026-09-28/gcp-iam-bootstrap-20260928-organization-authority-1-capabilities.txt new file mode 100644 index 00000000000..c897ef1e0af --- /dev/null +++ b/docs/plans/receipts/gcp-iam-bootstrap-authority-2026-09-28/gcp-iam-bootstrap-20260928-organization-authority-1-capabilities.txt @@ -0,0 +1 @@ +refused: status 200 undecodable: HTTP 200 body did not inhabit the declared output (null at etag) \ No newline at end of file diff --git a/docs/plans/receipts/gcp-iam-bootstrap-authority-2026-09-28/gcp-iam-bootstrap-20260928-organization-authority-1-deny.txt b/docs/plans/receipts/gcp-iam-bootstrap-authority-2026-09-28/gcp-iam-bootstrap-20260928-organization-authority-1-deny.txt new file mode 100644 index 00000000000..480da8f3ea6 --- /dev/null +++ b/docs/plans/receipts/gcp-iam-bootstrap-authority-2026-09-28/gcp-iam-bootstrap-20260928-organization-authority-1-deny.txt @@ -0,0 +1 @@ +stage checks completed; see the stage authority for configuration/probe scope diff --git a/docs/plans/receipts/gcp-iam-bootstrap-authority-2026-09-28/gcp-iam-bootstrap-20260928-organization-authority-1-identities.txt b/docs/plans/receipts/gcp-iam-bootstrap-authority-2026-09-28/gcp-iam-bootstrap-20260928-organization-authority-1-identities.txt new file mode 100644 index 00000000000..480da8f3ea6 --- /dev/null +++ b/docs/plans/receipts/gcp-iam-bootstrap-authority-2026-09-28/gcp-iam-bootstrap-20260928-organization-authority-1-identities.txt @@ -0,0 +1 @@ +stage checks completed; see the stage authority for configuration/probe scope diff --git a/docs/plans/receipts/gcp-iam-bootstrap-authority-2026-09-28/gcp-iam-bootstrap-20260928-organization-authority-1-roles.txt b/docs/plans/receipts/gcp-iam-bootstrap-authority-2026-09-28/gcp-iam-bootstrap-20260928-organization-authority-1-roles.txt new file mode 100644 index 00000000000..480da8f3ea6 --- /dev/null +++ b/docs/plans/receipts/gcp-iam-bootstrap-authority-2026-09-28/gcp-iam-bootstrap-20260928-organization-authority-1-roles.txt @@ -0,0 +1 @@ +stage checks completed; see the stage authority for configuration/probe scope diff --git a/docs/plans/receipts/gcp-iam-bootstrap-authority-2026-09-28/gcp-iam-bootstrap-20260928-organization-authority-1-started.txt b/docs/plans/receipts/gcp-iam-bootstrap-authority-2026-09-28/gcp-iam-bootstrap-20260928-organization-authority-1-started.txt new file mode 100644 index 00000000000..b286fc75b3e --- /dev/null +++ b/docs/plans/receipts/gcp-iam-bootstrap-authority-2026-09-28/gcp-iam-bootstrap-20260928-organization-authority-1-started.txt @@ -0,0 +1 @@ +operator bootstrap started; not a convergence receipt diff --git a/docs/plans/receipts/gcp-iam-bootstrap-authority-2026-09-28/operator-userinfo-readback.json b/docs/plans/receipts/gcp-iam-bootstrap-authority-2026-09-28/operator-userinfo-readback.json new file mode 100644 index 00000000000..7d0a1f9a653 --- /dev/null +++ b/docs/plans/receipts/gcp-iam-bootstrap-authority-2026-09-28/operator-userinfo-readback.json @@ -0,0 +1,7 @@ +{ + "endpoint": "https://openidconnect.googleapis.com/v1/userinfo", + "observation": "read-only authenticated API research; native convergence verifies before publication", + "sub": "116084671989231734979", + "email": "brian@gunb.ai", + "email_verified": true +} diff --git a/docs/plans/receipts/gcp-iam-bootstrap-authority-2026-09-28/organization-apply.log b/docs/plans/receipts/gcp-iam-bootstrap-authority-2026-09-28/organization-apply.log new file mode 100644 index 00000000000..6b28875b530 --- /dev/null +++ b/docs/plans/receipts/gcp-iam-bootstrap-authority-2026-09-28/organization-apply.log @@ -0,0 +1,117 @@ +[workspace-root] discovered /home/briansrls/scratch-wt/gcp-iam-bootstrap +✓ typecheck v2.std.compilers.target_model done in 23 seconds +✓ typecheck v2.compiler.target_serialize done in 2 seconds +✓ typecheck v2.compiler.translate done in 3 seconds +[file] read +[file] write_create_new target/gcp-iam-bootstrap-20260928-organization-authority-1-started.txt (54 bytes) +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-gcp-iam-converge +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-gcp-iam-converge/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/iam-observe@gunbai-secrets.iam.gserviceaccount.com +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-gcp-iam-converge +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-gcp-iam-converge/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/iam-observe@gunbai-secrets.iam.gserviceaccount.com +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-gcp-iam-converge +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-gcp-iam-converge/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-gcp-iam-converge +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-gcp-iam-converge/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/heal-publisher@gunbai-secrets.iam.gserviceaccount.com +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/heal-publisher@gunbai-secrets.iam.gserviceaccount.com +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher-private +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher-private/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/heal-publisher-private@gunbai-secrets.iam.gserviceaccount.com +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher-private +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher-private/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/heal-publisher-private@gunbai-secrets.iam.gserviceaccount.com +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-mtcollins1-boot +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-mtcollins1-boot/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/mtcollins1-boot@gunbai-secrets.iam.gserviceaccount.com +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-mtcollins1-boot +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-mtcollins1-boot/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/mtcollins1-boot@gunbai-secrets.iam.gserviceaccount.com +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-namecheap-dns +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-namecheap-dns/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/namecheap-dns@gunbai-secrets.iam.gserviceaccount.com +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-namecheap-dns +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-namecheap-dns/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/namecheap-dns@gunbai-secrets.iam.gserviceaccount.com +[file] write_create_new target/gcp-iam-bootstrap-20260928-organization-authority-1-identities.txt (78 bytes) +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/roles/gunbcIamConvergeCreate +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/roles/gunbcIamConvergeProvider +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/roles/gunbcIamConvergeAccountPolicy +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/roles/gunbcIamConvergeSecretPolicy +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/roles/gunbcIamObservePool +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/roles/gunbcIamObserveAccount +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/roles/gunbcIamObserveSecret +[file] write_create_new target/gcp-iam-bootstrap-20260928-organization-authority-1-roles.txt (78 bytes) +[rest] GET https://iam.googleapis.com/v2/policies/cloudresourcemanager.googleapis.com%2Fprojects%2F582015116396/denypolicies/gunbc-iam-converge-boundary +[rest] GET https://openidconnect.googleapis.com/v1/userinfo +[rest] GET https://cloudresourcemanager.googleapis.com/v3/projects/gunbai-secrets +[file] list target +[file] read target/gcp-iam-authority-deny-bootstrap-org-20260928-1-intent.txt +[file] list target +[file] read target/gcp-iam-authority-deny-bootstrap-org-20260928-1-publication-elected.txt +[file] list target +[file] read target/gcp-iam-authority-deny-bootstrap-org-20260928-1-publication-closed.txt +[file] list target +[file] read target/gcp-iam-authority-deny-bootstrap-org-20260928-1-retired.txt +[rest] POST https://cloudresourcemanager.googleapis.com/v3/organizations/266638272282:getIamPolicy +[file] write_create_new target/gcp-iam-authority-deny-bootstrap-org-20260928-1-intent.txt (397 bytes) +◐ started Clock.Now +✓ Clock.Now done in 1ms +[file] write_create_new target/gcp-iam-authority-deny-bootstrap-org-20260928-1-publication-elected.txt (397 bytes) +[rest] POST https://cloudresourcemanager.googleapis.com/v3/organizations/266638272282:setIamPolicy +[file] write_create_new target/gcp-iam-authority-deny-bootstrap-org-20260928-1-publication-closed.txt (397 bytes) +[rest] POST https://cloudresourcemanager.googleapis.com/v3/organizations/266638272282:getIamPolicy +[file] write_create_new target/gcp-iam-authority-deny-bootstrap-org-20260928-1-publication-closed.txt (397 bytes) +[file] list target +[file] read target/gcp-iam-authority-deny-bootstrap-org-20260928-1-publication-closed.txt +[rest] GET https://iam.googleapis.com/v2/policies/cloudresourcemanager.googleapis.com%2Fprojects%2F582015116396/denypolicies/gunbc-iam-converge-boundary +◐ started Clock.Now +✓ Clock.Now done in 2ms +[rest] POST https://iam.googleapis.com/v2/policies/cloudresourcemanager.googleapis.com%2Fprojects%2F582015116396/denypolicies +[rest] GET https://iam.googleapis.com/v2/policies/cloudresourcemanager.googleapis.com%2Fprojects%2F582015116396/denypolicies/gunbc-iam-converge-boundary +[file] write_create_new target/gcp-iam-bootstrap-20260928-organization-authority-1-deny.txt (78 bytes) +[file] list target +[file] read target/gcp-iam-authority-deny-bootstrap-org-20260928-1-intent.txt +[file] list target +[file] read target/gcp-iam-authority-deny-bootstrap-org-20260928-1-publication-elected.txt +[file] list target +[file] read target/gcp-iam-authority-deny-bootstrap-org-20260928-1-publication-closed.txt +[file] list target +[file] read target/gcp-iam-authority-deny-bootstrap-org-20260928-1-retired.txt +[rest] POST https://cloudresourcemanager.googleapis.com/v3/organizations/266638272282:getIamPolicy +◐ started Clock.Now +✓ Clock.Now done in 2ms +[file] write_create_new target/gcp-iam-authority-deny-bootstrap-org-20260928-1-publication-closed.txt (397 bytes) +[file] list target +[file] read target/gcp-iam-authority-deny-bootstrap-org-20260928-1-publication-closed.txt +[rest] POST https://cloudresourcemanager.googleapis.com/v3/organizations/266638272282:setIamPolicy +[rest] POST https://cloudresourcemanager.googleapis.com/v3/organizations/266638272282:getIamPolicy +[file] write_create_new target/gcp-iam-authority-deny-bootstrap-org-20260928-1-retired.txt (397 bytes) +[file] write_create_new target/gcp-iam-bootstrap-20260928-organization-authority-1-authority-cleanup.txt (78 bytes) +[rest] POST https://cloudresourcemanager.googleapis.com/v1/projects/gunbai-secrets:getIamPolicy +[rest] POST https://cloudresourcemanager.googleapis.com/v1/projects/gunbai-secrets:setIamPolicy +[rest] POST https://cloudresourcemanager.googleapis.com/v1/projects/gunbai-secrets:getIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher:getIamPolicy +[file] write_create_new target/gcp-iam-bootstrap-20260928-organization-authority-1-capabilities.txt (97 bytes) +[file] list target +[file] read target/gcp-iam-authority-deny-bootstrap-org-20260928-1-intent.txt +[file] list target +[file] read target/gcp-iam-authority-deny-bootstrap-org-20260928-1-publication-elected.txt +[file] list target +[file] read target/gcp-iam-authority-deny-bootstrap-org-20260928-1-publication-closed.txt +[file] list target +[file] read target/gcp-iam-authority-deny-bootstrap-org-20260928-1-retired.txt +[rest] POST https://cloudresourcemanager.googleapis.com/v3/organizations/266638272282:getIamPolicy +◐ started Clock.Now +✓ Clock.Now done in 2ms +[file] write_create_new target/gcp-iam-authority-deny-bootstrap-org-20260928-1-retired.txt (397 bytes) +[file] list target +[file] read target/gcp-iam-authority-deny-bootstrap-org-20260928-1-retired.txt +status 200 undecodable: HTTP 200 body did not inhabit the declared output (null at etag) diff --git a/docs/plans/receipts/gcp-iam-bootstrap-authority-2026-09-28/organization-identity-controls.log b/docs/plans/receipts/gcp-iam-bootstrap-authority-2026-09-28/organization-identity-controls.log new file mode 100644 index 00000000000..29fdcef5d65 --- /dev/null +++ b/docs/plans/receipts/gcp-iam-bootstrap-authority-2026-09-28/organization-identity-controls.log @@ -0,0 +1,16 @@ +[workspace-root] discovered /home/briansrls/scratch-wt/gcp-iam-bootstrap +✓ typecheck v2.std.compilers.target_model done in 23 seconds +✓ typecheck v2.compiler.target_serialize done in 2 seconds +✓ typecheck v2.compiler.translate done in 3 seconds +PASS bootstrap_role_drift_never_authorizes_adoption +PASS deny_exceptions_and_incomplete_readback_withhold_trust +PASS workflow_trust_requires_completed_deny_and_privilege_readback +PASS authority_deadline_is_bounded_and_retry_does_not_renew_it +PASS uncertain_publication_cannot_be_settled_from_an_absent_cell +PASS unknown_policy_and_unowned_grants_never_authorize_changes +PASS authority_retirement_preserves_existing_human_and_foreign_access +PASS bootstrap_authority_requires_an_organization_resource +PASS definite_publication_rejection_is_distinct_from_unknown_commit +PASS deny_propagation_retry_requires_live_authority_and_a_remaining_budget +PASS cleanup_dependency_does_not_wait_for_successful_deny_installation +PASS authority_beneficiary_requires_the_verified_stable_google_subject diff --git a/docs/plans/receipts/gcp-iam-bootstrap-authority-2026-09-28/organization-receipt.json b/docs/plans/receipts/gcp-iam-bootstrap-authority-2026-09-28/organization-receipt.json new file mode 100644 index 00000000000..68ca89d3178 --- /dev/null +++ b/docs/plans/receipts/gcp-iam-bootstrap-authority-2026-09-28/organization-receipt.json @@ -0,0 +1,24 @@ +{ + "source_revision": "70009ad9d2c8f184153976576f5a1ec2211da1b3", + "binary_sha256": "179882133a4c03958a1bed8a8b444ea06e48c63a6c42263ff83694508825b415", + "closure_modules": 843, + "closure_manifest_sha256": "73b2373809d4b41d22cf8d00db62dc996afd06042a28b676409e735b6b3653ca", + "memory_max": "6G", + "memory_swap_max": 0, + "focused_controls_passed": 12, + "authority_operation": "deny-bootstrap-org-20260928-1", + "authority_expires_at": "2026-09-28T23:15:00Z", + "organization": "organizations/266638272282", + "operator_subject": "116084671989231734979", + "operator_member": "user:brian@gunb.ai", + "cloud_status": "temporary authority granted and read back; deny policy installed and read back; temporary authority removed and absence verified; capabilities stopped at missing pool policy etag", + "full_landing_suite": "not run", + "workflow_trust": "not enabled", + "credential_file_removed": true, + "pool_policy_readback": { + "http": 200, + "etag_header": null, + "policy": {} + }, + "remaining_dependency": "safe initial pool IAM policy publication with concurrency protection" +} diff --git a/docs/plans/receipts/gcp-iam-bootstrap-authority-2026-09-28/project-ancestry-readback.json b/docs/plans/receipts/gcp-iam-bootstrap-authority-2026-09-28/project-ancestry-readback.json new file mode 100644 index 00000000000..a8c927d63d3 --- /dev/null +++ b/docs/plans/receipts/gcp-iam-bootstrap-authority-2026-09-28/project-ancestry-readback.json @@ -0,0 +1,8 @@ +{ + "endpoint": "https://cloudresourcemanager.googleapis.com/v3/projects/gunbai-secrets", + "observation": "read-only authenticated API research; native convergence rereads this before publication", + "name": "projects/582015116396", + "projectId": "gunbai-secrets", + "parent": "organizations/266638272282", + "state": "ACTIVE" +} diff --git a/docs/plans/receipts/gcp-iam-bootstrap-authority-2026-09-28/project-scope-rejected.log b/docs/plans/receipts/gcp-iam-bootstrap-authority-2026-09-28/project-scope-rejected.log new file mode 100644 index 00000000000..68eee46624a --- /dev/null +++ b/docs/plans/receipts/gcp-iam-bootstrap-authority-2026-09-28/project-scope-rejected.log @@ -0,0 +1,86 @@ +[workspace-root] discovered /home/briansrls/scratch-wt/gcp-iam-bootstrap +✓ typecheck v2.std.compilers.target_model done in 23 seconds +✓ typecheck v2.compiler.target_serialize done in 2 seconds +✓ typecheck v2.compiler.translate done in 3 seconds +[file] read +[file] write_create_new target/gcp-iam-bootstrap-20260928-authority-1-started.txt (54 bytes) +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-gcp-iam-converge +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-gcp-iam-converge/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/iam-observe@gunbai-secrets.iam.gserviceaccount.com +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-gcp-iam-converge +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-gcp-iam-converge/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/iam-observe@gunbai-secrets.iam.gserviceaccount.com +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-gcp-iam-converge +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-gcp-iam-converge/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-gcp-iam-converge +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-gcp-iam-converge/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/heal-publisher@gunbai-secrets.iam.gserviceaccount.com +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/heal-publisher@gunbai-secrets.iam.gserviceaccount.com +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher-private +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher-private/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/heal-publisher-private@gunbai-secrets.iam.gserviceaccount.com +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher-private +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher-private/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/heal-publisher-private@gunbai-secrets.iam.gserviceaccount.com +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-mtcollins1-boot +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-mtcollins1-boot/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/mtcollins1-boot@gunbai-secrets.iam.gserviceaccount.com +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-mtcollins1-boot +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-mtcollins1-boot/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/mtcollins1-boot@gunbai-secrets.iam.gserviceaccount.com +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-namecheap-dns +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-namecheap-dns/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/namecheap-dns@gunbai-secrets.iam.gserviceaccount.com +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-namecheap-dns +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-namecheap-dns/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/namecheap-dns@gunbai-secrets.iam.gserviceaccount.com +[file] write_create_new target/gcp-iam-bootstrap-20260928-authority-1-identities.txt (78 bytes) +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/roles/gunbcIamConvergeCreate +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/roles/gunbcIamConvergeProvider +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/roles/gunbcIamConvergeAccountPolicy +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/roles/gunbcIamConvergeSecretPolicy +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/roles/gunbcIamObservePool +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/roles/gunbcIamObserveAccount +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/roles/gunbcIamObserveSecret +[file] write_create_new target/gcp-iam-bootstrap-20260928-authority-1-roles.txt (78 bytes) +[rest] GET https://iam.googleapis.com/v2/policies/cloudresourcemanager.googleapis.com%2Fprojects%2F582015116396/denypolicies/gunbc-iam-converge-boundary +[file] list target +[file] read target/gcp-iam-authority-deny-bootstrap-20260928-1-intent.txt +[file] list target +[file] read target/gcp-iam-authority-deny-bootstrap-20260928-1-publication-elected.txt +[file] list target +[file] read target/gcp-iam-authority-deny-bootstrap-20260928-1-publication-closed.txt +[file] list target +[file] read target/gcp-iam-authority-deny-bootstrap-20260928-1-retired.txt +[rest] POST https://cloudresourcemanager.googleapis.com/v1/projects/gunbai-secrets:getIamPolicy +[file] write_create_new target/gcp-iam-authority-deny-bootstrap-20260928-1-intent.txt (344 bytes) +◐ started Clock.Now +✓ Clock.Now done in 2ms +[file] write_create_new target/gcp-iam-authority-deny-bootstrap-20260928-1-publication-elected.txt (344 bytes) +[rest] POST https://cloudresourcemanager.googleapis.com/v1/projects/gunbai-secrets:setIamPolicy +[file] write_create_new target/gcp-iam-bootstrap-20260928-authority-1-deny.txt (230 bytes) +[file] list target +[file] read target/gcp-iam-authority-deny-bootstrap-20260928-1-intent.txt +[file] list target +[file] read target/gcp-iam-authority-deny-bootstrap-20260928-1-publication-elected.txt +[file] list target +[file] read target/gcp-iam-authority-deny-bootstrap-20260928-1-publication-closed.txt +[file] list target +[file] read target/gcp-iam-authority-deny-bootstrap-20260928-1-retired.txt +[rest] POST https://cloudresourcemanager.googleapis.com/v1/projects/gunbai-secrets:getIamPolicy +◐ started Clock.Now +✓ Clock.Now done in 2ms +authority mutation not established; recovery remains owed: status 400: { + "error": { + "code": 400, + "message": "Role roles/iam.denyAdmin is not supported for this resource.", + "status": "INVALID_ARGUMENT" + } +} +; authority cleanup outstanding: authority dependency unresolved: expired, unowned, or publication may still commit; no grant retry or renewal diff --git a/docs/plans/receipts/gcp-iam-bootstrap-authority-2026-09-28/receipt.json b/docs/plans/receipts/gcp-iam-bootstrap-authority-2026-09-28/receipt.json new file mode 100644 index 00000000000..ae232277a9b --- /dev/null +++ b/docs/plans/receipts/gcp-iam-bootstrap-authority-2026-09-28/receipt.json @@ -0,0 +1,14 @@ +{ + "source_revision": "e413906c2a71e3268ddf4b93d0327f3d1becf2d4", + "binary_sha256": "179882133a4c03958a1bed8a8b444ea06e48c63a6c42263ff83694508825b415", + "closure_modules": 842, + "closure_manifest_sha256": "6befe7f9b00c41861cdccb76e1a7c25868bde8a876ccbd083b52c31ea905a73d", + "memory_max": "6G", + "memory_swap_max": 0, + "focused_controls_passed": 7, + "authority_operation": "deny-bootstrap-20260928-1", + "authority_expires_at": "2026-09-28T23:00:00Z", + "cloud_status": "HTTP 400: Deny Admin is not grantable on a project; no grant applied", + "full_landing_suite": "not run", + "temporary_credentials_removed": true +} diff --git a/docs/plans/receipts/gcp-iam-pool-initialization-2026-09-29/README.md b/docs/plans/receipts/gcp-iam-pool-initialization-2026-09-29/README.md new file mode 100644 index 00000000000..1e751548a20 --- /dev/null +++ b/docs/plans/receipts/gcp-iam-pool-initialization-2026-09-29/README.md @@ -0,0 +1,24 @@ +# Exclusive pool initialization source qualification + +The operator confirmed on 2026-09-29 that no other administrator will update the +pool policies during this bootstrap. The change models a one-off initial policy +publication under that exclusivity assumption, then requires an observed etag for +all normal updates. This is not provider-enforced CAS during initialization. + +All 15 focused controls passed under 6 GiB with no swap, using the branch release +binary and 843 byte-identical dependency modules. Controls distinguish empty +wire policies from update authority and reject nonempty unfenced policies, +unsupported policy versions, existing audit configuration, undeclared pools, +and incomplete or revisionless initialization readback. + +The complete bootstrap module typechecked in this closure. The new cloud-write +path and interrupted-publication recovery have not been exercised against GCP. +No live pool initialization or workflow credential-minting acceptance is claimed. +The create-only publication journal stays in the same recovery workspace; an +uncertain publication cannot be retried from a fresh workspace. + +An existing administrator credential is still needed for initial bootstrap. The +normal approval workflow first authenticates as iam-observe through its dedicated +pool, then requests approval and obtains iam-converge credentials. Its initial +trust is not yet installed, so it cannot create that trust for itself. There is +no usable gcloud installation, ADC file, or configured token file in this shell. diff --git a/docs/plans/receipts/gcp-iam-pool-initialization-2026-09-29/controls.log b/docs/plans/receipts/gcp-iam-pool-initialization-2026-09-29/controls.log new file mode 100644 index 00000000000..c0cc6807ad4 --- /dev/null +++ b/docs/plans/receipts/gcp-iam-pool-initialization-2026-09-29/controls.log @@ -0,0 +1,19 @@ +[workspace-root] discovered /home/briansrls/scratch-wt/gcp-iam-bootstrap +✓ typecheck v2.std.compilers.target_model done in 20 seconds +✓ typecheck v2.compiler.target_serialize done in 2 seconds +✓ typecheck v2.compiler.translate done in 3 seconds +PASS bootstrap_role_drift_never_authorizes_adoption +PASS deny_exceptions_and_incomplete_readback_withhold_trust +PASS workflow_trust_requires_completed_deny_and_privilege_readback +PASS authority_deadline_is_bounded_and_retry_does_not_renew_it +PASS uncertain_publication_cannot_be_settled_from_an_absent_cell +PASS unknown_policy_and_unowned_grants_never_authorize_changes +PASS authority_retirement_preserves_existing_human_and_foreign_access +PASS bootstrap_authority_requires_an_organization_resource +PASS definite_publication_rejection_is_distinct_from_unknown_commit +PASS deny_propagation_retry_requires_live_authority_and_a_remaining_budget +PASS cleanup_dependency_does_not_wait_for_successful_deny_installation +PASS authority_beneficiary_requires_the_verified_stable_google_subject +PASS empty_pool_policy_does_not_authorize_unconditional_publication +PASS pool_initialization_rejects_existing_unfenced_bindings +PASS pool_initialization_readback_requires_exact_cells_and_a_revision diff --git a/docs/plans/receipts/gcp-iam-pool-initialization-2026-09-29/receipt.json b/docs/plans/receipts/gcp-iam-pool-initialization-2026-09-29/receipt.json new file mode 100644 index 00000000000..a4206b1529a --- /dev/null +++ b/docs/plans/receipts/gcp-iam-pool-initialization-2026-09-29/receipt.json @@ -0,0 +1,11 @@ +{ + "source_revision": "07c17deb9eba5a1b545d68e56fddbfacac1c94e4", + "binary_sha256": "179882133a4c03958a1bed8a8b444ea06e48c63a6c42263ff83694508825b415", + "closure_modules": 843, + "memory_max": "6G", + "memory_swap_max": 0, + "focused_controls_passed": 15, + "live_pool_initialization": "not executed", + "full_landing_suite": "not run", + "credential_dependency": "existing administrator credential; normal workflow initial federation still awaits bootstrap" +} diff --git a/docs/plans/receipts/gcp-iam-pool-live-2026-09-29/apply.log b/docs/plans/receipts/gcp-iam-pool-live-2026-09-29/apply.log new file mode 100644 index 00000000000..320d988a0d7 --- /dev/null +++ b/docs/plans/receipts/gcp-iam-pool-live-2026-09-29/apply.log @@ -0,0 +1,244 @@ +[workspace-root] discovered /home/briansrls/scratch-wt/gcp-iam-bootstrap +✓ typecheck v2.std.compilers.target_model done in 23 seconds +✓ typecheck v2.compiler.target_serialize done in 2 seconds +✓ typecheck v2.compiler.translate done in 3 seconds +[file] read +[file] write_create_new target/gcp-iam-bootstrap-20260929-pool-initialization-1-started.txt (54 bytes) +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-gcp-iam-converge +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-gcp-iam-converge/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/iam-observe@gunbai-secrets.iam.gserviceaccount.com +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-gcp-iam-converge +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-gcp-iam-converge/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/iam-observe@gunbai-secrets.iam.gserviceaccount.com +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-gcp-iam-converge +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-gcp-iam-converge/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-gcp-iam-converge +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-gcp-iam-converge/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/heal-publisher@gunbai-secrets.iam.gserviceaccount.com +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/heal-publisher@gunbai-secrets.iam.gserviceaccount.com +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher-private +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher-private/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/heal-publisher-private@gunbai-secrets.iam.gserviceaccount.com +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher-private +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher-private/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/heal-publisher-private@gunbai-secrets.iam.gserviceaccount.com +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-mtcollins1-boot +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-mtcollins1-boot/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/mtcollins1-boot@gunbai-secrets.iam.gserviceaccount.com +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-mtcollins1-boot +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-mtcollins1-boot/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/mtcollins1-boot@gunbai-secrets.iam.gserviceaccount.com +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-namecheap-dns +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-namecheap-dns/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/namecheap-dns@gunbai-secrets.iam.gserviceaccount.com +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-namecheap-dns +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-namecheap-dns/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/namecheap-dns@gunbai-secrets.iam.gserviceaccount.com +[file] write_create_new target/gcp-iam-bootstrap-20260929-pool-initialization-1-identities.txt (78 bytes) +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/roles/gunbcIamConvergeCreate +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/roles/gunbcIamConvergeProvider +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/roles/gunbcIamConvergeAccountPolicy +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/roles/gunbcIamConvergeSecretPolicy +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/roles/gunbcIamObservePool +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/roles/gunbcIamObserveAccount +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/roles/gunbcIamObserveSecret +[file] write_create_new target/gcp-iam-bootstrap-20260929-pool-initialization-1-roles.txt (78 bytes) +[rest] GET https://iam.googleapis.com/v2/policies/cloudresourcemanager.googleapis.com%2Fprojects%2F582015116396/denypolicies/gunbc-iam-converge-boundary +[file] write_create_new target/gcp-iam-bootstrap-20260929-pool-initialization-1-deny.txt (78 bytes) +[file] list target +[file] read target/gcp-iam-authority-deny-bootstrap-org-20260928-1-intent.txt +[file] list target +[file] read target/gcp-iam-authority-deny-bootstrap-org-20260928-1-publication-elected.txt +[file] list target +[file] read target/gcp-iam-authority-deny-bootstrap-org-20260928-1-publication-closed.txt +[file] list target +[file] read target/gcp-iam-authority-deny-bootstrap-org-20260928-1-retired.txt +[rest] POST https://cloudresourcemanager.googleapis.com/v3/organizations/266638272282:getIamPolicy +◐ started Clock.Now +✓ Clock.Now done in 2ms +[file] write_create_new target/gcp-iam-authority-deny-bootstrap-org-20260928-1-retired.txt (397 bytes) +[file] list target +[file] read target/gcp-iam-authority-deny-bootstrap-org-20260928-1-retired.txt +[file] write_create_new target/gcp-iam-bootstrap-20260929-pool-initialization-1-authority-cleanup.txt (78 bytes) +[rest] POST https://cloudresourcemanager.googleapis.com/v1/projects/gunbai-secrets:getIamPolicy +[file] list target +[file] read target/gcp-iam-pool-initialization-github-heal-publisher-elected.txt +[file] list target +[file] read target/gcp-iam-pool-initialization-github-heal-publisher-closed.txt +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher:getIamPolicy +[rest] GET https://openidconnect.googleapis.com/v1/userinfo +[file] write_create_new target/gcp-iam-pool-initialization-github-heal-publisher-elected.txt (438 bytes) +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher:setIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher:getIamPolicy +[file] write_create_new target/gcp-iam-pool-initialization-github-heal-publisher-closed.txt (438 bytes) +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher:getIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/heal-publisher@gunbai-secrets.iam.gserviceaccount.com:getIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/heal-publisher@gunbai-secrets.iam.gserviceaccount.com:setIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/heal-publisher@gunbai-secrets.iam.gserviceaccount.com:getIamPolicy +[rest] GET https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/agent-github-app-private-key:getIamPolicy +[rest] POST https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/agent-github-app-private-key:setIamPolicy +[rest] GET https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/agent-github-app-private-key:getIamPolicy +[file] list target +[file] read target/gcp-iam-pool-initialization-github-heal-publisher-private-elected.txt +[file] list target +[file] read target/gcp-iam-pool-initialization-github-heal-publisher-private-closed.txt +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher-private:getIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher-private:getIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher-private:setIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher-private:getIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/heal-publisher-private@gunbai-secrets.iam.gserviceaccount.com:getIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/heal-publisher-private@gunbai-secrets.iam.gserviceaccount.com:setIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/heal-publisher-private@gunbai-secrets.iam.gserviceaccount.com:getIamPolicy +[file] list target +[file] read target/gcp-iam-pool-initialization-github-mtcollins1-boot-elected.txt +[file] list target +[file] read target/gcp-iam-pool-initialization-github-mtcollins1-boot-closed.txt +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-mtcollins1-boot:getIamPolicy +[rest] GET https://openidconnect.googleapis.com/v1/userinfo +[file] write_create_new target/gcp-iam-pool-initialization-github-mtcollins1-boot-elected.txt (439 bytes) +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-mtcollins1-boot:setIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-mtcollins1-boot:getIamPolicy +[file] write_create_new target/gcp-iam-pool-initialization-github-mtcollins1-boot-closed.txt (439 bytes) +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-mtcollins1-boot:getIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/mtcollins1-boot@gunbai-secrets.iam.gserviceaccount.com:getIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/mtcollins1-boot@gunbai-secrets.iam.gserviceaccount.com:setIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/mtcollins1-boot@gunbai-secrets.iam.gserviceaccount.com:getIamPolicy +[rest] GET https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/bmc-mtcollins1-gunbc:getIamPolicy +[rest] POST https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/bmc-mtcollins1-gunbc:setIamPolicy +[rest] GET https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/bmc-mtcollins1-gunbc:getIamPolicy +[rest] GET https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/fleet-automation-ssh-key:getIamPolicy +[rest] POST https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/fleet-automation-ssh-key:setIamPolicy +[rest] GET https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/fleet-automation-ssh-key:getIamPolicy +[file] list target +[file] read target/gcp-iam-pool-initialization-github-namecheap-dns-elected.txt +[file] list target +[file] read target/gcp-iam-pool-initialization-github-namecheap-dns-closed.txt +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-namecheap-dns:getIamPolicy +[rest] GET https://openidconnect.googleapis.com/v1/userinfo +[file] write_create_new target/gcp-iam-pool-initialization-github-namecheap-dns-elected.txt (437 bytes) +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-namecheap-dns:setIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-namecheap-dns:getIamPolicy +[file] write_create_new target/gcp-iam-pool-initialization-github-namecheap-dns-closed.txt (437 bytes) +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-namecheap-dns:getIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/namecheap-dns@gunbai-secrets.iam.gserviceaccount.com:getIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/namecheap-dns@gunbai-secrets.iam.gserviceaccount.com:setIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/namecheap-dns@gunbai-secrets.iam.gserviceaccount.com:getIamPolicy +[rest] GET https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/namecheap-api-key:getIamPolicy +[rest] POST https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/namecheap-api-key:setIamPolicy +[rest] GET https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/namecheap-api-key:getIamPolicy +[file] list target +[file] read target/gcp-iam-pool-initialization-github-heal-publisher-elected.txt +[file] list target +[file] read target/gcp-iam-pool-initialization-github-heal-publisher-closed.txt +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher:getIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher:getIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/heal-publisher@gunbai-secrets.iam.gserviceaccount.com:getIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/heal-publisher@gunbai-secrets.iam.gserviceaccount.com:setIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/heal-publisher@gunbai-secrets.iam.gserviceaccount.com:getIamPolicy +[rest] GET https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/agent-github-app-private-key:getIamPolicy +[rest] POST https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/agent-github-app-private-key:setIamPolicy +[rest] GET https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/agent-github-app-private-key:getIamPolicy +[file] list target +[file] read target/gcp-iam-pool-initialization-github-heal-publisher-private-elected.txt +[file] list target +[file] read target/gcp-iam-pool-initialization-github-heal-publisher-private-closed.txt +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher-private:getIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher-private:getIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher-private:setIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher-private:getIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/heal-publisher-private@gunbai-secrets.iam.gserviceaccount.com:getIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/heal-publisher-private@gunbai-secrets.iam.gserviceaccount.com:setIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/heal-publisher-private@gunbai-secrets.iam.gserviceaccount.com:getIamPolicy +[file] list target +[file] read target/gcp-iam-pool-initialization-github-mtcollins1-boot-elected.txt +[file] list target +[file] read target/gcp-iam-pool-initialization-github-mtcollins1-boot-closed.txt +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-mtcollins1-boot:getIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-mtcollins1-boot:getIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/mtcollins1-boot@gunbai-secrets.iam.gserviceaccount.com:getIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/mtcollins1-boot@gunbai-secrets.iam.gserviceaccount.com:setIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/mtcollins1-boot@gunbai-secrets.iam.gserviceaccount.com:getIamPolicy +[rest] GET https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/bmc-mtcollins1-gunbc:getIamPolicy +[rest] POST https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/bmc-mtcollins1-gunbc:setIamPolicy +[rest] GET https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/bmc-mtcollins1-gunbc:getIamPolicy +[rest] GET https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/fleet-automation-ssh-key:getIamPolicy +[rest] POST https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/fleet-automation-ssh-key:setIamPolicy +[rest] GET https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/fleet-automation-ssh-key:getIamPolicy +[file] list target +[file] read target/gcp-iam-pool-initialization-github-namecheap-dns-elected.txt +[file] list target +[file] read target/gcp-iam-pool-initialization-github-namecheap-dns-closed.txt +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-namecheap-dns:getIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-namecheap-dns:getIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/namecheap-dns@gunbai-secrets.iam.gserviceaccount.com:getIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/namecheap-dns@gunbai-secrets.iam.gserviceaccount.com:setIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/namecheap-dns@gunbai-secrets.iam.gserviceaccount.com:getIamPolicy +[rest] GET https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/namecheap-api-key:getIamPolicy +[rest] POST https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/namecheap-api-key:setIamPolicy +[rest] GET https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/namecheap-api-key:getIamPolicy +[file] list target +[file] read target/gcp-iam-pool-initialization-github-gcp-iam-converge-elected.txt +[file] list target +[file] read target/gcp-iam-pool-initialization-github-gcp-iam-converge-closed.txt +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-gcp-iam-converge:getIamPolicy +[rest] GET https://openidconnect.googleapis.com/v1/userinfo +[file] write_create_new target/gcp-iam-pool-initialization-github-gcp-iam-converge-elected.txt (282 bytes) +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-gcp-iam-converge:setIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-gcp-iam-converge:getIamPolicy +[file] write_create_new target/gcp-iam-pool-initialization-github-gcp-iam-converge-closed.txt (282 bytes) +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-gcp-iam-converge:getIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/iam-observe@gunbai-secrets.iam.gserviceaccount.com:getIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/iam-observe@gunbai-secrets.iam.gserviceaccount.com:setIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/iam-observe@gunbai-secrets.iam.gserviceaccount.com:getIamPolicy +[rest] GET https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/approval-submission-mac-key:getIamPolicy +[rest] POST https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/approval-submission-mac-key:setIamPolicy +[rest] GET https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/approval-submission-mac-key:getIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:getIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:setIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:getIamPolicy +[rest] GET https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/approval-submission-mac-key:getIamPolicy +[rest] POST https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/approval-submission-mac-key:setIamPolicy +[rest] GET https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/approval-submission-mac-key:getIamPolicy +[file] write_create_new target/gcp-iam-bootstrap-20260929-pool-initialization-1-capabilities.txt (78 bytes) +[rest] POST https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:generateAccessToken +[file] write_create_new target/gcp-iam-bootstrap-20260929-pool-initialization-1-privilege-readback.txt (1028 bytes) +[file] list target +[file] read target/gcp-iam-authority-deny-bootstrap-org-20260928-1-intent.txt +[file] list target +[file] read target/gcp-iam-authority-deny-bootstrap-org-20260928-1-publication-elected.txt +[file] list target +[file] read target/gcp-iam-authority-deny-bootstrap-org-20260928-1-publication-closed.txt +[file] list target +[file] read target/gcp-iam-authority-deny-bootstrap-org-20260928-1-retired.txt +[rest] POST https://cloudresourcemanager.googleapis.com/v3/organizations/266638272282:getIamPolicy +◐ started Clock.Now +✓ Clock.Now done in 2ms +[file] write_create_new target/gcp-iam-authority-deny-bootstrap-org-20260928-1-retired.txt (397 bytes) +[file] list target +[file] read target/gcp-iam-authority-deny-bootstrap-org-20260928-1-retired.txt +apply-account readback credential unavailable; workflow trust withheld: status 403: { + "error": { + "code": 403, + "message": "Permission 'iam.serviceAccounts.getAccessToken' denied on resource (or it may not exist). Remediate access with this Troubleshooter URL or share it with your administrator - https://console.cloud.google.com/iam-admin/troubleshooter/summary;errorId=CiQwMWEwZWE5My04Mzg0LTcwNzItYmI4OC04MjJiYWNlNzVkMGISAA%3D%3D .", + "status": "PERMISSION_DENIED", + "details": [ + { + "@type": "type.googleapis.com/google.rpc.ErrorInfo", + "reason": "IAM_PERMISSION_DENIED", + "domain": "iam.googleapis.com", + "metadata": { + "error_info_id": "CiQwMWEwZWE5My04Mzg0LTcwNzItYmI4OC04MjJiYWNlNzVkMGISAA==", + "permission": "iam.serviceAccounts.getAccessToken", + "troubleshooter_url": "https://console.cloud.google.com/iam-admin/troubleshooter/summary;errorId=CiQwMWEwZWE5My04Mzg0LTcwNzItYmI4OC04MjJiYWNlNzVkMGISAA%3D%3D" + } + } + ] + } +} + diff --git a/docs/plans/receipts/gcp-iam-pool-live-2026-09-29/gcp-iam-bootstrap-20260929-pool-initialization-1-authority-cleanup.txt b/docs/plans/receipts/gcp-iam-pool-live-2026-09-29/gcp-iam-bootstrap-20260929-pool-initialization-1-authority-cleanup.txt new file mode 100644 index 00000000000..480da8f3ea6 --- /dev/null +++ b/docs/plans/receipts/gcp-iam-pool-live-2026-09-29/gcp-iam-bootstrap-20260929-pool-initialization-1-authority-cleanup.txt @@ -0,0 +1 @@ +stage checks completed; see the stage authority for configuration/probe scope diff --git a/docs/plans/receipts/gcp-iam-pool-live-2026-09-29/gcp-iam-bootstrap-20260929-pool-initialization-1-capabilities.txt b/docs/plans/receipts/gcp-iam-pool-live-2026-09-29/gcp-iam-bootstrap-20260929-pool-initialization-1-capabilities.txt new file mode 100644 index 00000000000..480da8f3ea6 --- /dev/null +++ b/docs/plans/receipts/gcp-iam-pool-live-2026-09-29/gcp-iam-bootstrap-20260929-pool-initialization-1-capabilities.txt @@ -0,0 +1 @@ +stage checks completed; see the stage authority for configuration/probe scope diff --git a/docs/plans/receipts/gcp-iam-pool-live-2026-09-29/gcp-iam-bootstrap-20260929-pool-initialization-1-deny.txt b/docs/plans/receipts/gcp-iam-pool-live-2026-09-29/gcp-iam-bootstrap-20260929-pool-initialization-1-deny.txt new file mode 100644 index 00000000000..480da8f3ea6 --- /dev/null +++ b/docs/plans/receipts/gcp-iam-pool-live-2026-09-29/gcp-iam-bootstrap-20260929-pool-initialization-1-deny.txt @@ -0,0 +1 @@ +stage checks completed; see the stage authority for configuration/probe scope diff --git a/docs/plans/receipts/gcp-iam-pool-live-2026-09-29/gcp-iam-bootstrap-20260929-pool-initialization-1-identities.txt b/docs/plans/receipts/gcp-iam-pool-live-2026-09-29/gcp-iam-bootstrap-20260929-pool-initialization-1-identities.txt new file mode 100644 index 00000000000..480da8f3ea6 --- /dev/null +++ b/docs/plans/receipts/gcp-iam-pool-live-2026-09-29/gcp-iam-bootstrap-20260929-pool-initialization-1-identities.txt @@ -0,0 +1 @@ +stage checks completed; see the stage authority for configuration/probe scope diff --git a/docs/plans/receipts/gcp-iam-pool-live-2026-09-29/gcp-iam-bootstrap-20260929-pool-initialization-1-privilege-readback.txt b/docs/plans/receipts/gcp-iam-pool-live-2026-09-29/gcp-iam-bootstrap-20260929-pool-initialization-1-privilege-readback.txt new file mode 100644 index 00000000000..7b6068988cd --- /dev/null +++ b/docs/plans/receipts/gcp-iam-pool-live-2026-09-29/gcp-iam-bootstrap-20260929-pool-initialization-1-privilege-readback.txt @@ -0,0 +1,19 @@ +refused: apply-account readback credential unavailable; workflow trust withheld: status 403: { + "error": { + "code": 403, + "message": "Permission 'iam.serviceAccounts.getAccessToken' denied on resource (or it may not exist). Remediate access with this Troubleshooter URL or share it with your administrator - https://console.cloud.google.com/iam-admin/troubleshooter/summary;errorId=CiQwMWEwZWE5My04Mzg0LTcwNzItYmI4OC04MjJiYWNlNzVkMGISAA%3D%3D .", + "status": "PERMISSION_DENIED", + "details": [ + { + "@type": "type.googleapis.com/google.rpc.ErrorInfo", + "reason": "IAM_PERMISSION_DENIED", + "domain": "iam.googleapis.com", + "metadata": { + "error_info_id": "CiQwMWEwZWE5My04Mzg0LTcwNzItYmI4OC04MjJiYWNlNzVkMGISAA==", + "permission": "iam.serviceAccounts.getAccessToken", + "troubleshooter_url": "https://console.cloud.google.com/iam-admin/troubleshooter/summary;errorId=CiQwMWEwZWE5My04Mzg0LTcwNzItYmI4OC04MjJiYWNlNzVkMGISAA%3D%3D" + } + } + ] + } +} diff --git a/docs/plans/receipts/gcp-iam-pool-live-2026-09-29/gcp-iam-bootstrap-20260929-pool-initialization-1-roles.txt b/docs/plans/receipts/gcp-iam-pool-live-2026-09-29/gcp-iam-bootstrap-20260929-pool-initialization-1-roles.txt new file mode 100644 index 00000000000..480da8f3ea6 --- /dev/null +++ b/docs/plans/receipts/gcp-iam-pool-live-2026-09-29/gcp-iam-bootstrap-20260929-pool-initialization-1-roles.txt @@ -0,0 +1 @@ +stage checks completed; see the stage authority for configuration/probe scope diff --git a/docs/plans/receipts/gcp-iam-pool-live-2026-09-29/gcp-iam-bootstrap-20260929-pool-initialization-1-started.txt b/docs/plans/receipts/gcp-iam-pool-live-2026-09-29/gcp-iam-bootstrap-20260929-pool-initialization-1-started.txt new file mode 100644 index 00000000000..b286fc75b3e --- /dev/null +++ b/docs/plans/receipts/gcp-iam-pool-live-2026-09-29/gcp-iam-bootstrap-20260929-pool-initialization-1-started.txt @@ -0,0 +1 @@ +operator bootstrap started; not a convergence receipt diff --git a/docs/plans/receipts/gcp-iam-pool-live-2026-09-29/gcp-iam-pool-initialization-github-gcp-iam-converge-closed.txt b/docs/plans/receipts/gcp-iam-pool-live-2026-09-29/gcp-iam-pool-initialization-github-gcp-iam-converge-closed.txt new file mode 100644 index 00000000000..d87ad974c40 --- /dev/null +++ b/docs/plans/receipts/gcp-iam-pool-live-2026-09-29/gcp-iam-pool-initialization-github-gcp-iam-converge-closed.txt @@ -0,0 +1,3 @@ +exclusive-pool-policy-initialization-20260929 +projects/582015116396/locations/global/workloadIdentityPools/github-gcp-iam-converge +role:49:projects/gunbai-secrets/roles/gunbcIamObservePool;member:65:serviceAccount:iam-observe@gunbai-secrets.iam.gserviceaccount.com;condition:4:none; \ No newline at end of file diff --git a/docs/plans/receipts/gcp-iam-pool-live-2026-09-29/gcp-iam-pool-initialization-github-gcp-iam-converge-elected.txt b/docs/plans/receipts/gcp-iam-pool-live-2026-09-29/gcp-iam-pool-initialization-github-gcp-iam-converge-elected.txt new file mode 100644 index 00000000000..d87ad974c40 --- /dev/null +++ b/docs/plans/receipts/gcp-iam-pool-live-2026-09-29/gcp-iam-pool-initialization-github-gcp-iam-converge-elected.txt @@ -0,0 +1,3 @@ +exclusive-pool-policy-initialization-20260929 +projects/582015116396/locations/global/workloadIdentityPools/github-gcp-iam-converge +role:49:projects/gunbai-secrets/roles/gunbcIamObservePool;member:65:serviceAccount:iam-observe@gunbai-secrets.iam.gserviceaccount.com;condition:4:none; \ No newline at end of file diff --git a/docs/plans/receipts/gcp-iam-pool-live-2026-09-29/gcp-iam-pool-initialization-github-heal-publisher-closed.txt b/docs/plans/receipts/gcp-iam-pool-live-2026-09-29/gcp-iam-pool-initialization-github-heal-publisher-closed.txt new file mode 100644 index 00000000000..b195bdceb73 --- /dev/null +++ b/docs/plans/receipts/gcp-iam-pool-live-2026-09-29/gcp-iam-pool-initialization-github-heal-publisher-closed.txt @@ -0,0 +1,4 @@ +exclusive-pool-policy-initialization-20260929 +projects/582015116396/locations/global/workloadIdentityPools/github-heal-publisher +role:54:projects/gunbai-secrets/roles/gunbcIamConvergeProvider;member:66:serviceAccount:iam-converge@gunbai-secrets.iam.gserviceaccount.com;condition:4:none; +role:49:projects/gunbai-secrets/roles/gunbcIamObservePool;member:65:serviceAccount:iam-observe@gunbai-secrets.iam.gserviceaccount.com;condition:4:none; \ No newline at end of file diff --git a/docs/plans/receipts/gcp-iam-pool-live-2026-09-29/gcp-iam-pool-initialization-github-heal-publisher-elected.txt b/docs/plans/receipts/gcp-iam-pool-live-2026-09-29/gcp-iam-pool-initialization-github-heal-publisher-elected.txt new file mode 100644 index 00000000000..b195bdceb73 --- /dev/null +++ b/docs/plans/receipts/gcp-iam-pool-live-2026-09-29/gcp-iam-pool-initialization-github-heal-publisher-elected.txt @@ -0,0 +1,4 @@ +exclusive-pool-policy-initialization-20260929 +projects/582015116396/locations/global/workloadIdentityPools/github-heal-publisher +role:54:projects/gunbai-secrets/roles/gunbcIamConvergeProvider;member:66:serviceAccount:iam-converge@gunbai-secrets.iam.gserviceaccount.com;condition:4:none; +role:49:projects/gunbai-secrets/roles/gunbcIamObservePool;member:65:serviceAccount:iam-observe@gunbai-secrets.iam.gserviceaccount.com;condition:4:none; \ No newline at end of file diff --git a/docs/plans/receipts/gcp-iam-pool-live-2026-09-29/gcp-iam-pool-initialization-github-mtcollins1-boot-closed.txt b/docs/plans/receipts/gcp-iam-pool-live-2026-09-29/gcp-iam-pool-initialization-github-mtcollins1-boot-closed.txt new file mode 100644 index 00000000000..f4d0845f19d --- /dev/null +++ b/docs/plans/receipts/gcp-iam-pool-live-2026-09-29/gcp-iam-pool-initialization-github-mtcollins1-boot-closed.txt @@ -0,0 +1,4 @@ +exclusive-pool-policy-initialization-20260929 +projects/582015116396/locations/global/workloadIdentityPools/github-mtcollins1-boot +role:54:projects/gunbai-secrets/roles/gunbcIamConvergeProvider;member:66:serviceAccount:iam-converge@gunbai-secrets.iam.gserviceaccount.com;condition:4:none; +role:49:projects/gunbai-secrets/roles/gunbcIamObservePool;member:65:serviceAccount:iam-observe@gunbai-secrets.iam.gserviceaccount.com;condition:4:none; \ No newline at end of file diff --git a/docs/plans/receipts/gcp-iam-pool-live-2026-09-29/gcp-iam-pool-initialization-github-mtcollins1-boot-elected.txt b/docs/plans/receipts/gcp-iam-pool-live-2026-09-29/gcp-iam-pool-initialization-github-mtcollins1-boot-elected.txt new file mode 100644 index 00000000000..f4d0845f19d --- /dev/null +++ b/docs/plans/receipts/gcp-iam-pool-live-2026-09-29/gcp-iam-pool-initialization-github-mtcollins1-boot-elected.txt @@ -0,0 +1,4 @@ +exclusive-pool-policy-initialization-20260929 +projects/582015116396/locations/global/workloadIdentityPools/github-mtcollins1-boot +role:54:projects/gunbai-secrets/roles/gunbcIamConvergeProvider;member:66:serviceAccount:iam-converge@gunbai-secrets.iam.gserviceaccount.com;condition:4:none; +role:49:projects/gunbai-secrets/roles/gunbcIamObservePool;member:65:serviceAccount:iam-observe@gunbai-secrets.iam.gserviceaccount.com;condition:4:none; \ No newline at end of file diff --git a/docs/plans/receipts/gcp-iam-pool-live-2026-09-29/gcp-iam-pool-initialization-github-namecheap-dns-closed.txt b/docs/plans/receipts/gcp-iam-pool-live-2026-09-29/gcp-iam-pool-initialization-github-namecheap-dns-closed.txt new file mode 100644 index 00000000000..ece3b479b8e --- /dev/null +++ b/docs/plans/receipts/gcp-iam-pool-live-2026-09-29/gcp-iam-pool-initialization-github-namecheap-dns-closed.txt @@ -0,0 +1,4 @@ +exclusive-pool-policy-initialization-20260929 +projects/582015116396/locations/global/workloadIdentityPools/github-namecheap-dns +role:54:projects/gunbai-secrets/roles/gunbcIamConvergeProvider;member:66:serviceAccount:iam-converge@gunbai-secrets.iam.gserviceaccount.com;condition:4:none; +role:49:projects/gunbai-secrets/roles/gunbcIamObservePool;member:65:serviceAccount:iam-observe@gunbai-secrets.iam.gserviceaccount.com;condition:4:none; \ No newline at end of file diff --git a/docs/plans/receipts/gcp-iam-pool-live-2026-09-29/gcp-iam-pool-initialization-github-namecheap-dns-elected.txt b/docs/plans/receipts/gcp-iam-pool-live-2026-09-29/gcp-iam-pool-initialization-github-namecheap-dns-elected.txt new file mode 100644 index 00000000000..ece3b479b8e --- /dev/null +++ b/docs/plans/receipts/gcp-iam-pool-live-2026-09-29/gcp-iam-pool-initialization-github-namecheap-dns-elected.txt @@ -0,0 +1,4 @@ +exclusive-pool-policy-initialization-20260929 +projects/582015116396/locations/global/workloadIdentityPools/github-namecheap-dns +role:54:projects/gunbai-secrets/roles/gunbcIamConvergeProvider;member:66:serviceAccount:iam-converge@gunbai-secrets.iam.gserviceaccount.com;condition:4:none; +role:49:projects/gunbai-secrets/roles/gunbcIamObservePool;member:65:serviceAccount:iam-observe@gunbai-secrets.iam.gserviceaccount.com;condition:4:none; \ No newline at end of file diff --git a/docs/plans/receipts/gcp-iam-pool-live-2026-09-29/receipt.json b/docs/plans/receipts/gcp-iam-pool-live-2026-09-29/receipt.json new file mode 100644 index 00000000000..b2159f7436f --- /dev/null +++ b/docs/plans/receipts/gcp-iam-pool-live-2026-09-29/receipt.json @@ -0,0 +1,11 @@ +{ + "source_revision": "be068166e93def4ab273b3d4cd11c5476a182e57", + "pool_initialization": "completed with readbacks and revision tokens", + "capabilities": "completed", + "privilege_readback": "refused: operator lacks iam.serviceAccounts.getAccessToken on iam-converge", + "workflow_trust": "not granted", + "temporary_deny_authority": "absence reverified", + "temporary_token_file": "removed", + "memory_max": "6G", + "memory_swap_max": 0 +} diff --git a/docs/plans/receipts/gcp-iam-probe-authority-2026-09-29/apply.log b/docs/plans/receipts/gcp-iam-probe-authority-2026-09-29/apply.log new file mode 100644 index 00000000000..dcf9fd23b54 --- /dev/null +++ b/docs/plans/receipts/gcp-iam-probe-authority-2026-09-29/apply.log @@ -0,0 +1,404 @@ +[workspace-root] discovered /home/briansrls/scratch-wt/gcp-iam-bootstrap +✓ typecheck v2.std.compilers.target_model done in 20 seconds +✓ typecheck v2.compiler.target_serialize done in 2 seconds +✓ typecheck v2.compiler.translate done in 3 seconds +[file] read +[file] write_create_new target/gcp-iam-bootstrap-20260929-probe-authority-1-started.txt (54 bytes) +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-gcp-iam-converge +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-gcp-iam-converge/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/iam-observe@gunbai-secrets.iam.gserviceaccount.com +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-gcp-iam-converge +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-gcp-iam-converge/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/iam-observe@gunbai-secrets.iam.gserviceaccount.com +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-gcp-iam-converge +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-gcp-iam-converge/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-gcp-iam-converge +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-gcp-iam-converge/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/heal-publisher@gunbai-secrets.iam.gserviceaccount.com +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/heal-publisher@gunbai-secrets.iam.gserviceaccount.com +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher-private +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher-private/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/heal-publisher-private@gunbai-secrets.iam.gserviceaccount.com +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher-private +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher-private/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/heal-publisher-private@gunbai-secrets.iam.gserviceaccount.com +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-mtcollins1-boot +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-mtcollins1-boot/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/mtcollins1-boot@gunbai-secrets.iam.gserviceaccount.com +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-mtcollins1-boot +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-mtcollins1-boot/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/mtcollins1-boot@gunbai-secrets.iam.gserviceaccount.com +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-namecheap-dns +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-namecheap-dns/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/namecheap-dns@gunbai-secrets.iam.gserviceaccount.com +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-namecheap-dns +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-namecheap-dns/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/namecheap-dns@gunbai-secrets.iam.gserviceaccount.com +[file] write_create_new target/gcp-iam-bootstrap-20260929-probe-authority-1-identities.txt (78 bytes) +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/roles/gunbcIamConvergeCreate +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/roles/gunbcIamConvergeProvider +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/roles/gunbcIamConvergeAccountPolicy +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/roles/gunbcIamConvergeSecretPolicy +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/roles/gunbcIamObservePool +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/roles/gunbcIamObserveAccount +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/roles/gunbcIamObserveSecret +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/roles/gunbcIamBootstrapProbeToken +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/roles +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/roles/gunbcIamBootstrapProbeToken +[file] write_create_new target/gcp-iam-bootstrap-20260929-probe-authority-1-roles.txt (78 bytes) +[rest] GET https://iam.googleapis.com/v2/policies/cloudresourcemanager.googleapis.com%2Fprojects%2F582015116396/denypolicies/gunbc-iam-converge-boundary +[file] write_create_new target/gcp-iam-bootstrap-20260929-probe-authority-1-deny.txt (78 bytes) +[file] list target +[file] read target/gcp-iam-authority-deny-bootstrap-org-20260928-1-intent.txt +[file] list target +[file] read target/gcp-iam-authority-deny-bootstrap-org-20260928-1-publication-elected.txt +[file] list target +[file] read target/gcp-iam-authority-deny-bootstrap-org-20260928-1-publication-closed.txt +[file] list target +[file] read target/gcp-iam-authority-deny-bootstrap-org-20260928-1-retired.txt +[rest] POST https://cloudresourcemanager.googleapis.com/v3/organizations/266638272282:getIamPolicy +◐ started Clock.Now +✓ Clock.Now done in 2ms +[file] write_create_new target/gcp-iam-authority-deny-bootstrap-org-20260928-1-retired.txt (397 bytes) +[file] list target +[file] read target/gcp-iam-authority-deny-bootstrap-org-20260928-1-retired.txt +[file] write_create_new target/gcp-iam-bootstrap-20260929-probe-authority-1-authority-cleanup.txt (78 bytes) +[rest] POST https://cloudresourcemanager.googleapis.com/v1/projects/gunbai-secrets:getIamPolicy +[file] list target +[file] read target/gcp-iam-pool-initialization-github-heal-publisher-elected.txt +[file] list target +[file] read target/gcp-iam-pool-initialization-github-heal-publisher-closed.txt +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher:getIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher:getIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/heal-publisher@gunbai-secrets.iam.gserviceaccount.com:getIamPolicy +[rest] GET https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/agent-github-app-private-key:getIamPolicy +[file] list target +[file] read target/gcp-iam-pool-initialization-github-heal-publisher-private-elected.txt +[file] list target +[file] read target/gcp-iam-pool-initialization-github-heal-publisher-private-closed.txt +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher-private:getIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher-private:getIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/heal-publisher-private@gunbai-secrets.iam.gserviceaccount.com:getIamPolicy +[file] list target +[file] read target/gcp-iam-pool-initialization-github-mtcollins1-boot-elected.txt +[file] list target +[file] read target/gcp-iam-pool-initialization-github-mtcollins1-boot-closed.txt +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-mtcollins1-boot:getIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-mtcollins1-boot:getIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/mtcollins1-boot@gunbai-secrets.iam.gserviceaccount.com:getIamPolicy +[rest] GET https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/bmc-mtcollins1-gunbc:getIamPolicy +[rest] GET https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/fleet-automation-ssh-key:getIamPolicy +[file] list target +[file] read target/gcp-iam-pool-initialization-github-namecheap-dns-elected.txt +[file] list target +[file] read target/gcp-iam-pool-initialization-github-namecheap-dns-closed.txt +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-namecheap-dns:getIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-namecheap-dns:getIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/namecheap-dns@gunbai-secrets.iam.gserviceaccount.com:getIamPolicy +[rest] GET https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/namecheap-api-key:getIamPolicy +[file] list target +[file] read target/gcp-iam-pool-initialization-github-heal-publisher-elected.txt +[file] list target +[file] read target/gcp-iam-pool-initialization-github-heal-publisher-closed.txt +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher:getIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher:getIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/heal-publisher@gunbai-secrets.iam.gserviceaccount.com:getIamPolicy +[rest] GET https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/agent-github-app-private-key:getIamPolicy +[file] list target +[file] read target/gcp-iam-pool-initialization-github-heal-publisher-private-elected.txt +[file] list target +[file] read target/gcp-iam-pool-initialization-github-heal-publisher-private-closed.txt +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher-private:getIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher-private:getIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/heal-publisher-private@gunbai-secrets.iam.gserviceaccount.com:getIamPolicy +[file] list target +[file] read target/gcp-iam-pool-initialization-github-mtcollins1-boot-elected.txt +[file] list target +[file] read target/gcp-iam-pool-initialization-github-mtcollins1-boot-closed.txt +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-mtcollins1-boot:getIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-mtcollins1-boot:getIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/mtcollins1-boot@gunbai-secrets.iam.gserviceaccount.com:getIamPolicy +[rest] GET https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/bmc-mtcollins1-gunbc:getIamPolicy +[rest] GET https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/fleet-automation-ssh-key:getIamPolicy +[file] list target +[file] read target/gcp-iam-pool-initialization-github-namecheap-dns-elected.txt +[file] list target +[file] read target/gcp-iam-pool-initialization-github-namecheap-dns-closed.txt +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-namecheap-dns:getIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-namecheap-dns:getIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/namecheap-dns@gunbai-secrets.iam.gserviceaccount.com:getIamPolicy +[rest] GET https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/namecheap-api-key:getIamPolicy +[file] list target +[file] read target/gcp-iam-pool-initialization-github-gcp-iam-converge-elected.txt +[file] list target +[file] read target/gcp-iam-pool-initialization-github-gcp-iam-converge-closed.txt +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-gcp-iam-converge:getIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-gcp-iam-converge:getIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/iam-observe@gunbai-secrets.iam.gserviceaccount.com:getIamPolicy +[rest] GET https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/approval-submission-mac-key:getIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:getIamPolicy +[rest] GET https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/approval-submission-mac-key:getIamPolicy +[file] write_create_new target/gcp-iam-bootstrap-20260929-probe-authority-1-capabilities.txt (78 bytes) +[rest] GET https://openidconnect.googleapis.com/v1/userinfo +[file] list target +[file] read target/gcp-iam-probe-probe-20260929-1-iam-converge@gunbai-secrets.iam.gserviceaccount.com-intent.txt +[file] list target +[file] read target/gcp-iam-probe-probe-20260929-1-iam-converge@gunbai-secrets.iam.gserviceaccount.com-publication-elected.txt +[file] list target +[file] read target/gcp-iam-probe-probe-20260929-1-iam-converge@gunbai-secrets.iam.gserviceaccount.com-publication-closed.txt +[file] list target +[file] read target/gcp-iam-probe-probe-20260929-1-iam-converge@gunbai-secrets.iam.gserviceaccount.com-retired.txt +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:getIamPolicy +[file] write_create_new target/gcp-iam-probe-probe-20260929-1-iam-converge@gunbai-secrets.iam.gserviceaccount.com-intent.txt (427 bytes) +◐ started Clock.Now +✓ Clock.Now done in 2ms +[file] write_create_new target/gcp-iam-probe-probe-20260929-1-iam-converge@gunbai-secrets.iam.gserviceaccount.com-publication-elected.txt (427 bytes) +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:setIamPolicy +[file] write_create_new target/gcp-iam-probe-probe-20260929-1-iam-converge@gunbai-secrets.iam.gserviceaccount.com-publication-closed.txt (427 bytes) +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:getIamPolicy +[file] write_create_new target/gcp-iam-probe-probe-20260929-1-iam-converge@gunbai-secrets.iam.gserviceaccount.com-publication-closed.txt (427 bytes) +[file] list target +[file] read target/gcp-iam-probe-probe-20260929-1-iam-converge@gunbai-secrets.iam.gserviceaccount.com-publication-closed.txt +[file] list target +[file] read target/gcp-iam-probe-probe-20260929-1-iam-observe@gunbai-secrets.iam.gserviceaccount.com-intent.txt +[file] list target +[file] read target/gcp-iam-probe-probe-20260929-1-iam-observe@gunbai-secrets.iam.gserviceaccount.com-publication-elected.txt +[file] list target +[file] read target/gcp-iam-probe-probe-20260929-1-iam-observe@gunbai-secrets.iam.gserviceaccount.com-publication-closed.txt +[file] list target +[file] read target/gcp-iam-probe-probe-20260929-1-iam-observe@gunbai-secrets.iam.gserviceaccount.com-retired.txt +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/iam-observe@gunbai-secrets.iam.gserviceaccount.com:getIamPolicy +[file] write_create_new target/gcp-iam-probe-probe-20260929-1-iam-observe@gunbai-secrets.iam.gserviceaccount.com-intent.txt (426 bytes) +◐ started Clock.Now +✓ Clock.Now done in 2ms +[file] write_create_new target/gcp-iam-probe-probe-20260929-1-iam-observe@gunbai-secrets.iam.gserviceaccount.com-publication-elected.txt (426 bytes) +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/iam-observe@gunbai-secrets.iam.gserviceaccount.com:setIamPolicy +[file] write_create_new target/gcp-iam-probe-probe-20260929-1-iam-observe@gunbai-secrets.iam.gserviceaccount.com-publication-closed.txt (426 bytes) +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/iam-observe@gunbai-secrets.iam.gserviceaccount.com:getIamPolicy +[file] write_create_new target/gcp-iam-probe-probe-20260929-1-iam-observe@gunbai-secrets.iam.gserviceaccount.com-publication-closed.txt (426 bytes) +[file] list target +[file] read target/gcp-iam-probe-probe-20260929-1-iam-observe@gunbai-secrets.iam.gserviceaccount.com-publication-closed.txt +[file] write_create_new target/gcp-iam-bootstrap-20260929-probe-authority-1-probe-authority.txt (78 bytes) +[rest] POST https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:generateAccessToken +◐ started Clock.Now +✓ Clock.Now done in 2ms +◐ started sleep.Delay.Seconds +✓ sleep.Delay.Seconds done in 2 seconds +[rest] POST https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:generateAccessToken +◐ started Clock.Now +✓ Clock.Now done in 1ms +◐ started sleep.Delay.Seconds +✓ sleep.Delay.Seconds done in 2 seconds +[rest] POST https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:generateAccessToken +◐ started Clock.Now +✓ Clock.Now done in 2ms +◐ started sleep.Delay.Seconds +✓ sleep.Delay.Seconds done in 2 seconds +[rest] POST https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:generateAccessToken +◐ started Clock.Now +✓ Clock.Now done in 1ms +◐ started sleep.Delay.Seconds +✓ sleep.Delay.Seconds done in 2 seconds +[rest] POST https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:generateAccessToken +◐ started Clock.Now +✓ Clock.Now done in 2ms +◐ started sleep.Delay.Seconds +✓ sleep.Delay.Seconds done in 2 seconds +[rest] POST https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:generateAccessToken +◐ started Clock.Now +✓ Clock.Now done in 2ms +◐ started sleep.Delay.Seconds +✓ sleep.Delay.Seconds done in 2 seconds +[rest] POST https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:generateAccessToken +◐ started Clock.Now +✓ Clock.Now done in 2ms +◐ started sleep.Delay.Seconds +✓ sleep.Delay.Seconds done in 2 seconds +[rest] POST https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:generateAccessToken +◐ started Clock.Now +✓ Clock.Now done in 2ms +◐ started sleep.Delay.Seconds +✓ sleep.Delay.Seconds done in 2 seconds +[rest] POST https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:generateAccessToken +◐ started Clock.Now +✓ Clock.Now done in 2ms +◐ started sleep.Delay.Seconds +✓ sleep.Delay.Seconds done in 2 seconds +[rest] POST https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:generateAccessToken +◐ started Clock.Now +✓ Clock.Now done in 2ms +◐ started sleep.Delay.Seconds +✓ sleep.Delay.Seconds done in 2 seconds +[rest] POST https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:generateAccessToken +◐ started Clock.Now +✓ Clock.Now done in 2ms +◐ started sleep.Delay.Seconds +✓ sleep.Delay.Seconds done in 2 seconds +[rest] POST https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:generateAccessToken +◐ started Clock.Now +✓ Clock.Now done in 2ms +◐ started sleep.Delay.Seconds +✓ sleep.Delay.Seconds done in 2 seconds +[rest] POST https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:generateAccessToken +◐ started Clock.Now +✓ Clock.Now done in 2ms +◐ started sleep.Delay.Seconds +✓ sleep.Delay.Seconds done in 2 seconds +[rest] POST https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:generateAccessToken +◐ started Clock.Now +✓ Clock.Now done in 2ms +◐ started sleep.Delay.Seconds +✓ sleep.Delay.Seconds done in 2 seconds +[rest] POST https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:generateAccessToken +◐ started Clock.Now +✓ Clock.Now done in 2ms +◐ started sleep.Delay.Seconds +✓ sleep.Delay.Seconds done in 2 seconds +[rest] POST https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:generateAccessToken +◐ started Clock.Now +✓ Clock.Now done in 2ms +◐ started sleep.Delay.Seconds +✓ sleep.Delay.Seconds done in 2 seconds +[rest] POST https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:generateAccessToken +◐ started Clock.Now +✓ Clock.Now done in 2ms +◐ started sleep.Delay.Seconds +✓ sleep.Delay.Seconds done in 2 seconds +[rest] POST https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:generateAccessToken +◐ started Clock.Now +✓ Clock.Now done in 2ms +◐ started sleep.Delay.Seconds +✓ sleep.Delay.Seconds done in 2 seconds +[rest] POST https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:generateAccessToken +◐ started Clock.Now +✓ Clock.Now done in 2ms +◐ started sleep.Delay.Seconds +✓ sleep.Delay.Seconds done in 2 seconds +[rest] POST https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:generateAccessToken +◐ started Clock.Now +✓ Clock.Now done in 2ms +◐ started sleep.Delay.Seconds +✓ sleep.Delay.Seconds done in 2 seconds +[rest] POST https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:generateAccessToken +◐ started Clock.Now +✓ Clock.Now done in 2ms +◐ started sleep.Delay.Seconds +✓ sleep.Delay.Seconds done in 2 seconds +[rest] POST https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:generateAccessToken +◐ started Clock.Now +✓ Clock.Now done in 2ms +◐ started sleep.Delay.Seconds +✓ sleep.Delay.Seconds done in 2 seconds +[rest] POST https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:generateAccessToken +◐ started Clock.Now +✓ Clock.Now done in 2ms +◐ started sleep.Delay.Seconds +✓ sleep.Delay.Seconds done in 2 seconds +[rest] POST https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:generateAccessToken +◐ started Clock.Now +✓ Clock.Now done in 2ms +◐ started sleep.Delay.Seconds +✓ sleep.Delay.Seconds done in 2 seconds +[rest] POST https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:generateAccessToken +◐ started Clock.Now +✓ Clock.Now done in 2ms +◐ started sleep.Delay.Seconds +✓ sleep.Delay.Seconds done in 2 seconds +[rest] POST https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:generateAccessToken +◐ started Clock.Now +✓ Clock.Now done in 2ms +◐ started sleep.Delay.Seconds +✓ sleep.Delay.Seconds done in 2 seconds +[rest] POST https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:generateAccessToken +◐ started Clock.Now +✓ Clock.Now done in 2ms +◐ started sleep.Delay.Seconds +✓ sleep.Delay.Seconds done in 2 seconds +[rest] POST https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:generateAccessToken +◐ started Clock.Now +✓ Clock.Now done in 2ms +◐ started sleep.Delay.Seconds +✓ sleep.Delay.Seconds done in 2 seconds +[rest] POST https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:generateAccessToken +◐ started Clock.Now +✓ Clock.Now done in 1ms +◐ started sleep.Delay.Seconds +✓ sleep.Delay.Seconds done in 2 seconds +[rest] POST https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:generateAccessToken +◐ started Clock.Now +✓ Clock.Now done in 2ms +[file] write_create_new target/gcp-iam-bootstrap-20260929-probe-authority-1-privilege-readback.txt (1028 bytes) +[file] list target +[file] read target/gcp-iam-probe-probe-20260929-1-iam-converge@gunbai-secrets.iam.gserviceaccount.com-intent.txt +[file] list target +[file] read target/gcp-iam-probe-probe-20260929-1-iam-converge@gunbai-secrets.iam.gserviceaccount.com-publication-elected.txt +[file] list target +[file] read target/gcp-iam-probe-probe-20260929-1-iam-converge@gunbai-secrets.iam.gserviceaccount.com-publication-closed.txt +[file] list target +[file] read target/gcp-iam-probe-probe-20260929-1-iam-converge@gunbai-secrets.iam.gserviceaccount.com-retired.txt +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:getIamPolicy +◐ started Clock.Now +✓ Clock.Now done in 2ms +[file] write_create_new target/gcp-iam-probe-probe-20260929-1-iam-converge@gunbai-secrets.iam.gserviceaccount.com-publication-closed.txt (427 bytes) +[file] list target +[file] read target/gcp-iam-probe-probe-20260929-1-iam-converge@gunbai-secrets.iam.gserviceaccount.com-publication-closed.txt +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:setIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:getIamPolicy +[file] write_create_new target/gcp-iam-probe-probe-20260929-1-iam-converge@gunbai-secrets.iam.gserviceaccount.com-retired.txt (427 bytes) +[file] list target +[file] read target/gcp-iam-probe-probe-20260929-1-iam-observe@gunbai-secrets.iam.gserviceaccount.com-intent.txt +[file] list target +[file] read target/gcp-iam-probe-probe-20260929-1-iam-observe@gunbai-secrets.iam.gserviceaccount.com-publication-elected.txt +[file] list target +[file] read target/gcp-iam-probe-probe-20260929-1-iam-observe@gunbai-secrets.iam.gserviceaccount.com-publication-closed.txt +[file] list target +[file] read target/gcp-iam-probe-probe-20260929-1-iam-observe@gunbai-secrets.iam.gserviceaccount.com-retired.txt +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/iam-observe@gunbai-secrets.iam.gserviceaccount.com:getIamPolicy +◐ started Clock.Now +✓ Clock.Now done in 2ms +[file] write_create_new target/gcp-iam-probe-probe-20260929-1-iam-observe@gunbai-secrets.iam.gserviceaccount.com-publication-closed.txt (426 bytes) +[file] list target +[file] read target/gcp-iam-probe-probe-20260929-1-iam-observe@gunbai-secrets.iam.gserviceaccount.com-publication-closed.txt +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/iam-observe@gunbai-secrets.iam.gserviceaccount.com:setIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/iam-observe@gunbai-secrets.iam.gserviceaccount.com:getIamPolicy +[file] write_create_new target/gcp-iam-probe-probe-20260929-1-iam-observe@gunbai-secrets.iam.gserviceaccount.com-retired.txt (426 bytes) +[file] list target +[file] read target/gcp-iam-authority-deny-bootstrap-org-20260928-1-intent.txt +[file] list target +[file] read target/gcp-iam-authority-deny-bootstrap-org-20260928-1-publication-elected.txt +[file] list target +[file] read target/gcp-iam-authority-deny-bootstrap-org-20260928-1-publication-closed.txt +[file] list target +[file] read target/gcp-iam-authority-deny-bootstrap-org-20260928-1-retired.txt +[rest] POST https://cloudresourcemanager.googleapis.com/v3/organizations/266638272282:getIamPolicy +◐ started Clock.Now +✓ Clock.Now done in 2ms +[file] write_create_new target/gcp-iam-authority-deny-bootstrap-org-20260928-1-retired.txt (397 bytes) +[file] list target +[file] read target/gcp-iam-authority-deny-bootstrap-org-20260928-1-retired.txt +apply-account readback credential unavailable; workflow trust withheld: status 403: { + "error": { + "code": 403, + "message": "Permission 'iam.serviceAccounts.getAccessToken' denied on resource (or it may not exist). Remediate access with this Troubleshooter URL or share it with your administrator - https://console.cloud.google.com/iam-admin/troubleshooter/summary;errorId=CiQwMWEwZWE5ZS05ODlkLTcwZjYtOTRiZS1hOTZmNWI2NjYwNjMSAA%3D%3D .", + "status": "PERMISSION_DENIED", + "details": [ + { + "@type": "type.googleapis.com/google.rpc.ErrorInfo", + "reason": "IAM_PERMISSION_DENIED", + "domain": "iam.googleapis.com", + "metadata": { + "troubleshooter_url": "https://console.cloud.google.com/iam-admin/troubleshooter/summary;errorId=CiQwMWEwZWE5ZS05ODlkLTcwZjYtOTRiZS1hOTZmNWI2NjYwNjMSAA%3D%3D", + "permission": "iam.serviceAccounts.getAccessToken", + "error_info_id": "CiQwMWEwZWE5ZS05ODlkLTcwZjYtOTRiZS1hOTZmNWI2NjYwNjMSAA==" + } + } + ] + } +} + diff --git a/docs/plans/receipts/gcp-iam-probe-authority-2026-09-29/controls.log b/docs/plans/receipts/gcp-iam-probe-authority-2026-09-29/controls.log new file mode 100644 index 00000000000..6fdb2b3d97e --- /dev/null +++ b/docs/plans/receipts/gcp-iam-probe-authority-2026-09-29/controls.log @@ -0,0 +1,21 @@ +[workspace-root] discovered /home/briansrls/scratch-wt/gcp-iam-bootstrap +✓ typecheck v2.std.compilers.target_model done in 23 seconds +✓ typecheck v2.compiler.target_serialize done in 2 seconds +✓ typecheck v2.compiler.translate done in 3 seconds +PASS bootstrap_role_drift_never_authorizes_adoption +PASS deny_exceptions_and_incomplete_readback_withhold_trust +PASS workflow_trust_requires_completed_deny_and_privilege_readback +PASS authority_deadline_is_bounded_and_retry_does_not_renew_it +PASS uncertain_publication_cannot_be_settled_from_an_absent_cell +PASS unknown_policy_and_unowned_grants_never_authorize_changes +PASS authority_retirement_preserves_existing_human_and_foreign_access +PASS bootstrap_authority_requires_an_organization_resource +PASS definite_publication_rejection_is_distinct_from_unknown_commit +PASS deny_propagation_retry_requires_live_authority_and_a_remaining_budget +PASS cleanup_dependency_does_not_wait_for_successful_deny_installation +PASS authority_beneficiary_requires_the_verified_stable_google_subject +PASS empty_pool_policy_does_not_authorize_unconditional_publication +PASS pool_initialization_rejects_existing_unfenced_bindings +PASS pool_initialization_readback_requires_exact_cells_and_a_revision +PASS probe_authority_has_no_signing_or_policy_permission +PASS probe_cleanup_is_required_for_trust_but_not_successful_probes diff --git a/docs/plans/receipts/gcp-iam-probe-authority-2026-09-29/gcp-iam-bootstrap-20260929-probe-authority-1-authority-cleanup.txt b/docs/plans/receipts/gcp-iam-probe-authority-2026-09-29/gcp-iam-bootstrap-20260929-probe-authority-1-authority-cleanup.txt new file mode 100644 index 00000000000..480da8f3ea6 --- /dev/null +++ b/docs/plans/receipts/gcp-iam-probe-authority-2026-09-29/gcp-iam-bootstrap-20260929-probe-authority-1-authority-cleanup.txt @@ -0,0 +1 @@ +stage checks completed; see the stage authority for configuration/probe scope diff --git a/docs/plans/receipts/gcp-iam-probe-authority-2026-09-29/gcp-iam-bootstrap-20260929-probe-authority-1-capabilities.txt b/docs/plans/receipts/gcp-iam-probe-authority-2026-09-29/gcp-iam-bootstrap-20260929-probe-authority-1-capabilities.txt new file mode 100644 index 00000000000..480da8f3ea6 --- /dev/null +++ b/docs/plans/receipts/gcp-iam-probe-authority-2026-09-29/gcp-iam-bootstrap-20260929-probe-authority-1-capabilities.txt @@ -0,0 +1 @@ +stage checks completed; see the stage authority for configuration/probe scope diff --git a/docs/plans/receipts/gcp-iam-probe-authority-2026-09-29/gcp-iam-bootstrap-20260929-probe-authority-1-deny.txt b/docs/plans/receipts/gcp-iam-probe-authority-2026-09-29/gcp-iam-bootstrap-20260929-probe-authority-1-deny.txt new file mode 100644 index 00000000000..480da8f3ea6 --- /dev/null +++ b/docs/plans/receipts/gcp-iam-probe-authority-2026-09-29/gcp-iam-bootstrap-20260929-probe-authority-1-deny.txt @@ -0,0 +1 @@ +stage checks completed; see the stage authority for configuration/probe scope diff --git a/docs/plans/receipts/gcp-iam-probe-authority-2026-09-29/gcp-iam-bootstrap-20260929-probe-authority-1-identities.txt b/docs/plans/receipts/gcp-iam-probe-authority-2026-09-29/gcp-iam-bootstrap-20260929-probe-authority-1-identities.txt new file mode 100644 index 00000000000..480da8f3ea6 --- /dev/null +++ b/docs/plans/receipts/gcp-iam-probe-authority-2026-09-29/gcp-iam-bootstrap-20260929-probe-authority-1-identities.txt @@ -0,0 +1 @@ +stage checks completed; see the stage authority for configuration/probe scope diff --git a/docs/plans/receipts/gcp-iam-probe-authority-2026-09-29/gcp-iam-bootstrap-20260929-probe-authority-1-privilege-readback.txt b/docs/plans/receipts/gcp-iam-probe-authority-2026-09-29/gcp-iam-bootstrap-20260929-probe-authority-1-privilege-readback.txt new file mode 100644 index 00000000000..46c242cbff9 --- /dev/null +++ b/docs/plans/receipts/gcp-iam-probe-authority-2026-09-29/gcp-iam-bootstrap-20260929-probe-authority-1-privilege-readback.txt @@ -0,0 +1,19 @@ +refused: apply-account readback credential unavailable; workflow trust withheld: status 403: { + "error": { + "code": 403, + "message": "Permission 'iam.serviceAccounts.getAccessToken' denied on resource (or it may not exist). Remediate access with this Troubleshooter URL or share it with your administrator - https://console.cloud.google.com/iam-admin/troubleshooter/summary;errorId=CiQwMWEwZWE5ZS05ODlkLTcwZjYtOTRiZS1hOTZmNWI2NjYwNjMSAA%3D%3D .", + "status": "PERMISSION_DENIED", + "details": [ + { + "@type": "type.googleapis.com/google.rpc.ErrorInfo", + "reason": "IAM_PERMISSION_DENIED", + "domain": "iam.googleapis.com", + "metadata": { + "troubleshooter_url": "https://console.cloud.google.com/iam-admin/troubleshooter/summary;errorId=CiQwMWEwZWE5ZS05ODlkLTcwZjYtOTRiZS1hOTZmNWI2NjYwNjMSAA%3D%3D", + "permission": "iam.serviceAccounts.getAccessToken", + "error_info_id": "CiQwMWEwZWE5ZS05ODlkLTcwZjYtOTRiZS1hOTZmNWI2NjYwNjMSAA==" + } + } + ] + } +} diff --git a/docs/plans/receipts/gcp-iam-probe-authority-2026-09-29/gcp-iam-bootstrap-20260929-probe-authority-1-probe-authority.txt b/docs/plans/receipts/gcp-iam-probe-authority-2026-09-29/gcp-iam-bootstrap-20260929-probe-authority-1-probe-authority.txt new file mode 100644 index 00000000000..480da8f3ea6 --- /dev/null +++ b/docs/plans/receipts/gcp-iam-probe-authority-2026-09-29/gcp-iam-bootstrap-20260929-probe-authority-1-probe-authority.txt @@ -0,0 +1 @@ +stage checks completed; see the stage authority for configuration/probe scope diff --git a/docs/plans/receipts/gcp-iam-probe-authority-2026-09-29/gcp-iam-bootstrap-20260929-probe-authority-1-roles.txt b/docs/plans/receipts/gcp-iam-probe-authority-2026-09-29/gcp-iam-bootstrap-20260929-probe-authority-1-roles.txt new file mode 100644 index 00000000000..480da8f3ea6 --- /dev/null +++ b/docs/plans/receipts/gcp-iam-probe-authority-2026-09-29/gcp-iam-bootstrap-20260929-probe-authority-1-roles.txt @@ -0,0 +1 @@ +stage checks completed; see the stage authority for configuration/probe scope diff --git a/docs/plans/receipts/gcp-iam-probe-authority-2026-09-29/gcp-iam-bootstrap-20260929-probe-authority-1-started.txt b/docs/plans/receipts/gcp-iam-probe-authority-2026-09-29/gcp-iam-bootstrap-20260929-probe-authority-1-started.txt new file mode 100644 index 00000000000..b286fc75b3e --- /dev/null +++ b/docs/plans/receipts/gcp-iam-probe-authority-2026-09-29/gcp-iam-bootstrap-20260929-probe-authority-1-started.txt @@ -0,0 +1 @@ +operator bootstrap started; not a convergence receipt diff --git a/docs/plans/receipts/gcp-iam-probe-authority-2026-09-29/gcp-iam-probe-probe-20260929-1-iam-converge@gunbai-secrets.iam.gserviceaccount.com-intent.txt b/docs/plans/receipts/gcp-iam-probe-authority-2026-09-29/gcp-iam-probe-probe-20260929-1-iam-converge@gunbai-secrets.iam.gserviceaccount.com-intent.txt new file mode 100644 index 00000000000..ed615588743 --- /dev/null +++ b/docs/plans/receipts/gcp-iam-probe-authority-2026-09-29/gcp-iam-probe-probe-20260929-1-iam-converge@gunbai-secrets.iam.gserviceaccount.com-intent.txt @@ -0,0 +1 @@ +projects/gunbai-secrets/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com;role:57:projects/gunbai-secrets/roles/gunbcIamBootstrapProbeToken;member:18:user:brian@gunb.ai;condition-title:38:gunbc-bootstrap-probe-probe-20260929-1;condition-expression:48:request.time < timestamp('2026-09-29T01:05:00Z');condition-description:83:Temporary exact-account bootstrap readback; physical cleanup required after expiry.; \ No newline at end of file diff --git a/docs/plans/receipts/gcp-iam-probe-authority-2026-09-29/gcp-iam-probe-probe-20260929-1-iam-converge@gunbai-secrets.iam.gserviceaccount.com-publication-closed.txt b/docs/plans/receipts/gcp-iam-probe-authority-2026-09-29/gcp-iam-probe-probe-20260929-1-iam-converge@gunbai-secrets.iam.gserviceaccount.com-publication-closed.txt new file mode 100644 index 00000000000..ed615588743 --- /dev/null +++ b/docs/plans/receipts/gcp-iam-probe-authority-2026-09-29/gcp-iam-probe-probe-20260929-1-iam-converge@gunbai-secrets.iam.gserviceaccount.com-publication-closed.txt @@ -0,0 +1 @@ +projects/gunbai-secrets/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com;role:57:projects/gunbai-secrets/roles/gunbcIamBootstrapProbeToken;member:18:user:brian@gunb.ai;condition-title:38:gunbc-bootstrap-probe-probe-20260929-1;condition-expression:48:request.time < timestamp('2026-09-29T01:05:00Z');condition-description:83:Temporary exact-account bootstrap readback; physical cleanup required after expiry.; \ No newline at end of file diff --git a/docs/plans/receipts/gcp-iam-probe-authority-2026-09-29/gcp-iam-probe-probe-20260929-1-iam-converge@gunbai-secrets.iam.gserviceaccount.com-publication-elected.txt b/docs/plans/receipts/gcp-iam-probe-authority-2026-09-29/gcp-iam-probe-probe-20260929-1-iam-converge@gunbai-secrets.iam.gserviceaccount.com-publication-elected.txt new file mode 100644 index 00000000000..ed615588743 --- /dev/null +++ b/docs/plans/receipts/gcp-iam-probe-authority-2026-09-29/gcp-iam-probe-probe-20260929-1-iam-converge@gunbai-secrets.iam.gserviceaccount.com-publication-elected.txt @@ -0,0 +1 @@ +projects/gunbai-secrets/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com;role:57:projects/gunbai-secrets/roles/gunbcIamBootstrapProbeToken;member:18:user:brian@gunb.ai;condition-title:38:gunbc-bootstrap-probe-probe-20260929-1;condition-expression:48:request.time < timestamp('2026-09-29T01:05:00Z');condition-description:83:Temporary exact-account bootstrap readback; physical cleanup required after expiry.; \ No newline at end of file diff --git a/docs/plans/receipts/gcp-iam-probe-authority-2026-09-29/gcp-iam-probe-probe-20260929-1-iam-converge@gunbai-secrets.iam.gserviceaccount.com-retired.txt b/docs/plans/receipts/gcp-iam-probe-authority-2026-09-29/gcp-iam-probe-probe-20260929-1-iam-converge@gunbai-secrets.iam.gserviceaccount.com-retired.txt new file mode 100644 index 00000000000..ed615588743 --- /dev/null +++ b/docs/plans/receipts/gcp-iam-probe-authority-2026-09-29/gcp-iam-probe-probe-20260929-1-iam-converge@gunbai-secrets.iam.gserviceaccount.com-retired.txt @@ -0,0 +1 @@ +projects/gunbai-secrets/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com;role:57:projects/gunbai-secrets/roles/gunbcIamBootstrapProbeToken;member:18:user:brian@gunb.ai;condition-title:38:gunbc-bootstrap-probe-probe-20260929-1;condition-expression:48:request.time < timestamp('2026-09-29T01:05:00Z');condition-description:83:Temporary exact-account bootstrap readback; physical cleanup required after expiry.; \ No newline at end of file diff --git a/docs/plans/receipts/gcp-iam-probe-authority-2026-09-29/gcp-iam-probe-probe-20260929-1-iam-observe@gunbai-secrets.iam.gserviceaccount.com-intent.txt b/docs/plans/receipts/gcp-iam-probe-authority-2026-09-29/gcp-iam-probe-probe-20260929-1-iam-observe@gunbai-secrets.iam.gserviceaccount.com-intent.txt new file mode 100644 index 00000000000..10de7ae96f7 --- /dev/null +++ b/docs/plans/receipts/gcp-iam-probe-authority-2026-09-29/gcp-iam-probe-probe-20260929-1-iam-observe@gunbai-secrets.iam.gserviceaccount.com-intent.txt @@ -0,0 +1 @@ +projects/gunbai-secrets/serviceAccounts/iam-observe@gunbai-secrets.iam.gserviceaccount.com;role:57:projects/gunbai-secrets/roles/gunbcIamBootstrapProbeToken;member:18:user:brian@gunb.ai;condition-title:38:gunbc-bootstrap-probe-probe-20260929-1;condition-expression:48:request.time < timestamp('2026-09-29T01:05:00Z');condition-description:83:Temporary exact-account bootstrap readback; physical cleanup required after expiry.; \ No newline at end of file diff --git a/docs/plans/receipts/gcp-iam-probe-authority-2026-09-29/gcp-iam-probe-probe-20260929-1-iam-observe@gunbai-secrets.iam.gserviceaccount.com-publication-closed.txt b/docs/plans/receipts/gcp-iam-probe-authority-2026-09-29/gcp-iam-probe-probe-20260929-1-iam-observe@gunbai-secrets.iam.gserviceaccount.com-publication-closed.txt new file mode 100644 index 00000000000..10de7ae96f7 --- /dev/null +++ b/docs/plans/receipts/gcp-iam-probe-authority-2026-09-29/gcp-iam-probe-probe-20260929-1-iam-observe@gunbai-secrets.iam.gserviceaccount.com-publication-closed.txt @@ -0,0 +1 @@ +projects/gunbai-secrets/serviceAccounts/iam-observe@gunbai-secrets.iam.gserviceaccount.com;role:57:projects/gunbai-secrets/roles/gunbcIamBootstrapProbeToken;member:18:user:brian@gunb.ai;condition-title:38:gunbc-bootstrap-probe-probe-20260929-1;condition-expression:48:request.time < timestamp('2026-09-29T01:05:00Z');condition-description:83:Temporary exact-account bootstrap readback; physical cleanup required after expiry.; \ No newline at end of file diff --git a/docs/plans/receipts/gcp-iam-probe-authority-2026-09-29/gcp-iam-probe-probe-20260929-1-iam-observe@gunbai-secrets.iam.gserviceaccount.com-publication-elected.txt b/docs/plans/receipts/gcp-iam-probe-authority-2026-09-29/gcp-iam-probe-probe-20260929-1-iam-observe@gunbai-secrets.iam.gserviceaccount.com-publication-elected.txt new file mode 100644 index 00000000000..10de7ae96f7 --- /dev/null +++ b/docs/plans/receipts/gcp-iam-probe-authority-2026-09-29/gcp-iam-probe-probe-20260929-1-iam-observe@gunbai-secrets.iam.gserviceaccount.com-publication-elected.txt @@ -0,0 +1 @@ +projects/gunbai-secrets/serviceAccounts/iam-observe@gunbai-secrets.iam.gserviceaccount.com;role:57:projects/gunbai-secrets/roles/gunbcIamBootstrapProbeToken;member:18:user:brian@gunb.ai;condition-title:38:gunbc-bootstrap-probe-probe-20260929-1;condition-expression:48:request.time < timestamp('2026-09-29T01:05:00Z');condition-description:83:Temporary exact-account bootstrap readback; physical cleanup required after expiry.; \ No newline at end of file diff --git a/docs/plans/receipts/gcp-iam-probe-authority-2026-09-29/gcp-iam-probe-probe-20260929-1-iam-observe@gunbai-secrets.iam.gserviceaccount.com-retired.txt b/docs/plans/receipts/gcp-iam-probe-authority-2026-09-29/gcp-iam-probe-probe-20260929-1-iam-observe@gunbai-secrets.iam.gserviceaccount.com-retired.txt new file mode 100644 index 00000000000..10de7ae96f7 --- /dev/null +++ b/docs/plans/receipts/gcp-iam-probe-authority-2026-09-29/gcp-iam-probe-probe-20260929-1-iam-observe@gunbai-secrets.iam.gserviceaccount.com-retired.txt @@ -0,0 +1 @@ +projects/gunbai-secrets/serviceAccounts/iam-observe@gunbai-secrets.iam.gserviceaccount.com;role:57:projects/gunbai-secrets/roles/gunbcIamBootstrapProbeToken;member:18:user:brian@gunb.ai;condition-title:38:gunbc-bootstrap-probe-probe-20260929-1;condition-expression:48:request.time < timestamp('2026-09-29T01:05:00Z');condition-description:83:Temporary exact-account bootstrap readback; physical cleanup required after expiry.; \ No newline at end of file diff --git a/docs/plans/receipts/gcp-iam-probe-authority-2026-09-29/receipt.json b/docs/plans/receipts/gcp-iam-probe-authority-2026-09-29/receipt.json new file mode 100644 index 00000000000..5eec823bba3 --- /dev/null +++ b/docs/plans/receipts/gcp-iam-probe-authority-2026-09-29/receipt.json @@ -0,0 +1,14 @@ +{ + "source_revision": "da6f77d4bd3929d41c6ea4a58093420a6a4cb7d4", + "binary_sha256": "179882133a4c03958a1bed8a8b444ea06e48c63a6c42263ff83694508825b415", + "focused_controls_passed": 17, + "closure_modules": 843, + "memory_max": "6G", + "memory_swap_max": 0, + "probe_operation": "probe-20260929-1", + "probe_deadline": "2026-09-29T01:05:00Z", + "cloud_status": "both temporary probe grants installed and read back; token mint refused through bounded retry; both grants removed and absence verified; workflow trust withheld", + "full_landing_suite": "not run", + "temporary_token_file": "removed", + "mint_attempts": 30 +} diff --git a/docs/plans/receipts/gcp-iam-probe-propagation-2026-09-29/apply.log b/docs/plans/receipts/gcp-iam-probe-propagation-2026-09-29/apply.log new file mode 100644 index 00000000000..f3988596a10 --- /dev/null +++ b/docs/plans/receipts/gcp-iam-probe-propagation-2026-09-29/apply.log @@ -0,0 +1,352 @@ +[workspace-root] discovered /home/briansrls/scratch-wt/gcp-iam-bootstrap +✓ typecheck v2.std.compilers.target_model done in 23 seconds +✓ typecheck v2.compiler.target_serialize done in 2 seconds +✓ typecheck v2.compiler.translate done in 3 seconds +[file] read +[file] write_create_new target/gcp-iam-bootstrap-20260929-probe-authority-2-started.txt (54 bytes) +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-gcp-iam-converge +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-gcp-iam-converge/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/iam-observe@gunbai-secrets.iam.gserviceaccount.com +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-gcp-iam-converge +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-gcp-iam-converge/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/iam-observe@gunbai-secrets.iam.gserviceaccount.com +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-gcp-iam-converge +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-gcp-iam-converge/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-gcp-iam-converge +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-gcp-iam-converge/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/heal-publisher@gunbai-secrets.iam.gserviceaccount.com +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/heal-publisher@gunbai-secrets.iam.gserviceaccount.com +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher-private +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher-private/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/heal-publisher-private@gunbai-secrets.iam.gserviceaccount.com +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher-private +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher-private/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/heal-publisher-private@gunbai-secrets.iam.gserviceaccount.com +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-mtcollins1-boot +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-mtcollins1-boot/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/mtcollins1-boot@gunbai-secrets.iam.gserviceaccount.com +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-mtcollins1-boot +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-mtcollins1-boot/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/mtcollins1-boot@gunbai-secrets.iam.gserviceaccount.com +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-namecheap-dns +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-namecheap-dns/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/namecheap-dns@gunbai-secrets.iam.gserviceaccount.com +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-namecheap-dns +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-namecheap-dns/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/namecheap-dns@gunbai-secrets.iam.gserviceaccount.com +[file] write_create_new target/gcp-iam-bootstrap-20260929-probe-authority-2-identities.txt (78 bytes) +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/roles/gunbcIamConvergeCreate +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/roles/gunbcIamConvergeProvider +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/roles/gunbcIamConvergeAccountPolicy +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/roles/gunbcIamConvergeSecretPolicy +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/roles/gunbcIamObservePool +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/roles/gunbcIamObserveAccount +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/roles/gunbcIamObserveSecret +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/roles/gunbcIamBootstrapProbeToken +[file] write_create_new target/gcp-iam-bootstrap-20260929-probe-authority-2-roles.txt (78 bytes) +[rest] GET https://iam.googleapis.com/v2/policies/cloudresourcemanager.googleapis.com%2Fprojects%2F582015116396/denypolicies/gunbc-iam-converge-boundary +[file] write_create_new target/gcp-iam-bootstrap-20260929-probe-authority-2-deny.txt (78 bytes) +[file] list target +[file] read target/gcp-iam-authority-deny-bootstrap-org-20260928-1-intent.txt +[file] list target +[file] read target/gcp-iam-authority-deny-bootstrap-org-20260928-1-publication-elected.txt +[file] list target +[file] read target/gcp-iam-authority-deny-bootstrap-org-20260928-1-publication-closed.txt +[file] list target +[file] read target/gcp-iam-authority-deny-bootstrap-org-20260928-1-retired.txt +[rest] POST https://cloudresourcemanager.googleapis.com/v3/organizations/266638272282:getIamPolicy +◐ started Clock.Now +✓ Clock.Now done in 1ms +[file] write_create_new target/gcp-iam-authority-deny-bootstrap-org-20260928-1-retired.txt (397 bytes) +[file] list target +[file] read target/gcp-iam-authority-deny-bootstrap-org-20260928-1-retired.txt +[file] write_create_new target/gcp-iam-bootstrap-20260929-probe-authority-2-authority-cleanup.txt (78 bytes) +[rest] POST https://cloudresourcemanager.googleapis.com/v1/projects/gunbai-secrets:getIamPolicy +[file] list target +[file] read target/gcp-iam-pool-initialization-github-heal-publisher-elected.txt +[file] list target +[file] read target/gcp-iam-pool-initialization-github-heal-publisher-closed.txt +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher:getIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher:getIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/heal-publisher@gunbai-secrets.iam.gserviceaccount.com:getIamPolicy +[rest] GET https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/agent-github-app-private-key:getIamPolicy +[file] list target +[file] read target/gcp-iam-pool-initialization-github-heal-publisher-private-elected.txt +[file] list target +[file] read target/gcp-iam-pool-initialization-github-heal-publisher-private-closed.txt +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher-private:getIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher-private:getIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/heal-publisher-private@gunbai-secrets.iam.gserviceaccount.com:getIamPolicy +[file] list target +[file] read target/gcp-iam-pool-initialization-github-mtcollins1-boot-elected.txt +[file] list target +[file] read target/gcp-iam-pool-initialization-github-mtcollins1-boot-closed.txt +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-mtcollins1-boot:getIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-mtcollins1-boot:getIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/mtcollins1-boot@gunbai-secrets.iam.gserviceaccount.com:getIamPolicy +[rest] GET https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/bmc-mtcollins1-gunbc:getIamPolicy +[rest] GET https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/fleet-automation-ssh-key:getIamPolicy +[file] list target +[file] read target/gcp-iam-pool-initialization-github-namecheap-dns-elected.txt +[file] list target +[file] read target/gcp-iam-pool-initialization-github-namecheap-dns-closed.txt +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-namecheap-dns:getIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-namecheap-dns:getIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/namecheap-dns@gunbai-secrets.iam.gserviceaccount.com:getIamPolicy +[rest] GET https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/namecheap-api-key:getIamPolicy +[file] list target +[file] read target/gcp-iam-pool-initialization-github-heal-publisher-elected.txt +[file] list target +[file] read target/gcp-iam-pool-initialization-github-heal-publisher-closed.txt +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher:getIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher:getIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/heal-publisher@gunbai-secrets.iam.gserviceaccount.com:getIamPolicy +[rest] GET https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/agent-github-app-private-key:getIamPolicy +[file] list target +[file] read target/gcp-iam-pool-initialization-github-heal-publisher-private-elected.txt +[file] list target +[file] read target/gcp-iam-pool-initialization-github-heal-publisher-private-closed.txt +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher-private:getIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher-private:getIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/heal-publisher-private@gunbai-secrets.iam.gserviceaccount.com:getIamPolicy +[file] list target +[file] read target/gcp-iam-pool-initialization-github-mtcollins1-boot-elected.txt +[file] list target +[file] read target/gcp-iam-pool-initialization-github-mtcollins1-boot-closed.txt +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-mtcollins1-boot:getIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-mtcollins1-boot:getIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/mtcollins1-boot@gunbai-secrets.iam.gserviceaccount.com:getIamPolicy +[rest] GET https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/bmc-mtcollins1-gunbc:getIamPolicy +[rest] GET https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/fleet-automation-ssh-key:getIamPolicy +[file] list target +[file] read target/gcp-iam-pool-initialization-github-namecheap-dns-elected.txt +[file] list target +[file] read target/gcp-iam-pool-initialization-github-namecheap-dns-closed.txt +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-namecheap-dns:getIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-namecheap-dns:getIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/namecheap-dns@gunbai-secrets.iam.gserviceaccount.com:getIamPolicy +[rest] GET https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/namecheap-api-key:getIamPolicy +[file] list target +[file] read target/gcp-iam-pool-initialization-github-gcp-iam-converge-elected.txt +[file] list target +[file] read target/gcp-iam-pool-initialization-github-gcp-iam-converge-closed.txt +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-gcp-iam-converge:getIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-gcp-iam-converge:getIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/iam-observe@gunbai-secrets.iam.gserviceaccount.com:getIamPolicy +[rest] GET https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/approval-submission-mac-key:getIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:getIamPolicy +[rest] GET https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/approval-submission-mac-key:getIamPolicy +[file] write_create_new target/gcp-iam-bootstrap-20260929-probe-authority-2-capabilities.txt (78 bytes) +[rest] GET https://openidconnect.googleapis.com/v1/userinfo +[file] list target +[file] read target/gcp-iam-probe-probe-20260929-2-iam-converge@gunbai-secrets.iam.gserviceaccount.com-intent.txt +[file] list target +[file] read target/gcp-iam-probe-probe-20260929-2-iam-converge@gunbai-secrets.iam.gserviceaccount.com-publication-elected.txt +[file] list target +[file] read target/gcp-iam-probe-probe-20260929-2-iam-converge@gunbai-secrets.iam.gserviceaccount.com-publication-closed.txt +[file] list target +[file] read target/gcp-iam-probe-probe-20260929-2-iam-converge@gunbai-secrets.iam.gserviceaccount.com-retired.txt +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:getIamPolicy +[file] write_create_new target/gcp-iam-probe-probe-20260929-2-iam-converge@gunbai-secrets.iam.gserviceaccount.com-intent.txt (427 bytes) +◐ started Clock.Now +✓ Clock.Now done in 2ms +[file] write_create_new target/gcp-iam-probe-probe-20260929-2-iam-converge@gunbai-secrets.iam.gserviceaccount.com-publication-elected.txt (427 bytes) +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:setIamPolicy +[file] write_create_new target/gcp-iam-probe-probe-20260929-2-iam-converge@gunbai-secrets.iam.gserviceaccount.com-publication-closed.txt (427 bytes) +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:getIamPolicy +[file] write_create_new target/gcp-iam-probe-probe-20260929-2-iam-converge@gunbai-secrets.iam.gserviceaccount.com-publication-closed.txt (427 bytes) +[file] list target +[file] read target/gcp-iam-probe-probe-20260929-2-iam-converge@gunbai-secrets.iam.gserviceaccount.com-publication-closed.txt +[file] list target +[file] read target/gcp-iam-probe-probe-20260929-2-iam-observe@gunbai-secrets.iam.gserviceaccount.com-intent.txt +[file] list target +[file] read target/gcp-iam-probe-probe-20260929-2-iam-observe@gunbai-secrets.iam.gserviceaccount.com-publication-elected.txt +[file] list target +[file] read target/gcp-iam-probe-probe-20260929-2-iam-observe@gunbai-secrets.iam.gserviceaccount.com-publication-closed.txt +[file] list target +[file] read target/gcp-iam-probe-probe-20260929-2-iam-observe@gunbai-secrets.iam.gserviceaccount.com-retired.txt +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/iam-observe@gunbai-secrets.iam.gserviceaccount.com:getIamPolicy +[file] write_create_new target/gcp-iam-probe-probe-20260929-2-iam-observe@gunbai-secrets.iam.gserviceaccount.com-intent.txt (426 bytes) +◐ started Clock.Now +✓ Clock.Now done in 2ms +[file] write_create_new target/gcp-iam-probe-probe-20260929-2-iam-observe@gunbai-secrets.iam.gserviceaccount.com-publication-elected.txt (426 bytes) +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/iam-observe@gunbai-secrets.iam.gserviceaccount.com:setIamPolicy +[file] write_create_new target/gcp-iam-probe-probe-20260929-2-iam-observe@gunbai-secrets.iam.gserviceaccount.com-publication-closed.txt (426 bytes) +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/iam-observe@gunbai-secrets.iam.gserviceaccount.com:getIamPolicy +[file] write_create_new target/gcp-iam-probe-probe-20260929-2-iam-observe@gunbai-secrets.iam.gserviceaccount.com-publication-closed.txt (426 bytes) +[file] list target +[file] read target/gcp-iam-probe-probe-20260929-2-iam-observe@gunbai-secrets.iam.gserviceaccount.com-publication-closed.txt +[file] write_create_new target/gcp-iam-bootstrap-20260929-probe-authority-2-probe-authority.txt (78 bytes) +[rest] POST https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:generateAccessToken +◐ started Clock.Now +✓ Clock.Now done in 2ms +◐ started sleep.Delay.Seconds +✓ sleep.Delay.Seconds done in 5 seconds +[rest] POST https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:generateAccessToken +◐ started Clock.Now +✓ Clock.Now done in 2ms +◐ started sleep.Delay.Seconds +✓ sleep.Delay.Seconds done in 5 seconds +[rest] POST https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:generateAccessToken +◐ started Clock.Now +✓ Clock.Now done in 2ms +◐ started sleep.Delay.Seconds +✓ sleep.Delay.Seconds done in 5 seconds +[rest] POST https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:generateAccessToken +◐ started Clock.Now +✓ Clock.Now done in 2ms +◐ started sleep.Delay.Seconds +✓ sleep.Delay.Seconds done in 5 seconds +[rest] POST https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:generateAccessToken +◐ started Clock.Now +✓ Clock.Now done in 2ms +◐ started sleep.Delay.Seconds +✓ sleep.Delay.Seconds done in 5 seconds +[rest] POST https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:generateAccessToken +◐ started Clock.Now +✓ Clock.Now done in 2ms +◐ started sleep.Delay.Seconds +✓ sleep.Delay.Seconds done in 5 seconds +[rest] POST https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:generateAccessToken +◐ started Clock.Now +✓ Clock.Now done in 2ms +◐ started sleep.Delay.Seconds +✓ sleep.Delay.Seconds done in 5 seconds +[rest] POST https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:generateAccessToken +◐ started Clock.Now +✓ Clock.Now done in 2ms +◐ started sleep.Delay.Seconds +✓ sleep.Delay.Seconds done in 5 seconds +[rest] POST https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:generateAccessToken +◐ started Clock.Now +✓ Clock.Now done in 2ms +◐ started sleep.Delay.Seconds +✓ sleep.Delay.Seconds done in 5 seconds +[rest] POST https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:generateAccessToken +◐ started Clock.Now +✓ Clock.Now done in 2ms +◐ started sleep.Delay.Seconds +✓ sleep.Delay.Seconds done in 5 seconds +[rest] POST https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:generateAccessToken +◐ started Clock.Now +✓ Clock.Now done in 2ms +◐ started sleep.Delay.Seconds +✓ sleep.Delay.Seconds done in 5 seconds +[rest] POST https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:generateAccessToken +◐ started Clock.Now +✓ Clock.Now done in 2ms +◐ started sleep.Delay.Seconds +✓ sleep.Delay.Seconds done in 5 seconds +[rest] POST https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:generateAccessToken +◐ started Clock.Now +✓ Clock.Now done in 2ms +◐ started sleep.Delay.Seconds +✓ sleep.Delay.Seconds done in 5 seconds +[rest] POST https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:generateAccessToken +◐ started Clock.Now +✓ Clock.Now done in 2ms +◐ started sleep.Delay.Seconds +✓ sleep.Delay.Seconds done in 5 seconds +[rest] POST https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:generateAccessToken +◐ started Clock.Now +✓ Clock.Now done in 2ms +◐ started sleep.Delay.Seconds +✓ sleep.Delay.Seconds done in 5 seconds +[rest] POST https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:generateAccessToken +◐ started Clock.Now +✓ Clock.Now done in 2ms +◐ started sleep.Delay.Seconds +✓ sleep.Delay.Seconds done in 5 seconds +[rest] POST https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:generateAccessToken +[rest] POST https://cloudresourcemanager.googleapis.com/v1/projects/gunbai-secrets:testIamPermissions +[file] write_create_new target/gcp-iam-bootstrap-20260929-probe-authority-2-privilege-readback.txt (1828 bytes) +[file] list target +[file] read target/gcp-iam-probe-probe-20260929-2-iam-converge@gunbai-secrets.iam.gserviceaccount.com-intent.txt +[file] list target +[file] read target/gcp-iam-probe-probe-20260929-2-iam-converge@gunbai-secrets.iam.gserviceaccount.com-publication-elected.txt +[file] list target +[file] read target/gcp-iam-probe-probe-20260929-2-iam-converge@gunbai-secrets.iam.gserviceaccount.com-publication-closed.txt +[file] list target +[file] read target/gcp-iam-probe-probe-20260929-2-iam-converge@gunbai-secrets.iam.gserviceaccount.com-retired.txt +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:getIamPolicy +◐ started Clock.Now +✓ Clock.Now done in 2ms +[file] write_create_new target/gcp-iam-probe-probe-20260929-2-iam-converge@gunbai-secrets.iam.gserviceaccount.com-publication-closed.txt (427 bytes) +[file] list target +[file] read target/gcp-iam-probe-probe-20260929-2-iam-converge@gunbai-secrets.iam.gserviceaccount.com-publication-closed.txt +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:setIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:getIamPolicy +[file] write_create_new target/gcp-iam-probe-probe-20260929-2-iam-converge@gunbai-secrets.iam.gserviceaccount.com-retired.txt (427 bytes) +[file] list target +[file] read target/gcp-iam-probe-probe-20260929-2-iam-observe@gunbai-secrets.iam.gserviceaccount.com-intent.txt +[file] list target +[file] read target/gcp-iam-probe-probe-20260929-2-iam-observe@gunbai-secrets.iam.gserviceaccount.com-publication-elected.txt +[file] list target +[file] read target/gcp-iam-probe-probe-20260929-2-iam-observe@gunbai-secrets.iam.gserviceaccount.com-publication-closed.txt +[file] list target +[file] read target/gcp-iam-probe-probe-20260929-2-iam-observe@gunbai-secrets.iam.gserviceaccount.com-retired.txt +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/iam-observe@gunbai-secrets.iam.gserviceaccount.com:getIamPolicy +◐ started Clock.Now +✓ Clock.Now done in 2ms +[file] write_create_new target/gcp-iam-probe-probe-20260929-2-iam-observe@gunbai-secrets.iam.gserviceaccount.com-publication-closed.txt (426 bytes) +[file] list target +[file] read target/gcp-iam-probe-probe-20260929-2-iam-observe@gunbai-secrets.iam.gserviceaccount.com-publication-closed.txt +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/iam-observe@gunbai-secrets.iam.gserviceaccount.com:setIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/iam-observe@gunbai-secrets.iam.gserviceaccount.com:getIamPolicy +[file] write_create_new target/gcp-iam-probe-probe-20260929-2-iam-observe@gunbai-secrets.iam.gserviceaccount.com-retired.txt (426 bytes) +[file] list target +[file] read target/gcp-iam-authority-deny-bootstrap-org-20260928-1-intent.txt +[file] list target +[file] read target/gcp-iam-authority-deny-bootstrap-org-20260928-1-publication-elected.txt +[file] list target +[file] read target/gcp-iam-authority-deny-bootstrap-org-20260928-1-publication-closed.txt +[file] list target +[file] read target/gcp-iam-authority-deny-bootstrap-org-20260928-1-retired.txt +[rest] POST https://cloudresourcemanager.googleapis.com/v3/organizations/266638272282:getIamPolicy +◐ started Clock.Now +✓ Clock.Now done in 2ms +[file] write_create_new target/gcp-iam-authority-deny-bootstrap-org-20260928-1-retired.txt (397 bytes) +[file] list target +[file] read target/gcp-iam-authority-deny-bootstrap-org-20260928-1-retired.txt +status 403: { + "error": { + "code": 403, + "message": "Cloud Resource Manager API has not been used in project 582015116396 before or it is disabled. Enable it by visiting https://console.developers.google.com/apis/api/cloudresourcemanager.googleapis.com/overview?project=582015116396 then retry. If you enabled this API recently, wait a few minutes for the action to propagate to our systems and retry.", + "status": "PERMISSION_DENIED", + "details": [ + { + "@type": "type.googleapis.com/google.rpc.ErrorInfo", + "reason": "SERVICE_DISABLED", + "domain": "googleapis.com", + "metadata": { + "containerInfo": "582015116396", + "service": "cloudresourcemanager.googleapis.com", + "consumer": "projects/582015116396", + "serviceTitle": "Cloud Resource Manager API", + "activationUrl": "https://console.developers.google.com/apis/api/cloudresourcemanager.googleapis.com/overview?project=582015116396" + } + }, + { + "@type": "type.googleapis.com/google.rpc.LocalizedMessage", + "locale": "en-US", + "message": "Cloud Resource Manager API has not been used in project 582015116396 before or it is disabled. Enable it by visiting https://console.developers.google.com/apis/api/cloudresourcemanager.googleapis.com/overview?project=582015116396 then retry. If you enabled this API recently, wait a few minutes for the action to propagate to our systems and retry." + }, + { + "@type": "type.googleapis.com/google.rpc.Help", + "links": [ + { + "description": "Google developers console API activation", + "url": "https://console.developers.google.com/apis/api/cloudresourcemanager.googleapis.com/overview?project=582015116396" + } + ] + } + ] + } +} + diff --git a/docs/plans/receipts/gcp-iam-probe-propagation-2026-09-29/gcp-iam-bootstrap-20260929-probe-authority-2-authority-cleanup.txt b/docs/plans/receipts/gcp-iam-probe-propagation-2026-09-29/gcp-iam-bootstrap-20260929-probe-authority-2-authority-cleanup.txt new file mode 100644 index 00000000000..480da8f3ea6 --- /dev/null +++ b/docs/plans/receipts/gcp-iam-probe-propagation-2026-09-29/gcp-iam-bootstrap-20260929-probe-authority-2-authority-cleanup.txt @@ -0,0 +1 @@ +stage checks completed; see the stage authority for configuration/probe scope diff --git a/docs/plans/receipts/gcp-iam-probe-propagation-2026-09-29/gcp-iam-bootstrap-20260929-probe-authority-2-capabilities.txt b/docs/plans/receipts/gcp-iam-probe-propagation-2026-09-29/gcp-iam-bootstrap-20260929-probe-authority-2-capabilities.txt new file mode 100644 index 00000000000..480da8f3ea6 --- /dev/null +++ b/docs/plans/receipts/gcp-iam-probe-propagation-2026-09-29/gcp-iam-bootstrap-20260929-probe-authority-2-capabilities.txt @@ -0,0 +1 @@ +stage checks completed; see the stage authority for configuration/probe scope diff --git a/docs/plans/receipts/gcp-iam-probe-propagation-2026-09-29/gcp-iam-bootstrap-20260929-probe-authority-2-deny.txt b/docs/plans/receipts/gcp-iam-probe-propagation-2026-09-29/gcp-iam-bootstrap-20260929-probe-authority-2-deny.txt new file mode 100644 index 00000000000..480da8f3ea6 --- /dev/null +++ b/docs/plans/receipts/gcp-iam-probe-propagation-2026-09-29/gcp-iam-bootstrap-20260929-probe-authority-2-deny.txt @@ -0,0 +1 @@ +stage checks completed; see the stage authority for configuration/probe scope diff --git a/docs/plans/receipts/gcp-iam-probe-propagation-2026-09-29/gcp-iam-bootstrap-20260929-probe-authority-2-identities.txt b/docs/plans/receipts/gcp-iam-probe-propagation-2026-09-29/gcp-iam-bootstrap-20260929-probe-authority-2-identities.txt new file mode 100644 index 00000000000..480da8f3ea6 --- /dev/null +++ b/docs/plans/receipts/gcp-iam-probe-propagation-2026-09-29/gcp-iam-bootstrap-20260929-probe-authority-2-identities.txt @@ -0,0 +1 @@ +stage checks completed; see the stage authority for configuration/probe scope diff --git a/docs/plans/receipts/gcp-iam-probe-propagation-2026-09-29/gcp-iam-bootstrap-20260929-probe-authority-2-privilege-readback.txt b/docs/plans/receipts/gcp-iam-probe-propagation-2026-09-29/gcp-iam-bootstrap-20260929-probe-authority-2-privilege-readback.txt new file mode 100644 index 00000000000..ebc3b097d9f --- /dev/null +++ b/docs/plans/receipts/gcp-iam-probe-propagation-2026-09-29/gcp-iam-bootstrap-20260929-probe-authority-2-privilege-readback.txt @@ -0,0 +1,35 @@ +refused: status 403: { + "error": { + "code": 403, + "message": "Cloud Resource Manager API has not been used in project 582015116396 before or it is disabled. Enable it by visiting https://console.developers.google.com/apis/api/cloudresourcemanager.googleapis.com/overview?project=582015116396 then retry. If you enabled this API recently, wait a few minutes for the action to propagate to our systems and retry.", + "status": "PERMISSION_DENIED", + "details": [ + { + "@type": "type.googleapis.com/google.rpc.ErrorInfo", + "reason": "SERVICE_DISABLED", + "domain": "googleapis.com", + "metadata": { + "containerInfo": "582015116396", + "service": "cloudresourcemanager.googleapis.com", + "consumer": "projects/582015116396", + "serviceTitle": "Cloud Resource Manager API", + "activationUrl": "https://console.developers.google.com/apis/api/cloudresourcemanager.googleapis.com/overview?project=582015116396" + } + }, + { + "@type": "type.googleapis.com/google.rpc.LocalizedMessage", + "locale": "en-US", + "message": "Cloud Resource Manager API has not been used in project 582015116396 before or it is disabled. Enable it by visiting https://console.developers.google.com/apis/api/cloudresourcemanager.googleapis.com/overview?project=582015116396 then retry. If you enabled this API recently, wait a few minutes for the action to propagate to our systems and retry." + }, + { + "@type": "type.googleapis.com/google.rpc.Help", + "links": [ + { + "description": "Google developers console API activation", + "url": "https://console.developers.google.com/apis/api/cloudresourcemanager.googleapis.com/overview?project=582015116396" + } + ] + } + ] + } +} diff --git a/docs/plans/receipts/gcp-iam-probe-propagation-2026-09-29/gcp-iam-bootstrap-20260929-probe-authority-2-probe-authority.txt b/docs/plans/receipts/gcp-iam-probe-propagation-2026-09-29/gcp-iam-bootstrap-20260929-probe-authority-2-probe-authority.txt new file mode 100644 index 00000000000..480da8f3ea6 --- /dev/null +++ b/docs/plans/receipts/gcp-iam-probe-propagation-2026-09-29/gcp-iam-bootstrap-20260929-probe-authority-2-probe-authority.txt @@ -0,0 +1 @@ +stage checks completed; see the stage authority for configuration/probe scope diff --git a/docs/plans/receipts/gcp-iam-probe-propagation-2026-09-29/gcp-iam-bootstrap-20260929-probe-authority-2-roles.txt b/docs/plans/receipts/gcp-iam-probe-propagation-2026-09-29/gcp-iam-bootstrap-20260929-probe-authority-2-roles.txt new file mode 100644 index 00000000000..480da8f3ea6 --- /dev/null +++ b/docs/plans/receipts/gcp-iam-probe-propagation-2026-09-29/gcp-iam-bootstrap-20260929-probe-authority-2-roles.txt @@ -0,0 +1 @@ +stage checks completed; see the stage authority for configuration/probe scope diff --git a/docs/plans/receipts/gcp-iam-probe-propagation-2026-09-29/gcp-iam-bootstrap-20260929-probe-authority-2-started.txt b/docs/plans/receipts/gcp-iam-probe-propagation-2026-09-29/gcp-iam-bootstrap-20260929-probe-authority-2-started.txt new file mode 100644 index 00000000000..b286fc75b3e --- /dev/null +++ b/docs/plans/receipts/gcp-iam-probe-propagation-2026-09-29/gcp-iam-bootstrap-20260929-probe-authority-2-started.txt @@ -0,0 +1 @@ +operator bootstrap started; not a convergence receipt diff --git a/docs/plans/receipts/gcp-iam-probe-propagation-2026-09-29/gcp-iam-probe-probe-20260929-2-iam-converge@gunbai-secrets.iam.gserviceaccount.com-intent.txt b/docs/plans/receipts/gcp-iam-probe-propagation-2026-09-29/gcp-iam-probe-probe-20260929-2-iam-converge@gunbai-secrets.iam.gserviceaccount.com-intent.txt new file mode 100644 index 00000000000..8157e104a58 --- /dev/null +++ b/docs/plans/receipts/gcp-iam-probe-propagation-2026-09-29/gcp-iam-probe-probe-20260929-2-iam-converge@gunbai-secrets.iam.gserviceaccount.com-intent.txt @@ -0,0 +1 @@ +projects/gunbai-secrets/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com;role:57:projects/gunbai-secrets/roles/gunbcIamBootstrapProbeToken;member:18:user:brian@gunb.ai;condition-title:38:gunbc-bootstrap-probe-probe-20260929-2;condition-expression:48:request.time < timestamp('2026-09-29T01:15:00Z');condition-description:83:Temporary exact-account bootstrap readback; physical cleanup required after expiry.; \ No newline at end of file diff --git a/docs/plans/receipts/gcp-iam-probe-propagation-2026-09-29/gcp-iam-probe-probe-20260929-2-iam-converge@gunbai-secrets.iam.gserviceaccount.com-publication-closed.txt b/docs/plans/receipts/gcp-iam-probe-propagation-2026-09-29/gcp-iam-probe-probe-20260929-2-iam-converge@gunbai-secrets.iam.gserviceaccount.com-publication-closed.txt new file mode 100644 index 00000000000..8157e104a58 --- /dev/null +++ b/docs/plans/receipts/gcp-iam-probe-propagation-2026-09-29/gcp-iam-probe-probe-20260929-2-iam-converge@gunbai-secrets.iam.gserviceaccount.com-publication-closed.txt @@ -0,0 +1 @@ +projects/gunbai-secrets/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com;role:57:projects/gunbai-secrets/roles/gunbcIamBootstrapProbeToken;member:18:user:brian@gunb.ai;condition-title:38:gunbc-bootstrap-probe-probe-20260929-2;condition-expression:48:request.time < timestamp('2026-09-29T01:15:00Z');condition-description:83:Temporary exact-account bootstrap readback; physical cleanup required after expiry.; \ No newline at end of file diff --git a/docs/plans/receipts/gcp-iam-probe-propagation-2026-09-29/gcp-iam-probe-probe-20260929-2-iam-converge@gunbai-secrets.iam.gserviceaccount.com-publication-elected.txt b/docs/plans/receipts/gcp-iam-probe-propagation-2026-09-29/gcp-iam-probe-probe-20260929-2-iam-converge@gunbai-secrets.iam.gserviceaccount.com-publication-elected.txt new file mode 100644 index 00000000000..8157e104a58 --- /dev/null +++ b/docs/plans/receipts/gcp-iam-probe-propagation-2026-09-29/gcp-iam-probe-probe-20260929-2-iam-converge@gunbai-secrets.iam.gserviceaccount.com-publication-elected.txt @@ -0,0 +1 @@ +projects/gunbai-secrets/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com;role:57:projects/gunbai-secrets/roles/gunbcIamBootstrapProbeToken;member:18:user:brian@gunb.ai;condition-title:38:gunbc-bootstrap-probe-probe-20260929-2;condition-expression:48:request.time < timestamp('2026-09-29T01:15:00Z');condition-description:83:Temporary exact-account bootstrap readback; physical cleanup required after expiry.; \ No newline at end of file diff --git a/docs/plans/receipts/gcp-iam-probe-propagation-2026-09-29/gcp-iam-probe-probe-20260929-2-iam-converge@gunbai-secrets.iam.gserviceaccount.com-retired.txt b/docs/plans/receipts/gcp-iam-probe-propagation-2026-09-29/gcp-iam-probe-probe-20260929-2-iam-converge@gunbai-secrets.iam.gserviceaccount.com-retired.txt new file mode 100644 index 00000000000..8157e104a58 --- /dev/null +++ b/docs/plans/receipts/gcp-iam-probe-propagation-2026-09-29/gcp-iam-probe-probe-20260929-2-iam-converge@gunbai-secrets.iam.gserviceaccount.com-retired.txt @@ -0,0 +1 @@ +projects/gunbai-secrets/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com;role:57:projects/gunbai-secrets/roles/gunbcIamBootstrapProbeToken;member:18:user:brian@gunb.ai;condition-title:38:gunbc-bootstrap-probe-probe-20260929-2;condition-expression:48:request.time < timestamp('2026-09-29T01:15:00Z');condition-description:83:Temporary exact-account bootstrap readback; physical cleanup required after expiry.; \ No newline at end of file diff --git a/docs/plans/receipts/gcp-iam-probe-propagation-2026-09-29/gcp-iam-probe-probe-20260929-2-iam-observe@gunbai-secrets.iam.gserviceaccount.com-intent.txt b/docs/plans/receipts/gcp-iam-probe-propagation-2026-09-29/gcp-iam-probe-probe-20260929-2-iam-observe@gunbai-secrets.iam.gserviceaccount.com-intent.txt new file mode 100644 index 00000000000..d91803eb262 --- /dev/null +++ b/docs/plans/receipts/gcp-iam-probe-propagation-2026-09-29/gcp-iam-probe-probe-20260929-2-iam-observe@gunbai-secrets.iam.gserviceaccount.com-intent.txt @@ -0,0 +1 @@ +projects/gunbai-secrets/serviceAccounts/iam-observe@gunbai-secrets.iam.gserviceaccount.com;role:57:projects/gunbai-secrets/roles/gunbcIamBootstrapProbeToken;member:18:user:brian@gunb.ai;condition-title:38:gunbc-bootstrap-probe-probe-20260929-2;condition-expression:48:request.time < timestamp('2026-09-29T01:15:00Z');condition-description:83:Temporary exact-account bootstrap readback; physical cleanup required after expiry.; \ No newline at end of file diff --git a/docs/plans/receipts/gcp-iam-probe-propagation-2026-09-29/gcp-iam-probe-probe-20260929-2-iam-observe@gunbai-secrets.iam.gserviceaccount.com-publication-closed.txt b/docs/plans/receipts/gcp-iam-probe-propagation-2026-09-29/gcp-iam-probe-probe-20260929-2-iam-observe@gunbai-secrets.iam.gserviceaccount.com-publication-closed.txt new file mode 100644 index 00000000000..d91803eb262 --- /dev/null +++ b/docs/plans/receipts/gcp-iam-probe-propagation-2026-09-29/gcp-iam-probe-probe-20260929-2-iam-observe@gunbai-secrets.iam.gserviceaccount.com-publication-closed.txt @@ -0,0 +1 @@ +projects/gunbai-secrets/serviceAccounts/iam-observe@gunbai-secrets.iam.gserviceaccount.com;role:57:projects/gunbai-secrets/roles/gunbcIamBootstrapProbeToken;member:18:user:brian@gunb.ai;condition-title:38:gunbc-bootstrap-probe-probe-20260929-2;condition-expression:48:request.time < timestamp('2026-09-29T01:15:00Z');condition-description:83:Temporary exact-account bootstrap readback; physical cleanup required after expiry.; \ No newline at end of file diff --git a/docs/plans/receipts/gcp-iam-probe-propagation-2026-09-29/gcp-iam-probe-probe-20260929-2-iam-observe@gunbai-secrets.iam.gserviceaccount.com-publication-elected.txt b/docs/plans/receipts/gcp-iam-probe-propagation-2026-09-29/gcp-iam-probe-probe-20260929-2-iam-observe@gunbai-secrets.iam.gserviceaccount.com-publication-elected.txt new file mode 100644 index 00000000000..d91803eb262 --- /dev/null +++ b/docs/plans/receipts/gcp-iam-probe-propagation-2026-09-29/gcp-iam-probe-probe-20260929-2-iam-observe@gunbai-secrets.iam.gserviceaccount.com-publication-elected.txt @@ -0,0 +1 @@ +projects/gunbai-secrets/serviceAccounts/iam-observe@gunbai-secrets.iam.gserviceaccount.com;role:57:projects/gunbai-secrets/roles/gunbcIamBootstrapProbeToken;member:18:user:brian@gunb.ai;condition-title:38:gunbc-bootstrap-probe-probe-20260929-2;condition-expression:48:request.time < timestamp('2026-09-29T01:15:00Z');condition-description:83:Temporary exact-account bootstrap readback; physical cleanup required after expiry.; \ No newline at end of file diff --git a/docs/plans/receipts/gcp-iam-probe-propagation-2026-09-29/gcp-iam-probe-probe-20260929-2-iam-observe@gunbai-secrets.iam.gserviceaccount.com-retired.txt b/docs/plans/receipts/gcp-iam-probe-propagation-2026-09-29/gcp-iam-probe-probe-20260929-2-iam-observe@gunbai-secrets.iam.gserviceaccount.com-retired.txt new file mode 100644 index 00000000000..d91803eb262 --- /dev/null +++ b/docs/plans/receipts/gcp-iam-probe-propagation-2026-09-29/gcp-iam-probe-probe-20260929-2-iam-observe@gunbai-secrets.iam.gserviceaccount.com-retired.txt @@ -0,0 +1 @@ +projects/gunbai-secrets/serviceAccounts/iam-observe@gunbai-secrets.iam.gserviceaccount.com;role:57:projects/gunbai-secrets/roles/gunbcIamBootstrapProbeToken;member:18:user:brian@gunb.ai;condition-title:38:gunbc-bootstrap-probe-probe-20260929-2;condition-expression:48:request.time < timestamp('2026-09-29T01:15:00Z');condition-description:83:Temporary exact-account bootstrap readback; physical cleanup required after expiry.; \ No newline at end of file diff --git a/docs/plans/receipts/gcp-iam-probe-propagation-2026-09-29/receipt.json b/docs/plans/receipts/gcp-iam-probe-propagation-2026-09-29/receipt.json new file mode 100644 index 00000000000..3583953c60c --- /dev/null +++ b/docs/plans/receipts/gcp-iam-probe-propagation-2026-09-29/receipt.json @@ -0,0 +1,9 @@ +{ + "source_revision": "1630eddc7", + "mint_attempts": 17, + "token_mint": "succeeded", + "positive_probe": "refused SERVICE_DISABLED cloudresourcemanager.googleapis.com for project 582015116396", + "temporary_grants": "both removed and absence read back", + "workflow_trust": "withheld", + "credential_file": "removed" +} diff --git a/docs/plans/receipts/gcp-iam-services-2026-09-29/README.md b/docs/plans/receipts/gcp-iam-services-2026-09-29/README.md new file mode 100644 index 00000000000..4bcaa3f7825 --- /dev/null +++ b/docs/plans/receipts/gcp-iam-services-2026-09-29/README.md @@ -0,0 +1,43 @@ +# Successful GCP IAM bootstrap, 2026-09-29 + +At source `85bdb4e19`, all ten modeled bootstrap stages completed with exit 0. +Cloud Resource Manager was enabled and independently read back on the exact +project; the other required APIs were already enabled. Resource-specific grants, +positive and negative permission probes, and the observer estate read succeeded. +The observer could read the pinned approval-submission credential; the writer +received exact HTTP 403 reading that same known-readable credential. + +Both temporary exact-account probe grants were removed and absence verified +before the final observer/apply workload-trust bindings were installed/read back. +The earlier temporary organization Deny Admin grant was also verified absent. +Issued probe tokens retain their original 600-second lifetime; grant removal is +not revocation of already issued tokens. The operator credential file was removed. + +18 focused controls passed over 844 byte-identical modules under 6 GiB/no swap. +The full landing suite was not run. This is bootstrap commissioning, not a VM +allocation or a completed approval-workflow acceptance run. + +Workflow acceptance was separately dispatched on main as run `36505895733`, +source `15b977d417c1a7de52eae96f48bcd3955fb1f94c`, mode `gcp_iam_converge`, host +`srv1`. That run uses its own GitHub OIDC credentials, not the supplied operator +token. Its success and approval delivery must be established independently. + +The acceptance run subsequently completed its release build and its observer +GitHub OIDC authentication step successfully. The plan/file/wait step remains +in progress; approval delivery, apply authentication, and apply/readback have +not yet been established. + +Attempt 1 subsequently passed request approval and writer GitHub OIDC +impersonation. Apply accepted a Namecheap provider creation, then refused because +the created subject was not yet visible on immediate readback. No later effects +were applied. This is a partial cloud change, not a successful estate convergence. + +Attempt 2 reruns the failed job on the same source, with a fresh run-attempt +identity and a freshly observed plan. It has passed approval and writer OIDC +impersonation and is executing apply/readback. No administrator token is involved. + +Attempt 2 completed successfully. The existing approval app admitted the fresh +request; the apply identity was minted only afterward, and the existing workflow +completed its approved effects and readback. Run: https://github.com/gunb-ai/gunbc/actions/runs/36505895733/attempts/2 . +This closes the ordinary GCP IAM workflow acceptance case, not host credential +custody, protected storage commissioning, or VM acceptance. diff --git a/docs/plans/receipts/gcp-iam-services-2026-09-29/apply.log b/docs/plans/receipts/gcp-iam-services-2026-09-29/apply.log new file mode 100644 index 00000000000..2bd54d5da0f --- /dev/null +++ b/docs/plans/receipts/gcp-iam-services-2026-09-29/apply.log @@ -0,0 +1,410 @@ +[workspace-root] discovered /home/briansrls/scratch-wt/gcp-iam-bootstrap +✓ typecheck v2.std.compilers.target_model done in 23 seconds +✓ typecheck v2.compiler.target_serialize done in 2 seconds +✓ typecheck v2.compiler.translate done in 3 seconds +[file] read +[file] write_create_new target/gcp-iam-bootstrap-20260929-services-and-probes-1-started.txt (54 bytes) +[rest] GET https://serviceusage.googleapis.com/v1/projects/gunbai-secrets/services/iam.googleapis.com +[rest] GET https://serviceusage.googleapis.com/v1/projects/gunbai-secrets/services/iamcredentials.googleapis.com +[rest] GET https://serviceusage.googleapis.com/v1/projects/gunbai-secrets/services/cloudresourcemanager.googleapis.com +[rest] POST https://serviceusage.googleapis.com/v1/projects/gunbai-secrets/services:batchEnable +[rest] GET https://serviceusage.googleapis.com/v1/projects/gunbai-secrets/services/cloudresourcemanager.googleapis.com +[rest] GET https://serviceusage.googleapis.com/v1/projects/gunbai-secrets/services/secretmanager.googleapis.com +[rest] GET https://serviceusage.googleapis.com/v1/projects/gunbai-secrets/services/sts.googleapis.com +[file] write_create_new target/gcp-iam-bootstrap-20260929-services-and-probes-1-services.txt (78 bytes) +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-gcp-iam-converge +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-gcp-iam-converge/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/iam-observe@gunbai-secrets.iam.gserviceaccount.com +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-gcp-iam-converge +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-gcp-iam-converge/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/iam-observe@gunbai-secrets.iam.gserviceaccount.com +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-gcp-iam-converge +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-gcp-iam-converge/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-gcp-iam-converge +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-gcp-iam-converge/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/heal-publisher@gunbai-secrets.iam.gserviceaccount.com +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/heal-publisher@gunbai-secrets.iam.gserviceaccount.com +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher-private +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher-private/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/heal-publisher-private@gunbai-secrets.iam.gserviceaccount.com +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher-private +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher-private/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/heal-publisher-private@gunbai-secrets.iam.gserviceaccount.com +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-mtcollins1-boot +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-mtcollins1-boot/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/mtcollins1-boot@gunbai-secrets.iam.gserviceaccount.com +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-mtcollins1-boot +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-mtcollins1-boot/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/mtcollins1-boot@gunbai-secrets.iam.gserviceaccount.com +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-namecheap-dns +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-namecheap-dns/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/namecheap-dns@gunbai-secrets.iam.gserviceaccount.com +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-namecheap-dns +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-namecheap-dns/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/namecheap-dns@gunbai-secrets.iam.gserviceaccount.com +[file] write_create_new target/gcp-iam-bootstrap-20260929-services-and-probes-1-identities.txt (78 bytes) +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/roles/gunbcIamConvergeCreate +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/roles/gunbcIamConvergeProvider +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/roles/gunbcIamConvergeAccountPolicy +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/roles/gunbcIamConvergeSecretPolicy +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/roles/gunbcIamObservePool +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/roles/gunbcIamObserveAccount +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/roles/gunbcIamObserveSecret +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/roles/gunbcIamBootstrapProbeToken +[file] write_create_new target/gcp-iam-bootstrap-20260929-services-and-probes-1-roles.txt (78 bytes) +[rest] GET https://iam.googleapis.com/v2/policies/cloudresourcemanager.googleapis.com%2Fprojects%2F582015116396/denypolicies/gunbc-iam-converge-boundary +[file] write_create_new target/gcp-iam-bootstrap-20260929-services-and-probes-1-deny.txt (78 bytes) +[file] list target +[file] read target/gcp-iam-authority-deny-bootstrap-org-20260928-1-intent.txt +[file] list target +[file] read target/gcp-iam-authority-deny-bootstrap-org-20260928-1-publication-elected.txt +[file] list target +[file] read target/gcp-iam-authority-deny-bootstrap-org-20260928-1-publication-closed.txt +[file] list target +[file] read target/gcp-iam-authority-deny-bootstrap-org-20260928-1-retired.txt +[rest] POST https://cloudresourcemanager.googleapis.com/v3/organizations/266638272282:getIamPolicy +◐ started Clock.Now +✓ Clock.Now done in 2ms +[file] write_create_new target/gcp-iam-authority-deny-bootstrap-org-20260928-1-retired.txt (397 bytes) +[file] list target +[file] read target/gcp-iam-authority-deny-bootstrap-org-20260928-1-retired.txt +[file] write_create_new target/gcp-iam-bootstrap-20260929-services-and-probes-1-authority-cleanup.txt (78 bytes) +[rest] POST https://cloudresourcemanager.googleapis.com/v1/projects/gunbai-secrets:getIamPolicy +[file] list target +[file] read target/gcp-iam-pool-initialization-github-heal-publisher-elected.txt +[file] list target +[file] read target/gcp-iam-pool-initialization-github-heal-publisher-closed.txt +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher:getIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher:getIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/heal-publisher@gunbai-secrets.iam.gserviceaccount.com:getIamPolicy +[rest] GET https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/agent-github-app-private-key:getIamPolicy +[file] list target +[file] read target/gcp-iam-pool-initialization-github-heal-publisher-private-elected.txt +[file] list target +[file] read target/gcp-iam-pool-initialization-github-heal-publisher-private-closed.txt +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher-private:getIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher-private:getIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/heal-publisher-private@gunbai-secrets.iam.gserviceaccount.com:getIamPolicy +[file] list target +[file] read target/gcp-iam-pool-initialization-github-mtcollins1-boot-elected.txt +[file] list target +[file] read target/gcp-iam-pool-initialization-github-mtcollins1-boot-closed.txt +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-mtcollins1-boot:getIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-mtcollins1-boot:getIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/mtcollins1-boot@gunbai-secrets.iam.gserviceaccount.com:getIamPolicy +[rest] GET https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/bmc-mtcollins1-gunbc:getIamPolicy +[rest] GET https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/fleet-automation-ssh-key:getIamPolicy +[file] list target +[file] read target/gcp-iam-pool-initialization-github-namecheap-dns-elected.txt +[file] list target +[file] read target/gcp-iam-pool-initialization-github-namecheap-dns-closed.txt +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-namecheap-dns:getIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-namecheap-dns:getIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/namecheap-dns@gunbai-secrets.iam.gserviceaccount.com:getIamPolicy +[rest] GET https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/namecheap-api-key:getIamPolicy +[file] list target +[file] read target/gcp-iam-pool-initialization-github-heal-publisher-elected.txt +[file] list target +[file] read target/gcp-iam-pool-initialization-github-heal-publisher-closed.txt +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher:getIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher:getIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/heal-publisher@gunbai-secrets.iam.gserviceaccount.com:getIamPolicy +[rest] GET https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/agent-github-app-private-key:getIamPolicy +[file] list target +[file] read target/gcp-iam-pool-initialization-github-heal-publisher-private-elected.txt +[file] list target +[file] read target/gcp-iam-pool-initialization-github-heal-publisher-private-closed.txt +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher-private:getIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher-private:getIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/heal-publisher-private@gunbai-secrets.iam.gserviceaccount.com:getIamPolicy +[file] list target +[file] read target/gcp-iam-pool-initialization-github-mtcollins1-boot-elected.txt +[file] list target +[file] read target/gcp-iam-pool-initialization-github-mtcollins1-boot-closed.txt +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-mtcollins1-boot:getIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-mtcollins1-boot:getIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/mtcollins1-boot@gunbai-secrets.iam.gserviceaccount.com:getIamPolicy +[rest] GET https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/bmc-mtcollins1-gunbc:getIamPolicy +[rest] GET https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/fleet-automation-ssh-key:getIamPolicy +[file] list target +[file] read target/gcp-iam-pool-initialization-github-namecheap-dns-elected.txt +[file] list target +[file] read target/gcp-iam-pool-initialization-github-namecheap-dns-closed.txt +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-namecheap-dns:getIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-namecheap-dns:getIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/namecheap-dns@gunbai-secrets.iam.gserviceaccount.com:getIamPolicy +[rest] GET https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/namecheap-api-key:getIamPolicy +[file] list target +[file] read target/gcp-iam-pool-initialization-github-gcp-iam-converge-elected.txt +[file] list target +[file] read target/gcp-iam-pool-initialization-github-gcp-iam-converge-closed.txt +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-gcp-iam-converge:getIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-gcp-iam-converge:getIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/iam-observe@gunbai-secrets.iam.gserviceaccount.com:getIamPolicy +[rest] GET https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/approval-submission-mac-key:getIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:getIamPolicy +[rest] GET https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/approval-submission-mac-key:getIamPolicy +[file] write_create_new target/gcp-iam-bootstrap-20260929-services-and-probes-1-capabilities.txt (78 bytes) +[rest] GET https://openidconnect.googleapis.com/v1/userinfo +[file] list target +[file] read target/gcp-iam-probe-probe-20260929-3-iam-converge@gunbai-secrets.iam.gserviceaccount.com-intent.txt +[file] list target +[file] read target/gcp-iam-probe-probe-20260929-3-iam-converge@gunbai-secrets.iam.gserviceaccount.com-publication-elected.txt +[file] list target +[file] read target/gcp-iam-probe-probe-20260929-3-iam-converge@gunbai-secrets.iam.gserviceaccount.com-publication-closed.txt +[file] list target +[file] read target/gcp-iam-probe-probe-20260929-3-iam-converge@gunbai-secrets.iam.gserviceaccount.com-retired.txt +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:getIamPolicy +[file] write_create_new target/gcp-iam-probe-probe-20260929-3-iam-converge@gunbai-secrets.iam.gserviceaccount.com-intent.txt (427 bytes) +◐ started Clock.Now +✓ Clock.Now done in 2ms +[file] write_create_new target/gcp-iam-probe-probe-20260929-3-iam-converge@gunbai-secrets.iam.gserviceaccount.com-publication-elected.txt (427 bytes) +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:setIamPolicy +[file] write_create_new target/gcp-iam-probe-probe-20260929-3-iam-converge@gunbai-secrets.iam.gserviceaccount.com-publication-closed.txt (427 bytes) +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:getIamPolicy +[file] write_create_new target/gcp-iam-probe-probe-20260929-3-iam-converge@gunbai-secrets.iam.gserviceaccount.com-publication-closed.txt (427 bytes) +[file] list target +[file] read target/gcp-iam-probe-probe-20260929-3-iam-converge@gunbai-secrets.iam.gserviceaccount.com-publication-closed.txt +[file] list target +[file] read target/gcp-iam-probe-probe-20260929-3-iam-observe@gunbai-secrets.iam.gserviceaccount.com-intent.txt +[file] list target +[file] read target/gcp-iam-probe-probe-20260929-3-iam-observe@gunbai-secrets.iam.gserviceaccount.com-publication-elected.txt +[file] list target +[file] read target/gcp-iam-probe-probe-20260929-3-iam-observe@gunbai-secrets.iam.gserviceaccount.com-publication-closed.txt +[file] list target +[file] read target/gcp-iam-probe-probe-20260929-3-iam-observe@gunbai-secrets.iam.gserviceaccount.com-retired.txt +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/iam-observe@gunbai-secrets.iam.gserviceaccount.com:getIamPolicy +[file] write_create_new target/gcp-iam-probe-probe-20260929-3-iam-observe@gunbai-secrets.iam.gserviceaccount.com-intent.txt (426 bytes) +◐ started Clock.Now +✓ Clock.Now done in 2ms +[file] write_create_new target/gcp-iam-probe-probe-20260929-3-iam-observe@gunbai-secrets.iam.gserviceaccount.com-publication-elected.txt (426 bytes) +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/iam-observe@gunbai-secrets.iam.gserviceaccount.com:setIamPolicy +[file] write_create_new target/gcp-iam-probe-probe-20260929-3-iam-observe@gunbai-secrets.iam.gserviceaccount.com-publication-closed.txt (426 bytes) +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/iam-observe@gunbai-secrets.iam.gserviceaccount.com:getIamPolicy +[file] write_create_new target/gcp-iam-probe-probe-20260929-3-iam-observe@gunbai-secrets.iam.gserviceaccount.com-publication-closed.txt (426 bytes) +[file] list target +[file] read target/gcp-iam-probe-probe-20260929-3-iam-observe@gunbai-secrets.iam.gserviceaccount.com-publication-closed.txt +[file] write_create_new target/gcp-iam-bootstrap-20260929-services-and-probes-1-probe-authority.txt (78 bytes) +[rest] POST https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:generateAccessToken +◐ started Clock.Now +✓ Clock.Now done in 2ms +◐ started sleep.Delay.Seconds +✓ sleep.Delay.Seconds done in 5 seconds +[rest] POST https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:generateAccessToken +◐ started Clock.Now +✓ Clock.Now done in 2ms +◐ started sleep.Delay.Seconds +✓ sleep.Delay.Seconds done in 5 seconds +[rest] POST https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:generateAccessToken +◐ started Clock.Now +✓ Clock.Now done in 1ms +◐ started sleep.Delay.Seconds +✓ sleep.Delay.Seconds done in 5 seconds +[rest] POST https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:generateAccessToken +◐ started Clock.Now +✓ Clock.Now done in 2ms +◐ started sleep.Delay.Seconds +✓ sleep.Delay.Seconds done in 5 seconds +[rest] POST https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:generateAccessToken +◐ started Clock.Now +✓ Clock.Now done in 2ms +◐ started sleep.Delay.Seconds +✓ sleep.Delay.Seconds done in 5 seconds +[rest] POST https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:generateAccessToken +◐ started Clock.Now +✓ Clock.Now done in 2ms +◐ started sleep.Delay.Seconds +✓ sleep.Delay.Seconds done in 5 seconds +[rest] POST https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:generateAccessToken +◐ started Clock.Now +✓ Clock.Now done in 2ms +◐ started sleep.Delay.Seconds +✓ sleep.Delay.Seconds done in 5 seconds +[rest] POST https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:generateAccessToken +◐ started Clock.Now +✓ Clock.Now done in 2ms +◐ started sleep.Delay.Seconds +✓ sleep.Delay.Seconds done in 5 seconds +[rest] POST https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:generateAccessToken +◐ started Clock.Now +✓ Clock.Now done in 2ms +◐ started sleep.Delay.Seconds +✓ sleep.Delay.Seconds done in 5 seconds +[rest] POST https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:generateAccessToken +◐ started Clock.Now +✓ Clock.Now done in 2ms +◐ started sleep.Delay.Seconds +✓ sleep.Delay.Seconds done in 5 seconds +[rest] POST https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:generateAccessToken +◐ started Clock.Now +✓ Clock.Now done in 2ms +◐ started sleep.Delay.Seconds +✓ sleep.Delay.Seconds done in 5 seconds +[rest] POST https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:generateAccessToken +◐ started Clock.Now +✓ Clock.Now done in 2ms +◐ started sleep.Delay.Seconds +✓ sleep.Delay.Seconds done in 5 seconds +[rest] POST https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:generateAccessToken +◐ started Clock.Now +✓ Clock.Now done in 1ms +◐ started sleep.Delay.Seconds +✓ sleep.Delay.Seconds done in 5 seconds +[rest] POST https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:generateAccessToken +◐ started Clock.Now +✓ Clock.Now done in 2ms +◐ started sleep.Delay.Seconds +✓ sleep.Delay.Seconds done in 5 seconds +[rest] POST https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:generateAccessToken +◐ started Clock.Now +✓ Clock.Now done in 2ms +◐ started sleep.Delay.Seconds +✓ sleep.Delay.Seconds done in 5 seconds +[rest] POST https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:generateAccessToken +◐ started Clock.Now +✓ Clock.Now done in 1ms +◐ started sleep.Delay.Seconds +✓ sleep.Delay.Seconds done in 5 seconds +[rest] POST https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:generateAccessToken +◐ started Clock.Now +✓ Clock.Now done in 2ms +◐ started sleep.Delay.Seconds +✓ sleep.Delay.Seconds done in 5 seconds +[rest] POST https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:generateAccessToken +◐ started Clock.Now +✓ Clock.Now done in 2ms +◐ started sleep.Delay.Seconds +✓ sleep.Delay.Seconds done in 5 seconds +[rest] POST https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:generateAccessToken +[rest] POST https://cloudresourcemanager.googleapis.com/v1/projects/gunbai-secrets:testIamPermissions +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher:testIamPermissions +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/heal-publisher@gunbai-secrets.iam.gserviceaccount.com:testIamPermissions +[rest] POST https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/agent-github-app-private-key:testIamPermissions +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher-private:testIamPermissions +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/heal-publisher-private@gunbai-secrets.iam.gserviceaccount.com:testIamPermissions +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-mtcollins1-boot:testIamPermissions +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/mtcollins1-boot@gunbai-secrets.iam.gserviceaccount.com:testIamPermissions +[rest] POST https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/bmc-mtcollins1-gunbc:testIamPermissions +[rest] POST https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/fleet-automation-ssh-key:testIamPermissions +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-namecheap-dns:testIamPermissions +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/namecheap-dns@gunbai-secrets.iam.gserviceaccount.com:testIamPermissions +[rest] POST https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/namecheap-api-key:testIamPermissions +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/iam-observe@gunbai-secrets.iam.gserviceaccount.com:testIamPermissions +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:testIamPermissions +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-gcp-iam-converge:testIamPermissions +[rest] POST https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/iam-observe@gunbai-secrets.iam.gserviceaccount.com:generateAccessToken +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/heal-publisher@gunbai-secrets.iam.gserviceaccount.com +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/heal-publisher@gunbai-secrets.iam.gserviceaccount.com:getIamPolicy +[rest] GET https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/agent-github-app-private-key:getIamPolicy +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher-private +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-heal-publisher-private/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/heal-publisher-private@gunbai-secrets.iam.gserviceaccount.com +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/heal-publisher-private@gunbai-secrets.iam.gserviceaccount.com:getIamPolicy +[rest] GET https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/agent-github-app-private-key:getIamPolicy +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-mtcollins1-boot +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-mtcollins1-boot/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/mtcollins1-boot@gunbai-secrets.iam.gserviceaccount.com +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/mtcollins1-boot@gunbai-secrets.iam.gserviceaccount.com:getIamPolicy +[rest] GET https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/bmc-mtcollins1-gunbc:getIamPolicy +[rest] GET https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/fleet-automation-ssh-key:getIamPolicy +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-namecheap-dns +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/locations/global/workloadIdentityPools/github-namecheap-dns/providers +[rest] GET https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/namecheap-dns@gunbai-secrets.iam.gserviceaccount.com +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/namecheap-dns@gunbai-secrets.iam.gserviceaccount.com:getIamPolicy +[rest] GET https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/namecheap-api-key:getIamPolicy +[rest] GET https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/approval-submission-mac-key/versions/1:access +[rest] GET https://secretmanager.googleapis.com/v1/projects/gunbai-secrets/secrets/approval-submission-mac-key/versions/1:access +[file] write_create_new target/gcp-iam-bootstrap-20260929-services-and-probes-1-privilege-readback.txt (78 bytes) +[file] list target +[file] read target/gcp-iam-probe-probe-20260929-3-iam-converge@gunbai-secrets.iam.gserviceaccount.com-intent.txt +[file] list target +[file] read target/gcp-iam-probe-probe-20260929-3-iam-converge@gunbai-secrets.iam.gserviceaccount.com-publication-elected.txt +[file] list target +[file] read target/gcp-iam-probe-probe-20260929-3-iam-converge@gunbai-secrets.iam.gserviceaccount.com-publication-closed.txt +[file] list target +[file] read target/gcp-iam-probe-probe-20260929-3-iam-converge@gunbai-secrets.iam.gserviceaccount.com-retired.txt +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:getIamPolicy +◐ started Clock.Now +✓ Clock.Now done in 2ms +[file] write_create_new target/gcp-iam-probe-probe-20260929-3-iam-converge@gunbai-secrets.iam.gserviceaccount.com-publication-closed.txt (427 bytes) +[file] list target +[file] read target/gcp-iam-probe-probe-20260929-3-iam-converge@gunbai-secrets.iam.gserviceaccount.com-publication-closed.txt +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:setIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:getIamPolicy +[file] write_create_new target/gcp-iam-probe-probe-20260929-3-iam-converge@gunbai-secrets.iam.gserviceaccount.com-retired.txt (427 bytes) +[file] list target +[file] read target/gcp-iam-probe-probe-20260929-3-iam-observe@gunbai-secrets.iam.gserviceaccount.com-intent.txt +[file] list target +[file] read target/gcp-iam-probe-probe-20260929-3-iam-observe@gunbai-secrets.iam.gserviceaccount.com-publication-elected.txt +[file] list target +[file] read target/gcp-iam-probe-probe-20260929-3-iam-observe@gunbai-secrets.iam.gserviceaccount.com-publication-closed.txt +[file] list target +[file] read target/gcp-iam-probe-probe-20260929-3-iam-observe@gunbai-secrets.iam.gserviceaccount.com-retired.txt +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/iam-observe@gunbai-secrets.iam.gserviceaccount.com:getIamPolicy +◐ started Clock.Now +✓ Clock.Now done in 2ms +[file] write_create_new target/gcp-iam-probe-probe-20260929-3-iam-observe@gunbai-secrets.iam.gserviceaccount.com-publication-closed.txt (426 bytes) +[file] list target +[file] read target/gcp-iam-probe-probe-20260929-3-iam-observe@gunbai-secrets.iam.gserviceaccount.com-publication-closed.txt +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/iam-observe@gunbai-secrets.iam.gserviceaccount.com:setIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/iam-observe@gunbai-secrets.iam.gserviceaccount.com:getIamPolicy +[file] write_create_new target/gcp-iam-probe-probe-20260929-3-iam-observe@gunbai-secrets.iam.gserviceaccount.com-retired.txt (426 bytes) +[file] write_create_new target/gcp-iam-bootstrap-20260929-services-and-probes-1-probe-cleanup.txt (78 bytes) +[rest] GET https://iam.googleapis.com/v2/policies/cloudresourcemanager.googleapis.com%2Fprojects%2F582015116396/denypolicies/gunbc-iam-converge-boundary +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/iam-observe@gunbai-secrets.iam.gserviceaccount.com:getIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/iam-observe@gunbai-secrets.iam.gserviceaccount.com:setIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/iam-observe@gunbai-secrets.iam.gserviceaccount.com:getIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:getIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:setIamPolicy +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:getIamPolicy +[file] write_create_new target/gcp-iam-bootstrap-20260929-services-and-probes-1-workflow-trust.txt (78 bytes) +[file] list target +[file] read target/gcp-iam-probe-probe-20260929-3-iam-converge@gunbai-secrets.iam.gserviceaccount.com-intent.txt +[file] list target +[file] read target/gcp-iam-probe-probe-20260929-3-iam-converge@gunbai-secrets.iam.gserviceaccount.com-publication-elected.txt +[file] list target +[file] read target/gcp-iam-probe-probe-20260929-3-iam-converge@gunbai-secrets.iam.gserviceaccount.com-publication-closed.txt +[file] list target +[file] read target/gcp-iam-probe-probe-20260929-3-iam-converge@gunbai-secrets.iam.gserviceaccount.com-retired.txt +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com:getIamPolicy +◐ started Clock.Now +✓ Clock.Now done in 2ms +[file] write_create_new target/gcp-iam-probe-probe-20260929-3-iam-converge@gunbai-secrets.iam.gserviceaccount.com-retired.txt (427 bytes) +[file] list target +[file] read target/gcp-iam-probe-probe-20260929-3-iam-converge@gunbai-secrets.iam.gserviceaccount.com-retired.txt +[file] list target +[file] read target/gcp-iam-probe-probe-20260929-3-iam-observe@gunbai-secrets.iam.gserviceaccount.com-intent.txt +[file] list target +[file] read target/gcp-iam-probe-probe-20260929-3-iam-observe@gunbai-secrets.iam.gserviceaccount.com-publication-elected.txt +[file] list target +[file] read target/gcp-iam-probe-probe-20260929-3-iam-observe@gunbai-secrets.iam.gserviceaccount.com-publication-closed.txt +[file] list target +[file] read target/gcp-iam-probe-probe-20260929-3-iam-observe@gunbai-secrets.iam.gserviceaccount.com-retired.txt +[rest] POST https://iam.googleapis.com/v1/projects/gunbai-secrets/serviceAccounts/iam-observe@gunbai-secrets.iam.gserviceaccount.com:getIamPolicy +◐ started Clock.Now +✓ Clock.Now done in 2ms +[file] write_create_new target/gcp-iam-probe-probe-20260929-3-iam-observe@gunbai-secrets.iam.gserviceaccount.com-retired.txt (426 bytes) +[file] list target +[file] read target/gcp-iam-probe-probe-20260929-3-iam-observe@gunbai-secrets.iam.gserviceaccount.com-retired.txt +[file] list target +[file] read target/gcp-iam-authority-deny-bootstrap-org-20260928-1-intent.txt +[file] list target +[file] read target/gcp-iam-authority-deny-bootstrap-org-20260928-1-publication-elected.txt +[file] list target +[file] read target/gcp-iam-authority-deny-bootstrap-org-20260928-1-publication-closed.txt +[file] list target +[file] read target/gcp-iam-authority-deny-bootstrap-org-20260928-1-retired.txt +[rest] POST https://cloudresourcemanager.googleapis.com/v3/organizations/266638272282:getIamPolicy +◐ started Clock.Now +✓ Clock.Now done in 2ms +[file] write_create_new target/gcp-iam-authority-deny-bootstrap-org-20260928-1-retired.txt (397 bytes) +[file] list target +[file] read target/gcp-iam-authority-deny-bootstrap-org-20260928-1-retired.txt diff --git a/docs/plans/receipts/gcp-iam-services-2026-09-29/controls.log b/docs/plans/receipts/gcp-iam-services-2026-09-29/controls.log new file mode 100644 index 00000000000..bcdab55c990 --- /dev/null +++ b/docs/plans/receipts/gcp-iam-services-2026-09-29/controls.log @@ -0,0 +1,22 @@ +[workspace-root] discovered /home/briansrls/scratch-wt/gcp-iam-bootstrap +✓ typecheck v2.std.compilers.target_model done in 23 seconds +✓ typecheck v2.compiler.target_serialize done in 2 seconds +✓ typecheck v2.compiler.translate done in 3 seconds +PASS bootstrap_role_drift_never_authorizes_adoption +PASS deny_exceptions_and_incomplete_readback_withhold_trust +PASS workflow_trust_requires_completed_deny_and_privilege_readback +PASS authority_deadline_is_bounded_and_retry_does_not_renew_it +PASS uncertain_publication_cannot_be_settled_from_an_absent_cell +PASS unknown_policy_and_unowned_grants_never_authorize_changes +PASS authority_retirement_preserves_existing_human_and_foreign_access +PASS bootstrap_authority_requires_an_organization_resource +PASS definite_publication_rejection_is_distinct_from_unknown_commit +PASS deny_propagation_retry_requires_live_authority_and_a_remaining_budget +PASS cleanup_dependency_does_not_wait_for_successful_deny_installation +PASS authority_beneficiary_requires_the_verified_stable_google_subject +PASS empty_pool_policy_does_not_authorize_unconditional_publication +PASS pool_initialization_rejects_existing_unfenced_bindings +PASS pool_initialization_readback_requires_exact_cells_and_a_revision +PASS probe_authority_has_no_signing_or_policy_permission +PASS probe_cleanup_is_required_for_trust_but_not_successful_probes +PASS service_enablement_requires_enabled_state_on_the_exact_project diff --git a/docs/plans/receipts/gcp-iam-services-2026-09-29/gcp-iam-bootstrap-20260929-services-and-probes-1-authority-cleanup.txt b/docs/plans/receipts/gcp-iam-services-2026-09-29/gcp-iam-bootstrap-20260929-services-and-probes-1-authority-cleanup.txt new file mode 100644 index 00000000000..480da8f3ea6 --- /dev/null +++ b/docs/plans/receipts/gcp-iam-services-2026-09-29/gcp-iam-bootstrap-20260929-services-and-probes-1-authority-cleanup.txt @@ -0,0 +1 @@ +stage checks completed; see the stage authority for configuration/probe scope diff --git a/docs/plans/receipts/gcp-iam-services-2026-09-29/gcp-iam-bootstrap-20260929-services-and-probes-1-capabilities.txt b/docs/plans/receipts/gcp-iam-services-2026-09-29/gcp-iam-bootstrap-20260929-services-and-probes-1-capabilities.txt new file mode 100644 index 00000000000..480da8f3ea6 --- /dev/null +++ b/docs/plans/receipts/gcp-iam-services-2026-09-29/gcp-iam-bootstrap-20260929-services-and-probes-1-capabilities.txt @@ -0,0 +1 @@ +stage checks completed; see the stage authority for configuration/probe scope diff --git a/docs/plans/receipts/gcp-iam-services-2026-09-29/gcp-iam-bootstrap-20260929-services-and-probes-1-deny.txt b/docs/plans/receipts/gcp-iam-services-2026-09-29/gcp-iam-bootstrap-20260929-services-and-probes-1-deny.txt new file mode 100644 index 00000000000..480da8f3ea6 --- /dev/null +++ b/docs/plans/receipts/gcp-iam-services-2026-09-29/gcp-iam-bootstrap-20260929-services-and-probes-1-deny.txt @@ -0,0 +1 @@ +stage checks completed; see the stage authority for configuration/probe scope diff --git a/docs/plans/receipts/gcp-iam-services-2026-09-29/gcp-iam-bootstrap-20260929-services-and-probes-1-identities.txt b/docs/plans/receipts/gcp-iam-services-2026-09-29/gcp-iam-bootstrap-20260929-services-and-probes-1-identities.txt new file mode 100644 index 00000000000..480da8f3ea6 --- /dev/null +++ b/docs/plans/receipts/gcp-iam-services-2026-09-29/gcp-iam-bootstrap-20260929-services-and-probes-1-identities.txt @@ -0,0 +1 @@ +stage checks completed; see the stage authority for configuration/probe scope diff --git a/docs/plans/receipts/gcp-iam-services-2026-09-29/gcp-iam-bootstrap-20260929-services-and-probes-1-privilege-readback.txt b/docs/plans/receipts/gcp-iam-services-2026-09-29/gcp-iam-bootstrap-20260929-services-and-probes-1-privilege-readback.txt new file mode 100644 index 00000000000..480da8f3ea6 --- /dev/null +++ b/docs/plans/receipts/gcp-iam-services-2026-09-29/gcp-iam-bootstrap-20260929-services-and-probes-1-privilege-readback.txt @@ -0,0 +1 @@ +stage checks completed; see the stage authority for configuration/probe scope diff --git a/docs/plans/receipts/gcp-iam-services-2026-09-29/gcp-iam-bootstrap-20260929-services-and-probes-1-probe-authority.txt b/docs/plans/receipts/gcp-iam-services-2026-09-29/gcp-iam-bootstrap-20260929-services-and-probes-1-probe-authority.txt new file mode 100644 index 00000000000..480da8f3ea6 --- /dev/null +++ b/docs/plans/receipts/gcp-iam-services-2026-09-29/gcp-iam-bootstrap-20260929-services-and-probes-1-probe-authority.txt @@ -0,0 +1 @@ +stage checks completed; see the stage authority for configuration/probe scope diff --git a/docs/plans/receipts/gcp-iam-services-2026-09-29/gcp-iam-bootstrap-20260929-services-and-probes-1-probe-cleanup.txt b/docs/plans/receipts/gcp-iam-services-2026-09-29/gcp-iam-bootstrap-20260929-services-and-probes-1-probe-cleanup.txt new file mode 100644 index 00000000000..480da8f3ea6 --- /dev/null +++ b/docs/plans/receipts/gcp-iam-services-2026-09-29/gcp-iam-bootstrap-20260929-services-and-probes-1-probe-cleanup.txt @@ -0,0 +1 @@ +stage checks completed; see the stage authority for configuration/probe scope diff --git a/docs/plans/receipts/gcp-iam-services-2026-09-29/gcp-iam-bootstrap-20260929-services-and-probes-1-roles.txt b/docs/plans/receipts/gcp-iam-services-2026-09-29/gcp-iam-bootstrap-20260929-services-and-probes-1-roles.txt new file mode 100644 index 00000000000..480da8f3ea6 --- /dev/null +++ b/docs/plans/receipts/gcp-iam-services-2026-09-29/gcp-iam-bootstrap-20260929-services-and-probes-1-roles.txt @@ -0,0 +1 @@ +stage checks completed; see the stage authority for configuration/probe scope diff --git a/docs/plans/receipts/gcp-iam-services-2026-09-29/gcp-iam-bootstrap-20260929-services-and-probes-1-services.txt b/docs/plans/receipts/gcp-iam-services-2026-09-29/gcp-iam-bootstrap-20260929-services-and-probes-1-services.txt new file mode 100644 index 00000000000..480da8f3ea6 --- /dev/null +++ b/docs/plans/receipts/gcp-iam-services-2026-09-29/gcp-iam-bootstrap-20260929-services-and-probes-1-services.txt @@ -0,0 +1 @@ +stage checks completed; see the stage authority for configuration/probe scope diff --git a/docs/plans/receipts/gcp-iam-services-2026-09-29/gcp-iam-bootstrap-20260929-services-and-probes-1-started.txt b/docs/plans/receipts/gcp-iam-services-2026-09-29/gcp-iam-bootstrap-20260929-services-and-probes-1-started.txt new file mode 100644 index 00000000000..b286fc75b3e --- /dev/null +++ b/docs/plans/receipts/gcp-iam-services-2026-09-29/gcp-iam-bootstrap-20260929-services-and-probes-1-started.txt @@ -0,0 +1 @@ +operator bootstrap started; not a convergence receipt diff --git a/docs/plans/receipts/gcp-iam-services-2026-09-29/gcp-iam-bootstrap-20260929-services-and-probes-1-workflow-trust.txt b/docs/plans/receipts/gcp-iam-services-2026-09-29/gcp-iam-bootstrap-20260929-services-and-probes-1-workflow-trust.txt new file mode 100644 index 00000000000..480da8f3ea6 --- /dev/null +++ b/docs/plans/receipts/gcp-iam-services-2026-09-29/gcp-iam-bootstrap-20260929-services-and-probes-1-workflow-trust.txt @@ -0,0 +1 @@ +stage checks completed; see the stage authority for configuration/probe scope diff --git a/docs/plans/receipts/gcp-iam-services-2026-09-29/gcp-iam-probe-probe-20260929-3-iam-converge@gunbai-secrets.iam.gserviceaccount.com-intent.txt b/docs/plans/receipts/gcp-iam-services-2026-09-29/gcp-iam-probe-probe-20260929-3-iam-converge@gunbai-secrets.iam.gserviceaccount.com-intent.txt new file mode 100644 index 00000000000..dc3646c20d9 --- /dev/null +++ b/docs/plans/receipts/gcp-iam-services-2026-09-29/gcp-iam-probe-probe-20260929-3-iam-converge@gunbai-secrets.iam.gserviceaccount.com-intent.txt @@ -0,0 +1 @@ +projects/gunbai-secrets/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com;role:57:projects/gunbai-secrets/roles/gunbcIamBootstrapProbeToken;member:18:user:brian@gunb.ai;condition-title:38:gunbc-bootstrap-probe-probe-20260929-3;condition-expression:48:request.time < timestamp('2026-09-29T01:20:00Z');condition-description:83:Temporary exact-account bootstrap readback; physical cleanup required after expiry.; \ No newline at end of file diff --git a/docs/plans/receipts/gcp-iam-services-2026-09-29/gcp-iam-probe-probe-20260929-3-iam-converge@gunbai-secrets.iam.gserviceaccount.com-publication-closed.txt b/docs/plans/receipts/gcp-iam-services-2026-09-29/gcp-iam-probe-probe-20260929-3-iam-converge@gunbai-secrets.iam.gserviceaccount.com-publication-closed.txt new file mode 100644 index 00000000000..dc3646c20d9 --- /dev/null +++ b/docs/plans/receipts/gcp-iam-services-2026-09-29/gcp-iam-probe-probe-20260929-3-iam-converge@gunbai-secrets.iam.gserviceaccount.com-publication-closed.txt @@ -0,0 +1 @@ +projects/gunbai-secrets/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com;role:57:projects/gunbai-secrets/roles/gunbcIamBootstrapProbeToken;member:18:user:brian@gunb.ai;condition-title:38:gunbc-bootstrap-probe-probe-20260929-3;condition-expression:48:request.time < timestamp('2026-09-29T01:20:00Z');condition-description:83:Temporary exact-account bootstrap readback; physical cleanup required after expiry.; \ No newline at end of file diff --git a/docs/plans/receipts/gcp-iam-services-2026-09-29/gcp-iam-probe-probe-20260929-3-iam-converge@gunbai-secrets.iam.gserviceaccount.com-publication-elected.txt b/docs/plans/receipts/gcp-iam-services-2026-09-29/gcp-iam-probe-probe-20260929-3-iam-converge@gunbai-secrets.iam.gserviceaccount.com-publication-elected.txt new file mode 100644 index 00000000000..dc3646c20d9 --- /dev/null +++ b/docs/plans/receipts/gcp-iam-services-2026-09-29/gcp-iam-probe-probe-20260929-3-iam-converge@gunbai-secrets.iam.gserviceaccount.com-publication-elected.txt @@ -0,0 +1 @@ +projects/gunbai-secrets/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com;role:57:projects/gunbai-secrets/roles/gunbcIamBootstrapProbeToken;member:18:user:brian@gunb.ai;condition-title:38:gunbc-bootstrap-probe-probe-20260929-3;condition-expression:48:request.time < timestamp('2026-09-29T01:20:00Z');condition-description:83:Temporary exact-account bootstrap readback; physical cleanup required after expiry.; \ No newline at end of file diff --git a/docs/plans/receipts/gcp-iam-services-2026-09-29/gcp-iam-probe-probe-20260929-3-iam-converge@gunbai-secrets.iam.gserviceaccount.com-retired.txt b/docs/plans/receipts/gcp-iam-services-2026-09-29/gcp-iam-probe-probe-20260929-3-iam-converge@gunbai-secrets.iam.gserviceaccount.com-retired.txt new file mode 100644 index 00000000000..dc3646c20d9 --- /dev/null +++ b/docs/plans/receipts/gcp-iam-services-2026-09-29/gcp-iam-probe-probe-20260929-3-iam-converge@gunbai-secrets.iam.gserviceaccount.com-retired.txt @@ -0,0 +1 @@ +projects/gunbai-secrets/serviceAccounts/iam-converge@gunbai-secrets.iam.gserviceaccount.com;role:57:projects/gunbai-secrets/roles/gunbcIamBootstrapProbeToken;member:18:user:brian@gunb.ai;condition-title:38:gunbc-bootstrap-probe-probe-20260929-3;condition-expression:48:request.time < timestamp('2026-09-29T01:20:00Z');condition-description:83:Temporary exact-account bootstrap readback; physical cleanup required after expiry.; \ No newline at end of file diff --git a/docs/plans/receipts/gcp-iam-services-2026-09-29/gcp-iam-probe-probe-20260929-3-iam-observe@gunbai-secrets.iam.gserviceaccount.com-intent.txt b/docs/plans/receipts/gcp-iam-services-2026-09-29/gcp-iam-probe-probe-20260929-3-iam-observe@gunbai-secrets.iam.gserviceaccount.com-intent.txt new file mode 100644 index 00000000000..384a398af82 --- /dev/null +++ b/docs/plans/receipts/gcp-iam-services-2026-09-29/gcp-iam-probe-probe-20260929-3-iam-observe@gunbai-secrets.iam.gserviceaccount.com-intent.txt @@ -0,0 +1 @@ +projects/gunbai-secrets/serviceAccounts/iam-observe@gunbai-secrets.iam.gserviceaccount.com;role:57:projects/gunbai-secrets/roles/gunbcIamBootstrapProbeToken;member:18:user:brian@gunb.ai;condition-title:38:gunbc-bootstrap-probe-probe-20260929-3;condition-expression:48:request.time < timestamp('2026-09-29T01:20:00Z');condition-description:83:Temporary exact-account bootstrap readback; physical cleanup required after expiry.; \ No newline at end of file diff --git a/docs/plans/receipts/gcp-iam-services-2026-09-29/gcp-iam-probe-probe-20260929-3-iam-observe@gunbai-secrets.iam.gserviceaccount.com-publication-closed.txt b/docs/plans/receipts/gcp-iam-services-2026-09-29/gcp-iam-probe-probe-20260929-3-iam-observe@gunbai-secrets.iam.gserviceaccount.com-publication-closed.txt new file mode 100644 index 00000000000..384a398af82 --- /dev/null +++ b/docs/plans/receipts/gcp-iam-services-2026-09-29/gcp-iam-probe-probe-20260929-3-iam-observe@gunbai-secrets.iam.gserviceaccount.com-publication-closed.txt @@ -0,0 +1 @@ +projects/gunbai-secrets/serviceAccounts/iam-observe@gunbai-secrets.iam.gserviceaccount.com;role:57:projects/gunbai-secrets/roles/gunbcIamBootstrapProbeToken;member:18:user:brian@gunb.ai;condition-title:38:gunbc-bootstrap-probe-probe-20260929-3;condition-expression:48:request.time < timestamp('2026-09-29T01:20:00Z');condition-description:83:Temporary exact-account bootstrap readback; physical cleanup required after expiry.; \ No newline at end of file diff --git a/docs/plans/receipts/gcp-iam-services-2026-09-29/gcp-iam-probe-probe-20260929-3-iam-observe@gunbai-secrets.iam.gserviceaccount.com-publication-elected.txt b/docs/plans/receipts/gcp-iam-services-2026-09-29/gcp-iam-probe-probe-20260929-3-iam-observe@gunbai-secrets.iam.gserviceaccount.com-publication-elected.txt new file mode 100644 index 00000000000..384a398af82 --- /dev/null +++ b/docs/plans/receipts/gcp-iam-services-2026-09-29/gcp-iam-probe-probe-20260929-3-iam-observe@gunbai-secrets.iam.gserviceaccount.com-publication-elected.txt @@ -0,0 +1 @@ +projects/gunbai-secrets/serviceAccounts/iam-observe@gunbai-secrets.iam.gserviceaccount.com;role:57:projects/gunbai-secrets/roles/gunbcIamBootstrapProbeToken;member:18:user:brian@gunb.ai;condition-title:38:gunbc-bootstrap-probe-probe-20260929-3;condition-expression:48:request.time < timestamp('2026-09-29T01:20:00Z');condition-description:83:Temporary exact-account bootstrap readback; physical cleanup required after expiry.; \ No newline at end of file diff --git a/docs/plans/receipts/gcp-iam-services-2026-09-29/gcp-iam-probe-probe-20260929-3-iam-observe@gunbai-secrets.iam.gserviceaccount.com-retired.txt b/docs/plans/receipts/gcp-iam-services-2026-09-29/gcp-iam-probe-probe-20260929-3-iam-observe@gunbai-secrets.iam.gserviceaccount.com-retired.txt new file mode 100644 index 00000000000..384a398af82 --- /dev/null +++ b/docs/plans/receipts/gcp-iam-services-2026-09-29/gcp-iam-probe-probe-20260929-3-iam-observe@gunbai-secrets.iam.gserviceaccount.com-retired.txt @@ -0,0 +1 @@ +projects/gunbai-secrets/serviceAccounts/iam-observe@gunbai-secrets.iam.gserviceaccount.com;role:57:projects/gunbai-secrets/roles/gunbcIamBootstrapProbeToken;member:18:user:brian@gunb.ai;condition-title:38:gunbc-bootstrap-probe-probe-20260929-3;condition-expression:48:request.time < timestamp('2026-09-29T01:20:00Z');condition-description:83:Temporary exact-account bootstrap readback; physical cleanup required after expiry.; \ No newline at end of file diff --git a/docs/plans/receipts/gcp-iam-services-2026-09-29/receipt.json b/docs/plans/receipts/gcp-iam-services-2026-09-29/receipt.json new file mode 100644 index 00000000000..6e985b5b2a8 --- /dev/null +++ b/docs/plans/receipts/gcp-iam-services-2026-09-29/receipt.json @@ -0,0 +1,20 @@ +{ + "source_revision": "85bdb4e19", + "binary_sha256": "179882133a4c03958a1bed8a8b444ea06e48c63a6c42263ff83694508825b415", + "focused_controls_passed": 18, + "closure_modules": 844, + "memory_max": "6G", + "memory_swap_max": 0, + "cloud_status": "all ten bootstrap stages completed; exit 0", + "probe_operation": "probe-20260929-3", + "probe_deadline": "2026-09-29T01:20:00Z", + "full_landing_suite": "not run", + "temporary_grants": "probe grants and prior deny grant absent on final independent readback", + "credential_file": "removed", + "newly_enabled_api": "cloudresourcemanager.googleapis.com", + "workflow_acceptance": { + "run_id": 36505895733, + "source_revision": "15b977d417c1a7de52eae96f48bcd3955fb1f94c", + "status": "dispatched; no success claimed" + } +}