thinkwork-cli 0.13.0 → 0.13.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{api-client-JUBCQQDF.js → api-client-JYZXYQJR.js} +3 -1
- package/dist/bowser-6Z6JFAIR.js +2822 -0
- package/dist/chunk-DV3MRBR5.js +43 -0
- package/dist/chunk-DVPBI5HM.js +1728 -0
- package/dist/chunk-EI5ZX7CN.js +522 -0
- package/dist/chunk-EUJ7X6YD.js +126 -0
- package/dist/chunk-GG5WKBOX.js +436 -0
- package/dist/chunk-I64EZSU5.js +358 -0
- package/dist/chunk-ILZKG5WV.js +6151 -0
- package/dist/chunk-KMIJ5KGT.js +128 -0
- package/dist/chunk-OCPHU34C.js +996 -0
- package/dist/chunk-QOERT44A.js +3286 -0
- package/dist/chunk-XYM7PE2A.js +2264 -0
- package/dist/{chunk-STY44FQO.js → chunk-Z2F6RJFR.js} +12 -18
- package/dist/cli.js +72564 -18884
- package/dist/dist-es-CRV46D5B.js +315 -0
- package/dist/dist-es-DCY4IC3Y.js +2000 -0
- package/dist/dist-es-ENBUC3JP.js +14726 -0
- package/dist/dist-es-G54LBMRN.js +48 -0
- package/dist/dist-es-G7PTG75M.js +176 -0
- package/dist/dist-es-GKDBE7E6.js +88 -0
- package/dist/dist-es-HY3535OI.js +70 -0
- package/dist/dist-es-KL7Y5NB2.js +377 -0
- package/dist/dist-es-LDE437AM.js +170 -0
- package/dist/dist-es-P26FWFLT.js +485 -0
- package/dist/dist-es-Q57SZXZ7.js +70 -0
- package/dist/dist-es-R7FGL2IB.js +88 -0
- package/dist/dist-es-USORBXZ5.js +318 -0
- package/dist/dist-es-ZKC2QKD2.js +377 -0
- package/dist/drizzle/0076_scheduled_jobs_marco_backfill.sql +2 -1
- package/dist/drizzle/0079_seed_tenant_customize_catalog.sql +1 -0
- package/dist/drizzle/0090_brain_schema_extraction.sql +10 -0
- package/dist/drizzle/0132_drop_computer_tables.sql +3 -0
- package/dist/drizzle/0160_compliance_event_types_plugins.sql +2 -0
- package/dist/drizzle/0214_think137_drop_judge_evidence_roi.sql +1 -0
- package/dist/drizzle/0216_drop_agent_loop_versions_goal_worker_policy.sql +1 -0
- package/dist/drizzle/0227_analyst_reader_role.sql +106 -38
- package/dist/drizzle/0230_analyst_rls.sql +192 -66
- package/dist/drizzle/0232_memory_source_thin_ledger.sql +176 -0
- package/dist/drizzle/0232_message_source_event_id.sql +9 -0
- package/dist/drizzle/0233_msteams_install_and_links.sql +151 -0
- package/dist/drizzle/0233_workflow_memory_stage_tokens.sql +20 -0
- package/dist/drizzle/0234_memory_claim_lifecycle.sql +160 -0
- package/dist/drizzle/0235_workflow_token_execution_claim.sql +34 -0
- package/dist/drizzle/0236_memory_retraction_fencing.sql +50 -0
- package/dist/drizzle/0237_memory_claims_active_value_uidx.sql +172 -0
- package/dist/drizzle/0238_memory_erase_generation_fence.sql +97 -0
- package/dist/drizzle/0239_canonical_entity_identity.sql +257 -0
- package/dist/drizzle/0240_memory_personal_scope.sql +45 -0
- package/dist/drizzle/0241_knowledge_base_document_manifest.sql +67 -0
- package/dist/drizzle/0242_search_queries.sql +28 -0
- package/dist/drizzle/0243_section_sources_thread_refs.sql +12 -0
- package/dist/drizzle/0244_system_automations_and_memory_stage_overrides.sql +45 -0
- package/dist/drizzle/0245_wiki_page_render.sql +21 -0
- package/dist/drizzle/0246_automation_workflow_ownership.sql +30 -0
- package/dist/drizzle/0247_capability_runtime.sql +402 -0
- package/dist/drizzle/0248_capability_private_interpreter.sql +13 -0
- package/dist/drizzle/0249_capability_headless_execution_evidence.sql +40 -0
- package/dist/drizzle/0250_kg_schema_extraction.sql +576 -0
- package/dist/drizzle/0251_agents_json_render_ui.sql +25 -0
- package/dist/drizzle/0251_brain_stragglers.sql +263 -0
- package/dist/drizzle/0252_drop_kg_and_straggler_compat_views.sql +100 -0
- package/dist/drizzle/0253_drop_brain_substrate.sql +93 -0
- package/dist/drizzle/0254_capability_autonomous_admission.sql +29 -0
- package/dist/drizzle/0255_capability_autonomous_approval_mode.sql +16 -0
- package/dist/drizzle/0256_agent_profile_manifest_authority.sql +18 -0
- package/dist/drizzle/0257_agents_runtime_allow_harness.sql +24 -0
- package/dist/drizzle/0257_capability_approvals.sql +53 -0
- package/dist/drizzle/0258_pending_tool_approvals.sql +84 -0
- package/dist/drizzle/0259_tenant_capability_registry_trust.sql +19 -0
- package/dist/drizzle/0260_scheduled_jobs_run_as_user.sql +20 -0
- package/dist/drizzle/0261_managed_harness_multiplayer.sql +313 -0
- package/dist/drizzle/0261_native_auth_control_plane.sql +328 -0
- package/dist/drizzle/0262_auth_subscription_tickets.sql +59 -0
- package/dist/drizzle/0262_parallel_harness_threads.sql +9 -0
- package/dist/drizzle/0263_agentcore_runtime_identifier.sql +69 -0
- package/dist/drizzle/0263_drop_workos_auth_runtime.sql +236 -0
- package/dist/drizzle/0264_auth_enrollment_attempt_limits.sql +15 -0
- package/dist/drizzle/0264_harness_tool_execution_ledger.sql +113 -0
- package/dist/drizzle/0265_eval_runs_requester_identity.sql +28 -0
- package/dist/drizzle/0265_require_fully_valid_auth_publication.sql +27 -0
- package/dist/drizzle/0266_auth_identity_recovery_grants.sql +9 -0
- package/dist/drizzle/0266_eval_profiles_runtime_type.sql +12 -0
- package/dist/drizzle/0267_ontology_candidate_rejections.sql +53 -0
- package/dist/drizzle/0268_identity_crosswalk_routing.sql +182 -0
- package/dist/drizzle/0269_ontology_system_map.sql +31 -0
- package/dist/event-streams-7JNLJEEL.js +894 -0
- package/dist/event-streams-KDF5RWR6.js +1515 -0
- package/dist/loadSso-DJVB7BXE.js +576 -0
- package/dist/loadSso-JAMNA3WY.js +583 -0
- package/dist/plugins/catalog/package.json +1 -2
- package/dist/plugins/catalog/src/__tests__/build-catalog.test.ts +0 -1
- package/dist/plugins/catalog/src/__tests__/contracts.test.ts +0 -69
- package/dist/plugins/catalog/src/__tests__/plugin-package.test.ts +0 -2
- package/dist/plugins/catalog/src/__tests__/plugin-registry.test.ts +0 -5
- package/dist/plugins/catalog/src/contracts.ts +2 -179
- package/dist/plugins/catalog/src/registry/generated-first-party.ts +0 -6
- package/dist/signin-UGDWTGS4.js +821 -0
- package/dist/sso-oidc-Q4VXANXY.js +837 -0
- package/dist/sts-CWLSE3GH.js +1241 -0
- package/dist/sts-DNZLNVOV.js +1245 -0
- package/dist/terraform/examples/greenfield/main.tf +247 -50
- package/dist/terraform/examples/greenfield/terraform.tfvars.example +15 -1
- package/dist/terraform/modules/app/agentcore-admin/main.tf +341 -0
- package/dist/terraform/modules/app/agentcore-admin/outputs.tf +20 -0
- package/dist/terraform/modules/app/agentcore-code-interpreter/main.tf +61 -0
- package/dist/terraform/modules/app/agentcore-gateway/main.tf +305 -0
- package/dist/terraform/modules/app/agentcore-gateway/outputs.tf +44 -0
- package/dist/terraform/modules/app/agentcore-gateway/scripts/delete_gateway.sh +93 -0
- package/dist/terraform/modules/app/agentcore-gateway/scripts/read_gateway.sh +33 -0
- package/dist/terraform/modules/app/agentcore-gateway/scripts/reconcile_gateway.sh +543 -0
- package/dist/terraform/modules/app/agentcore-gateway/variables.tf +70 -0
- package/dist/terraform/modules/app/agentcore-harness/main.tf +330 -0
- package/dist/terraform/modules/app/agentcore-harness/outputs.tf +72 -0
- package/dist/terraform/modules/app/agentcore-harness/scripts/delete_harness.sh +5 -0
- package/dist/terraform/modules/app/agentcore-harness/scripts/harness-lifecycle.mjs +430 -0
- package/dist/terraform/modules/app/agentcore-harness/scripts/harness-readback.mjs +180 -0
- package/dist/terraform/modules/app/agentcore-harness/scripts/harness-tool-contract.mjs +193 -0
- package/dist/terraform/modules/app/agentcore-harness/scripts/prune_harness.sh +5 -0
- package/dist/terraform/modules/app/agentcore-harness/scripts/read_harness.sh +5 -0
- package/dist/terraform/modules/app/agentcore-harness/scripts/reconcile_harness.sh +5 -0
- package/dist/terraform/modules/app/agentcore-harness/variables.tf +96 -0
- package/dist/terraform/modules/app/agentcore-identity/main.tf +173 -0
- package/dist/terraform/modules/app/agentcore-identity/outputs.tf +51 -0
- package/dist/terraform/modules/app/agentcore-identity/scripts/bootstrap_twenty_oauth_client.sh +122 -0
- package/dist/terraform/modules/app/agentcore-identity/scripts/delete_identity.sh +25 -0
- package/dist/terraform/modules/app/agentcore-identity/scripts/delete_twenty_identity.sh +16 -0
- package/dist/terraform/modules/app/agentcore-identity/scripts/read_identity.sh +40 -0
- package/dist/terraform/modules/app/agentcore-identity/scripts/reconcile_identity.sh +73 -0
- package/dist/terraform/modules/app/agentcore-identity/scripts/reconcile_twenty_identity.sh +74 -0
- package/dist/terraform/modules/app/agentcore-identity/scripts/reconcile_twenty_provider.mjs +61 -0
- package/dist/terraform/modules/app/agentcore-identity/variables.tf +54 -0
- package/dist/terraform/modules/app/agentcore-pi/main.tf +7 -0
- package/dist/terraform/modules/app/appsync-subscriptions/main.tf +127 -21
- package/dist/terraform/modules/app/appsync-subscriptions/outputs.tf +0 -6
- package/dist/terraform/modules/app/appsync-subscriptions/variables.tf +5 -0
- package/dist/terraform/modules/app/capability-broker/main.tf +393 -0
- package/dist/terraform/modules/app/capability-broker/outputs.tf +54 -0
- package/dist/terraform/modules/app/capability-broker/variables.tf +98 -0
- package/dist/terraform/modules/app/deployment-control-plane/runner.py +1011 -31
- package/dist/terraform/modules/app/deployment-control-plane/test_runner_bundle.py +655 -2
- package/dist/terraform/modules/app/lambda-api/agentcore-harness-alarms.tf +88 -0
- package/dist/terraform/modules/app/lambda-api/agentcore-identity-alarms.tf +91 -0
- package/dist/terraform/modules/app/lambda-api/capability-search-alarms.tf +60 -0
- package/dist/terraform/modules/app/lambda-api/handlers.tf +606 -37
- package/dist/terraform/modules/app/lambda-api/iam-grouped.tf +192 -21
- package/dist/terraform/modules/app/lambda-api/main.tf +35 -0
- package/dist/terraform/modules/app/lambda-api/mcp-oauth.tf +181 -4
- package/dist/terraform/modules/app/lambda-api/memory-alarms.tf +92 -0
- package/dist/terraform/modules/app/lambda-api/msteams-app-secrets.tf +46 -0
- package/dist/terraform/modules/app/lambda-api/outputs.tf +35 -0
- package/dist/terraform/modules/app/lambda-api/runtime-config.tf +3 -21
- package/dist/terraform/modules/app/lambda-api/variables.tf +188 -3
- package/dist/terraform/modules/app/workflow-interpreter-stepfunctions/alarms.tf +47 -0
- package/dist/terraform/modules/app/workflow-interpreter-stepfunctions/main.tf +43 -2
- package/dist/terraform/modules/foundation/cognito/main.tf +319 -73
- package/dist/terraform/modules/foundation/cognito/outputs.tf +42 -0
- package/dist/terraform/modules/foundation/cognito/variables.tf +124 -2
- package/dist/terraform/modules/foundation/vpc/outputs.tf +5 -0
- package/dist/terraform/modules/thinkwork/main.tf +396 -26
- package/dist/terraform/modules/thinkwork/outputs.tf +188 -5
- package/dist/terraform/modules/thinkwork/variables.tf +154 -5
- package/dist/terraform/schema.graphql +69 -36
- package/dist/workspace-defaults/files/TOOLS.md +39 -0
- package/dist/workspace-defaults/files/USER.md +1 -0
- package/dist/workspace-defaults/files/agents/analyst/INSTRUCTIONS.md +18 -0
- package/dist/workspace-defaults/files/agents/coding/INSTRUCTIONS.md +11 -0
- package/dist/workspace-defaults/files/agents/research/INSTRUCTIONS.md +12 -0
- package/dist/workspace-defaults/files/agents/reviewer/INSTRUCTIONS.md +15 -0
- package/dist/workspace-defaults/files/skills/document-composer/SKILL.md +6 -2
- package/package.json +6 -3
- package/dist/plugins/workos-auth/package.json +0 -22
- package/dist/plugins/workos-auth/src/index.ts +0 -30
- package/dist/plugins/workos-auth/src/manifest.ts +0 -40
- package/dist/plugins/workos-auth/src/provider-contract.ts +0 -39
- package/dist/plugins/workos-auth/test/manifest.test.ts +0 -94
- package/dist/plugins/workos-auth/tsconfig.json +0 -8
- /package/dist/workspace-defaults/files/{AGENTS.md → INSTRUCTIONS.md} +0 -0
|
@@ -8,6 +8,7 @@ import subprocess
|
|
|
8
8
|
import tarfile
|
|
9
9
|
from hashlib import sha256
|
|
10
10
|
from pathlib import Path
|
|
11
|
+
from types import SimpleNamespace
|
|
11
12
|
|
|
12
13
|
import pytest
|
|
13
14
|
|
|
@@ -94,6 +95,376 @@ def write_drizzle_files(source_dir: Path, names: list[str]) -> None:
|
|
|
94
95
|
(migrations / name).write_text(f"-- {name}\n", encoding="utf-8")
|
|
95
96
|
|
|
96
97
|
|
|
98
|
+
def test_customer_update_requires_explicit_agentcore_pi_source_image() -> None:
|
|
99
|
+
runner = load_runner()
|
|
100
|
+
|
|
101
|
+
with pytest.raises(RuntimeError, match="agentcorePiSourceImageUri"):
|
|
102
|
+
runner.resolve_agentcore_pi_source_image_uri(
|
|
103
|
+
{
|
|
104
|
+
"action": "update",
|
|
105
|
+
"operation": {"kind": "foundation", "action": "update"},
|
|
106
|
+
}
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def test_customer_update_rejects_non_customer_agentcore_pi_source_image() -> None:
|
|
111
|
+
runner = load_runner()
|
|
112
|
+
|
|
113
|
+
with pytest.raises(RuntimeError, match="customer-owned ECR"):
|
|
114
|
+
runner.resolve_agentcore_pi_source_image_uri(
|
|
115
|
+
{
|
|
116
|
+
"action": "update",
|
|
117
|
+
"awsAccountId": "637423202447",
|
|
118
|
+
"awsRegion": "us-east-1",
|
|
119
|
+
"agentcorePiSourceImageUri": (
|
|
120
|
+
"ghcr.io/thinkwork-ai/thinkwork-agentcore@sha256:abc"
|
|
121
|
+
),
|
|
122
|
+
}
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def test_customer_update_accepts_customer_ecr_agentcore_pi_source_image() -> None:
|
|
127
|
+
runner = load_runner()
|
|
128
|
+
image = (
|
|
129
|
+
"637423202447.dkr.ecr.us-east-1.amazonaws.com/thinkwork-tei-e2e-agentcore:pinned@sha256:abc"
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
assert (
|
|
133
|
+
runner.resolve_agentcore_pi_source_image_uri(
|
|
134
|
+
{
|
|
135
|
+
"action": "update",
|
|
136
|
+
"awsAccountId": "637423202447",
|
|
137
|
+
"awsRegion": "us-east-1",
|
|
138
|
+
"agentcorePiSourceImageUri": image,
|
|
139
|
+
}
|
|
140
|
+
)
|
|
141
|
+
== image
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def test_native_auth_reconciliation_builds_route_specific_secret_free_manifest(
|
|
146
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
147
|
+
) -> None:
|
|
148
|
+
runner = load_runner()
|
|
149
|
+
outputs = {
|
|
150
|
+
"user_pool_id": {"value": "us-east-1_Example123"},
|
|
151
|
+
"auth_route_clients": {
|
|
152
|
+
"value": {
|
|
153
|
+
"web:local": {
|
|
154
|
+
"client_id": "1234567890abcdefghijklmnop",
|
|
155
|
+
"route_key": "local",
|
|
156
|
+
"client_family": "web",
|
|
157
|
+
"provider_names": ["COGNITO"],
|
|
158
|
+
"explicit_auth_flows": [
|
|
159
|
+
"ALLOW_USER_PASSWORD_AUTH",
|
|
160
|
+
"ALLOW_USER_SRP_AUTH",
|
|
161
|
+
"ALLOW_REFRESH_TOKEN_AUTH",
|
|
162
|
+
],
|
|
163
|
+
"callback_urls": ["https://app.example/auth/callback"],
|
|
164
|
+
"logout_urls": ["https://app.example"],
|
|
165
|
+
"lifecycle_state": "native",
|
|
166
|
+
},
|
|
167
|
+
"web:google": {
|
|
168
|
+
"client_id": "abcdefghijklmnopqrstuvwxyz",
|
|
169
|
+
"route_key": "google",
|
|
170
|
+
"client_family": "web",
|
|
171
|
+
"provider_names": ["Google"],
|
|
172
|
+
"explicit_auth_flows": ["ALLOW_REFRESH_TOKEN_AUTH"],
|
|
173
|
+
"callback_urls": ["https://app.example/auth/callback"],
|
|
174
|
+
"logout_urls": ["https://app.example"],
|
|
175
|
+
"lifecycle_state": "native",
|
|
176
|
+
},
|
|
177
|
+
"web:microsoft": {
|
|
178
|
+
"client_id": "zyxwvutsrqponmlkjihgfedcba",
|
|
179
|
+
"route_key": "microsoft",
|
|
180
|
+
"client_family": "web",
|
|
181
|
+
"provider_names": ["MicrosoftOrganizations"],
|
|
182
|
+
"explicit_auth_flows": ["ALLOW_REFRESH_TOKEN_AUTH"],
|
|
183
|
+
"callback_urls": ["https://app.example/auth/callback"],
|
|
184
|
+
"logout_urls": ["https://app.example"],
|
|
185
|
+
"lifecycle_state": "native",
|
|
186
|
+
},
|
|
187
|
+
}
|
|
188
|
+
},
|
|
189
|
+
}
|
|
190
|
+
scope = {
|
|
191
|
+
"stage": "dev",
|
|
192
|
+
"account_id": "123456789012",
|
|
193
|
+
"region": "us-east-1",
|
|
194
|
+
"microsoft_oauth_tenant": "00000000-0000-4000-8000-000000000123",
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
payload, state = runner.native_auth_reconciliation_payload(outputs, scope, {})
|
|
198
|
+
|
|
199
|
+
assert payload["revision"] == 1
|
|
200
|
+
assert [item["connectionKey"] for item in payload["connections"]] == [
|
|
201
|
+
"google",
|
|
202
|
+
"local",
|
|
203
|
+
"microsoft:organizations",
|
|
204
|
+
]
|
|
205
|
+
by_route = {item["routeKey"]: item for item in payload["routeClients"]}
|
|
206
|
+
assert by_route["local"]["providerNames"] == ["COGNITO"]
|
|
207
|
+
assert by_route["google"]["providerNames"] == ["Google"]
|
|
208
|
+
assert by_route["microsoft"]["providerNames"] == ["MicrosoftOrganizations"]
|
|
209
|
+
microsoft_connection = next(
|
|
210
|
+
item
|
|
211
|
+
for item in payload["connections"]
|
|
212
|
+
if item["connectionKey"] == "microsoft:organizations"
|
|
213
|
+
)
|
|
214
|
+
assert microsoft_connection["issuerUrl"] == (
|
|
215
|
+
"https://login.microsoftonline.com/00000000-0000-4000-8000-000000000123/v2.0"
|
|
216
|
+
)
|
|
217
|
+
assert "secret" not in json.dumps(payload).lower()
|
|
218
|
+
assert len(payload["manifestFingerprint"]) == 64
|
|
219
|
+
|
|
220
|
+
retry_payload, _ = runner.native_auth_reconciliation_payload(outputs, scope, {})
|
|
221
|
+
assert retry_payload["idempotencyKey"] == payload["idempotencyKey"]
|
|
222
|
+
assert retry_payload["manifestFingerprint"] == payload["manifestFingerprint"]
|
|
223
|
+
|
|
224
|
+
replay, replay_state = runner.native_auth_reconciliation_payload(outputs, scope, state)
|
|
225
|
+
assert replay is None
|
|
226
|
+
assert replay_state == state
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def tenant_entra_operation(action: str, revision: int = 1) -> dict:
|
|
230
|
+
tenant_id = "00000000-0000-4000-8000-000000000123"
|
|
231
|
+
payload = {
|
|
232
|
+
"action": "update",
|
|
233
|
+
"operation": {
|
|
234
|
+
"kind": "identity_provider",
|
|
235
|
+
"action": action,
|
|
236
|
+
"revision": revision,
|
|
237
|
+
"expectedPreviousRevision": revision - 1,
|
|
238
|
+
"connection": {
|
|
239
|
+
"connectionKey": f"microsoft:tenant:{tenant_id}",
|
|
240
|
+
"tenantId": tenant_id,
|
|
241
|
+
"providerName": "Entra_0000000000004000_deadbeef",
|
|
242
|
+
"displayName": "Acme Microsoft",
|
|
243
|
+
"clientId": "entra-client-id",
|
|
244
|
+
"clientSecretRef": (
|
|
245
|
+
"arn:aws:secretsmanager:us-east-1:123456789012:"
|
|
246
|
+
f"secret:thinkwork/dev/auth/entra/{tenant_id}-ABC123"
|
|
247
|
+
),
|
|
248
|
+
"issuerUrl": f"https://login.microsoftonline.com/{tenant_id}/v2.0",
|
|
249
|
+
"tenantBindings": [
|
|
250
|
+
{
|
|
251
|
+
"tenantId": "019f762b-683c-7153-acff-24ee932d73f6",
|
|
252
|
+
"label": "Acme",
|
|
253
|
+
"hostnames": ["Login.Acme.Example", "login.acme.example"],
|
|
254
|
+
}
|
|
255
|
+
],
|
|
256
|
+
},
|
|
257
|
+
},
|
|
258
|
+
}
|
|
259
|
+
if action == "rotate":
|
|
260
|
+
payload["operation"]["connection"]["clientSecretVersionStage"] = "AWSPENDING"
|
|
261
|
+
return payload
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def test_identity_provider_operation_builds_revisioned_secret_free_desired_state() -> None:
|
|
265
|
+
runner = load_runner()
|
|
266
|
+
payload = tenant_entra_operation("create")
|
|
267
|
+
|
|
268
|
+
desired = runner.identity_provider_desired_connections(
|
|
269
|
+
payload,
|
|
270
|
+
stage="dev",
|
|
271
|
+
account_id="123456789012",
|
|
272
|
+
region="us-east-1",
|
|
273
|
+
previous_state={},
|
|
274
|
+
current_outputs={"user_pool_id": {"value": "us-east-1_Example123"}},
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
assert len(desired) == 1
|
|
278
|
+
assert desired[0]["lifecycleState"] == "native"
|
|
279
|
+
assert desired[0]["tenantDirectoryId"] == "00000000-0000-4000-8000-000000000123"
|
|
280
|
+
assert desired[0]["tenantBindings"][0]["hostnames"] == ["login.acme.example"]
|
|
281
|
+
assert runner.tenant_entra_terraform_projection(desired) == [
|
|
282
|
+
{
|
|
283
|
+
"connection_key": "microsoft:tenant:00000000-0000-4000-8000-000000000123",
|
|
284
|
+
"tenant_id": "00000000-0000-4000-8000-000000000123",
|
|
285
|
+
"provider_name": "Entra_0000000000004000_deadbeef",
|
|
286
|
+
"display_name": "Acme Microsoft",
|
|
287
|
+
}
|
|
288
|
+
]
|
|
289
|
+
assert "tenant-super-secret" not in json.dumps(desired)
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def test_identity_provider_disable_removes_route_before_provider_retirement() -> None:
|
|
293
|
+
runner = load_runner()
|
|
294
|
+
created = runner.identity_provider_desired_connections(
|
|
295
|
+
tenant_entra_operation("create"),
|
|
296
|
+
stage="dev",
|
|
297
|
+
account_id="123456789012",
|
|
298
|
+
region="us-east-1",
|
|
299
|
+
previous_state={},
|
|
300
|
+
current_outputs={"user_pool_id": {"value": "us-east-1_Example123"}},
|
|
301
|
+
)
|
|
302
|
+
disabled = runner.identity_provider_desired_connections(
|
|
303
|
+
tenant_entra_operation("disable", revision=2),
|
|
304
|
+
stage="dev",
|
|
305
|
+
account_id="123456789012",
|
|
306
|
+
region="us-east-1",
|
|
307
|
+
previous_state={"desiredRevision": 1, "tenantConnections": created},
|
|
308
|
+
current_outputs={"user_pool_id": {"value": "us-east-1_Example123"}},
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
assert disabled[0]["lifecycleState"] == "denied"
|
|
312
|
+
assert disabled[0]["tenantBindings"][0]["status"] == "disabled"
|
|
313
|
+
assert runner.tenant_entra_terraform_projection(disabled) == []
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def test_identity_provider_operation_rejects_cross_stage_secret_reference() -> None:
|
|
317
|
+
runner = load_runner()
|
|
318
|
+
payload = tenant_entra_operation("create")
|
|
319
|
+
payload["operation"]["connection"]["clientSecretRef"] = (
|
|
320
|
+
"arn:aws:secretsmanager:us-east-1:123456789012:secret:thinkwork/prod/auth/entra/example"
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
with pytest.raises(RuntimeError, match="account, region, stage"):
|
|
324
|
+
runner.identity_provider_desired_connections(
|
|
325
|
+
payload,
|
|
326
|
+
stage="dev",
|
|
327
|
+
account_id="123456789012",
|
|
328
|
+
region="us-east-1",
|
|
329
|
+
previous_state={},
|
|
330
|
+
current_outputs={"user_pool_id": {"value": "us-east-1_Example123"}},
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def test_identity_provider_operation_rejects_invalid_secret_version_stage() -> None:
|
|
335
|
+
runner = load_runner()
|
|
336
|
+
payload = tenant_entra_operation("rotate", revision=2)
|
|
337
|
+
payload["operation"]["connection"]["clientSecretVersionStage"] = "AWS_PENDING"
|
|
338
|
+
|
|
339
|
+
with pytest.raises(RuntimeError, match="must be AWSPENDING"):
|
|
340
|
+
runner.identity_provider_desired_connections(
|
|
341
|
+
payload,
|
|
342
|
+
stage="dev",
|
|
343
|
+
account_id="123456789012",
|
|
344
|
+
region="us-east-1",
|
|
345
|
+
previous_state={"desiredRevision": 1, "tenantConnections": []},
|
|
346
|
+
current_outputs={"user_pool_id": {"value": "us-east-1_Example123"}},
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
def test_identity_provider_operation_rejects_pending_stage_outside_rotation() -> None:
|
|
351
|
+
runner = load_runner()
|
|
352
|
+
payload = tenant_entra_operation("create")
|
|
353
|
+
payload["operation"]["connection"]["clientSecretVersionStage"] = "AWSPENDING"
|
|
354
|
+
|
|
355
|
+
with pytest.raises(RuntimeError, match="valid only.*rotation"):
|
|
356
|
+
runner.identity_provider_desired_connections(
|
|
357
|
+
payload,
|
|
358
|
+
stage="dev",
|
|
359
|
+
account_id="123456789012",
|
|
360
|
+
region="us-east-1",
|
|
361
|
+
previous_state={},
|
|
362
|
+
current_outputs={"user_pool_id": {"value": "us-east-1_Example123"}},
|
|
363
|
+
)
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
def test_identity_provider_rotation_requires_pending_stage() -> None:
|
|
367
|
+
runner = load_runner()
|
|
368
|
+
created = runner.identity_provider_desired_connections(
|
|
369
|
+
tenant_entra_operation("create"),
|
|
370
|
+
stage="dev",
|
|
371
|
+
account_id="123456789012",
|
|
372
|
+
region="us-east-1",
|
|
373
|
+
previous_state={},
|
|
374
|
+
current_outputs={"user_pool_id": {"value": "us-east-1_Example123"}},
|
|
375
|
+
)
|
|
376
|
+
payload = tenant_entra_operation("rotate", revision=2)
|
|
377
|
+
payload["operation"]["connection"].pop("clientSecretVersionStage")
|
|
378
|
+
|
|
379
|
+
with pytest.raises(RuntimeError, match="requires clientSecretVersionStage AWSPENDING"):
|
|
380
|
+
runner.identity_provider_desired_connections(
|
|
381
|
+
payload,
|
|
382
|
+
stage="dev",
|
|
383
|
+
account_id="123456789012",
|
|
384
|
+
region="us-east-1",
|
|
385
|
+
previous_state={"desiredRevision": 1, "tenantConnections": created},
|
|
386
|
+
current_outputs={"user_pool_id": {"value": "us-east-1_Example123"}},
|
|
387
|
+
)
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
def test_identity_provider_update_does_not_require_agent_image_pin() -> None:
|
|
391
|
+
runner = load_runner()
|
|
392
|
+
|
|
393
|
+
assert (
|
|
394
|
+
runner.resolve_agentcore_pi_source_image_uri(
|
|
395
|
+
{"action": "update", "operation": {"kind": "identity_provider"}}
|
|
396
|
+
)
|
|
397
|
+
== ""
|
|
398
|
+
)
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
def test_identity_provider_rotate_reads_pending_secret_and_promotes_only_after_reconcile(
|
|
402
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
403
|
+
) -> None:
|
|
404
|
+
runner = load_runner()
|
|
405
|
+
connection = runner.identity_provider_desired_connections(
|
|
406
|
+
tenant_entra_operation("rotate", revision=2),
|
|
407
|
+
stage="dev",
|
|
408
|
+
account_id="123456789012",
|
|
409
|
+
region="us-east-1",
|
|
410
|
+
previous_state={
|
|
411
|
+
"desiredRevision": 1,
|
|
412
|
+
"tenantConnections": runner.identity_provider_desired_connections(
|
|
413
|
+
tenant_entra_operation("create"),
|
|
414
|
+
stage="dev",
|
|
415
|
+
account_id="123456789012",
|
|
416
|
+
region="us-east-1",
|
|
417
|
+
previous_state={},
|
|
418
|
+
current_outputs={"user_pool_id": {"value": "us-east-1_Example123"}},
|
|
419
|
+
),
|
|
420
|
+
},
|
|
421
|
+
current_outputs={"user_pool_id": {"value": "us-east-1_Example123"}},
|
|
422
|
+
)[0]
|
|
423
|
+
calls = []
|
|
424
|
+
|
|
425
|
+
def fake_output(args, **_kwargs):
|
|
426
|
+
calls.append(args)
|
|
427
|
+
if "get-secret-value" in args:
|
|
428
|
+
return json.dumps({"clientId": "entra-client-id", "clientSecret": "pending-secret"})
|
|
429
|
+
if "describe-secret" in args:
|
|
430
|
+
return json.dumps(
|
|
431
|
+
{"pending-version": ["AWSPENDING"], "current-version": ["AWSCURRENT"]}
|
|
432
|
+
)
|
|
433
|
+
return "{}"
|
|
434
|
+
|
|
435
|
+
monkeypatch.setattr(runner, "output", fake_output)
|
|
436
|
+
assert runner._read_tenant_entra_secret(connection) == "pending-secret"
|
|
437
|
+
assert "AWSPENDING" in calls[0]
|
|
438
|
+
|
|
439
|
+
vars_json = {"auth_tenant_connection_metadata": [connection]}
|
|
440
|
+
promoted = runner.promote_tenant_entra_pending_secret(
|
|
441
|
+
tenant_entra_operation("rotate", revision=2), vars_json
|
|
442
|
+
)
|
|
443
|
+
assert promoted["status"] == "promoted"
|
|
444
|
+
promotion = next(args for args in calls if "update-secret-version-stage" in args)
|
|
445
|
+
assert "pending-version" in promotion
|
|
446
|
+
assert "current-version" in promotion
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
def test_bootstrap_can_fall_back_to_release_agentcore_pi_image(
|
|
450
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
451
|
+
) -> None:
|
|
452
|
+
runner = load_runner()
|
|
453
|
+
monkeypatch.setattr(
|
|
454
|
+
runner,
|
|
455
|
+
"release_runtime_image",
|
|
456
|
+
lambda name: (
|
|
457
|
+
"ghcr.io/thinkwork-ai/thinkwork-agentcore@sha256:abc"
|
|
458
|
+
if name == "agentcore-pi-amd64"
|
|
459
|
+
else ""
|
|
460
|
+
),
|
|
461
|
+
)
|
|
462
|
+
|
|
463
|
+
assert runner.resolve_agentcore_pi_source_image_uri({"action": "deploy"}) == (
|
|
464
|
+
"ghcr.io/thinkwork-ai/thinkwork-agentcore@sha256:abc"
|
|
465
|
+
)
|
|
466
|
+
|
|
467
|
+
|
|
97
468
|
def test_sync_release_artifacts_stages_artifacts_from_platform_bundle(
|
|
98
469
|
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
99
470
|
) -> None:
|
|
@@ -562,6 +933,61 @@ def run_push_database_schema(
|
|
|
562
933
|
return db.events
|
|
563
934
|
|
|
564
935
|
|
|
936
|
+
def test_prepare_additive_schema_before_app_applies_data_before_app_retirement(
|
|
937
|
+
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
938
|
+
) -> None:
|
|
939
|
+
runner = load_runner()
|
|
940
|
+
commands: list[list[str]] = []
|
|
941
|
+
pushed_vars: list[dict] = []
|
|
942
|
+
|
|
943
|
+
def fake_run(command, **_kwargs):
|
|
944
|
+
commands.append(command)
|
|
945
|
+
return SimpleNamespace(returncode=0)
|
|
946
|
+
|
|
947
|
+
monkeypatch.setattr(runner, "TF", tmp_path)
|
|
948
|
+
monkeypatch.setattr(runner, "TERRAFORM_EVIDENCE", {})
|
|
949
|
+
monkeypatch.setattr(runner.subprocess, "run", fake_run)
|
|
950
|
+
monkeypatch.setattr(runner, "output", lambda *_args, **_kwargs: "{}")
|
|
951
|
+
monkeypatch.setattr(
|
|
952
|
+
runner,
|
|
953
|
+
"push_database_schema",
|
|
954
|
+
lambda _outputs_path, vars_json: pushed_vars.append(vars_json),
|
|
955
|
+
)
|
|
956
|
+
|
|
957
|
+
result = runner.prepare_additive_schema_before_app(
|
|
958
|
+
{"action": "deploy"},
|
|
959
|
+
{
|
|
960
|
+
"stage": "prod",
|
|
961
|
+
"auth_retirement_phase": "retired",
|
|
962
|
+
"finalize_auth_retirement": True,
|
|
963
|
+
},
|
|
964
|
+
)
|
|
965
|
+
|
|
966
|
+
assert result.returncode == 0
|
|
967
|
+
assert commands == [
|
|
968
|
+
[
|
|
969
|
+
"terraform",
|
|
970
|
+
"plan",
|
|
971
|
+
"-target=module.thinkwork.module.database",
|
|
972
|
+
"-out=tfplan-data",
|
|
973
|
+
"-no-color",
|
|
974
|
+
],
|
|
975
|
+
["terraform", "apply", "-auto-approve", "-no-color", "tfplan-data"],
|
|
976
|
+
]
|
|
977
|
+
assert pushed_vars == [
|
|
978
|
+
{
|
|
979
|
+
"stage": "prod",
|
|
980
|
+
"auth_retirement_phase": "retired",
|
|
981
|
+
"finalize_auth_retirement": False,
|
|
982
|
+
}
|
|
983
|
+
]
|
|
984
|
+
assert runner.TERRAFORM_EVIDENCE["schemaOrdering"] == {
|
|
985
|
+
"status": "succeeded",
|
|
986
|
+
"target": "module.thinkwork.module.database",
|
|
987
|
+
"phase": "foundation-data-additive-before-app",
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
|
|
565
991
|
def test_push_database_schema_checks_out_release_selected_module_source(
|
|
566
992
|
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
567
993
|
) -> None:
|
|
@@ -648,6 +1074,47 @@ def test_push_database_schema_applies_only_unrecorded_migrations(
|
|
|
648
1074
|
)
|
|
649
1075
|
|
|
650
1076
|
|
|
1077
|
+
@pytest.mark.parametrize(
|
|
1078
|
+
("phase", "finalize", "expected"),
|
|
1079
|
+
[
|
|
1080
|
+
("coexistence", False, []),
|
|
1081
|
+
("coexistence", True, []),
|
|
1082
|
+
("cutover", False, []),
|
|
1083
|
+
("cutover", True, []),
|
|
1084
|
+
("retired", False, []),
|
|
1085
|
+
("retired", True, ["0263_drop_workos_auth_runtime.sql"]),
|
|
1086
|
+
],
|
|
1087
|
+
)
|
|
1088
|
+
def test_push_database_schema_requires_retired_phase_and_explicit_finalization(
|
|
1089
|
+
tmp_path: Path,
|
|
1090
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
1091
|
+
phase: str,
|
|
1092
|
+
finalize: bool,
|
|
1093
|
+
expected: list[str],
|
|
1094
|
+
) -> None:
|
|
1095
|
+
runner = load_runner()
|
|
1096
|
+
monkeypatch.setattr(runner, "POST_SEED_MIGRATIONS", [])
|
|
1097
|
+
db = FakeLedgerDb(tenants_exist=True, ledger=set())
|
|
1098
|
+
run_push_database_schema(
|
|
1099
|
+
runner,
|
|
1100
|
+
tmp_path,
|
|
1101
|
+
monkeypatch,
|
|
1102
|
+
db,
|
|
1103
|
+
["0263_drop_workos_auth_runtime.sql"],
|
|
1104
|
+
migration_bodies={
|
|
1105
|
+
"0263_drop_workos_auth_runtime.sql": ("-- deployment-phase: auth-retired\nSELECT 1;\n")
|
|
1106
|
+
},
|
|
1107
|
+
vars_json={
|
|
1108
|
+
"stage": "tei-e2e",
|
|
1109
|
+
"auth_retirement_phase": phase,
|
|
1110
|
+
"finalize_auth_retirement": finalize,
|
|
1111
|
+
},
|
|
1112
|
+
)
|
|
1113
|
+
|
|
1114
|
+
assert db.applied_files == expected
|
|
1115
|
+
assert ("0263_drop_workos_auth_runtime.sql" in db.ledger) is bool(expected)
|
|
1116
|
+
|
|
1117
|
+
|
|
651
1118
|
def test_push_database_schema_transition_backfills_ledger_via_markers(
|
|
652
1119
|
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
653
1120
|
) -> None:
|
|
@@ -685,6 +1152,40 @@ def test_push_database_schema_transition_backfills_ledger_via_markers(
|
|
|
685
1152
|
assert db.ledger["0001_ancient.sql"] == "transition-assumed"
|
|
686
1153
|
|
|
687
1154
|
|
|
1155
|
+
def test_push_database_schema_transition_applies_post_cutoff_migrations(
|
|
1156
|
+
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
1157
|
+
) -> None:
|
|
1158
|
+
runner = load_runner()
|
|
1159
|
+
db = FakeLedgerDb(
|
|
1160
|
+
tenants_exist=True,
|
|
1161
|
+
ledger=None,
|
|
1162
|
+
present_objects={"public.existing_native_auth_table"},
|
|
1163
|
+
)
|
|
1164
|
+
run_push_database_schema(
|
|
1165
|
+
runner,
|
|
1166
|
+
tmp_path,
|
|
1167
|
+
monkeypatch,
|
|
1168
|
+
db,
|
|
1169
|
+
[
|
|
1170
|
+
"0155_tenant_model_catalog.sql",
|
|
1171
|
+
"0261_native_auth_control_plane.sql",
|
|
1172
|
+
"0262_auth_subscription_tickets.sql",
|
|
1173
|
+
],
|
|
1174
|
+
migration_bodies={
|
|
1175
|
+
"0261_native_auth_control_plane.sql": (
|
|
1176
|
+
"-- creates: public.missing_native_auth_table\nSELECT 1;\n"
|
|
1177
|
+
),
|
|
1178
|
+
"0262_auth_subscription_tickets.sql": (
|
|
1179
|
+
"-- creates: public.existing_native_auth_table\nSELECT 1;\n"
|
|
1180
|
+
),
|
|
1181
|
+
},
|
|
1182
|
+
)
|
|
1183
|
+
|
|
1184
|
+
assert "0261_native_auth_control_plane.sql" in db.applied_files
|
|
1185
|
+
assert db.ledger["0261_native_auth_control_plane.sql"] == "runner"
|
|
1186
|
+
assert db.ledger["0262_auth_subscription_tickets.sql"] == "transition-verified"
|
|
1187
|
+
|
|
1188
|
+
|
|
688
1189
|
def test_push_database_schema_greenfield_records_full_ledger(
|
|
689
1190
|
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
690
1191
|
) -> None:
|
|
@@ -995,6 +1496,7 @@ def _cognito_email_runner_env(runner, tmp_path: Path, monkeypatch: pytest.Monkey
|
|
|
995
1496
|
monkeypatch.setenv("THINKWORK_TERRAFORM_LOCK_TABLE", "thinkwork-locks")
|
|
996
1497
|
monkeypatch.setenv("THINKWORK_RELEASE_ARTIFACT_BUCKET", "thinkwork-artifacts")
|
|
997
1498
|
monkeypatch.setenv("THINKWORK_RELEASE_VERSION", "v0.1.0-canary.150")
|
|
1499
|
+
monkeypatch.setenv("AUTH_MIGRATION_RECOVERY_DEADLINE", "2099-01-01T00:00:00Z")
|
|
998
1500
|
monkeypatch.setenv("THINKWORK_RELEASE_MANIFEST_URL", file_url(manifest_path))
|
|
999
1501
|
monkeypatch.setenv("THINKWORK_RELEASE_MANIFEST_SHA256", manifest_sha)
|
|
1000
1502
|
return tf_dir
|
|
@@ -1101,9 +1603,17 @@ def test_write_runner_files_cognito_email_vars_prefer_runner_secrets_and_default
|
|
|
1101
1603
|
"apiAuthSecret": "api-secret",
|
|
1102
1604
|
"cognitoEmailSourceArn": "arn:aws:ses:us-east-1:637423202447:identity/payload.example.com",
|
|
1103
1605
|
},
|
|
1104
|
-
{
|
|
1606
|
+
{
|
|
1607
|
+
"cognitoEmailSourceArn": secret_arn,
|
|
1608
|
+
"authRetirementPhase": "cutover",
|
|
1609
|
+
},
|
|
1105
1610
|
)
|
|
1106
1611
|
assert vars_json["cognito_email_source_arn"] == secret_arn
|
|
1612
|
+
assert vars_json["auth_retirement_phase"] == "cutover"
|
|
1613
|
+
assert vars_json["finalize_auth_retirement"] is False
|
|
1614
|
+
assert "auth_retirement_phase = var.auth_retirement_phase" in (runner.TF / "main.tf").read_text(
|
|
1615
|
+
encoding="utf-8"
|
|
1616
|
+
)
|
|
1107
1617
|
|
|
1108
1618
|
vars_json_default = runner.write_runner_files(
|
|
1109
1619
|
{
|
|
@@ -1120,6 +1630,150 @@ def test_write_runner_files_cognito_email_vars_prefer_runner_secrets_and_default
|
|
|
1120
1630
|
assert vars_json_default["cognito_reply_to_email_address"] == ""
|
|
1121
1631
|
assert vars_json_default["app_domain"] == ""
|
|
1122
1632
|
assert vars_json_default["app_certificate_arn"] == ""
|
|
1633
|
+
assert vars_json_default["auth_retirement_phase"] == "retired"
|
|
1634
|
+
assert vars_json_default["finalize_auth_retirement"] is False
|
|
1635
|
+
|
|
1636
|
+
vars_json_retired = runner.write_runner_files(
|
|
1637
|
+
{
|
|
1638
|
+
"stage": "tei-e2e",
|
|
1639
|
+
"awsRegion": "us-east-1",
|
|
1640
|
+
"awsAccountId": "637423202447",
|
|
1641
|
+
"dbPassword": "db-secret",
|
|
1642
|
+
"apiAuthSecret": "api-secret",
|
|
1643
|
+
"authRetirementPhase": "retired",
|
|
1644
|
+
"finalizeAuthRetirement": True,
|
|
1645
|
+
},
|
|
1646
|
+
{},
|
|
1647
|
+
)
|
|
1648
|
+
assert vars_json_retired["auth_retirement_phase"] == "retired"
|
|
1649
|
+
assert vars_json_retired["finalize_auth_retirement"] is True
|
|
1650
|
+
|
|
1651
|
+
|
|
1652
|
+
def test_write_runner_files_defaults_existing_preauth_stack_to_coexistence(
|
|
1653
|
+
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
1654
|
+
) -> None:
|
|
1655
|
+
runner = load_runner()
|
|
1656
|
+
_cognito_email_runner_env(runner, tmp_path, monkeypatch)
|
|
1657
|
+
monkeypatch.setattr(
|
|
1658
|
+
runner,
|
|
1659
|
+
"current_terraform_state",
|
|
1660
|
+
lambda _stage: {
|
|
1661
|
+
"outputs": {"user_pool_id": {"value": "us-east-1_Existing"}},
|
|
1662
|
+
"resources": [{"type": "aws_cognito_user_pool", "name": "main"}],
|
|
1663
|
+
},
|
|
1664
|
+
)
|
|
1665
|
+
|
|
1666
|
+
vars_json = runner.write_runner_files(
|
|
1667
|
+
{
|
|
1668
|
+
"stage": "tei-e2e",
|
|
1669
|
+
"awsRegion": "us-east-1",
|
|
1670
|
+
"awsAccountId": "637423202447",
|
|
1671
|
+
"dbPassword": "db-secret",
|
|
1672
|
+
"apiAuthSecret": "api-secret",
|
|
1673
|
+
},
|
|
1674
|
+
{},
|
|
1675
|
+
)
|
|
1676
|
+
|
|
1677
|
+
assert vars_json["auth_retirement_phase"] == "coexistence"
|
|
1678
|
+
assert vars_json["auth_migration_recovery_deadline"] == "2099-01-01T00:00:00Z"
|
|
1679
|
+
|
|
1680
|
+
|
|
1681
|
+
def test_write_runner_files_requires_deadline_for_coexistence(
|
|
1682
|
+
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
1683
|
+
) -> None:
|
|
1684
|
+
runner = load_runner()
|
|
1685
|
+
_cognito_email_runner_env(runner, tmp_path, monkeypatch)
|
|
1686
|
+
monkeypatch.delenv("AUTH_MIGRATION_RECOVERY_DEADLINE")
|
|
1687
|
+
|
|
1688
|
+
with pytest.raises(RuntimeError, match="authMigrationRecoveryDeadline is required"):
|
|
1689
|
+
runner.write_runner_files(
|
|
1690
|
+
{
|
|
1691
|
+
"stage": "tei-e2e",
|
|
1692
|
+
"awsRegion": "us-east-1",
|
|
1693
|
+
"awsAccountId": "637423202447",
|
|
1694
|
+
"dbPassword": "db-secret",
|
|
1695
|
+
"apiAuthSecret": "api-secret",
|
|
1696
|
+
"authRetirementPhase": "coexistence",
|
|
1697
|
+
},
|
|
1698
|
+
{},
|
|
1699
|
+
)
|
|
1700
|
+
|
|
1701
|
+
|
|
1702
|
+
def test_write_runner_files_preserves_deployed_auth_retirement_phase(
|
|
1703
|
+
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
1704
|
+
) -> None:
|
|
1705
|
+
runner = load_runner()
|
|
1706
|
+
_cognito_email_runner_env(runner, tmp_path, monkeypatch)
|
|
1707
|
+
monkeypatch.setattr(
|
|
1708
|
+
runner,
|
|
1709
|
+
"current_terraform_state",
|
|
1710
|
+
lambda _stage: {
|
|
1711
|
+
"outputs": {
|
|
1712
|
+
"auth_retirement_phase": {"value": "cutover"},
|
|
1713
|
+
"user_pool_id": {"value": "us-east-1_Existing"},
|
|
1714
|
+
}
|
|
1715
|
+
},
|
|
1716
|
+
)
|
|
1717
|
+
|
|
1718
|
+
vars_json = runner.write_runner_files(
|
|
1719
|
+
{
|
|
1720
|
+
"stage": "tei-e2e",
|
|
1721
|
+
"awsRegion": "us-east-1",
|
|
1722
|
+
"awsAccountId": "637423202447",
|
|
1723
|
+
"dbPassword": "db-secret",
|
|
1724
|
+
"apiAuthSecret": "api-secret",
|
|
1725
|
+
},
|
|
1726
|
+
{},
|
|
1727
|
+
)
|
|
1728
|
+
|
|
1729
|
+
assert vars_json["auth_retirement_phase"] == "cutover"
|
|
1730
|
+
|
|
1731
|
+
|
|
1732
|
+
def test_main_reconciles_native_auth_before_destructive_schema() -> None:
|
|
1733
|
+
source = Path(__file__).with_name("runner.py").read_text(encoding="utf-8")
|
|
1734
|
+
post_apply = source[source.index("def main():") :]
|
|
1735
|
+
reconciliation = post_apply.index(
|
|
1736
|
+
"CONTROLLER_EVIDENCE[\"authReconciliation\"] = reconcile_native_auth_metadata"
|
|
1737
|
+
)
|
|
1738
|
+
schema = post_apply.index("push_database_schema(outputs_path, vars_json)", reconciliation)
|
|
1739
|
+
|
|
1740
|
+
assert reconciliation < schema
|
|
1741
|
+
|
|
1742
|
+
|
|
1743
|
+
def test_write_runner_files_wires_analyst_lambda_vpc_egress(
|
|
1744
|
+
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
1745
|
+
) -> None:
|
|
1746
|
+
"""analyst_lambda_vpc_egress needs all three wiring points (vars_json,
|
|
1747
|
+
root variable, module argument) and reads runner-secrets first — the
|
|
1748
|
+
durable per-environment home for the flag — with a payload fallback.
|
|
1749
|
+
Secrets Manager JSON strings ("true") must land as real booleans."""
|
|
1750
|
+
runner = load_runner()
|
|
1751
|
+
tf_dir = _cognito_email_runner_env(runner, tmp_path, monkeypatch)
|
|
1752
|
+
base_payload = {
|
|
1753
|
+
"stage": "tei-e2e",
|
|
1754
|
+
"awsRegion": "us-east-1",
|
|
1755
|
+
"awsAccountId": "637423202447",
|
|
1756
|
+
"dbPassword": "db-secret",
|
|
1757
|
+
"apiAuthSecret": "api-secret",
|
|
1758
|
+
}
|
|
1759
|
+
|
|
1760
|
+
assert runner.write_runner_files(base_payload, {})["analyst_lambda_vpc_egress"] is False
|
|
1761
|
+
assert (
|
|
1762
|
+
runner.write_runner_files(base_payload, {"analystLambdaVpcEgress": "true"})[
|
|
1763
|
+
"analyst_lambda_vpc_egress"
|
|
1764
|
+
]
|
|
1765
|
+
is True
|
|
1766
|
+
)
|
|
1767
|
+
assert (
|
|
1768
|
+
runner.write_runner_files({**base_payload, "analystLambdaVpcEgress": True}, {})[
|
|
1769
|
+
"analyst_lambda_vpc_egress"
|
|
1770
|
+
]
|
|
1771
|
+
is True
|
|
1772
|
+
)
|
|
1773
|
+
|
|
1774
|
+
main_tf = (tf_dir / "main.tf").read_text(encoding="utf-8")
|
|
1775
|
+
assert 'variable "analyst_lambda_vpc_egress"' in main_tf
|
|
1776
|
+
assert "= var.analyst_lambda_vpc_egress" in main_tf
|
|
1123
1777
|
|
|
1124
1778
|
|
|
1125
1779
|
def test_write_runner_files_threads_customer_domain_vars_from_payload(
|
|
@@ -2720,7 +3374,6 @@ def test_runtime_profile_contains_customer_authority_metadata(
|
|
|
2720
3374
|
"app_url": {"value": "https://app.example.com"},
|
|
2721
3375
|
"appsync_api_url": {"value": "https://appsync.example.com/graphql"},
|
|
2722
3376
|
"appsync_realtime_url": {"value": "wss://appsync.example.com/graphql"},
|
|
2723
|
-
"appsync_api_key": {"value": "api-key"},
|
|
2724
3377
|
"auth_domain": {"value": "thinkwork-tei-e2e"},
|
|
2725
3378
|
"user_pool_id": {"value": "us-east-1_abc"},
|
|
2726
3379
|
"admin_client_id": {"value": "client-id"},
|