@opengeni/db 0.28.9 → 0.36.0

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.
Files changed (225) hide show
  1. package/dist/attached-browser-devices.d.ts +42 -0
  2. package/dist/attempt-tool-catalogs.d.ts +16 -0
  3. package/dist/browser-auth.d.ts +299 -0
  4. package/dist/browser-downloads.d.ts +51 -0
  5. package/dist/browser-identities.d.ts +137 -0
  6. package/dist/browser-sessions.d.ts +354 -0
  7. package/dist/browser-state-artifacts.d.ts +91 -0
  8. package/dist/canonical-human-identities.d.ts +48 -0
  9. package/dist/canonical-human-identities.js +28 -0
  10. package/dist/canonical-human-identities.js.map +1 -0
  11. package/dist/capability-components.d.ts +37 -0
  12. package/dist/capability-integrations.d.ts +194 -0
  13. package/dist/chunk-5KTIEDX7.js +13200 -0
  14. package/dist/chunk-5KTIEDX7.js.map +1 -0
  15. package/dist/chunk-CQZ4QSP5.js +995 -0
  16. package/dist/chunk-CQZ4QSP5.js.map +1 -0
  17. package/dist/chunk-F32YT7MP.js +117 -0
  18. package/dist/chunk-F32YT7MP.js.map +1 -0
  19. package/dist/chunk-JC5QPLUM.js +666 -0
  20. package/dist/chunk-JC5QPLUM.js.map +1 -0
  21. package/dist/chunk-P3RE4D2B.js +3535 -0
  22. package/dist/chunk-P3RE4D2B.js.map +1 -0
  23. package/dist/chunk-TAUTWOS5.js +259 -0
  24. package/dist/chunk-TAUTWOS5.js.map +1 -0
  25. package/dist/chunk-VKLB2ZGI.js +2389 -0
  26. package/dist/chunk-VKLB2ZGI.js.map +1 -0
  27. package/dist/codemode-operations.d.ts +117 -0
  28. package/dist/codex-token-resolver.d.ts +2 -2
  29. package/dist/company-profile-schema.d.ts +798 -0
  30. package/dist/company-profile.d.ts +268 -0
  31. package/dist/computer-sessions.d.ts +109 -0
  32. package/dist/connection-token-resolver.d.ts +9 -4
  33. package/dist/database.d.ts +57 -3
  34. package/dist/editable-artifact-durable-export.d.ts +153 -0
  35. package/dist/editable-artifact-durable-export.js +788 -0
  36. package/dist/editable-artifact-durable-export.js.map +1 -0
  37. package/dist/editable-artifact-materialization.d.ts +123 -0
  38. package/dist/editable-artifacts-schema.d.ts +4152 -0
  39. package/dist/editable-artifacts.d.ts +610 -0
  40. package/dist/editable-artifacts.js +26 -0
  41. package/dist/editable-artifacts.js.map +1 -0
  42. package/dist/generated-images.d.ts +139 -0
  43. package/dist/index.d.ts +1122 -126
  44. package/dist/index.js +42329 -18407
  45. package/dist/index.js.map +1 -1
  46. package/dist/insights.d.ts +73 -10
  47. package/dist/integration-bindings.d.ts +70 -0
  48. package/dist/integration-facets.d.ts +99 -0
  49. package/dist/interaction-revisions.d.ts +14 -0
  50. package/dist/interaction-schema.d.ts +6036 -0
  51. package/dist/knowledge-source-sync-schema.d.ts +1679 -0
  52. package/dist/knowledge-source-sync.d.ts +274 -0
  53. package/dist/memory-governance.d.ts +2 -2
  54. package/dist/memory-slack-delivery.d.ts +101 -0
  55. package/dist/new-session-drafts.d.ts +2 -2
  56. package/dist/pack-components.d.ts +76 -0
  57. package/dist/pack-installations.d.ts +86 -0
  58. package/dist/persistence-errors.d.ts +3 -3
  59. package/dist/plugin-packages.d.ts +126 -0
  60. package/dist/preference-registry.d.ts +1 -1
  61. package/dist/provision-roles.d.ts +9 -1
  62. package/dist/provision-roles.js +1 -1
  63. package/dist/runtime-posture.d.ts +19 -6
  64. package/dist/schema.d.ts +22654 -9359
  65. package/dist/schema.js +209 -3
  66. package/dist/scoped-knowledge.d.ts +97 -1
  67. package/dist/session-control.d.ts +33 -7
  68. package/dist/session-queue-commands.d.ts +19 -11
  69. package/dist/session-realtime-context.d.ts +2 -2
  70. package/dist/session-realtime-ledger.d.ts +4 -4
  71. package/dist/session-realtime-mirror.d.ts +2 -2
  72. package/dist/session-realtime-state.d.ts +1 -1
  73. package/dist/session-realtime-terminal.d.ts +1 -1
  74. package/dist/session-realtime.d.ts +7 -7
  75. package/dist/session-tenancy.d.ts +44 -0
  76. package/dist/session-tenancy.js +16 -0
  77. package/dist/session-tenancy.js.map +1 -0
  78. package/dist/session-tool-call-settlement.d.ts +1 -1
  79. package/dist/slack-task-policy-schema.d.ts +847 -0
  80. package/dist/slack-task-policy.d.ts +65 -0
  81. package/dist/slack-user-link-access.d.ts +54 -0
  82. package/dist/task-notes-schema.d.ts +924 -0
  83. package/dist/task-notes.d.ts +85 -0
  84. package/dist/transcription-recordings.d.ts +2 -2
  85. package/dist/turn-initiator.d.ts +2 -2
  86. package/dist/video-generation.d.ts +225 -0
  87. package/dist/video-generation.js +68 -0
  88. package/dist/video-generation.js.map +1 -0
  89. package/dist/workspace-artifacts.d.ts +1 -1
  90. package/dist/workspace-instruction-policies.d.ts +1 -1
  91. package/dist/workspace-learning-policy-schema.d.ts +932 -0
  92. package/dist/workspace-learning-policy.d.ts +161 -0
  93. package/dist/xai-subscription.d.ts +326 -0
  94. package/drizzle/0183_model_call_provider_cost_estimates.sql +39 -0
  95. package/drizzle/0184_sandbox_drain_teardown_fence.sql +682 -0
  96. package/drizzle/0185_temporal_schedule_cleanup_outbox.sql +168 -0
  97. package/drizzle/0186_sandbox_capture_provider_contract.sql +400 -0
  98. package/drizzle/0187_generated_image_artifacts.sql +173 -0
  99. package/drizzle/0188_image_generation_retention_failure.sql +25 -0
  100. package/drizzle/0189_retained_session_image_formats.sql +13 -0
  101. package/drizzle/0190_timeline_annotations.sql +31 -0
  102. package/drizzle/0191_editable_artifact_engine.sql +3853 -0
  103. package/drizzle/0192_editable_artifact_live_tickets.sql +338 -0
  104. package/drizzle/0193_editable_artifact_authorization.sql +190 -0
  105. package/drizzle/0194_editable_artifact_durable_exports.sql +160 -0
  106. package/drizzle/0195_editable_artifact_import_authorization.sql +203 -0
  107. package/drizzle/0196_rig_provider_images.sql +152 -0
  108. package/drizzle/0197_knowledge_source_sync_schedules.sql +523 -0
  109. package/drizzle/0198_memory_slack_publication_delivery.sql +393 -0
  110. package/drizzle/0199_workspace_learning_policy.sql +980 -0
  111. package/drizzle/0201_company_profile_authority.sql +849 -0
  112. package/drizzle/0202_document_index_checkpoints.sql +85 -0
  113. package/drizzle/0203_durable_video_generation.sql +353 -0
  114. package/drizzle/0204_video_generation_funding.sql +44 -0
  115. package/drizzle/0205_attempt_tool_catalogs.sql +230 -0
  116. package/drizzle/0206_browser_sessions.sql +646 -0
  117. package/drizzle/0207_browser_identities.sql +379 -0
  118. package/drizzle/0208_attached_browser_devices.sql +165 -0
  119. package/drizzle/0209_computer_sessions.sql +575 -0
  120. package/drizzle/0210_browser_auth_network_interventions.sql +441 -0
  121. package/drizzle/0211_editable_artifact_session_links.sql +58 -0
  122. package/drizzle/0212_browser_state_transfer_hardening.sql +560 -0
  123. package/drizzle/0212_slack_installation_bindings.sql +348 -0
  124. package/drizzle/0213_browser_interaction_authority.sql +67 -0
  125. package/drizzle/0213_slack_user_link_access_requests.sql +177 -0
  126. package/drizzle/0214_browser_download_saves.sql +34 -0
  127. package/drizzle/0214_session_activity_commit_gate.sql +262 -0
  128. package/drizzle/0215_browser_controller_host.sql +385 -0
  129. package/drizzle/0215_capabilities_platform.sql +980 -0
  130. package/drizzle/0216_browser_auth_health_evidence.sql +137 -0
  131. package/drizzle/0216_pack_component_ownership.sql +235 -0
  132. package/drizzle/0217_capability_definition_delete_authority.sql +32 -0
  133. package/drizzle/0217_external_browser_auth_operations.sql +22 -0
  134. package/drizzle/0218_organization_tenancy_foundation.sql +299 -0
  135. package/drizzle/0219_organization_tenancy_managed_human_provisioning.sql +317 -0
  136. package/drizzle/0219_site_auth_maintenance_sessions.sql +234 -0
  137. package/drizzle/0220_memory_slack_append_only_cascade.sql +38 -0
  138. package/drizzle/0220_session_channels.sql +54 -0
  139. package/drizzle/0221_sessions_channel_index.sql +5 -0
  140. package/drizzle/0222_session_visibility_authority_epochs.sql +158 -0
  141. package/drizzle/0222_sessions_channel_fk.sql +89 -0
  142. package/drizzle/0223_pending_tool_event_output.sql +55 -0
  143. package/drizzle/0223_sessions_channel_fk_validate.sql +118 -0
  144. package/drizzle/0224_slack_post_outcome_reconciliation.sql +123 -0
  145. package/drizzle/0225_session_visibility_fork_activation.sql +1363 -0
  146. package/drizzle/0226_personal_codex_authority_foundation.sql +365 -0
  147. package/drizzle/0227_slack_native_actions.sql +145 -0
  148. package/drizzle/0228_interaction_controller_data_plane.sql +14 -0
  149. package/drizzle/0228_slack_task_policy.sql +400 -0
  150. package/drizzle/0229_slack_inbox_file_fact.sql +10 -0
  151. package/drizzle/0230_user_scoped_variable_sets_rigs.sql +84 -0
  152. package/drizzle/0231_integration_definition_identity_cutover.sql +271 -0
  153. package/drizzle/0232_integration_facet_authority_cutover.sql +610 -0
  154. package/drizzle/0233_skill_and_integration_authority_cutover.sql +1162 -0
  155. package/drizzle/0234_xai_subscription_authority.sql +1139 -0
  156. package/drizzle/0235_canonical_human_login_bindings.sql +920 -0
  157. package/drizzle/0236_browser_identity_lifecycle.sql +39 -0
  158. package/drizzle/0236_session_visibility_slack_policy.sql +72 -0
  159. package/drizzle/0237_interaction_transition_reaper.sql +263 -0
  160. package/drizzle/0238_goal_persistence_policy.sql +471 -0
  161. package/drizzle/0238_supergrok_realtime_model.sql +29 -0
  162. package/drizzle/0239_supergrok_video_funding.sql +50 -0
  163. package/drizzle/0239_task_tree_notes.sql +990 -0
  164. package/package.json +26 -5
  165. package/src/attached-browser-devices.ts +371 -0
  166. package/src/attempt-tool-catalogs.ts +131 -0
  167. package/src/browser-auth.ts +4373 -0
  168. package/src/browser-downloads.ts +517 -0
  169. package/src/browser-identities.ts +1385 -0
  170. package/src/browser-sessions.ts +2294 -0
  171. package/src/browser-state-artifacts.ts +449 -0
  172. package/src/canonical-human-identities.ts +317 -0
  173. package/src/capability-components.ts +166 -0
  174. package/src/capability-integrations.ts +1571 -0
  175. package/src/codemode-operations.ts +674 -0
  176. package/src/company-profile-schema.ts +143 -0
  177. package/src/company-profile.ts +968 -0
  178. package/src/computer-sessions.ts +1130 -0
  179. package/src/connection-token-resolver.ts +270 -25
  180. package/src/database.ts +468 -24
  181. package/src/editable-artifact-durable-export.ts +1201 -0
  182. package/src/editable-artifact-materialization.ts +893 -0
  183. package/src/editable-artifacts-schema.ts +879 -0
  184. package/src/editable-artifacts.ts +5404 -0
  185. package/src/generated-images.ts +740 -0
  186. package/src/index.ts +9963 -1465
  187. package/src/insights.ts +251 -23
  188. package/src/integration-bindings.ts +455 -0
  189. package/src/integration-facets.ts +862 -0
  190. package/src/interaction-revisions.ts +70 -0
  191. package/src/interaction-schema.ts +1606 -0
  192. package/src/knowledge-source-sync-schema.ts +192 -0
  193. package/src/knowledge-source-sync.ts +1922 -0
  194. package/src/memory-slack-delivery.ts +801 -0
  195. package/src/pack-components.ts +954 -0
  196. package/src/pack-installations.ts +886 -0
  197. package/src/persistence-errors.ts +4 -7
  198. package/src/plugin-packages.ts +1186 -0
  199. package/src/provision-roles.ts +534 -1
  200. package/src/runtime-posture-cli.ts +9 -0
  201. package/src/runtime-posture.ts +721 -5
  202. package/src/schema.ts +6270 -3159
  203. package/src/scoped-knowledge-schema.ts +1 -1
  204. package/src/scoped-knowledge.ts +655 -3
  205. package/src/session-control.ts +130 -42
  206. package/src/session-queue-commands.ts +265 -15
  207. package/src/session-realtime-context.ts +2 -2
  208. package/src/session-realtime-ledger.ts +3 -3
  209. package/src/session-realtime.ts +7 -7
  210. package/src/session-tenancy.ts +188 -0
  211. package/src/session-tool-call-settlement.ts +18 -3
  212. package/src/slack-task-policy-schema.ts +130 -0
  213. package/src/slack-task-policy.ts +341 -0
  214. package/src/slack-user-link-access.ts +702 -0
  215. package/src/task-notes-schema.ts +135 -0
  216. package/src/task-notes.ts +178 -0
  217. package/src/video-generation.ts +1530 -0
  218. package/src/workspace-artifacts.ts +1 -1
  219. package/src/workspace-learning-policy-schema.ts +139 -0
  220. package/src/workspace-learning-policy.ts +595 -0
  221. package/src/xai-subscription.ts +1887 -0
  222. package/dist/chunk-P6CUHXQZ.js +0 -7248
  223. package/dist/chunk-P6CUHXQZ.js.map +0 -1
  224. package/dist/chunk-VHBFHMPC.js +0 -1219
  225. package/dist/chunk-VHBFHMPC.js.map +0 -1
