Skip to content

chore(ci)(deps): bump pnpm/action-setup from 4 to 6#8

Closed
dependabot[bot] wants to merge 1 commit into
mainfrom
dependabot/github_actions/pnpm/action-setup-6
Closed

chore(ci)(deps): bump pnpm/action-setup from 4 to 6#8
dependabot[bot] wants to merge 1 commit into
mainfrom
dependabot/github_actions/pnpm/action-setup-6

Conversation

@dependabot
Copy link
Copy Markdown

@dependabot dependabot Bot commented on behalf of github May 19, 2026

Bumps pnpm/action-setup from 4 to 6.

Release notes

Sourced from pnpm/action-setup's releases.

v6.0.0

Added support for pnpm v11.

v5.0.0

Updated the action to use Node.js 24.

v4.4.0

Updated the action to use Node.js 24.

v4.3.0

What's Changed

New Contributors

Full Changelog: pnpm/action-setup@v4.2.0...v4.3.0

v4.2.0

When there's a .npmrc file at the root of the repository, pnpm will be fetched from the registry that is specified in that .npmrc file #179

v4.1.0

Add support for package.yaml #156.

Commits
  • 0e279bb fix: update pnpm to 11.1.1 (#248)
  • 3e83581 fix: drop patchPnpmEnv so standalone+self-update works on Windows (#258)
  • 551b42e docs(README): fix cache_dependency_path type (#257)
  • 739bfe4 fix: self-update bootstrap to packageManager-pinned version (#233) (#256)
  • f61705d chore: add CODEOWNERS
  • 7a5507b fix: restore inputs from state in post (#255)
  • 1155470 fix: honor devEngines.packageManager.onFail=error (#252) (#254)
  • 91ab88e fix: bin_dest output points to self-updated pnpm, not bootstrap (#249)
  • e578e19 fix: update pnpm to 11.0.4
  • 8912a91 fix: append (not prepend) action node dir to PATH for npm bootstrap (#241)
  • Additional commits viewable in compare view

Dependabot compatibility score

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


Dependabot commands and options

You can trigger Dependabot actions by commenting on this PR:

  • @dependabot rebase will rebase this PR
  • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
  • @dependabot show <dependency name> ignore conditions will show all of the ignore conditions of the specified dependency
  • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
  • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
  • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)

Bumps [pnpm/action-setup](https://github.com/pnpm/action-setup) from 4 to 6.
- [Release notes](https://github.com/pnpm/action-setup/releases)
- [Commits](pnpm/action-setup@v4...v6)

---
updated-dependencies:
- dependency-name: pnpm/action-setup
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
@dependabot dependabot Bot added dependencies Pull requests that update a dependency file github_actions Pull requests that update GitHub Actions code labels May 19, 2026
@julia-kafarska julia-kafarska deleted the dependabot/github_actions/pnpm/action-setup-6 branch May 19, 2026 21:47
@dependabot @github
Copy link
Copy Markdown
Author

dependabot Bot commented on behalf of github May 19, 2026

OK, I won't notify you again about this release, but will get in touch when a new version is available. If you'd rather skip all updates until the next major or minor version, let me know by commenting @dependabot ignore this major version or @dependabot ignore this minor version. You can also ignore all major, minor, or patch releases for a dependency by adding an ignore condition with the desired update_types to your config file.

If you change your mind, just re-open this PR and I'll resolve any conflicts on it.

julia-kafarska added a commit that referenced this pull request May 20, 2026
Six useEffect blocks (install-inspector + update-inspector + auto-organize + logCanvasRender + overlay-dismiss-reset + overlay-dismiss-on-AI) consolidate into packages/ui/src/features/canvas/hooks/use-canvas-side-effects.ts. The autoOrganizeCard threshold > 10 (blueprint risk #7) preserved. The setOverlayDismissed read-but-never-read-back setter (blueprint risk #8) preserved verbatim.

No behavior change.
julia-kafarska added a commit that referenced this pull request May 20, 2026
Move `deleteCardNode`, `deleteCardEdge`, and `addToActiveCard` to
`cards/reducers/node-delete-merge.ts`. The orchestrator spreads
`nodeDeleteMergeReducers` into `createSlice`'s `reducers` block so RTK
keeps owning the action type strings.

Pins the two load-bearing behaviors in tests: `deleteCardNode` removes
the node AND every incident edge in a single reducer body (RISK #1 — no
intermediate frame), and `addToActiveCard` runs `migrateCardNodes`
BEFORE the offset transform (RISK #8 — a `Monitoring.Terminal` payload
lands on the canvas with both the migrated iceType and the offset
coordinates applied).

17 tests, 100% statements/branches/funcs/lines coverage on the new
module. Full @ice/ui suite: 2439 tests pass.
julia-kafarska added a commit that referenced this pull request May 20, 2026
Move `importToActiveCard` (the cloud-restore / context-menu "import"
ingestion path) into `cards/reducers/import.ts`, spread into the
orchestrator's `createSlice`. The two-phase mutation (replace
nodes/edges, then run autoLayout + applyEdgeRoutes) stays inside one
reducer body — RISK #3 pin: applyEdgeRoutes runs AFTER node remapping
so routePoints align with post-layout positions. skipAutoOrganize=true
short-circuits the layout path entirely. Migration runs first on the
incoming payload (RISK #8 ingestion-path migration parity).

13 tests at 100% coverage; autoLayout mocked via a let-closure pattern
so each test seeds its own `{nodes, edgeRoutes: Map}` shape.
Orchestrator drops the now-unused `CardEdge` and `migrateCardNodes`
syntactic references from local imports (re-export shims preserved).
julia-kafarska added a commit that referenced this pull request May 20, 2026
Move `scaleLayoutForZoom` and `expandBlueprintToCard` into
`cards/reducers/scale-blueprint.ts`, spread into the orchestrator's
`createSlice`. Both reducers preserved verbatim. RISK #10 pin in tests:
`scaleX = 1` / `scaleY = 1` is hard-coded — tests drive a meaningful zoom
delta (1.0 → 1.5) so a future refactor that swaps in `zoom / prevZoom`
would fail loudly with mismatched widths/heights. Centroid math still
runs at scale=1 (identity transform). RISK #8 pin: blueprint-emitted
nodes flow through `migrateCardNode` so legacy iceTypes
(`Monitoring.Terminal`, `Block.Frontend`) land migrated, with the type
flip on the `Block.*` branch covered. Orchestrator drops the now-unused
`migrateCardNode` and `ExpandedBlueprint` imports; `pushSnapshot` stays
for `groupSelectedNodes`. Test file at 100/100/100/100 coverage on the
new module; full cards/ test sweep still green at 277/277.
julia-kafarska added a commit that referenced this pull request May 20, 2026
…ser.ts

Combined the two units atomically. Splitting would require a placeholder
stub for parse_primary; the circular import between the two files
resolves at function-call time so they must land together.

- parser-binary-exprs.ts (321 LOC): 10-level expression grammar chain
  parse_expression → parse_conditional → parse_or → parse_and →
  parse_equality → parse_comparison → parse_term → parse_factor →
  parse_unary → parse_postfix.
- parser-primary.ts (354 LOC): parse_primary, parse_array_expression,
  parse_object_expression, parse_for_expression, parse_reference.
- parser.ts: 938 → 505 LOC. 15 expression methods deleted; the 7
  this.parse_expression() callsites in block-parsers rewritten to
  parse_expression(this.state).

Pinned 6 risks via 94 new tests (838 → 932 in @ice/core, all pass):
RISK #5 (parse_equality operator ternary), #6 (parse_postfix error-but-
continue), #7 (10-level precedence chain order), #8 (parse_primary
pre-advance snapshot), #9 (parse_for_expression key/value identity),
#10 (parse_reference path undefined vs []).
julia-kafarska added a commit that referenced this pull request May 20, 2026
Highest-risk unit. Pull scan_heredoc out of the Lexer class into a
sibling lexer-heredoc.ts. Imports is_alpha and is_digit from
lexer-scanners.ts (rf-lex-2). Mechanical port — no logic changes.

Pins four RISKs from the blueprint:

  RISK #7 — Terminator backtrack to check_start (NOT line_start).
            The leading indent whitespace consumed for indentation
            is permanently consumed on the failing-match path. The
            backtrack contract is about cursor position management
            (so the trailing read-until-newline doesn't double-
            consume the indent), NOT about content offsets — the
            content slice already covers the line from line_start.

  RISK #8 — content_end = line_start (start of terminator line)
            BEFORE leading-whitespace consumption. The terminating
            content.trimEnd() strips the trailing newline + indent
            in one shot. Reordering breaks both indented and
            non-indented heredocs.

  RISK #9 — EOF without a closing delimiter is SILENT — no
            ls_add_error fires. The token still emits with an
            empty literal (content_end stays at content_start
            initial value). Preserve verbatim — downstream
            consumers may rely on this shape.

  RISK #10 — Two newline-accounting sites: opening-line and
             content-loop. Both ls_advance + line++ + column=1.
             Both load-bearing.

Tests: 244 → 257 (+13). Includes a direct unit test that seeds
LexerState with cursor at the post-dispatch position, plus
integration tests through Lexer.tokenize. All four RISK pins have
dedicated assertions.
julia-kafarska added a commit that referenced this pull request May 20, 2026
Move placeholder index.html + 404.html upload logic + adopted-bucket
ACL backfill into cloud-storage/placeholder-uploader.ts. Used by both
create() and update().

uploadPlaceholders({ bucket, name, publicAccess, ublaForcedOn,
publicGrantStrategy, bucketAlreadyExisted, ctx }): Promise<string[]>

RISK #8 pinned: independent skip-if-exists guards. The exists() call
on each placeholder is .catch(() => [false]) wrapped SEPARATELY so a
failure on index.html doesn't block the 404.html check. Tests cover
the throw-on-index path and the throw-on-404 path independently.

Behavior unification: update() previously had a silent outer catch
(no warning) and missing per-call .catch guards. The unified helper
adopts create()'s defensive shape: warning push on outer error +
independent skip guards. update() callsite passes
bucketAlreadyExisted=true (true by definition for an update path).

18 new tests cover:
  - clean upload (both placeholders)
  - skip-if-exists for index, 404, both
  - RISK #8: index throw doesn't block 404; 404 throw doesn't block
    index
  - predefinedAcl flag matrix (publicRead vs undefined)
  - ACL backfill: legacy-acl + adopted + public path runs;
    iam/private/fresh paths skip
  - getFiles reject logs but doesn't warn
  - per-file acl.add reject is best-effort
  - outer catch pushes warning + coerces non-Error throws

Orchestrator drops to 376 LOC (-88 LOC vs previous unit).
julia-kafarska added a commit that referenced this pull request May 20, 2026
Findings #8: previously the idempotency-write catch did `throw err`
on a non-P2002 prisma error. Express 4 has no default async-error
handler, so the request hung until the client timed out. The bug had
been pinned by a regression test that captured `unhandledRejection`
and waited for `AbortSignal.timeout(150)` to fire — that test now
inverts to assert the 500 envelope.

Behavior is now consistent with the lower catch arm at lines 85-96:
log + 500 + error message in the body.

43 tests pass in webhooks.test.ts.
julia-kafarska added a commit that referenced this pull request May 25, 2026
* fix(canvas): correct snap target + drag-snap stickiness for Custom Domain 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.

* refactor(secret-store): schema-driven 1->N deploy expansion

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.

* refactor(canvas-renderer): drop hardcoded iceType branches via SPECIAL_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).

* refactor(canvas-path): drop hardcoded iceType in socket-position via 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).

* refactor(properties): schema-drive tab visibility + deployment-target 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.

* refactor(properties): schema-drive per-tab section dispatch via SECTION_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.

* refactor(canvas-sizing): schema-drive bespoke node sizing via BESPOKE_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.

* refactor(deploy/edge-classifier): schema-drive isolation + standalone 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.

* refactor(deploy/passes): schema-drive public-ingress detection + domain 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).

* refactor(deploy/security-rules): schema-drive iceType classifiers via 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.

* refactor(classifiers): unify connection-rules + propagation-rules iceType 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.

* refactor(deploy): dedup SERVICE_BACKEND_ICE_TYPES via shared serviceBackend 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.

* refactor(translator): drop hardcoded iceType + provider branches via 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).

* refactor(deploy): drop hardcoded Compute.StaticSite in storage extractor + 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.

* refactor(deploy/aws): modularise AWSDeployer to mirror gcp/ shape

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).

* feat(deploy/aws): extractors for compute (ecs.service, lambda.function, 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.

* feat(deploy/aws): extractors for database (rds, dynamodb, elasticache, 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.

* feat(deploy/aws): extractors for network (s3, apigateway, cloudfront, 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).

* feat(deploy/aws): extractors for ancillary (sqs, sns, cognito, secrets, 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.

* feat(deploy/aws): extractors for AI/analytics (opensearch, bedrock, sagemaker, 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.

* feat(deploy/aws): shared infra — STS account-id resolver + IAM ensure-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.

* feat(deploy/aws): s3 handler — account-id suffix + publicWebsite bucket 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.

* feat(deploy/aws): lambda handler — fail-fast role + code-source validation

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.

* feat(deploy/aws): cloudwatch-logs handler + shared _result helpers

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.

* feat(deploy/aws): secrets-manager handler + shared test harness

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.

* feat(deploy/aws): sqs handler — CreateQueue/SetQueueAttributes/DeleteQueue, 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.

* feat(deploy/aws): sns handler — CreateTopic/SetTopicAttributes/DeleteTopic, FIFO .fifo suffix

* feat(deploy/aws): dynamodb handler — CreateTable + key schema + PITR

* feat(deploy/aws): elasticache handler — single-node + replication-group paths

* feat(deploy/aws): rds handler — no-default-password gate + provisioning 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).

* feat(deploy/aws): docdb handler — cluster + per-instance creation

* feat(deploy/aws): cognito handler — user pool with password policy + MFA

* feat(deploy/aws): cloudfront handler — us-east-1 ACM cert + minimal distribution

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.

* feat(deploy/aws): elbv2 handler — LB + skeleton target group

* feat(deploy/aws): api-gateway handler — REST API + default-stage deployment

* feat(deploy/aws): events-rule handler (CronJob) — PutRule + PutTargets

* feat(deploy/aws): ecs handler — auto-cluster + task role + service create

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).

* feat(deploy/aws): opensearch handler — CreateDomain with cluster/EBS/encryption config

* feat(deploy/aws): bedrock handler — on-demand no-op + provisioned-throughput create

* feat(deploy/aws): sagemaker handler — EndpointConfig + Endpoint, requires model_name

* feat(deploy/aws): redshift handler — CreateCluster + no-default-password gate

* feat(deploy/aws): lambda auto-build from Source.Repository

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.

* test(deploy/aws): unskip AWS Type Map block + end-to-end coverage

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).

* docs(deploy/aws): provider notes — quirks, assumptions, deferred work

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.

* feat(aws): selectively enable safe categories via feature flags

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.

* fix(palette): enable provider dropdown items when any block is available

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.

* docs(architecture): explain how canvas edges become cloud infra

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.

* docs(architecture): explain how canvas edges become cloud infra
julia-kafarska added a commit that referenced this pull request May 25, 2026
* fix(canvas): correct snap target + drag-snap stickiness for Custom Domain 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.

* refactor(secret-store): schema-driven 1→N deploy expansion

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.

* refactor(canvas-renderer): drop hardcoded iceType branches via SPECIAL_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).

* refactor(canvas-path): drop hardcoded iceType in socket-position via 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).

* refactor(properties): schema-drive tab visibility + deployment-target 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.

* refactor(properties): schema-drive per-tab section dispatch via SECTION_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.

* refactor(canvas-sizing): schema-drive bespoke node sizing via BESPOKE_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.

* refactor(deploy/edge-classifier): schema-drive isolation + standalone 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.

* refactor(deploy/passes): schema-drive public-ingress detection + domain 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).

* refactor(deploy/security-rules): schema-drive iceType classifiers via 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.

* refactor(classifiers): unify connection-rules + propagation-rules iceType 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.

* refactor(deploy): dedup SERVICE_BACKEND_ICE_TYPES via shared serviceBackend 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.

* refactor(translator): drop hardcoded iceType + provider branches via 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).

