Schema driven refactor#33
Merged
Merged
Conversation
…main rows Two bugs in connection drag: 1. dragCompatibility positions used getPortAnchorPoint (schema's side-distribution math) instead of getSocketCanvasPosition, so the snap target Y on Custom Domain row ports drifted progressively from the visible dot (~50px on row 0, ~0px on the last row). 2. snap was computed from currentPoint, which itself is set to the snapped port's position. Distance-to-self stayed at 0 so the snap locked onto the first port that ever won. Added cursorPoint to DrawingConnectionState and compute snap from it; currentPoint remains the visible (snapped or cursor) wire endpoint.
Properties + deploy model rebuilt around the canonical schema:
- Properties: name → "Store name"; secrets list → new `secret_bindings`
field with two inputs per row (env-var key ← upstream ref); auto_rotate
dropped (was inert + wrong-scoped). Bindings explained: the block does
NOT hold secret values — values live in the cloud secret manager.
- Schema: add optional `iceType` and `deployExpansion` to
HighLevelResource. Secret Store declares
`deployExpansion: { partitionBy: 'bindings', nameFrom: { field: 'ref',
fallback: 'key' }, labelFrom: 'key', tagPerEntry: ... }`.
- Lookup: getHighLevelResourceByIceType helper with a cached index.
- Generic pass: new deploy/passes/deploy-expansion.ts emits one cloud
resource per partition entry, dedup'd within/across blocks, forwarding
provider-shaped properties verbatim. Knows nothing about secrets.
- Translator: the previous `if (iceType === 'Security.Secret')` branch
is replaced with `if (schemaResource?.deployExpansion)` — cardinal
rule, no iceType hardcoded in cross-cutting code.
Adding AWS Secrets Manager or Azure Key Vault requires only an extractor
+ handler for the provider's resource type. Adding a new expanding block
requires only a `deployExpansion` declaration on its schema entry.
…L_NODE_RENDERERS table Audit item #11 — cardinal rule violation. The dispatcher had three hardcoded `if (iceType === 'X')` branches for Custom Domain, Reroute, and Private Network, each wiring a bespoke component with its own prop set + innerKey formula. Consolidated into a single declarative `SPECIAL_NODE_RENDERERS` table keyed by iceType, with factory entries that own their own component AND innerKey formula. The dispatcher iterates this table generically — no iceType-specific code paths remain. Adding a new bespoke renderer extends the table; the dispatcher stays unchanged. Dispatch order preserved by construction: the special table is consulted BEFORE the container check so PrivateNetwork (a container we render with a custom header) and Reroute (which the classifier calls a container despite being a pass-through dot) hit their bespoke factories first. Tests: locked-in entries list + per-entry contract (element + innerKey) + innerKey-changes-on-relevant-data assertions for Custom Domain (routes count) and PrivateNetwork (ingress mode).
…BESPOKE_SOCKET_POSITIONS table Audit item #12 — cardinal rule violation. `getSocketCanvasPosition` had an `if (iceType === 'Network.CustomDomain' && socketId.startsWith ('domain-out-'))` branch that resolved the bespoke row-Y for per-route ports. Consolidated into a `BESPOKE_SOCKET_POSITIONS` table keyed by iceType, with resolver entries returning `Point | null` (null = fall through to the standard layout). The dispatcher iterates this table generically — no iceType branches in the resolver function. New bespoke layouts register here; dispatch stays unchanged. Tests: locked-in entries list, per-resolver contract (returns null on miss), and dispatcher behaviour (bespoke hit, fall-through, dangling socket id).
… skip Audit item #13 — cardinal rule violation. The tab builder and the deployment-target card both branched on hardcoded iceType strings: - build-visible-tabs.ts:41-46 — config tab visible for 4 iceTypes - build-visible-tabs.ts:53 — domain tab visible for 2 iceTypes - build-visible-tabs.ts:56 — source tab visible for Source.Repository - node-properties-section.tsx:166 — deployment target hidden for 2 iceTypes Consolidated into a single declarative table `BLOCK_PROPERTY_PANEL_CONFIGS` keyed by iceType, with per-block `forceTabs` and `skipDeploymentTarget` flags. Both the builder and the panel iterate this table generically — no iceType branches remain. Adding a bespoke panel experience adds an entry; both call sites pick it up. Per-tab SECTION rendering (CustomDomainPanel, EnvVarsEditor, etc.) inside the panel body is still iceType-conditioned — covered by audit item #14 in the next commit, which extends this same config table.
…ON_COMPONENTS Audit item #14 — cardinal rule violation. The panel body had six hardcoded `iceType === 'X'` branches choosing which bespoke section component to render inside which tab: - domain tab: PublicEndpointDomainSection / CustomDomainPanel - config tab: EnvVarsEditor / CustomDomainPanel / PrivateNetworkPanel / MonitoringLogSection - source tab: SourceRepositorySection - config tab fallback: SourceRepositorySection when no source tab Extended BLOCK_PROPERTY_PANEL_CONFIGS with a `sections: Record<TabId, SectionId[]>` field. Each iceType declares which sections render under which tabs; the new SECTION_COMPONENTS factory map renders them. `renderSectionsForTab(iceType, tab, ctx)` is the generic dispatcher the JSX calls — no iceType branches remain. Dropped the dead `visibleTabs.length <= 1 && iceType === 'Source. Repository'` config-tab fallback: with the source tab now always forced for that block via forceTabs, the fallback could never fire. Tests: per-iceType set + section-id-tab validity check.
…_NODE_SIZING table Audit item #16 — cardinal rule violation. computeNodeSizes had three hardcoded iceType checks driving the width/height/fold dispatch: - isCustomDomain = iceType === 'Network.CustomDomain' - isPrivateNetwork = isPrivateNetworkIce(iceType) - isCronJob = iceType === 'Compute.CronJob' Plus 4 nested ternaries threading those flags through width, height, expandedHeight, and visualHeight. Consolidated into BESPOKE_NODE_SIZING — a Record<iceType, BespokeSizingEntry> where each entry owns its width function, height function, and an `alwaysExpanded` flag that opts the block out of folding (so dynamic content like Custom Domain route slots can't collapse to a pill). The dispatcher does a single table lookup, falls through to the compact-node helpers when no bespoke entry exists, and respects `alwaysExpanded` uniformly. No iceType branches remain. Tests: locked-in entries list + alwaysExpanded invariant.
… classification Audit items #5 + #6 — cardinal rule violations. Three hardcoded iceType checks in cross-cutting classifier code: - edge-classifier.ts:65 — `parent.data?.iceType === 'Network.PrivateNetwork'` in the ancestor walk - edge-classifier.ts:90 — guard: `iceType !== 'Network.CustomDomain'` - edge-classifier.ts:93 — `parent.iceType !== 'Network.PrivateNetwork'` in the standalone-mode check Introduced `BLOCK_DEPLOY_CLASSIFIERS` — a per-iceType flag table with two flags: - `isolatesNetworkContext`: this iceType is a network-isolation container (services nested inside should be internal-only) - `metadataOnlyWhenStandalone`: this iceType has two deploy modes based on parent context (metadata-only standalone vs. deployable when nested in an isolation container) Renamed predicates to match the generic shape: - `hasPrivateNetworkAncestor` → `hasNetworkIsolatingAncestor` - `isCustomDomainStandalone` → `isStandaloneMetadataOnly` Old names kept as `@deprecated` aliases so external callers and tests don't break. Card-translator call sites switched to the new names. Adding a new isolation container or a new standalone/nested block adds a table entry; classifier code stays unchanged.
…in propagation Audit items #7 + #8 — cardinal rule violations in two passes: - pass-1-5-endpoint-wiring.ts:107-114 — inline `isEndpointIceType` branched on Network.PublicEndpoint AND Network.CustomDomain (with nested-in-PrivateNetwork check) to identify ingress endpoints - pass-1-45-domain-propagation.ts:55-58 — hardcoded srcIce / dstIce === 'Network.CustomDomain' to route domain propagation Extended BLOCK_DEPLOY_CLASSIFIERS with two new flags: - `publicIngressMode`: 'always' (PublicEndpoint) or 'when-nested-in-isolated-network' (CustomDomain — only counts as ingress when nested inside an isolatesNetworkContext container) - `isDomainPropagator`: true for CustomDomain — generic name so any future domain-source block can flow through the same pass Added generic `isPublicIngressNode(node, allNodes)` predicate to edge-classifier that reads the flag table. Refactored pass-1-5 to call it; pass-1-45 reads `isDomainPropagator` directly. No iceType strings remain in either pass. Tests: 3 new flag assertions + 5 new isPublicIngressNode behaviour cases (always-mode, standalone CD, nested-in-isolated-network, nested- in-non-isolation, plain compute).
… SECURITY_ROLES table Audit item #15 — cardinal rule violation. The pre-deploy security scanner had 9+ tiny iceType-comparing classifier functions (`isDatabase`, `isStorage`, `isGateway`, `isService`, `isAuth`, `isSecret`, `isMonitoring`, `isVpc`, `isSubnet`, `isPrivateNetwork`, `isVpcLike`), each inlining its own `iceType === 'X'` check. Consolidated into a schema-shaped role table: - `SECURITY_ROLES_BY_ICE_TYPE`: per-iceType role list - `SECURITY_ROLES_BY_PREFIX`: category-prefix inheritance (Database./Compute./Monitoring.* automatically pick up their role without per-iceType table edits) - `hasSecurityRole(iceType, role)`: the single lookup function the classifier readers call Classifier functions remain as thin one-line role readers; rule evaluation code is unchanged. New blocks that need a security role add an entry to the table. Split the previous `isVpcLike` into two distinct roles: `isolatesNestedChildren` (VPC + Subnet + PrivateNetwork — used by the ancestor check) and `topLevelNetworkBoundary` (VPC + PrivateNetwork only — used by Rule 6, where a Subnet at the canvas root doesn't isolate anything). The original code conflated these into a single overloaded helper.
…Type classifiers via shared @ice/constants table Audit items #9 + #10 — cardinal rule violations across two packages. Both `@ice/types/connection-rules/predicates.ts` and `@ice/core/compute/propagation-rules.ts` had ~15 identical-ish classifier functions (isBackend, isFrontend, isDatabase, isCache, isStorage, isQueue, isSecrets, isCustomDomain, …), each duplicating the same regex + prefix + exact-match bodies. The propagation-rules copy carried a comment apologising for the duplication ("Minimal copies of the classifiers from @ice/types/connection-rules. Kept local to avoid cross-package moduleResolution conflicts."). Introduced `@ice/constants/block-classifiers.ts` as the single source of truth — a three-tier role table: - `BLOCK_ROLES_BY_ICE_TYPE`: exact iceType → roles - `BLOCK_ROLES_BY_PREFIX`: category prefix → role (Compute.*, Database.*, Storage.*, Messaging.*, Monitoring.*, Log.*) - `BLOCK_ROLES_BY_REGEX`: legacy provider-specific iceTypes (PostgreSQL/Redis/Bucket/Worker/…) authored under varied namespaces `hasBlockRole(t, role)` queries all three tiers. Both packages import it — `predicates.ts` and `propagation-rules.ts` predicate bodies are now one-line lookups, no iceType strings remain in classifier code. Adding a new role/iceType binding edits ONE table; both connection- rules and propagation-rules pick it up automatically. Tests: full equivalence preserved (4664 tests pass) + new block-classifiers test suite covering exact / prefix / regex / composite / negative cases + table integrity.
…ackend role Audit items #17 + #18 — duplicated static set in two cross-cutting locations: - edge-classifier.ts:34-40 — exported SERVICE_BACKEND_ICE_TYPES_FOR_INGRESS - pass-1-5-endpoint-wiring.ts:188-194 — local SERVICE_BACKEND_ICE_TYPES Both held the same 5 iceTypes (Compute.Container/BackendAPI/SSRSite/ Worker/ServerlessFunction). Two copies, two sources of drift. Added a new `serviceBackend` role to the shared classifier table in @ice/constants. The 5 iceTypes register the role via BLOCK_ROLES_BY_ICE_TYPE — Compute.StaticSite is intentionally excluded (compiles to backendBucket via Firebase Hosting, not a NEG). - edge-classifier exports SERVICE_BACKEND_ICE_TYPES_FOR_INGRESS as a thin materialisation derived from the role table (kept for callers that want `.has(t)` membership). - pass-1-5-endpoint-wiring.ts now uses `hasBlockRole(t, 'serviceBackend')` directly; its inline duplicate set is gone. New iceTypes register the role in ONE table; both consumers pick it up.
…type-map + override table Audit items #1 + #2 — two critical violations in the otherwise- provider-agnostic translator: - card-translator.ts:227 — `ice_type === 'Network.CustomDomain' ? 'gcp.compute.globalForwardingRule' : type_map[ice_type]` (iceType AND GCP-specific in one cross-cutting line) - card-translator.ts:313-322 — `if (gcp_type === 'gcp.run.service') ... else if (gcp_type === 'aws.ecs.service') ... else if (gcp_type === 'azure.containerapp.containerApp') ...` cascade applying provider-specific internal-mode mutations inline #1: Added Network.CustomDomain to each provider's type-map (mirrors PublicEndpoint — the nested CD acts as the network's gateway and compiles to the same ingress chain on every provider; standalone CDs are filtered earlier by isStandaloneMetadataOnly). Translator now does a plain `type_map[ice_type]` lookup — no iceType branches. #2: Introduced `internal-ingress-overrides.ts` — a per-provider mutator table keyed by resolved resource type: - gcp.run.service → ingress=internal-and-cloud-load-balancing, allow_unauthenticated=false - aws.ecs.service → assign_public_ip=false, internal=true - azure.containerapp.containerApp → ingress_external=false Translator calls `applyInternalIngressOverride(resource_type, props)` generically. The `SERVICE_BACKEND_ICE_TYPES_FOR_INGRESS.has(t)` set membership is replaced with `hasBlockRole(t, 'serviceBackend')` per the earlier dedup commit. No provider strings or iceType strings appear in the translator's body. New providers add an override entry; the translator stays unchanged. Tests: existing type-map entry counts bumped by 1 + new CustomDomain mapping assertions per provider + new internal-ingress-overrides suite (table contents + per-provider behaviour + no-op fallthrough).
…tor + endpoint pass Audit items #3 + #4 — last two cardinal-rule violations: - pass-1-5-endpoint-wiring.ts:204 — `if (be.targetIceType === 'Compute.StaticSite')` skipped LB wiring for static-site backends (GCP-Firebase-Hosting-specific behaviour) - extractors/network.ts:25 — `iceType === 'Compute.StaticSite'` flipped a Storage.Bucket extractor's `public_access` + `website_hosting` when the bucket backs a static site Two schema-shaped declarations replace them, each scoped to its natural layer: #3 → `self-serving-resources.ts`: a new `SELF_SERVING_PUBLIC_RESOURCES` set keyed by RESOLVED PROVIDER RESOURCE TYPE (gcp.firebase.hosting today; future: aws.amplify.app, azure.staticwebapps.staticSite). The endpoint-wiring pass reads `targetGraphNode.type` and calls `isSelfServingPublicResource(...)` — no iceType strings. #4 → New `publicWebsiteSource` role on the shared `BLOCK_ROLES_BY_ICE_TYPE` table. Compute.StaticSite registers this role; the storage extractor reads it via `hasBlockRole(iceType, 'publicWebsiteSource')`. Adding a future static-site-style block (Compute.JamstackSite, …) adds one table entry — extractor stays unchanged. Tests: - existing pass-1-5 fixtures updated to mark static-site graph nodes with `type: 'gcp.firebase.hosting'` (the resolved type the new check reads); behavioural assertions unchanged - new self-serving-resources test suite covering registered + negative cases With this commit, every audit item in the schema-driven-refactor punch list is shipped.
Phase 0 of the AWS deploy buildout. The previous 496-LOC monolithic
aws-deployer.ts is replaced with the same dispatch shape the GCP
deployer uses:
providers/aws/
aws-deployer.ts — thin dispatcher + HANDLER_REGISTRY map
types.ts — AWSHandlerContext + AWSResourceHandler
sdk-loader.ts — load_aws_sdk + initialize_aws_clients + destroy
index.ts — barrel
handlers/
ec2.ts — migrated from aws-deployer.ts (no behaviour change)
s3.ts — migrated (account-id suffix arrives in commit #8)
lambda.ts — migrated (S3-ref only; auto-build in commit #28)
The old `providers/aws-deployer.ts` becomes a re-export shim so the
existing import paths in `providers/index.ts` and the test suite keep
resolving without edits.
Cardinal-rule schema-driven: HANDLER_REGISTRY is the single declarative
fact for "which handler runs for which resource type". The dispatcher
iterates it generically; no `if (type === 'aws.X')` branches. Adding
the next ~17 AWS services is now register-an-entry + drop-a-file.
Behaviour preserved verbatim — all 64 existing AWS deployer tests pass
unchanged (one minor wording fix in the dispatcher to match the
original "Unsupported resource type for creation/update/deletion"
phrasing the test suite pins).
…n, events.rule)
Phase 1 commit 1/5 — first AWS extractor module.
extractors/aws/compute.ts (new):
- extract_ecs_service_properties ← Compute.Container, Compute.BackendAPI,
Compute.SSRSite, Compute.Worker
- extract_lambda_function_properties ← Compute.ServerlessFunction
- extract_events_rule_properties ← Compute.CronJob
extractors/dispatch.ts:
Register the 3 extractors under their resolved aws.* resource types.
Adds the first AWS section to PROPERTY_EXTRACTORS.
Provider parity notes (extractor only — handlers come later):
- ECS multi-port uses the shared parse_exposed_ports() so the canvas
contract matches what Cloud Run sees today.
- Lambda accepts the nested code.{s3Bucket,s3Key} shape AND falls
through to the flat s3_bucket / s3_key fields for back-compat with
the existing Lambda test harness. Auto-build from Source.Repository
lands in commit #28.
- EventBridge cron is the 6-field format (not unix 5-field). The
named "daily"/"hourly"/"weekly"/"monthly" presets that GCP Cloud
Scheduler accepts are normalised to the AWS expression so cards
stay provider-portable.
Tests: 24 new assertions covering defaults, passthrough, exposed_ports
parsing, code-source shape variants, cron preset normalisation. The
dispatch table test gets a new shape — counts GCP entries (27)
separately from AWS (>=3, will grow with commits #3–#6), accepts
aws.* keys in the {provider}.{service}.{kind} regex.
…, docdb)
Phase 1 commit 2/5.
extractors/aws/database.ts (new):
- extract_rds_db_instance_properties ← Database.PostgreSQL, MySQL
- extract_dynamodb_table_properties ← Database.DynamoDB
- extract_elasticache_cluster_properties ← Database.Redis
- extract_docdb_cluster_properties ← Database.MongoDB
extractors/dispatch.ts: register all 4 under aws.* resource types.
Provider parity:
- RDS engine + version inferred from iceType + runtime string, same
rule the GCP Cloud SQL extractor uses → cards stay portable.
- master_user_password defaults to '' so the handler fails loudly
rather than provisioning RDS / DocDB with no real credential.
- ElastiCache exposes ELASTICACHE_REDIS_SIZE_MAP for the canvas
M-series enum (M1 → cache.t3.micro, M5 → cache.m5.xlarge ×2 for HA
parity with GCP STANDARD_HA).
- DynamoDB defaults to PAY_PER_REQUEST (AWS-recommended for new
workloads); PROVISIONED branch emits RCU/WCU only when set.
15 new test assertions across the four resources covering engine
detection, version extraction, size-enum translation, billing-mode
branching, and password defaults.
… elbv2)
Phase 1 commit 3/5.
extractors/aws/network.ts (new):
- extract_s3_bucket_properties ← Storage.Bucket / Storage.ObjectStorage / Compute.StaticSite
- extract_api_gateway_rest_api_properties ← Network.Gateway
- extract_cloudfront_distribution_properties ← Network.PublicEndpoint / Network.CustomDomain
- extract_elbv2_load_balancer_properties ← Network.LoadBalancer
extractors/dispatch.ts: register all 4 under aws.* resource types.
Cross-provider parity:
- S3 reads the publicWebsiteSource role from the shared block-
classifier table; same flip-policy the GCP cloud_storage extractor
uses for Compute.StaticSite. Plain Storage.Bucket stays private.
- CloudFront defaults to HTTPS + auto-cert + PriceClass_100 (most-
common cost-aware preset). Cert provisioning in us-east-1 is the
handler's job in commit #19.
- ELBv2 defaults to internet-facing ALB on HTTPS:443; flips to
`internal` scheme when `internal: true` (parity with the
INTERNAL_INGRESS_OVERRIDES table semantics).
11 new tests + dispatch table assertion updated (the previous
"aws.s3.bucket is intentionally absent" assertion is replaced with
a generic "aws.unknown.thing" check now that S3 has landed).
…s, cw-logs)
Phase 1 commit 4/5.
extractors/aws/ancillary.ts (new):
- extract_sqs_queue_properties ← Messaging.Queue
- extract_sns_topic_properties ← Messaging.Topic / CloudPubSub
- extract_cognito_user_pool_properties ← Security.Identity
- extract_secrets_manager_secret_properties ← Security.Secret
- extract_cloudwatch_log_group_properties ← Monitoring.Log
extractors/dispatch.ts: register all 5 under aws.* resource types.
Notable:
- SQS content_based_deduplication is only emitted on FIFO queues
(AWS rejects the field on standard SQS).
- Secrets Manager extractor forwards data.secrets as `bindings` —
same shape the schema-declared deploy-expansion pass already uses
for GCP Secret Manager. Adding AWS doesn't require translator
changes; the same expansion branch fires.
- Cognito reads both signInProviders (canvas camelCase) and
sign_in_providers (snake) so projects authored with either work.
14 new test assertions.
…agemaker, redshift)
Phase 1 commit 5/5 — last extractor module. Every aws.* resource type
in AWS_TYPE_MAP now has a registered property extractor.
extractors/aws/ai.ts (new):
- extract_opensearch_domain_properties ← AI.VectorDB
- extract_bedrock_endpoint_properties ← AI.LLMGateway
- extract_sagemaker_endpoint_properties ← AI.ModelServing
- extract_redshift_cluster_properties ← Analytics.DataWarehouse
extractors/dispatch.ts: register all 4 under aws.* resource types.
Notable defaults:
- OpenSearch starts cost-conscious: single t3.small.search node with
encryption-at-rest + node-to-node encryption on. Production users
flip dedicated_master_enabled + bump instance_count ≥ 3.
- Bedrock defaults to on-demand Claude 3 Haiku (zero provisioned
model units → handler emits no resource). Provisioned throughput
fires only when model_units > 0.
- SageMaker defaults to a real-time ml.t2.medium endpoint.
- Redshift defaults to a single-node dc2.large with the no-default-
password invariant the RDS + DocDB extractors share.
12 new test assertions. With this commit Phase 1 is complete:
PROPERTY_EXTRACTORS table now has 27 GCP + 20 AWS entries.
…-role helper
Phase 2 commit 1 — the shared helpers later handlers depend on.
providers/aws/account.ts (new):
- create_account_id_resolver(region): memoised STS GetCallerIdentity
caller. First call hits STS, subsequent calls return cached value.
Concurrent first-calls coalesce into one STS request. Throws a
clear "install @aws-sdk/client-sts" message when SDK is absent.
providers/aws/iam-roles.ts (new):
- ensureManagedRole(region, roleName, trustPolicyJson, managedPolicyArn):
idempotent GetRole → CreateRole-on-NoSuchEntity → AttachRolePolicy
pattern. Returns the role ARN. Tolerates already-attached
policies (AlreadyExists swallowed; any other error fatal).
- ensureEcsTaskExecutionRole(region): convenience wrapper for the
standard Fargate execution role (consumed by the ECS handler in
commit #23).
providers/aws/types.ts:
AWSHandlerContext gains `ensure_account_id: AccountIdResolver` —
handlers `await ctx.ensure_account_id()` to get the cached id.
providers/aws/aws-deployer.ts:
initialize() wires the resolver into the context. A pre-init stub
throws "called before initialize()" if a handler tries to use it
out of band.
providers/aws/index.ts: re-export the new helpers.
Tests: 9 new — memoisation, concurrent-call coalescing, missing-SDK
error path, missing-Account-field error path, ensureManagedRole
happy-path + create-on-miss + IAM-SDK-missing path.
…et policy Handler #8 in Phase 2. Two upgrades over the Phase 0 baseline: 1. **Account-id suffix.** S3 bucket names are globally unique across all AWS accounts. The handler awaits ctx.ensure_account_id() and appends `-{accountId}` to the translator's resource name before any SDK call. `ice-myapp-bucket` becomes `ice-myapp-bucket-111122223333`, eliminating the global-collision class. The provider_id ARN carries the post-suffix name so update + delete round-trip cleanly (bucket_name_from_arn parses it back out). 2. **publicWebsite policy.** When the extractor sets `public_access` + `website_hosting` (today only Compute.StaticSite triggers this via the publicWebsiteSource role from the shared classifier table), the handler runs a 4-step create: CreateBucket → PutPublicAccessBlock (loosen account-default block) → PutBucketPolicy (attach the public-read policy) → PutBucketWebsite (set index/404 pages) Plain Storage.Bucket skips all three follow-up commands. Tests: - Existing test harness extended with a makeStsModule mock + a FAKE_ACCOUNT_ID constant; the makeFullRegistry now installs STS alongside the SDK clients. - All existing S3 ARN assertions updated to expect the suffixed form. - 3 new tests: account-id suffix lock-in, public-website 4-step sequence with policy + website config, plain-bucket negative path. 64 → 67 AWS deployer tests passing.
…ation Handler #9 in Phase 2. Hardens the existing Lambda S3-ref handler with two pre-create validations that turn cryptic AWS API errors into clear messages: 1. **IAM role required.** The AWS SDK returns "Could not find resource ..." when CreateFunction is called with an empty Role ARN. The handler now refuses up front with: "Lambda function requires an IAM execution role ARN (properties.role). Wire one in or use the auto-role helper." 2. **Code source required.** When neither s3_bucket + s3_key NOR a base64 zip_file is supplied, the handler refuses with: "Lambda function code source is missing. Provide properties.code.{s3Bucket,s3Key} or zip_file (auto-build from Source.Repository lands in a later commit)." Both checks fire before any SDK call, so the failure surfaces in the deployer's `error` field with full context instead of as an opaque AWS error. Tests updated so happy-path Lambda create tests now pass both role and code source. 2 new tests pin the fail-fast paths.
Handler #10 in Phase 2. providers/aws/handlers/cloudwatch-logs.ts (new): - aws.cloudwatch.logGroup handler — CreateLogGroup + PutRetentionPolicy (when retention_in_days set) on create. PutRetentionPolicy on update. DeleteLogGroup on delete. providers/aws/handlers/_result.ts (new): - ok / err / sdkMissing helpers shared across all AWS handlers. Stops the per-handler result/fail boilerplate copy-paste. providers/aws/sdk-loader.ts: load @aws-sdk/client-cloudwatch-logs under the 'cloudwatch-logs' client key. providers/aws/aws-deployer.ts: register cloudwatch_logs_handler in HANDLER_REGISTRY. Tests: 3 new — create-with-retention, create-without-retention skips PutRetentionPolicy, delete sequence. Test harness extended with makeCloudWatchLogsModule + corresponding FakeImportRegistry entry.
Handler #11 in Phase 2. providers/aws/handlers/secrets-manager.ts (new): - aws.secretsmanager.secret handler. Mirrors the GCP Secret Manager contract: the schema-declared deploy-expansion pass emits one Secret per binding row; this handler just creates / updates / deletes ONE. Values are NOT written (operators populate via AWS console/CLI — same security tradeoff as GCP). - delete uses ForceDeleteWithoutRecovery=true (skips the 30-day recovery window — appropriate when ICE removes the binding). providers/aws/sdk-loader.ts: load @aws-sdk/client-secrets-manager under the 'secrets-manager' client key. providers/aws/aws-deployer.ts: register secrets_manager_handler. providers/__tests__/_aws-test-harness.ts (new): Extracts the Function-constructor stub + generic SDK-mock factory out of the original aws-deployer.test.ts so per-handler test files stay small. Strips the trailing 'Command' from command class names when building the __cmd label so assertions read the operation name (`CreateSecret`, not `CreateSecretCommand`). providers/__tests__/aws-secrets-manager.test.ts (new): 4 focused tests — create returns the SDK ARN, update + delete sequences, SDK-not-installed path.
…Queue, FIFO .fifo suffix Handler #12 in Phase 2. Standard + FIFO queues. FIFO queues get the .fifo suffix appended to the name automatically (AWS enforces). 3 focused tests.
…Topic, FIFO .fifo suffix
…ng poll Handler #16 in Phase 2. CreateDBInstance + 20-min status-poll loop that respects ctx.abort_signal and reports progress via on_step. Refuses to create when master_user_password is empty (parity with the extractor's no-default-password invariant).
…istribution Handler #19. CloudFront requires ACM certs in us-east-1 regardless of deploy region; the handler spins up a one-shot ACM client pinned to us-east-1 for RequestCertificate, then attaches the ARN to the distribution's ViewerCertificate. Falls back to CloudFrontDefaultCertificate when ACM SDK is absent.
…eate Handler #23. Compute.Container 'just works' on AWS — the handler idempotently bootstraps ecsTaskExecutionRole + ice-default-cluster before RegisterTaskDefinition + CreateService. Mirrors the GCP Cloud Run UX (no cluster to think about).
…encryption config
Phase 3 (commit #28). When a Compute.ServerlessFunction block has a connected Source.Repository AND no explicit S3 ref, the handler auto-builds the zip and uploads it before CreateFunction: 1. git clone --depth 1 --branch <branch> <repo> 2. npm install --omit=dev (skipped if no package.json) 3. zip -qr function.zip . 4. PutObject to ice-bootstrap-{accountId}-{region}/lambda/{name}/{ts}.zip (HeadBucket → CreateBucket if absent) 5. Stamp s3_bucket + s3_key onto properties and continue. Local-only — assumes git/npm/zip on the deploy host. AWS CodeBuild integration deferred to a future commit. Existing manual S3-ref + zip paths are unchanged; the auto-build branch only fires when `properties.repository` is set AND no explicit code source exists.
Phase 4 commit #29. With every aws.* resource type registered in PROPERTY_EXTRACTORS (commits #2–#6), the AWS Type Map test block can finally turn on. Expanded the iceType matrix from 5 to 19 entries covering every AWS-mapped block. New end-to-end test wires Compute.StaticSite + Security.Secret (with two bindings — exercising the schema-declared deploy-expansion pass) + Database.PostgreSQL into a single translator call, asserts the resulting graph has 4 deployables resolving to s3.bucket / secretsmanager.secret×2 / rds.dbInstance. The Azure block remains skipped (deferred to a future Azure handler buildout).
Phase 4 commit #30 — final commit of the AWS buildout. providers/aws/README.md documents the AWS-specific decisions the 30 commits in this series bake in: - architecture (mirrors gcp/, schema-driven HANDLER_REGISTRY) - S3 account-id suffix - CloudFront us-east-1 cert - ECS auto-cluster + task role - RDS / DocDB / Redshift no-default-password invariant - RDS provisioning poll - Lambda auto-build flow (git + npm + zip + bootstrap S3) - Bedrock on-demand no-op - Secrets Manager values-never-written contract - SQS / SNS .fifo suffix - SDK packages as optional peer deps - test harness layout - deferred work (VPC blocks, CodeBuild, drift detection, LocalStack) Read this before changing any AWS handler.
Flip PROVIDER_FLAGS.aws.enabled to true with a hand-picked category map (Storage, Messaging, Cache, Monitoring, Security, Source, Config). Compute / Frontend / Scheduler / Network / Database / AI / Analytics stay gated until their concrete unblockers land — ECS VPC blocks, CloudFront cert-validation flow, update-paths, etc. README.md gets a Rollout state table documenting why each gated category is held back and what unblocks it. Integrity test in packages/constants asserts the per-category map stays exhaustive, so future CategoryId additions force a deliberate on/off decision here.
Replace the project-provider lock on the palette provider dropdown with an availability check: a provider option is selectable iff at least one concept has it in providers and its category is enabled for that provider. Before: in a GCP project, AWS was greyed in the palette dropdown even after AWS feature-flag enabled — so users couldn't browse the AWS catalog from a GCP project. After: AWS opens as long as it has any available block under the current PROVIDER_FLAGS — drag-into-project compatibility remains enforced at the canvas-drop layer. availableProviderIds is derived in resource-palette.tsx from the unfiltered component list using isCategoryEnabledForProvider — same schema-driven gate the component filter already uses.
New page docs/architecture/connections-to-cloud.md walks the five-layer pipeline (connection-rules → propagation → type-maps → extractors → handlers) and grounds it with two worked GCP examples: - Storage.Bucket → Compute.BackendAPI: env-var injection + IAM binding, no edge resource in GCP. - Compute.CronJob → Compute.BackendAPI: Cloud Scheduler HTTP target + run.invoker IAM binding. Links the new page from architecture/README.md and the existing core-engine.md "Computing flows" section so readers landing on either find their way to the deep dive.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Apply the schema-driven cardinal rule across canvas/properties/deploy, stand up an AWS deploy path mirroring the GCP layout, flip a hand-picked AWS category set on via feature flags, and unblock workspace typecheck.
Closes #
What changed
iceType === 'X'branches into declarative tables.providers/aws/mirrors GCP: dispatcher, lazy SDK loader, STS account-id resolver, IAM helper, 17 handlers (S3 account-id suffix, Lambda auto-build, RDS poll, ECS auto-cluster, CloudFront us-east-1 cert, SQS/SNS FIFO, …), 20 extractors, mocked-SDK test harness.PROVIDER_FLAGS.aws.enabled = truewith safe categories on (Storage/Messaging/Cache/Monitoring/Security/Source/Config) and risky ones held back; rollout table inproviders/aws/README.md.docs/architecture/connections-to-cloud.mdwalks the edge → propagation → type-maps → extractors → handlers pipeline with GCP examples; AWS provider README documents every quirk.How I tested
pnpm typecheck— all 22 projects cleanpnpm test:unit— 18,070 passing, 8 deliberately skippedpnpm lint:checkpnpm format:checkpnpm dev:all/pnpm dev:desktopScreenshots / recordings
Snap fix is repro-by-eye: drag a connection to any Custom Domain row; before the line tail drifts further off with each row index, after it stays anchored.
Notes for reviewers
driven refactor + snap fix + docs, (2) AWS build-out (additive, gated off), (3) feature-flag flip + palette fix + typecheck sweep.
iceType === '...'branches in cross-cutting code; per-provider behaviour lives in per-provider files only.git/npm/zip.