Bug Description
Three defects in the same feature, all of which make a non-Eastern deployment look and behave as Eastern. They share ten lines of code, so they are reported together; splitting them is fine if preferred.
1. The hour variables are documented as Eastern, unconditionally
# gemini-stage-0/variables.tf:102-110
variable "access_start_hour" {
description = "The hour (0-23) in America/New_York timezone when access starts."
}
variable "access_end_hour" {
description = "The hour (0-23) in America/New_York timezone when access ends."
}
Both descriptions name America/New_York as the timezone the hour is expressed in. That is only true when access_time_zone is left at its default. The expression actually evaluates the hour in whatever access_time_zone is set to (access_policy.tf:51 — request.time.getHours("${var.access_time_zone}")), so for any other timezone the documentation is simply wrong about what the number means. An operator configuring America/Anchorage reads that access_start_hour = 8 is 8am Eastern; it is 8am Alaska.
2. The access level's title hardcodes Eastern
# gemini-stage-0/access_policy.tf:48
title = "Business Hours East Coast"
This never varies with access_time_zone. Anyone reading the level in the Console or via gcloud access-context-manager levels describe time sees "Business Hours East Coast" asserted over a window that may be Alaska, Pacific or anything else. Combined with defect 1, every user-facing surface of this feature claims Eastern while the evaluation is doing something different — which is precisely the state that makes a misconfiguration invisible.
To be clear about what is NOT broken: the CEL expression itself is correct. It substitutes var.access_time_zone into all four getHours / getDayOfWeek calls, so a level built from a fresh, complete run evaluates in the configured zone. The bug is that the labels and docs say otherwise — and defect 3 then throws the configured value away entirely.
3. "Preserving" silently overwrites the configured window with the defaults
When the time access level already exists and is Terraform-managed, deploy.sh reports that it is preserving it and skips every time-related prompt:
# deploy.sh:1143-1150
if echo "$EXISTING_LEVELS" | grep -qE "(/|^)time$"; then
if [[ "$MANAGED_ACCESS_LEVELS" == *"time"* ]]; then
echo -e "${GREEN}Found existing MANAGED Access Level 'time'. Preserving.${NC}"
CREATE_TIME_ACCESS="true"
else
echo -e "${YELLOW}Access Level 'time' already exists (Unmanaged). Skipping.${NC}"
CREATE_TIME_ACCESS="false"
fi
else
# the five prompts — start day, end day, start hour, end hour, TIME ZONE — live here
fi
Because the prompts are skipped, ACCESS_START_DAY, ACCESS_END_DAY, ACCESS_START_HOUR, ACCESS_END_HOUR and ACCESS_TIME_ZONE are all unset. They are never hydrated from prior state either — the only assignments to any of them in the whole script are the prompts at :1155-1164.
The tfvars writer then skips each one, because every write is guarded on the variable being non-empty:
# deploy.sh:2455-2469
if [[ -n "$ACCESS_START_DAY" ]]; then echo "access_start_day = ${ACCESS_START_DAY}" >> gemini-stage-0/terraform.tfvars; fi
...
if [[ -n "$ACCESS_TIME_ZONE" ]]; then echo "access_time_zone = \"${ACCESS_TIME_ZONE}\"" >> gemini-stage-0/terraform.tfvars; fi
So nothing is written, and Terraform falls back to the variable defaults — access_time_zone = "America/New_York", access_start_hour = 7, access_end_hour = 21, access_start_day = 1, access_end_day = 5.
Meanwhile CREATE_TIME_ACCESS="true" means the resource is still fully managed, and its expression is rebuilt from exactly those variables:
# gemini-stage-0/access_policy.tf:44-53
resource "google_access_context_manager_access_level" "time" {
count = var.access_policy_number != "" && var.create_time_access ? 1 : 0
title = "Business Hours East Coast"
custom { expr {
expression = ("request.time.getHours(\"${var.access_time_zone}\") >= ${var.access_start_hour} && ... <= ${var.access_end_day}")
}}
}
The apply therefore overwrites the operator's configured window with the East-Coast defaults, immediately after printing "Preserving." The script reports the opposite of what it does.
The expire level has the identical defect (deploy.sh:1173-1180): "Preserving" + CREATE_EXPIRE_ACCESS="true", no prompt, ACCESS_EXPIRATION_TIMESTAMP unset, not written, so the expiry silently resets to the default 2028-01-01T00:00:00Z. The us level shares the shape but carries no parameters, so nothing is lost there.
Environment and Deployment Context
- Stellar Engine Version/Commit:
main at commit f64ce6cd (= tag v3.0.0), verified 2026-08-11
- Deployment Type:
- FAST Stage (if applicable): N/A — this is a blueprint, not a FAST stage
- Affected Component:
blueprints/fedramp-high/gemini-enterprise/gemini-stage-0/variables.tf:102-110 (hour descriptions hardcoding America/New_York)
blueprints/fedramp-high/gemini-enterprise/gemini-stage-0/access_policy.tf:48 (title hardcoding Business Hours East Coast)
blueprints/fedramp-high/gemini-enterprise/deploy.sh:1143-1150 (the "Preserving" branch that skips the prompts)
blueprints/fedramp-high/gemini-enterprise/deploy.sh:1155-1164 (the only assignments to the five variables)
blueprints/fedramp-high/gemini-enterprise/deploy.sh:2455-2469 (writes guarded on non-empty)
blueprints/fedramp-high/gemini-enterprise/deploy.sh:1173-1180 (same defect for expire)
blueprints/fedramp-high/gemini-enterprise/gemini-stage-0/access_policy.tf:44-53 (the managed resource)
- Terraform Version:
1.12.2 (pinned by deploy.sh via tfenv; the stage declares required_version >= 1.7.4)
- GCP Provider Version:
hashicorp/google >= 6.21.0 (stage-0 declared constraint)
Steps to Reproduce
- Run
deploy.sh, answer y to time-based access, and set a non-default window — e.g. Time Zone America/Anchorage, start hour 8, end hour 18.
- Complete the apply. Read the level back and confirm the expression carries
America/Anchorage and >= 8 && <= 18.
- Run
deploy.sh again against the same environment — any second pass, which is the normal path for adding an application or re-running a stage.
- Observe
Found existing MANAGED Access Level 'time'. Preserving. and note that no time prompts appear.
- Complete the apply and read the level again:
gcloud access-context-manager levels describe time --policy <access_policy_number>.
Expected Behavior
The configured time zone is honored and represented consistently. Specifically: the hour descriptions say the hour is expressed in access_time_zone, not in America/New_York; the level's title reflects the configured zone rather than asserting Eastern; and "Preserving" preserves — either the existing values are read back and re-written unchanged, or the operator is re-prompted with them as defaults, or the resource is left unmanaged so Terraform does not touch it.
Actual Behavior
Every user-facing surface asserts Eastern regardless of configuration, and the configured value does not survive a second run:
access_start_hour / access_end_hour are documented as being "in America/New_York timezone" whatever access_time_zone is set to.
- The level is titled
Business Hours East Coast whatever access_time_zone is set to.
- On any re-run through the wizard, the level is rewritten to the defaults: timezone reverts to
America/New_York, hours to 07:00-21:00, days to Mon-Fri. For a deployment configured in America/Anchorage that shifts the effective window by four hours — an 08:00-18:00 Alaska window silently becomes 07:00-21:00 Eastern, i.e. 03:00-17:00 local. No warning, no prompt, and the console line says the opposite.
The net effect is that an operator who sets a non-Eastern zone has no surface anywhere — variable docs, level title, or a second wizard run — that agrees with what they configured.
Relevant Logs and Errors
No error is emitted — that is the defect. The only operator-visible signal is the line that misdescribes what happens:
--- Time Based Access ---
Found existing MANAGED Access Level 'time'. Preserving.
The resulting expression, rebuilt from the defaults:
request.time.getHours("America/New_York") >= 7 && request.time.getHours("America/New_York") <= 21 &&
request.time.getDayOfWeek("America/New_York") >= 1 && request.time.getDayOfWeek("America/New_York") <= 5
Suggested Fix
Defects 1 and 2 are one-liners. Change the access_start_hour / access_end_hour descriptions to say the hour is expressed in access_time_zone (naming America/New_York only as the default), and make the level title carry the configured zone — e.g. "Business Hours (${var.access_time_zone})" — instead of hardcoding East Coast.
Defect 3, in the "Preserving" branch, do one of:
- Read the existing level back and populate
ACCESS_* from it before the tfvars write, so the values round-trip; or
- Re-prompt using the existing values as the defaults; or
- Set
CREATE_TIME_ACCESS="false" so Terraform stops managing a level the script has decided not to reconfigure — matching what "Preserving" already implies.
Whichever is chosen, the console message must match the behavior, and the same fix applies to the expire branch.
Additional Context
Found while running a deployment configured for America/Anchorage. Defect 3 is security-relevant rather than cosmetic: a time-based access control silently shifts its effective window on an ordinary re-run, and an operator who reads the "Preserving" line has no reason to re-check it. Defects 1 and 2 are what make it hard to catch — every label and description the operator can consult says Eastern, so the reset back to Eastern looks like the intended state.
Related: #186 covers the day-numbering mismatch in the same expression — the day prompts at deploy.sh:1155 / :1157 and the access_start_day / access_end_day descriptions all state 1=Mon, 7=Sun while CEL's getDayOfWeek() is 0-6. Between the two reports, every input to this one access level is either mis-documented or discarded on re-run; they are worth fixing together.
Bug Description
Three defects in the same feature, all of which make a non-Eastern deployment look and behave as Eastern. They share ten lines of code, so they are reported together; splitting them is fine if preferred.
1. The hour variables are documented as Eastern, unconditionally
Both descriptions name
America/New_Yorkas the timezone the hour is expressed in. That is only true whenaccess_time_zoneis left at its default. The expression actually evaluates the hour in whateveraccess_time_zoneis set to (access_policy.tf:51—request.time.getHours("${var.access_time_zone}")), so for any other timezone the documentation is simply wrong about what the number means. An operator configuringAmerica/Anchoragereads thataccess_start_hour = 8is 8am Eastern; it is 8am Alaska.2. The access level's title hardcodes Eastern
This never varies with
access_time_zone. Anyone reading the level in the Console or viagcloud access-context-manager levels describe timesees "Business Hours East Coast" asserted over a window that may be Alaska, Pacific or anything else. Combined with defect 1, every user-facing surface of this feature claims Eastern while the evaluation is doing something different — which is precisely the state that makes a misconfiguration invisible.To be clear about what is NOT broken: the CEL expression itself is correct. It substitutes
var.access_time_zoneinto all fourgetHours/getDayOfWeekcalls, so a level built from a fresh, complete run evaluates in the configured zone. The bug is that the labels and docs say otherwise — and defect 3 then throws the configured value away entirely.3. "Preserving" silently overwrites the configured window with the defaults
When the
timeaccess level already exists and is Terraform-managed,deploy.shreports that it is preserving it and skips every time-related prompt:Because the prompts are skipped,
ACCESS_START_DAY,ACCESS_END_DAY,ACCESS_START_HOUR,ACCESS_END_HOURandACCESS_TIME_ZONEare all unset. They are never hydrated from prior state either — the only assignments to any of them in the whole script are the prompts at:1155-1164.The tfvars writer then skips each one, because every write is guarded on the variable being non-empty:
So nothing is written, and Terraform falls back to the variable defaults —
access_time_zone = "America/New_York",access_start_hour = 7,access_end_hour = 21,access_start_day = 1,access_end_day = 5.Meanwhile
CREATE_TIME_ACCESS="true"means the resource is still fully managed, and its expression is rebuilt from exactly those variables:The apply therefore overwrites the operator's configured window with the East-Coast defaults, immediately after printing "Preserving." The script reports the opposite of what it does.
The
expirelevel has the identical defect (deploy.sh:1173-1180): "Preserving" +CREATE_EXPIRE_ACCESS="true", no prompt,ACCESS_EXPIRATION_TIMESTAMPunset, not written, so the expiry silently resets to the default2028-01-01T00:00:00Z. Theuslevel shares the shape but carries no parameters, so nothing is lost there.Environment and Deployment Context
mainat commitf64ce6cd(= tagv3.0.0), verified 2026-08-11blueprints/fedramp-high/gemini-enterprise/gemini-stage-0/variables.tf:102-110(hour descriptions hardcodingAmerica/New_York)blueprints/fedramp-high/gemini-enterprise/gemini-stage-0/access_policy.tf:48(title hardcodingBusiness Hours East Coast)blueprints/fedramp-high/gemini-enterprise/deploy.sh:1143-1150(the "Preserving" branch that skips the prompts)blueprints/fedramp-high/gemini-enterprise/deploy.sh:1155-1164(the only assignments to the five variables)blueprints/fedramp-high/gemini-enterprise/deploy.sh:2455-2469(writes guarded on non-empty)blueprints/fedramp-high/gemini-enterprise/deploy.sh:1173-1180(same defect forexpire)blueprints/fedramp-high/gemini-enterprise/gemini-stage-0/access_policy.tf:44-53(the managed resource)1.12.2(pinned bydeploy.shvia tfenv; the stage declaresrequired_version >= 1.7.4)hashicorp/google >= 6.21.0(stage-0 declared constraint)Steps to Reproduce
deploy.sh, answer y to time-based access, and set a non-default window — e.g. Time ZoneAmerica/Anchorage, start hour8, end hour18.America/Anchorageand>= 8 && <= 18.deploy.shagain against the same environment — any second pass, which is the normal path for adding an application or re-running a stage.Found existing MANAGED Access Level 'time'. Preserving.and note that no time prompts appear.gcloud access-context-manager levels describe time --policy <access_policy_number>.Expected Behavior
The configured time zone is honored and represented consistently. Specifically: the hour descriptions say the hour is expressed in
access_time_zone, not inAmerica/New_York; the level's title reflects the configured zone rather than asserting Eastern; and "Preserving" preserves — either the existing values are read back and re-written unchanged, or the operator is re-prompted with them as defaults, or the resource is left unmanaged so Terraform does not touch it.Actual Behavior
Every user-facing surface asserts Eastern regardless of configuration, and the configured value does not survive a second run:
access_start_hour/access_end_hourare documented as being "in America/New_York timezone" whateveraccess_time_zoneis set to.Business Hours East Coastwhateveraccess_time_zoneis set to.America/New_York, hours to07:00-21:00, days to Mon-Fri. For a deployment configured inAmerica/Anchoragethat shifts the effective window by four hours — an08:00-18:00Alaska window silently becomes07:00-21:00Eastern, i.e.03:00-17:00local. No warning, no prompt, and the console line says the opposite.The net effect is that an operator who sets a non-Eastern zone has no surface anywhere — variable docs, level title, or a second wizard run — that agrees with what they configured.
Relevant Logs and Errors
No error is emitted — that is the defect. The only operator-visible signal is the line that misdescribes what happens:
The resulting expression, rebuilt from the defaults:
Suggested Fix
Defects 1 and 2 are one-liners. Change the
access_start_hour/access_end_hourdescriptions to say the hour is expressed inaccess_time_zone(namingAmerica/New_Yorkonly as the default), and make the level title carry the configured zone — e.g."Business Hours (${var.access_time_zone})"— instead of hardcoding East Coast.Defect 3, in the "Preserving" branch, do one of:
ACCESS_*from it before the tfvars write, so the values round-trip; orCREATE_TIME_ACCESS="false"so Terraform stops managing a level the script has decided not to reconfigure — matching what "Preserving" already implies.Whichever is chosen, the console message must match the behavior, and the same fix applies to the
expirebranch.Additional Context
Found while running a deployment configured for
America/Anchorage. Defect 3 is security-relevant rather than cosmetic: a time-based access control silently shifts its effective window on an ordinary re-run, and an operator who reads the "Preserving" line has no reason to re-check it. Defects 1 and 2 are what make it hard to catch — every label and description the operator can consult says Eastern, so the reset back to Eastern looks like the intended state.Related: #186 covers the day-numbering mismatch in the same expression — the day prompts at
deploy.sh:1155/:1157and theaccess_start_day/access_end_daydescriptions all state1=Mon, 7=Sunwhile CEL'sgetDayOfWeek()is 0-6. Between the two reports, every input to this one access level is either mis-documented or discarded on re-run; they are worth fixing together.