v6.3.0
Minor Changes
-
156a7b6: Emit edge-cache invalidation from
theming, and enforce the obligation by
surface ownership.themingowns thetheming-tokenssurface (/theming/{tenantCode}/tokens.css),
so publish, rollback, and retire each change what a cached object contains.
All three now callenqueueModuleContentPurgeinside the same transaction as
the change (ADR-0042 §9 / ADR-0006).news_portalandmedia_librarydeliberately do not. Neither owns a
declared surface, so nothing cached is taggedm:news_portalor
m:media_library— a ban for those keys matches no object while the queue
recordssent=1. Adding them now would be ceremony that reads as coverage and
provides none.bun run edge-cache:surfaces:checknow demands a purge call site from every
module that owns a declared surface, resolving*_MODULE_KEYconstants across
files. Framing it by ownership rather than by a hand-kept module list means the
obligation appears on its own the daynews_portalormedia_librarydeclares
a surface, and stays silent until then.The asymmetry this closes: declaring a surface is one line and takes effect
immediately; wiring its invalidation is a separate edit in another file that
nothing forced. Miss it and the surface caches correctly, serves correctly, and
never updates — with no error anywhere.
Patch Changes
-
156a7b6: Stop
Acceptedadmission ADRs from reading as shipped modules.Five ADRs — 0016
organization_structure, 0017document_infrastructure, 0018
data_exchange, 0019integration_hub, 0021reference_data— areAccepted
for modules with no code in this repository.Acceptedis a decision status, not
a delivery status, but nothing said so, and the roadmap already named the
consequence: someone readingdocs/adr/"will concludeorganization_structure
can be called. It cannot."Not hypothetical. ADR-0020 asserted
reference_dataisstatus: "active"in the
registry, citing a merged PR number — true ofawcms-mini, where the sentence
came from, and false here. Corrected.Each of the five now carries an unmissable not-implemented block naming what is
absent and pointing at Wave A of the absorption roadmap.tests/adr-admission-implementation-status.test.tsbinds the two facts, which
otherwise live in different places and move independently: an admitted module
must be inlistModules()or its ADR must carry the marker. It fails in both
directions — landing a module while the marker remains is caught too — and it
asserts separately that no ADR claims an absent module is active in the registry,
since prose copied between family repos is the likely source of the next
instance. No database, so it runs on every PR. -
bfd9638: Pin the default tenant per environment, and state the owner-account convention
for all three phases.PUBLIC_DEFAULT_TENANT_ID/_CODEare now set in staging and production rather
than left to the end of the resolution chain. Unset still worked — the chain
terminates atawcms_setup_state.tenant_id— but that makes "which tenant does
an unmatched host resolve to?" an implicit answer living in a table rather than a
stated one, and it silently becomes the wrong answer the moment a second tenant
exists. The consumers are real:seo_distribution(/robots.txt, sitemap, feeds)
andsite_search.PUBLIC_TENANT_RESOLUTION_MODEis deliberately left unset. Production does have
anawcms_tenant_domainsrow forawcms.ahlikoding.com, sohost_defaultwould
work — but enabling host lookup widens the reachable surface and is its own
decision, not part of "set the default tenant".Documents the owner convention across development, staging and production: the
login identifieradmin@ahlikoding.comis shared, the password never is.
awcms_identitiesis unique on(tenant_id, login_identifier), so one address in
three environments is three unrelated accounts with three password hashes and
threeAUTH_JWT_SECRETs.Also records the permission-seed gap where it will actually be read, with the
backfill SQL: a seed migration reaches only tenants created after it, so landing a
module does not grant its permissions to an existing owner — the symptom is a 403
on a module that is plainly installed. Plus the queries that show whether "full
access" is genuinely full, since RBAC 197/197 means nothing if an ABAC deny, an
SoD rule, or a business-scope constraint is in play. -
2e907a5: Samakan environment development dengan staging/produksi, dan buang variabel env
hantu dari dokumentasi.Development sebelumnya bukan versi kecil produksi melainkan environment yang
berbeda secara diam-diam: skema berhenti di migrasi 30 (produksi 70), nol
tenant, tanpa.env, dan satu-satunya role ber-LOGIN adalah superuser milik
container — sehinggaFORCE RLSinert dan justru bug termahal (kebocoran
tenant, 403 permission) yang paling mustahil direproduksi di sana. Dev kini
cocok baris per baris: migrasi 70, 118 tabel, 197 permission, RLSENABLE+FORCE
109/118, runtime sebagaiawcms_app, ownerowner197/197 — dengan perbedaan
yang disengaja (AUTH_COOKIE_SECURE,TRUSTED_PROXY_ENABLED,EDGE_CACHE_MODE)
dicatat beserta alasannya.Dokumentasi menyebut
AUTH_JWT_SECRETsebagai variabel wajib di lima berkas.
Variabel itu tidak ada di awcms — tidak dibaca kode mana pun, dan tidak ada
JWT di jalur sesi (token acak buram ber-hash sha256 diawcms_sessions).
Klaimnya bukan sekadar usang: ia menopang pernyataan keamanan bahwa tiga
environment terisolasi sebagian karena masing-masing punya JWT secret sendiri.
Operator yang mengikutinya akan menyetel variabel yang tidak berefek apa pun.
APP_TIMEZONEjuga tercantum wajib dan sama-sama tidak ada.tests/env-required-vars-doc.test.tsmengikat daftar wajib di
deployment-profiles.mdkeRULESdiscripts/validate-env.ts, menolak
kemunculan ulangAUTH_JWT_SECRETsebagai variabel hidup, dan memverifikasi
kedua nama itu memang tak pernah dibaca kode — empat mutasi terbukti merah. -
4c2459d: Sapu drift docs/skill terhadap kode, dan pasang dua gate supaya kelasnya tidak
kembali.Lima klaim yang salah, bukan sekadar usang:
awcms-data-lifecyclemenyebutform_drafts/comments"DITUNDA (modul belum
di-port)" — keduanya sudah di-port dan keduanya adopterdelegated. Skill itu
juga menyebut 2 adopter padahal ada 10 deskriptor di 7 modul; agen yang
mengikutinya akan melewatkan guard legal-hold pada tabel yang mewajibkannya.awcms-themingmenyebutmedia_library"di-drop — belum ada di base", dan
menerangkan ketiadaan purge preview dengan "data_lifecycletidak ada di base
ini; tak adaawcms_worker". Ketiganya ada.awcms-wizard-formmenyebutform_draftsbelum di-port.awcms-module-managementmelaporkan "17 modul (dari 23)" mendeklarasikan
permissions, dengan daftar yang tujuh di antaranya milik awcms-mini. Angka
nyata: 20 dari 21, dan satu-satunya pengecualian adalahemail.- Lima dokumen menyatakan total yang tertinggal (
sql/001–067, "65 migrasi",
"20 modul") — termasuk paragraf direpo-inventory.mdyang tugasnya justru
MENGOREKSI klaim usang. Koreksi yang ikut usang lebih buruk dari aslinya: ia
terbaca seperti baru saja diverifikasi.
src/lib/theming/theme-media.tspunya kembaran klaim itu di kode — header
seam-nya menerangkan resolusi asset no-op karenamedia_librarytidak ada.
Modulnya ada, lengkap dengan adapter nyata yang sudah dipakaiblog_contentdan
news_portal. Akibat yang terlihat pengguna dan sebelumnya tidak tercatat di
mana pun: tenant bisa mengunggah logo, id-nya tersimpan, dan tema tetap merender
fallback nama-tema. Header-nya kini menyatakan itu; wiring adapternya tetap
pekerjaan tersendiri.domain-event-runtime/infrastructure/consumer-registry.tsjuga: header-nya
menyatakan consumerreporting"intentionally NOT ported (they would import
modules that are absent)" sementara berkas yang sama meng-importreportingdi
baris 8. Sekaligus mencatat cycle level-modul yang tak terlihat gate mana pun —
reportingmendeklarasikandomain_event_runtime, dan modul ini meng-import
reporting;modules:dag:checkmemvalidasi deklarasi saja (registry murni,
tanpa I/O by design), jadi import tak-terdeklarasi tak terlihat, dan
mendeklarasikannya secara jujur justru membuat gate itu merah karena cycle.Dua gate baru, keduanya mutation-proven:
tests/module-absence-claims.test.ts(tidak ada dokumen/skill yang boleh
menyangkal modul terdaftar) dantests/doc-inventory-counts.test.ts(total modul
dan rentangsql/001–NNNharus cocok dengan repo). -
c44d4ee: Stop tracking graphify's dated backup directories.
Every
graphifyrebuild writes a full copy of the curated graph to
graphify-out/<YYYY-MM-DD>/— roughly 12 MB of duplicate JSON per run. The
previous refresh happened not to commit one;.gitignorenow makes that a rule
rather than something whoever stages the change has to notice.The live artifacts beside it (
graph.json,graph.html,GRAPH_REPORT.md,
manifest.json) stay tracked — those are the reviewable output. -
156a7b6: Make the migration layer visible to the knowledge graph, and stop tracking
graph.html.tree_sitter_sqlwas missing, so all 70 files insql/contributed nothing
to the graph — the layer that holds every RLS policy, every grant, and every
tenant-isolation predicate was simply absent. Three defects fixed this week lived
there, and the graph could not have helped find any of them. With the grammar
installed the graph gains 179 nodes and 153 edges, including the tables
themselves (awcms_tenants,awcms_offices, …) rather than just file names.Note for anyone rebuilding: graphify keys its cache on
manifest.json, not on
cache/stat-index.json. Installing a new grammar does not invalidate anything,
so--updatereports every file unchanged and the new grammar never runs. The
entries have to be dropped frommanifest.jsonto force re-extraction.graph.htmlis no longer tracked. It silently stops being emitted once the
corpus passes graphify's viz node limit — the committed copy then rots while
graph.jsonbeside it stays current, which is precisely the failure mode this
repo keeps getting bitten by. It is also ~8.7 MB per rebuild on top of
graph.json's ~10 MB, doubling what each refresh adds to history permanently.
Regenerating is one command, documented in.gitignorenext to the rule.