Bug Description
Two components write the assistant's customerPolicy, and the second silently discards what the first configured.
deploy.sh binds the template correctly. When an application has enable_model_armor = true and a template exists, it merges modelArmorConfig into the assistant patch and widens the mask:
# deploy.sh:2565-2568
if [[ "$ENABLE_MA" == "true" && -n "$MODEL_ARMOR_TEMPLATE_NAME" && "$MODEL_ARMOR_TEMPLATE_NAME" != "null" ]]; then
ASSISTANT_BODY=$(echo "$BASE_JSON" | jq --arg template "$MODEL_ARMOR_TEMPLATE_NAME" \
'. + {customerPolicy: {modelArmorConfig: {userPromptTemplate: $template, responseTemplate: $template, failureMode: "FAIL_OPEN"}}}')
MASK="${MASK},customerPolicy"
fi
gem4gov app update-compliance then overwrites it. For FedRAMP High, configure_gemini_enterprise_for_fedramp_high() (gem4gov.py:1526) PATCHes the same assistant:
# gem4gov.py:1577 — the mask still names customerPolicy
url = f"...{assistant_name}?updateMask=customerPolicy,agentConfigs,generationConfig,disableLocationContext,webGroundingType,defaultWebGroundingToggleOff"
# gem4gov.py:1583-1585 — but the body has no modelArmorConfig
"customerPolicy":{
"bannedPhrases":[]
},
A field named in an updateMask is replaced wholesale, not merged. So this PATCH sets customerPolicy to a value containing only an empty bannedPhrases list — the API drops the empty list, leaving customerPolicy: {} — and the modelArmorConfig deploy.sh just wrote is gone.
The two are not far apart in practice: deploy.sh:2802 is the Helper Function that runs gem4gov app update-compliance, and the command is the documented way to re-assert compliance posture on an existing engine. Running it is the recommended remediation for a drifted deployment — so the action an operator takes to restore compliance is the action that removes this control.
Scope: FedRAMP High only. The IL4 (gem4gov.py:1642) and IL5 (gem4gov.py:1754) paths use updateMask=generationConfig.defaultLanguage,webGroundingType,defaultWebGroundingToggleOff,enableEndUserAgentCreation,disableLocationContext, which does not name customerPolicy, so they leave an existing binding alone. (The original write-up said all three regimes carried the same body — that was wrong, and is corrected here.)
Environment and Deployment Context
- Stellar Engine Version/Commit:
main at commit f64ce6cd (re-verified 2026-08-10)
- Deployment Type:
- FAST Stage (if applicable): N/A — this is a blueprint, not a FAST stage
- Affected Component:
blueprints/fedramp-high/gemini-enterprise/deploy.sh, blueprints/fedramp-high/gemini-enterprise/gem4gov-cli/gem4gov.py
- 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
- Deploy
gemini-stage-0 answering yes to Model Armor for an application.
- Read the assistant and confirm
customerPolicy.modelArmorConfig is present: GET https://us-discoveryengine.googleapis.com/v1alpha/projects/<project>/locations/us/collections/default_collection/engines/<engine>/assistants/default_assistant
gem4gov app update-compliance --project-id <project> --engine-id <engine> --compliance-regime FEDRAMP_HIGH
- Read the assistant again.
Expected Behavior
update-compliance preserves — or re-asserts — customerPolicy.modelArmorConfig. Re-running the command that enforces compliance posture should never remove a compliance control.
Actual Behavior
customerPolicy comes back empty. The Model Armor template still exists in the project, it is simply no longer referenced by the assistant, so no prompt or response is screened. The command prints Compliance configuration complete! on its way out.
Relevant Logs and Errors
Observed live 2026-07-29, reading the assistant back at step 4:
{ "googleSearchGroundingEnabled": null,
"webGroundingType": "WEB_GROUNDING_TYPE_ENTERPRISE_WEB_SEARCH",
"disableLocationContext": true,
"customerPolicy": {} }
The empty bannedPhrases list the CLI sends is dropped by the API, which is why customerPolicy reads {} rather than {"bannedPhrases": []}. The template is still present at projects/<project>/locations/us/templates/<prefix>-model-armor-template.
No error is emitted at any point — the PATCH is issued as curl -s -o /dev/null with no status or body check, so a silently-destructive write looks identical to a successful one.
Impact
Model Armor is one of the controls update-compliance explicitly lists as required for FedRAMP High ("- Model Armor" in its own printed output). The command prints Compliance configuration complete! immediately after removing it. An operator who enabled Model Armor, saw the template in the console, and then re-ran the compliance command has every reason to believe prompt screening is on; it is not, and nothing surfaces the change unless the assistant is read back by hand. A control that is silently removed by the tool that claims to enforce it is worse than one that was never enabled — the deployment now attests to a screening posture it does not have.
Additional Context
Source locations:
blueprints/fedramp-high/gemini-enterprise/deploy.sh:2565-2578 — binds the template, then PATCHes with curl -s -o /dev/null (no status check; the same pattern appears in the usage-audit logging defect reported separately)
blueprints/fedramp-high/gemini-enterprise/gem4gov-cli/gem4gov.py:1577 (mask), :1583-1585 (body) — strips it
blueprints/fedramp-high/gemini-enterprise/deploy.sh:2802 — the Helper Function that invokes update-compliance
Blocks a FedRAMP High control (prompt/response screening) silently, so it is worth treating as a compliance defect rather than a UX one.
Suggested fix
Have configure_gemini_enterprise_for_fedramp_high() read the current assistant first and preserve any modelArmorConfig it finds, or accept the template name and re-assert the binding:
"customerPolicy": {
"bannedPhrases": [],
"modelArmorConfig": {
"userPromptTemplate": "projects/<project>/locations/us/templates/<prefix>-model-armor-template",
"responseTemplate": "projects/<project>/locations/us/templates/<prefix>-model-armor-template",
"failureMode": "FAIL_OPEN"
}
}
Failing that, drop customerPolicy from the updateMask — the only thing the body contributes is an empty bannedPhrases list the API discards anyway, so removing it from the mask costs nothing and stops the clobber. Either way, modelArmorConfig should be part of whatever the tool verifies before printing Compliance configuration complete!.
Bug Description
Two components write the assistant's
customerPolicy, and the second silently discards what the first configured.deploy.shbinds the template correctly. When an application hasenable_model_armor = trueand a template exists, it mergesmodelArmorConfiginto the assistant patch and widens the mask:gem4gov app update-compliancethen overwrites it. For FedRAMP High,configure_gemini_enterprise_for_fedramp_high()(gem4gov.py:1526) PATCHes the same assistant:A field named in an
updateMaskis replaced wholesale, not merged. So this PATCH setscustomerPolicyto a value containing only an emptybannedPhraseslist — the API drops the empty list, leavingcustomerPolicy: {}— and themodelArmorConfigdeploy.shjust wrote is gone.The two are not far apart in practice:
deploy.sh:2802is the Helper Function that runsgem4gov app update-compliance, and the command is the documented way to re-assert compliance posture on an existing engine. Running it is the recommended remediation for a drifted deployment — so the action an operator takes to restore compliance is the action that removes this control.Scope: FedRAMP High only. The IL4 (
gem4gov.py:1642) and IL5 (gem4gov.py:1754) paths useupdateMask=generationConfig.defaultLanguage,webGroundingType,defaultWebGroundingToggleOff,enableEndUserAgentCreation,disableLocationContext, which does not namecustomerPolicy, so they leave an existing binding alone. (The original write-up said all three regimes carried the same body — that was wrong, and is corrected here.)Environment and Deployment Context
mainat commitf64ce6cd(re-verified 2026-08-10)blueprints/fedramp-high/gemini-enterprise/deploy.sh,blueprints/fedramp-high/gemini-enterprise/gem4gov-cli/gem4gov.py1.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
gemini-stage-0answering yes to Model Armor for an application.customerPolicy.modelArmorConfigis present:GET https://us-discoveryengine.googleapis.com/v1alpha/projects/<project>/locations/us/collections/default_collection/engines/<engine>/assistants/default_assistantgem4gov app update-compliance --project-id <project> --engine-id <engine> --compliance-regime FEDRAMP_HIGHExpected Behavior
update-compliancepreserves — or re-asserts —customerPolicy.modelArmorConfig. Re-running the command that enforces compliance posture should never remove a compliance control.Actual Behavior
customerPolicycomes back empty. The Model Armor template still exists in the project, it is simply no longer referenced by the assistant, so no prompt or response is screened. The command printsCompliance configuration complete!on its way out.Relevant Logs and Errors
Observed live 2026-07-29, reading the assistant back at step 4:
{ "googleSearchGroundingEnabled": null, "webGroundingType": "WEB_GROUNDING_TYPE_ENTERPRISE_WEB_SEARCH", "disableLocationContext": true, "customerPolicy": {} }The empty
bannedPhraseslist the CLI sends is dropped by the API, which is whycustomerPolicyreads{}rather than{"bannedPhrases": []}. The template is still present atprojects/<project>/locations/us/templates/<prefix>-model-armor-template.No error is emitted at any point — the PATCH is issued as
curl -s -o /dev/nullwith no status or body check, so a silently-destructive write looks identical to a successful one.Impact
Model Armor is one of the controls
update-complianceexplicitly lists as required for FedRAMP High ("- Model Armor" in its own printed output). The command printsCompliance configuration complete!immediately after removing it. An operator who enabled Model Armor, saw the template in the console, and then re-ran the compliance command has every reason to believe prompt screening is on; it is not, and nothing surfaces the change unless the assistant is read back by hand. A control that is silently removed by the tool that claims to enforce it is worse than one that was never enabled — the deployment now attests to a screening posture it does not have.Additional Context
Source locations:
blueprints/fedramp-high/gemini-enterprise/deploy.sh:2565-2578— binds the template, then PATCHes withcurl -s -o /dev/null(no status check; the same pattern appears in the usage-audit logging defect reported separately)blueprints/fedramp-high/gemini-enterprise/gem4gov-cli/gem4gov.py:1577(mask),:1583-1585(body) — strips itblueprints/fedramp-high/gemini-enterprise/deploy.sh:2802— the Helper Function that invokesupdate-complianceBlocks a FedRAMP High control (prompt/response screening) silently, so it is worth treating as a compliance defect rather than a UX one.
Suggested fix
Have
configure_gemini_enterprise_for_fedramp_high()read the current assistant first and preserve anymodelArmorConfigit finds, or accept the template name and re-assert the binding:Failing that, drop
customerPolicyfrom the updateMask — the only thing the body contributes is an emptybannedPhraseslist the API discards anyway, so removing it from the mask costs nothing and stops the clobber. Either way,modelArmorConfigshould be part of whatever the tool verifies before printingCompliance configuration complete!.