@@ -0,0 +1,1162 @@
1
+ -- deployment-mode: maintenance
2
+ -- Make normalized Plugin/Version/Facet installations authoritative for Skills,
3
+ -- Plugins, and Integration Definitions, and keep Packs authoritative in their
4
+ -- dedicated installation ledger. Exact active curated Skill selections are
5
+ -- materialized from the reviewed immutable library before every non-MCP row is
6
+ -- retired from the generic catalog/installations compatibility ledger.
7
+
8
+ SET LOCAL lock_timeout = '5s';
9
+ SET LOCAL statement_timeout = '30min';
10
+
11
+ DO $maintenance_preflight_guard$
12
+ BEGIN
13
+ IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app')
14
+ AND EXISTS (
15
+ SELECT 1
16
+ FROM pg_stat_activity
17
+ WHERE datname = current_database()
18
+ AND usename = 'opengeni_app'
19
+ AND pid <> pg_backend_pid()
20
+ )
21
+ THEN
22
+ RAISE EXCEPTION
23
+ 'Capability authority activation requires all opengeni_app sessions to be stopped'
24
+ USING ERRCODE = '55000';
25
+ END IF;
26
+
27
+ IF EXISTS (
28
+ SELECT 1
29
+ FROM capability_operations
30
+ WHERE target_kind IN ('plugin', 'integration', 'skill', 'pack', 'facet_binding')
31
+ AND status IN ('pending', 'running')
32
+ ) THEN
33
+ RAISE EXCEPTION
34
+ 'Capability authority activation requires Plugin, Integration, Skill, Pack, and Facet operations to be settled'
35
+ USING ERRCODE = '55000';
36
+ END IF;
37
+ END
38
+ $maintenance_preflight_guard$;
39
+
40
+ DO $lock_authority$
41
+ DECLARE
42
+ relation_name text;
43
+ BEGIN
44
+ FOREACH relation_name IN ARRAY ARRAY[
45
+ 'capability_catalog_items',
46
+ 'capability_installations',
47
+ 'capability_plugins',
48
+ 'capability_plugin_versions',
49
+ 'capability_facets',
50
+ 'capability_skill_facets',
51
+ 'capability_skill_files',
52
+ 'capability_plugin_installations',
53
+ 'capability_facet_installations',
54
+ 'capability_component_owners',
55
+ 'capability_operations'
56
+ ]
57
+ LOOP
58
+ EXECUTE format('LOCK TABLE %I IN ACCESS EXCLUSIVE MODE', relation_name);
59
+ END LOOP;
60
+ END
61
+ $lock_authority$;
62
+
63
+ CREATE OR REPLACE FUNCTION opengeni_private.skill_authority_canonical_json(value jsonb)
64
+ RETURNS text
65
+ LANGUAGE plpgsql
66
+ IMMUTABLE
67
+ STRICT
68
+ PARALLEL SAFE
69
+ AS $canonical_json$
70
+ DECLARE
71
+ rendered text;
72
+ BEGIN
73
+ CASE jsonb_typeof(value)
74
+ WHEN 'object' THEN
75
+ SELECT COALESCE(
76
+ '{' || string_agg(
77
+ to_jsonb(entry.key)::text || ':' ||
78
+ opengeni_private.skill_authority_canonical_json(entry.value),
79
+ ',' ORDER BY entry.key COLLATE "C"
80
+ ) || '}',
81
+ '{}'
82
+ )
83
+ INTO rendered
84
+ FROM jsonb_each(value) AS entry(key, value);
85
+ RETURN rendered;
86
+ WHEN 'array' THEN
87
+ SELECT COALESCE(
88
+ '[' || string_agg(
89
+ opengeni_private.skill_authority_canonical_json(entry.value),
90
+ ',' ORDER BY entry.ordinality
91
+ ) || ']',
92
+ '[]'
93
+ )
94
+ INTO rendered
95
+ FROM jsonb_array_elements(value) WITH ORDINALITY AS entry(value, ordinality);
96
+ RETURN rendered;
97
+ ELSE
98
+ RETURN value::text;
99
+ END CASE;
100
+ END
101
+ $canonical_json$;
102
+
103
+ -- BEGIN GENERATED CURATED SKILL LIBRARY SEED
104
+ CREATE TEMP TABLE skill_authority_library ON COMMIT DROP AS
105
+ SELECT *
106
+ FROM jsonb_to_recordset(
107
+ $skill_authority_seed$
108
+ [
109
+ {
110
+ "library_id": "azure-verified-modules",
111
+ "capability_id": "skill:azure-verified-modules",
112
+ "plugin_key": "skill/library/azure-verified-modules",
113
+ "version": "1.0.0",
114
+ "name": "azure-verified-modules",
115
+ "description": "Azure Verified Modules (AVM) requirements and best practices for certified Terraform modules.",
116
+ "category": "infrastructure",
117
+ "tags": [
118
+ "skill",
119
+ "infrastructure",
120
+ "terraform",
121
+ "azure",
122
+ "opt-in"
123
+ ],
124
+ "source_url": "https://github.com/hashicorp/agent-skills/tree/de4323afdfbc30d1387f287b55062fa8d82b62e8/terraform/code-generation/skills/azure-verified-modules",
125
+ "repository_url": "https://github.com/hashicorp/agent-skills",
126
+ "source_commit": "de4323afdfbc30d1387f287b55062fa8d82b62e8",
127
+ "source_path": "azure-verified-modules",
128
+ "source_provenance": "Vendored from hashicorp/agent-skills; reviewed OpenGeni curated entry.",
129
+ "content_sha256": "bbc029412fd4893c35cf2a4df6e052efa5583d57d3c26e35d62869dcf4625699",
130
+ "file_count": 1,
131
+ "total_bytes": 16261,
132
+ "license": "MPL-2.0",
133
+ "manifest": {
134
+ "schemaVersion": 1,
135
+ "kind": "skill",
136
+ "source": "library",
137
+ "sourceUrl": "https://github.com/hashicorp/agent-skills/tree/de4323afdfbc30d1387f287b55062fa8d82b62e8/terraform/code-generation/skills/azure-verified-modules",
138
+ "repositoryUrl": "https://github.com/hashicorp/agent-skills",
139
+ "version": "1.0.0",
140
+ "sourceCommit": "de4323afdfbc30d1387f287b55062fa8d82b62e8",
141
+ "sourcePath": "azure-verified-modules",
142
+ "sourceProvenance": "Vendored from hashicorp/agent-skills; reviewed OpenGeni curated entry.",
143
+ "contentSha256": "bbc029412fd4893c35cf2a4df6e052efa5583d57d3c26e35d62869dcf4625699",
144
+ "fileCount": 1,
145
+ "totalBytes": 16261
146
+ },
147
+ "manifest_digest": "5caf400ed9f3a367873229ee207567d34450c75690c27d64a96698c12c680813",
148
+ "files": [
149
+ {
150
+ "path": "SKILL.md",
151
+ "content": "---\nname: azure-verified-modules\ndescription: Azure Verified Modules (AVM) requirements and best practices for developing certified Azure Terraform modules. Use when creating or reviewing Azure modules that need AVM certification.\n---\n\n# Azure Verified Modules (AVM) Requirements\n\nThis guide covers the mandatory requirements for Azure Verified Modules certification. These requirements ensure consistency, quality, and maintainability across Azure Terraform modules.\n\n**References:**\n- [Azure Verified Modules](https://azure.github.io/Azure-Verified-Modules/)\n- [AVM Terraform Requirements](https://azure.github.io/Azure-Verified-Modules/specs/terraform/)\n\n## Table of Contents\n\n- [Module Cross-Referencing](#module-cross-referencing)\n- [Azure Provider Requirements](#azure-provider-requirements)\n- [Code Style Standards](#code-style-standards)\n- [Variable Requirements](#variable-requirements)\n- [Output Requirements](#output-requirements)\n- [Local Values Standards](#local-values-standards)\n- [Terraform Configuration Requirements](#terraform-configuration-requirements)\n- [Testing Requirements](#testing-requirements)\n- [Documentation Requirements](#documentation-requirements)\n- [Breaking Changes & Feature Management](#breaking-changes--feature-management)\n- [Contribution Standards](#contribution-standards)\n- [Compliance Checklist](#compliance-checklist)\n\n---\n\n## Module Cross-Referencing\n\n**Severity:** MUST | **Requirement:** TFFR1\n\nWhen building Resource or Pattern modules, module owners **MAY** cross-reference other modules. However:\n\n- Modules **MUST** be referenced using HashiCorp Terraform registry reference to a pinned version\n - Example: `source = \"Azure/xxx/azurerm\"` with `version = \"1.2.3\"`\n- Modules **MUST NOT** use git references (e.g., `git::https://xxx.yyy/xxx.git` or `github.com/xxx/yyy`)\n- Modules **MUST NOT** contain references to non-AVM modules\n\n---\n\n## Azure Provider Requirements\n\n**Severity:** MUST | **Requirement:** TFFR3\n\nAuthors **MUST** only use the following Azure providers:\n\n| Provider | Min Version | Max Version |\n|----------|-------------|-------------|\n| azapi | >= 2.0 | < 3.0 |\n| azurerm | >= 4.0 | < 5.0 |\n\n**Requirements:**\n\n- Authors **MAY** select either Azurerm, Azapi, or both providers\n- **MUST** use `required_providers` block to enforce provider versions\n- **SHOULD** use pessimistic version constraint operator (`~>`)\n\n**Example:**\n\n```hcl\nterraform {\n required_providers {\n azurerm = {\n source = \"hashicorp/azurerm\"\n version = \"~> 4.0\"\n }\n azapi = {\n source = \"Azure/azapi\"\n version = \"~> 2.0\"\n }\n }\n}\n```\n\n---\n\n## Code Style Standards\n\n### Lower snake_casing\n\n**Severity:** MUST | **Requirement:** TFNFR4\n\n**MUST** use lower snake_casing for:\n\n- Locals\n- Variables\n- Outputs\n- Resources (symbolic names)\n- Modules (symbolic names)\n\nExample: `snake_casing_example`\n\n### Resource & Data Source Ordering\n\n**Severity:** SHOULD | **Requirement:** TFNFR6\n\n- Resources that are depended on **SHOULD** come first\n- Resources with dependencies **SHOULD** be defined close to each other\n\n### Count & for_each Usage\n\n**Severity:** MUST | **Requirement:** TFNFR7\n\n- Use `count` for conditional resource creation\n- **MUST** use `map(xxx)` or `set(xxx)` as resource's `for_each` collection\n- The map's key or set's element **MUST** be static literals\n\n**Example:**\n\n```hcl\nresource \"azurerm_subnet\" \"pair\" {\n for_each = var.subnet_map # map(string)\n name = \"${each.value}-pair\"\n resource_group_name = azurerm_resource_group.example.name\n virtual_network_name = azurerm_virtual_network.example.name\n address_prefixes = [\"10.0.1.0/24\"]\n}\n```\n\n### Resource & Data Block Internal Ordering\n\n**Severity:** SHOULD | **Requirement:** TFNFR8\n\n**Order within resource/data blocks:**\n\n1. **Meta-arguments (top)**:\n - `provider`\n - `count`\n - `for_each`\n\n2. **Arguments/blocks (middle, alphabetical)**:\n - Required arguments\n - Optional arguments\n - Required nested blocks\n - Optional nested blocks\n\n3. **Meta-arguments (bottom)**:\n - `depends_on`\n - `lifecycle` (with sub-order: `create_before_destroy`, `ignore_changes`, `prevent_destroy`)\n\nSeparate sections with blank lines.\n\n### Module Block Ordering\n\n**Severity:** SHOULD | **Requirement:** TFNFR9\n\n**Order within module blocks:**\n\n1. **Top meta-arguments**:\n - `source`\n - `version`\n - `count`\n - `for_each`\n\n2. **Arguments (alphabetical)**:\n - Required arguments\n - Optional arguments\n\n3. **Bottom meta-arguments**:\n - `depends_on`\n - `providers`\n\n### Lifecycle ignore_changes Syntax\n\n**Severity:** MUST | **Requirement:** TFNFR10\n\nThe `ignore_changes` attribute **MUST NOT** be enclosed in double quotes.\n\n**Good:**\n\n```hcl\nlifecycle {\n ignore_changes = [tags]\n}\n```\n\n**Bad:**\n\n```hcl\nlifecycle {\n ignore_changes = [\"tags\"]\n}\n```\n\n### Null Comparison for Conditional Creation\n\n**Severity:** SHOULD | **Requirement:** TFNFR11\n\nFor parameters requiring conditional resource creation, wrap with `object` type to avoid \"known after apply\" issues during plan stage.\n\n**Recommended:**\n\n```hcl\nvariable \"security_group\" {\n type = object({\n id = string\n })\n default = null\n}\n```\n\n### Dynamic Blocks for Optional Nested Objects\n\n**Severity:** MUST | **Requirement:** TFNFR12\n\nNested blocks under conditions **MUST** use this pattern:\n\n```hcl\ndynamic \"identity\" {\n for_each = <condition> ? [<some_item>] : []\n\n content {\n # block content\n }\n}\n```\n\n### Default Values with coalesce/try\n\n**Severity:** SHOULD | **Requirement:** TFNFR13\n\n**Good:**\n\n```hcl\ncoalesce(var.new_network_security_group_name, \"${var.subnet_name}-nsg\")\n```\n\n**Bad:**\n\n```hcl\nvar.new_network_security_group_name == null ? \"${var.subnet_name}-nsg\" : var.new_network_security_group_name\n```\n\n### Provider Declarations in Modules\n\n**Severity:** MUST | **Requirement:** TFNFR27\n\n- `provider` **MUST NOT** be declared in modules (except for `configuration_aliases`)\n- `provider` blocks in modules **MUST** only use `alias`\n- Provider configurations **SHOULD** be passed in by module users\n\n---\n\n## Variable Requirements\n\n### Not Allowed Variables\n\n**Severity:** MUST | **Requirement:** TFNFR14\n\nModule owners **MUST NOT** add variables like `enabled` or `module_depends_on` to control entire module operation. Boolean feature toggles for specific resources are acceptable.\n\n### Variable Definition Order\n\n**Severity:** SHOULD | **Requirement:** TFNFR15\n\nVariables **SHOULD** follow this order:\n\n1. All required fields (alphabetical)\n2. All optional fields (alphabetical)\n\n### Variable Naming Rules\n\n**Severity:** SHOULD | **Requirement:** TFNFR16\n\n- Follow [HashiCorp's naming rules](https://www.terraform.io/docs/extend/best-practices/naming.html)\n- Feature switches **SHOULD** use positive statements: `xxx_enabled` instead of `xxx_disabled`\n\n### Variables with Descriptions\n\n**Severity:** SHOULD | **Requirement:** TFNFR17\n\n- `description` **SHOULD** precisely describe the parameter's purpose and expected data type\n- Target audience is module users, not developers\n- For `object` types, use HEREDOC format\n\n### Variables with Types\n\n**Severity:** MUST | **Requirement:** TFNFR18\n\n- `type` **MUST** be defined for every variable\n- `type` **SHOULD** be as precise as possible\n- `any` **MAY** only be used with adequate reasons\n- Use `bool` instead of `string`/`number` for true/false values\n- Use concrete `object` instead of `map(any)`\n\n### Sensitive Data Variables\n\n**Severity:** SHOULD | **Requirement:** TFNFR19\n\nIf a variable's type is `object` and contains sensitive fields, the entire variable **SHOULD** be `sensitive = true`, or extract sensitive fields into separate variables.\n\n### Non-Nullable Defaults for Collections\n\n**Severity:** SHOULD | **Requirement:** TFNFR20\n\nNullable **SHOULD** be set to `false` for collection values (sets, maps, lists) when using them in loops. For scalar values, null may have semantic meaning.\n\n### Discourage Nullability by Default\n\n**Severity:** MUST | **Requirement:** TFNFR21\n\n`nullable = true` **MUST** be avoided unless there's a specific semantic need for null values.\n\n### Avoid sensitive = false\n\n**Severity:** MUST | **Requirement:** TFNFR22\n\n`sensitive = false` **MUST** be avoided (this is the default).\n\n### Sensitive Default Value Conditions\n\n**Severity:** MUST | **Requirement:** TFNFR23\n\nA default value **MUST NOT** be set for sensitive inputs (e.g., default passwords).\n\n### Handling Deprecated Variables\n\n**Severity:** MUST | **Requirement:** TFNFR24\n\n- Move deprecated variables to `deprecated_variables.tf`\n- Annotate with `DEPRECATED` at the beginning of description\n- Declare the replacement's name\n- Clean up during major version releases\n\n---\n\n## Output Requirements\n\n### Additional Terraform Outputs\n\n**Severity:** SHOULD | **Requirement:** TFFR2\n\nAuthors **SHOULD NOT** output entire resource objects as these may contain sensitive data and the schema can change with API or provider versions.\n\n**Best Practices:**\n\n- Output *computed* attributes of resources as discrete outputs (anti-corruption layer pattern)\n- **SHOULD NOT** output values that are already inputs (except `name`)\n- Use `sensitive = true` for sensitive attributes\n- For resources deployed with `for_each`, output computed attributes in a map structure\n\n**Examples:**\n\n```hcl\n# Single resource computed attribute\noutput \"foo\" {\n description = \"MyResource foo attribute\"\n value = azurerm_resource_myresource.foo\n}\n\n# for_each resources\noutput \"childresource_foos\" {\n description = \"MyResource children's foo attributes\"\n value = {\n for key, value in azurerm_resource_mychildresource : key => value.foo\n }\n}\n\n# Sensitive output\noutput \"bar\" {\n description = \"MyResource bar attribute\"\n value = azurerm_resource_myresource.bar\n sensitive = true\n}\n```\n\n### Sensitive Data Outputs\n\n**Severity:** MUST | **Requirement:** TFNFR29\n\nOutputs containing confidential data **MUST** be declared with `sensitive = true`.\n\n### Handling Deprecated Outputs\n\n**Severity:** MUST | **Requirement:** TFNFR30\n\n- Move deprecated outputs to `deprecated_outputs.tf`\n- Define new outputs in `outputs.tf`\n- Clean up during major version releases\n\n---\n\n## Local Values Standards\n\n### locals.tf Organization\n\n**Severity:** MAY | **Requirement:** TFNFR31\n\n- `locals.tf` **SHOULD** only contain `locals` blocks\n- **MAY** declare `locals` blocks next to resources for advanced scenarios\n\n### Alphabetical Local Arrangement\n\n**Severity:** MUST | **Requirement:** TFNFR32\n\nExpressions in `locals` blocks **MUST** be arranged alphabetically.\n\n### Precise Local Types\n\n**Severity:** SHOULD | **Requirement:** TFNFR33\n\nUse precise types (e.g., `number` for age, not `string`).\n\n---\n\n## Terraform Configuration Requirements\n\n### Terraform Version Requirements\n\n**Severity:** MUST | **Requirement:** TFNFR25\n\n**`terraform.tf` requirements:**\n\n- **MUST** contain only one `terraform` block\n- First line **MUST** define `required_version`\n- **MUST** include minimum version constraint\n- **MUST** include maximum major version constraint\n- **SHOULD** use `~> #.#` or `>= #.#.#, < #.#.#` format\n\n**Example:**\n\n```hcl\nterraform {\n required_version = \"~> 1.6\"\n required_providers {\n azurerm = {\n source = \"hashicorp/azurerm\"\n version = \"~> 4.0\"\n }\n }\n}\n```\n\n### Providers in required_providers\n\n**Severity:** MUST | **Requirement:** TFNFR26\n\n- `terraform` block **MUST** contain `required_providers` block\n- Each provider **MUST** specify `source` and `version`\n- Providers **SHOULD** be sorted alphabetically\n- Only include directly required providers\n- `source` **MUST** be in format `namespace/name`\n- `version` **MUST** include minimum and maximum major version constraints\n- **SHOULD** use `~> #.#` or `>= #.#.#, < #.#.#` format\n\n---\n\n## Testing Requirements\n\n### Test Tooling\n\n**Severity:** MUST | **Requirement:** TFNFR5\n\n**Required testing tools for AVM:**\n\n- Terraform (`terraform validate/fmt/test`)\n- terrafmt\n- Checkov\n- tflint (with azurerm ruleset)\n- Go (optional for custom tests)\n\n### Test Provider Configuration\n\n**Severity:** SHOULD | **Requirement:** TFNFR36\n\nFor robust testing, `prevent_deletion_if_contains_resources` **SHOULD** be explicitly set to `false` in test provider configurations.\n\n---\n\n## Documentation Requirements\n\n### Module Documentation Generation\n\n**Severity:** MUST | **Requirement:** TFNFR2\n\n- Documentation **MUST** be automatically generated via [Terraform Docs](https://github.com/terraform-docs/terraform-docs)\n- A `.terraform-docs.yml` file **MUST** be present in the module root\n\n---\n\n## Breaking Changes & Feature Management\n\n### Using Feature Toggles\n\n**Severity:** MUST | **Requirement:** TFNFR34\n\nNew resources added in minor/patch versions **MUST** have a toggle variable to avoid creation by default:\n\n```hcl\nvariable \"create_route_table\" {\n type = bool\n default = false\n nullable = false\n}\n\nresource \"azurerm_route_table\" \"this\" {\n count = var.create_route_table ? 1 : 0\n # ...\n}\n```\n\n### Reviewing Potential Breaking Changes\n\n**Severity:** MUST | **Requirement:** TFNFR35\n\n**Breaking changes requiring caution:**\n\n**Resource blocks:**\n\n1. Adding new resource without conditional creation\n2. Adding arguments with non-default values\n3. Adding nested blocks without `dynamic`\n4. Renaming resources without `moved` blocks\n5. Changing `count` to `for_each` or vice versa\n\n**Variable/Output blocks:**\n\n1. Deleting/renaming variables\n2. Changing variable `type`\n3. Changing variable `default` values\n4. Changing `nullable` to false\n5. Changing `sensitive` from false to true\n6. Adding variables without `default`\n7. Deleting outputs\n8. Changing output `value`\n9. Changing output `sensitive` value\n\n---\n\n## Contribution Standards\n\n### GitHub Repository Branch Protection\n\n**Severity:** MUST | **Requirement:** TFNFR3\n\nModule owners **MUST** set branch protection policies on the default branch (typically `main`):\n\n1. Require Pull Request before merging\n2. Require approval of most recent reviewable push\n3. Dismiss stale PR approvals when new commits are pushed\n4. Require linear history\n5. Prevent force pushes\n6. Not allow deletions\n7. Require CODEOWNERS review\n8. No bypassing settings allowed\n9. Enforce for administrators\n\n---\n\n## Compliance Checklist\n\nUse this checklist when developing or reviewing Azure Verified Modules:\n\n### Module Structure\n- [ ] Module cross-references use registry sources with pinned versions\n- [ ] Azure providers (azurerm/azapi) versions meet AVM requirements\n- [ ] `.terraform-docs.yml` present in module root\n- [ ] CODEOWNERS file present\n\n### Code Style\n- [ ] All names use lower snake_casing\n- [ ] Resources ordered with dependencies first\n- [ ] `for_each` uses `map()` or `set()` with static keys\n- [ ] Resource/data/module blocks follow proper internal ordering\n- [ ] `ignore_changes` not quoted\n- [ ] Dynamic blocks used for conditional nested objects\n- [ ] `coalesce()` or `try()` used for default values\n\n### Variables\n- [ ] No `enabled` or `module_depends_on` variables\n- [ ] Variables ordered: required (alphabetical) then optional (alphabetical)\n- [ ] All variables have precise types (avoid `any`)\n- [ ] All variables have descriptions\n- [ ] Collections have `nullable = false`\n- [ ] No `sensitive = false` declarations\n- [ ] No default values for sensitive inputs\n- [ ] Deprecated variables moved to `deprecated_variables.tf`\n\n### Outputs\n- [ ] Outputs use anti-corruption layer pattern (discrete attributes)\n- [ ] Sensitive outputs marked `sensitive = true`\n- [ ] Deprecated outputs moved to `deprecated_outputs.tf`\n\n### Terraform Configuration\n- [ ] `terraform.tf` has version constraints (`~>` format)\n- [ ] `required_providers` block present with all providers\n- [ ] No `provider` declarations in module (except aliases)\n- [ ] Locals arranged alphabetically\n\n### Testing & Quality\n- [ ] Required testing tools configured\n- [ ] New resources have feature toggles\n- [ ] Breaking changes reviewed and documented\n\n---\n\n## Summary Statistics\n\n- **Functional Requirements:** 3\n- **Non-Functional Requirements:** 34\n- **Total Requirements:** 37\n\n### By Severity\n- **MUST:** 21 requirements\n- **SHOULD:** 14 requirements\n- **MAY:** 2 requirements\n\n---\n\n*Based on: Azure Verified Modules - Terraform Requirements*\n",
152
+ "byte_size": 16261,
153
+ "content_sha256": "f17d1e7d909797042d71ae1ccfee04a2f5a3d96f4972db8ca005f1173cd40564"
154
+ }
155
+ ]
156
+ },
157
+ {
158
+ "library_id": "checkov",
159
+ "capability_id": "skill:checkov",
160
+ "plugin_key": "skill/library/checkov",
161
+ "version": "1.0.0",
162
+ "name": "checkov",
163
+ "description": "Use Checkov to scan Terraform and infrastructure-as-code repositories, explain findings, apply safe fixes, and verify remediations.",
164
+ "category": "infrastructure",
165
+ "tags": [
166
+ "skill",
167
+ "infrastructure",
168
+ "terraform",
169
+ "security",
170
+ "opt-in"
171
+ ],
172
+ "source_url": "https://github.com/Cloudgeni-ai/opengeni/tree/e9734c4aa062e0421a68acb5650bd5bf33ce2e10/packages/runtime/src/bundled_hashicorp_terraform_skills/checkov",
173
+ "repository_url": "https://github.com/Cloudgeni-ai/opengeni",
174
+ "source_commit": "e9734c4aa062e0421a68acb5650bd5bf33ce2e10",
175
+ "source_path": "checkov",
176
+ "source_provenance": "OpenGeni-authored guidance; reviewed immutable opt-in entry.",
177
+ "content_sha256": "0331b987cd609946c4b95928fec9982b96b0a8614a95e5e628a531efb8ad8577",
178
+ "file_count": 1,
179
+ "total_bytes": 1808,
180
+ "license": "Apache-2.0",
181
+ "manifest": {
182
+ "schemaVersion": 1,
183
+ "kind": "skill",
184
+ "source": "library",
185
+ "sourceUrl": "https://github.com/Cloudgeni-ai/opengeni/tree/e9734c4aa062e0421a68acb5650bd5bf33ce2e10/packages/runtime/src/bundled_hashicorp_terraform_skills/checkov",
186
+ "repositoryUrl": "https://github.com/Cloudgeni-ai/opengeni",
187
+ "version": "1.0.0",
188
+ "sourceCommit": "e9734c4aa062e0421a68acb5650bd5bf33ce2e10",
189
+ "sourcePath": "checkov",
190
+ "sourceProvenance": "OpenGeni-authored guidance; reviewed immutable opt-in entry.",
191
+ "contentSha256": "0331b987cd609946c4b95928fec9982b96b0a8614a95e5e628a531efb8ad8577",
192
+ "fileCount": 1,
193
+ "totalBytes": 1808
194
+ },
195
+ "manifest_digest": "b2717ba6b8fb842b9a9a1bbb102554f471acf42cb6d95519b9ba43c2fb9a3e82",
196
+ "files": [
197
+ {
198
+ "path": "SKILL.md",
199
+ "content": "---\nname: checkov\ndescription: Use Checkov to scan Terraform and infrastructure-as-code repositories for policy violations, explain findings, choose safe fixes, rerun scans, and prepare pull requests with remediations.\n---\n\n# Checkov\n\nUse this skill when the user asks to scan Terraform or infrastructure-as-code for security, compliance, or best-practice issues.\n\n## Workflow\n\n1. Confirm the repository path before scanning. Repository resources are usually mounted under `/workspace/repos/<host>/<owner>/<repo>`; use the session's resolved resource path instead of guessing.\n2. Run Checkov from the repository root or the relevant Terraform subdirectory:\n\n```bash\ncheckov -d . --framework terraform --compact\n```\n\n3. For a machine-readable result that is easier to inspect and summarize, write JSON to a temporary file:\n\n```bash\ncheckov -d . --framework terraform -o json --output-file-path /tmp/checkov-results\n```\n\n4. Summarize the failed checks in plain language. Include the check ID, file, resource, and reason.\n5. When the user asks for fixes, edit only the selected findings. Keep changes focused and preserve the existing Terraform style.\n6. Validate after edits:\n\n```bash\nterraform fmt -recursive\nterraform init -backend=false\nterraform validate\ncheckov -d . --framework terraform --compact\n```\n\n7. If GitHub credentials are available, create a branch and draft pull request for the fix.\n\n## Guardrails\n\n- Do not run `terraform apply` unless the user explicitly asks for it.\n- Prefer `terraform init -backend=false` for validation so remote state is not touched.\n- Do not suppress Checkov findings unless the user asks for a suppression and the reason is documented in code.\n- If provider credentials are missing, still run static checks and explain which validation steps could not be completed.\n",
200
+ "byte_size": 1808,
201
+ "content_sha256": "0e8234d9c4b8c49e65d726441770dc2a418317774d7662a51472d05f59b34124"
202
+ }
203
+ ]
204
+ },
205
+ {
206
+ "library_id": "refactor-module",
207
+ "capability_id": "skill:refactor-module",
208
+ "plugin_key": "skill/library/refactor-module",
209
+ "version": "0.0.1",
210
+ "name": "refactor-module",
211
+ "description": "Transform monolithic Terraform configurations into reusable, maintainable modules following HashiCorp module-design practices.",
212
+ "category": "infrastructure",
213
+ "tags": [
214
+ "skill",
215
+ "infrastructure",
216
+ "terraform",
217
+ "modules",
218
+ "opt-in"
219
+ ],
220
+ "source_url": "https://github.com/hashicorp/agent-skills/tree/de4323afdfbc30d1387f287b55062fa8d82b62e8/terraform/module-generation/skills/refactor-module",
221
+ "repository_url": "https://github.com/hashicorp/agent-skills",
222
+ "source_commit": "de4323afdfbc30d1387f287b55062fa8d82b62e8",
223
+ "source_path": "refactor-module",
224
+ "source_provenance": "Vendored from hashicorp/agent-skills; reviewed immutable opt-in entry.",
225
+ "content_sha256": "cc6d70034c4d11ef6a496c0081ca28219cbd310283d77e327914bcd2f27f3a09",
226
+ "file_count": 1,
227
+ "total_bytes": 13670,
228
+ "license": "MPL-2.0",
229
+ "manifest": {
230
+ "schemaVersion": 1,
231
+ "kind": "skill",
232
+ "source": "library",
233
+ "sourceUrl": "https://github.com/hashicorp/agent-skills/tree/de4323afdfbc30d1387f287b55062fa8d82b62e8/terraform/module-generation/skills/refactor-module",
234
+ "repositoryUrl": "https://github.com/hashicorp/agent-skills",
235
+ "version": "0.0.1",
236
+ "sourceCommit": "de4323afdfbc30d1387f287b55062fa8d82b62e8",
237
+ "sourcePath": "refactor-module",
238
+ "sourceProvenance": "Vendored from hashicorp/agent-skills; reviewed immutable opt-in entry.",
239
+ "contentSha256": "cc6d70034c4d11ef6a496c0081ca28219cbd310283d77e327914bcd2f27f3a09",
240
+ "fileCount": 1,
241
+ "totalBytes": 13670
242
+ },
243
+ "manifest_digest": "4589816f6daff04410ea5cde993981bcbcb75aadbe1400d5f4742d3e4afde74a",
244
+ "files": [
245
+ {
246
+ "path": "SKILL.md",
247
+ "content": "---\nname: refactor-module\ndescription: Transform monolithic Terraform configurations into reusable, maintainable modules following HashiCorp's module design principles and community best practices.\nmetadata:\n copyright: Copyright IBM Corp. 2026\n version: \"0.0.1\"\n---\n\n# Skill: Refactor Module\n\n## Overview\nThis skill guides AI agents in transforming monolithic Terraform configurations into reusable, maintainable modules following HashiCorp's module design principles and community best practices.\n\n## Capability Statement\nThe agent will analyze existing Terraform code and systematically refactor it into well-structured modules with:\n- Clear interface contracts (variables and outputs)\n- Proper encapsulation and abstraction\n- Versioning and documentation\n- Testing frameworks\n- Migration path for existing state\n\n## Prerequisites\n- Existing Terraform configuration to refactor\n- Understanding of resource dependencies\n- Access to current state file (for migration planning)\n- Knowledge of module registry patterns\n\n## Input Parameters\n\n| Parameter | Type | Required | Description |\n|-----------|------|----------|-------------|\n| `source_directory` | string | Yes | Path to existing Terraform configuration |\n| `module_name` | string | Yes | Name for the new module |\n| `abstraction_level` | string | No | \"simple\", \"intermediate\", \"advanced\" (default: intermediate) |\n| `preserve_state` | boolean | Yes | Whether to maintain state compatibility |\n| `target_registry` | string | No | Target module registry (local, private, public) |\n\n## Execution Steps\n\n### 1. Analysis Phase\n```markdown\n**Identify Refactoring Candidates**\n- Group resources by logical function\n- Identify repeated patterns\n- Map resource dependencies\n- Detect configuration coupling\n- Analyze variable usage patterns\n\n**Complexity Assessment**\n- Count resource relationships\n- Measure variable propagation depth\n- Identify cross-resource references\n- Evaluate state migration complexity\n```\n\n### 2. Module Design\n\n#### Interface Design\n```hcl\n# Define clear input contract\nvariable \"network_config\" {\n description = \"Network configuration parameters\"\n type = object({\n cidr_block = string\n availability_zones = list(string)\n enable_nat = bool\n })\n \n validation {\n condition = can(cidrhost(var.network_config.cidr_block, 0))\n error_message = \"CIDR block must be valid IPv4 CIDR.\"\n }\n}\n\n# Define output contract\noutput \"vpc_id\" {\n description = \"ID of the created VPC\"\n value = aws_vpc.main.id\n}\n\noutput \"private_subnet_ids\" {\n description = \"List of private subnet IDs\"\n value = { for k, v in aws_subnet.private : k => v.id }\n}\n```\n\n#### Encapsulation Strategy\n```markdown\n**What to Include in Module:**\n- Tightly coupled resources (VPC + subnets)\n- Resources with shared lifecycle\n- Configuration with clear boundaries\n\n**What to Keep Separate:**\n- Cross-cutting concerns (monitoring, tagging)\n- Resources with different lifecycles\n- Provider-specific configurations\n```\n\n### 3. Code Transformation\n\n#### Before: Monolithic Configuration\n```hcl\n# main.tf (monolithic)\nresource \"aws_vpc\" \"main\" {\n cidr_block = \"10.0.0.0/16\"\n enable_dns_hostnames = true\n \n tags = {\n Name = \"production-vpc\"\n Environment = \"prod\"\n }\n}\n\nresource \"aws_subnet\" \"public_1\" {\n vpc_id = aws_vpc.main.id\n cidr_block = \"10.0.1.0/24\"\n availability_zone = \"us-east-1a\"\n \n tags = {\n Name = \"public-subnet-1\"\n Type = \"public\"\n }\n}\n\nresource \"aws_subnet\" \"public_2\" {\n vpc_id = aws_vpc.main.id\n cidr_block = \"10.0.2.0/24\"\n availability_zone = \"us-east-1b\"\n \n tags = {\n Name = \"public-subnet-2\"\n Type = \"public\"\n }\n}\n\nresource \"aws_internet_gateway\" \"main\" {\n vpc_id = aws_vpc.main.id\n \n tags = {\n Name = \"production-igw\"\n }\n}\n\n# ... more repetitive subnet and routing resources\n```\n\n#### After: Modular Structure\n```hcl\n# modules/vpc/main.tf\nlocals {\n subnet_count = length(var.availability_zones)\n}\n\nresource \"aws_vpc\" \"main\" {\n cidr_block = var.cidr_block\n enable_dns_hostnames = var.enable_dns_hostnames\n enable_dns_support = var.enable_dns_support\n \n tags = merge(\n var.tags,\n {\n Name = var.name\n }\n )\n}\n\nresource \"aws_subnet\" \"public\" {\n for_each = var.create_public_subnets ? toset(var.availability_zones) : []\n \n vpc_id = aws_vpc.main.id\n cidr_block = cidrsubnet(var.cidr_block, 8, index(var.availability_zones, each.value))\n availability_zone = each.value\n map_public_ip_on_launch = true\n \n tags = merge(\n var.tags,\n {\n Name = \"${var.name}-public-${each.value}\"\n Type = \"public\"\n }\n )\n}\n\nresource \"aws_internet_gateway\" \"main\" {\n count = var.create_public_subnets ? 1 : 0\n vpc_id = aws_vpc.main.id\n \n tags = merge(\n var.tags,\n {\n Name = \"${var.name}-igw\"\n }\n )\n}\n\n# modules/vpc/variables.tf\nvariable \"name\" {\n description = \"Name prefix for all resources\"\n type = string\n}\n\nvariable \"cidr_block\" {\n description = \"CIDR block for the VPC\"\n type = string\n \n validation {\n condition = can(cidrhost(var.cidr_block, 0))\n error_message = \"Must be a valid IPv4 CIDR block.\"\n }\n}\n\nvariable \"availability_zones\" {\n description = \"List of availability zones\"\n type = list(string)\n}\n\nvariable \"create_public_subnets\" {\n description = \"Whether to create public subnets\"\n type = bool\n default = true\n}\n\nvariable \"enable_dns_hostnames\" {\n description = \"Enable DNS hostnames in the VPC\"\n type = bool\n default = true\n}\n\nvariable \"enable_dns_support\" {\n description = \"Enable DNS support in the VPC\"\n type = bool\n default = true\n}\n\nvariable \"tags\" {\n description = \"Tags to apply to all resources\"\n type = map(string)\n default = {}\n}\n\n# modules/vpc/outputs.tf\noutput \"vpc_id\" {\n description = \"ID of the VPC\"\n value = aws_vpc.main.id\n}\n\noutput \"vpc_cidr_block\" {\n description = \"CIDR block of the VPC\"\n value = aws_vpc.main.cidr_block\n}\n\noutput \"public_subnet_ids\" {\n description = \"Map of availability zones to public subnet IDs\"\n value = { for k, v in aws_subnet.public : k => v.id }\n}\n\noutput \"internet_gateway_id\" {\n description = \"ID of the internet gateway\"\n value = try(aws_internet_gateway.main[0].id, null)\n}\n\n# Root configuration using module\nmodule \"vpc\" {\n source = \"./modules/vpc\"\n \n name = \"production\"\n cidr_block = \"10.0.0.0/16\"\n availability_zones = [\"us-east-1a\", \"us-east-1b\", \"us-east-1c\"]\n \n tags = {\n Environment = \"production\"\n ManagedBy = \"Terraform\"\n }\n}\n```\n\n### 4. State Migration\n\n#### Generate Migration Plan\n```hcl\n# migration.tf\n# Use moved blocks for state refactoring (Terraform 1.1+)\n\nmoved {\n from = aws_vpc.main\n to = module.vpc.aws_vpc.main\n}\n\nmoved {\n from = aws_subnet.public_1\n to = module.vpc.aws_subnet.public[\"us-east-1a\"]\n}\n\nmoved {\n from = aws_subnet.public_2\n to = module.vpc.aws_subnet.public[\"us-east-1b\"]\n}\n\nmoved {\n from = aws_internet_gateway.main\n to = module.vpc.aws_internet_gateway.main[0]\n}\n```\n\n#### Manual State Migration (Pre-1.1)\n```bash\n# Generate state migration commands\nterraform state mv aws_vpc.main module.vpc.aws_vpc.main\nterraform state mv aws_subnet.public_1 'module.vpc.aws_subnet.public[\"us-east-1a\"]'\nterraform state mv aws_subnet.public_2 'module.vpc.aws_subnet.public[\"us-east-1b\"]'\nterraform state mv aws_internet_gateway.main 'module.vpc.aws_internet_gateway.main[0]'\n```\n\n### 5. Module Documentation\n\n```markdown\n# VPC Module\n\n## Overview\nCreates a VPC with configurable public and private subnets across multiple availability zones.\n\n## Features\n- Multi-AZ subnet deployment\n- Optional NAT gateway configuration\n- VPC Flow Logs integration\n- Customizable CIDR allocation\n\n## Usage\n\n\\`\\`\\`hcl\nmodule \"vpc\" {\n source = \"./modules/vpc\"\n \n name = \"my-vpc\"\n cidr_block = \"10.0.0.0/16\"\n availability_zones = [\"us-east-1a\", \"us-east-1b\"]\n \n create_public_subnets = true\n create_private_subnets = true\n enable_nat_gateway = true\n \n tags = {\n Environment = \"production\"\n }\n}\n\\`\\`\\`\n\n## Requirements\n\n| Name | Version |\n|------|---------|\n| terraform | >= 1.5.0 |\n| aws | ~> 5.0 |\n\n## Inputs\n\n| Name | Description | Type | Default | Required |\n|------|-------------|------|---------|----------|\n| name | Name prefix for resources | `string` | n/a | yes |\n| cidr_block | VPC CIDR block | `string` | n/a | yes |\n| availability_zones | List of AZs | `list(string)` | n/a | yes |\n\n## Outputs\n\n| Name | Description |\n|------|-------------|\n| vpc_id | VPC identifier |\n| public_subnet_ids | Map of public subnet IDs |\n| private_subnet_ids | Map of private subnet IDs |\n\n## Examples\n\nSee [examples/](./examples/) directory for complete usage examples.\n```\n\n### 6. Testing\n\nUse skill terraform-test\n\n**Test File**: A `.tftest.hcl` or `.tftest.json` file containing test configuration and run blocks that validate your Terraform configuration.\n\n**Test Block**: Optional configuration block that defines test-wide settings (available since Terraform 1.6.0).\n\n**Run Block**: Defines a single test scenario with optional variables, provider configurations, and assertions. Each test file requires at least one run block.\n\n**Assert Block**: Contains conditions that must evaluate to true for the test to pass. Failed assertions cause the test to fail.\n\n**Mock Provider**: Simulates provider behavior without creating real infrastructure (available since Terraform 1.7.0).\n\n**Test Modes**: Tests run in apply mode (default, creates real infrastructure) or plan mode (validates logic without creating resources).\n\n#### File Structure\n\nTerraform test files use the `.tftest.hcl` or `.tftest.json` extension and are typically organized in a `tests/` directory. Use clear naming conventions to distinguish between unit tests (plan mode) and integration tests (apply mode):\n\n```\nmy-module/\n├── main.tf\n├── variables.tf\n├── outputs.tf\n└── tests/\n ├── unit_test.tftest.hcl # Unit test (plan mode)\n └── integration_test.tftest.hcl # Integration test (apply mode - creates real resources)\n```\n\n## Refactoring Patterns\n\n### Pattern 1: Resource Grouping\nExtract related resources into cohesive modules:\n- Networking (VPC, Subnets, Route Tables)\n- Compute (ASG, Launch Templates, Load Balancers)\n- Data (RDS, ElastiCache, S3)\n\n### Pattern 2: Configuration Layering\n```hcl\n# Base module with defaults\nmodule \"vpc_base\" {\n source = \"./modules/vpc-base\"\n # Minimal required inputs\n}\n\n# Environment-specific wrapper\nmodule \"vpc_prod\" {\n source = \"./modules/vpc-production\"\n # Inherits from base, adds prod-specific config\n}\n```\n\n### Pattern 3: Composition\n```hcl\n# Small, focused modules\nmodule \"vpc\" {\n source = \"./modules/vpc\"\n}\n\nmodule \"security_groups\" {\n source = \"./modules/security-groups\"\n vpc_id = module.vpc.vpc_id\n}\n\nmodule \"application\" {\n source = \"./modules/application\"\n vpc_id = module.vpc.vpc_id\n subnet_ids = module.vpc.private_subnet_ids\n sg_ids = module.security_groups.app_sg_ids\n}\n```\n\n## Common Pitfalls\n\n### 1. Over-Abstraction\n```hcl\n# ❌ Don't create overly generic modules\nvariable \"resources\" {\n type = map(map(any)) # Too flexible, hard to validate\n}\n\n# ✅ Do use specific, typed interfaces\nvariable \"database_config\" {\n type = object({\n engine = string\n instance_class = string\n })\n}\n```\n\n### 2. Tight Coupling\n```hcl\n# ❌ Don't couple modules through direct references\n# module A\noutput \"instance_id\" { value = aws_instance.app.id }\n\n# module B (in same config)\nresource \"aws_eip\" \"app\" {\n instance = module.a.instance_id # Tight coupling\n}\n\n# ✅ Do pass dependencies through root module\nmodule \"compute\" {\n source = \"./modules/compute\"\n}\n\nresource \"aws_eip\" \"app\" {\n instance = module.compute.instance_id\n}\n```\n\n### 3. State Migration Errors\nAlways test migration in non-production first:\n```bash\n# Create plan to verify no changes after migration\nterraform plan -out=migration.tfplan\n\n# Review carefully\nterraform show migration.tfplan\n\n# Apply only if plan shows no changes\nterraform apply migration.tfplan\n```\n\n## Version Control Strategy\n\n```hcl\n# Use semantic versioning for modules\nmodule \"vpc\" {\n source = \"git::https://github.com/org/terraform-modules.git//vpc?ref=v1.2.0\"\n version = \"~> 1.2\"\n}\n\n# Pin to specific versions in production\n# Use version ranges in development\n```\n\n## Success Criteria\n\n- [ ] Module has single, well-defined responsibility\n- [ ] All variables have descriptions and types\n- [ ] Validation rules prevent invalid configurations\n- [ ] Outputs provide sufficient information for consumers\n- [ ] Documentation includes usage examples\n- [ ] Tests verify module behavior\n- [ ] State migration completed without resource recreation\n- [ ] No plan differences after refactoring\n\n## Related Skills\n- [Terraform code generation](https://raw.githubusercontent.com/hashicorp/agent-skills/refs/heads/main/terraform/code-generation/skills/terraform-style-guide/SKILL.md) - Style guide for the new Terraform Module\n- [Azure Verified Modules](https://raw.githubusercontent.com/hashicorp/agent-skills/refs/heads/main/terraform/code-generation/skills/azure-verified-modules/SKILL.md) - Recommended module specifications for Azure\n\n## Resources\n- [Terraform Module Development](https://developer.hashicorp.com/terraform/language/modules/develop)\n- [Module Best Practices](https://developer.hashicorp.com/terraform/cloud-docs/registry/design)\n\n## Revision History\n\n| Version | Date | Changes |\n|---------|------|---------|\n| 1.0.0 | 2025-11-07 | Initial skill definition |\n",
248
+ "byte_size": 13670,
249
+ "content_sha256": "51357bb86eb46cb8f9afbe1cb04fdfb2355a66d7db4189a7d013c4c6d1bcba4c"
250
+ }
251
+ ]
252
+ },
253
+ {
254
+ "library_id": "social-media-marketing",
255
+ "capability_id": "skill:social-media-marketing",
256
+ "plugin_key": "skill/library/social-media-marketing",
257
+ "version": "1.0.0",
258
+ "name": "social-media-marketing",
259
+ "description": "Analyze connected social accounts, content performance, audience signals, campaigns, and daily media activity without inventing unavailable metrics.",
260
+ "category": "marketing",
261
+ "tags": [
262
+ "skill",
263
+ "marketing",
264
+ "social",
265
+ "analysis",
266
+ "opt-in"
267
+ ],
268
+ "source_url": "https://github.com/Cloudgeni-ai/opengeni/tree/e9734c4aa062e0421a68acb5650bd5bf33ce2e10/packages/runtime/src/bundled_hashicorp_terraform_skills/social-media-marketing",
269
+ "repository_url": "https://github.com/Cloudgeni-ai/opengeni",
270
+ "source_commit": "e9734c4aa062e0421a68acb5650bd5bf33ce2e10",
271
+ "source_path": "social-media-marketing",
272
+ "source_provenance": "OpenGeni-authored guidance; reviewed immutable opt-in entry.",
273
+ "content_sha256": "66893de1fd2110f18d9be69b1e0adb61193e0a736a88f0c0725168465d2b06a3",
274
+ "file_count": 1,
275
+ "total_bytes": 1844,
276
+ "license": "Apache-2.0",
277
+ "manifest": {
278
+ "schemaVersion": 1,
279
+ "kind": "skill",
280
+ "source": "library",
281
+ "sourceUrl": "https://github.com/Cloudgeni-ai/opengeni/tree/e9734c4aa062e0421a68acb5650bd5bf33ce2e10/packages/runtime/src/bundled_hashicorp_terraform_skills/social-media-marketing",
282
+ "repositoryUrl": "https://github.com/Cloudgeni-ai/opengeni",
283
+ "version": "1.0.0",
284
+ "sourceCommit": "e9734c4aa062e0421a68acb5650bd5bf33ce2e10",
285
+ "sourcePath": "social-media-marketing",
286
+ "sourceProvenance": "OpenGeni-authored guidance; reviewed immutable opt-in entry.",
287
+ "contentSha256": "66893de1fd2110f18d9be69b1e0adb61193e0a736a88f0c0725168465d2b06a3",
288
+ "fileCount": 1,
289
+ "totalBytes": 1844
290
+ },
291
+ "manifest_digest": "2b29da81bab99cd735802c27a3b32ffd67860c0be136ef5f547bd621d10822a1",
292
+ "files": [
293
+ {
294
+ "path": "SKILL.md",
295
+ "content": "---\nname: social-media-marketing\ndescription: Use when running marketing, social media, content performance, audience signal, campaign reporting, or daily media analysis tasks through OpenGeni social account connectors and MCP tools.\n---\n\n# Social Media Marketing\n\nUse this skill for scheduled or ad hoc marketing analysis over connected social media accounts.\n\n## Workflow\n\n1. Call `opengeni__social_daily_analysis_context` first with the selected `connectionIds`, `documentBaseIds`, and a 24 hour window unless the user requested another window.\n2. If you need narrower post data, call `opengeni__social_posts_recent` with explicit connection IDs and date bounds.\n3. If document base IDs are available, use the docs MCP search tools for brand voice, campaign calendars, audience research, messaging rules, and reporting definitions.\n4. Produce a report with:\n - Executive summary\n - Notable account changes\n - Winning posts\n - Underperforming posts\n - Audience and content signals\n - Recommended actions for the next 24 hours\n - Data gaps and caveats\n\n## Analysis Rules\n\n- Use only metrics, posts, account data, and document snippets returned by tools.\n- Do not invent impressions, engagement, conversions, sentiment, follower counts, or platform capabilities.\n- Treat missing metrics as missing data and say what integration or provider sync would be needed.\n- Compare posts with like-for-like metrics from the same platform when possible.\n- Keep recommendations concrete: target account, content theme, suggested action, expected signal to monitor.\n- Separate observation from recommendation.\n\n## Output Style\n\nPrefer a concise structured report. Include exact post URLs or external IDs when available. If there are no posts in the window, provide account-level gaps and next data collection steps instead of filler analysis.\n",
296
+ "byte_size": 1844,
297
+ "content_sha256": "d2da2bd1d6fb00ae6386a3ff0633f469952dc69344b684c8bfd95dc87ed7ff4c"
298
+ }
299
+ ]
300
+ },
301
+ {
302
+ "library_id": "terraform-search-import",
303
+ "capability_id": "skill:terraform-search-import",
304
+ "plugin_key": "skill/library/terraform-search-import",
305
+ "version": "0.1.0",
306
+ "name": "terraform-search-import",
307
+ "description": "Discover existing cloud resources with Terraform Search and bring supported resources under Terraform management.",
308
+ "category": "infrastructure",
309
+ "tags": [
310
+ "skill",
311
+ "infrastructure",
312
+ "terraform",
313
+ "import",
314
+ "opt-in"
315
+ ],
316
+ "source_url": "https://github.com/hashicorp/agent-skills/tree/de4323afdfbc30d1387f287b55062fa8d82b62e8/terraform/code-generation/skills/terraform-search-import",
317
+ "repository_url": "https://github.com/hashicorp/agent-skills",
318
+ "source_commit": "de4323afdfbc30d1387f287b55062fa8d82b62e8",
319
+ "source_path": "terraform-search-import",
320
+ "source_provenance": "Vendored from hashicorp/agent-skills; reviewed immutable opt-in entry.",
321
+ "content_sha256": "994d7a48dd6a610daa8a4dbdf4b0f0e52eaf8662a509b6a163bc6e76611227f9",
322
+ "file_count": 3,
323
+ "total_bytes": 12604,
324
+ "license": "MPL-2.0",
325
+ "manifest": {
326
+ "schemaVersion": 1,
327
+ "kind": "skill",
328
+ "source": "library",
329
+ "sourceUrl": "https://github.com/hashicorp/agent-skills/tree/de4323afdfbc30d1387f287b55062fa8d82b62e8/terraform/code-generation/skills/terraform-search-import",
330
+ "repositoryUrl": "https://github.com/hashicorp/agent-skills",
331
+ "version": "0.1.0",
332
+ "sourceCommit": "de4323afdfbc30d1387f287b55062fa8d82b62e8",
333
+ "sourcePath": "terraform-search-import",
334
+ "sourceProvenance": "Vendored from hashicorp/agent-skills; reviewed immutable opt-in entry.",
335
+ "contentSha256": "994d7a48dd6a610daa8a4dbdf4b0f0e52eaf8662a509b6a163bc6e76611227f9",
336
+ "fileCount": 3,
337
+ "totalBytes": 12604
338
+ },
339
+ "manifest_digest": "177b99b6b0785fc8acbdd715afee203433a184fc4d07d824bca38bf0120245bc",
340
+ "files": [
341
+ {
342
+ "path": "SKILL.md",
343
+ "content": "---\nname: terraform-search-import\ndescription: Discover existing cloud resources using Terraform Search queries and bulk import them into Terraform management. Use when bringing unmanaged infrastructure under Terraform control, auditing cloud resources, or migrating to IaC.\nmetadata:\n copyright: Copyright IBM Corp. 2026\n version: \"0.1.0\"\ncompatibility: Requires Terraform >= 1.14 and providers with list resource support (always use latest provider version)\n---\n\n# Terraform Search and Bulk Import\n\nDiscover existing cloud resources using declarative queries and generate configuration for bulk import into Terraform state.\n\n**References:**\n- [Terraform Search - list block](https://developer.hashicorp.com/terraform/language/block/tfquery/list)\n- [Bulk Import](https://developer.hashicorp.com/terraform/language/import/bulk)\n\n## When to Use\n\n- Bringing unmanaged resources under Terraform control\n- Auditing existing cloud infrastructure\n- Migrating from manual provisioning to IaC\n- Discovering resources across multiple regions/accounts\n\n## IMPORTANT: Check Provider Support First\n\n**BEFORE starting, you MUST verify the target resource type is supported:**\n\n```bash\n# Check what list resources are available\n./scripts/list_resources.sh aws # Specific provider\n./scripts/list_resources.sh # All configured providers\n```\n\n## Decision Tree\n\n1. **Identify target resource type** (e.g., aws_s3_bucket, aws_instance)\n2. **Check if supported**: Run `./scripts/list_resources.sh <provider>`\n3. **Choose workflow**:\n - ** If supported**: Check for terraform version available.\n - ** If terraform version is above 1.14.0** Use Terraform Search workflow (below)\n - ** If not supported or terraform version is below 1.14.0 **: Use Manual Discovery workflow (see [references/MANUAL-IMPORT.md](references/MANUAL-IMPORT.md))\n \n **Note**: The list of supported resources is rapidly expanding. Always verify current support before using manual import.\n\n## Prerequisites\n\nBefore writing queries, verify the provider supports list resources for your target resource type.\n\n### Discover Available List Resources\n\nRun the helper script to extract supported list resources from your provider:\n\n```bash\n# From a directory with provider configuration (runs terraform init if needed)\n./scripts/list_resources.sh aws # Specific provider\n./scripts/list_resources.sh # All configured providers\n```\n\nOr manually query the provider schema:\n\n```bash\nterraform providers schema -json | jq '.provider_schemas | to_entries | map({key: (.key | split(\"/\")[-1]), value: (.value.list_resource_schemas // {} | keys)})'\n```\n\nTerraform Search requires an initialized working directory. Ensure you have a configuration with the required provider before running queries:\n\n```hcl\n# terraform.tf\nterraform {\n required_providers {\n aws = {\n source = \"hashicorp/aws\"\n version = \"~> 6.0\"\n }\n }\n}\n```\n\nRun `terraform init` to download the provider, then proceed with queries.\n\n## Terraform Search Workflow (Supported Resources Only)\n\n1. Create `.tfquery.hcl` files with `list` blocks defining search queries\n2. Run `terraform query` to discover matching resources\n3. Generate configuration with `-generate-config-out=<file>`\n4. Review and refine generated `resource` and `import` blocks\n5. Run `terraform plan` and `terraform apply` to import\n\n## Query File Structure\n\nQuery files use `.tfquery.hcl` extension and support:\n- `provider` blocks for authentication\n- `list` blocks for resource discovery\n- `variable` and `locals` blocks for parameterization\n\n```hcl\n# discovery.tfquery.hcl\nprovider \"aws\" {\n region = \"us-west-2\"\n}\n\nlist \"aws_instance\" \"all\" {\n provider = aws\n}\n```\n\n## List Block Syntax\n\n```hcl\nlist \"<list_type>\" \"<symbolic_name>\" {\n provider = <provider_reference> # Required\n\n # Optional: filter configuration (provider-specific)\n # The `config` block schema is provider-specific. Discover available options using `terraform providers schema -json | jq '.provider_schemas.\"registry.terraform.io/hashicorp/<provider>\".list_resource_schemas.\"<resource_type>\"'`\n\n config {\n filter {\n name = \"<filter_name>\"\n values = [\"<value1>\", \"<value2>\"]\n }\n region = \"<region>\" # AWS-specific\n }\n # Optional: limit results\n limit = 100\n}\n```\n\n## Supported List Resources\n\nProvider support for list resources varies by version. **Always check what's available for your specific provider version using the discovery script.**\n\n## Query Examples\n\n### Basic Discovery\n\n```hcl\n# Find all EC2 instances in configured region\nlist \"aws_instance\" \"all\" {\n provider = aws\n}\n```\n\n### Filtered Discovery\n\n```hcl\n# Find instances by tag\nlist \"aws_instance\" \"production\" {\n provider = aws\n \n config {\n filter {\n name = \"tag:Environment\"\n values = [\"production\"]\n }\n }\n}\n\n# Find instances by type\nlist \"aws_instance\" \"large\" {\n provider = aws\n \n config {\n filter {\n name = \"instance-type\"\n values = [\"t3.large\", \"t3.xlarge\"]\n }\n }\n}\n```\n\n### Multi-Region Discovery\n\n```hcl\nprovider \"aws\" {\n region = \"us-west-2\"\n}\n\nlocals {\n regions = [\"us-west-2\", \"us-east-1\", \"eu-west-1\"]\n}\n\nlist \"aws_instance\" \"all_regions\" {\n for_each = toset(local.regions)\n provider = aws\n \n config {\n region = each.value\n }\n}\n```\n\n### Parameterized Queries\n\n```hcl\nvariable \"target_environment\" {\n type = string\n default = \"staging\"\n}\n\nlist \"aws_instance\" \"by_env\" {\n provider = aws\n \n config {\n filter {\n name = \"tag:Environment\"\n values = [var.target_environment]\n }\n }\n}\n```\n\n## Running Queries\n\n```bash\n# Execute queries and display results\nterraform query\n\n# Generate configuration file\nterraform query -generate-config-out=imported.tf\n\n# Pass variables\nterraform query -var='target_environment=production'\n```\n\n## Query Output Format\n\n```\nlist.aws_instance.all account_id=123456789012,id=i-0abc123,region=us-west-2 web-server\n```\n\nColumns: `<query_address> <identity_attributes> <name_tag>`\n\n## Generated Configuration\n\nThe `-generate-config-out` flag creates:\n\n```hcl\n# __generated__ by Terraform\nresource \"aws_instance\" \"all_0\" {\n ami = \"ami-0c55b159cbfafe1f0\"\n instance_type = \"t2.micro\"\n # ... all attributes\n}\n\nimport {\n to = aws_instance.all_0\n provider = aws\n identity = {\n account_id = \"123456789012\"\n id = \"i-0abc123\"\n region = \"us-west-2\"\n }\n}\n```\n\n## Post-Generation Cleanup\n\nGenerated configuration includes all attributes. Clean up by:\n\n1. Remove computed/read-only attributes\n2. Replace hardcoded values with variables\n3. Add proper resource naming\n4. Organize into appropriate files\n\n```hcl\n# Before: generated\nresource \"aws_instance\" \"all_0\" {\n ami = \"ami-0c55b159cbfafe1f0\"\n instance_type = \"t2.micro\"\n arn = \"arn:aws:ec2:...\" # Remove - computed\n id = \"i-0abc123\" # Remove - computed\n # ... many more attributes\n}\n\n# After: cleaned\nresource \"aws_instance\" \"web_server\" {\n ami = var.ami_id\n instance_type = var.instance_type\n subnet_id = var.subnet_id\n \n tags = {\n Name = \"web-server\"\n Environment = var.environment\n }\n}\n```\n\n## Import by Identity\n\nGenerated imports use identity-based import (Terraform 1.12+):\n\n```hcl\nimport {\n to = aws_instance.web\n provider = aws\n identity = {\n account_id = \"123456789012\"\n id = \"i-0abc123\"\n region = \"us-west-2\"\n }\n}\n```\n\n## Best Practices\n\n### Query Design\n- Start broad, then add filters to narrow results\n- Use `limit` to prevent overwhelming output\n- Test queries before generating configuration\n\n### Configuration Management\n- Review all generated code before applying\n- Remove unnecessary default values\n- Use consistent naming conventions\n- Add proper variable abstraction\n\n## Troubleshooting\n\n| Issue | Solution |\n|-------|----------|\n| \"No list resources found\" | Check provider version supports list resources |\n| Query returns empty | Verify region and filter values |\n| Generated config has errors | Remove computed attributes, fix deprecated arguments |\n| Import fails | Ensure resource not already in state |\n\n## Complete Example\n\n```hcl\n# main.tf - Initialize provider\nterraform {\n required_version = \">= 1.14\"\n required_providers {\n aws = {\n source = \"hashicorp/aws\"\n version = \"~> 6.0\" # Always use latest version\n }\n }\n}\n\n# discovery.tfquery.hcl - Define queries\nprovider \"aws\" {\n region = \"us-west-2\"\n}\n\nlist \"aws_instance\" \"team_instances\" {\n provider = aws\n \n config {\n filter {\n name = \"tag:Owner\"\n values = [\"platform\"]\n }\n filter {\n name = \"instance-state-name\"\n values = [\"running\"]\n }\n }\n \n limit = 50\n}\n```\n\n```bash\n# Execute workflow\nterraform init\nterraform query\nterraform query -generate-config-out=generated.tf\n# Review and clean generated.tf\nterraform plan\nterraform apply\n```\n",
344
+ "byte_size": 8924,
345
+ "content_sha256": "113efa26c15f3a6dbcae0db92b3b58943c9af36bd0ce55a7e2f9be1a6f4c2133"
346
+ },
347
+ {
348
+ "path": "references/MANUAL-IMPORT.md",
349
+ "content": "# Manual Terraform Import Reference\n\nUse this workflow when your target resource type isn't supported by Terraform Search.\n\n## 1. Discover Resources Using Provider CLI\n\nAWS CLI examples:\n\n```bash\n# RDS instances (not yet supported by Terraform Search)\naws rds describe-db-instances --query 'DBInstances[].DBInstanceIdentifier'\n\n# DynamoDB tables (not yet supported by Terraform Search)\naws dynamodb list-tables --query 'TableNames[]'\n\n# API Gateway REST APIs (not yet supported by Terraform Search)\naws apigateway get-rest-apis --query 'items[].id'\n\n# SNS topics (not yet supported by Terraform Search)\naws sns list-topics --query 'Topics[].TopicArn'\n```\n\n## 2. Create Resource Blocks Manually\n\n```hcl\n# Example for RDS instance\nresource \"aws_db_instance\" \"existing_db\" {\n identifier = \"my-existing-db\"\n # Add other required attributes\n}\n\n# Example for DynamoDB table\nresource \"aws_dynamodb_table\" \"existing_table\" {\n name = \"my-existing-table\"\n # Add other required attributes\n}\n\n# Example for SNS topic\nresource \"aws_sns_topic\" \"existing_topic\" {\n name = \"my-existing-topic\"\n}\n```\n\n## 3. Create Import Blocks (Config-Driven Import)\n\n```hcl\n# Example for RDS instance\nresource \"aws_db_instance\" \"existing_db\" {\n identifier = \"my-existing-db\"\n # Add other required attributes\n}\n\nimport {\n to = aws_db_instance.existing_db\n id = \"my-existing-db\"\n}\n\n# Example for DynamoDB table\nresource \"aws_dynamodb_table\" \"existing_table\" {\n name = \"my-existing-table\"\n # Add other required attributes\n}\n\nimport {\n to = aws_dynamodb_table.existing_table\n id = \"my-existing-table\"\n}\n```\n\n## 4. Run Import Plan\n\n```bash\n# Plan the import to see what will happen\nterraform plan\n\n# Apply to import the resources\nterraform apply\n```\n\n## Bulk Import Script Example\n\nFor multiple resources of the same type:\n\n```bash\n#!/bin/bash\n# bulk-import-dynamodb.sh\n\n# Get all table names\ntables=$(aws dynamodb list-tables --query 'TableNames[]' --output text)\n\n# Generate import configuration\ncat > dynamodb-imports.tf << 'EOF'\n# DynamoDB Table Resources and Imports\nEOF\n\nfor table in $tables; do\n # Create resource and import blocks\n cat >> dynamodb-imports.tf << EOF\nresource \"aws_dynamodb_table\" \"table_${table//[-.]/_}\" {\n name = \"$table\"\n}\n\nimport {\n to = aws_dynamodb_table.table_${table//[-.]/_}\n id = \"$table\"\n}\n\nEOF\ndone\n\necho \"Generated dynamodb-imports.tf with import blocks\"\necho \"Run 'terraform plan' to review, then 'terraform apply' to import\"\n```\n",
350
+ "byte_size": 2449,
351
+ "content_sha256": "705db1d5286a090e73cec26cd308b5add4f500a8332193a4e2a2fd7e83a8f220"
352
+ },
353
+ {
354
+ "path": "scripts/list_resources.sh",
355
+ "content": "#!/bin/bash\n# Copyright IBM Corp. 2025, 2026\n# SPDX-License-Identifier: MPL-2.0\n\n# Extract list resources supported by Terraform providers\n# Usage: ./list_resources.sh [provider_name]\n# Requires: terraform, jq\n# Note: Run from an initialized Terraform directory (terraform init)\n\nset -e\n\nPROVIDER=$1\n\n# Ensure terraform is initialized\nif [ ! -d \".terraform\" ]; then\n echo \"Initializing Terraform...\" >&2\n terraform init -upgrade > /dev/null 2>&1\nfi\n\n# Get provider schema and extract list_resource_schemas\nif [ -n \"$PROVIDER\" ]; then\n # Specific provider\n provider_key=$(terraform providers schema -json 2>/dev/null | jq -r '.provider_schemas | keys[]' | grep \"/${PROVIDER}$\" || true)\n if [ -n \"$provider_key\" ]; then\n terraform providers schema -json 2>/dev/null | jq -r \\\n \"{\\\"$PROVIDER\\\": (.provider_schemas.\\\"${provider_key}\\\" | .list_resource_schemas // {} | keys | sort)}\"\n else\n echo \"{\\\"$PROVIDER\\\": []}\"\n fi\nelse\n # All providers\n terraform providers schema -json 2>/dev/null | jq -r '\n .provider_schemas\n | to_entries\n | map({key: (.key | split(\"/\")[-1]), value: (.value.list_resource_schemas // {} | keys | sort)})\n | from_entries\n '\nfi\n",
356
+ "byte_size": 1231,
357
+ "content_sha256": "8325fbf9e82e8b4ab1d281c02ad7058220a82878278a7f004c92f899167c79ea"
358
+ }
359
+ ]
360
+ },
361
+ {
362
+ "library_id": "terraform-stacks",
363
+ "capability_id": "skill:terraform-stacks",
364
+ "plugin_key": "skill/library/terraform-stacks",
365
+ "version": "0.0.1",
366
+ "name": "terraform-stacks",
367
+ "description": "Create, modify, validate, and troubleshoot Terraform Stack component and deployment configurations.",
368
+ "category": "infrastructure",
369
+ "tags": [
370
+ "skill",
371
+ "infrastructure",
372
+ "terraform",
373
+ "stacks",
374
+ "opt-in"
375
+ ],
376
+ "source_url": "https://github.com/hashicorp/agent-skills/tree/de4323afdfbc30d1387f287b55062fa8d82b62e8/terraform/code-generation/skills/terraform-stacks",
377
+ "repository_url": "https://github.com/hashicorp/agent-skills",
378
+ "source_commit": "de4323afdfbc30d1387f287b55062fa8d82b62e8",
379
+ "source_path": "terraform-stacks",
380
+ "source_provenance": "Vendored from hashicorp/agent-skills; reviewed immutable opt-in entry.",
381
+ "content_sha256": "0a6244ecddf1cce0357db41b41b3b20a1bfa71f331092ebc8bbd15e649733d35",
382
+ "file_count": 7,
383
+ "total_bytes": 104793,
384
+ "license": "MPL-2.0",
385
+ "manifest": {
386
+ "schemaVersion": 1,
387
+ "kind": "skill",
388
+ "source": "library",
389
+ "sourceUrl": "https://github.com/hashicorp/agent-skills/tree/de4323afdfbc30d1387f287b55062fa8d82b62e8/terraform/code-generation/skills/terraform-stacks",
390
+ "repositoryUrl": "https://github.com/hashicorp/agent-skills",
391
+ "version": "0.0.1",
392
+ "sourceCommit": "de4323afdfbc30d1387f287b55062fa8d82b62e8",
393
+ "sourcePath": "terraform-stacks",
394
+ "sourceProvenance": "Vendored from hashicorp/agent-skills; reviewed immutable opt-in entry.",
395
+ "contentSha256": "0a6244ecddf1cce0357db41b41b3b20a1bfa71f331092ebc8bbd15e649733d35",
396
+ "fileCount": 7,
397
+ "totalBytes": 104793
398
+ },
399
+ "manifest_digest": "d484ccc1279954e5dbcdd9b8b57bc21e6a0fa47d38dc11854ffcf8e61289e883",
400
+ "files": [
401
+ {
402
+ "path": "SKILL.md",
403
+ "content": "---\nname: terraform-stacks\ndescription: Comprehensive guide for working with HashiCorp Terraform Stacks. Use when creating, modifying, or validating Terraform Stack configurations (.tfcomponent.hcl, .tfdeploy.hcl files), working with stack components and deployments from local modules, public registry, or private registry sources, managing multi-region or multi-environment infrastructure, or troubleshooting Terraform Stacks syntax and structure.\nmetadata:\n copyright: Copyright IBM Corp. 2026\n version: \"0.0.1\"\n---\n\n# Terraform Stacks\n\nTerraform Stacks simplify infrastructure provisioning and management at scale by providing a configuration layer above traditional Terraform modules. Stacks enable declarative orchestration of multiple components across environments, regions, and cloud accounts.\n\n## Core Concepts\n\n**Stack**: A complete unit of infrastructure composed of components and deployments that can be managed together.\n\n**Component**: An abstraction around a Terraform module that defines infrastructure pieces. Each component specifies a source module, inputs, and providers.\n\n**Deployment**: An instance of all components in a stack with specific input values. Use deployments for different environments (dev/staging/prod), regions, or cloud accounts.\n\n**Stack Language**: A separate HCL-based language (not regular Terraform HCL) with distinct blocks and file extensions.\n\n## File Structure\n\nTerraform Stacks use specific file extensions:\n\n- **Component configuration**: `.tfcomponent.hcl`\n- **Deployment configuration**: `.tfdeploy.hcl`\n- **Provider lock file**: `.terraform.lock.hcl` (generated by CLI)\n\nAll configuration files must be at the root level of the Stack repository. HCP Terraform processes all files in dependency order.\n\n### Recommended File Organization\n\n```\nmy-stack/\n├── .terraform-version # The required Terraform version for this Stack\n├── variables.tfcomponent.hcl # Variable declarations\n├── providers.tfcomponent.hcl # Provider configurations\n├── components.tfcomponent.hcl # Component definitions\n├── outputs.tfcomponent.hcl # Stack outputs\n├── deployments.tfdeploy.hcl # Deployment definitions\n├── .terraform.lock.hcl # Provider lock file (generated)\n└── modules/ # Local modules (optional - only if using local modules)\n ├── s3/\n └── compute/\n```\n\n**Note**: The `modules/` directory is only required when using local module sources. Components can reference modules from:\n- Local file paths: `./modules/vpc`\n- Public registry: `terraform-aws-modules/vpc/aws`\n- Private registry: `app.terraform.io/<org-name>/vpc/aws`\n- Git: `git::https://github.com/org/repo.git//path?ref=v1.0.0`\n\nHCP Terraform processes all `.tfcomponent.hcl` and `.tfdeploy.hcl` files in dependency order.\n\n## Required Terraform version (.terraform-version)\n\nUse Terraform v1.13.x or later to access the Stacks CLI plugin and to run\nterraform stacks CLI commands. Begin by adding a .terraform-version file to\nyour Stack's root directory to specify the Terraform version required for your\nStack. For example, the following file specifies Terraform v1.14.5:\n\n```\n1.14.5\n```\n\n## Component Configuration (.tfcomponent.hcl)\n\n### Variable Block\n\nDeclare input variables for the Stack configuration. Variables must define a `type` field and do not support the `validation` argument.\n\n```hcl\nvariable \"aws_region\" {\n type = string\n description = \"AWS region for deployments\"\n default = \"us-west-1\"\n}\n\nvariable \"identity_token\" {\n type = string\n description = \"OIDC identity token\"\n ephemeral = true # Does not persist to state file\n}\n\nvariable \"instance_count\" {\n type = number\n nullable = false\n}\n```\n\n**Important**: Use `ephemeral = true` for credentials and tokens (identity tokens, API keys, passwords) to prevent them from persisting in state files. Use `stable` for longer-lived values like license keys that need to persist across runs.\n\n### Required Providers Block\n\n```hcl\nrequired_providers {\n aws = {\n source = \"hashicorp/aws\"\n version = \"~> 6.0\"\n }\n random = {\n source = \"hashicorp/random\"\n version = \"~> 3.5.0\"\n }\n}\n```\n\n### Provider Block\n\nProvider blocks differ from traditional Terraform:\n\n1. Support `for_each` meta-argument\n2. Define aliases in the block header (not as an argument)\n3. Accept configuration through a `config` block\n\n**Single Provider Configuration:**\n\n```hcl\nprovider \"aws\" \"this\" {\n config {\n region = var.aws_region\n assume_role_with_web_identity {\n role_arn = var.role_arn\n web_identity_token = var.identity_token\n }\n }\n}\n```\n\n**Multiple Provider Configurations with for_each:**\n\n```hcl\nprovider \"aws\" \"configurations\" {\n for_each = var.regions\n\n config {\n region = each.value\n assume_role_with_web_identity {\n role_arn = var.role_arn\n web_identity_token = var.identity_token\n }\n }\n}\n```\n\n**Authentication Best Practice**: Use **workload identity** (OIDC) as the preferred authentication method for Stacks. This approach:\n- Avoids long-lived static credentials\n- Provides temporary, scoped credentials per deployment run\n- Integrates with cloud provider IAM (AWS IAM Roles, Azure Managed Identities, GCP Service Accounts)\n- Eliminates need for platform-managed environment variables\n\nConfigure workload identity using `identity_token` blocks and `assume_role_with_web_identity` in provider configuration. For detailed setup instructions for AWS, Azure, and GCP, see: https://developer.hashicorp.com/terraform/cloud-docs/dynamic-provider-credentials\n\n### Component Block\n\nEach Stack requires at least one component block. Add a component for each module to include in the Stack. Components reference modules from local paths, registries, or Git.\n\n```hcl\ncomponent \"vpc\" {\n source = \"app.terraform.io/my-org/vpc/aws\" # Local, registry, or Git URL\n version = \"2.1.0\" # For registry modules\n\n inputs = {\n cidr_block = var.vpc_cidr\n name_prefix = var.name_prefix\n }\n\n providers = {\n aws = provider.aws.this\n }\n}\n```\n\nSee `references/component-blocks.md` for examples of dependencies, for_each, public registry modules, Git sources, and more.\n\n**Key Points:**\n- Reference outputs: `component.<name>.<output>` or `component.<name>[key].<output>` for for_each\n- Dependencies inferred automatically from component references\n- Aggregate with for expressions: `[for x in component.s3 : x.bucket_name]`\n- For components with `for_each`, reference specific instances: `component.<name>[each.value].<output>`\n- Provider references are normal values: `provider.<type>.<alias>` or `provider.<type>.<alias>[each.value]`\n\n### Output Block\n\nOutputs require a `type` argument and do not support `preconditions`:\n\n```hcl\noutput \"vpc_id\" {\n type = string\n description = \"VPC ID\"\n value = component.vpc.vpc_id\n}\n\noutput \"endpoint_urls\" {\n type = map(string)\n value = {\n for region, comp in component.api : region => comp.endpoint_url\n }\n sensitive = false\n}\n```\n\n### Locals Block\n\nLocals blocks work the same in both `.tfcomponent.hcl` and `.tfdeploy.hcl` files:\n\n```hcl\nlocals {\n common_tags = {\n Environment = var.environment\n ManagedBy = \"Terraform Stacks\"\n Project = var.project_name\n }\n\n region_config = {\n for region in var.regions : region => {\n name_suffix = \"${var.environment}-${region}\"\n }\n }\n}\n```\n\n### Removed Block\n\nUse to safely remove components from a Stack. HCP Terraform requires the component's providers to remove it.\n\n```hcl\nremoved {\n from = component.old_component\n source = \"./modules/old-module\"\n \n providers = {\n aws = provider.aws.this\n }\n}\n```\n\n## Deployment Configuration (.tfdeploy.hcl)\n\n### Identity Token Block\n\nGenerate JWT tokens for OIDC authentication with cloud providers:\n\n```hcl\nidentity_token \"aws\" {\n audience = [\"aws.workload.identity\"]\n}\n\nidentity_token \"azure\" {\n audience = [\"api://AzureADTokenExchange\"]\n}\n```\n\nReference tokens in deployments using `identity_token.<name>.jwt`\n\n### Store Block\n\nAccess HCP Terraform variable sets within Stack deployments:\n\n```hcl\nstore \"varset\" \"aws_credentials\" {\n id = \"varset-ABC123\" # Alternatively use: name = \"varset_name\"\n source = \"tfc-cloud-shared\"\n category = \"terraform\" # Alternatively use: category = \"env\" for environment variables\n}\n\ndeployment \"production\" {\n inputs = {\n aws_access_key = store.varset.aws_credentials.AWS_ACCESS_KEY_ID\n }\n}\n```\n\nUse to centralize credentials and share variables across Stacks. See `references/deployment-blocks.md` for details.\n\n### Deployment Block\n\nDefine deployment instances (minimum 1, maximum 20 per Stack):\n\n```hcl\ndeployment \"production\" {\n inputs = {\n aws_region = \"us-west-1\"\n instance_count = 3\n role_arn = local.role_arn\n identity_token = identity_token.aws.jwt\n }\n}\n\n# Create multiple deployments for different environments\ndeployment \"development\" {\n inputs = {\n aws_region = \"us-east-1\"\n instance_count = 1\n name_suffix = \"dev\"\n role_arn = local.role_arn\n identity_token = identity_token.aws.jwt\n }\n}\n```\n\n**To destroy a deployment**: Set `destroy = true`, upload configuration, approve destroy run, then remove the deployment block. See `references/deployment-blocks.md` for details.\n\n### Deployment Group Block\n\nGroup deployments together for shared settings (HCP Terraform Premium tier feature). Free/standard tiers use default groups named `{deployment-name}_default`.\n\n```hcl\ndeployment_group \"canary\" {\n auto_approve_checks = [deployment_auto_approve.safe_changes]\n}\n\ndeployment \"dev\" {\n inputs = { /* ... */ }\n deployment_group = deployment_group.canary\n}\n```\n\nMultiple deployments can reference the same group. See `references/deployment-blocks.md` for details.\n\n### Deployment Auto-Approve Block\n\nDefine rules to automatically approve deployment plans (HCP Terraform Premium tier feature):\n\n```hcl\ndeployment_auto_approve \"safe_changes\" {\n deployment_group = deployment_group.canary\n\n check {\n condition = context.plan.changes.remove == 0\n reason = \"Cannot auto-approve plans with resource deletions\"\n }\n}\n```\n\n**Available context variables**: `context.plan.applyable`, `context.plan.changes.add/change/remove/total`, `context.success`\n\n**Note:** `orchestrate` blocks are deprecated. Use `deployment_group` and `deployment_auto_approve` instead.\n\nSee `references/deployment-blocks.md` for all context variables and patterns.\n\n### Publish Output and Upstream Input Blocks\n\nLink Stacks together by publishing outputs from one Stack and consuming them in another:\n\n```hcl\n# In network Stack - publish outputs\npublish_output \"vpc_id_network\" {\n type = string\n value = deployment.network.vpc_id\n}\n\n# In application Stack - consume outputs\nupstream_input \"network_stack\" {\n type = \"stack\"\n source = \"app.terraform.io/my-org/my-project/networking-stack\"\n}\n\ndeployment \"app\" {\n inputs = {\n vpc_id = upstream_input.network_stack.vpc_id_network\n }\n}\n```\n\nSee `references/linked-stacks.md` for complete documentation and examples.\n\n## Terraform Stacks CLI\n\n**Note**: Terraform Stacks is Generally Available (GA) as of Terraform CLI v1.13+. Stacks now count toward Resources Under Management (RUM) for HCP Terraform billing.\n\n### Initialize and Validate\n\n```bash\nterraform stacks init # Download providers, modules, generate lock file\nterraform stacks providers-lock # Regenerate lock file (add platforms if needed)\nterraform stacks validate # Check syntax without uploading\n```\n\n### Deployment Workflow\n\n**Important**: No `plan` or `apply` commands. Upload configuration triggers deployment runs automatically.\n\n```bash\n# 1. Upload configuration (triggers deployment runs)\nterraform stacks configuration upload\n\n# 2. Monitor deployments\nterraform stacks deployment-run list # List runs (non-interactive)\nterraform stacks deployment-group watch -deployment-group=... # Stream status updates\n\n# 3. Approve deployments (if auto-approve not configured)\nterraform stacks deployment-run approve-all-plans -deployment-run-id=...\nterraform stacks deployment-group approve-all-plans -deployment-group=...\nterraform stacks deployment-run cancel -deployment-run-id=... # Cancel if needed\n```\n\n### Configuration Management\n\n```bash\nterraform stacks configuration list # List configuration versions\nterraform stacks configuration fetch -configuration-id=... # Download configuration\nterraform stacks configuration watch # Monitor upload status\n```\n\n### Other Commands\n\n```bash\nterraform stacks create # Create new Stack (interactive)\nterraform stacks fmt # Format Stack files\nterraform stacks list # Show all Stacks\nterraform stacks version # Display version\nterraform stacks deployment-group rerun -deployment-group=... # Rerun deployment\n```\n\n## Monitoring Deployments with HCP Terraform API\n\nFor programmatic monitoring in automation, CI/CD, or non-interactive environments (like AI agents), use the HCP Terraform API instead of CLI watch commands. The API provides endpoints for:\n\n- Configuration status and validation\n- Deployment group summaries\n- Deployment run status\n- Deployment step details (plan/apply)\n- Error diagnostics with file locations and code snippets\n- Stack outputs via artifacts endpoint\n\n**Key points:**\n- CLI watch commands stream indefinitely and don't work in automation\n- Use artifacts endpoint to retrieve Stack outputs: `GET /api/v2/stack-deployment-steps/{step-id}/artifacts?name=apply-description`\n- Diagnostics endpoint requires `stack_deployment_step_id` query parameter\n- Artifacts endpoint returns HTTP 307 redirect (use `curl -L`)\n\nFor complete API workflow, authentication, polling best practices, and example scripts, see `references/api-monitoring.md`.\n\n## Common Patterns\n\n**Component Dependencies**: Dependencies are automatically inferred when one component references another's output (e.g., `subnet_ids = component.vpc.private_subnet_ids`).\n\n**Multi-Region Deployment**: Use `for_each` on providers and components to deploy across multiple regions. Each region gets its own provider configuration and component instances.\n\n**Deferred Changes**: Stacks support deferred changes to handle dependencies where values are only known after apply. This enables complex multi-component deployments where some resources depend on runtime values from other components (cluster endpoints, generated passwords, etc.).\n\nFor complete examples including multi-region deployments, component dependencies, deferred changes patterns, and linked Stacks, see `references/examples.md`.\n\n## Best Practices\n\n1. **Component Granularity**: Create components for logical infrastructure units that share a lifecycle\n2. **Module Compatibility**:\n - Modules used with Stacks cannot include provider blocks (configure providers in Stack configuration)\n - **Test public registry modules** before using in production Stacks - some modules may have compatibility issues\n - Consider using raw resources for critical infrastructure if module compatibility is uncertain\n - Example: Some terraform-aws-modules versions have been found to have compatibility issues with Stacks (e.g., ALB and ECS modules)\n3. **State Isolation**: Each deployment has its own isolated state\n4. **Input Variables**: Use variables for values that differ across deployments; use locals for shared values\n5. **Provider Lock Files**: Always generate and commit `.terraform.lock.hcl` to version control\n6. **Naming Conventions**: Use descriptive names for components and deployments\n7. **Deployment Groups**: You can organize deployments into deployment groups. Deployment groups enable auto-approval rules, logical organization, and provide a foundation for scaling. Deployment groups are an HCP Terraform Premium tier feature\n8. **Testing**: Test Stack configurations in dev/staging deployments before production\n\n## Troubleshooting\n\n**Circular Dependencies**: Refactor to break circular references or use intermediate components.\n\n**Deployment Destruction**: Cannot destroy from UI. Set `destroy = true` in deployment block, upload configuration, and HCP Terraform creates a destroy run.\n\n**Empty Diagnostics**: Add required `stack_deployment_step_id` query parameter to diagnostics API requests.\n\n**Module Compatibility**: Test public registry modules before production use. Some modules may have compatibility issues with Stacks.\n\n## References\n\nFor detailed documentation, see:\n- `references/component-blocks.md` - Complete component block reference with all arguments and syntax\n- `references/deployment-blocks.md` - Complete deployment block reference with all configuration options\n- `references/linked-stacks.md` - Publish outputs and upstream inputs for linking Stacks together\n- `references/examples.md` - Complete working examples for multi-region and component dependencies\n- `references/api-monitoring.md` - Full API workflow for programmatic monitoring and automation\n- `references/troubleshooting.md` - Detailed troubleshooting guide for common issues and solutions\n",
404
+ "byte_size": 17249,
405
+ "content_sha256": "7c459ac61bea8e9c24be733100600ca7726d222efc04e551e52a7577741e2a64"
406
+ },
407
+ {
408
+ "path": "references/api-monitoring.md",
409
+ "content": "# API Monitoring Reference\n\nComplete guide for monitoring Terraform Stack deployments using the HCP Terraform API. Use this approach for automation, CI/CD pipelines, and non-interactive environments like AI agents.\n\n## Table of Contents\n\n1. [When to Use the API](#when-to-use-the-api)\n2. [Authentication](#authentication)\n3. [API Monitoring Workflow](#api-monitoring-workflow)\n4. [Detailed Endpoint Reference](#detailed-endpoint-reference)\n5. [Notes for AI Agents and Automation](#notes-for-ai-agents-and-automation)\n\n## When to Use the API\n\nUse the HCP Terraform API instead of CLI commands when:\n- Running in non-interactive environments (CI/CD, automation scripts)\n- Building tools or integrations that need programmatic access\n- Monitoring multiple Stacks simultaneously\n- Implementing custom retry logic or error handling\n- Working in environments where streaming CLI commands don't work\n\n**CLI commands that don't work in automation:**\n- `terraform stacks deployment-run watch` - Streams output, blocks indefinitely\n- `terraform stacks deployment-group watch` - Streams output, blocks indefinitely\n- `terraform stacks configuration watch` - Streams output, blocks indefinitely\n\n## Authentication\n\n### Extract API Token from Credentials File\n\n```bash\nTOKEN=$(jq -r '.credentials[\"app.terraform.io\"].token' ~/.terraform.d/credentials.tfrc.json)\n```\n\n### Alternative: Use Environment Variable\n\n```bash\nexport TFC_TOKEN=\"your-token-here\"\nTOKEN=$TFC_TOKEN\n```\n\n### API Request Headers\n\nAll API requests require these headers:\n\n```bash\n-H \"Authorization: Bearer $TOKEN\"\n-H \"Content-Type: application/vnd.api+json\"\n```\n\n## API Monitoring Workflow\n\nAfter uploading a configuration with `terraform stacks configuration upload`, follow this sequence to monitor deployment progress:\n\n### Step 1: Get Configuration Status\n\n**Endpoint:** `GET /api/v2/stack-configurations/{configuration-id}`\n\n**Purpose:** Verify configuration upload completed successfully and get the configuration details.\n\n**Request:**\n\n```bash\ncurl -s -H \"Authorization: Bearer $TOKEN\" \\\n -H \"Content-Type: application/vnd.api+json\" \\\n \"https://app.terraform.io/api/v2/stack-configurations/{configuration-id}\" | jq '.'\n```\n\n**Response Fields:**\n- `attributes.status` - Configuration processing status (pending/completed)\n- `attributes.sequence-number` - Version number of this configuration\n- `attributes.components-detected` - Number of components found\n- `attributes.deployments-detected` - Number of deployments found\n\n**Example Response:**\n\n```json\n{\n \"data\": {\n \"id\": \"stc-ABC123\",\n \"type\": \"stack-configurations\",\n \"attributes\": {\n \"status\": \"completed\",\n \"sequence-number\": 5,\n \"components-detected\": 3,\n \"deployments-detected\": 2,\n \"created-at\": \"2024-01-15T10:30:00.000Z\",\n \"updated-at\": \"2024-01-15T10:30:45.000Z\"\n }\n }\n}\n```\n\n### Step 2: Get Deployment Group Summaries\n\n**Endpoint:** `GET /api/v2/stack-configurations/{configuration-id}/stack-deployment-group-summaries`\n\n**Purpose:** Get list of deployment groups, their IDs, and current status summary.\n\n**Request:**\n\n```bash\ncurl -s -H \"Authorization: Bearer $TOKEN\" \\\n -H \"Content-Type: application/vnd.api+json\" \\\n \"https://app.terraform.io/api/v2/stack-configurations/{configuration-id}/stack-deployment-group-summaries\" | jq '.'\n```\n\n**Response Fields:**\n- `id` - Deployment group ID (needed for next step)\n- `attributes.name` - Deployment group name (e.g., `dev_default`)\n- `attributes.status` - Overall status (running/succeeded/failed)\n- `attributes.status-counts` - Breakdown of deployment statuses\n\n**Example Response:**\n\n```json\n{\n \"data\": [\n {\n \"id\": \"sdg-XYZ789\",\n \"type\": \"stack-deployment-group-summaries\",\n \"attributes\": {\n \"name\": \"dev_default\",\n \"status\": \"running\",\n \"status-counts\": {\n \"pending\": 0,\n \"running\": 1,\n \"succeeded\": 1,\n \"failed\": 0\n }\n }\n }\n ]\n}\n```\n\n### Step 3: Get Deployment Runs\n\n**Endpoint:** `GET /api/v2/stack-deployment-groups/{group-id}/stack-deployment-runs`\n\n**Purpose:** Get list of deployment runs for a specific group with their current status.\n\n**Request:**\n\n```bash\ncurl -s -H \"Authorization: Bearer $TOKEN\" \\\n -H \"Content-Type: application/vnd.api+json\" \\\n \"https://app.terraform.io/api/v2/stack-deployment-groups/{group-id}/stack-deployment-runs\" | jq '.'\n```\n\n**Response Fields:**\n- `id` - Deployment run ID (needed for next step)\n- `attributes.status` - Current status (planning/planned/applying/applied/failed)\n- `attributes.created-at` - Run start time\n- `attributes.updated-at` - Last update time\n\n**Example Response:**\n\n```json\n{\n \"data\": [\n {\n \"id\": \"sdr-123ABC\",\n \"type\": \"stack-deployment-runs\",\n \"attributes\": {\n \"status\": \"planning\",\n \"created-at\": \"2024-01-15T10:31:00.000Z\",\n \"updated-at\": \"2024-01-15T10:31:15.000Z\"\n }\n }\n ]\n}\n```\n\n### Step 4: Get Deployment Steps\n\n**Endpoint:** `GET /api/v2/stack-deployment-runs/{run-id}/stack-deployment-steps`\n\n**Purpose:** Get detailed information about individual plan and apply steps.\n\n**Request:**\n\n```bash\ncurl -s -H \"Authorization: Bearer $TOKEN\" \\\n -H \"Content-Type: application/vnd.api+json\" \\\n \"https://app.terraform.io/api/v2/stack-deployment-runs/{run-id}/stack-deployment-steps\" | jq '.'\n```\n\n**Response Fields:**\n- `id` - Step ID (needed for diagnostics and outputs)\n- `attributes.operation-type` - Type of operation (plan/apply)\n- `attributes.status` - Step status (running/completed/failed)\n- `attributes.component-name` - Which component is being processed\n\n**Example Response:**\n\n```json\n{\n \"data\": [\n {\n \"id\": \"sds-PlanStep123\",\n \"type\": \"stack-deployment-steps\",\n \"attributes\": {\n \"operation-type\": \"plan\",\n \"status\": \"completed\",\n \"component-name\": \"vpc\",\n \"created-at\": \"2024-01-15T10:31:05.000Z\",\n \"completed-at\": \"2024-01-15T10:31:30.000Z\"\n }\n },\n {\n \"id\": \"sds-ApplyStep456\",\n \"type\": \"stack-deployment-steps\",\n \"attributes\": {\n \"operation-type\": \"apply\",\n \"status\": \"running\",\n \"component-name\": \"vpc\",\n \"created-at\": \"2024-01-15T10:32:00.000Z\"\n }\n }\n ]\n}\n```\n\n### Step 5: Get Error Diagnostics (When Deployment Fails)\n\n**Endpoint:** `GET /api/v2/stack-deployment-steps/{step-id}/stack-diagnostics`\n\n**Purpose:** Retrieve detailed error messages when a deployment step fails.\n\n**Critical:** The `stack_deployment_step_id` query parameter is **required**. Without it, the API returns empty results.\n\n**Request:**\n\n```bash\ncurl -s -H \"Authorization: Bearer $TOKEN\" \\\n -H \"Content-Type: application/vnd.api+json\" \\\n \"https://app.terraform.io/api/v2/stack-deployment-steps/{step-id}/stack-diagnostics?stack_deployment_step_id={step-id}\" | jq '.'\n```\n\n**Response Fields:**\n- `attributes.severity` - Diagnostic level (error/warning)\n- `attributes.summary` - Brief error description\n- `attributes.detail` - Detailed error message\n- `attributes.diags` - Array of diagnostic objects with file locations and code snippets\n\n**Example Response (Error with Details):**\n\n```json\n{\n \"data\": [\n {\n \"id\": \"stf-ErrorExampleId\",\n \"type\": \"stack-diagnostics\",\n \"attributes\": {\n \"severity\": \"error\",\n \"summary\": \"Diagnostics reported\",\n \"detail\": \"2 errors\",\n \"diags\": [\n {\n \"summary\": \"Unsupported attribute\",\n \"detail\": \"This object does not have an attribute named \\\"target_id\\\".\",\n \"range\": {\n \"filename\": \"main.tf\",\n \"start\": {\n \"line\": 634,\n \"column\": 33\n },\n \"end\": {\n \"line\": 634,\n \"column\": 43\n },\n \"source\": \"registry.terraform.io/terraform-aws-modules/alb/aws@9.17.0//main.tf\"\n },\n \"snippet\": {\n \"code\": \" target_id = each.value.target_id\",\n \"context\": \"resource \\\"aws_lb_target_group_attachment\\\" \\\"this\\\"\"\n }\n },\n {\n \"summary\": \"Invalid reference\",\n \"detail\": \"A reference to a resource type must be followed by at least one attribute access.\",\n \"range\": {\n \"filename\": \"main.tf\",\n \"start\": {\n \"line\": 142,\n \"column\": 15\n },\n \"end\": {\n \"line\": 142,\n \"column\": 28\n },\n \"source\": \"local-module//main.tf\"\n },\n \"snippet\": {\n \"code\": \" vpc_id = aws_vpc.main\",\n \"context\": \"resource \\\"aws_subnet\\\" \\\"private\\\"\"\n }\n }\n ],\n \"acknowledged\": false,\n \"created-at\": \"2024-01-15T10:32:15.000Z\"\n }\n }\n ]\n}\n```\n\n**Parsing Diagnostics:**\n\nExtract error information with jq:\n\n```bash\n# Get error summaries\ncurl -s -H \"Authorization: Bearer $TOKEN\" \\\n \"https://app.terraform.io/api/v2/stack-deployment-steps/{step-id}/stack-diagnostics?stack_deployment_step_id={step-id}\" | \\\n jq -r '.data[].attributes.diags[]? | \"\\(.summary): \\(.detail)\"'\n\n# Get file locations\ncurl -s -H \"Authorization: Bearer $TOKEN\" \\\n \"https://app.terraform.io/api/v2/stack-deployment-steps/{step-id}/stack-diagnostics?stack_deployment_step_id={step-id}\" | \\\n jq -r '.data[].attributes.diags[]? | \"\\(.range.filename):\\(.range.start.line)\"'\n```\n\n### Step 6: Get Stack Outputs (After Successful Deployment)\n\n**Endpoint:** `GET /api/v2/stack-deployment-steps/{final-apply-step-id}/artifacts?name=apply-description`\n\n**Purpose:** Retrieve Stack outputs after a successful deployment completes.\n\n**Important Notes:**\n- This endpoint returns HTTP 307 redirect - use `curl -L` to follow redirects automatically\n- This is currently the **only way** to retrieve Stack outputs programmatically\n- This endpoint is **not documented** in public API documentation\n- You need the final apply step ID from Step 4\n\n**Request:**\n\n```bash\ncurl -L -s -H \"Authorization: Bearer $TOKEN\" \\\n \"https://app.terraform.io/api/v2/stack-deployment-steps/{final-apply-step-id}/artifacts?name=apply-description\"\n```\n\n**Response Structure:**\n\nThe artifact response includes an `.outputs` object where each output contains a `change.after` property with the actual output value:\n\n```json\n{\n \"outputs\": {\n \"alb_url\": {\n \"change\": {\n \"actions\": [\"no-op\"],\n \"before\": \"http://my-alb-123456789.us-west-2.elb.amazonaws.com\",\n \"after\": \"http://my-alb-123456789.us-west-2.elb.amazonaws.com\",\n \"after_unknown\": false,\n \"before_sensitive\": false,\n \"after_sensitive\": false\n },\n \"type\": \"string\"\n },\n \"ecr_repository_url\": {\n \"change\": {\n \"actions\": [\"no-op\"],\n \"before\": \"123456789.dkr.ecr.us-west-2.amazonaws.com/my-repo\",\n \"after\": \"123456789.dkr.ecr.us-west-2.amazonaws.com/my-repo\",\n \"after_unknown\": false,\n \"before_sensitive\": false,\n \"after_sensitive\": false\n },\n \"type\": \"string\"\n }\n }\n}\n```\n\n**Extract Only Output Values:**\n\n```bash\ncurl -L -s --header \"Authorization: Bearer $TOKEN\" \\\n \"https://app.terraform.io/api/v2/stack-deployment-steps/{final-apply-step-id}/artifacts?name=apply-description\" | \\\n jq -r '.outputs | to_entries | .[] | \"\\(.key): \\(.value.change.after)\"'\n```\n\n**Example Output:**\n\n```\nalb_url: http://my-alb-123456789.us-west-2.elb.amazonaws.com\necr_repository_url: 123456789.dkr.ecr.us-west-2.amazonaws.com/my-repo\n```\n\n## Detailed Endpoint Reference\n\n### Available Artifact Types\n\nThe artifacts endpoint accepts these `name` parameter values:\n\n- `plan-description` - Terraform plan output in JSON format\n- `plan-debug-log` - Detailed debug logs from plan operation\n- `apply-description` - Terraform apply output including outputs (JSON format)\n- `apply-debug-log` - Detailed debug logs from apply operation\n\n### Polling Best Practices\n\n**Recommended polling intervals:**\n- Configuration status: Check every 5 seconds until status is \"completed\"\n- Deployment runs: Check every 10 seconds during active deployment\n- Deployment steps: Check every 10 seconds for individual step status\n\n**Implement exponential backoff:**\n\n```bash\n# Example polling script with backoff\nRETRY_COUNT=0\nMAX_RETRIES=30\nBACKOFF=5\n\nwhile [ $RETRY_COUNT -lt $MAX_RETRIES ]; do\n STATUS=$(curl -s -H \"Authorization: Bearer $TOKEN\" \\\n \"https://app.terraform.io/api/v2/stack-deployment-runs/{run-id}\" | \\\n jq -r '.data.attributes.status')\n\n if [ \"$STATUS\" = \"applied\" ] || [ \"$STATUS\" = \"failed\" ]; then\n echo \"Deployment finished with status: $STATUS\"\n break\n fi\n\n echo \"Current status: $STATUS. Waiting ${BACKOFF}s...\"\n sleep $BACKOFF\n RETRY_COUNT=$((RETRY_COUNT + 1))\ndone\n```\n\n## Notes for AI Agents and Automation\n\n### CLI Command Limitations\n\n**These CLI commands DO NOT work in automation:**\n- `terraform stacks deployment-run watch` - Streams output, blocks indefinitely\n- `terraform stacks deployment-group watch` - Streams output, blocks indefinitely\n- `terraform stacks configuration watch` - Streams output, blocks indefinitely\n\n**Solution:** Use API polling instead of watch commands.\n\n### No Direct Output Command\n\nThere is currently no CLI command to retrieve Stack outputs. You must:\n1. Use API to get deployment steps\n2. Find the final apply step ID\n3. Request the `apply-description` artifact\n4. Parse JSON to extract outputs\n\n### Handling Redirects\n\nThe artifacts endpoint returns HTTP 307 redirect to the actual artifact location. Ensure your HTTP client follows redirects:\n\n**curl:** Use `-L` flag\n**Python requests:** Set `allow_redirects=True` (default)\n**Node.js fetch:** Set `redirect: 'follow'` (default)\n\n### Error Handling\n\n**Common API errors:**\n\n- **401 Unauthorized:** Invalid or expired token - refresh credentials\n- **404 Not Found:** Invalid ID or resource doesn't exist yet - retry with backoff\n- **429 Too Many Requests:** Rate limited - implement exponential backoff\n- **Empty diagnostics:** Missing required `stack_deployment_step_id` query parameter\n\n### Complete Monitoring Script Example\n\n```bash\n#!/bin/bash\n\n# Configuration\nTOKEN=$(jq -r '.credentials[\"app.terraform.io\"].token' ~/.terraform.d/credentials.tfrc.json)\nCONFIG_ID=\"stc-ABC123\"\nBASE_URL=\"https://app.terraform.io/api/v2\"\n\n# Helper function\napi_get() {\n curl -s -H \"Authorization: Bearer $TOKEN\" \\\n -H \"Content-Type: application/vnd.api+json\" \\\n \"$1\"\n}\n\n# 1. Wait for configuration to complete\necho \"Checking configuration status...\"\nwhile true; do\n STATUS=$(api_get \"$BASE_URL/stack-configurations/$CONFIG_ID\" | jq -r '.data.attributes.status')\n [ \"$STATUS\" = \"completed\" ] && break\n echo \"Configuration status: $STATUS. Waiting...\"\n sleep 5\ndone\n\n# 2. Get deployment groups\necho \"Getting deployment groups...\"\nGROUP_ID=$(api_get \"$BASE_URL/stack-configurations/$CONFIG_ID/stack-deployment-group-summaries\" | \\\n jq -r '.data[0].id')\n\n# 3. Get deployment run\necho \"Getting deployment run...\"\nRUN_ID=$(api_get \"$BASE_URL/stack-deployment-groups/$GROUP_ID/stack-deployment-runs\" | \\\n jq -r '.data[0].id')\n\n# 4. Monitor deployment run\necho \"Monitoring deployment run: $RUN_ID\"\nwhile true; do\n STATUS=$(api_get \"$BASE_URL/stack-deployment-runs/$RUN_ID\" | jq -r '.data.attributes.status')\n echo \"Deployment status: $STATUS\"\n\n if [ \"$STATUS\" = \"applied\" ]; then\n echo \"Deployment succeeded!\"\n\n # 5. Get outputs from final apply step\n APPLY_STEP=$(api_get \"$BASE_URL/stack-deployment-runs/$RUN_ID/stack-deployment-steps\" | \\\n jq -r '.data[] | select(.attributes[\"operation-type\"] == \"apply\") | .id' | tail -1)\n\n echo \"Retrieving outputs from step: $APPLY_STEP\"\n curl -L -s -H \"Authorization: Bearer $TOKEN\" \\\n \"$BASE_URL/stack-deployment-steps/$APPLY_STEP/artifacts?name=apply-description\" | \\\n jq -r '.outputs | to_entries | .[] | \"\\(.key): \\(.value.change.after)\"'\n break\n fi\n\n if [ \"$STATUS\" = \"failed\" ]; then\n echo \"Deployment failed!\"\n\n # Get error diagnostics\n FAILED_STEP=$(api_get \"$BASE_URL/stack-deployment-runs/$RUN_ID/stack-deployment-steps\" | \\\n jq -r '.data[] | select(.attributes.status == \"failed\") | .id' | head -1)\n\n echo \"Error diagnostics from step: $FAILED_STEP\"\n api_get \"$BASE_URL/stack-deployment-steps/$FAILED_STEP/stack-diagnostics?stack_deployment_step_id=$FAILED_STEP\" | \\\n jq -r '.data[].attributes.diags[]? | \"\\(.summary): \\(.detail)\"'\n exit 1\n fi\n\n sleep 10\ndone\n```\n\nThis script demonstrates a complete monitoring workflow from configuration upload to output retrieval with error handling.\n",
410
+ "byte_size": 16608,
411
+ "content_sha256": "41c93db72e44cb19a4129143cc3f4e93c29f5f98343500a097bce5b4b2c733b2"
412
+ },
413
+ {
414
+ "path": "references/component-blocks.md",
415
+ "content": "# Component Configuration Block Reference\n\nComplete reference for all blocks available in Terraform Stack component configuration files (`.tfcomponent.hcl`).\n\n## Table of Contents\n\n1. [Variable Block](#variable-block)\n2. [Required Providers Block](#required-providers-block)\n3. [Provider Block](#provider-block)\n4. [Component Block](#component-block)\n5. [Output Block](#output-block)\n6. [Locals Block](#locals-block)\n7. [Removed Block](#removed-block)\n\n## Variable Block\n\nDeclares input variables for Stack configuration.\n\n### Syntax\n\n```hcl\nvariable \"variable_name\" {\n type = <type>\n description = \"<description>\"\n default = <value>\n sensitive = <bool>\n nullable = <bool>\n ephemeral = <bool>\n}\n```\n\n### Arguments\n\n- **type** (required): Data type (string, number, bool, list, map, object, set, tuple, any)\n- **description** (optional): Variable description\n- **default** (optional): Default value\n- **sensitive** (optional, default false): Mark as sensitive to redact from logs\n- **nullable** (optional, default true): Whether null is allowed\n- **ephemeral** (optional, default false): Do not persist to state file\n\n### Differences from Traditional Terraform\n\n- **type** is required (not optional)\n- **validation** argument is not supported\n\n### Examples\n\n```hcl\nvariable \"aws_region\" {\n type = string\n description = \"AWS region for infrastructure\"\n default = \"us-west-1\"\n}\n\nvariable \"identity_token\" {\n type = string\n description = \"OIDC identity token\"\n ephemeral = true\n}\n\nvariable \"subnet_config\" {\n type = object({\n cidr_block = string\n availability_zone = string\n map_public_ip = bool\n })\n}\n```\n\nFor complete variable examples in context, see `examples.md`.\n\n## Required Providers Block\n\nDeclares provider dependencies.\n\n### Syntax\n\n```hcl\nrequired_providers {\n <provider_name> = {\n source = \"<source>\"\n version = \"<version_constraint>\"\n }\n}\n```\n\n### Arguments\n\n- **source** (required): Provider source address (e.g., \"hashicorp/aws\")\n- **version** (optional): Version constraint (e.g., \"~> 5.0\")\n\n### Examples\n\n```hcl\nrequired_providers {\n aws = {\n source = \"hashicorp/aws\"\n version = \"~> 5.7.0\"\n }\n \n random = {\n source = \"hashicorp/random\"\n version = \"~> 3.5.0\"\n }\n \n azurerm = {\n source = \"hashicorp/azurerm\"\n version = \">= 3.0\"\n }\n}\n```\n\n## Provider Block\n\nConfigures provider instances.\n\n### Syntax\n\n```hcl\nprovider \"<provider_type>\" \"<alias>\" {\n for_each = <map_or_set> # Optional\n \n config {\n <provider_arguments>\n }\n}\n```\n\n### Arguments\n\n- **provider_type** (label 1, required): Provider type (e.g., \"aws\", \"azurerm\")\n- **alias** (label 2, required): Unique identifier for this provider configuration\n- **for_each** (optional): Create multiple provider instances from a map or set\n- **config** (required): Nested block containing provider-specific configuration\n\n### Key Differences from Traditional Terraform\n\n1. Alias is defined in block header, not as an argument\n2. Configuration goes in a nested `config` block\n3. Supports `for_each` meta-argument\n4. Provider configurations are treated as first-class values\n\n### Example\n\n```hcl\nprovider \"aws\" \"main\" {\n config {\n region = var.aws_region\n\n assume_role_with_web_identity {\n role_arn = var.role_arn\n web_identity_token = var.identity_token\n }\n }\n}\n```\n\nFor complete provider examples including for_each and multi-cloud patterns, see `examples.md`.\n\n## Component Block\n\nDefines infrastructure components to include in the Stack.\n\n### Syntax\n\n```hcl\ncomponent \"<component_name>\" {\n for_each = <map_or_set> # Optional\n \n source = \"<module_source>\"\n \n inputs = {\n <input_name> = <value>\n }\n \n providers = {\n <provider_local_name> = provider.<type>.<alias>[<key>]\n }\n}\n```\n\n### Arguments\n\n- **component_name** (label, required): Unique identifier for this component\n- **for_each** (optional): Create multiple component instances\n- **source** (required): Module source (see [Source Argument](#source-argument) below)\n- **version** (optional): Version constraint for registry-based sources only\n- **inputs** (required): Map of input variables for the module\n- **providers** (required): Map of provider configurations\n\n### Source Argument\n\nThe `source` argument accepts the same module sources as traditional Terraform configurations.\n\n**Local File Path:**\n```hcl\nsource = \"./modules/vpc\"\nsource = \"../shared-modules/networking\"\n```\n\n**Public Terraform Registry:**\n```hcl\nsource = \"terraform-aws-modules/vpc/aws\"\nsource = \"hashicorp/consul/aws\"\n```\nFormat: `<NAMESPACE>/<NAME>/<PROVIDER>`\n\n**Private HCP Terraform Registry:**\n```hcl\nsource = \"app.terraform.io/my-org/vpc/aws\"\nsource = \"app.terraform.io/example-corp/networking/azurerm\"\n```\nFormat: `<HOSTNAME>/<ORGANIZATION>/<MODULE_NAME>/<PROVIDER_NAME>`\n\n- **HCP Terraform (SaaS)**: Use hostname `app.terraform.io`\n- **Terraform Enterprise**: Use your instance hostname (e.g., `terraform.mycompany.com`)\n- **Generic hostname**: Use `localterraform.com` for deployments spanning multiple Terraform Enterprise instances\n\n**Git Repository:**\n```hcl\nsource = \"git::https://github.com/org/repo.git//modules/vpc?ref=v1.0.0\"\nsource = \"git::ssh://git@github.com/org/repo.git//modules/vpc?ref=main\"\n```\n\n**HTTP/HTTPS Archive:**\n```hcl\nsource = \"https://example.com/modules/vpc-module.tar.gz\"\n```\n\n### Version Argument\n\nThe `version` argument is supported only for registry-based sources (public and private registries). Local file paths and Git sources do not support the `version` argument.\n\n```hcl\ncomponent \"vpc\" {\n source = \"app.terraform.io/my-org/vpc/aws\"\n version = \"~> 2.0\" # Semantic versioning constraint\n\n inputs = {\n cidr_block = var.vpc_cidr\n }\n\n providers = {\n aws = provider.aws.main\n }\n}\n```\n\n**Note**: Modules sourced from local file paths always share the same version as their caller and cannot have independent version constraints.\n\n### Component References\n\nAccess component outputs using: `component.<name>.<output>`\n\nFor components with `for_each`: `component.<name>[<key>].<output>`\n\n### Examples\n\n**Basic Component:**\n\n```hcl\ncomponent \"vpc\" {\n source = \"app.terraform.io/my-org/vpc/aws\"\n version = \"2.1.0\"\n\n inputs = {\n cidr_block = var.vpc_cidr\n name_prefix = var.name_prefix\n }\n\n providers = {\n aws = provider.aws.main\n }\n}\n```\n\n**Component with Dependencies:**\n\n```hcl\ncomponent \"database\" {\n source = \"./modules/rds\"\n\n inputs = {\n vpc_id = component.vpc.vpc_id\n subnet_ids = component.vpc.private_subnet_ids\n security_group_ids = [component.security.database_sg_id]\n engine_version = var.db_engine_version\n }\n\n providers = {\n aws = provider.aws.main\n }\n}\n```\n\nFor complete component examples including for_each, multi-region, public registry, and multi-provider patterns, see `examples.md`.\n\n## Output Block\n\nExposes values from Stack configuration.\n\n### Syntax\n\n```hcl\noutput \"<output_name>\" {\n type = <type>\n description = \"<description>\"\n value = <expression>\n sensitive = <bool>\n ephemeral = <bool>\n}\n```\n\n### Arguments\n\n- **output_name** (label, required): Unique identifier for this output\n- **type** (required): Data type of the output\n- **description** (optional): Output description\n- **value** (required): Expression to output\n- **sensitive** (optional, default false): Mark as sensitive\n- **ephemeral** (optional, default false): Ephemeral value\n\n### Differences from Traditional Terraform\n\n- **type** is required\n- **precondition** block is not supported\n\n### Examples\n\n```hcl\noutput \"vpc_id\" {\n type = string\n description = \"VPC ID\"\n value = component.vpc.vpc_id\n}\n\noutput \"instance_details\" {\n type = object({\n id = string\n public_ip = string\n private_ip = string\n })\n description = \"EC2 instance details\"\n value = {\n id = component.compute.instance_id\n public_ip = component.compute.public_ip\n private_ip = component.compute.private_ip\n }\n}\n```\n\nFor complete output examples including sensitive outputs and for expressions, see `examples.md`.\n\n## Locals Block\n\nDefines local values for reuse within the Stack configuration.\n\n### Syntax\n\n```hcl\nlocals {\n <name> = <expression>\n}\n```\n\n### Example\n\n```hcl\nlocals {\n common_tags = {\n Environment = var.environment\n ManagedBy = \"Terraform Stacks\"\n Project = var.project_name\n }\n\n name_prefix = \"${var.project_name}-${var.environment}\"\n\n region_config = {\n for region in var.regions : region => {\n name_suffix = region\n instance_count = var.environment == \"prod\" ? 3 : 1\n }\n }\n}\n```\n\n## Removed Block\n\nDeclares components to be removed from the Stack.\n\n### Syntax\n\n```hcl\nremoved {\n from = component.<component_name>\n source = \"<original_module_source>\"\n \n providers = {\n <provider_name> = provider.<type>.<alias>\n }\n}\n```\n\n### Arguments\n\n- **from** (required): Reference to the component being removed\n- **source** (required): Original module source\n- **providers** (required): Provider configurations needed for removal\n\n### Important Notes\n\n- Required for safe component removal\n- Must include all providers the component used\n- Do not remove providers before removing components that use them\n\n### Examples\n\n```hcl\nremoved {\n from = component.old_component\n source = \"./modules/deprecated-module\"\n \n providers = {\n aws = provider.aws.main\n }\n}\n\nremoved {\n from = component.legacy_regional\n source = \"registry.terraform.io/example/legacy/aws\"\n \n providers = {\n aws = provider.aws.main\n random = provider.random.main\n }\n}\n```\n\n## Provider References in Component Blocks\n\n### Single Provider\n\n```hcl\nproviders = {\n aws = provider.aws.main\n}\n```\n\n### Multiple Providers\n\n```hcl\nproviders = {\n aws = provider.aws.main\n random = provider.random.main\n tls = provider.tls.main\n}\n```\n\n### Provider from for_each\n\n```hcl\nproviders = {\n aws = provider.aws.regional[each.value]\n}\n```\n\n### Aliased Providers in Module\n\nIf module requires specific provider aliases:\n\n```hcl\nproviders = {\n aws.source = provider.aws.us_east\n aws.dest = provider.aws.eu_west\n}\n```\n",
416
+ "byte_size": 10165,
417
+ "content_sha256": "9881d3f31cd584cddb8e94a28868892b02464c42d2c57f0c0f99f45d18d30baa"
418
+ },
419
+ {
420
+ "path": "references/deployment-blocks.md",
421
+ "content": "# Deployment Configuration Block Reference\n\nComplete reference for all blocks available in Terraform Stack deployment configuration files (`.tfdeploy.hcl`).\n\n## Table of Contents\n\n1. [Identity Token Block](#identity-token-block)\n2. [Locals Block](#locals-block)\n3. [Deployment Block](#deployment-block)\n4. [Deployment Group Block](#deployment-group-block)\n5. [Deployment Auto-Approve Block](#deployment-auto-approve-block)\n\n**Note**: For Publish Output and Upstream Input blocks (linked Stacks), see `linked-stacks.md`.\n\n## Identity Token Block\n\nGenerates JWT tokens for OIDC authentication with cloud providers.\n\n### Syntax\n\n```hcl\nidentity_token \"<token_name>\" {\n audience = [<audience_strings>]\n}\n```\n\n### Arguments\n\n- **token_name** (label, required): Unique identifier for this token\n- **audience** (required): List of audience strings for the JWT\n\n### Accessing Token\n\nReference the JWT using: `identity_token.<n>.jwt`\n\n### Cloud Provider Audiences\n\n**AWS:**\n```hcl\nidentity_token \"aws\" {\n audience = [\"aws.workload.identity\"]\n}\n```\n\n**Azure:**\n```hcl\nidentity_token \"azure\" {\n audience = [\"api://AzureADTokenExchange\"]\n}\n```\n\n**Google Cloud:**\n```hcl\nidentity_token \"gcp\" {\n audience = [\"//iam.googleapis.com/projects/<PROJECT_NUMBER>/locations/global/workloadIdentityPools/<POOL_ID>/providers/<PROVIDER_ID>\"]\n}\n```\n\n**Setup Documentation:** For detailed instructions on configuring OIDC/workload identity for each cloud provider (including IAM roles, trust policies, and federated credentials), see: https://developer.hashicorp.com/terraform/cloud-docs/dynamic-provider-credentials\n\n### Examples\n\n**Single Token:**\n\n```hcl\nidentity_token \"aws\" {\n audience = [\"aws.workload.identity\"]\n}\n\ndeployment \"production\" {\n inputs = {\n identity_token = identity_token.aws.jwt\n role_arn = var.role_arn\n }\n}\n```\n\nFor complete working examples including multi-region identity token usage, see `examples.md`.\n\n## Locals Block\n\nDefines local values for reuse within deployment configuration.\n\n### Syntax\n\n```hcl\nlocals {\n <n> = <expression>\n}\n```\n\n### Example\n\n```hcl\nlocals {\n aws_regions = [\"us-west-1\", \"us-east-1\", \"eu-west-1\"]\n role_arn = \"arn:aws:iam::123456789012:role/hcp-terraform-stacks\"\n\n common_inputs = {\n project_name = \"my-app\"\n environment = \"production\"\n }\n}\n```\n\n## Deployment Block\n\nDefines deployment instances of the Stack.\n\n### Syntax\n\n```hcl\ndeployment \"<deployment_name>\" {\n inputs = {\n <input_name> = <value>\n }\n}\n```\n\n### Arguments\n\n- **deployment_name** (label, required): Unique identifier for this deployment\n- **inputs** (required): Map of input variable values\n- **destroy** (optional, default: false): Boolean flag to destroy this deployment\n\n### Constraints\n\n- Minimum 1 deployment per Stack\n- Maximum 20 deployments per Stack\n- No meta-arguments supported (no `for_each`, `count`)\n\n### Destroying a Deployment\n\nTo safely remove a deployment from your Stack:\n\n1. Set `destroy = true` in the deployment block\n2. Apply the plan through HCP Terraform\n3. After successful destruction, remove the deployment block from your configuration\n\n**Important**: Using the `destroy` argument ensures your configuration has the provider authentication necessary to properly destroy the deployment's resources.\n\n**Example:**\n```hcl\ndeployment \"old_environment\" {\n inputs = {\n aws_region = \"us-west-1\"\n instance_count = 2\n role_arn = local.role_arn\n identity_token = identity_token.aws.jwt\n }\n destroy = true # Mark for destruction\n}\n```\n\nAfter applying this plan and the deployment is destroyed, remove the entire `deployment \"old_environment\"` block from your configuration.\n\n### Examples\n\n**Single Deployment:**\n\n```hcl\ndeployment \"production\" {\n inputs = {\n aws_region = \"us-west-1\"\n instance_count = 5\n instance_type = \"t3.large\"\n role_arn = local.role_arn\n identity_token = identity_token.aws.jwt\n }\n}\n```\n\n**Using Locals for Multiple Deployments:**\n\n```hcl\nlocals {\n common_inputs = {\n role_arn = \"arn:aws:iam::123456789012:role/terraform\"\n identity_token = identity_token.aws.jwt\n project_name = \"my-app\"\n }\n}\n\ndeployment \"dev\" {\n inputs = merge(local.common_inputs, {\n aws_region = \"us-east-1\"\n instance_count = 1\n environment = \"dev\"\n })\n}\n\ndeployment \"prod\" {\n inputs = merge(local.common_inputs, {\n aws_region = \"us-west-1\"\n instance_count = 5\n environment = \"prod\"\n })\n}\n```\n\nFor complete multi-environment and multi-region deployment examples, see `examples.md`.\n\n## Deployment Group Block\n\nGroups deployments together to configure shared settings and auto-approval rules (HCP Terraform Premium tier feature).\n\n### Syntax\n\n```hcl\ndeployment_group \"<group_name>\" {\n deployments = [<deployment_references>]\n}\n```\n\n### Arguments\n\n- **group_name** (label, required): Unique identifier for this deployment group\n- **deployments** (required): List of deployment references to include in this group\n\n### Purpose\n\nDeployment groups allow you to:\n- Organize deployments logically (by environment, team, region, etc.)\n- Configure shared auto-approval rules for multiple deployments\n- Manage deployments more effectively at scale\n- Establish consistent configuration patterns across all Stacks\n\n### Examples\n\n**Single Deployment Group (Best Practice):**\n\n```hcl\ndeployment \"production\" {\n inputs = {\n aws_region = \"us-west-1\"\n instance_count = 5\n role_arn = local.role_arn\n identity_token = identity_token.aws.jwt\n }\n}\n\ndeployment_group \"production\" {\n deployments = [deployment.production]\n}\n```\n\n**Multiple Deployment Groups:**\n\n```hcl\ndeployment_group \"non_production\" {\n deployments = [\n deployment.development,\n deployment.staging\n ]\n}\n\ndeployment_group \"production\" {\n deployments = [\n deployment.prod_us_east,\n deployment.prod_us_west,\n deployment.prod_eu_west\n ]\n}\n```\n\n## Deployment Auto-Approve Block\n\nDefines rules that automatically approve deployment plans based on specific conditions (HCP Terraform Premium feature).\n\n### Syntax\n\n```hcl\ndeployment_auto_approve \"<rule_name>\" {\n deployment_group = deployment_group.<group_name>\n \n check {\n condition = <boolean_expression>\n reason = \"<failure_message>\"\n }\n}\n```\n\n### Arguments\n\n- **rule_name** (label, required): Unique identifier for this auto-approve rule\n- **deployment_group** (required): Reference to the deployment group this rule applies to\n- **check** (required, one or more): Condition that must be met for auto-approval\n\n### Context Variables\n\nAccess plan information through `context` object:\n\n- `context.plan.applyable` - Boolean: plan succeeded without errors\n- `context.plan.changes.add` - Number: resources to add\n- `context.plan.changes.change` - Number: resources to change\n- `context.plan.changes.remove` - Number: resources to remove\n- `context.plan.changes.import` - Number: resources to import\n\n### Important Notes\n\n- All checks must pass for auto-approval to occur\n- If any check fails, manual approval is required\n- HCP Terraform displays the failure reason from failed checks\n- Auto-approve rules only apply to deployments in the specified deployment group\n\n### Examples\n\n**Auto-approve Successful Plans:**\n\n```hcl\ndeployment_group \"canary\" {\n deployments = [\n deployment.dev,\n deployment.staging\n ]\n}\n\ndeployment_auto_approve \"applyable_plans\" {\n deployment_group = deployment_group.canary\n \n check {\n condition = context.plan.applyable\n reason = \"Plan must be applyable without errors\"\n }\n}\n```\n\n**Auto-approve Non-Destructive Changes:**\n\n```hcl\ndeployment_group \"production\" {\n deployments = [\n deployment.prod_primary,\n deployment.prod_secondary\n ]\n}\n\ndeployment_auto_approve \"safe_production_changes\" {\n deployment_group = deployment_group.production\n \n check {\n condition = context.plan.changes.remove == 0\n reason = \"Production deletions require manual approval\"\n }\n \n check {\n condition = context.plan.applyable\n reason = \"Plan must be successful\"\n }\n}\n```\n\n**Graduated Rollout Pattern:**\n\n```hcl\ndeployment_group \"canary\" {\n deployments = [deployment.canary]\n}\n\ndeployment_group \"production\" {\n deployments = [\n deployment.prod_us,\n deployment.prod_eu,\n deployment.prod_asia\n ]\n}\n\n# Canary auto-approves with strict checks\ndeployment_auto_approve \"canary_strict\" {\n deployment_group = deployment_group.canary\n \n check {\n condition = context.plan.changes.remove == 0\n reason = \"Canary cannot delete resources\"\n }\n \n check {\n condition = context.plan.changes.change <= 5\n reason = \"Canary limited to 5 resource changes\"\n }\n \n check {\n condition = context.plan.applyable\n reason = \"Plan must be applyable\"\n }\n}\n\n# Production requires manual approval after canary validation\n```\n\nFor complete deployment configuration examples with all blocks, see `examples.md`.\n",
422
+ "byte_size": 8877,
423
+ "content_sha256": "1c4dfd005e51bf6fc078552c1e52ee5a87b118499cb0a380a9b35b97dc072868"
424
+ },
425
+ {
426
+ "path": "references/examples.md",
427
+ "content": "# Terraform Stacks Complete Examples\n\nComplete, working examples for common Terraform Stacks scenarios.\n\n## Table of Contents\n\n1. [Simple Single-Region Stack](#simple-single-region-stack)\n2. [Stack with Private Registry Modules](#stack-with-private-registry-modules)\n3. [Multi-Environment Stack](#multi-environment-stack)\n4. [Multi-Region Stack](#multi-region-stack)\n5. [Linked Stacks (Cross-Stack Dependencies)](#linked-stacks-cross-stack-dependencies)\n6. [Multi-Cloud Stack](#multi-cloud-stack)\n7. [Complete AWS Production Stack](#complete-aws-production-stack)\n8. [Destroying Deployments](#destroying-deployments)\n\n## Simple Single-Region Stack\n\nBasic Stack with a single environment deployment.\n\n### File Structure\n```\nsimple-stack/\n├── variables.tfcomponent.hcl\n├── providers.tfcomponent.hcl\n├── components.tfcomponent.hcl\n├── deployments.tfdeploy.hcl\n└── modules/\n └── webapp/\n ├── main.tf\n ├── variables.tf\n └── outputs.tf\n```\n\n### variables.tfcomponent.hcl\n```hcl\nvariable \"aws_region\" {\n type = string\n default = \"us-west-1\"\n}\n\nvariable \"identity_token\" {\n type = string\n ephemeral = true\n}\n\nvariable \"role_arn\" {\n type = string\n}\n\nvariable \"app_name\" {\n type = string\n}\n```\n\n### providers.tfcomponent.hcl\n```hcl\nrequired_providers {\n aws = {\n source = \"hashicorp/aws\"\n version = \"~> 5.7.0\"\n }\n}\n\nprovider \"aws\" \"main\" {\n config {\n region = var.aws_region\n \n assume_role_with_web_identity {\n role_arn = var.role_arn\n web_identity_token = var.identity_token\n }\n }\n}\n```\n\n### components.tfcomponent.hcl\n```hcl\ncomponent \"webapp\" {\n source = \"./modules/webapp\"\n \n inputs = {\n app_name = var.app_name\n region = var.aws_region\n }\n \n providers = {\n aws = provider.aws.main\n }\n}\n```\n\n### deployments.tfdeploy.hcl\n```hcl\nidentity_token \"aws\" {\n audience = [\"aws.workload.identity\"]\n}\n\ndeployment \"production\" {\n inputs = {\n aws_region = \"us-west-1\"\n app_name = \"my-webapp\"\n role_arn = \"arn:aws:iam::123456789012:role/terraform-stacks\"\n identity_token = identity_token.aws.jwt\n }\n}\n\n# Deployment groups\ndeployment_group \"production\" {\n deployments = [deployment.production]\n}\n```\n\n## Stack with Private Registry Modules\n\nExample Stack using modules from a private HCP Terraform registry, combining both private and public registry sources.\n\n### File Structure\n```\nprivate-registry-stack/\n├── variables.tfcomponent.hcl\n├── providers.tfcomponent.hcl\n├── components.tfcomponent.hcl\n├── outputs.tfcomponent.hcl\n└── deployments.tfdeploy.hcl\n```\n\n### variables.tfcomponent.hcl\n```hcl\nvariable \"aws_region\" {\n type = string\n default = \"us-west-2\"\n}\n\nvariable \"environment\" {\n type = string\n}\n\nvariable \"identity_token\" {\n type = string\n ephemeral = true\n}\n\nvariable \"role_arn\" {\n type = string\n}\n\nvariable \"vpc_cidr\" {\n type = string\n default = \"10.0.0.0/16\"\n}\n\nvariable \"app_name\" {\n type = string\n}\n\nvariable \"db_password\" {\n type = string\n sensitive = true\n}\n```\n\n### providers.tfcomponent.hcl\n```hcl\nrequired_providers {\n aws = {\n source = \"hashicorp/aws\"\n version = \"~> 5.7.0\"\n }\n random = {\n source = \"hashicorp/random\"\n version = \"~> 3.5.0\"\n }\n}\n\nprovider \"aws\" \"main\" {\n config {\n region = var.aws_region\n\n assume_role_with_web_identity {\n role_arn = var.role_arn\n web_identity_token = var.identity_token\n }\n\n default_tags {\n tags = {\n Environment = var.environment\n ManagedBy = \"Terraform Stacks\"\n Application = var.app_name\n }\n }\n }\n}\n\nprovider \"random\" \"main\" {\n config {}\n}\n```\n\n### components.tfcomponent.hcl\n```hcl\nlocals {\n name_prefix = \"${var.app_name}-${var.environment}\"\n common_tags = {\n Project = var.app_name\n Environment = var.environment\n }\n}\n\n# Using a private registry module for VPC\ncomponent \"vpc\" {\n source = \"app.terraform.io/my-org/vpc/aws\"\n version = \"2.1.0\"\n\n inputs = {\n name_prefix = local.name_prefix\n cidr_block = var.vpc_cidr\n availability_zones = [\"${var.aws_region}a\", \"${var.aws_region}b\", \"${var.aws_region}c\"]\n enable_nat_gateway = true\n single_nat_gateway = var.environment != \"prod\"\n tags = local.common_tags\n }\n\n providers = {\n aws = provider.aws.main\n }\n}\n\n# Using a private registry module for security groups\ncomponent \"security_groups\" {\n source = \"app.terraform.io/my-org/security-groups/aws\"\n version = \"1.5.2\"\n\n inputs = {\n vpc_id = component.vpc.vpc_id\n name_prefix = local.name_prefix\n environment = var.environment\n }\n\n providers = {\n aws = provider.aws.main\n }\n}\n\n# Using a public registry module for RDS\ncomponent \"database\" {\n source = \"terraform-aws-modules/rds/aws\"\n version = \"~> 6.0\"\n\n inputs = {\n identifier = \"${local.name_prefix}-db\"\n engine = \"postgres\"\n engine_version = \"15.3\"\n family = \"postgres15\"\n major_engine_version = \"15\"\n instance_class = var.environment == \"prod\" ? \"db.t3.large\" : \"db.t3.micro\"\n\n allocated_storage = var.environment == \"prod\" ? 100 : 20\n db_name = replace(var.app_name, \"-\", \"_\")\n username = \"dbadmin\"\n password = var.db_password\n port = 5432\n\n db_subnet_group_name = component.vpc.database_subnet_group_name\n vpc_security_group_ids = [component.security_groups.database_sg_id]\n\n backup_retention_period = var.environment == \"prod\" ? 30 : 7\n skip_final_snapshot = var.environment != \"prod\"\n deletion_protection = var.environment == \"prod\"\n\n tags = local.common_tags\n }\n\n providers = {\n aws = provider.aws.main\n }\n}\n\n# Using a private registry module for application infrastructure\ncomponent \"application\" {\n source = \"app.terraform.io/my-org/ecs-application/aws\"\n version = \"3.2.1\"\n\n inputs = {\n name_prefix = local.name_prefix\n vpc_id = component.vpc.vpc_id\n private_subnet_ids = component.vpc.private_subnet_ids\n public_subnet_ids = component.vpc.public_subnet_ids\n app_security_group_id = component.security_groups.app_sg_id\n\n container_image = \"my-org/my-app:latest\"\n container_port = 8080\n desired_count = var.environment == \"prod\" ? 3 : 1\n\n environment_variables = {\n ENVIRONMENT = var.environment\n DATABASE_HOST = component.database.db_instance_endpoint\n DATABASE_NAME = component.database.db_instance_name\n }\n\n tags = local.common_tags\n }\n\n providers = {\n aws = provider.aws.main\n }\n}\n```\n\n### outputs.tfcomponent.hcl\n```hcl\noutput \"vpc_id\" {\n type = string\n description = \"VPC ID\"\n value = component.vpc.vpc_id\n}\n\noutput \"application_url\" {\n type = string\n description = \"Application load balancer URL\"\n value = component.application.load_balancer_dns\n}\n\noutput \"database_endpoint\" {\n type = string\n description = \"Database endpoint\"\n value = component.database.db_instance_endpoint\n sensitive = true\n}\n```\n\n### deployments.tfdeploy.hcl\n```hcl\nidentity_token \"aws\" {\n audience = [\"aws.workload.identity\"]\n}\n\nlocals {\n role_arn = \"arn:aws:iam::123456789012:role/terraform-stacks\"\n}\n\ndeployment \"development\" {\n inputs = {\n aws_region = \"us-west-2\"\n environment = \"dev\"\n app_name = \"myapp\"\n vpc_cidr = \"10.0.0.0/16\"\n db_password = \"dev-password-change-me\"\n role_arn = local.role_arn\n identity_token = identity_token.aws.jwt\n }\n}\n\ndeployment \"production\" {\n inputs = {\n aws_region = \"us-east-1\"\n environment = \"prod\"\n app_name = \"myapp\"\n vpc_cidr = \"10.1.0.0/16\"\n db_password = \"prod-password-use-secrets-manager\"\n role_arn = local.role_arn\n identity_token = identity_token.aws.jwt\n }\n}\n\n# Deployment groups\ndeployment_group \"development\" {\n deployments = [deployment.development]\n}\n\ndeployment_group \"production\" {\n deployments = [deployment.production]\n}\n```\n\n### Key Points\n\n- **Private registry modules** use the format `app.terraform.io/<org>/<module>/<provider>`\n- **Version constraints** ensure consistent module versions across environments\n- **Mixed sources**: Combining private registry modules (VPC, security groups, application) with public registry modules (RDS)\n- **Authentication**: HCP Terraform workspaces automatically authenticate to private registries; CLI users need credentials configured\n- **Terraform Enterprise**: Replace `app.terraform.io` with your instance hostname\n\n## Multi-Environment Stack\n\nStack with development, staging, and production deployments.\n\n### variables.tfcomponent.hcl\n```hcl\nvariable \"aws_region\" {\n type = string\n}\n\nvariable \"environment\" {\n type = string\n}\n\nvariable \"instance_count\" {\n type = number\n}\n\nvariable \"instance_type\" {\n type = string\n}\n\nvariable \"identity_token\" {\n type = string\n ephemeral = true\n}\n\nvariable \"role_arn\" {\n type = string\n}\n```\n\n### providers.tfcomponent.hcl\n```hcl\nrequired_providers {\n aws = {\n source = \"hashicorp/aws\"\n version = \"~> 5.7.0\"\n }\n}\n\nprovider \"aws\" \"this\" {\n config {\n region = var.aws_region\n \n assume_role_with_web_identity {\n role_arn = var.role_arn\n web_identity_token = var.identity_token\n }\n \n default_tags {\n tags = {\n Environment = var.environment\n ManagedBy = \"Terraform Stacks\"\n }\n }\n }\n}\n```\n\n### components.tfcomponent.hcl\n```hcl\nlocals {\n name_prefix = \"myapp-${var.environment}\"\n}\n\ncomponent \"vpc\" {\n source = \"./modules/vpc\"\n \n inputs = {\n name_prefix = local.name_prefix\n cidr_block = \"10.0.0.0/16\"\n }\n \n providers = {\n aws = provider.aws.this\n }\n}\n\ncomponent \"compute\" {\n source = \"./modules/compute\"\n \n inputs = {\n name_prefix = local.name_prefix\n vpc_id = component.vpc.vpc_id\n subnet_ids = component.vpc.private_subnet_ids\n instance_count = var.instance_count\n instance_type = var.instance_type\n }\n \n providers = {\n aws = provider.aws.this\n }\n}\n```\n\n### outputs.tfcomponent.hcl\n```hcl\noutput \"vpc_id\" {\n type = string\n value = component.vpc.vpc_id\n}\n\noutput \"load_balancer_url\" {\n type = string\n value = component.compute.load_balancer_url\n}\n```\n\n### deployments.tfdeploy.hcl\n```hcl\nidentity_token \"aws\" {\n audience = [\"aws.workload.identity\"]\n}\n\nlocals {\n role_arn = \"arn:aws:iam::123456789012:role/terraform-stacks\"\n \n environments = {\n dev = {\n region = \"us-east-1\"\n instance_count = 1\n instance_type = \"t3.micro\"\n }\n staging = {\n region = \"us-west-1\"\n instance_count = 2\n instance_type = \"t3.small\"\n }\n prod = {\n region = \"us-west-1\"\n instance_count = 5\n instance_type = \"t3.large\"\n }\n }\n}\n\ndeployment \"development\" {\n inputs = {\n aws_region = local.environments.dev.region\n environment = \"dev\"\n instance_count = local.environments.dev.instance_count\n instance_type = local.environments.dev.instance_type\n role_arn = local.role_arn\n identity_token = identity_token.aws.jwt\n }\n}\n\ndeployment \"staging\" {\n inputs = {\n aws_region = local.environments.staging.region\n environment = \"staging\"\n instance_count = local.environments.staging.instance_count\n instance_type = local.environments.staging.instance_type\n role_arn = local.role_arn\n identity_token = identity_token.aws.jwt\n }\n}\n\ndeployment \"production\" {\n inputs = {\n aws_region = local.environments.prod.region\n environment = \"prod\"\n instance_count = local.environments.prod.instance_count\n instance_type = local.environments.prod.instance_type\n role_arn = local.role_arn\n identity_token = identity_token.aws.jwt\n }\n}\n\n# Deployment groups\ndeployment_group \"development\" {\n deployments = [deployment.development]\n}\n\ndeployment_group \"non_production\" {\n deployments = [deployment.staging]\n}\n\ndeployment_group \"production\" {\n deployments = [deployment.production]\n}\n\n# Auto-approve dev deployments\ndeployment_auto_approve \"dev_auto\" {\n deployment_group = deployment_group.development\n\n check {\n condition = context.plan.applyable\n reason = \"Development plans must be applyable\"\n }\n}\n```\n\n## Multi-Region Stack\n\nStack that deploys identical infrastructure across multiple AWS regions.\n\n### variables.tfcomponent.hcl\n```hcl\nvariable \"regions\" {\n type = set(string)\n}\n\nvariable \"identity_token\" {\n type = string\n ephemeral = true\n}\n\nvariable \"role_arn\" {\n type = string\n}\n\nvariable \"app_name\" {\n type = string\n}\n```\n\n### providers.tfcomponent.hcl\n```hcl\nrequired_providers {\n aws = {\n source = \"hashicorp/aws\"\n version = \"~> 5.7.0\"\n }\n}\n\nprovider \"aws\" \"regional\" {\n for_each = var.regions\n \n config {\n region = each.value\n \n assume_role_with_web_identity {\n role_arn = var.role_arn\n web_identity_token = var.identity_token\n }\n \n default_tags {\n tags = {\n Region = each.value\n ManagedBy = \"Terraform Stacks\"\n AppName = var.app_name\n }\n }\n }\n}\n```\n\n### components.tfcomponent.hcl\n```hcl\ncomponent \"regional_infrastructure\" {\n for_each = var.regions\n \n source = \"./modules/regional-infra\"\n \n inputs = {\n region = each.value\n app_name = var.app_name\n name_suffix = each.value\n }\n \n providers = {\n aws = provider.aws.regional[each.value]\n }\n}\n\ncomponent \"global_route53\" {\n source = \"./modules/route53\"\n \n inputs = {\n app_name = var.app_name\n domain_name = \"example.com\"\n regional_lbs = {\n for region, comp in component.regional_infrastructure :\n region => comp.load_balancer_dns\n }\n }\n \n # Use one region's provider for global resources\n providers = {\n aws = provider.aws.regional[\"us-west-1\"]\n }\n}\n```\n\n### outputs.tfcomponent.hcl\n```hcl\noutput \"regional_endpoints\" {\n type = map(string)\n value = {\n for region, comp in component.regional_infrastructure :\n region => comp.load_balancer_url\n }\n}\n\noutput \"global_domain\" {\n type = string\n value = component.global_route53.domain_name\n}\n```\n\n### deployments.tfdeploy.hcl\n```hcl\nidentity_token \"aws\" {\n audience = [\"aws.workload.identity\"]\n}\n\nlocals {\n regions = [\"us-west-1\", \"us-east-1\", \"eu-west-1\"]\n}\n\ndeployment \"multi_region_prod\" {\n inputs = {\n regions = toset(local.regions)\n app_name = \"my-global-app\"\n role_arn = \"arn:aws:iam::123456789012:role/terraform-stacks\"\n identity_token = identity_token.aws.jwt\n }\n}\n\n# Deployment groups\ndeployment_group \"production\" {\n deployments = [deployment.multi_region_prod]\n}\n```\n\n## Linked Stacks (Cross-Stack Dependencies)\n\nTwo Stacks where the application Stack depends on the network Stack.\n\n### Network Stack\n\n#### network-stack/variables.tfcomponent.hcl\n```hcl\nvariable \"vpc_cidr\" {\n type = string\n}\n\nvariable \"environment\" {\n type = string\n}\n\nvariable \"aws_region\" {\n type = string\n}\n\nvariable \"identity_token\" {\n type = string\n ephemeral = true\n}\n\nvariable \"role_arn\" {\n type = string\n}\n```\n\n#### network-stack/providers.tfcomponent.hcl\n```hcl\nrequired_providers {\n aws = {\n source = \"hashicorp/aws\"\n version = \"~> 5.7.0\"\n }\n}\n\nprovider \"aws\" \"this\" {\n config {\n region = var.aws_region\n \n assume_role_with_web_identity {\n role_arn = var.role_arn\n web_identity_token = var.identity_token\n }\n }\n}\n```\n\n#### network-stack/components.tfcomponent.hcl\n```hcl\ncomponent \"vpc\" {\n source = \"./modules/vpc\"\n \n inputs = {\n cidr_block = var.vpc_cidr\n environment = var.environment\n }\n \n providers = {\n aws = provider.aws.this\n }\n}\n\ncomponent \"security_groups\" {\n source = \"./modules/security-groups\"\n \n inputs = {\n vpc_id = component.vpc.vpc_id\n environment = var.environment\n }\n \n providers = {\n aws = provider.aws.this\n }\n}\n```\n\n#### network-stack/outputs.tfcomponent.hcl\n```hcl\noutput \"vpc_id\" {\n type = string\n value = component.vpc.vpc_id\n}\n\noutput \"private_subnet_ids\" {\n type = list(string)\n value = component.vpc.private_subnet_ids\n}\n\noutput \"public_subnet_ids\" {\n type = list(string)\n value = component.vpc.public_subnet_ids\n}\n\noutput \"app_security_group_id\" {\n type = string\n value = component.security_groups.app_sg_id\n}\n```\n\n#### network-stack/deployments.tfdeploy.hcl\n```hcl\nidentity_token \"aws\" {\n audience = [\"aws.workload.identity\"]\n}\n\nlocals {\n role_arn = \"arn:aws:iam::123456789012:role/terraform-stacks\"\n}\n\ndeployment \"network\" {\n inputs = {\n aws_region = \"us-west-1\"\n environment = \"production\"\n vpc_cidr = \"10.0.0.0/16\"\n role_arn = local.role_arn\n identity_token = identity_token.aws.jwt\n }\n}\n\n# Publish outputs for other stacks\npublish_output \"vpc_id_network\" {\n type = string\n value = deployment.network.vpc_id\n}\n\npublish_output \"private_subnet_ids\" {\n type = list(string)\n value = deployment.network.private_subnet_ids\n}\n\npublish_output \"public_subnet_ids\" {\n type = list(string)\n value = deployment.network.public_subnet_ids\n}\n\npublish_output \"app_security_group_id\" {\n type = string\n value = deployment.network.app_security_group_id\n}\n\n# Deployment groups\ndeployment_group \"network\" {\n deployments = [deployment.network]\n}\n```\n\n### Application Stack\n\n#### application-stack/variables.tfcomponent.hcl\n```hcl\nvariable \"vpc_id\" {\n type = string\n}\n\nvariable \"subnet_ids\" {\n type = list(string)\n}\n\nvariable \"security_group_id\" {\n type = string\n}\n\nvariable \"instance_count\" {\n type = number\n}\n\nvariable \"aws_region\" {\n type = string\n}\n\nvariable \"identity_token\" {\n type = string\n ephemeral = true\n}\n\nvariable \"role_arn\" {\n type = string\n}\n```\n\n#### application-stack/providers.tfcomponent.hcl\n```hcl\nrequired_providers {\n aws = {\n source = \"hashicorp/aws\"\n version = \"~> 5.7.0\"\n }\n}\n\nprovider \"aws\" \"this\" {\n config {\n region = var.aws_region\n \n assume_role_with_web_identity {\n role_arn = var.role_arn\n web_identity_token = var.identity_token\n }\n }\n}\n```\n\n#### application-stack/components.tfcomponent.hcl\n```hcl\ncomponent \"application\" {\n source = \"./modules/app\"\n \n inputs = {\n vpc_id = var.vpc_id\n subnet_ids = var.subnet_ids\n security_group_id = var.security_group_id\n instance_count = var.instance_count\n }\n \n providers = {\n aws = provider.aws.this\n }\n}\n```\n\n#### application-stack/deployments.tfdeploy.hcl\n```hcl\nidentity_token \"aws\" {\n audience = [\"aws.workload.identity\"]\n}\n\n# Reference the network stack\nupstream_input \"network\" {\n type = \"stack\"\n source = \"app.terraform.io/my-org/my-project/network-stack\"\n}\n\ndeployment \"application\" {\n inputs = {\n aws_region = \"us-west-1\"\n vpc_id = upstream_input.network.vpc_id_network\n subnet_ids = upstream_input.network.private_subnet_ids\n security_group_id = upstream_input.network.app_security_group_id\n instance_count = 3\n role_arn = \"arn:aws:iam::123456789012:role/terraform-stacks\"\n identity_token = identity_token.aws.jwt\n }\n}\n\n# Deployment groups\ndeployment_group \"application\" {\n deployments = [deployment.application]\n}\n```\n\n## Multi-Cloud Stack\n\nStack that deploys to both AWS and Azure.\n\n### variables.tfcomponent.hcl\n```hcl\nvariable \"aws_region\" {\n type = string\n}\n\nvariable \"azure_location\" {\n type = string\n}\n\nvariable \"aws_identity_token\" {\n type = string\n ephemeral = true\n}\n\nvariable \"aws_role_arn\" {\n type = string\n}\n\nvariable \"azure_identity_token\" {\n type = string\n ephemeral = true\n}\n\nvariable \"azure_subscription_id\" {\n type = string\n}\n\nvariable \"azure_tenant_id\" {\n type = string\n}\n\nvariable \"azure_client_id\" {\n type = string\n}\n\nvariable \"app_name\" {\n type = string\n}\n```\n\n### providers.tfcomponent.hcl\n```hcl\nrequired_providers {\n aws = {\n source = \"hashicorp/aws\"\n version = \"~> 5.7.0\"\n }\n azurerm = {\n source = \"hashicorp/azurerm\"\n version = \"~> 3.0\"\n }\n}\n\nprovider \"aws\" \"this\" {\n config {\n region = var.aws_region\n \n assume_role_with_web_identity {\n role_arn = var.aws_role_arn\n web_identity_token = var.aws_identity_token\n }\n }\n}\n\nprovider \"azurerm\" \"this\" {\n config {\n features {}\n \n subscription_id = var.azure_subscription_id\n tenant_id = var.azure_tenant_id\n client_id = var.azure_client_id\n \n use_oidc = true\n oidc_token = var.azure_identity_token\n }\n}\n```\n\n### components.tfcomponent.hcl\n```hcl\ncomponent \"aws_infrastructure\" {\n source = \"./modules/aws-infra\"\n \n inputs = {\n region = var.aws_region\n app_name = var.app_name\n }\n \n providers = {\n aws = provider.aws.this\n }\n}\n\ncomponent \"azure_infrastructure\" {\n source = \"./modules/azure-infra\"\n \n inputs = {\n location = var.azure_location\n app_name = var.app_name\n }\n \n providers = {\n azurerm = provider.azurerm.this\n }\n}\n```\n\n### deployments.tfdeploy.hcl\n```hcl\nidentity_token \"aws\" {\n audience = [\"aws.workload.identity\"]\n}\n\nidentity_token \"azure\" {\n audience = [\"api://AzureADTokenExchange\"]\n}\n\ndeployment \"multi_cloud\" {\n inputs = {\n aws_region = \"us-west-1\"\n azure_location = \"westus2\"\n app_name = \"my-multi-cloud-app\"\n aws_role_arn = \"arn:aws:iam::123456789012:role/terraform-stacks\"\n aws_identity_token = identity_token.aws.jwt\n azure_subscription_id = \"12345678-1234-1234-1234-123456789012\"\n azure_tenant_id = \"87654321-4321-4321-4321-210987654321\"\n azure_client_id = \"11111111-1111-1111-1111-111111111111\"\n azure_identity_token = identity_token.azure.jwt\n }\n}\n\n# Deployment groups\ndeployment_group \"multi_cloud\" {\n deployments = [deployment.multi_cloud]\n}\n```\n\n## Complete AWS Production Stack\n\nFull production-grade Stack with VPC, RDS, ECS, and monitoring.\n\n### variables.tfcomponent.hcl\n```hcl\nvariable \"aws_region\" {\n type = string\n description = \"AWS region\"\n}\n\nvariable \"environment\" {\n type = string\n description = \"Environment name\"\n}\n\nvariable \"vpc_cidr\" {\n type = string\n description = \"VPC CIDR block\"\n}\n\nvariable \"app_name\" {\n type = string\n description = \"Application name\"\n}\n\nvariable \"db_instance_class\" {\n type = string\n description = \"RDS instance class\"\n}\n\nvariable \"ecs_desired_count\" {\n type = number\n description = \"Desired ECS task count\"\n}\n\nvariable \"identity_token\" {\n type = string\n ephemeral = true\n}\n\nvariable \"role_arn\" {\n type = string\n}\n```\n\n### providers.tfcomponent.hcl\n```hcl\nrequired_providers {\n aws = {\n source = \"hashicorp/aws\"\n version = \"~> 5.7.0\"\n }\n random = {\n source = \"hashicorp/random\"\n version = \"~> 3.5.0\"\n }\n}\n\nprovider \"aws\" \"this\" {\n config {\n region = var.aws_region\n \n assume_role_with_web_identity {\n role_arn = var.role_arn\n web_identity_token = var.identity_token\n }\n \n default_tags {\n tags = {\n Environment = var.environment\n Application = var.app_name\n ManagedBy = \"Terraform Stacks\"\n }\n }\n }\n}\n\nprovider \"random\" \"this\" {\n config {}\n}\n```\n\n### components.tfcomponent.hcl\n```hcl\nlocals {\n name_prefix = \"${var.app_name}-${var.environment}\"\n}\n\ncomponent \"vpc\" {\n source = \"./modules/vpc\"\n \n inputs = {\n name_prefix = local.name_prefix\n cidr_block = var.vpc_cidr\n azs_count = 3\n }\n \n providers = {\n aws = provider.aws.this\n }\n}\n\ncomponent \"security_groups\" {\n source = \"./modules/security-groups\"\n \n inputs = {\n name_prefix = local.name_prefix\n vpc_id = component.vpc.vpc_id\n }\n \n providers = {\n aws = provider.aws.this\n }\n}\n\ncomponent \"rds\" {\n source = \"./modules/rds\"\n \n inputs = {\n name_prefix = local.name_prefix\n instance_class = var.db_instance_class\n subnet_ids = component.vpc.private_subnet_ids\n security_group_ids = [component.security_groups.database_sg_id]\n }\n \n providers = {\n aws = provider.aws.this\n random = provider.random.this\n }\n}\n\ncomponent \"ecs_cluster\" {\n source = \"./modules/ecs-cluster\"\n \n inputs = {\n name_prefix = local.name_prefix\n }\n \n providers = {\n aws = provider.aws.this\n }\n}\n\ncomponent \"ecs_service\" {\n source = \"./modules/ecs-service\"\n \n inputs = {\n name_prefix = local.name_prefix\n cluster_id = component.ecs_cluster.cluster_id\n desired_count = var.ecs_desired_count\n subnet_ids = component.vpc.private_subnet_ids\n security_group_id = component.security_groups.app_sg_id\n database_endpoint = component.rds.endpoint\n }\n \n providers = {\n aws = provider.aws.this\n }\n}\n\ncomponent \"alb\" {\n source = \"./modules/alb\"\n \n inputs = {\n name_prefix = local.name_prefix\n vpc_id = component.vpc.vpc_id\n subnet_ids = component.vpc.public_subnet_ids\n security_group_id = component.security_groups.alb_sg_id\n target_group_arn = component.ecs_service.target_group_arn\n }\n \n providers = {\n aws = provider.aws.this\n }\n}\n\ncomponent \"cloudwatch\" {\n source = \"./modules/cloudwatch\"\n \n inputs = {\n name_prefix = local.name_prefix\n cluster_name = component.ecs_cluster.cluster_name\n service_name = component.ecs_service.service_name\n }\n \n providers = {\n aws = provider.aws.this\n }\n}\n```\n\n### outputs.tfcomponent.hcl\n```hcl\noutput \"load_balancer_url\" {\n type = string\n description = \"Application load balancer URL\"\n value = component.alb.dns_name\n}\n\noutput \"database_endpoint\" {\n type = string\n description = \"RDS endpoint\"\n value = component.rds.endpoint\n sensitive = true\n}\n\noutput \"vpc_id\" {\n type = string\n value = component.vpc.vpc_id\n}\n\noutput \"ecs_cluster_name\" {\n type = string\n value = component.ecs_cluster.cluster_name\n}\n```\n\n### deployments.tfdeploy.hcl\n```hcl\nidentity_token \"aws\" {\n audience = [\"aws.workload.identity\"]\n}\n\nlocals {\n role_arn = \"arn:aws:iam::123456789012:role/terraform-stacks\"\n}\n\ndeployment \"staging\" {\n inputs = {\n aws_region = \"us-west-1\"\n environment = \"staging\"\n app_name = \"myapp\"\n vpc_cidr = \"10.1.0.0/16\"\n db_instance_class = \"db.t3.small\"\n ecs_desired_count = 2\n role_arn = local.role_arn\n identity_token = identity_token.aws.jwt\n }\n}\n\ndeployment \"production\" {\n inputs = {\n aws_region = \"us-west-1\"\n environment = \"production\"\n app_name = \"myapp\"\n vpc_cidr = \"10.0.0.0/16\"\n db_instance_class = \"db.r5.large\"\n ecs_desired_count = 5\n role_arn = local.role_arn\n identity_token = identity_token.aws.jwt\n }\n}\n\n# Deployment groups\ndeployment_group \"staging\" {\n deployments = [deployment.staging]\n}\n\ndeployment_group \"production\" {\n deployments = [deployment.production]\n}\n\n# Auto-approve staging with safety checks\ndeployment_auto_approve \"staging_safe\" {\n deployment_group = deployment_group.staging\n\n check {\n condition = context.plan.changes.remove == 0\n reason = \"Cannot auto-approve deletions in staging\"\n }\n\n check {\n condition = context.plan.applyable\n reason = \"Plan must be applyable\"\n }\n}\n```\n\n## Testing Configurations\n\n### Validate Stack Configuration\n```bash\nterraform stacks providers lock\nterraform stacks validate\n```\n\n### Plan Specific Deployment\n```bash\nterraform stacks plan --deployment=development\nterraform stacks plan --deployment=production\n```\n\n### Apply Deployment\n```bash\nterraform stacks apply --deployment=staging\n```\n\n## Destroying Deployments\n\nExample of safely removing a deployment from your Stack.\n\n### Scenario\n\nYou want to decommission the \"development\" deployment while keeping staging and production active.\n\n### Step 1: Mark Deployment for Destruction\n\nUpdate your `deployments.tfdeploy.hcl` file to set `destroy = true`:\n\n```hcl\nidentity_token \"aws\" {\n audience = [\"aws.workload.identity\"]\n}\n\nlocals {\n role_arn = \"arn:aws:iam::123456789012:role/terraform-stacks\"\n}\n\n# Mark this deployment for destruction\ndeployment \"development\" {\n inputs = {\n aws_region = \"us-east-1\"\n environment = \"dev\"\n instance_count = 1\n role_arn = local.role_arn\n identity_token = identity_token.aws.jwt\n }\n destroy = true # This tells HCP Terraform to destroy all resources\n}\n\n# Keep these deployments active\ndeployment \"staging\" {\n inputs = {\n aws_region = \"us-west-1\"\n environment = \"staging\"\n instance_count = 2\n role_arn = local.role_arn\n identity_token = identity_token.aws.jwt\n }\n}\n\ndeployment \"production\" {\n inputs = {\n aws_region = \"us-west-1\"\n environment = \"prod\"\n instance_count = 5\n role_arn = local.role_arn\n identity_token = identity_token.aws.jwt\n }\n}\n\n# Deployment groups\ndeployment_group \"staging\" {\n deployments = [deployment.staging]\n}\n\ndeployment_group \"production\" {\n deployments = [deployment.production]\n}\n```\n\n### Step 2: Plan and Apply\n\n```bash\n# Review the destruction plan\nterraform stacks plan --deployment=development\n\n# Apply the destruction\nterraform stacks apply --deployment=development\n```\n\nHCP Terraform will destroy all resources in the development deployment.\n\n### Step 3: Remove the Deployment Block\n\nAfter the deployment is successfully destroyed, remove the entire deployment block from your configuration:\n\n```hcl\nidentity_token \"aws\" {\n audience = [\"aws.workload.identity\"]\n}\n\nlocals {\n role_arn = \"arn:aws:iam::123456789012:role/terraform-stacks\"\n}\n\n# deployment \"development\" block has been removed\n\ndeployment \"staging\" {\n inputs = {\n aws_region = \"us-west-1\"\n environment = \"staging\"\n instance_count = 2\n role_arn = local.role_arn\n identity_token = identity_token.aws.jwt\n }\n}\n\ndeployment \"production\" {\n inputs = {\n aws_region = \"us-west-1\"\n environment = \"prod\"\n instance_count = 5\n role_arn = local.role_arn\n identity_token = identity_token.aws.jwt\n }\n}\n\n# Deployment groups\ndeployment_group \"staging\" {\n deployments = [deployment.staging]\n}\n\ndeployment_group \"production\" {\n deployments = [deployment.production]\n}\n```\n\n### Important Notes\n\n- **Provider Authentication**: The `destroy` argument ensures your configuration retains the provider authentication needed to destroy resources\n- **Do Not Remove Immediately**: Don't remove the deployment block until after the destruction is complete\n- **Verify Before Removing**: Check HCP Terraform UI to confirm all resources are destroyed before removing the block\n- **Alternative**: You could manually destroy resources through HCP Terraform UI, but using `destroy = true` is the recommended approach for maintaining infrastructure-as-code practices\n",
428
+ "byte_size": 30849,
429
+ "content_sha256": "c46d6a2d3baf8fdcb888f1c743a1beeba207afaa30890c8bab6c3991aad5eb60"
430
+ },
431
+ {
432
+ "path": "references/linked-stacks.md",
433
+ "content": "# Linked Stacks Reference\n\nComplete reference for linking Terraform Stacks together using published outputs and upstream inputs.\n\n## Publish Output Block\n\nExports outputs from a Stack for consumption by other Stacks (linked Stacks).\n\n###Syntax\n\n```hcl\npublish_output \"<output_name>\" {\n type = <type>\n value = <expression>\n}\n```\n\n### Arguments\n\n- **output_name** (label, required): Unique identifier for this published output\n- **type** (required): Data type of the output\n- **value** (required): Expression to export\n\n### Accessing Deployment Outputs\n\nReference deployment outputs using: `deployment.<deployment_name>.<output_name>`\n\n### Important Notes\n\n- Must apply the Stack's deployment configuration before downstream Stacks can reference outputs\n- Published outputs create a snapshot that other Stacks can read\n- Changes to published outputs automatically trigger runs in downstream Stacks\n\n### Examples\n\n**Basic Published Output:**\n\n```hcl\npublish_output \"vpc_id\" {\n type = string\n value = deployment.network.vpc_id\n}\n\npublish_output \"subnet_ids\" {\n type = list(string)\n value = deployment.network.private_subnet_ids\n}\n```\n\n**Multiple Deployment Outputs:**\n\n```hcl\npublish_output \"regional_vpc_ids\" {\n type = map(string)\n value = {\n us_east = deployment.us_east.vpc_id\n us_west = deployment.us_west.vpc_id\n eu_west = deployment.eu_west.vpc_id\n }\n}\n```\n\n**Complex Output:**\n\n```hcl\npublish_output \"database_config\" {\n type = object({\n endpoint = string\n port = number\n name = string\n })\n value = {\n endpoint = deployment.production.db_endpoint\n port = deployment.production.db_port\n name = deployment.production.db_name\n }\n}\n```\n\n**Regional Endpoints:**\n\n```hcl\npublish_output \"api_endpoints\" {\n type = map(object({\n url = string\n region = string\n }))\n value = {\n for env in [\"dev\", \"staging\", \"prod\"] : env => {\n url = deployment[env].api_url\n region = deployment[env].region\n }\n }\n}\n```\n\n## Upstream Input Block\n\nReferences published outputs from another Stack (linked Stacks).\n\n### Syntax\n\n```hcl\nupstream_input \"<input_name>\" {\n type = \"stack\"\n source = \"<stack_address>\"\n}\n```\n\n### Arguments\n\n- **input_name** (label, required): Local name for this upstream input\n- **type** (required): Must be \"stack\"\n- **source** (required): Full Stack address in format: `app.terraform.io/<org>/<project>/<stack-name>`\n\n### Accessing Upstream Outputs\n\nReference upstream outputs using: `upstream_input.<input_name>.<output_name>`\n\n### Important Notes\n\n- Creates a dependency on the upstream Stack\n- Upstream Stack must have applied its deployment configuration\n- Changes in upstream Stack automatically trigger downstream Stack runs\n- Only works with Stacks in the same HCP Terraform project\n\n### Examples\n\n**Basic Upstream Reference:**\n\n```hcl\nupstream_input \"network\" {\n type = \"stack\"\n source = \"app.terraform.io/my-org/my-project/networking-stack\"\n}\n\ndeployment \"application\" {\n inputs = {\n vpc_id = upstream_input.network.vpc_id\n subnet_ids = upstream_input.network.subnet_ids\n }\n}\n```\n\n**Multiple Upstream Stacks:**\n\n```hcl\nupstream_input \"network\" {\n type = \"stack\"\n source = \"app.terraform.io/my-org/my-project/network-stack\"\n}\n\nupstream_input \"database\" {\n type = \"stack\"\n source = \"app.terraform.io/my-org/my-project/database-stack\"\n}\n\ndeployment \"application\" {\n inputs = {\n vpc_id = upstream_input.network.vpc_id\n subnet_ids = upstream_input.network.private_subnet_ids\n database_endpoint = upstream_input.database.endpoint\n database_credentials = upstream_input.database.credentials\n }\n}\n```\n\n**Regional Upstream Dependencies:**\n\n```hcl\nupstream_input \"regional_network\" {\n type = \"stack\"\n source = \"app.terraform.io/my-org/my-project/regional-networks\"\n}\n\ndeployment \"us_east_app\" {\n inputs = {\n region = \"us-east-1\"\n vpc_id = upstream_input.regional_network.regional_vpc_ids[\"us_east\"]\n subnet_ids = upstream_input.regional_network.regional_subnet_ids[\"us_east\"]\n }\n}\n```\n\n## Complete Working Example\n\nFor a complete example showing full Stack configurations with all files (variables, providers, components, outputs, deployments) for both upstream and downstream Stacks, see the \"Linked Stacks (Cross-Stack Dependencies)\" section in `examples.md`.\n",
434
+ "byte_size": 4341,
435
+ "content_sha256": "e7cafa2a3b54a05ec5d1e60222f91fd77031914eb55000904beadf5fd3a21b6e"
436
+ },
437
+ {
438
+ "path": "references/troubleshooting.md",
439
+ "content": "# Troubleshooting Reference\n\nCommon issues and solutions when working with Terraform Stacks.\n\n## Table of Contents\n\n1. [Configuration Issues](#configuration-issues)\n2. [Deployment Issues](#deployment-issues)\n3. [Provider and Authentication Issues](#provider-and-authentication-issues)\n4. [Module Compatibility Issues](#module-compatibility-issues)\n5. [State and Dependency Issues](#state-and-dependency-issues)\n6. [API and CLI Issues](#api-and-cli-issues)\n\n## Configuration Issues\n\n### Circular Dependencies\n\n**Issue:** Component A references Component B, and Component B references Component A.\n\n**Error Message:**\n```\nError: Cycle detected in component dependencies\n```\n\n**Solutions:**\n\n1. **Break the circular reference** by refactoring components:\n\n```hcl\n# Before (circular dependency)\ncomponent \"vpc\" {\n source = \"./modules/vpc\"\n inputs = {\n security_group_id = component.app.security_group_id # References app\n }\n}\n\ncomponent \"app\" {\n source = \"./modules/app\"\n inputs = {\n vpc_id = component.vpc.vpc_id # References vpc\n }\n}\n\n# After (broken circular reference)\ncomponent \"vpc\" {\n source = \"./modules/vpc\"\n inputs = {\n # Remove reference to app\n }\n}\n\ncomponent \"security_group\" {\n source = \"./modules/security-group\"\n inputs = {\n vpc_id = component.vpc.vpc_id\n }\n}\n\ncomponent \"app\" {\n source = \"./modules/app\"\n inputs = {\n vpc_id = component.vpc.vpc_id\n security_group_id = component.security_group.id\n }\n}\n```\n\n2. **Use intermediate components** to break the dependency chain\n3. **Refactor modules** to remove the circular dependency at the module level\n\n### Validation Errors on Variables\n\n**Issue:** Variable block validation errors during `terraform stacks validate`.\n\n**Error Message:**\n```\nError: Unsupported argument\n on variables.tfcomponent.hcl line 5:\n 5: validation {\n\nValidation blocks are not supported in Stack configurations\n```\n\n**Solution:** Remove `validation` blocks from variable declarations. Stacks do not support validation blocks:\n\n```hcl\n# Incorrect\nvariable \"instance_count\" {\n type = number\n validation {\n condition = var.instance_count > 0\n error_message = \"Instance count must be positive\"\n }\n}\n\n# Correct\nvariable \"instance_count\" {\n type = number\n description = \"Number of instances (must be positive)\"\n}\n```\n\nMove validation logic into the underlying modules if needed.\n\n### Missing Type in Variable Declarations\n\n**Issue:** Variables fail validation when `type` is not specified.\n\n**Error Message:**\n```\nError: Missing required argument\n on variables.tfcomponent.hcl line 3:\n 3: variable \"region\" {\n\nThe argument \"type\" is required in Stack variable declarations\n```\n\n**Solution:** Always specify `type` for variables - it's required in Stacks (unlike traditional Terraform):\n\n```hcl\n# Incorrect\nvariable \"region\" {\n default = \"us-west-1\"\n}\n\n# Correct\nvariable \"region\" {\n type = string\n default = \"us-west-1\"\n}\n```\n\n### Provider Configuration in Modules\n\n**Issue:** Modules with embedded provider blocks cause errors.\n\n**Error Message:**\n```\nError: Provider configuration not allowed in module\n\nModules used with Terraform Stacks cannot contain provider blocks\n```\n\n**Solution:**\n\n1. **Remove provider blocks from modules** - configure providers in Stack configuration instead\n2. **Use modules that don't contain provider blocks** (most public registry modules are compatible)\n3. **Fork and modify modules** if necessary to remove provider blocks\n\n## Deployment Issues\n\n### Cannot Destroy Deployment from UI\n\n**Issue:** The HCP Terraform UI doesn't provide an option to destroy Stack deployments.\n\n**Why:** Stack deployment destruction is only available through configuration, not the UI.\n\n**Solution:** Set `destroy = true` in the deployment block and upload the configuration:\n\n```hcl\ndeployment \"old_environment\" {\n inputs = {\n aws_region = \"us-west-1\"\n instance_count = 2\n role_arn = local.role_arn\n identity_token = identity_token.aws.jwt\n }\n\n destroy = true # Marks deployment for destruction\n}\n```\n\n**Workflow:**\n\n1. Add `destroy = true` to the deployment block\n2. Run `terraform stacks configuration upload`\n3. HCP Terraform creates a destroy run automatically\n4. Approve the destroy run (if auto-approve is not configured)\n5. After destruction completes, remove the deployment block entirely\n6. Upload configuration again to clean up the deployment definition\n\n**Important:** You cannot destroy deployments from the UI. This is by design to prevent accidental destruction.\n\n### Deployment Stuck in \"Planning\" State\n\n**Issue:** Deployment remains in \"planning\" state indefinitely.\n\n**Possible Causes:**\n\n1. **Provider authentication failed** - Check OIDC configuration and IAM roles\n2. **Module download failed** - Verify module sources are accessible\n3. **Provider version conflict** - Check `.terraform.lock.hcl` matches required providers\n\n**Diagnosis:**\n\n```bash\n# Get deployment step diagnostics\nterraform stacks deployment-run list\n# Note the run ID, then:\ncurl -s -H \"Authorization: Bearer $TOKEN\" \\\n \"https://app.terraform.io/api/v2/stack-deployment-runs/{run-id}/stack-deployment-steps\" | \\\n jq '.data[] | {id, status: .attributes.status, component: .attributes[\"component-name\"]}'\n```\n\n**Solutions:**\n\n1. Check diagnostics for the stuck step\n2. Verify provider authentication is configured correctly\n3. Ensure all module sources are accessible\n4. Check provider lock file matches required providers\n\n### Deployment Requires Approval But No Approval Prompt\n\n**Issue:** Deployment is waiting for approval but CLI doesn't show approval prompt.\n\n**Why:** CLI monitoring commands are non-blocking and don't automatically prompt for approval.\n\n**Solution:**\n\n**Option 1: Approve via CLI**\n```bash\n# Approve all pending plans in a deployment run\nterraform stacks deployment-run approve-all-plans -deployment-run-id=sdr-ABC123\n\n# Or approve all plans in a deployment group\nterraform stacks deployment-group approve-all-plans -deployment-group=canary\n```\n\n**Option 2: Configure auto-approve** (Premium feature)\n```hcl\ndeployment_auto_approve \"safe_changes\" {\n deployment_group = deployment_group.canary\n\n check {\n condition = context.plan.applyable\n reason = \"Plan must be successful\"\n }\n}\n```\n\n## Provider and Authentication Issues\n\n### OIDC Authentication Failing\n\n**Issue:** Provider authentication fails with OIDC/workload identity.\n\n**Error Messages:**\n```\nError: Error assuming role with web identity\nError: Failed to retrieve credentials\nError: Invalid identity token\n```\n\n**Diagnosis Steps:**\n\n1. **Verify identity token configuration:**\n\n```hcl\n# Check identity_token block exists\nidentity_token \"aws\" {\n audience = [\"aws.workload.identity\"]\n}\n\n# Check deployment references the token\ndeployment \"production\" {\n inputs = {\n identity_token = identity_token.aws.jwt\n }\n}\n```\n\n2. **Verify provider configuration:**\n\n```hcl\nprovider \"aws\" \"this\" {\n config {\n region = var.aws_region\n assume_role_with_web_identity {\n role_arn = var.role_arn\n web_identity_token = var.identity_token\n }\n }\n}\n```\n\n3. **Check IAM role trust policy:**\n\n**AWS - Verify trust policy includes HCP Terraform:**\n\n```json\n{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Effect\": \"Allow\",\n \"Principal\": {\n \"Federated\": \"arn:aws:iam::<account-id>:oidc-provider/app.terraform.io\"\n },\n \"Action\": \"sts:AssumeRoleWithWebIdentity\",\n \"Condition\": {\n \"StringEquals\": {\n \"app.terraform.io:aud\": \"aws.workload.identity\"\n },\n \"StringLike\": {\n \"app.terraform.io:sub\": \"organization:<org-name>:project:<project-name>:stack:<stack-name>:deployment:<deployment-name>\"\n }\n }\n }\n ]\n}\n```\n\n**Azure - Verify federated credential:**\n- Application ID matches the one in provider configuration\n- Subject matches: `organization:<org>:project:<project>:stack:<stack>:deployment:<deployment>`\n- Issuer is `https://app.terraform.io`\n\n**GCP - Verify workload identity pool:**\n- Provider configuration includes correct workload identity provider\n- Service account has necessary IAM permissions\n- Attribute mapping includes `google.subject` from token claims\n\n**Solutions:**\n\n1. Fix IAM role trust policy to include correct HCP Terraform OIDC provider\n2. Ensure audience matches between identity_token block and IAM trust policy\n3. Verify subject pattern matches your organization/project/stack/deployment names\n4. Check that the role_arn is correct in provider configuration\n\n### Provider Version Lock File Issues\n\n**Issue:** Provider version conflicts or \"could not retrieve provider\" errors.\n\n**Error Messages:**\n```\nError: Failed to install provider\nError: Provider version not found\nError: Checksum mismatch for provider\n```\n\n**Solutions:**\n\n1. **Regenerate provider lock file:**\n\n```bash\nterraform stacks providers-lock\n```\n\n2. **Add additional platforms** (if deploying from different OS):\n\n```bash\nterraform stacks providers-lock \\\n -platform=linux_amd64 \\\n -platform=darwin_amd64 \\\n -platform=darwin_arm64\n```\n\n3. **Verify required_providers block:**\n\n```hcl\nrequired_providers {\n aws = {\n source = \"hashicorp/aws\"\n version = \"~> 5.7.0\" # Ensure version constraint is valid\n }\n}\n```\n\n4. **Commit `.terraform.lock.hcl`** to version control\n\n## Module Compatibility Issues\n\n### Public Registry Module Errors\n\n**Issue:** Modules from the Terraform public registry cause errors during plan or apply.\n\n**Common Errors:**\n```\nError: Unsupported attribute\nError: Invalid reference\nError: Missing required argument\n```\n\n**Known Problematic Modules:**\n- `terraform-aws-modules/alb/aws` - Some versions have compatibility issues\n- `terraform-aws-modules/ecs-service/aws` - May have issues with certain configurations\n\n**Solutions:**\n\n1. **Test modules in dev deployment first** before using in production\n\n2. **Check module compatibility** by reviewing recent issues on the module repository\n\n3. **Use specific module versions** rather than latest:\n\n```hcl\ncomponent \"alb\" {\n source = \"terraform-aws-modules/alb/aws\"\n version = \"8.7.0\" # Use specific version known to work\n # ...\n}\n```\n\n4. **Consider using raw resources** for critical infrastructure:\n\n```hcl\n# Instead of using a module that has issues\ncomponent \"alb\" {\n source = \"./modules/alb\" # Create local module with raw resources\n # ...\n}\n```\n\n5. **Fork and fix modules** if you have the resources to maintain them\n\n6. **Report compatibility issues** to module maintainers\n\n### Local Module Not Found\n\n**Issue:** Stack can't find local module sources.\n\n**Error Message:**\n```\nError: Module not found\n Could not load module ./modules/vpc\n```\n\n**Solutions:**\n\n1. **Verify module path is relative** to Stack root:\n\n```hcl\n# Correct\ncomponent \"vpc\" {\n source = \"./modules/vpc\"\n}\n\n# Incorrect (absolute paths don't work)\ncomponent \"vpc\" {\n source = \"/path/to/project/modules/vpc\"\n}\n```\n\n2. **Ensure module directory exists** with proper structure:\n\n```\nmy-stack/\n├── components.tfcomponent.hcl\n└── modules/\n └── vpc/\n ├── main.tf\n ├── variables.tf\n └── outputs.tf\n```\n\n3. **Check file permissions** on module directories\n\n## State and Dependency Issues\n\n### Component Output Not Available\n\n**Issue:** Component output is not available to referencing component.\n\n**Error Message:**\n```\nError: Reference to unknown component\n Component \"vpc\" has not been defined\n```\n\n**Solutions:**\n\n1. **Verify component exists** in configuration:\n\n```hcl\ncomponent \"vpc\" {\n source = \"./modules/vpc\"\n # Must define component before referencing it\n}\n\ncomponent \"app\" {\n source = \"./modules/app\"\n inputs = {\n vpc_id = component.vpc.vpc_id # Now valid\n }\n}\n```\n\n2. **Check output is defined in module:**\n\n```hcl\n# In modules/vpc/outputs.tf\noutput \"vpc_id\" {\n value = aws_vpc.main.id\n}\n```\n\n3. **For components with for_each**, reference specific instance:\n\n```hcl\ncomponent \"regional\" {\n for_each = var.regions\n # ...\n}\n\ncomponent \"app\" {\n inputs = {\n # Correct - reference specific instance\n vpc_id = component.regional[\"us-west-1\"].vpc_id\n\n # Incorrect - can't reference for_each component directly\n # vpc_id = component.regional.vpc_id\n }\n}\n```\n\n### Deferred Changes Not Converging\n\n**Issue:** Deployment with deferred changes doesn't complete after multiple iterations.\n\n**Error Message:**\n```\nError: Maximum deferred change iterations reached\n```\n\n**Cause:** Dependency cycle or values that never stabilize.\n\n**Solutions:**\n\n1. **Review component dependencies** for logical cycles\n2. **Check for computed values that change on every run**\n3. **Refactor to break dependency chain**\n4. **Consider multi-stage deployments** if resources truly can't be created together\n\n## API and CLI Issues\n\n### Empty Diagnostics Response\n\n**Issue:** API request for diagnostics returns empty results.\n\n**Request:**\n```bash\ncurl \"https://app.terraform.io/api/v2/stack-deployment-steps/{step-id}/stack-diagnostics\"\n```\n\n**Response:**\n```json\n{\n \"data\": []\n}\n```\n\n**Solution:** Add required `stack_deployment_step_id` query parameter:\n\n```bash\ncurl \"https://app.terraform.io/api/v2/stack-deployment-steps/{step-id}/stack-diagnostics?stack_deployment_step_id={step-id}\"\n```\n\n### Cannot Retrieve Stack Outputs\n\n**Issue:** No CLI command to retrieve Stack outputs after deployment.\n\n**Why:** Currently no direct CLI command for outputs retrieval.\n\n**Solution:** Use the artifacts API endpoint:\n\n```bash\n# Get final apply step ID first\nAPPLY_STEP=$(terraform stacks deployment-run list --json | \\\n jq -r '.[0].deployment_steps[] | select(.operation_type == \"apply\") | .id' | tail -1)\n\n# Get outputs\ncurl -L -s -H \"Authorization: Bearer $TOKEN\" \\\n \"https://app.terraform.io/api/v2/stack-deployment-steps/$APPLY_STEP/artifacts?name=apply-description\" | \\\n jq -r '.outputs | to_entries | .[] | \"\\(.key): \\(.value.change.after)\"'\n```\n\n### CLI Watch Commands Hang in CI/CD\n\n**Issue:** Commands like `terraform stacks deployment-run watch` never return in CI/CD pipelines.\n\n**Why:** Watch commands stream output indefinitely and are designed for interactive use.\n\n**Solution:** Use API polling instead of watch commands. See `api-monitoring.md` for complete workflow.\n\n### Artifacts Endpoint Returns 404\n\n**Issue:** Request to artifacts endpoint returns 404 Not Found.\n\n**Possible Causes:**\n\n1. **Step hasn't completed yet** - wait for step status to be \"completed\"\n2. **Wrong artifact name** - use one of: plan-description, plan-debug-log, apply-description, apply-debug-log\n3. **Invalid step ID** - verify step ID from deployment-steps endpoint\n\n**Solution:**\n\n```bash\n# Check step status first\ncurl -s -H \"Authorization: Bearer $TOKEN\" \\\n \"https://app.terraform.io/api/v2/stack-deployment-steps/{step-id}\" | \\\n jq '.data.attributes.status'\n\n# Only request artifacts when status is \"completed\"\nif [ \"$STATUS\" = \"completed\" ]; then\n curl -L -H \"Authorization: Bearer $TOKEN\" \\\n \"https://app.terraform.io/api/v2/stack-deployment-steps/{step-id}/artifacts?name=apply-description\"\nfi\n```\n\n### HTTP 307 Redirect Not Followed\n\n**Issue:** Artifacts endpoint returns redirect response instead of artifact content.\n\n**Why:** The endpoint returns HTTP 307 redirect to the actual artifact URL.\n\n**Solution:** Configure HTTP client to follow redirects:\n\n```bash\n# curl: Use -L flag\ncurl -L -H \"Authorization: Bearer $TOKEN\" \\\n \"https://app.terraform.io/api/v2/stack-deployment-steps/{step-id}/artifacts?name=apply-description\"\n\n# Python requests: allow_redirects=True (default)\nimport requests\nresponse = requests.get(url, headers=headers, allow_redirects=True)\n\n# Node.js fetch: redirect: 'follow' (default)\nconst response = await fetch(url, {\n headers: headers,\n redirect: 'follow'\n});\n```\n\n## Getting Additional Help\n\n### Enable Debug Logging\n\nFor more detailed error information, enable debug logging:\n\n```bash\n# CLI commands\nTF_LOG=DEBUG terraform stacks validate\nTF_LOG=DEBUG terraform stacks configuration upload\n\n# API artifacts\n# Request the debug-log artifact instead of description\ncurl -L -H \"Authorization: Bearer $TOKEN\" \\\n \"https://app.terraform.io/api/v2/stack-deployment-steps/{step-id}/artifacts?name=apply-debug-log\"\n```\n\n### Check HCP Terraform Status\n\nIf experiencing widespread issues, check HCP Terraform status page:\n- https://status.hashicorp.com\n\n### Review Configuration Version\n\nList recent configurations to identify when issues started:\n\n```bash\nterraform stacks configuration list\n```\n\n### Contact Support\n\nFor issues not covered here:\n1. Gather relevant error messages and diagnostics\n2. Note the configuration sequence number\n3. Include deployment run IDs\n4. Contact HashiCorp Support with details\n",
440
+ "byte_size": 16704,
441
+ "content_sha256": "4b335f00733f7283b4ccef003b5638ada315e6d4a7ef40c3ad50f525499235fc"
442
+ }
443
+ ]
444
+ },
445
+ {
446
+ "library_id": "terraform-style-guide",
447
+ "capability_id": "skill:terraform-style-guide",
448
+ "plugin_key": "skill/library/terraform-style-guide",
449
+ "version": "1.0.0",
450
+ "name": "terraform-style-guide",
451
+ "description": "Generate and review Terraform HCL using HashiCorp's official style conventions and maintainability practices.",
452
+ "category": "infrastructure",
453
+ "tags": [
454
+ "skill",
455
+ "infrastructure",
456
+ "terraform",
457
+ "style",
458
+ "opt-in"
459
+ ],
460
+ "source_url": "https://github.com/hashicorp/agent-skills/tree/de4323afdfbc30d1387f287b55062fa8d82b62e8/terraform/code-generation/skills/terraform-style-guide",
461
+ "repository_url": "https://github.com/hashicorp/agent-skills",
462
+ "source_commit": "de4323afdfbc30d1387f287b55062fa8d82b62e8",
463
+ "source_path": "terraform-style-guide",
464
+ "source_provenance": "Vendored from hashicorp/agent-skills; reviewed immutable opt-in entry.",
465
+ "content_sha256": "1453c4f11636d2d88c5186a4ce2d7532d4b2056a861ed69653df21e8e45e19cd",
466
+ "file_count": 1,
467
+ "total_bytes": 7713,
468
+ "license": "MPL-2.0",
469
+ "manifest": {
470
+ "schemaVersion": 1,
471
+ "kind": "skill",
472
+ "source": "library",
473
+ "sourceUrl": "https://github.com/hashicorp/agent-skills/tree/de4323afdfbc30d1387f287b55062fa8d82b62e8/terraform/code-generation/skills/terraform-style-guide",
474
+ "repositoryUrl": "https://github.com/hashicorp/agent-skills",
475
+ "version": "1.0.0",
476
+ "sourceCommit": "de4323afdfbc30d1387f287b55062fa8d82b62e8",
477
+ "sourcePath": "terraform-style-guide",
478
+ "sourceProvenance": "Vendored from hashicorp/agent-skills; reviewed immutable opt-in entry.",
479
+ "contentSha256": "1453c4f11636d2d88c5186a4ce2d7532d4b2056a861ed69653df21e8e45e19cd",
480
+ "fileCount": 1,
481
+ "totalBytes": 7713
482
+ },
483
+ "manifest_digest": "c2c7b6e7fa26e0cc8e640b4d93fa6e636411e7c0945d581d5260c19b67bc3e2a",
484
+ "files": [
485
+ {
486
+ "path": "SKILL.md",
487
+ "content": "---\nname: terraform-style-guide\ndescription: Generate Terraform HCL code following HashiCorp's official style conventions and best practices. Use when writing, reviewing, or generating Terraform configurations.\n---\n\n# Terraform Style Guide\n\nGenerate and maintain Terraform code following HashiCorp's official style conventions and best practices.\n\n**Reference:** [HashiCorp Terraform Style Guide](https://developer.hashicorp.com/terraform/language/style)\n\n## Code Generation Strategy\n\nWhen generating Terraform code:\n\n1. Start with provider configuration and version constraints\n2. Create data sources before dependent resources\n3. Build resources in dependency order\n4. Add outputs for key resource attributes\n5. Use variables for all configurable values\n\n## File Organization\n\n| File | Purpose |\n|------|---------|\n| `terraform.tf` | Terraform and provider version requirements |\n| `providers.tf` | Provider configurations |\n| `main.tf` | Primary resources and data sources |\n| `variables.tf` | Input variable declarations (alphabetical) |\n| `outputs.tf` | Output value declarations (alphabetical) |\n| `locals.tf` | Local value declarations |\n\n### Example Structure\n\n```hcl\n# terraform.tf\nterraform {\n required_version = \">= 1.7\"\n\n required_providers {\n aws = {\n source = \"hashicorp/aws\"\n version = \"~> 5.0\"\n }\n }\n}\n\n# variables.tf\nvariable \"environment\" {\n description = \"Target deployment environment\"\n type = string\n\n validation {\n condition = contains([\"dev\", \"staging\", \"prod\"], var.environment)\n error_message = \"Environment must be dev, staging, or prod.\"\n }\n}\n\n# locals.tf\nlocals {\n common_tags = {\n Environment = var.environment\n ManagedBy = \"Terraform\"\n }\n}\n\n# main.tf\nresource \"aws_vpc\" \"main\" {\n cidr_block = var.vpc_cidr\n enable_dns_hostnames = true\n\n tags = merge(local.common_tags, {\n Name = \"${var.project_name}-${var.environment}-vpc\"\n })\n}\n\n# outputs.tf\noutput \"vpc_id\" {\n description = \"ID of the created VPC\"\n value = aws_vpc.main.id\n}\n```\n\n## Code Formatting\n\n### Indentation and Alignment\n\n- Use **two spaces** per nesting level (no tabs)\n- Align equals signs for consecutive arguments\n\n```hcl\nresource \"aws_instance\" \"web\" {\n ami = \"ami-0c55b159cbfafe1f0\"\n instance_type = \"t2.micro\"\n subnet_id = \"subnet-12345678\"\n\n tags = {\n Name = \"web-server\"\n Environment = \"production\"\n }\n}\n```\n\n### Block Organization\n\nArguments precede blocks, with meta-arguments first:\n\n```hcl\nresource \"aws_instance\" \"example\" {\n # Meta-arguments\n count = 3\n\n # Arguments\n ami = \"ami-0c55b159cbfafe1f0\"\n instance_type = \"t2.micro\"\n\n # Blocks\n root_block_device {\n volume_size = 20\n }\n\n # Lifecycle last\n lifecycle {\n create_before_destroy = true\n }\n}\n```\n\n## Naming Conventions\n\n- Use **lowercase with underscores** for all names\n- Use **descriptive nouns** excluding the resource type\n- Be specific and meaningful\n- Resource names must be singular, not plural\n- Default to `main` for resources where a specific descriptive name is redundant or unavailable, provided only one instance exists\n\n```hcl\n# Bad\nresource \"aws_instance\" \"webAPI-aws-instance\" {}\nresource \"aws_instance\" \"web_apis\" {}\nvariable \"name\" {}\n\n# Good\nresource \"aws_instance\" \"web_api\" {}\nresource \"aws_vpc\" \"main\" {}\nvariable \"application_name\" {}\n```\n\n## Variables\n\nEvery variable must include `type` and `description`:\n\n```hcl\nvariable \"instance_type\" {\n description = \"EC2 instance type for the web server\"\n type = string\n default = \"t2.micro\"\n\n validation {\n condition = contains([\"t2.micro\", \"t2.small\", \"t2.medium\"], var.instance_type)\n error_message = \"Instance type must be t2.micro, t2.small, or t2.medium.\"\n }\n}\n\nvariable \"database_password\" {\n description = \"Password for the database admin user\"\n type = string\n sensitive = true\n}\n```\n\n## Outputs\n\nEvery output must include `description`:\n\n```hcl\noutput \"instance_id\" {\n description = \"ID of the EC2 instance\"\n value = aws_instance.web.id\n}\n\noutput \"database_password\" {\n description = \"Database administrator password\"\n value = aws_db_instance.main.password\n sensitive = true\n}\n```\n\n## Dynamic Resource Creation\n\n### Prefer for_each over count\n\n```hcl\n# Bad - count for multiple resources\nresource \"aws_instance\" \"web\" {\n count = var.instance_count\n tags = { Name = \"web-${count.index}\" }\n}\n\n# Good - for_each with named instances\nvariable \"instance_names\" {\n type = set(string)\n default = [\"web-1\", \"web-2\", \"web-3\"]\n}\n\nresource \"aws_instance\" \"web\" {\n for_each = var.instance_names\n tags = { Name = each.key }\n}\n```\n\n### count for Conditional Creation\n\n```hcl\nresource \"aws_cloudwatch_metric_alarm\" \"cpu\" {\n count = var.enable_monitoring ? 1 : 0\n\n alarm_name = \"high-cpu-usage\"\n threshold = 80\n}\n```\n\n## Security Best Practices\n\nWhen generating code, apply security hardening:\n\n- Enable encryption at rest by default\n- Configure private networking where applicable\n- Apply principle of least privilege for security groups\n- Enable logging and monitoring\n- Never hardcode credentials or secrets\n- Mark sensitive outputs with `sensitive = true`\n\n### Example: Secure S3 Bucket\n\n```hcl\nresource \"aws_s3_bucket\" \"data\" {\n bucket = \"${var.project}-${var.environment}-data\"\n tags = local.common_tags\n}\n\nresource \"aws_s3_bucket_versioning\" \"data\" {\n bucket = aws_s3_bucket.data.id\n\n versioning_configuration {\n status = \"Enabled\"\n }\n}\n\nresource \"aws_s3_bucket_server_side_encryption_configuration\" \"data\" {\n bucket = aws_s3_bucket.data.id\n\n rule {\n apply_server_side_encryption_by_default {\n sse_algorithm = \"aws:kms\"\n kms_master_key_id = aws_kms_key.s3.arn\n }\n }\n}\n\nresource \"aws_s3_bucket_public_access_block\" \"data\" {\n bucket = aws_s3_bucket.data.id\n\n block_public_acls = true\n block_public_policy = true\n ignore_public_acls = true\n restrict_public_buckets = true\n}\n```\n\n## Version Pinning\n\n```hcl\nterraform {\n required_version = \">= 1.7\"\n\n required_providers {\n aws = {\n source = \"hashicorp/aws\"\n version = \"~> 5.0\" # Allow minor updates\n }\n }\n}\n```\n\n**Version constraint operators:**\n- `= 1.0.0` - Exact version\n- `>= 1.0.0` - Greater than or equal\n- `~> 1.0` - Allow rightmost component to increment\n- `>= 1.0, < 2.0` - Version range\n\n## Provider Configuration\n\n```hcl\nprovider \"aws\" {\n region = \"us-west-2\"\n\n default_tags {\n tags = {\n ManagedBy = \"Terraform\"\n Project = var.project_name\n }\n }\n}\n\n# Aliased provider for multi-region\nprovider \"aws\" {\n alias = \"east\"\n region = \"us-east-1\"\n}\n```\n\n## Version Control\n\n**Never commit:**\n- `terraform.tfstate`, `terraform.tfstate.backup`\n- `.terraform/` directory\n- `*.tfplan`\n- `.tfvars` files with sensitive data\n\n**Always commit:**\n- All `.tf` configuration files\n- `.terraform.lock.hcl` (dependency lock file)\n\n## Validation Tools\n\nRun before committing:\n\n```bash\nterraform fmt -recursive\nterraform validate\n```\n\nAdditional tools:\n- `tflint` - Linting and best practices\n- `checkov` / `tfsec` - Security scanning\n\n## Code Review Checklist\n\n- [ ] Code formatted with `terraform fmt`\n- [ ] Configuration validated with `terraform validate`\n- [ ] Files organized according to standard structure\n- [ ] All variables have type and description\n- [ ] All outputs have descriptions\n- [ ] Resource names use descriptive nouns with underscores\n- [ ] Version constraints pinned explicitly\n- [ ] Sensitive values marked with `sensitive = true`\n- [ ] No hardcoded credentials or secrets\n- [ ] Security best practices applied\n\n---\n\n*Based on: [HashiCorp Terraform Style Guide](https://developer.hashicorp.com/terraform/language/style)*\n",
488
+ "byte_size": 7713,
489
+ "content_sha256": "9d08cde101042ff656c473b6db9e8a7bcfc0cc191441b8613be3b47f11fec060"
490
+ }
491
+ ]
492
+ },
493
+ {
494
+ "library_id": "terraform-test",
495
+ "capability_id": "skill:terraform-test",
496
+ "plugin_key": "skill/library/terraform-test",
497
+ "version": "0.0.2",
498
+ "name": "terraform-test",
499
+ "description": "Write and run Terraform tests with assertions, mocked providers, data sources, and plan/apply scenarios.",
500
+ "category": "infrastructure",
501
+ "tags": [
502
+ "skill",
503
+ "infrastructure",
504
+ "terraform",
505
+ "testing",
506
+ "opt-in"
507
+ ],
508
+ "source_url": "https://github.com/hashicorp/agent-skills/tree/de4323afdfbc30d1387f287b55062fa8d82b62e8/terraform/code-generation/skills/terraform-test",
509
+ "repository_url": "https://github.com/hashicorp/agent-skills",
510
+ "source_commit": "de4323afdfbc30d1387f287b55062fa8d82b62e8",
511
+ "source_path": "terraform-test",
512
+ "source_provenance": "Vendored from hashicorp/agent-skills; reviewed immutable opt-in entry.",
513
+ "content_sha256": "61be0fa43c48f49980fee28c64215f593c4ab55d55a93c8f2e514d9ca566a97b",
514
+ "file_count": 4,
515
+ "total_bytes": 24280,
516
+ "license": "MPL-2.0",
517
+ "manifest": {
518
+ "schemaVersion": 1,
519
+ "kind": "skill",
520
+ "source": "library",
521
+ "sourceUrl": "https://github.com/hashicorp/agent-skills/tree/de4323afdfbc30d1387f287b55062fa8d82b62e8/terraform/code-generation/skills/terraform-test",
522
+ "repositoryUrl": "https://github.com/hashicorp/agent-skills",
523
+ "version": "0.0.2",
524
+ "sourceCommit": "de4323afdfbc30d1387f287b55062fa8d82b62e8",
525
+ "sourcePath": "terraform-test",
526
+ "sourceProvenance": "Vendored from hashicorp/agent-skills; reviewed immutable opt-in entry.",
527
+ "contentSha256": "61be0fa43c48f49980fee28c64215f593c4ab55d55a93c8f2e514d9ca566a97b",
528
+ "fileCount": 4,
529
+ "totalBytes": 24280
530
+ },
531
+ "manifest_digest": "c1393b3cdea711c1687b8e1267cf858720bd46e0b62320d1012c73263b8f538d",
532
+ "files": [
533
+ {
534
+ "path": "SKILL.md",
535
+ "content": "---\nname: terraform-test\ndescription: Comprehensive guide for writing and running Terraform tests. Use when creating test files (.tftest.hcl), writing test scenarios with run blocks, validating infrastructure behavior with assertions, mocking providers and data sources, testing module outputs and resource configurations, or troubleshooting Terraform test syntax and execution.\nmetadata:\n copyright: Copyright IBM Corp. 2026\n version: \"0.0.2\"\n---\n\n# Terraform Test\n\nTerraform's built-in testing framework validates that configuration updates don't introduce breaking changes. Tests run against temporary resources, protecting existing infrastructure and state files.\n\n## Reference Files\n\n- `references/MOCK_PROVIDERS.md` — Mock provider syntax, common defaults, when to use mocks (Terraform 1.7.0+ only — skip if the user's version is below 1.7)\n- `references/CI_CD.md` — GitHub Actions and GitLab CI pipeline examples\n- `references/EXAMPLES.md` — Complete example test suite (unit, integration, and mock tests for a VPC module)\n\nRead the relevant reference file when the user asks about mocking, CI/CD integration, or wants a full example.\n\n## Core Concepts\n\n- **Test file** (`.tftest.hcl` / `.tftest.json`): Contains `run` blocks that validate your configuration\n- **Run block**: A single test scenario with optional variables, providers, and assertions\n- **Assert block**: Conditions that must be true for the test to pass\n- **Mock provider**: Simulates provider behavior without real infrastructure (Terraform 1.7.0+)\n- **Test modes**: `apply` (default, creates real resources) or `plan` (validates logic only)\n\n## File Structure\n\n```\nmy-module/\n├── main.tf\n├── variables.tf\n├── outputs.tf\n└── tests/\n ├── defaults_unit_test.tftest.hcl # plan mode — fast, no resources\n ├── validation_unit_test.tftest.hcl # plan mode\n └── full_stack_integration_test.tftest.hcl # apply mode — creates real resources\n```\n\nUse `*_unit_test.tftest.hcl` for plan-mode tests and `*_integration_test.tftest.hcl` for apply-mode tests so they can be filtered separately in CI.\n\n## Test File Structure\n\n```hcl\n# Optional: test-wide settings\ntest {\n parallel = true # Enable parallel execution for all run blocks (default: false)\n}\n\n# Optional: file-level variables (highest precedence, override all other sources)\nvariables {\n aws_region = \"us-west-2\"\n instance_type = \"t2.micro\"\n}\n\n# Optional: provider configuration\nprovider \"aws\" {\n region = var.aws_region\n}\n\n# Required: at least one run block\nrun \"test_default_configuration\" {\n command = plan\n\n assert {\n condition = aws_instance.example.instance_type == \"t2.micro\"\n error_message = \"Instance type should be t2.micro by default\"\n }\n}\n```\n\n## Run Block\n\n```hcl\nrun \"test_name\" {\n command = plan # or apply (default)\n parallel = true # optional, since v1.9.0\n\n # Override file-level variables\n variables {\n instance_type = \"t3.large\"\n }\n\n # Reference a specific module\n module {\n source = \"./modules/vpc\" # local or registry only (not git/http)\n version = \"5.0.0\" # registry modules only\n }\n\n # Control state isolation\n state_key = \"shared_state\" # since v1.9.0\n\n # Plan behavior\n plan_options {\n mode = refresh-only # or normal (default)\n refresh = true\n replace = [aws_instance.example]\n target = [aws_instance.example]\n }\n\n # Assertions\n assert {\n condition = aws_instance.example.id != \"\"\n error_message = \"Instance should have a valid ID\"\n }\n\n # Expected failures (test passes if these fail)\n expect_failures = [\n var.instance_count\n ]\n}\n```\n\n## Common Test Patterns\n\n### Validate outputs\n\n```hcl\nrun \"test_outputs\" {\n command = plan\n\n assert {\n condition = output.vpc_id != null\n error_message = \"VPC ID output must be defined\"\n }\n\n assert {\n condition = can(regex(\"^vpc-\", output.vpc_id))\n error_message = \"VPC ID should start with 'vpc-'\"\n }\n}\n```\n\n### Conditional resources\n\n```hcl\nrun \"test_nat_gateway_disabled\" {\n command = plan\n\n variables {\n create_nat_gateway = false\n }\n\n assert {\n condition = length(aws_nat_gateway.main) == 0\n error_message = \"NAT gateway should not be created when disabled\"\n }\n}\n```\n\n### Resource counts\n\n```hcl\nrun \"test_resource_count\" {\n command = plan\n\n variables {\n instance_count = 3\n }\n\n assert {\n condition = length(aws_instance.workers) == 3\n error_message = \"Should create exactly 3 worker instances\"\n }\n}\n```\n\n### Tags\n\n```hcl\nrun \"test_resource_tags\" {\n command = plan\n\n variables {\n common_tags = {\n Environment = \"production\"\n ManagedBy = \"Terraform\"\n }\n }\n\n assert {\n condition = aws_instance.example.tags[\"Environment\"] == \"production\"\n error_message = \"Environment tag should be set correctly\"\n }\n\n assert {\n condition = aws_instance.example.tags[\"ManagedBy\"] == \"Terraform\"\n error_message = \"ManagedBy tag should be set correctly\"\n }\n}\n```\n\n### Data sources\n\n```hcl\nrun \"test_data_source_lookup\" {\n command = plan\n\n assert {\n condition = data.aws_ami.ubuntu.id != \"\"\n error_message = \"Should find a valid Ubuntu AMI\"\n }\n\n assert {\n condition = can(regex(\"^ami-\", data.aws_ami.ubuntu.id))\n error_message = \"AMI ID should be in correct format\"\n }\n}\n```\n\n### Validation rules\n\n```hcl\nrun \"test_invalid_environment\" {\n command = plan\n\n variables {\n environment = \"invalid\"\n }\n\n expect_failures = [\n var.environment\n ]\n}\n```\n\n### Sequential tests with dependencies\n\n```hcl\nrun \"setup_vpc\" {\n command = apply\n\n assert {\n condition = output.vpc_id != \"\"\n error_message = \"VPC should be created\"\n }\n}\n\nrun \"test_subnet_in_vpc\" {\n command = plan\n\n variables {\n vpc_id = run.setup_vpc.vpc_id\n }\n\n assert {\n condition = aws_subnet.example.vpc_id == run.setup_vpc.vpc_id\n error_message = \"Subnet should be in the VPC from setup_vpc\"\n }\n}\n```\n\n### Plan options (refresh-only, targeted)\n\n```hcl\nrun \"test_refresh_only\" {\n command = plan\n\n plan_options {\n mode = refresh-only\n }\n\n assert {\n condition = aws_instance.example.tags[\"Environment\"] == \"production\"\n error_message = \"Tags should be refreshed correctly\"\n }\n}\n\nrun \"test_specific_resource\" {\n command = plan\n\n plan_options {\n target = [aws_instance.example]\n }\n\n assert {\n condition = aws_instance.example.instance_type == \"t2.micro\"\n error_message = \"Targeted resource should be planned\"\n }\n}\n```\n\n### Parallel modules\n\n```hcl\nrun \"test_networking_module\" {\n command = plan\n parallel = true\n\n module {\n source = \"./modules/networking\"\n }\n\n assert {\n condition = output.vpc_id != \"\"\n error_message = \"VPC should be created\"\n }\n}\n\nrun \"test_compute_module\" {\n command = plan\n parallel = true\n\n module {\n source = \"./modules/compute\"\n }\n\n assert {\n condition = output.instance_id != \"\"\n error_message = \"Instance should be created\"\n }\n}\n```\n\n### State key sharing\n\n```hcl\nrun \"create_foundation\" {\n command = apply\n state_key = \"foundation\"\n\n assert {\n condition = aws_vpc.main.id != \"\"\n error_message = \"Foundation VPC should be created\"\n }\n}\n\nrun \"create_application\" {\n command = apply\n state_key = \"foundation\"\n\n variables {\n vpc_id = run.create_foundation.vpc_id\n }\n\n assert {\n condition = aws_instance.app.vpc_id == run.create_foundation.vpc_id\n error_message = \"Application should use foundation VPC\"\n }\n}\n```\n\n### Cleanup ordering (S3 objects before bucket)\n\n```hcl\nrun \"create_bucket\" {\n command = apply\n\n assert {\n condition = aws_s3_bucket.example.id != \"\"\n error_message = \"Bucket should be created\"\n }\n}\n\nrun \"add_objects\" {\n command = apply\n\n assert {\n condition = length(aws_s3_object.files) > 0\n error_message = \"Objects should be added\"\n }\n}\n\n# Cleanup destroys in reverse: objects first, then bucket\n```\n\n### Multiple aliased providers\n\n```hcl\nprovider \"aws\" {\n alias = \"primary\"\n region = \"us-west-2\"\n}\n\nprovider \"aws\" {\n alias = \"secondary\"\n region = \"us-east-1\"\n}\n\nrun \"test_with_specific_provider\" {\n command = plan\n\n providers = {\n aws = provider.aws.secondary\n }\n\n assert {\n condition = aws_instance.example.availability_zone == \"us-east-1a\"\n error_message = \"Instance should be in us-east-1 region\"\n }\n}\n```\n\n### Complex conditions\n\n```hcl\nassert {\n condition = alltrue([\n for subnet in aws_subnet.private :\n can(regex(\"^10\\\\.0\\\\.\", subnet.cidr_block))\n ])\n error_message = \"All private subnets should use 10.0.0.0/8 CIDR range\"\n}\n```\n\n## Cleanup\n\nResources are destroyed in **reverse run block order** after test completion. This matters for dependencies (e.g., S3 objects before bucket). Use `terraform test -no-cleanup` to skip cleanup for debugging.\n\n## Running Tests\n\n```bash\nterraform test # all tests\nterraform test tests/defaults.tftest.hcl # specific file\nterraform test -filter=test_vpc_configuration # by run block name\nterraform test -test-directory=integration-tests # custom directory\nterraform test -verbose # detailed output\nterraform test -no-cleanup # skip resource cleanup\n```\n\n## Best Practices\n\n1. **Naming**: `*_unit_test.tftest.hcl` for plan mode, `*_integration_test.tftest.hcl` for apply mode\n2. **Test naming**: Use descriptive run block names that explain the scenario being tested\n3. **Default to plan**: Use `command = plan` unless you need to test real resource behavior\n4. **Use mocks** for external dependencies — faster and no credentials needed (see `references/MOCK_PROVIDERS.md`)\n5. **Error messages**: Make them specific enough to diagnose failures without running the test again\n6. **Negative tests**: Use `expect_failures` to verify validation rules reject bad inputs\n7. **Variable coverage**: Test different variable combinations to validate all code paths — test variables have the highest precedence and override all other sources\n8. **Module sources**: Test files only support local paths and registry modules — not git or HTTP URLs\n9. **Parallel execution**: Use `parallel = true` for independent tests with different state files\n10. **Cleanup**: Integration tests destroy resources in reverse run block order automatically; use `-no-cleanup` for debugging\n11. **CI/CD**: Run unit tests on every PR, integration tests on merge (see `references/CI_CD.md`)\n\n## Troubleshooting\n\n| Issue | Solution |\n|-------|----------|\n| Assertion failures | Use `-verbose` to see actual vs expected values |\n| Missing credentials | Use mock providers for unit tests |\n| Unsupported module source | Convert git/HTTP sources to local modules |\n| Tests interfering | Use `state_key` or separate modules for isolation |\n| Slow tests | Use `command = plan` and mocks; run integration tests separately |\n\n## References\n\n- [Terraform Testing Documentation](https://developer.hashicorp.com/terraform/language/tests)\n- [Terraform Test Command](https://developer.hashicorp.com/terraform/cli/commands/test)\n- [Testing Best Practices](https://developer.hashicorp.com/terraform/language/tests/best-practices)\n",
536
+ "byte_size": 11221,
537
+ "content_sha256": "f7b0c4d1e1381751f75c4a07b7a97d8c011f741723aea1cca7f73e54af27b138"
538
+ },
539
+ {
540
+ "path": "references/CI_CD.md",
541
+ "content": "# CI/CD Integration\n\n## GitHub Actions\n\n```yaml\nname: Terraform Tests\n\non:\n pull_request:\n branches: [ main ]\n push:\n branches: [ main ]\n\njobs:\n unit-tests:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v4\n - uses: hashicorp/setup-terraform@v3\n with:\n terraform_version: 1.9.0\n\n - run: terraform fmt -check -recursive\n - run: terraform init\n - run: terraform validate\n - name: Run unit tests (plan mode, no credentials needed)\n run: terraform test -filter=unit_test -verbose\n\n integration-tests:\n runs-on: ubuntu-latest\n needs: unit-tests\n if: github.ref == 'refs/heads/main'\n steps:\n - uses: actions/checkout@v4\n - uses: hashicorp/setup-terraform@v3\n with:\n terraform_version: 1.9.0\n\n - run: terraform init\n - name: Run integration tests\n run: terraform test -filter=integration_test -verbose\n env:\n AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}\n AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}\n```\n\n## GitLab CI\n\n```yaml\nstages:\n - validate\n - test\n\nterraform-unit-tests:\n image: hashicorp/terraform:1.9\n stage: validate\n before_script:\n - terraform init\n script:\n - terraform fmt -check -recursive\n - terraform validate\n - terraform test -filter=unit_test -verbose\n\nterraform-integration-tests:\n image: hashicorp/terraform:1.9\n stage: test\n before_script:\n - terraform init\n script:\n - terraform test -filter=integration_test -verbose\n only:\n - main\n```\n\n## Recommended CI Strategy\n\n- Run unit tests (plan mode + mock tests) on every PR — fast, no credentials needed\n- Run integration tests only on merge to main or nightly — requires cloud credentials\n- Use `-filter=unit_test` / `-filter=integration_test` to separate test types based on naming convention\n- Store cloud credentials as CI secrets, never in code\n",
542
+ "byte_size": 1931,
543
+ "content_sha256": "88fdffa4d3b8f361c570cb7c0df90a44380c70eb4471b3e27d552f32820179c8"
544
+ },
545
+ {
546
+ "path": "references/EXAMPLES.md",
547
+ "content": "# Example Test Suite\n\nComplete example testing a VPC module with unit, integration, and mock tests.\n\n## Unit Tests (Plan Mode)\n\n```hcl\n# tests/vpc_module_unit_test.tftest.hcl\n\nvariables {\n environment = \"test\"\n aws_region = \"us-west-2\"\n}\n\nrun \"test_defaults\" {\n command = plan\n\n variables {\n vpc_cidr = \"10.0.0.0/16\"\n vpc_name = \"test-vpc\"\n }\n\n assert {\n condition = aws_vpc.main.cidr_block == \"10.0.0.0/16\"\n error_message = \"VPC CIDR should match input\"\n }\n\n assert {\n condition = aws_vpc.main.enable_dns_hostnames == true\n error_message = \"DNS hostnames should be enabled by default\"\n }\n\n assert {\n condition = aws_vpc.main.tags[\"Name\"] == \"test-vpc\"\n error_message = \"VPC name tag should match input\"\n }\n}\n\nrun \"test_subnets\" {\n command = plan\n\n variables {\n vpc_cidr = \"10.0.0.0/16\"\n vpc_name = \"test-vpc\"\n public_subnets = [\"10.0.1.0/24\", \"10.0.2.0/24\"]\n private_subnets = [\"10.0.10.0/24\", \"10.0.11.0/24\"]\n }\n\n assert {\n condition = length(aws_subnet.public) == 2\n error_message = \"Should create 2 public subnets\"\n }\n\n assert {\n condition = length(aws_subnet.private) == 2\n error_message = \"Should create 2 private subnets\"\n }\n\n assert {\n condition = alltrue([\n for subnet in aws_subnet.private :\n subnet.map_public_ip_on_launch == false\n ])\n error_message = \"Private subnets should not assign public IPs\"\n }\n}\n\nrun \"test_outputs\" {\n command = plan\n\n variables {\n vpc_cidr = \"10.0.0.0/16\"\n vpc_name = \"test-vpc\"\n }\n\n assert {\n condition = output.vpc_id != \"\"\n error_message = \"VPC ID output should not be empty\"\n }\n\n assert {\n condition = can(regex(\"^vpc-\", output.vpc_id))\n error_message = \"VPC ID should have correct format\"\n }\n\n assert {\n condition = output.vpc_cidr == \"10.0.0.0/16\"\n error_message = \"VPC CIDR output should match input\"\n }\n}\n\nrun \"test_invalid_cidr\" {\n command = plan\n\n variables {\n vpc_cidr = \"invalid\"\n vpc_name = \"test-vpc\"\n }\n\n expect_failures = [\n var.vpc_cidr\n ]\n}\n```\n\n## Integration Tests (Apply Mode)\n\n```hcl\n# tests/vpc_module_integration_test.tftest.hcl\n\nvariables {\n environment = \"integration-test\"\n aws_region = \"us-west-2\"\n}\n\nrun \"integration_test_vpc_creation\" {\n # command defaults to apply — creates real AWS resources\n\n variables {\n vpc_cidr = \"10.100.0.0/16\"\n vpc_name = \"integration-test-vpc\"\n }\n\n assert {\n condition = aws_vpc.main.id != \"\"\n error_message = \"VPC should be created with valid ID\"\n }\n\n assert {\n condition = aws_vpc.main.state == \"available\"\n error_message = \"VPC should be in available state\"\n }\n}\n```\n\n## Mock Tests (Plan Mode, No Credentials)\n\n```hcl\n# tests/vpc_module_mock_test.tftest.hcl\n\nmock_provider \"aws\" {\n mock_resource \"aws_instance\" {\n defaults = {\n id = \"i-1234567890abcdef0\"\n instance_type = \"t2.micro\"\n ami = \"ami-12345678\"\n public_ip = \"203.0.113.1\"\n private_ip = \"10.0.1.100\"\n }\n }\n\n mock_resource \"aws_vpc\" {\n defaults = {\n id = \"vpc-12345678\"\n cidr_block = \"10.0.0.0/16\"\n enable_dns_hostnames = true\n enable_dns_support = true\n }\n }\n\n mock_resource \"aws_subnet\" {\n defaults = {\n id = \"subnet-12345678\"\n vpc_id = \"vpc-12345678\"\n cidr_block = \"10.0.1.0/24\"\n availability_zone = \"us-west-2a\"\n map_public_ip_on_launch = false\n }\n }\n\n mock_data \"aws_ami\" {\n defaults = {\n id = \"ami-0c55b159cbfafe1f0\"\n name = \"ubuntu-focal-20.04-amd64\"\n }\n }\n\n mock_data \"aws_availability_zones\" {\n defaults = {\n names = [\"us-west-2a\", \"us-west-2b\", \"us-west-2c\"]\n }\n }\n}\n\nrun \"test_instance_with_mocks\" {\n command = plan\n\n variables {\n instance_type = \"t2.micro\"\n ami_id = \"ami-12345678\"\n }\n\n assert {\n condition = aws_instance.example.instance_type == \"t2.micro\"\n error_message = \"Instance type should match input variable\"\n }\n\n assert {\n condition = aws_instance.example.id == \"i-1234567890abcdef0\"\n error_message = \"Mock should return consistent instance ID\"\n }\n}\n\nrun \"test_data_source_with_mocks\" {\n command = plan\n\n assert {\n condition = data.aws_ami.ubuntu.id == \"ami-0c55b159cbfafe1f0\"\n error_message = \"Mock data source should return predictable AMI ID\"\n }\n\n assert {\n condition = length(data.aws_availability_zones.available.names) == 3\n error_message = \"Should return 3 mocked availability zones\"\n }\n\n assert {\n condition = contains(data.aws_availability_zones.available.names, \"us-west-2a\")\n error_message = \"Should include us-west-2a in mocked zones\"\n }\n}\n\nrun \"test_outputs_with_mocks\" {\n command = plan\n\n assert {\n condition = output.vpc_id == \"vpc-12345678\"\n error_message = \"VPC ID output should match mocked value\"\n }\n\n assert {\n condition = can(regex(\"^vpc-\", output.vpc_id))\n error_message = \"VPC ID output should have correct format\"\n }\n}\n\nrun \"test_conditional_resources_with_mocks\" {\n command = plan\n\n variables {\n create_bastion = true\n create_nat_gateway = false\n }\n\n assert {\n condition = length(aws_instance.bastion) == 1\n error_message = \"Bastion should be created when enabled\"\n }\n\n assert {\n condition = length(aws_nat_gateway.nat) == 0\n error_message = \"NAT gateway should not be created when disabled\"\n }\n}\n\nrun \"test_tag_inheritance_with_mocks\" {\n command = plan\n\n variables {\n common_tags = {\n Environment = \"test\"\n ManagedBy = \"Terraform\"\n }\n }\n\n assert {\n condition = alltrue([\n for key in keys(var.common_tags) :\n contains(keys(aws_instance.example.tags), key)\n ])\n error_message = \"All common tags should be present on instance\"\n }\n}\n\nrun \"test_invalid_cidr_with_mocks\" {\n command = plan\n\n variables {\n vpc_cidr = \"invalid\"\n }\n\n expect_failures = [\n var.vpc_cidr\n ]\n}\n\nrun \"setup_vpc_with_mocks\" {\n command = plan\n\n variables {\n vpc_cidr = \"10.0.0.0/16\"\n vpc_name = \"test-vpc\"\n }\n\n assert {\n condition = aws_vpc.main.cidr_block == \"10.0.0.0/16\"\n error_message = \"VPC CIDR should match input\"\n }\n}\n\nrun \"test_subnet_references_vpc_with_mocks\" {\n command = plan\n\n variables {\n vpc_id = run.setup_vpc_with_mocks.vpc_id\n subnet_cidr = \"10.0.1.0/24\"\n }\n\n assert {\n condition = aws_subnet.example.vpc_id == run.setup_vpc_with_mocks.vpc_id\n error_message = \"Subnet should reference VPC from previous run\"\n }\n}\n```\n",
548
+ "byte_size": 6609,
549
+ "content_sha256": "e57ef65b6c6545e00f7dc6106c71d44d29e819923ea8e0040efb38fbfbc60f50"
550
+ },
551
+ {
552
+ "path": "references/MOCK_PROVIDERS.md",
553
+ "content": "# Mock Providers\n\nMock providers simulate provider behavior without creating real infrastructure (Terraform 1.7.0+). Use them for fast, credential-free unit tests.\n\n## Basic Mock Provider\n\n```hcl\nmock_provider \"aws\" {\n mock_resource \"aws_instance\" {\n defaults = {\n id = \"i-1234567890abcdef0\"\n instance_type = \"t2.micro\"\n ami = \"ami-12345678\"\n public_ip = \"203.0.113.1\"\n private_ip = \"10.0.1.100\"\n }\n }\n\n mock_data \"aws_ami\" {\n defaults = {\n id = \"ami-0c55b159cbfafe1f0\"\n }\n }\n\n mock_data \"aws_availability_zones\" {\n defaults = {\n names = [\"us-west-2a\", \"us-west-2b\", \"us-west-2c\"]\n }\n }\n}\n\nrun \"test_with_mocks\" {\n command = plan # Mocks only work with plan mode\n\n assert {\n condition = aws_instance.example.id == \"i-1234567890abcdef0\"\n error_message = \"Mock instance ID should match\"\n }\n}\n```\n\n## Aliased Mock Provider\n\n```hcl\nmock_provider \"aws\" {\n alias = \"mocked\"\n\n mock_resource \"aws_s3_bucket\" {\n defaults = {\n id = \"test-bucket-12345\"\n arn = \"arn:aws:s3:::test-bucket-12345\"\n }\n }\n}\n\nrun \"test_with_aliased_mock\" {\n command = plan\n\n providers = {\n aws = provider.aws.mocked\n }\n\n assert {\n condition = aws_s3_bucket.example.id == \"test-bucket-12345\"\n error_message = \"Bucket ID should match mock\"\n }\n}\n```\n\n## Common Mock Defaults\n\n```hcl\nmock_provider \"aws\" {\n mock_resource \"aws_instance\" {\n defaults = {\n id = \"i-1234567890abcdef0\"\n arn = \"arn:aws:ec2:us-west-2:123456789012:instance/i-1234567890abcdef0\"\n instance_type = \"t2.micro\"\n ami = \"ami-12345678\"\n availability_zone = \"us-west-2a\"\n subnet_id = \"subnet-12345678\"\n vpc_security_group_ids = [\"sg-12345678\"]\n associate_public_ip_address = true\n public_ip = \"203.0.113.1\"\n private_ip = \"10.0.1.100\"\n tags = {}\n }\n }\n\n mock_resource \"aws_vpc\" {\n defaults = {\n id = \"vpc-12345678\"\n arn = \"arn:aws:ec2:us-west-2:123456789012:vpc/vpc-12345678\"\n cidr_block = \"10.0.0.0/16\"\n enable_dns_hostnames = true\n enable_dns_support = true\n instance_tenancy = \"default\"\n tags = {}\n }\n }\n\n mock_resource \"aws_subnet\" {\n defaults = {\n id = \"subnet-12345678\"\n arn = \"arn:aws:ec2:us-west-2:123456789012:subnet/subnet-12345678\"\n vpc_id = \"vpc-12345678\"\n cidr_block = \"10.0.1.0/24\"\n availability_zone = \"us-west-2a\"\n map_public_ip_on_launch = false\n tags = {}\n }\n }\n\n mock_resource \"aws_s3_bucket\" {\n defaults = {\n id = \"test-bucket-12345\"\n arn = \"arn:aws:s3:::test-bucket-12345\"\n bucket = \"test-bucket-12345\"\n bucket_domain_name = \"test-bucket-12345.s3.amazonaws.com\"\n region = \"us-west-2\"\n tags = {}\n }\n }\n\n mock_data \"aws_ami\" {\n defaults = {\n id = \"ami-0c55b159cbfafe1f0\"\n name = \"ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-20210430\"\n architecture = \"x86_64\"\n root_device_type = \"ebs\"\n virtualization_type = \"hvm\"\n }\n }\n\n mock_data \"aws_availability_zones\" {\n defaults = {\n names = [\"us-west-2a\", \"us-west-2b\", \"us-west-2c\"]\n zone_ids = [\"usw2-az1\", \"usw2-az2\", \"usw2-az3\"]\n }\n }\n\n mock_data \"aws_vpc\" {\n defaults = {\n id = \"vpc-12345678\"\n cidr_block = \"10.0.0.0/16\"\n enable_dns_hostnames = true\n enable_dns_support = true\n }\n }\n}\n```\n\n## When to Use Mocks\n\n**Good fit:**\n- Testing Terraform logic, conditionals, `for_each`/`count` expressions\n- Validating variable transformations and output calculations\n- Local development without cloud credentials\n- Fast CI/CD feedback loops\n\n**Not a good fit:**\n- Validating actual provider API behavior\n- Testing real resource creation side effects\n- End-to-end integration testing\n\n## Limitations\n\n- **Plan mode only** — mocks don't work with `command = apply`\n- Mock defaults may not reflect real computed attribute values\n- Mocks need manual updates when provider schemas change\n- Can't test real resource dependencies or timing\n",
554
+ "byte_size": 4519,
555
+ "content_sha256": "788a9fcb988df8a207c1251fd971a11d1cd59b6b8e4023907b00a0fee976c001"
556
+ }
557
+ ]
558
+ }
559
+ ]
560
+ $skill_authority_seed$::jsonb
561
+ ) AS seed(
562
+ library_id text,
563
+ capability_id text,
564
+ plugin_key text,
565
+ version text,
566
+ name text,
567
+ description text,
568
+ category text,
569
+ tags jsonb,
570
+ source_url text,
571
+ repository_url text,
572
+ source_commit text,
573
+ source_path text,
574
+ source_provenance text,
575
+ content_sha256 text,
576
+ file_count integer,
577
+ total_bytes integer,
578
+ license text,
579
+ manifest jsonb,
580
+ manifest_digest text,
581
+ files jsonb
582
+ );
583
+
584
+ CREATE UNIQUE INDEX skill_authority_library_id_idx
585
+ ON skill_authority_library (library_id);
586
+ CREATE UNIQUE INDEX skill_authority_capability_id_idx
587
+ ON skill_authority_library (capability_id);
588
+ -- END GENERATED CURATED SKILL LIBRARY SEED
589
+
590
+ DO $seed_integrity$
591
+ BEGIN
592
+ IF EXISTS (
593
+ SELECT 1
594
+ FROM skill_authority_library seed
595
+ WHERE seed.capability_id <> 'skill:' || seed.library_id
596
+ OR seed.plugin_key <> 'skill/library/' || seed.library_id
597
+ OR seed.file_count <> jsonb_array_length(seed.files)
598
+ OR seed.total_bytes <> COALESCE((
599
+ SELECT sum(file.byte_size)
600
+ FROM jsonb_to_recordset(seed.files) AS file(
601
+ path text,
602
+ content text,
603
+ byte_size integer,
604
+ content_sha256 text
605
+ )
606
+ ), 0)
607
+ OR seed.manifest_digest <> encode(
608
+ digest(
609
+ convert_to(
610
+ opengeni_private.skill_authority_canonical_json(seed.manifest),
611
+ 'UTF8'
612
+ ),
613
+ 'sha256'
614
+ ),
615
+ 'hex'
616
+ )
617
+ OR EXISTS (
618
+ SELECT 1
619
+ FROM jsonb_to_recordset(seed.files) AS file(
620
+ path text,
621
+ content text,
622
+ byte_size integer,
623
+ content_sha256 text
624
+ )
625
+ WHERE file.byte_size <> octet_length(convert_to(file.content, 'UTF8'))
626
+ OR file.content_sha256 <> encode(
627
+ digest(convert_to(file.content, 'UTF8'), 'sha256'),
628
+ 'hex'
629
+ )
630
+ )
631
+ ) THEN
632
+ RAISE EXCEPTION 'curated Skill authority seed failed immutable integrity validation'
633
+ USING ERRCODE = '23514';
634
+ END IF;
635
+ END
636
+ $seed_integrity$;
637
+
638
+ CREATE TEMP TABLE skill_authority_curated_selections ON COMMIT DROP AS
639
+ SELECT
640
+ installation.id AS legacy_installation_id,
641
+ installation.account_id,
642
+ installation.workspace_id,
643
+ installation.enabled_at,
644
+ seed.*
645
+ FROM capability_installations installation
646
+ JOIN skill_authority_library seed
647
+ ON seed.capability_id = installation.capability_id
648
+ WHERE installation.kind = 'skill'
649
+ AND installation.status = 'active'
650
+ AND COALESCE(installation.metadata ->> 'platformVersion', '') <> '2'
651
+ AND installation.config ->> 'version' = seed.version
652
+ AND installation.metadata ->> 'libraryId' = seed.library_id
653
+ AND installation.metadata ->> 'libraryVersion' = seed.version
654
+ AND installation.metadata ->> 'contentSha256' = seed.content_sha256
655
+ AND installation.metadata ->> 'sourceCommit' = seed.source_commit
656
+ AND installation.metadata ->> 'provenance' = seed.source_provenance;
657
+
658
+ CREATE UNIQUE INDEX skill_authority_curated_selection_legacy_idx
659
+ ON skill_authority_curated_selections (legacy_installation_id);
660
+
661
+ DO $legacy_skill_precondition$
662
+ BEGIN
663
+ IF EXISTS (
664
+ SELECT 1
665
+ FROM capability_installations installation
666
+ WHERE installation.kind = 'skill'
667
+ AND installation.status = 'active'
668
+ AND NOT EXISTS (
669
+ SELECT 1
670
+ FROM skill_authority_curated_selections selected
671
+ WHERE selected.legacy_installation_id = installation.id
672
+ )
673
+ AND NOT (
674
+ installation.metadata ->> 'platformVersion' = '2'
675
+ AND CASE
676
+ WHEN installation.metadata ->> 'facetInstallationId'
677
+ ~* '^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$'
678
+ THEN EXISTS (
679
+ SELECT 1
680
+ FROM capability_facet_installations facet_installation
681
+ JOIN capability_plugin_installations plugin_installation
682
+ ON plugin_installation.id = facet_installation.plugin_installation_id
683
+ JOIN capability_skill_facets skill
684
+ ON skill.facet_id = facet_installation.facet_id
685
+ WHERE facet_installation.id =
686
+ (installation.metadata ->> 'facetInstallationId')::uuid
687
+ AND facet_installation.account_id = installation.account_id
688
+ AND facet_installation.workspace_id = installation.workspace_id
689
+ AND facet_installation.status = 'active'
690
+ AND plugin_installation.account_id = installation.account_id
691
+ AND plugin_installation.workspace_id = installation.workspace_id
692
+ AND plugin_installation.status = 'active'
693
+ AND skill.capability_id = installation.capability_id
694
+ AND EXISTS (
695
+ SELECT 1
696
+ FROM capability_component_owners owner
697
+ WHERE owner.facet_installation_id = facet_installation.id
698
+ )
699
+ )
700
+ ELSE false
701
+ END
702
+ )
703
+ ) THEN
704
+ RAISE EXCEPTION
705
+ 'an active legacy Skill installation is neither an exact curated selection nor a valid normalized Skill projection'
706
+ USING ERRCODE = '23514';
707
+ END IF;
708
+ END
709
+ $legacy_skill_precondition$;
710
+
711
+ DO $materialize_curated_skill_selections$
712
+ DECLARE
713
+ selected record;
714
+ v_plugin_id uuid;
715
+ v_plugin_version_id uuid;
716
+ v_skill_facet_id uuid;
717
+ v_plugin_installation_id uuid;
718
+ v_facet_installation_id uuid;
719
+ existing_plugin_version record;
720
+ existing_facet record;
721
+ existing_skill record;
722
+ existing_plugin_installation record;
723
+ existing_facet_installation record;
724
+ BEGIN
725
+ FOR selected IN
726
+ SELECT *
727
+ FROM skill_authority_curated_selections
728
+ ORDER BY workspace_id, library_id
729
+ LOOP
730
+ SELECT plugin.id
731
+ INTO v_plugin_id
732
+ FROM capability_plugins plugin
733
+ WHERE plugin.workspace_id = selected.workspace_id
734
+ AND plugin.plugin_key = selected.plugin_key
735
+ FOR UPDATE;
736
+
737
+ IF v_plugin_id IS NULL THEN
738
+ INSERT INTO capability_plugins (
739
+ plugin_key,
740
+ account_id,
741
+ workspace_id,
742
+ name,
743
+ description,
744
+ category,
745
+ tags,
746
+ provenance
747
+ ) VALUES (
748
+ selected.plugin_key,
749
+ selected.account_id,
750
+ selected.workspace_id,
751
+ selected.name,
752
+ selected.description,
753
+ selected.category,
754
+ selected.tags,
755
+ 'platform'
756
+ )
757
+ RETURNING id INTO v_plugin_id;
758
+ ELSE
759
+ IF EXISTS (
760
+ SELECT 1
761
+ FROM capability_plugins plugin
762
+ WHERE plugin.id = v_plugin_id
763
+ AND plugin.account_id IS DISTINCT FROM selected.account_id
764
+ ) THEN
765
+ RAISE EXCEPTION 'curated Skill Plugin tenant mismatch for %', selected.plugin_key
766
+ USING ERRCODE = '23514';
767
+ END IF;
768
+ UPDATE capability_plugins
769
+ SET name = selected.name,
770
+ description = selected.description,
771
+ category = selected.category,
772
+ tags = selected.tags,
773
+ provenance = 'platform',
774
+ updated_at = now()
775
+ WHERE id = v_plugin_id
776
+ AND (
777
+ name IS DISTINCT FROM selected.name
778
+ OR description IS DISTINCT FROM selected.description
779
+ OR category IS DISTINCT FROM selected.category
780
+ OR tags IS DISTINCT FROM selected.tags
781
+ OR provenance IS DISTINCT FROM 'platform'
782
+ );
783
+ END IF;
784
+
785
+ SELECT version.*
786
+ INTO existing_plugin_version
787
+ FROM capability_plugin_versions version
788
+ WHERE version.plugin_id = v_plugin_id
789
+ AND version.version = selected.version
790
+ FOR UPDATE;
791
+
792
+ IF NOT FOUND THEN
793
+ INSERT INTO capability_plugin_versions (
794
+ plugin_id,
795
+ version,
796
+ manifest_digest,
797
+ manifest,
798
+ status
799
+ ) VALUES (
800
+ v_plugin_id,
801
+ selected.version,
802
+ selected.manifest_digest,
803
+ selected.manifest,
804
+ 'published'
805
+ )
806
+ RETURNING id INTO v_plugin_version_id;
807
+ ELSE
808
+ IF existing_plugin_version.manifest_digest <> selected.manifest_digest
809
+ OR existing_plugin_version.manifest IS DISTINCT FROM selected.manifest
810
+ THEN
811
+ RAISE EXCEPTION 'curated Skill immutable version conflict for %@%',
812
+ selected.library_id,
813
+ selected.version
814
+ USING ERRCODE = '23514';
815
+ END IF;
816
+ v_plugin_version_id := existing_plugin_version.id;
817
+ UPDATE capability_plugin_versions
818
+ SET status = 'published'
819
+ WHERE id = v_plugin_version_id
820
+ AND status IS DISTINCT FROM 'published';
821
+ END IF;
822
+
823
+ SELECT facet.*
824
+ INTO existing_facet
825
+ FROM capability_facets facet
826
+ WHERE facet.plugin_version_id = v_plugin_version_id
827
+ AND facet.facet_key = 'skill';
828
+
829
+ IF NOT FOUND THEN
830
+ INSERT INTO capability_facets (
831
+ plugin_version_id,
832
+ facet_key,
833
+ kind,
834
+ activation_mode,
835
+ required
836
+ ) VALUES (
837
+ v_plugin_version_id,
838
+ 'skill',
839
+ 'skill',
840
+ 'workspace_managed',
841
+ true
842
+ )
843
+ RETURNING id INTO v_skill_facet_id;
844
+ ELSE
845
+ IF existing_facet.kind <> 'skill'
846
+ OR existing_facet.activation_mode <> 'workspace_managed'
847
+ OR existing_facet.required IS DISTINCT FROM true
848
+ THEN
849
+ RAISE EXCEPTION 'curated Skill immutable Facet conflict for %', selected.library_id
850
+ USING ERRCODE = '23514';
851
+ END IF;
852
+ v_skill_facet_id := existing_facet.id;
853
+ END IF;
854
+
855
+ SELECT skill.*
856
+ INTO existing_skill
857
+ FROM capability_skill_facets skill
858
+ WHERE skill.facet_id = v_skill_facet_id;
859
+
860
+ IF NOT FOUND THEN
861
+ INSERT INTO capability_skill_facets (
862
+ facet_id,
863
+ capability_id,
864
+ name,
865
+ description,
866
+ source_url,
867
+ source_commit,
868
+ source_path,
869
+ content_sha256,
870
+ file_count,
871
+ total_bytes,
872
+ license
873
+ ) VALUES (
874
+ v_skill_facet_id,
875
+ selected.capability_id,
876
+ selected.name,
877
+ selected.description,
878
+ selected.source_url,
879
+ selected.source_commit,
880
+ selected.source_path,
881
+ selected.content_sha256,
882
+ selected.file_count,
883
+ selected.total_bytes,
884
+ selected.license
885
+ );
886
+
887
+ INSERT INTO capability_skill_files (
888
+ skill_facet_id,
889
+ path,
890
+ content,
891
+ byte_size,
892
+ content_sha256
893
+ )
894
+ SELECT
895
+ v_skill_facet_id,
896
+ file.path,
897
+ file.content,
898
+ file.byte_size,
899
+ file.content_sha256
900
+ FROM jsonb_to_recordset(selected.files) AS file(
901
+ path text,
902
+ content text,
903
+ byte_size integer,
904
+ content_sha256 text
905
+ );
906
+ ELSE
907
+ IF existing_skill.capability_id <> selected.capability_id
908
+ OR existing_skill.name <> selected.name
909
+ OR existing_skill.description <> selected.description
910
+ OR existing_skill.source_url <> selected.source_url
911
+ OR existing_skill.source_commit <> selected.source_commit
912
+ OR existing_skill.source_path <> selected.source_path
913
+ OR existing_skill.content_sha256 <> selected.content_sha256
914
+ OR existing_skill.file_count <> selected.file_count
915
+ OR existing_skill.total_bytes <> selected.total_bytes
916
+ OR existing_skill.license IS DISTINCT FROM selected.license
917
+ THEN
918
+ RAISE EXCEPTION 'curated Skill immutable artifact conflict for %', selected.library_id
919
+ USING ERRCODE = '23514';
920
+ END IF;
921
+ END IF;
922
+
923
+ IF (SELECT count(*) FROM capability_skill_files file WHERE file.skill_facet_id = v_skill_facet_id)
924
+ <> selected.file_count
925
+ OR EXISTS (
926
+ SELECT 1
927
+ FROM jsonb_to_recordset(selected.files) AS seeded_file(
928
+ path text,
929
+ content text,
930
+ byte_size integer,
931
+ content_sha256 text
932
+ )
933
+ LEFT JOIN capability_skill_files stored_file
934
+ ON stored_file.skill_facet_id = v_skill_facet_id
935
+ AND stored_file.path = seeded_file.path
936
+ WHERE stored_file.id IS NULL
937
+ OR stored_file.content <> seeded_file.content
938
+ OR stored_file.byte_size <> seeded_file.byte_size
939
+ OR stored_file.content_sha256 <> seeded_file.content_sha256
940
+ )
941
+ THEN
942
+ RAISE EXCEPTION 'curated Skill immutable file conflict for %', selected.library_id
943
+ USING ERRCODE = '23514';
944
+ END IF;
945
+
946
+ SELECT installation.*
947
+ INTO existing_plugin_installation
948
+ FROM capability_plugin_installations installation
949
+ WHERE installation.workspace_id = selected.workspace_id
950
+ AND installation.plugin_id = v_plugin_id
951
+ FOR UPDATE;
952
+
953
+ IF NOT FOUND THEN
954
+ INSERT INTO capability_plugin_installations (
955
+ account_id,
956
+ workspace_id,
957
+ plugin_id,
958
+ plugin_version_id,
959
+ status,
960
+ installed_by_subject_id,
961
+ installed_at,
962
+ updated_at
963
+ ) VALUES (
964
+ selected.account_id,
965
+ selected.workspace_id,
966
+ v_plugin_id,
967
+ v_plugin_version_id,
968
+ 'active',
969
+ 'migration:0233',
970
+ selected.enabled_at,
971
+ now()
972
+ )
973
+ RETURNING id INTO v_plugin_installation_id;
974
+ ELSE
975
+ v_plugin_installation_id := existing_plugin_installation.id;
976
+ IF existing_plugin_installation.plugin_version_id <> v_plugin_version_id THEN
977
+ IF EXISTS (
978
+ SELECT 1
979
+ FROM capability_facet_installations facet_installation
980
+ JOIN capability_component_owners owner
981
+ ON owner.facet_installation_id = facet_installation.id
982
+ WHERE facet_installation.plugin_installation_id = v_plugin_installation_id
983
+ AND NOT (
984
+ owner.owner_kind = 'direct'
985
+ AND owner.owner_id = selected.capability_id
986
+ )
987
+ ) THEN
988
+ RAISE EXCEPTION 'curated Skill version is pinned by another owner for %',
989
+ selected.library_id
990
+ USING ERRCODE = '23514';
991
+ END IF;
992
+ DELETE FROM capability_facet_installations
993
+ WHERE plugin_installation_id = existing_plugin_installation.id;
994
+ END IF;
995
+ IF existing_plugin_installation.plugin_version_id <> v_plugin_version_id
996
+ OR existing_plugin_installation.status <> 'active'
997
+ THEN
998
+ UPDATE capability_plugin_installations
999
+ SET plugin_version_id = v_plugin_version_id,
1000
+ status = 'active',
1001
+ version = version + 1,
1002
+ installed_by_subject_id = 'migration:0233',
1003
+ installed_at = selected.enabled_at,
1004
+ updated_at = now()
1005
+ WHERE id = v_plugin_installation_id;
1006
+ END IF;
1007
+ END IF;
1008
+
1009
+ SELECT installation.*
1010
+ INTO existing_facet_installation
1011
+ FROM capability_facet_installations installation
1012
+ WHERE installation.plugin_installation_id = v_plugin_installation_id
1013
+ AND installation.facet_id = v_skill_facet_id
1014
+ FOR UPDATE;
1015
+
1016
+ IF NOT FOUND THEN
1017
+ INSERT INTO capability_facet_installations (
1018
+ account_id,
1019
+ workspace_id,
1020
+ plugin_installation_id,
1021
+ facet_id,
1022
+ status,
1023
+ config
1024
+ ) VALUES (
1025
+ selected.account_id,
1026
+ selected.workspace_id,
1027
+ v_plugin_installation_id,
1028
+ v_skill_facet_id,
1029
+ 'active',
1030
+ '{}'::jsonb
1031
+ )
1032
+ RETURNING id INTO v_facet_installation_id;
1033
+ ELSE
1034
+ v_facet_installation_id := existing_facet_installation.id;
1035
+ IF existing_facet_installation.account_id <> selected.account_id
1036
+ OR existing_facet_installation.workspace_id <> selected.workspace_id
1037
+ THEN
1038
+ RAISE EXCEPTION 'curated Skill Facet installation tenant mismatch for %',
1039
+ selected.library_id
1040
+ USING ERRCODE = '23514';
1041
+ END IF;
1042
+ IF existing_facet_installation.status <> 'active'
1043
+ OR existing_facet_installation.config IS DISTINCT FROM '{}'::jsonb
1044
+ OR existing_facet_installation.attention_code IS NOT NULL
1045
+ THEN
1046
+ UPDATE capability_facet_installations
1047
+ SET status = 'active',
1048
+ config = '{}'::jsonb,
1049
+ version = version + 1,
1050
+ attention_code = NULL,
1051
+ updated_at = now()
1052
+ WHERE id = v_facet_installation_id;
1053
+ END IF;
1054
+ END IF;
1055
+
1056
+ INSERT INTO capability_component_owners (
1057
+ account_id,
1058
+ workspace_id,
1059
+ facet_installation_id,
1060
+ owner_kind,
1061
+ owner_id,
1062
+ removable
1063
+ ) VALUES (
1064
+ selected.account_id,
1065
+ selected.workspace_id,
1066
+ v_facet_installation_id,
1067
+ 'direct',
1068
+ selected.capability_id,
1069
+ true
1070
+ )
1071
+ ON CONFLICT (facet_installation_id, owner_kind, owner_id) DO NOTHING;
1072
+ END LOOP;
1073
+ END
1074
+ $materialize_curated_skill_selections$;
1075
+
1076
+ DO $curated_skill_postcondition$
1077
+ BEGIN
1078
+ IF EXISTS (
1079
+ SELECT 1
1080
+ FROM skill_authority_curated_selections selected
1081
+ WHERE NOT EXISTS (
1082
+ SELECT 1
1083
+ FROM capability_plugins plugin
1084
+ JOIN capability_plugin_versions version
1085
+ ON version.plugin_id = plugin.id
1086
+ AND version.version = selected.version
1087
+ AND version.manifest_digest = selected.manifest_digest
1088
+ JOIN capability_facets facet
1089
+ ON facet.plugin_version_id = version.id
1090
+ AND facet.facet_key = 'skill'
1091
+ AND facet.kind = 'skill'
1092
+ JOIN capability_skill_facets skill
1093
+ ON skill.facet_id = facet.id
1094
+ AND skill.capability_id = selected.capability_id
1095
+ AND skill.content_sha256 = selected.content_sha256
1096
+ JOIN capability_plugin_installations plugin_installation
1097
+ ON plugin_installation.plugin_id = plugin.id
1098
+ AND plugin_installation.plugin_version_id = version.id
1099
+ AND plugin_installation.workspace_id = selected.workspace_id
1100
+ AND plugin_installation.status = 'active'
1101
+ JOIN capability_facet_installations facet_installation
1102
+ ON facet_installation.plugin_installation_id = plugin_installation.id
1103
+ AND facet_installation.facet_id = facet.id
1104
+ AND facet_installation.status = 'active'
1105
+ JOIN capability_component_owners owner
1106
+ ON owner.facet_installation_id = facet_installation.id
1107
+ AND owner.owner_kind = 'direct'
1108
+ AND owner.owner_id = selected.capability_id
1109
+ WHERE plugin.workspace_id = selected.workspace_id
1110
+ AND plugin.account_id = selected.account_id
1111
+ AND plugin.plugin_key = selected.plugin_key
1112
+ )
1113
+ ) THEN
1114
+ RAISE EXCEPTION 'curated Skill authority migration did not converge'
1115
+ USING ERRCODE = '23514';
1116
+ END IF;
1117
+ END
1118
+ $curated_skill_postcondition$;
1119
+
1120
+ DELETE FROM capability_installations
1121
+ WHERE kind <> 'mcp';
1122
+
1123
+ DELETE FROM capability_catalog_items
1124
+ WHERE kind <> 'mcp';
1125
+
1126
+ ALTER TABLE capability_installations
1127
+ DROP CONSTRAINT IF EXISTS capability_installations_kind_authority_chk;
1128
+ ALTER TABLE capability_installations
1129
+ ADD CONSTRAINT capability_installations_kind_authority_chk
1130
+ CHECK (kind = 'mcp') NOT VALID;
1131
+ ALTER TABLE capability_installations
1132
+ VALIDATE CONSTRAINT capability_installations_kind_authority_chk;
1133
+
1134
+ ALTER TABLE capability_catalog_items
1135
+ DROP CONSTRAINT IF EXISTS capability_catalog_items_kind_authority_chk;
1136
+ ALTER TABLE capability_catalog_items
1137
+ ADD CONSTRAINT capability_catalog_items_kind_authority_chk
1138
+ CHECK (kind = 'mcp') NOT VALID;
1139
+ ALTER TABLE capability_catalog_items
1140
+ VALIDATE CONSTRAINT capability_catalog_items_kind_authority_chk;
1141
+
1142
+ COMMENT ON TABLE capability_catalog_items IS
1143
+ 'MCP discovery catalog only. Skills, Plugins, Integration Definitions, and Packs use their dedicated authoritative ledgers.';
1144
+ COMMENT ON TABLE capability_installations IS
1145
+ 'MCP enablement only. Skill, Plugin, Integration Definition, and Pack lifecycle is owned by normalized domain installations.';
1146
+ COMMENT ON TABLE capability_plugins IS
1147
+ 'Authoritative normalized Plugin definitions for Skills, Plugins, and Integration Definitions.';
1148
+ COMMENT ON TABLE capability_plugin_installations IS
1149
+ 'Authoritative workspace Plugin-version lifecycle for Skills, Plugins, and Integration Definitions.';
1150
+
1151
+ DO $authority_postcondition$
1152
+ BEGIN
1153
+ IF EXISTS (SELECT 1 FROM capability_installations WHERE kind <> 'mcp')
1154
+ OR EXISTS (SELECT 1 FROM capability_catalog_items WHERE kind <> 'mcp')
1155
+ THEN
1156
+ RAISE EXCEPTION 'generic Capability authority cutover did not converge'
1157
+ USING ERRCODE = '23514';
1158
+ END IF;
1159
+ END
1160
+ $authority_postcondition$;
1161
+
1162
+ DROP FUNCTION opengeni_private.skill_authority_canonical_json(jsonb);