Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
c8717a5
fix(canvas): correct snap target + drag-snap stickiness for Custom Do…
julia-kafarska May 24, 2026
77c2568
refactor(secret-store): schema-driven 1→N deploy expansion
julia-kafarska May 24, 2026
e808b3d
refactor(canvas-renderer): drop hardcoded iceType branches via SPECIA…
julia-kafarska May 24, 2026
8901ea5
refactor(canvas-path): drop hardcoded iceType in socket-position via …
julia-kafarska May 24, 2026
441a94f
refactor(properties): schema-drive tab visibility + deployment-target…
julia-kafarska May 24, 2026
3c14e84
refactor(properties): schema-drive per-tab section dispatch via SECTI…
julia-kafarska May 24, 2026
58c048f
refactor(canvas-sizing): schema-drive bespoke node sizing via BESPOKE…
julia-kafarska May 24, 2026
fbae1d3
refactor(deploy/edge-classifier): schema-drive isolation + standalone…
julia-kafarska May 24, 2026
8bd7a36
refactor(deploy/passes): schema-drive public-ingress detection + doma…
julia-kafarska May 24, 2026
39bb4e0
refactor(deploy/security-rules): schema-drive iceType classifiers via…
julia-kafarska May 24, 2026
0bfbf50
refactor(classifiers): unify connection-rules + propagation-rules ice…
julia-kafarska May 24, 2026
d963ad6
refactor(deploy): dedup SERVICE_BACKEND_ICE_TYPES via shared serviceB…
julia-kafarska May 24, 2026
f055ba8
refactor(translator): drop hardcoded iceType + provider branches via …
julia-kafarska May 24, 2026
cc92fea
refactor(deploy): drop hardcoded Compute.StaticSite in storage extrac…
julia-kafarska May 24, 2026
57daeca
refactor(deploy/aws): modularise AWSDeployer to mirror gcp/ shape
julia-kafarska May 24, 2026
afdaf6a
feat(deploy/aws): extractors for compute (ecs.service, lambda.functio…
julia-kafarska May 24, 2026
60c4fc8
feat(deploy/aws): extractors for database (rds, dynamodb, elasticache…
julia-kafarska May 24, 2026
d010862
feat(deploy/aws): extractors for network (s3, apigateway, cloudfront,…
julia-kafarska May 24, 2026
27a55ed
feat(deploy/aws): extractors for ancillary (sqs, sns, cognito, secret…
julia-kafarska May 24, 2026
7f5fac9
feat(deploy/aws): extractors for AI/analytics (opensearch, bedrock, s…
julia-kafarska May 24, 2026
8fcf181
feat(deploy/aws): shared infra — STS account-id resolver + IAM ensure…
julia-kafarska May 24, 2026
c9757a1
feat(deploy/aws): s3 handler — account-id suffix + publicWebsite buck…
julia-kafarska May 24, 2026
2a77cde
feat(deploy/aws): lambda handler — fail-fast role + code-source valid…
julia-kafarska May 24, 2026
9e87b9e
feat(deploy/aws): cloudwatch-logs handler + shared _result helpers
julia-kafarska May 24, 2026
e3cf87c
feat(deploy/aws): secrets-manager handler + shared test harness
julia-kafarska May 24, 2026
fae0bf0
feat(deploy/aws): sqs handler — CreateQueue/SetQueueAttributes/Delete…
julia-kafarska May 24, 2026
c2054f3
feat(deploy/aws): sns handler — CreateTopic/SetTopicAttributes/Delete…
julia-kafarska May 24, 2026
af7f2dc
feat(deploy/aws): dynamodb handler — CreateTable + key schema + PITR
julia-kafarska May 24, 2026
3f7713d
feat(deploy/aws): elasticache handler — single-node + replication-gro…
julia-kafarska May 24, 2026
2989447
feat(deploy/aws): rds handler — no-default-password gate + provisioni…
julia-kafarska May 24, 2026
d3adaef
feat(deploy/aws): docdb handler — cluster + per-instance creation
julia-kafarska May 24, 2026
c399b8f
feat(deploy/aws): cognito handler — user pool with password policy + MFA
julia-kafarska May 24, 2026
f5e1df6
feat(deploy/aws): cloudfront handler — us-east-1 ACM cert + minimal d…
julia-kafarska May 24, 2026
a6bafbe
feat(deploy/aws): elbv2 handler — LB + skeleton target group
julia-kafarska May 24, 2026
b3e64c8
feat(deploy/aws): api-gateway handler — REST API + default-stage depl…
julia-kafarska May 24, 2026
8614079
feat(deploy/aws): events-rule handler (CronJob) — PutRule + PutTargets
julia-kafarska May 24, 2026
88871d9
feat(deploy/aws): ecs handler — auto-cluster + task role + service cr…
julia-kafarska May 24, 2026
b5389bf
feat(deploy/aws): opensearch handler — CreateDomain with cluster/EBS/…
julia-kafarska May 24, 2026
f8bcbd8
feat(deploy/aws): bedrock handler — on-demand no-op + provisioned-thr…
julia-kafarska May 24, 2026
494f2e9
feat(deploy/aws): sagemaker handler — EndpointConfig + Endpoint, requ…
julia-kafarska May 24, 2026
e70aae5
feat(deploy/aws): redshift handler — CreateCluster + no-default-passw…
julia-kafarska May 24, 2026
cb85063
feat(deploy/aws): lambda auto-build from Source.Repository
julia-kafarska May 24, 2026
aeca368
test(deploy/aws): unskip AWS Type Map block + end-to-end coverage
julia-kafarska May 24, 2026
ac4281d
docs(deploy/aws): provider notes — quirks, assumptions, deferred work
julia-kafarska May 24, 2026
ad69a21
feat(aws): selectively enable safe categories via feature flags
julia-kafarska May 25, 2026
b32c53a
fix(palette): enable provider dropdown items when any block is available
julia-kafarska May 25, 2026
6ac85de
docs(architecture): explain how canvas edges become cloud infra
julia-kafarska May 25, 2026
4f6c739
docs(architecture): explain how canvas edges become cloud infra
julia-kafarska May 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/architecture/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -184,4 +184,5 @@ See [`../../SECURITY.md`](../../SECURITY.md) for the disclosure process.
## See also

- [core-engine.md](core-engine.md), [frontend.md](frontend.md), [services.md](services.md), [database.md](database.md), [desktop.md](desktop.md).
- [connections-to-cloud.md](connections-to-cloud.md) — how a canvas edge collapses into env vars, IAM, and network policy at deploy time.
- [`packages/core/src/`](../../packages/core/src/) — the canonical implementation of everything on this page.
250 changes: 250 additions & 0 deletions docs/architecture/connections-to-cloud.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
# Connections → cloud infra

How a line drawn between two blocks on the canvas becomes real cloud
resources, IAM bindings, and network policy. Worked examples on GCP at
the end.

## The mental model

A canvas edge is **not** a cloud resource. The cloud sees only resources
plus IAM plus network policy — there is no "edge" object in any cloud
SDK. So every line you draw must collapse into some mix of three things
on the endpoint nodes:

1. **Property propagation** — env vars, URLs, connection strings, etc.
written onto a block's properties because of the connection.
2. **IAM binding** — the source node's identity gets a role on the
target's resource (and vice versa).
3. **Network policy** — firewall / ingress allow-list entries, VPC
peering, custom domain routes.

Which of the three an edge produces is decided by its
`connectionCategory` (`traffic` | `config` | `source` | …) plus the
roles of the two endpoint blocks (`backend`, `database`, `storage`,
`secrets`, `repo`, …). Roles come from a single table in
`@ice/constants/block-classifiers.ts` so the connection-rules predicates
and the propagation predicates can never drift apart on what "is a
database" means.

## The pipeline

```
canvas edge (drawn in the UI)
connection rules packages/types/connection-rules
Shape check: is this combo legal at all?
propagation rules packages/core/src/compute/propagation-rules.ts
Mutate node data based on the edge.
type-maps packages/core/src/deploy/type-maps.ts
iceType → provider resource type (per cloud).
extractors packages/core/src/deploy/extractors/*
Block properties → provider resource properties.
handlers packages/core/src/deploy/providers/<cloud>/handlers/*
Resource properties → cloud SDK call.
```

Each layer is schema-driven; per-provider behaviour lives in
per-provider files only. The cardinal rule: no hardcoded `iceType ===`
branches in cross-cutting code. See
[refactoring-patterns.md](../refactoring-patterns.md) for the rationale.

### Layer 1 — connection rules

`packages/types/connection-rules` holds the **legality** table: which
(source iceType, target iceType) pairs are even valid edges. It runs in
the UI as you drag a connection — incompatible combos are rejected
before the edge can land. Predicates here use `hasBlockRole` so
`isDatabase(t)` works the same way on both sides of the layer split.

### Layer 2 — propagation rules

`packages/core/src/compute/propagation-rules.ts` is a declarative array.
Each `PropagationRule` says: "when source role × target role match
this pattern AND this edge has these data fields, write these derived
properties onto the receiving node."

There are two flavours:

- **Per-edge rules** (`PROPAGATION_RULES`) — fire on a single edge,
compute a property patch for the source or target depending on
`direction`. E.g. `Backend → DataStore: connection string
propagation` writes `envVarName: 'BUCKET_NAME'` onto the edge so
downstream the backend knows what env var to read.
- **Aggregate rules** (`AGGREGATE_RULES`) — fire on a node, scan all
inbound or outbound edges, compute a property patch from the
collection. E.g. `DataStore: derive allowedClients from inbound
traffic edges` populates the bucket's `allowedClients` array, which
the handler turns into an IAM policy binding.

These run live in the UI (so the properties panel reacts as soon as
you draw an edge) and again pre-deploy (so the translator sees the
fully-propagated graph).

### Layer 3 — type-maps

`packages/core/src/deploy/type-maps.ts` is the provider-specific
collapse: a single `Record<iceType, providerResourceType>` per cloud.
`Compute.BackendAPI` becomes `gcp.run.service` on GCP,
`aws.ecs.service` on AWS, `azure.containerapps.app` on Azure. One iceType
can map to different resources per provider because the schema-driven
extractor dispatch sits behind it.

Some iceTypes deliberately compile differently on different clouds —
`Compute.StaticSite` → `gcp.firebase.hosting` on GCP (bypasses the
public-bucket org policy) but → `aws.s3.bucket` + CloudFront on AWS.
That's documented inline in `type-maps.ts`.

### Layer 4 — extractors

`packages/core/src/deploy/extractors/*` transform a canvas block's
**user-facing properties** (`data.schedule = 'daily'`,
`data.bucket_name = 'photos'`) into the **provider resource
properties** the handler expects (`schedule: '0 0 * * *'`,
`bucket_name: 'ice-photos-abc123'`). One extractor per provider
resource type. The dispatcher (`extractors/dispatch.ts`) routes by the
type-map output, not by iceType.

### Layer 5 — handlers

`packages/core/src/deploy/providers/<cloud>/handlers/*` make the actual
SDK call. Handlers are dumb — they assume the extractor has already
filled every required property correctly, so the only branching is
between create / update / delete. See
[`providers/aws/README.md`](../../packages/core/src/deploy/providers/aws/README.md)
and [deploying-to-gcp.md](../deploying-to-gcp.md) for the per-provider
quirks each layer handles.

## Worked example 1 — `Storage.Bucket` connected to `Compute.BackendAPI` (GCP)

You drag a Backend onto the canvas, drop a Storage Bucket next to it,
draw an edge Backend → Bucket. Here's what each layer does.

**Type-map** (`type-maps.ts:31,39`):

- `Compute.BackendAPI` → `gcp.run.service`
- `Storage.Bucket` → `gcp.storage.bucket`

**Propagation fires twice as you draw the edge:**

1. `Backend → DataStore: connection string propagation`
(`propagation-rules.ts:166`) — `hasBlockRole('storage')` matches the
bucket, so the rule resolves `envVarName: 'BUCKET_NAME'` from
`DEFAULT_ENV_VARS['Storage.Bucket']` (in `@ice/constants/derived`)
and stamps it onto the **edge data**.
2. `DataStore: derive allowedClients from inbound traffic edges`
(`propagation-rules.ts:218`) — runs on the bucket node, looks at
every inbound `traffic` edge, writes
`allowedClients: [{ nodeId, iceType, label }]` onto the bucket. This
array is what the IAM-binding pass reads.
3. `Service: derive allowedTargets from outbound traffic edges`
(`propagation-rules.ts:263`) — the symmetric view on the backend, so
it knows what it's allowed to reach.

**Extractors then run:**

- `extract_cloud_run_properties` for the backend — picks up
`injectedEnvVars: { BUCKET_NAME: <bucket-name> }` (resolved from the
edge's `envVarName` plus the target bucket's name) and merges it into
the Cloud Run service's `env` array.
- `extract_storage_bucket_properties` for the bucket — passes through
bucket name, location, uniform-access-level, plus the
`allowedClients` from the aggregate rule.

**Handlers call GCP:**

- The Cloud Run handler creates a `google.cloud.run.v2.Service` with
`env: [{ name: 'BUCKET_NAME', value: 'ice-myapp-photos' }]`.
- The Cloud Storage handler creates a private
`google.cloud.storage.Bucket` (no `allUsers` binding — that's
reserved for `Compute.StaticSite`; see
[`cloud-storage/public-access-granter.ts`](../../packages/core/src/deploy/providers/gcp/handlers/cloud-storage/public-access-granter.ts)).
- After both exist, an IAM pass grants the Cloud Run service's
identity `roles/storage.objectUser` on the bucket. The bucket-side
`allowedClients` is the input list.

**Cloud-side result of one canvas edge:**

- ✅ env var injection on the Cloud Run service
- ✅ IAM policy binding on the bucket
- ❌ no "edge resource" — none exists in GCP

## Worked example 2 — `Compute.CronJob` connected to `Compute.BackendAPI` (GCP)

You drop a Cron block, set schedule to `daily`, draw an edge Cron →
Backend.

**Type-map** (`type-maps.ts:31,33`):

- `Compute.CronJob` → `gcp.cloudscheduler.job`
- `Compute.BackendAPI` → `gcp.run.service`

**Propagation:** a translator pass reads the cron's outbound edge to
the Backend and writes the Backend's URL onto `cron.data.targetUri`.
Because cron is a job-like trigger (not a data store), it doesn't
participate in the `Backend → DataStore` rule — its outbound edge is
the propagation source.

**Extractor** (`extractors/compute.ts:135`,
`extract_cloud_scheduler_properties`) turns `data.schedule = 'daily'`
into a real cron expression `'0 0 * * *'` via a built-in map, then
emits:

```ts
{
region,
schedule: '0 0 * * *',
timezone: 'UTC',
target_type: 'http',
target_uri: <backend-cloud-run-url>,
labels: {},
}
```

**Handler** (`gcp/handlers/cloud-scheduler.ts:62`) calls
`projects.locations.jobs.create` with:

```ts
httpTarget: { uri: properties.target_uri, httpMethod: 'POST', ... }
```

If the Cloud Run service is private (not public-traffic), the IAM pass
grants the scheduler's service account `roles/run.invoker` on the
service. That binding is applied by
[`gcp/handlers/cloud-run/iam.ts:19`](../../packages/core/src/deploy/providers/gcp/handlers/cloud-run/iam.ts).

**Cloud-side result of one canvas edge:**

- ✅ a `google.cloud.scheduler.v1.Job` that POSTs the backend URL on
schedule
- ✅ optional `run.invoker` IAM binding scoped to the scheduler's SA
- ❌ no "edge resource"

## Why this layering matters

- **Property propagation runs both live (UI) and pre-deploy.** Drawing
an edge updates the properties panel before you save — same rules,
same code. The deploy never sees an unpropagated graph.
- **Layers can be swapped per provider without touching the others.**
AWS and Azure plug in by registering their own type-maps,
extractors, and handlers; the propagation rules and connection rules
are provider-agnostic because they only read roles (`backend`,
`database`, …) from the shared classifier table.
- **No edges in the cloud.** If your debugging instinct is "find the
connection in the cloud console" — stop. Look at the env vars on the
consumer, the IAM bindings on the producer, and the firewall rules
on the network.

## See also

- [core-engine.md](core-engine.md) — graph engine, plan/apply scheduler,
state store.
- [extending-providers.md](../extending-providers.md) — adding a new
provider goes through these same layers.
- [blocks-reference.md](../blocks-reference.md) — every iceType and the
roles it carries.
- [`packages/core/src/compute/propagation-rules.ts`](../../packages/core/src/compute/propagation-rules.ts) — the full rules table.
- [`packages/core/src/deploy/type-maps.ts`](../../packages/core/src/deploy/type-maps.ts) — every iceType → provider resource mapping.
2 changes: 2 additions & 0 deletions docs/architecture/core-engine.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,8 @@ A clean clone of ICE with no deploys has an empty state store; every node in the

Some block properties are derived from others (`derived`), aggregated across connected nodes (`aggregate`), or propagated along edges (`propagation`). Rules live in `packages/core/src/compute/propagation-rules.ts` and similar. The UI applies these live so that, e.g., a Static Site's "CDN" toggle automatically suggests a Custom Domain requirement.

The full canvas-edge → cloud-resource pipeline (propagation → type-maps → extractors → handlers, with worked GCP examples for Storage→Backend and Cron→Backend) is documented separately in [connections-to-cloud.md](connections-to-cloud.md).

## Entry points worth reading

- [`packages/core/src/index.ts`](../../packages/core/src/index.ts) - the export surface.
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "ice",
"license": "Apache-2.0",
"version": "0.1.720",
"version": "0.1.769",
"description": "ICE - Integrated Cloud Environment (Web + Backend)",
"private": true,
"type": "module",
Expand Down
Loading
Loading