diff --git a/.changesets/2026-09-05-bun-naik-ke-1-4-2.md b/.changesets/2026-09-05-bun-naik-ke-1-4-2.md
new file mode 100644
index 0000000..5414227
--- /dev/null
+++ b/.changesets/2026-09-05-bun-naik-ke-1-4-2.md
@@ -0,0 +1,37 @@
+---
+bump: patch
+tipe: dependency
+dampak: internal
+---
+
+# Bun naik dari `1.3.14` ke `1.4.2`, dan `bun.lock` ikut berganti format
+
+Pin Bun repo ini naik di kelima nilai sekaligus — `packageManager` dan
+`engines.bun` di `package.json`, `bun-version` di kedua job
+`.github/workflows/ci.yml`, dan tag `oven/bun` beserta digestnya di kedua
+stage `Dockerfile` — mengikuti aturan lima-nilai-plus-digest yang
+`tests/versi-toolchain.test.mjs` gerbangi. `bun.lock` diregenerasi penuh
+(`rm -rf node_modules bun.lock && bun install`), bukan disunting sebagian,
+sesuai aturan repo.
+
+Regenerasi itu bukan sekadar isi lockfile yang berubah — **formatnya sendiri
+berubah**: Bun 1.4 menulis `bun.lock` sebagai `lockfileVersion: 2`, sementara
+setiap `bun.lock` yang lebih tua di repo ini adalah `lockfileVersion: 1`. Bun
+yang hanya memenuhi `engines.bun` LAMA (`>=1.3.0`) tidak bisa mengurai
+`lockfileVersion: 2` sama sekali — `bun install --frozen-lockfile` gagal
+dengan `error: Unknown lockfile version at bun.lock:2:22`, sebuah pesan yang
+menyebut nama berkas dan bukan versi Bun sama sekali. `engines.bun` karena itu
+juga naik ke `>=1.4.0`: batas lama menerima versi yang justru tidak bisa
+menginstal repo ini sejak `bun.lock` berformat baru.
+
+- Tidak ada perubahan output publik situs mana pun; ini murni kenaikan
+ toolchain.
+- **Terasa oleh situs yang diturunkan dari template ini sebelum 5 September
+ 2026, atau yang belum mengikuti kenaikan ini**: begitu `bun.lock` situs itu
+ sendiri diregenerasi oleh siapa pun dengan Bun ≥1.4 terpasang lokal — bukan
+ mesti oleh perubahan di template ini — instalasi `--frozen-lockfile`
+ berikutnya di CI atau `docker build` di sana gagal dengan pesan yang sama,
+ selama kelima nilai pin situs itu belum ikut naik. Gejala, sebab, dan
+ perbaikannya (lima nilai plus digest, sama seperti aturan repo ini) ditulis
+ di [`docs/deploy-coolify.md`](../docs/deploy-coolify.md#a-bun-install---frozen-lockfile-that-names-a-lockfile-not-a-bun-version)
+ dan [`docs/awcms-astro/checklist-repo-baru.md`](../docs/awcms-astro/checklist-repo-baru.md).
diff --git a/CONTRIBUTING.id.md b/CONTRIBUTING.id.md
index 7bc218c..d6ad0cc 100644
--- a/CONTRIBUTING.id.md
+++ b/CONTRIBUTING.id.md
@@ -1,6 +1,6 @@
🇮🇩 Bahasa Indonesia · 🇬🇧 [English (source)](CONTRIBUTING.md)
-
+
# Panduan Kontribusi
@@ -35,7 +35,7 @@ Kontributor agen AI: baca [`AGENTS.md`](AGENTS.md) lebih dulu. Ia kontrak kerja
## Menyiapkan lingkungan
```bash
-bun --version # >= 1.3.0, sesuai `engines.bun`
+bun --version # >= 1.4.0, sesuai `engines.bun`
cp .env.example .env # isi AWCMS_API_URL, token, dan tenant
bun install
bun run dev # http://localhost:4321
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 0cbc6dc..5681d26 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -33,7 +33,7 @@ AI agent contributors: read [`AGENTS.md`](AGENTS.md) first. It is the binding te
## Setting up
```bash
-bun --version # >= 1.3.0, per `engines.bun`
+bun --version # >= 1.4.0, per `engines.bun`
cp .env.example .env # fill in AWCMS_API_URL, the token, and the tenant
bun install
bun run dev # http://localhost:4321
diff --git a/docs/awcms-astro/checklist-repo-baru.id.md b/docs/awcms-astro/checklist-repo-baru.id.md
index d8fb076..f72c2aa 100644
--- a/docs/awcms-astro/checklist-repo-baru.id.md
+++ b/docs/awcms-astro/checklist-repo-baru.id.md
@@ -1,6 +1,6 @@
🇮🇩 Bahasa Indonesia · 🇬🇧 [English (source)](checklist-repo-baru.md)
-
+
# Memulai Situs Baru di Atas awcms-astro
@@ -44,6 +44,8 @@ Yang **tidak** perlu disentuh: `src/lib/`, `src/layouts/`, `src/components/`, `s
- [ ] `package.json` — `name`, `description`, `homepage`, `repository`, `engines`, dan seluruh skrip.
- [ ] Versi Bun konsisten di **lima nilai**, bukan tiga berkas — `packageManager`, `engines.bun`, `bun-version` di **dua** job CI, dan tag image (identik di kedua stage `Dockerfile`) — ditambah digest yang dipin di sebelah tag itu, sehingga ada enam hal yang bergerak bersama ([ADR-0030](../adr/0030-aturan-tertulis-mendapat-pemeriksanya.md) §Konsekuensi). Menghitung BERKAS alih-alih NILAI adalah kesalahan yang [ADR-0030](../adr/0030-aturan-tertulis-mendapat-pemeriksanya.md) ditulis untuk mengakhirinya: duplikat kedua di tiap berkas yang paling mungkin tertinggal, dan keduanya tetap hijau sendirian. `tests/versi-toolchain.test.mjs` membandingkan seluruhnya, dan ia juga menolak satu stage dipin ke digest sementara yang lain tidak — saat tag dan digest sama-sama ada, Docker mematuhi digest dan tag hanya menjadi komentar.
+ **Menaikkan pin-nya belakangan punya satu jebakan yang tidak bisa dilihat gerbang di atas, karena itu soal BINER Bun yang dijalankan seseorang, bukan nilai yang tertulis di repo ini.** Sejak Bun 1.4 menulis `bun.lock` sebagai `lockfileVersion: 2` — format yang sama sekali tidak bisa diurai Bun yang hanya memenuhi `>=1.3.0` — sebuah `bun.lock` yang diregenerasi oleh siapa pun dengan Bun lebih baru terpasang di mesinnya (kontributor, PR rekan tim, Dependabot) diam-diam menaikkan formatnya sementara kelima nilai pin situsmu sendiri bisa saja masih menyebut `1.3.x`. Kelimanya tetap konsisten satu sama lain dan gerbang di atas tetap hijau, tetapi `bun install --frozen-lockfile` berikutnya di CI atau `docker build` gagal dengan `error: Unknown lockfile version at bun.lock:2:22` — pesan yang menyebut nama lockfile, tidak pernah Bun. Gejala, sebab, dan perbaikannya tertulis di [`deploy-coolify.md`](../deploy-coolify.id.md#bun-install---frozen-lockfile-yang-menyebut-nama-lockfile-bukan-versi-bun).
+
Pertanyaan yang menentukan bentuk skema: **kesalahan apa yang paling merugikan pembaca situs ini, dan field mana yang membuat kesalahan itu sulit dilakukan?** Di repo rujukan jawabannya `cakupan` dan `biaya[].jenis` — keduanya memaksa keputusan yang kalau tidak dipaksa akan terlewat.
## 3. i18n
diff --git a/docs/awcms-astro/checklist-repo-baru.md b/docs/awcms-astro/checklist-repo-baru.md
index 0b2a0bd..7bcc919 100644
--- a/docs/awcms-astro/checklist-repo-baru.md
+++ b/docs/awcms-astro/checklist-repo-baru.md
@@ -42,6 +42,8 @@ What does **not** need touching: `src/lib/`, `src/layouts/`, `src/components/`,
- [ ] `package.json` — `name`, `description`, `homepage`, `repository`, `engines`, and every script.
- [ ] The Bun version consistent across **five values**, not three files — `packageManager`, `engines.bun`, `bun-version` in **two** CI jobs, and the image tag (identical in both `Dockerfile` stages) — plus the digest pinned beside those tags, making six things that move together ([ADR-0030](../adr/0030-aturan-tertulis-mendapat-pemeriksanya.md) §Consequences). Counting files rather than values is the mistake [ADR-0030](../adr/0030-aturan-tertulis-mendapat-pemeriksanya.md) was written to end: the second duplicate in each file is the one most likely to be left behind, and each stays green on its own. `tests/versi-toolchain.test.mjs` compares all of them, and it also refuses one stage being pinned to a digest while the other is not — when a tag and a digest are both present, Docker obeys the digest and the tag becomes a comment.
+ **Raising the pin later has one trap that gate cannot see, because it is about a Bun BINARY someone runs, not a value written in this repo.** Since Bun 1.4 writes `bun.lock` as `lockfileVersion: 2` — a format a Bun that only satisfies `>=1.3.0` cannot parse at all — a `bun.lock` regenerated by anyone with a newer Bun installed locally (a contributor, a teammate's PR, Dependabot) silently upgrades the format while your site's own five pinned values may still say `1.3.x`. The five stay internally consistent and the gate above stays green, yet the very next `bun install --frozen-lockfile` in CI or `docker build` fails with `error: Unknown lockfile version at bun.lock:2:22` — a message naming the lockfile, never Bun. The symptom, cause, and fix are written out in [`deploy-coolify.md`](../deploy-coolify.md#a-bun-install---frozen-lockfile-that-names-a-lockfile-not-a-bun-version).
+
The question that shapes your schema: **what mistake harms this site's readers most, and which field makes that mistake hard to make?** In the reference repo the answers were `cakupan` and `biaya[].jenis` — both force a decision that would otherwise be missed.
## 3. i18n
diff --git a/docs/awcms-astro/standar-performa-dan-keamanan.id.md b/docs/awcms-astro/standar-performa-dan-keamanan.id.md
index ad186d5..8ec0ee7 100644
--- a/docs/awcms-astro/standar-performa-dan-keamanan.id.md
+++ b/docs/awcms-astro/standar-performa-dan-keamanan.id.md
@@ -1,6 +1,6 @@
🇮🇩 Bahasa Indonesia · 🇬🇧 [English (source)](standar-performa-dan-keamanan.md)
-
+
# awcms-astro — Standar Performa dan Keamanan
@@ -215,7 +215,7 @@ situs yang butuh cakupan lebih menaikkannya di berkas itu.
| Tanpa webfont — `system-ui` sebagai `--font-sans` | Nol permintaan font, nol FOIT/FOUT, nol kontribusi ke CLS. Ia dicatat sebagai keputusan **privasi** di [`src/styles/global.css`](../../src/styles/global.css); ia juga keputusan performa | `src/styles/global.css` |
| Tanpa framework UI, tanpa framework CSS | JS terkirim mendekati nol pada sebagian besar halaman | [`standar-teknis.md`](standar-teknis.md#the-stack) |
| `compressHTML: true` | HTML lebih kecil sebelum kompresi transport | `astro.config.mjs` |
-| Kompresi respons memakai pustaka matang | Bukan hanya gzip: `compression` v1.8 menegosiasikan **Brotli** (RFC 7932) saat browser memintanya, dan Brotli mengalahkan gzip sekitar 15–20% pada HTML | [`server/penyaji.mjs`](../../server/penyaji.mjs) |
+| Kompresi respons memakai pustaka matang | Bukan hanya gzip: `compression` v1.8 menegosiasikan **Brotli** (RFC 7932) saat browser memintanya, dan Brotli mengalahkan gzip sekitar 15–20% pada HTML. Keberadaannya di `dependencies` adalah keputusan mempertahankan yang disengaja, bukan runtime Node.js yang menyelinap kembali — ia berjalan sepenuhnya di atas Bun, digerbangi dari sisi mempertahankan oleh [ADR-0050](../adr/0050-bun-is-this-repos-only-runtime-and-a-gate-finally-says-so.md) | [`server/penyaji.mjs`](../../server/penyaji.mjs) |
| `Cache-Control` dua aturan | Aset ber-hash `immutable` satu tahun; HTML `max-age=0, must-revalidate` sehingga rebuild langsung terlihat. Keduanya sesuai RFC 9111, dan keduanya **dibuktikan tes** — termasuk paritas GET/HEAD yang pernah membuat `curl -I` melaporkan nilai yang salah | [`tests/penyaji.test.mjs`](../../tests/penyaji.test.mjs) |
| Konten ditarik saat build | Nol panggilan ke CMS saat pembaca meminta halaman; situs tetap tayang saat `awcms` mati | [ADR-0018](../adr/0018-kontrak-build-token-mesin-dan-traversal-konten.md) |
| Media di-resolve **sekali per build** | Situs 300 artikel × 2 locale tidak berubah menjadi ratusan permintaan HTTP saat render | [`src/lib/content.ts`](../../src/lib/content.ts) |
diff --git a/docs/awcms-astro/standar-performa-dan-keamanan.md b/docs/awcms-astro/standar-performa-dan-keamanan.md
index e0ccfe9..21a1bec 100644
--- a/docs/awcms-astro/standar-performa-dan-keamanan.md
+++ b/docs/awcms-astro/standar-performa-dan-keamanan.md
@@ -210,7 +210,7 @@ row 8.
| No webfonts — `system-ui` as `--font-sans` | Zero font requests, zero FOIT/FOUT, zero contribution to CLS. It is recorded as a **privacy** decision in [`src/styles/global.css`](../../src/styles/global.css); it is also a performance decision | `src/styles/global.css` |
| No UI framework, no CSS framework | JS shipped is near zero on most pages | [`standar-teknis.md`](standar-teknis.md#the-stack) |
| `compressHTML: true` | Smaller HTML before transport compression | `astro.config.mjs` |
-| Response compression uses a mature library | Not only gzip: `compression` v1.8 negotiates **Brotli** (RFC 7932) when a browser asks for it, and Brotli beats gzip by roughly 15–20% on HTML | [`server/penyaji.mjs`](../../server/penyaji.mjs) |
+| Response compression uses a mature library | Not only gzip: `compression` v1.8 negotiates **Brotli** (RFC 7932) when a browser asks for it, and Brotli beats gzip by roughly 15–20% on HTML. Its presence in `dependencies` is a deliberate keep, not the Node.js runtime creeping back in — it runs entirely under Bun, gated from the keep side by [ADR-0050](../adr/0050-bun-is-this-repos-only-runtime-and-a-gate-finally-says-so.md) | [`server/penyaji.mjs`](../../server/penyaji.mjs) |
| `Cache-Control` in two rules | Hashed assets `immutable` for a year; HTML `max-age=0, must-revalidate` so a rebuild is immediately visible. Both per RFC 9111, and both **proven by tests** — including the GET/HEAD parity that once made `curl -I` report the wrong value | [`tests/penyaji.test.mjs`](../../tests/penyaji.test.mjs) |
| Content pulled at build time | Zero calls to the CMS when a reader asks for a page; the site stays live when `awcms` is down | [ADR-0018](../adr/0018-kontrak-build-token-mesin-dan-traversal-konten.md) |
| Media resolved **once per build** | A 300-article × 2-locale site does not turn into hundreds of HTTP requests at render time | [`src/lib/content.ts`](../../src/lib/content.ts) |
diff --git a/docs/awcms-astro/standar-teknis.id.md b/docs/awcms-astro/standar-teknis.id.md
index 5b1f26e..e842531 100644
--- a/docs/awcms-astro/standar-teknis.id.md
+++ b/docs/awcms-astro/standar-teknis.id.md
@@ -1,6 +1,6 @@
🇮🇩 Bahasa Indonesia · 🇬🇧 [English (source)](standar-teknis.md)
-
+
# awcms-astro — Standar Teknis
@@ -13,7 +13,7 @@ Kata **wajib** di dokumen ini berarti pelanggarannya menggagalkan gerbang mutu,
| Aspek | Keputusan | Alasan |
| --- | --- | --- |
| Framework | Astro 7, `output: 'static'` | Nol JavaScript pengiriman default; halaman tetap terbaca penuh tanpa JS |
-| Runtime | Bun `>=1.3.0`, ditegakkan `engines.bun` + `packageManager` | Satu runtime untuk seluruh keluarga AWCMS (ADR-0015); `bun.lock` satu-satunya lockfile |
+| Runtime | Bun `>=1.4.0`, ditegakkan `engines.bun` + `packageManager` | Satu runtime untuk seluruh keluarga AWCMS (ADR-0015); `bun.lock` satu-satunya lockfile. Batas bawahnya naik dari `>=1.3.0` pada 5 September 2026: Bun 1.4 menulis `bun.lock` sebagai `lockfileVersion: 2`, yang tidak bisa diurai sama sekali oleh Bun yang hanya memenuhi `>=1.3.0` — jadi batas lama menerima versi yang justru tidak bisa menginstal repo ini |
| Framework UI | **Tidak ada** | Interaktivitas ditulis DOM vanilla. Tidak ada React/Vue/Svelte |
| Styling | Satu berkas CSS global + design token | Tanpa framework CSS; token di `:root`, tema gelap lewat `data-theme` |
| Konten | **Ditarik dari `awcms` saat build** (ADR-0018); standar keluarga menyebut markdown per koleksi per locale | Kontraknya `LocalizedArticle` di `src/lib/content.ts`, bukan frontmatter |
@@ -190,7 +190,7 @@ Cara mencapainya — dan ini yang mengikat:
- **Tidak ada webfont.** `--font-sans` adalah `system-ui`: nol permintaan font, nol FOIT/FOUT, nol kontribusi ke CLS. Ia dicatat sebagai keputusan privasi di `src/styles/global.css`; ia juga keputusan performa, dan sebuah situs yang menambahkan font wajib men-self-host-nya di `public/` alih-alih menambah origin ke jalur render kritis.
- Gambar layar pertama `loading="eager"` + `fetchpriority="high"`; sisanya `loading="lazy"`. Keduanya dibutuhkan dan tidak saling menggantikan: `eager` hanya berarti "jangan tunda", sementara prioritas bawaan sebuah `
` tetap **Low** sampai layout membuktikan ia di viewport. Ditegakkan `bun run audit:konten` atas keluaran, jadi `
` yang tidak lewat `src/components/Ilustrasi.astro` ikut tertangkap.
- **Tema dipasang berkas eksternal `public/tema.js` yang dimuat `