v6.1.0
Minor Changes
-
eb5519a: Reposisi governance AWCMS (ADR-0035, menyempurnakan positioning ADR-0034 —
docs/adr/0035-awcms-online-first-erp-saas-superset-repositioning.md):awcmskini diposisikan sebagai template online-first hybrid (online jalur utama; offline/LAN mode ketahanan), siap ERP + SaaS terintegrasi, dan superset keluarga yang menyerap klaster website/e-commerce, UI/UX, dan pengerasan authawcms-microlangsung kesrc/modules/.awcms-minitetap hybrid offline-first (siap SaaS);awcms-microtetap template website full-online ramping. Model tata kelola dipakai-langsung/tanpa-repo-turunan (ADR-0034 §2/§3) tidak berubah.Perubahan dokumentasi/governance saja (tanpa perubahan kode runtime): ADR-0035 baru + banner supersede-parsial di ADR-0034; reposisi README/README.id/AGENTS/PROJECT_STATE + paket
docs/awcms/(01/06/09/10/12/13/15, alur-pengembangan-mini-first, README index, api-contribution-guide); manifestawcms-family-compatibility.yaml(role+ rasional divergence Turnstile diselaraskan ke mode hybrid); dokumen peta barudocs/awcms/absorb-awcms-micro-roadmap.mduntuk penyerapan bertahap awcms-micro. -
c25e795: Port the redirect-governance scope of
seo_distributionfrom awcms-micro (ADR-0039, companion to ADR-0038 —docs/adr/0039-seo-distribution-redirect-governance.md), completing the module whose discovery half shipped in ADR-0038. Adds tenant-contained exact-path redirect rules (301/302/307/308), URL-change capture into audited redirect proposals, privacy-minimized 404 telemetry, and the admin API under/api/v1/seo/redirects/*+/api/v1/seo/not-found/*.- Migrations 060 (schema) + 061 (permissions). Three tenant-scoped tables (
awcms_seo_redirects,awcms_seo_not_found_observations,awcms_seo_redirect_settings), allENABLE+FORCE ROW LEVEL SECURITY+tenant_isolation; the 404 table has adataLifecycleanalytics_telemetry descriptor (seo_distribution.not_found_observations, generic purge, 30d default) with aSELECT, DELETE ... TO awcms_workergrant. Six new permissions (redirect.{read,create,update,delete},not_found.{read,update}). - One invasive
src/middleware.tsedit — the non-/adminbranch resolves a public redirect BEFORE serving and records a best-effort 404 observation AFTER. FAIL-OPEN: the resolver swallows all faults to null (never a 500), the 404 capture never throws; the/adminlogin guard and API body-ceiling are untouched. Wiring lives in the importablesrc/lib/seo/redirect-middleware.ts. - Open-redirect / loop / hijack defenses — the frozen
classifyRedirectTarget/assertSafeRedirectTargetguard is re-homed as a standalone domain helper (redirect-target-classification.ts), NOT re-added to theseo_factsport, and enforced on write AND every resolve; normalization rejects CRLF/traversal/Unicode-confusion/protocol-relative; chains are bounded + non-recursive (fail-closed on loop/over-cap); the eligibility gate excludes admin/API/auth/static/system/discovery paths. - Adaptations (documented in ADR-0039): tenant resolution is host-based-only first cut (path-tenant deferred); the legacy
/blog/{tenantCode}→/newsrewrite is INERT (no/newsroute family, policy off by default);localeis always null (awcms has no i18n seam).seo_distributionbumped 0.1.0 → 0.2.0.
- Migrations 060 (schema) + 061 (permissions). Three tenant-scoped tables (
-
0dce625: Media-library ownership inversion (ADR-0036, mengadaptasi awcms-micro ADR-0026 —
docs/adr/0036-media-library-module-admission-ownership-inversion.md).CAPABILITY RETIREMENT (bukan bump minor kapabilitas): capability
news_mediadipensiunkan dan digantikanmedia_library. Penyedianya berubah (news_portal→media_librarybaru) dan kontrak port kehilangan satu method (isFullOnlineR2ModeActiveForTenant→isManagedMediaEnforcementActiveForTenant;resolveMediaPublicBaseUrldi-drop)._shared/capability-contract-versions.ts+ manifestawcms-family-compatibility.yamlmenambahmedia_library: "1.0.0"; setiap konsumen yang dipin kenews_mediaharus gagal terang-terangan.Perubahan NON-aditif — menyentuh modul yang sudah di-ship:
- Modul baru
media_library(System Foundation,type: system,isCore: false, deps[tenant_admin, identity_access]): registry mediaawcms_news_media_objects(tabel TIDAK di-rename — FK komposit keras dari ad placements), presigned upload/finalize/cancel, MIME sniffing, verifikasi R2, jobnews-media:reconcile(nama command dipertahankan), plus penyalaan enforcement (POST/GET /api/v1/media/enforcement, satu arah, readiness-gated + audited). news_portaltidak lagi PROVIDESnews_media; kini CONSUMESmedia_library(wajib) +public_content; basePath berubah ke/api/v1/news-portal; job reconcile & 9 permission media pindah keluar.blog_contentconsumesmedia_library(opsional, dulunews_media); adaptor no-op media vestigial dihapus; gate media & 12 composition-root handler + worker menyuntikmediaLibraryPortAdapter.- Migrasi (ADD-only, urutan load-bearing):
052repoint permissionnews_portal.media.*→media_library.media.*(INSERT→repoint grant→DELETE),053tabelawcms_media_library_tenant_state(RLS ENABLE+FORCE + backfill dariawcms_news_portal_tenant_state),054permissionmedia_library.enforcement.{read,enable}. - Fragment OpenAPI media dipindah ke
openapi/modules/media-library.openapi.yaml(+ path enforcement); bundle + api-reference diregenerasi.
Diverifikasi terhadap PostgreSQL nyata: repoint permission bersih, RLS FORCE + isolasi tenant + fail-closed
awcms_app, dan backfill lintas-tenant (role migrasi BYPASSRLS). Step 5b/5c/5d micro (/admin/media, srcset, PDF) ditunda. - Modul baru
-
a777152: Port modul
blog_contentdari awcms-mini: manajemen blog/konten tenant-scoped (posts, pages, kategori/tag, riwayat revisi append-only, pencarian full-text, template/menu/widget/iklan presentasi, pengaturan blog, dan automatic internal tag linking). Menambahkan 6 migrasi baru (sql/035-sql/040, 15 tabel + seed 39 permission), ~40 route admin di/api/v1/blog/*, 7 route publik anonim di/blog/{tenantCode}/...(ADR-0009), job terjadwalbun run blog:publish:scheduled, serta fragment OpenAPI/AsyncAPI baru untuk modul ini.Dua kapabilitas opsional modul ini (
news_mediadarinews_portal,social_publishingdarisocial_publishing) belum punya provider nyata di base ini — setiap titik panggil memakai adapter no-op modul sendiri (mode full-online-R2-only selalu tidak aktif, hook social-publishing selalu no-op{ jobsCreated: 0 }), aman dan terdokumentasi, tanpa mengimpor modul yang belum ada. Keluarga rute/news/**(butuh modultenant_domainyang belum di-port) sengaja tidak diikutkan di port ini. -
cc52dce: Port modul
data_lifecycledari awcms-micro (Issue #745, ADR-0037) sebagai modul System Foundation net-baru aditif, PLUS re-wire kopling legal-hold dua konsumen (visitor_analytics,logging) yang di-drop saat port awalnya.- Seam kontrak (aditif, MINOR):
ModuleDescriptor.dataLifecycle?: HighVolumeTableDescriptor[]+ keluarga tipeLifecycle*di_shared/module-contract.ts(MODULE_CONTRACT_VERSION2.0.0 → 2.1.0, pin manifest keluarga diselaraskan). Registry dikontribusikan tiap modul pemilik, divalidasibun run data-lifecycle:registry:check(masuk rantaicheck) +security:readiness. - Modul (
src/modules/data-lifecycle/, 16 berkas): legal-hold (rules murni + service + guard-port adapter), lifecycle-registry, dry-run planner (zero-mutation), bounded archive/purge engine di worker runner bersama, archive port provider-neutral + local/offline adapter (JSONL/CSV + SHA-256), cursor/manifest/run stores, cursor-boundary safety margin (1ms, fix presisi timestamptz mikrodetik).type: system, deps[tenant_admin, identity_access, logging], 6 permission, jobdata-lifecycle:archive-purge, satu descriptorgeneric(tabel run-history sendiri), aturan SoD maker/checkerlegal_hold.createvs.release. - Skema (migrasi
055schema,056permission): empat tabel tenant-scoped (awcms_data_lifecycle_legal_holds/_cursors/_archive_manifests/_runs), semuaENABLE+FORCE ROW LEVEL SECURITY+ policytenant_isolation, CHECK konsistensi + index; GRANTawcms_workerleast-privilege — SELECT-only pada legal_holds (create/release tetap aksi admin/API), SELECT/INSERT/UPDATE pada cursors+manifests, SELECT/INSERT/DELETE pada runs. Non-destruktif (semuaIF NOT EXISTS). - Endpoint (
/api/v1/data-lifecycle/*): registry GET, dry-run POST (tanpa idempotency), runs GET, legal-holds GET+POST (Idempotency-Key + audit critical), legal-holds/{id}/release POST (Idempotency-Key + audit critical). Archive/purge nyata tidak diekspos lewat HTTP (job saja). Fragmen OpenAPI per-modul + bundle + api-reference diregenerasi. AccessActionbaru:release(HIGH-RISK — melepas hold menghapus safeguard perlindungan data).- Legal hold tidak bisa di-bypass diam-diam: dienforce pada RECORD hold aktif (bukan metadata
legalHold.applicable), dicek SEBELUM DELETE dan tanpa syarat. Untuk deskriptordelegated, fungsi purge modul pemilik adalah titik enforcement nyata viaLegalHoldGuardPort(_shared/ports/legal-hold-guard-port.ts, seam level-sumber, bukan capability-registry) yang di-wire di composition root. - Re-wire
visitor_analytics: descriptorvisitor_analytics.visit_events+ param ke-5legalHoldGuardpadapurgeVisitorAnalyticsDatamenggerbangi HANYA DELETE step-1awcms_visit_events(step 2-4 tetap tak-tergerbang); adaptor di-inject diPOST /api/v1/analytics/retention/purge+scripts/visitor-analytics-purge.ts. - Re-wire
logging: descriptorlogging.audit_events+ param WAJIBlegalHoldGuardpadapurgeExpiredAuditEventsmenggerbangi DELETE audit-events; adaptor di-inject discripts/audit-log-purge.ts. - Ditunda: konsumen
form_drafts/newsletter/comments(modul belum di-port).
- Seam kontrak (aditif, MINOR):
-
a777152: Port modul
news_portaldari awcms-mini: registry media objek R2-only tenant-scoped (awcms_news_media_objects) dengan alur presigned upload langsung-ke-R2 (create/finalize/cancel), homepage section composer editorial (awcms_news_portal_homepage_sections), ad placement preset R2-only (awcms_news_portal_ad_placements), state tenant mode R2-only (awcms_news_portal_tenant_state), dan job rekonsiliasinews-media:reconcile. Migrasisql/041..sql/045(empat tabel baru RLS ENABLE+FORCE). Modul MENYEDIAKAN capabilitynews_media— adapter nyata kini menggantikan no-op blog_content di seluruh composition root (route + workerblog:publish:scheduled) — dan MENGONSUMSIpublic_contentblog_content untuk validasi referensi homepage section. Rute publik/news/**(butuhtenant_domain), halaman admin.astro, dan aktivasi preset (butuh subsistem presetmodule_management) sengaja di-drop dan didokumentasikan. Menambah aksiverifyke unionAccessAction, grantawcms_workeruntuk job rekonsiliasi, dan skripnews-media:reconcile. -
c9baa0c: Port modul
seo_distribution— scope discovery — dari awcms-micro (ADR-0038, mengadaptasi awcms-micro ADR-0028; program penyerapan ADR-0035, Wave 1). Aditif net-baru; DAG tetap asiklik.Yang ditambahkan:
- Seam capability
seo_facts(_shared/ports/seo-facts-port.ts,CAPABILITY_CONTRACT_VERSIONS["seo_facts"]="1.1.0"): kontrak kontribusi beku (tipe fakta + guard JSON-LD terkontrol + predikat visibility + cache-key).blog_contentkiniprovides: ["public_content","seo_facts"]lewat adaptorapplication/seo-facts-port-adapter.ts(barisawcms_blog_posts→SeoResourceFacts; noindex/non-publik/belum-terbit →sitemap:null/feed:null).seo_distributionconsumesseo_facts(opsional) +media_library(opsional). - Modul
seo_distribution(type: domain, v0.1.0, deps Core-only): renderer metadata terpusat (canonical/hreflang/robots/OG/Twitter/JSON-LD terkontrol, host diturunkan server daritenant_domain), serializer sitemap/robots/feed, orkestrator discovery + validator cache (ETag/Last-Modified/304). - Route discovery publik tak-terautentikasi di root host:
/robots.txt,/sitemap.xml,/sitemap-{n}.xml,/feed.xml,/atom.xml,/feed.json(route Astro XML/text, bukan OpenAPI;src/middleware.tsTIDAK diedit). - Config admin tenant
GET/PUT /api/v1/seo/config(config.read/config.update, tenant-scoped,PUTidempoten + di-audit) + fragment OpenAPI + tag "SEO & Distribution". - Migrasi 057-059:
awcms_seo_tenant_settings(RLS ENABLE+FORCE +tenant_isolation), seed permission config, kolom config feed/sitemap. - Helper aditif
escapeXmlText+ varian errortext/plaindisrc/lib/html/*; env var publik didokumentasikan (PUBLIC_TRUST_PROXY,PUBLIC_TENANT_RESOLUTION_MODE,PUBLIC_DEFAULT_TENANT_*).
Ditunda ke PR lanjutan (tata-kelola redirect): aturan redirect + hook redirect middleware, tabel telemetri 404, descriptor
dataLifecycle, dan permissionredirect.*/not_found.*. - Seam capability
-
359bd7a: Port modul
tenant_domaindari awcms-micro (epic #555): pemetaan
hostname/subdomain → tenant untuk routing publik berbasis host (Wave-0 program
penyerapan awcms-micro). Menambah tabelawcms_tenant_domains(migrasi 046, tenant-scoped
ENABLE+FORCE ROW LEVEL SECURITY, unique hostname lintas-tenant, satu primary
per tenant), seed permissiontenant_domain.domains.*(migrasi 047), dan fungsi
lookup host→tenantawcms_resolve_tenant_domain_lookupSECURITY DEFINER
(migrasi 048). Fungsi ini di-own oleh role bootstrap khususawcms_domain_bootstrap
(NOLOGIN/NOSUPERUSER/NOBYPASSRLS, tanpa anggota) dengan policyFOR SELECT
ter-scope (USING (true)khusus role itu) sehingga bootstrap host→tenant tetap
resolve di deployment role-separated tempat owner migrasi bukan superuser
(mis.awcms_app/awcms_worker/awcms_setupdari sql/019–022, dan harness
integrasi yang men-demote owner-nya) — tanpa memberiBYPASSRLSke role apa pun,
tanpa melepasFORCE ROW LEVEL SECURITY, dan tanpa menyentuh policy
tenant_isolation.EXECUTEhanya keawcms_app; kolom sensitif
(verification_token_hash/verification_record_value) tetap tak terbaca.API manajemen tenant-scoped di
/api/v1/tenant/domains(list/create/read/
update/soft-delete +verifydanset-primaryyang ber-Idempotency-Keydan
diaudit), layar admin/admin/tenant/domains, resolver host publik ADITIF
(lib/tenant/public-host-tenant-resolver.ts— hidup berdampingan dengan
routing berbasis path/blog/{tenantCode}ADR-0009, tidak meregresi), dan
adapter Cloudflare DNS OPSIONAL (env-gated, aman tanpa kredensial, belum
di-wire ke rute mana pun).Deferral yang didokumentasikan: rute konten publik ber-resolusi host belum
di-wire (deferral yang sama seperti/news/**news_portal);src/middleware.ts
tidak disentuh (jaminan login/Turnstile/CSP tak berubah). UnionAccessAction
identity-access diperluas denganset_primary.Risiko residual (harden sebelum go-live self-service custom domain).
verify
saat ini mengaktifkan domain berdasarkan field in-row tanpa bukti kepemilikan
outbound (model manual-first; adapter DNS ada tapi belum di-wire). Untuk mencegah
pengambilalihan domain (dangling-DNS) pada custom domain bersama, aktivasi
custom_domainwajib digerbangi operator/manual sampai bukti kepemilikan
DNS-token (verification_token_hash+ cek TXT/CNAME lewat adapter) di-wire.
verifysudah default-deny + di-audit; risiko ini didokumentasikan di README modul
dan skillawcms-tenant-domain-routing. -
8c959ff: Port modul
visitor_analyticsdari awcms-micro (epic #617-#624) sebagai modul standalonetype: "system"(ADR-0035 Wave 1). Menambah statistik pengunjung manusia privacy-first untuk rute admin & publik, online maupun offline/LAN.- Skema (migrasi 049 permission, 050 schema, 051 session-lookup index):
awcms_visitor_sessions/awcms_visit_events/awcms_visitor_daily_rollups, semuaFORCE ROW LEVEL SECURITY+ policytenant_isolation, index tenant_id-first, composite FK(tenant_id, visitor_session_id)lintas-tenant, dan GRANTawcms_workerleast-privilege untuk job terjadwal. - Privasi: off by default (
VISITOR_ANALYTICS_ENABLED=false); visitor-key/IP/user-agent disimpan hanya sebagai HMAC-SHA256 bersalt (salt wajib saat enabled — ditegakkanvalidate-env); raw IP & login snapshot opt-in terpisah; query string sensitif di-strip fail-safe. - Koleksi = endpoint ingest PUBLIK
POST /api/v1/analytics/collect(anonim, resolve tenant daritenantCodetabelawcms_tenantsyang RLS-free — TANPA SECURITY DEFINER), bukan middleware:src/middleware.tstidak disentuh (jaminan login/Turnstile/CSP tetap). - API terautentikasi ABAC:
GET /api/v1/analytics/{summary,realtime,sessions,events,pages,devices,locations,security,settings},PATCH .../settings, danPOST .../retention/purge(Idempotency-Key + auditcritical). Raw-detail digerbangivisitor_analytics.raw_detail.read. - Job:
bun run analytics:rollup&bun run analytics:purge(worker role, offline-safe). - Dashboard
/admin/analytics(SSR-render).
Adaptasi port terdokumentasi: kopling
data_lifecycle/LegalHoldGuardPortDI-DROP (modul belum ada di base — purge tanpa gerbang legal-hold), dan wiring presetnews_portal_full_online_r2DEFERRED (modulnews_portaltidak disentuh).Security hardening (DoD + security review atas port ini):
- Rate-limit backstop pada beacon publik.
POST /api/v1/analytics/collect(unauth DB write) kini digerbangi rate limit per-IP (checkRateLimityang sama dengan login/setup) SEBELUM tulis DB — mencegah flooding baris/pencemaran agregat oleh pemegangtenantCodepublik. Kunci berbasis IP saja (tak membocorkan eksistensi tenant);pathdibatasi panjang sebelum disimpan. TunableVISITOR_ANALYTICS_COLLECT_RATE_LIMIT_MAX/_WINDOW_SEC(default 120/60s). - Salt HMAC per-tenant (privacy-by-design).
visitor_key_hash/ip_hash/user_agent_hashkini di-key dengan salt deployment DANtenantId(domain-separator\0), sehingga browser/IP/user-agent yang sama menghasilkan hash BERBEDA lintas tenant satu origin — menutup korelasi lintas-tenant di lapisan penyimpanan. Diterapkan mumpung belum ada data.VISITOR_ANALYTICS_HASH_SALTkini wajib ≥ 16 karakter saat modul aktif. - raw_detail lewat ABAC, bukan hanya keanggotaan RBAC. Field de-anonimisasi (
ipHash/ipAddress/userAgentHash/loginIdentifierSnapshot) diGET /sessions,GET /events, dan/admin/analyticskini diputuskan lewat evaluator ABAC (evaluateFieldAccessInTransaction) sehingga kebijakan DSLdenyatasraw_detail.readdihormati (deny-overrides-allow). - Log fragmen IP mentah pada header forwarded multi-nilai dihapus (
client-ip.tshanya mencatatvalueCount, bukan nilai).
- Skema (migrasi 049 permission, 050 schema, 051 session-lookup index):
-
bc7c4fa: Overhaul UI/UX seluruh surface pengguna: mobile-first responsif, animasi profesional CSS-murni, dan aksesibilitas (WCAG AA,
prefers-reduced-motion, skip-link, target sentuh ≥44px). Semua di dalam jaminan CSP single-owner "zero third-party origin di LAN/offline" — tanpa font CDN/library eksternal; animasi = keyframes/transition CSS; styling di-serve same-origin (bundle Astro ataupublic/css/*.css), tidak ada<style>/<script>inline.- Design system (fondasi): perkaya
tokens.css(skala tipografi/spacing/radius/elevation, tint interaksi, token MOTION durasi+easing), tambah lapisan utility animasi reusablemotion.css(fade/scale/slide/stagger/hover-lift/skeleton/spinner), dan shell layout admin+publik responsif dengan drawer mobile CSS-only. - Login: redesign form + auto tenant picker — 1 tenant disembunyikan/prefilled, 2–50 dropdown nama tenant, >50 fallback manual (anti mass-enumeration), fail-closed ke input manual saat pre-setup/DB error. Tanpa endpoint publik baru; kontrak DOM login dipertahankan.
- Admin: 8 layar (
index/users/roles/offices/profiles/modules/abac-policies/email-templates) mobile-first — tabel lebar → pola kartu/stack (data-labelper sel), stat/quick-link beranimasi, hierarki visual & empty state konsisten. Selektor/hook E2E dipertahankan. - Blog publik (
/blog/{tenantCode}/...): tipografi baca nyaman (measure ~65ch), kartu post grid→stack, media/tabel/kode responsif, animasi entrance halus; renderercontent_jsonwhitelist-based tidak dilonggarkan.
- Design system (fondasi): perkaya
Patch Changes
- 2a1e73e: Perbaiki logika fallback media type di
scripts/api-spec-check.ts(CodeQL alert #140,js/trivial-conditional).asRecord()selalu mengembalikan objek non-null, sehingga operator??padaasRecord(content["application/json"]) ?? Object.values(content)[0]membuat cabang fallback jadi dead code — response error yang hanya memakai media type non-application/jsonsalah dilaporkan tidak beresolusi ke envelopeApiError. Nullish-coalescing dipindahkan ke dalamasRecordagar fallback ke media type pertama benar-benar berjalan. - 59757f1: chore: refresh the tracked graphify knowledge-graph output (
graphify-out/)
via/graphify --updateafter the project-state doc sync and agent-guide PDF
removal — 6664 nodes / 19913 edges / 330 communities. Artifact-only; no runtime
behavior change. - 4905024: Login card entrance is now transform-only (
@keyframes auth-card-rise,
translateY) instead of the shared.fade-in-uputility that fades from
opacity: 0. Fading the whole card — including its text — from transparent can
let an axe-core contrast scan read semi-transparent text as a contrast
violation if it scans mid-animation; a transform-only entrance keeps the text
fully opaque throughout. A localprefers-reduced-motionguard neutralises it
(motion.css's global reduced-motion block only targets its utility classes).
CSS/markup only — the DOM contract and login logic are unchanged. Documented as
the canonical rule in doc 14 §Motion / §Auth screen. - 4c42029: feat(tooling): port
memory:docs:syncfrom awcms-mini — snapshot the
out-of-repo Claude Code agent memory into a committeddocs/awcms/agent-memory.md
so it survives clones/device moves (sync/restore/check). Adapts the doc
path, header, password-placeholder redaction, and excludes the device-specific
local-Postgres memory. check:docs exempts the generated mirror; prettier ignores
it. Dev-tooling only — no runtime behavior change. - d5ec206: chore: track the graphify knowledge-graph output (
graphify-out/) so the repo
graph is viewable from a clone. Regenerable cache and machine/user-specific path
markers stay gitignored; the artifacts are excluded from prettier.