refactoring#3
Closed
julia-kafarska wants to merge 680 commits into
Closed
Conversation
Pulls 11 query methods (get_schema, has_schema, query, get_categories, get_providers, get_implementation, get_native_type, get_property_schema, get_required_properties, get_computed_properties, get_stats) into schema/embedded/queries.ts as standalone functions taking the registry + a QueryCache holder for the two cached methods. Class fields `cached_stats` and `cached_providers` are unified into a single `query_cache: QueryCache` slot (initialised by `make_query_cache`). Behaviour preserved verbatim: null registry returns InternalError / []/undefined, lazy cache for providers + stats, missing terraform/pulumi sources default to 0 in stats. 24 new tests pin the helpers. embedded-schema-provider.ts: 402 -> 335 LOC.
Pulls get_dependencies, get_dependents, and get_equivalents into schema/embedded/graph-queries.ts. Each helper takes the registry as its first arg; behaviour preserved: null registry -> InternalError, max_depth forwarded to registry, results mapped through convert_resource_to_schema. The default `max_depth = 10` stays on the orchestrator class so the public API still exposes the default. 7 tests pin the new helpers. embedded-schema-provider.ts: 335 -> 324 LOC.
Pulls the on/off/emit_event trio into schema/embedded/events.ts (operating on a passed-in EventListenerMap) and the dynamic schemas/db import + project-vs-bundled DB resolution into schema/embedded/initialization.ts. The dynamic-import path moves from `'../schemas/db'` to `'../../schemas/db'` since the new file is one directory deeper. The TS2834 baseline noise simply moves with the import — total count unchanged at 29 (verified post-extraction). initialize_registry returns null gracefully when the schemas package or its `get_schema_registry` export is absent. resolve_db_path tests chdir into a tmpdir rather than mocking fs (`fs.existsSync` is non-configurable under Vitest ESM). 14 new tests for the helpers. embedded-schema-provider.ts: 324 -> 284 LOC. File 1 series complete.
…lidator Moves the public Validation* types into resource-validator-types.ts so helper modules can import them without dragging in the orchestrator class. The shim file `resource-validator.ts` re-exports every type from the new sibling file -> public API unchanged. Pulls `validate_type` and `get_type_name` (private methods of ResourceValidator) into validation/type-checker.ts as standalone pure functions. The 'object'/'map' shared check, NaN-as-number-mismatch, and 'any' fall-through behaviour are preserved verbatim. 26 tests cover every branch including unknown-type fall-through. resource-validator.ts: 543 -> 374 LOC.
Pulls `validate_constraints` (private method on ResourceValidator) into schema/validation/constraints.ts with five named helpers: check_enum, check_pattern, check_numeric_range, check_string_length, check_array_length, plus a `validate_constraints` orchestrator. Behaviour preserved verbatim: invalid regex strings silently swallowed, inclusive boundaries on min/max and min_length/max_length, canonical issue order (enum, pattern, range, length). Each helper independently testable. 25 new tests pin every branch including issue ordering. resource-validator.ts: 374 -> 258 LOC.
Pulls the recursive `validate_property` walker into schema/validation/property-validator.ts and the `to_validation_error` mapper into schema/validation/error-conversion.ts. The class methods on ResourceValidator now delegate, including the public `to_validation_error`, `is_valid`, and `validate_property_value`. The recursive walker is byte-identical (required-missing, type-mismatch, constraint, and nested object/array branches preserved); 17 new tests pin its behaviour, plus 5 for the error converter. resource-validator.ts: 258 -> 157 LOC. File 2 series complete.
…oader Pulls the four `_example.<ext>.disabled` content blocks (provider JSON, override YAML, custom resource YAML, relationships YAML) out of the inline `create_example_files` private method into named constants in schema/customization/example-files.ts. The CustomizationPaths type and subdir constants move to schema/customization/paths.ts. Customization-loader.ts re-exports CustomizationPaths from the new location so external consumers' import paths are unchanged. Behaviour preserved: each file only created when missing, content byte-identical. 9 new tests pin both the constants and the directory write-out. customization-loader.ts: 521 -> 410 LOC.
Pulls the four `validate_*_file` private methods (provider JSON, override YAML, custom resource YAML, relationships YAML) into schema/customization/file-validators.ts as standalone async functions, each returning `FileValidationResult`. The CustomizationError and ValidationWarning types move to that file too — the orchestrator re-exports them so external consumers (schema/index.ts) keep their import paths. Behaviour preserved verbatim: "Invalid JSON: ..." / "Invalid YAML: ..." prefixes on parse failures, 1-indexed relationship error messages, provider resources without properties emit warnings (not errors). 16 new tests drive each helper end-to-end via tmp file writes. customization-loader.ts: 410 -> 261 LOC.
Pulls the `scan_directory` private method into
schema/customization/scanner.ts (along with the CustomizationFile type
that the scanner produces) and the `get_base_db_path` standalone
function into schema/customization/base-db.ts. The orchestrator
re-exports CustomizationFile + delegates `get_base_db_path` so external
imports are unaffected.
Behaviour preserved verbatim including the pre-existing buggy
`require.resolve('@ice-engine/schemas/data/ice-schemas.db')` eagerness
(noted in the test file's docstring; not in scope to fix here). 7 new
tests for the scanner driving real fs reads via tmp dirs.
customization-loader.ts: 261 -> 204 LOC. File 3 series complete.
…oad) - ts2834-baseline-error-moves-with-the-import: extracting a TS2834 baseline import into a deeper subdirectory relocates the error, doesn't introduce one. - fs-existssync-is-non-configurable-under-vitest-esm: vi.spyOn fails on node:fs namespace imports; use chdir into tmpdir instead. - one-source-of-truth-for-types-in-shim-refactors: when decomposing a class file, public types live in a sibling <name>-types.ts to avoid cycles between the shim and helper modules.
Pulls six pure helpers out of state-importer.ts into a new
parsing.ts module:
- get_deployment — read PulumiDeployment from either state shape
- get_stack_info — derive {stack,project} from checkpoint or URN
- extract_name_from_urn — last-segment fallback when parse_urn fails
- is_secret_value — sentinel-UUID check for Pulumi secret wrappers
- unwrap_secret — ciphertext > plaintext > value resolver
- create_empty_metadata — unknown-sentinel PulumiImportMetadata for errors
state-importer.ts: 564 -> 481 LOC.
22 new tests in parsing.test.ts pin the byte-identical behaviour
(secret sentinel UUID, both state shapes, URN fallback path,
empty-metadata defaults). All 79 existing pulumi/terraform tests
still pass; TS2834 baseline (29) unchanged.
Pulls the resource-shape conversion out of state-importer.ts into a new resource-conversion.ts module: - process_properties — recursive property walker with secret unwrap - import_resource — PulumiResource -> PulumiImportedResource Both are pure (warnings array is mutated like before). Behaviour preserved verbatim: - URN parse + extract_name_from_urn fallback for name - outputs > inputs precedence (with NO_OUTPUTS warning fallback) - dependencies = explicit ++ parent (parent appended last, in order) - additional_secret_outputs mirrored verbatim - protect/external default false; id passes through state-importer.ts: 481 -> 387 LOC. 18 new tests pin the secret-mask path, the warning emission path, the parent-last dependency append, and the protect/external defaults. 86 total importer tests pass; TS2834 baseline (29) unchanged.
Pulls the two graph-emit functions out of state-importer.ts into a
new graph-conversion.ts module:
- import_result_to_graph — PulumiImportResult -> MutableGraph
- import_pulumi_to_graph — file-path -> MutableGraph wrapper
state-importer.ts re-exports both via barrel-style 'export {...} from'
to preserve the public surface — index.ts imports state-importer and
the existing 79 importer tests bind directly to it.
state-importer.ts: 387 -> 278 LOC (564 LOC at series start).
Behaviour preserved verbatim:
- graph-level labels (source/version/stack/project)
- per-node labels (provider, pulumi_type, optional protected/external)
- per-node provenance annotations (imported_from, pulumi_urn)
- resource.id lifted to node properties.id
- self-dependency and missing-target edges skipped
- target_graph merge mode preserves nodes only (edges already dropped)
12 new tests cover the node properties/labels/annotations, the edge
filters (self-loop + missing target), and the graph metadata. 98
total importer tests pass; TS2834 baseline (29) unchanged.
Pulls three pure helpers out of state-importer.ts into a new sensitive.ts module: - mask_sensitive_attributes — top-level walker over sensitive paths - mask_path — recursive leaf-mask mutator - create_empty_metadata — unknown-sentinel ImportMetadata for errors state-importer.ts: 547 -> 514 LOC. 15 new tests pin the path-tokenisation behaviour: - top-level / dotted / bracket-array path forms - non-object intermediates abort the walk (no error) - missing leaves and null intermediates are no-ops - empty path / empty-input early return All 79 existing pulumi/terraform tests still pass; TS2834 baseline (29) unchanged.
Pulls three functions out of state-importer.ts into a new resource-conversion.ts module: - import_resource_instance — (resource, instance) -> ImportedResource - infer_dependencies — id/arn-driven dep inference post-pass - scan_for_references — recursive walker over property tree state-importer.ts: 496 -> 368 LOC. Behaviour preserved verbatim: - address = [module.]type.name[index_key], JSON-encoded index_key - ICE name = name_prefix + name + (_index_key when present) - sensitive_attributes path masking + SENSITIVE_MASKED warning - explicit instance.dependencies pass-through - id_lookup indexed by both 'id' AND 'arn' property values - Set-seeded dedup union of explicit + inferred deps - dependencies[] mutated in place per resource 22 new tests cover address building (incl. module/index_key paths), the masking warning emission, the include_sensitive=true bypass, and all three sub-cases of scan_for_references (string/array/object/null). 65 total terraform tests pass; TS2834 baseline (29) unchanged.
Pulls the two graph-emit functions out of state-importer.ts into a
new graph-conversion.ts module:
- import_result_to_graph — TerraformImportResult -> MutableGraph
- import_terraform_to_graph — file-path -> MutableGraph wrapper
state-importer.ts re-exports both via barrel-style 'export {...} from'
to preserve the public surface — index.ts imports state-importer.
state-importer.ts: 368 -> 268 LOC (547 LOC at series start).
Behaviour preserved verbatim:
- graph-level labels (source/version/lineage)
- per-node labels (provider, terraform_type, optional module)
- per-node provenance annotations (imported_from, terraform_address)
- all edges tagged 'inferred: true' regardless of origin
- missing-target edges silently skipped
- target_graph merge mode preserves nodes only
10 new tests cover the node properties/labels/annotations, the
optional module label, and the missing-target edge filter. 75 total
terraform importer tests pass; TS2834 baseline (29) unchanged.
Pulls four pure helpers out of aws-importer.ts into a new arn-helpers.ts module: - extract_name_from_arn — trailing /-or-:-separated name - extract_account_from_arn — 5th segment, '' when malformed - extract_region_from_arn — 4th segment, 'global' default for IAM/CF - parse_tags — Tags-array OR tags-object normalisation aws-importer.ts: 533 -> 495 LOC. Behaviour preserved verbatim: - 6-segment ARN check, segment-5+ join for resource portion - resource fallback when split-on-/-or-: leaves empty trailing - global default for empty region slot (IAM, CloudFront) - Tags array preferred over tags object when both exist - String() coercion for non-string Key/Value pairs 22 new tests cover all four helpers across well-formed ARNs, malformed inputs, IAM-style global resources, and both tag formats. TS2834 baseline (29) unchanged.
Pulls the dynamic-import wrapper functions out of aws-importer.ts into
a new sdk-init.ts module:
- AWSSdk interface — STS / ResourceExplorer / ConfigService bundle
- init_aws_sdk — dynamic-import client-sts/-resource-explorer-2/
-config-service, optional fromIni({profile})
- get_account_id — STS GetCallerIdentity wrapper, 'unknown' on err
aws-importer.ts: 495 -> 444 LOC.
Behaviour preserved verbatim — including the load-bearing
'Function("m", "return import(m)")' pattern that prevents bundlers
from transpiling the dynamic import to a static require (which would
break the optional-dep guarantee for users who never use AWS).
5 new tests cover the friendly install-the-sdk error path and the
'unknown' fallback in get_account_id. TS2834 baseline (29) unchanged.
Pulls the two paginated AWS-API discovery loops out of aws-importer.ts
into a new discovery.ts module:
- map_resource_explorer_hit — pure: hit -> AWSResource
- map_config_result — pure: JSON-string -> AWSResource|null
- discover_with_resource_explorer — paginated SearchCommand wrapper
- discover_with_config — paginated SelectResourceConfig wrapper
Two pure mappers were extracted alongside the discover_*() loops to
make the response-shape -> AWSResource conversion testable without
needing to stub the dynamic @aws-sdk/client-* imports (the
'Function("m", "return import(m)")' indirection bypasses any
Vitest module registry).
aws-importer.ts: 438 -> 358 LOC.
Behaviour preserved verbatim:
- Resource Explorer: QueryString='*', MaxResults=100, NextToken pagination
- Config: SelectResourceConfigCommand SQL DSL (LIKE '%')
- region default 'global' for both paths
- resourceId preferred over ARN-derived name (Config only)
- JSON.parse failures silently skipped (Config only)
8 new tests cover the pure mappers across well-formed / partial /
malformed inputs, plus the SDK-not-installed failure path on each
discover_*() entrypoint. TS2834 baseline (29) unchanged.
Pulls the two graph functions out of aws-importer.ts into a new
graph-conversion.ts module:
- aws_result_to_graph — AWSImportResult -> MutableGraph
- infer_relationships — ARN-driven dep inference post-pass
aws-importer.ts re-exports aws_result_to_graph via barrel-style
'export {...} from' to preserve the public surface — index.ts and
the importers index both bind to it. Local consumer
import_aws_to_graph aliases the local import as aws_result_to_graph_impl
to avoid same-name collision with the re-export.
aws-importer.ts: 346 -> 250 LOC (533 LOC at series start).
Behaviour preserved verbatim:
- graph-level labels (source, account_id)
- per-node labels: provider, aws_type, account_id, region, ...tags
(tags spread last — tags WIN on key collision with canonical labels)
- per-node provenance: imported_from, aws_arn, aws_account
- depends_on edges with inferred:true + source:aws labels
- self-dependency and missing-target edges silently skipped
- infer_relationships REPLACES dependencies (not unions) — load-bearing
- ARN matching gated by 'arn:aws:' prefix and arn_set membership
19 new tests cover the canonical-label-vs-tag collision, the
self-dep/missing-target edge filters, dedup of repeated references,
own-ARN exclusion, and the dependency-replacement (not -union)
contract. 148 total importer tests pass; TS2834 baseline (29)
unchanged.
dynamic-import-indirection-blocks-test-mocks
The Function('m', 'return import(m)') pattern in aws-importer
bypasses Vitest's module registry — vi.mock can't stub the AWS
SDK calls. Workaround: extract pure mappers and test those.
same-name-local-import-and-reexport-collision
When an extracted function is both consumed locally AND re-exported
for the public surface, alias the local import to make the two
roles explicit (X_impl vs X).
Verbatim port of TerraformExportOptions / RequiredProvider / TerraformResource / TerraformLifecycle / TerraformConfig / TerraformBlock / TerraformProviderConfig / TerraformVariable / TerraformOutput / TerraformExportResult shapes from terraform-exporter.ts (pre-extraction L20-160) into a dedicated types module. Public surface preserved — re-exported from the orchestrator in the slim-down unit.
Verbatim port of the private sanitizeName method from terraform-exporter.ts (pre-extraction L420-428) into a pure helper module. The Terraform identifier rules differ from Pulumi's (underscore prefix vs r- prefix; preserves _ in identifiers); kept separate to avoid coupling. Tests: 10 cases covering alphanumeric pass-through, dot/slash/ space substitution, leading-digit prefix, unicode replacement.
Verbatim port of the private fallbackTypeMapping method from terraform-exporter.ts (pre-extraction L335-362). Provider-prefix table preserved exactly; gcp/aws/azure branch ordering preserved (a gcp.* type always hits the gcp branch even if provider token is non-gcp). Documented the pre-extraction quirks where the aws and azure branches hard-code the prefix regardless of provider token. Tests: 17 cases covering each branch + provider mapping table.
Verbatim port of mapProperties / transformValue / formatDependencies from terraform-exporter.ts (pre-extraction L367-418). Three pure transformations with no class-state dependency. Key differences vs Pulumi's value-transform: keys are preserved AS-IS (Terraform uses snake_case natively); transform_value does not rename nested keys. format_dependencies emits # placeholders (pre-extraction had a TODO comment about lookup; preserved). Tests: 23 cases covering null/undefined normalisation, _-prefix filtering at top level only, recursive nested transforms, array handling, dependency placeholder formatting.
Verbatim port of formatHCLValue / toHCL / toJSON from terraform-exporter.ts (pre-extraction L433-545). Pure functions; no class state. The HCL output format is byte-identical to the pre-extraction class methods. Particularly load-bearing: - String escape order (backslash first, then quote) - null/undefined property values SKIPPED (not emitted as null) - depends_on block omitted when empty - HCL object syntax 'key = value' (not JSON-style 'key: value') - Trailing blank line after each section Tests: 37 cases covering all value types + full to_hcl snapshot regression guard.
Verbatim port of buildDependencyMap / nodeToResource /
exportGraph from terraform-exporter.ts (pre-extraction L189-330).
The class state previously held by the orchestrator
(schema_provider) is now passed as the first argument to each
helper.
Pre-extraction quirks preserved:
- depends_on edge filter (other relationships ignored)
- node.properties || {} defensive default
- unmapped_types deduped via Set; warnings NOT deduped
- format-selection branch checks 'json' literal; everything
else (including undefined) emits HCL
The class is now a thin orchestration shell: - constructor instantiates schema_provider - initialize() lazy-initialises the schema provider - exportGraph() delegates to ./terraform/converter.export_graph Public API unchanged — TerraformExporter, create_terraform_exporter, and the eleven exported types all keep their pre-extraction shape. External consumers (export/index.ts) continue importing through the orchestrator path; the new terraform/* modules are internal.
Verbatim port of PROVIDER_MAP (~24 entries) and TYPE_MAP (~280 entries) from importers/pulumi/type-mapper.ts (pre-extraction L94-413). The TYPE_MAP entries are the SOURCE OF TRUTH for ICE iceType names — external consumers depend on the exact dotted-form values; preserved verbatim. Size exception: 376 LOC justified by data-only nature (cf. /docs/refactoring-patterns.md 'Data-heavy shim split'). Tests: 18 cases pinning provider count, key existence, ICE type format, dedup behaviour for collapsed mappings (e.g. aws:s3/bucket:Bucket and aws:s3/bucketV2:BucketV2 both mapping to aws.s3.bucket).
Verbatim port of parse_urn and parse_type from importers/pulumi/type-mapper.ts (pre-extraction L19-86). Pure string parsers; no data-table dependency. Tests: 19 cases covering URN parts validation, special type handling (pulumi:pulumi:Stack, pulumi:providers:*), standard format vs alternative format priority order, malformed input fallback to empty object.
Feature-local Redux slice at packages/ui/src/features/tour/store/ with the persistence thunk and the public useTour() hook. Wired the reducer into the root store and added 'tour/' to the action-logger prefix list. Slice (tour-slice.ts, ~256 LOC): startTour validates against the registry and seeds per-tour telemetry, advance/skip/stop fan out through the hook, hydrateFromUser unions localStorage + server with server-wins-on-conflict semantics, persistCompletedTour is optimistic (failures warn but don't roll back). Hook (use-tour.ts, ~171 LOC): selectors + dispatchers per blueprint §2.2; advance() dispatches recordAdvance + setStep + setPhase, or on the terminal step markCompleted + persistCompletedTour. start() unregistered → no-op + dev warn. Tests: 44 slice tests + 17 useTour tests, node env per the test- ceiling decision (2026-05-08). Two learnings appended: tour-6-localstorage-fastpath-needs-vi-resetmodules-to-re-evaluate- initialstate (vi.resetModules + dynamic re-import for top-level IO) and tour-6-react-usecallback-stub-keeps-hook-driveable-without- renderer (vi.mock react useCallback identity stub for hook unit tests outside a fiber tree).
useTourKeyboard: capture-phase keydown listener wiring Esc (skip), ArrowRight/Enter (advance), ArrowLeft (previous). Suppresses advance/previous when an INPUT/TEXTAREA/SELECT is focused (Enter) or when an editable element including [contenteditable] holds focus (arrows). preventDefault on consumed keys. useTourRoute: router-aware navigation gate. Phase derives from useLocation().pathname.startsWith(targetRoute); imperative navigateTo no-ops when already arrived or targetRoute is undefined. Caller sequences onEnter side-effects around the navigation. 22 keyboard tests + 12 route tests, all node-env per the test-env ceiling (decision 2026-05-08). Refs: state/blueprints/tour.md §3.4 §3.6 §6/tour-11
Anchored Radix Popover wrapping the tour-2 primitive plus tour-5 focus trap. Composes title / body / counter / Back / Skip / Next / Close X with role=dialog, aria-modal=false, aria-labelledby, aria-describedby. - Auto-placement helper picks the side with the most viewport space (tie-break top -> bottom -> right -> left); inlined per blueprint guidance, exported for direct testing. - Honors useReducedMotion via data-reduced-motion attribute and motion-reduce:animate-none / data-[state=open|closed]:animate-none. - Hands focus management to installFocusTrap on mount; Radix's PopoverContent onOpenAutoFocus / onCloseAutoFocus are e.preventDefault so our trap owns initial and return focus. - Skip is hidden on the last step (avoids skip-vs-finish UX confusion) and when actions.hideSkip is set; Back is hidden on the first step. - Close (X) is distinct from Skip: dispatches onClose so the runner can stopTour without marking completed. 27 tests (jsdom env per state/decisions.md 2026-05-08), all green. Learning: tour-10-radix-popper-virtualref-must-be-identity-stable-or-ooms.
Critic-flagged: wizard step-4 Create button shared the wizard-btn-next anchor. Different semantics (handleCreate vs handleAdvance, different label, different tour copy "Click *Create* to finish") — distinct anchor keeps tour configs precise. Added a focused step-4 assertion alongside.
Critic-flagged: TourRunner forwards liveRect from useElementPosition but had no test asserting the overlay rect updates when the anchor moves. Added: dispatch scroll → re-read getBoundingClientRect → assert overlay's rect prop reflects the new position. 25 runner tests now.
- New `useTourAutostart` hook reads `?tour=<id>` from the URL, dispatches
`start(id)` once when registered + not in completedTours, then strips
the param via `navigate(..., { replace: true })`. Other query params
and the hash are preserved. StrictMode-safe via a `(pathname, tour)`
tuple ref.
- TourRunner now calls `useTourAutostart()` so the URL surface lights up
on every project page.
- AppBar gains a Help menu (HelpCircle icon → DropdownMenu with
"Show me around" submenu). Each registered tour gets a
DropdownMenuItem; click → `useTour().start(tour.id)`.
- i18n: new `appBar.help.tooltip` + `appBar.help.showMeAround` keys
(en/zh).
- 12 new hook tests (jsdom + MemoryRouter + Provider) covering the
registered-tour, unknown-id, completed-id, no-param, hash/extra-param
preservation, double-effect, multi-value, empty-value, and re-paste
paths.
- 5 new AppBar tests (tree-walker pattern) covering Help button,
submenu trigger, per-tour items, click→start dispatch, and empty
registry.
Per blueprint §4.3 + §6/tour-13: v1 auto-fire is OFF by default —
predicate-based ("first project after wizard") is v2 territory. v1 only
ships URL `?tour=` + Help menu.
…nt (tour-12 followup)
Authored alongside the 14-unit tour initiative (tour-1 .. tour-14). Multiple feat(tour) commits reference §-numbers in this blueprint; tracking it now keeps cross-references resolvable.
The OnboardingChecklist appears bottom-left on every route, including the folder view at /. The canvas-tour highlights elements that only exist on a project canvas (#ice-canvas-svg, palette, properties, AI panel) — clicking "Show me how" from / would silently auto-skip every step (each missing anchor → console.warn + advance) and "complete" the tour without showing anything. Fix: gate startTour() on the canvas anchor being in the DOM; otherwise surface "Open a project to start the tour." inline. No org/project URL plumbing — the click handler is a single DOM check.
Multi-agent workflow definitions (planner/implementer/critic/ux-tester + decomposer/util-broker/test-author) referenced by CLAUDE.md were unintentionally swept into 647adf8 as deletions. Restoring from HEAD~1.
julia-kafarska
added a commit
that referenced
this pull request
May 20, 2026
Connection-drawing state machine (port-drag start, mouse-track, drop-validate, edge-create) consolidates into packages/ui/src/features/canvas/hooks/use-connection-drawing.ts. Per blueprint risk #3, card stays in the hook's dep array (no ref). Per risk #5, the orchestrator's onMouseDown event-target sniff stays at the orchestrator level — the hook only owns the post-classList work. Last hook of the rf-canv series (only orchestrator slim-down + cleanup remain). No behavior change.
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 the in-memory tar parser (`parseTar` + `FileEntry` interface) from the orchestrator into `firebase-hosting/tar-parser.ts`. firebase-hosting.ts now imports both via `./firebase-hosting/tar-parser.js`. Removes 48 LOC from the orchestrator (1071 → 1023). New tests at `firebase-hosting/__tests__/tar-parser.test.ts` (17 cases, 100% line + branch + function coverage) cover RISK #3 from the blueprint: single-block EOF (vs GNU two-block), octal `parseInt(_, 8)` size parsing (`'00000000200'` → 128, not 200), `Math.ceil(size/512)*512` block padding including the size=0 → 0-advancement edge, ustar `prefix`+`name` concatenation, 100-char filename field, and the `Buffer.from(data)` deep copy that decouples returned payloads from the source archive buffer. Fixtures are constructed in-memory via a `makeTarHeader` helper that fills the 512-byte header with name, size, optional prefix, magic, version and a computed checksum — no `tar` package dependency. Append learning `git-stash-pop-conflicts-with-tsconfig-tsbuildinfo`: `pnpm exec tsc` writes `tsbuildinfo` as a side-effect, which collides with `git stash pop` and partially reverts changes; grep the typecheck output for your file paths instead of reaching for stash.
julia-kafarska
added a commit
that referenced
this pull request
May 20, 2026
Extract `parse_identifier`, `parse_type_identifier`, `parse_string_literal`, `parse_boolean_literal`, `create_null_literal`, and the parser-internal 2-arg `create_span` from the `Parser` class into `parser-literals.ts`. The 6 class methods are deleted; 67 callsites (20+2+6+2+2 + 35 spans) switch to standalone-function form. - New module: `parser-literals.ts` (167 LOC, 100% coverage) - New tests: `__tests__/parser-literals.test.ts` (23 tests) - parser.ts: 1005 LOC -> 938 LOC (-67) - Risks pinned: RISK #3 (silent dot-skip when type-id continuation token is neither IDENTIFIER nor TYPE_IDENTIFIER), RISK #4 (`create_span` here is the 2-arg parser-internal variant, distinct from `ast.ts::create_span` 6-arg variant; same name, different fn) - All 838 @ice/core tests pass; pre-existing TS2834 errors in unrelated files unchanged.
julia-kafarska
added a commit
that referenced
this pull request
May 20, 2026
Pull scan_number, scan_identifier, scan_line_comment, and scan_block_comment out of the Lexer class and into a sibling lexer-scanners.ts. The 3 char predicates (is_digit, is_alpha, is_alphanumeric) move with them as module-private; is_alpha and is_digit are also exported (without index re-export) for lexer-heredoc.ts to consume in rf-lex-3. Pins four RISKs from the blueprint: RISK #3 — scan_number._negative param preserved (unused). The leading '-' is consumed by scan_token's case '-' before dispatch; the param is kept for signature stability. RISK #4 — 3-branch keyword dispatch in scan_identifier: TRUE/FALSE/NULL_KEYWORD each emit literal-bearing tokens. Other keywords fall through to plain add_token. RISK #5 — TYPE_IDENTIFIER detection regex preserved verbatim: value.includes('.') || /^[A-Z]/.test(value). RISK #6 — Block-comment nested-depth counter — both increment and decrement load-bearing; nested /* /* ... */ ... */ must match the outer close. JSDoc gotcha: the closing star-slash inside a doc comment closes the JSDoc early. Risk-doc on slash-star/star-slash uses spelled-out descriptions instead of literal sequences. Tests: 216 → 244 (+28). Lexer.tokenize integration smoke tests included to verify the dispatch site routes through the extracted scanners without behavioural drift.
julia-kafarska
added a commit
that referenced
this pull request
May 20, 2026
Highest-risk unit in the rf-cstor series. Extract two-tier creation
retry from create() L58-190 (~133 inline LOC) into
cloud-storage/bucket-creator.ts.
Function signature:
createOrAdoptBucket(storage, name, createOptions, publicAccess, ctx)
-> { ublaForcedOn, bucketAlreadyExisted }
Risks pinned by 22 new tests (4 broad scenarios + branch coverage):
RISK #2 — "already exists" guard checks 3 conditions in BOTH the
initial-fail catch and the retry-fail catch:
.includes('you already own it')
.includes('already own this bucket')
.code === 409
Tests cover all 3 paths in both catch sites + the re-throw fallback.
RISK #3 — adopted-bucket UBLA-disable branch only sets
ublaForcedOn=true when the disable error matches the UBLA constraint
string (either variant). Non-UBLA disable errors are silently
swallowed so the orchestrator can still try IAM. Tests cover:
- constraint-string match (both variants)
- non-UBLA disable error (silent swallow path)
- getMetadata reject (outer best-effort catch)
- storage.bucket() throw (outer best-effort catch)
Other branches:
- clean first-try success
- publicAccess=false path (no retry attempted on UBLA)
- non-recoverable error re-throw from initial create
- retry path with quota-exceeded (re-throw)
- non-Error string thrown (coercion to string for scan)
Orchestrator drops to 672 LOC (-94 LOC vs previous unit).
julia-kafarska
added a commit
that referenced
this pull request
May 20, 2026
Findings #3 / BE-3: when a refresh token isn't found in the DB the old code called `deleteMany({ user_id })`, claiming this was "reuse detection". The bug: any non-reuse cause of a missing row (cleanup job, manual revoke, race) would log the legit user out everywhere; an attacker who stole a token could deliberately trigger the wipe. Without a token-family column we can't distinguish actual reuse from benign causes, so the safe behavior is to reject the request (401) and emit a `console.warn` for security-log review. Legit sessions stay intact. Inverted the regression test that pinned the old wipe-everything behavior; 33 tests in auth.service.test.ts pass.
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).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.