* refactor(deploy): drop hardcoded Compute.StaticSite in storage extractor + 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.

* refactor(deploy/aws): modularise AWSDeployer to mirror gcp/ shape

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).

* feat(deploy/aws): extractors for compute (ecs.service, lambda.function, 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.

* feat(deploy/aws): extractors for database (rds, dynamodb, elasticache, 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.

* feat(deploy/aws): extractors for network (s3, apigateway, cloudfront, 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).

* feat(deploy/aws): extractors for ancillary (sqs, sns, cognito, secrets, 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.

* feat(deploy/aws): extractors for AI/analytics (opensearch, bedrock, sagemaker, 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.

* feat(deploy/aws): shared infra — STS account-id resolver + IAM ensure-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.

* feat(deploy/aws): s3 handler — account-id suffix + publicWebsite bucket 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.

* feat(deploy/aws): lambda handler — fail-fast role + code-source validation

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.

* feat(deploy/aws): cloudwatch-logs handler + shared _result helpers

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.

* feat(deploy/aws): secrets-manager handler + shared test harness

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.

* feat(deploy/aws): sqs handler — CreateQueue/SetQueueAttributes/DeleteQueue, 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.

* feat(deploy/aws): sns handler — CreateTopic/SetTopicAttributes/DeleteTopic, FIFO .fifo suffix

* feat(deploy/aws): dynamodb handler — CreateTable + key schema + PITR

* feat(deploy/aws): elasticache handler — single-node + replication-group paths

* feat(deploy/aws): rds handler — no-default-password gate + provisioning 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).

* feat(deploy/aws): docdb handler — cluster + per-instance creation

* feat(deploy/aws): cognito handler — user pool with password policy + MFA

* feat(deploy/aws): cloudfront handler — us-east-1 ACM cert + minimal distribution

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.

* feat(deploy/aws): elbv2 handler — LB + skeleton target group

* feat(deploy/aws): api-gateway handler — REST API + default-stage deployment

* feat(deploy/aws): events-rule handler (CronJob) — PutRule + PutTargets

* feat(deploy/aws): ecs handler — auto-cluster + task role + service create

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).

* feat(deploy/aws): opensearch handler — CreateDomain with cluster/EBS/encryption config

* feat(deploy/aws): bedrock handler — on-demand no-op + provisioned-throughput create

* feat(deploy/aws): sagemaker handler — EndpointConfig + Endpoint, requires model_name

* feat(deploy/aws): redshift handler — CreateCluster + no-default-password gate

* feat(deploy/aws): lambda auto-build from Source.Repository

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.

* test(deploy/aws): unskip AWS Type Map block + end-to-end coverage

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).

* docs(deploy/aws): provider notes — quirks, assumptions, deferred work

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.

* feat(aws): selectively enable safe categories via feature flags

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.

* fix(palette): enable provider dropdown items when any block is available

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.

* docs(architecture): explain how canvas edges become cloud infra

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.

* fix(typecheck): unblock blocks + templates + core deploy-expansion

- deploy-expansion: use get_node_by_name for name-based dedup; has_node
  expects a branded NodeId, not a plain string.
- requirements.test: vitest needs `beforeEach` in the named imports
  (was previously globalised but tsc no longer sees it).
- validate.test: annotate `.map((c) => ...)` callbacks; vitest's bare
  `ReturnType<typeof vi.spyOn>` no longer carries the called-fn signature
  so the implicit-any error fires.

* fix(typecheck): unblock packages/ui + packages/web

Sweep the workspace's pre-existing typecheck errors so the whole repo
compiles cleanly under tsc --noEmit:

- packages/constants: re-export IntegrationStatus from the barrel so
  packages/ui/src/store/slices/integrations-slice.ts resolves.
- vitest type tightening: `vi.fn(() => ...)` infers Parameters as [],
  breaking `.mock.calls[i][j]` indexing. Widen to (..._args: unknown[])
  on the mock decls that get indexed (deploy-panel, requirements-
  section, deploy-diagnosis, inline-table-view, axios-instance,
  use-cost-calculation, use-computing-flows, template-picker,
  invite-accept).
- stopPropagation event mocks: vitest no longer accepts
  `{ stopPropagation: vi.fn() } as React.MouseEvent` without an unknown
  intermediate. Sweep through compact-node / custom-domain / group-node
  / log-node / palette / inline-table tests.
- KNOWN_MOCKS includes: cast through unknown[] in canvas-context-menu
  and inline-table-view test helpers.
- block-summary-card: import CanvasNode from ../../../svg-canvas (the
  path resolved one level too shallow).
- blueprints test: import BlockBlueprint from ../types not ../../types.
- group-node + region-label test fixtures: type 'container' (the
  CanvasNode union doesn't include 'group' or 'region' yet).
- reroute-node: switch sockets prop from SocketDef[] to PortDef[] with
  role: 'any' to match TypedSockets' contract.
- use-wizard-state: add missing 'name' field on EnvironmentPreset
  fixture.
- store/index.test: cast resolveFirst to its original closure type so
  optional-call type-narrows correctly.
- use-mouse-handlers test: drop minZoom/maxZoom (not in
  UseMouseHandlersDeps).
- deploy-diagnosis: widen the diagnosis state shape; replace
  setImmediate with setTimeout(resolve, 0) — Node's setImmediate isn't
  surfaced in the test types.

* docs(architecture): explain how canvas edges become cloud infra

* docs(readme): link AWS rollout state + connections-to-cloud page

- root README: sharpen "AWS — in progress" to mention the handler /
  extractor count and link the staged-rollout table in the AWS README.
- docs/README: add connections-to-cloud to the architecture mermaid
  and to the contributors table.

* docs(aws): reflect handler buildout + staged rollout

- provider-status: AWS matrix entry now lists the 17 handlers + 20
  extractors and the per-category feature-flag state instead of the
  stale "EC2 / S3 / Lambda only" copy.
- deploying-to-aws: drop the dead reference to "provider-status.md - to
  be added" (the doc exists now), fix the broken handlers-source link
  (handlers live at packages/core/src/deploy/providers/aws/, not
  packages/providers/aws/), fix the broken architecture.md anchor.
- Add a quirks section pointing operators at the AWS README for S3
  account-id suffix, CloudFront us-east-1 cert pin, ECS auto-cluster,
  RDS password gate + provisioning poll, Lambda auto-build, FIFO
  suffix. Update the "known gaps" list to match the deferred items in
  the AWS README (VPC blocks, CodeBuild, update paths, LocalStack).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file github_actions Pull requests that update GitHub Actions code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant