> ## Documentation Index
> Fetch the complete documentation index at: https://docs.fjall.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Changelog

> New capabilities, behaviour changes, and upgrade notes for the Fjall CLI, the infrastructure constructs, and the fjall.io control plane.

What shipped, newest first, across the `fjall` CLI, the `@fjall/*` infrastructure constructs, and the fjall.io control plane.

<Note>
  **About version numbers.** The CLI and the infrastructure constructs ship as one deploy engine on a shared version line, so a change to what a construct *synthesises* is a major even when no command, flag, or output moved. You are not expected to track them. Your application pins its constructs, and the engine refuses to run against infrastructure newer than itself rather than deploying something different from what you wrote. Upgrading the CLI never locks you out of redeploying an older application. See [Engine compatibility](/deployment/engine-compatibility).
</Note>

<Update label="4 September 2026" description="Asia Pacific target names, SSH is no longer part of an instance size">
  <Note>
    **fjall 32 is not published yet.** The four changes below are merged and ship
    in the next release. Until it lands, `npm install -g fjall@latest` installs
    fjall 31, and there is no fjall 32 on npm to install, so none of this
    behaviour is available yet.
  </Note>

  ## Asia Pacific, Africa and southern Europe target names changed

  A deployment target is named `<accountname>-<regionabbrev>`, and the region part is the only thing that says where a deploy lands. Abbreviating the compass word to a single letter gave `ap-southeast-1` and `ap-south-1` the same code, `aps1`, and likewise `aps2` for `ap-southeast-2` and `ap-south-2`.

  Compound and ambiguous directions now get two letters:

  | Region           | Was    | Now     |
  | ---------------- | ------ | ------- |
  | `ap-southeast-1` | `aps1` | `apse1` |
  | `ap-south-1`     | `aps1` | `apso1` |
  | `ap-southeast-2` | `aps2` | `apse2` |
  | `ap-northeast-1` | `apn1` | `apne1` |

  The same applies to the rest of `ap-southeast-*`, `ap-northeast-*`, `ap-south-*`, plus `af-south-1`, `eu-south-1`, `eu-south-2` and `me-south-1`. Every other region keeps the name it had, so `production-use2`, `staging-euw1` and `development-euc1` are untouched.

  The old codes are retired rather than re-pointed. Naming one is refused with the replacement to use, so a command written against `aps1` stops rather than resolving to a region you did not name. Run `fjall target list` for the current names, then `fjall target set <name>`.

  Requires fjall 32. See [fjall target](/cli/target).

  ## SSH is no longer part of choosing an instance size

  The "Standard instance" preset in the `fjall add compute` wizard enabled SSH. Because presets skip the advanced step, nothing asked first, so picking a size also put the instance in a public subnet with a public IP and opened port 22 to `0.0.0.0/0`.

  Choosing a size now decides only the size. Pick "Custom" to be asked about SSH, or set `ssh` in `infrastructure.ts` yourself. Existing applications are unchanged: this is the wizard's default for new compute, not a change to what your file already says.

  Requires fjall 32. See [EC2 Instance](/resources/compute/ec2-instance).

  ## The VPC peering companion command is runnable

  When the two halves of a peering pair live in different repositories, the wizard prints the command to run in the other one. That line named a `--for` flag that does not exist and carried none of the properties the peer half needs. It is now rendered from the same properties the single-repository path writes, so it can be pasted as printed.

  Requires fjall 32. See [VPC peering](/deployment/vpc-peering).

  ## `fjall target set <name>` honours the name at a terminal

  Naming a target opened the account and region pickers anyway and ignored the argument, so setting a named target took `--non-interactive`. It now sets what you named, at a terminal or not. `fjall target set` with no name still opens the pickers.

  Requires fjall 32. See [fjall target](/cli/target).
</Update>

<Update label="3 September 2026" description="Deploy-role permissions boundary, object versioning on resilient buckets">
  ## Deploy roles are capped by a permissions boundary

  Every deploy role Fjall creates in a connected AWS account now carries a `FjallDeployBoundary` permissions boundary, rendered by default by both the CloudFormation quick-create template and its CDK equivalent. The boundary caps what a mistaken or compromised deploy can reach, and it protects itself at every tier: a deploy cannot widen or delete the policy that constrains it.

  The policy is an account-global singleton, adopted by name if it already exists and created if it does not, through a custom resource rather than a stack-owned managed policy. Connecting a second account, or re-connecting an existing one, no longer risks an `EntityAlreadyExists` refusal or a connector stack stuck in `DELETE_FAILED`. `fjall org destroy` removes the policy only when the last stack referencing it goes.

  Requires fjall 31. See [Configure deployment credentials](/deployment/configure-deployment-user).

  ## Resilient and enterprise buckets version their objects

  S3 buckets provisioned at the `resilient` or `enterprise` backup tier now enable object versioning by default, so an overwrite or an accidental delete stays recoverable. Buckets at other tiers are unchanged, and an explicit `versioned` setting always wins.

  Requires fjall 30. See [S3 Bucket](/resources/storage/s3-bucket).
</Update>

<Update label="16 August 2026" description="Destruction consent covers five more stateful types">
  ## The destruction gate covers log groups, secrets, KMS keys, hosted zones and user pools

  The data-loss oracle already recognised databases, buckets, volumes and file systems. It did not recognise five types whose destruction is equally irreversible, so a deploy whose plan deleted or replaced one of them went through ungated:

  | Type                   | What is lost                                                             |
  | ---------------------- | ------------------------------------------------------------------------ |
  | CloudWatch log group   | Retained history                                                         |
  | Secrets Manager secret | The stored value and its rotation lineage                                |
  | KMS key                | Everything encrypted under it, once the deletion window closes           |
  | Route 53 hosted zone   | Delegation: resolvers cache stale NS records long after recreation       |
  | Cognito user pool      | The user directory itself, including passwords, which cannot be exported |

  All five are now stateful to the oracle, so deleting or replacing one requires the same per-resource consent as a database.

  Requires fjall 13. See [Destructive changes](/deployment/destructive-changes).
</Update>

<Update label="13 August 2026" description="ECS on EC2 rejects impossible container placements at synth">
  ## Containers that cannot fit their instance type fail at synth

  On the EC2 launch type, ECS places a task only if the sum of its containers' hard memory limits fits the memory an instance actually registers with the cluster. That is always strictly below the instance type's nominal figure, because the ECS agent and OS reserve a slice first. A t4g.medium registers 3835 of its nominal 4096 MiB. A task sized against the nominal number can never be placed, on any instance count.

  The constructs now reject that shape during synth, with the numbers, instead of leaving you to read a placement failure at deploy time.

  ## The ECS on EC2 default instance type is now t4g.small

  The previous default, t4g.micro, was structurally unable to run an all-defaults service: roughly 896 MiB usable against a 1024 MiB default container limit. The default is now t4g.small, where the default container fits both on its own and alongside the schema-gate sidecar.

  Requires fjall 12. See [ECS Cluster](/resources/compute/ecs-cluster).
</Update>

<Update label="9 August 2026" description="Scaffolds no longer overwrite your edits">
  ## Scaffold commands refuse to overwrite files you have edited

  `fjall create account`, `fjall create organisation` and `fjall domain import` used to render their scaffold straight over whatever was on disk. Re-running `create account` after a failed install step silently reverted a hand-edited `infrastructure.ts` to the template; a domain re-import clobbered the previous one without comment.

  All three now guard every scaffold write and stop rather than discard your work.

  Requires fjall 8. See [fjall create](/cli/create).
</Update>

<Update label="18 July 2026" description="Domains are typed infrastructure, with classification and ceremonies">
  ## Domains are declared in `infrastructure.ts`

  A domain is now declared with the typed `Domain` construct in `fjall/domains/<zone>/infrastructure.ts`, a discriminated union on `registrar`. The older directive-driven pipeline is gone, and BIND survives as an import and export format rather than a source of truth.

  ## New domain verbs

  | Command                            | What it does                                                       |
  | ---------------------------------- | ------------------------------------------------------------------ |
  | `fjall domain records list <zone>` | Every live record with its ownership classification                |
  | `fjall domain sync <zone>`         | Read-only classification report; `--fix` deletes consented residue |
  | `fjall domain delegate <zone>`     | Write or report a delegated child's NS records                     |
  | `fjall domain destroy <zone>`      | Ceremony-gated teardown, delegated children NS-deleted first       |
  | `fjall domain import`              | Imports from Route 53 or BIND, and curates the declaration         |

  ## Zone safety

  Every live record classifies as `declared`, `satellite` (owned by another live stack), `residue` (provably orphaned) or `unknown`. The classifier fails closed: an evidence gap yields `unknown`, never `residue`, and unknown records are never remediable.

  Destructive domain paths run the same consent ceremony as deploys. Consent is per row and by exact name, and a withheld ceremony exits `4`: success-shaped, with nothing mutated.

  Requires fjall 3. See [fjall domain](/cli/domain) and [Custom domains](/deployment/custom-domain).
</Update>

<Update label="15 July 2026" description="Static sites, and recovering a wedged deploy slot from the CLI">
  ## Static sites

  `fjall create app --pattern staticsite` deploys a pre-built static site from a private S3 bucket behind CloudFront with Origin Access Control, at roughly \$0 per month. Single-page and multi-page routing, form handling, and a custom domain are all flags on the create.

  See [Static site pattern](/patterns/static-site-pattern).

  ## Recover a wedged deploy slot without the dashboard

  * `fjall deployments cancel <id>` requests co-operative cancellation of the deployment holding the slot. If the occupier is a dead remote source, the response distinguishes "still progressing, wait" from "demonstrably silent" and prints the exact force-release command.
  * `fjall deployments force-release <id> --confirm <id>` clears the slot. It is owner and admin gated, audited server-side, and requires a signed-in user credential, so agents and CI tokens route it through a human.

  See [fjall deployments](/cli/deployments).
</Update>

<Update label="14 July 2026" description="Drift repair, destructive-change consent, and remote deploy control">
  ## Destructive changes require explicit consent

  A deploy whose plan would delete or replace a stateful resource now stops with exit code `4` until every such resource is consented individually, by exact physical name and verb. No flag bypasses the gate: `--auto-approve`, `--skip-confirmation` and `--force` never satisfy it.

  Interactive deploys walk a per-resource ceremony. CI passes `--remediate <physicalName>=<verb>`, optionally pinned to the reviewed plan with `--destruction-consent-digest`, and a consent minted against a plan that has since moved is refused. A withheld deploy mutates nothing and frees the organisation's deploy slot immediately.

  See [Destructive changes](/deployment/destructive-changes).

  ## `fjall drift` detects and repairs out-of-band deletions

  Deleting a stack resource in the AWS console used to wedge deploys indefinitely, because CloudFormation keeps failing `NotFound`.

  * `fjall drift detect` probes each stack's resources against their owning services and reports confirmed, unconfirmed and recovered verdicts.
  * `fjall drift repair` runs the consent-gated repair (forensics capture, retain flip, surgical template removal), journalled and resumable with `fjall drift resume`.
  * A failed deploy whose root cause is a confirmed out-of-band deletion now names the resources and prints the exact repair command.

  See [fjall drift](/cli/drift).

  ## Deploys are observable and cancellable from fjall.io

  Tracked deploys send a liveness heartbeat, consult the deployment lease before every mutating CloudFormation boundary, and honour a cancel initiated from the dashboard across deploy, destroy, rollout and rollback. A remote cancel renders a cancelled outcome with exit code `2`, never a false success, and lands before the next stack mutation with completed stacks left standing.

  <Note>
    CI should treat exit code `2` as "stopped without failure" rather than as a deploy failure. It covers a remote cancel and a plan halted at the approval gate.
  </Note>
</Update>

<Update label="6 July 2026" description="Releases, rollback, in-place rollouts, and the CI command group">
  ## Releases and rollback

  `fjall releases` lists the releases minted from your deploys, and rollback is release-addressed with per-service granularity. Application ECR repositories moved from count-based lifecycle retention to release-aware retention, so a rollback target is not garbage-collected out from under you.

  See [fjall releases](/cli/releases).

  ## `fjall rollout` restarts services in place

  `fjall rollout <app>` restarts an application's services without a full deploy: the lightweight way to apply a secret or connection change. `fjall secrets set` and `delete` now detect consuming services and offer the roll.

  See [fjall rollout](/cli/rollout).

  ## The CI command group

  `fjall ci` covers `setup`, `token`, `status`, `verify` and `run`. Both CI plugins, the GitHub Action and the Buildkite plugin, are thin shims over `fjall ci run`, so all three surfaces share one code path and behave identically. CI deploy tokens are capped at 90-day expiry at mint time.

  See [fjall ci](/cli/ci) and [CI/CD](/deployment/ci-cd).
</Update>

## Upgrading

```bash theme={null}
npm install -g fjall@latest
```

Then bump `@fjall/components-infrastructure` in your application to match, and redeploy. If the two drift apart in the unsafe direction, the deploy refuses before it changes anything in AWS and tells you which version to install. See [Engine compatibility](/deployment/engine-compatibility).
