بنقرة واحدة
awcms-mini-service-catalog
Kerjakan bagian mana pun dari modul service_catalog AWCMS-Mini (Issue
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
القائمة
Kerjakan bagian mana pun dari modul service_catalog AWCMS-Mini (Issue
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
استنادا إلى تصنيف SOC المهني
| name | awcms-mini-service-catalog |
| description | Kerjakan bagian mana pun dari modul service_catalog AWCMS-Mini (Issue |
service_catalog (src/modules/service-catalog, Issue #870, epic #868 SaaS
control plane Wave 1, ADR-0022) adalah modul control-plane PERTAMA —
Official Optional Business Foundation, opt-in per tenant, default-disabled.
Katalog plan/offer SaaS berversi provider-neutral. Bukan ERP item/product
master (ADR-0013 §3). Baca src/modules/service-catalog/README.md +
docs/adr/0022-*.md sebelum mengubah.
Default-disabled = MEKANISME + GATE, bukan prosa (§7/Medium-3). Modul ini
memperkenalkan ModuleDescriptor.defaultTenantState +
isModuleTenantEnabledByDefault (_shared/module-contract.ts), dibaca di
EMPAT titik resolusi: resolveModuleEnabled (auth-context, gate API/route),
SSR permission gate (lib/auth/ssr-session.ts), nav registry, tenant-module
matrix. Tanpa baris awcms_mini_tenant_modules eksplisit, ketujuh key
control-plane resolve disabled. Gate:
tests/unit/module-governance-default-disabled.test.ts — GAGAL bila salah
satu control-plane key resolve enabled tanpa baris. Modul control-plane baru
WAJIB set defaultTenantState: "disabled" + masuk daftar gate.
Boundary control-plane ↔ tenant-plane (ADR Consequences).
tests/unit/module-boundary.test.ts menegakkan: (a) tak ada modul lain
meng-import service-catalog/application|domain (tenant-plane baca lewat
port service_catalog_read saja, di-wire di composition-root-nya sendiri);
(b) no-shared-table-write — hanya modul service_catalog menulis
awcms_mini_service_catalog_*; (c) port file = neutral ground. Pola ini
registry-wide; modul control-plane #871+ tinggal tambah key/prefix-tabelnya.
RLS-free HANYA baris published + effective-dated (§3/Medium-1). SEMUA
tabel katalog GLOBAL (no tenant_id, terdaftar di RLS_FREE_TABLES +
ALLOWED_GLOBAL_TABLE_GRANTS di scripts/security-readiness.ts). TAPI:
Tier A (authoring: plans/versions/features/quotas/prices — termasuk draft &
harga internal) operator-only + default-deny; Tier B
(awcms_mini_service_catalog_published_offers) tenant-readable, secara
fisik tak bisa memuat draft/harga-internal (hanya published + subset harga
public). service_catalog_read HANYA baca Tier B. Least-privilege: DELETE
di-revoke pada ..._plans + ..._published_offers.
Offer published IMMUTABLE (§3). Ditegakkan 2 lapis: application
(plan-directory.ts tolak edit versi non-draft) + DB BEFORE trigger
(sql/079: guard_version_immutability + guard_child_immutability).
Koreksi = versi BARU (createDraftVersion → edit → publish), bukan edit
in-place. Mutation-guard test membuktikan gate nyata.
Fail-closed key registry (§security). Sejak #874 domain/key-registry.ts
me-RE-EXPORT SINGLE SOURCE src/modules/_shared/saas-contract-registry.ts
(bukan lagi agregasi privat). module key = listModules() keys; feature/meter
key = union rich descriptor ModuleDescriptor.serviceCatalog.{features,meters}.
Key tak dikenal → DITOLAK (bukan diterima diam) di draft-edit/validate/publish.
Kontribusi lewat modul domain di src/modules/ (registry base) — descriptor
serviceCatalog di module.ts masing-masing, jangan edit base registry. Untuk
bentuk descriptor lengkap (quota/commercial-event, validasi fail-closed,
versioning) pakai skill awcms-mini-saas-contracts.
Uang EXACT minor-unit. amount_minor bigint, limit_value bigint — NO
float/double. Validasi Number.isInteger + bound. Harga per komponen wajib
currency == currency versi.
Idempotency + audit + event. create/versions/publish/retire wajib
Idempotency-Key (hash BER-resource-id: planKey[+version]). publish/retire
emit awcms-mini.service-catalog.offer.{published,retired} (v1.0) via
appendDomainEvent dalam tx yang sama + recordAuditEvent. Event terdaftar
di domain-event-runtime/domain/event-type-registry.ts + AsyncAPI channel +
operation + module.ts events.publishes (parity di-gate).
SETIAP operator write path di application/plan-directory.ts wajib pola SAMA —
tak ada satu pun check-then-write tanpa lock:
SELECT ... FOR UPDATE pada row yang
akan ditransisikan DULU: publish/retire lock row version
(loadVersionByPlanKeyForUpdate); updatePlanDraft lock draft version
(loadDraftVersionForUpdate) SEBELUM sentuh plan header (Codex-D: cegah
projeksi published simpan plan_name/type LAMA saat header di-PATCH konkuren);
createDraftVersion lock row plan (loadPlanIdForUpdate) sebelum cek
draft_exists (Codex-E: cegah 2 draft konkuren → unique violation 500).UPDATE ... WHERE id=? AND status=<expected>;
0-row = konflik idempoten bersih → caller kembalikan 409 deterministik,
TANPA event/audit kedua.ON CONFLICT DO NOTHING RETURNING. Bila belum ada
row utk di-lock (createPlan): concurrent same-key → 0-row → duplicate_key
(409) bersih, bukan raw 23505 (500).Deteksi unique violation manual (jika perlu catch): String(error.errno) === "23505" (pola repo, profile-identity/application/identifier-directory.ts).
Tiap path punya test konkurensi (1 pemenang + 1 409 bersih; publish/retire tepat
1 event + 1 audit) di service-catalog.integration.test.ts.
Dari audit adversarial 5-lensa (10 defect di PR #885). Terapkan ke SEMUA modul control-plane:
offerHash disimpan di projeksi + dikembalikan service_catalog_read → JANGAN hash
data operator-only (harga internal) lalu ekspos = ORACLE brute-force. Hash atas
publicPrices+features+quotas+metadata saja. Properti: hash berubah IFF offer
tenant-visible berubah.version_id change + cek KEDUA parent OLD&NEW),
plan_key (immutable), published_offers (semua kolom kecuali retired_at).
Child trigger pakai COALESCE(NEW,OLD) bug: WHERE id=COALESCE(NEW.version_id,OLD.version_id)
pada UPDATE selalu resolve NEW → OLD parent (published) tak dicek → reparent bypass.IdempotencyRaceLostError.replay tak terpicu → loser dapat 409 padahal operasi SUKSES
(oleh winner same-key). FIX: di cabang business-conflict, panggil
replayConcurrentIdempotentWinner (re-query findIdempotencyRecord) SEBELUM 409 → replay
200 winner bila hash cocok. Wire di SEMUA route mutasi. Helper di _shared/idempotency.ts.asBool(x, true) kembalikan true utk non-boolean → feature yang dimaksud OFF tersimpan ON.
x || default coerce empty-string → default. FIX: default HANYA saat ABSENT ("key" in record);
nilai HADIR diteruskan verbatim → validator domain tolak (typeof boolean / enum includes).
Terapkan enabled/trialEnabled/isUnlimited (boolean) + interval/resetPolicy/planType/visibility/
featureKind (enum). Pakai strict === true di logika (truthy string jangan lolos).SET col = CASE WHEN <provided> THEN <new> ELSE col END — tulis hanya kolom yang disediakan (ELSE baca nilai live di bawah row-lock UPDATE).published_offers yang tenant-visible
(header+konten+publicPrices); GATE PROJECTION_COLUMN_TO_HASH_FIELD vs kolom nyata (test
information_schema) + behavioral (ubah tiap field → hash beda). Tambah kolom projeksi = paksa
keputusan hash. Pelajaran: cakupan hash di-GATE vs kolom nyata, jangan diasumsikan.{"prices":{...}} (objek bukan array) → parseArray coerce [] → field PRESENT →
updatePlanDraft DELETE prices existing + insert nol = DATA LOSS diam. FIX: parseCollectionPresent
teruskan non-array verbatim (validator Array.isArray tolak 400) + present-key detection (absent=[]
create/keep patch, present-valid=replace, present-malformed=REJECT jangan []). Metadata non-object
juga (isPlainObject). Bedakan ABSENT vs PRESENT-MALFORMED di SEMUA field (scalar+enum+collection+object).retired_at IS NULL=active) walau version
'retired'. FIX: trigger izinkan HANYA NULL→non-null; tolak non-null→NULL & non-null→beda. Audit
field write-once lain (published_at/by, retired_at/by, status) — pastikan semua one-way.asStringOrNull/asNumberOrNull coerce present-wrong-type→null → di PATCH dianggap explicit
CLEAR → HAPUS data (availability/description/notes) diam. FIX tri-state KETAT tiap nullable
(description/market/availableFrom/availableTo/notes/trialDays/limitValue): ABSENT(!(key in))→keep,
explicit null→clear, PRESENT wrong-type→REJECT 400 (keep raw, validator cek typeof). isValidTimestamp
typeof==="string" (Date.parse/.test/.length coerce non-string
diam). Setelah ini SEMUA tipe (scalar/enum/boolean/collection/object/nullable) fail-closed tri-state.published + plan_key/version COCOK source (cegah insert offer utk
DRAFT/mismatch = bocor draft ke tenant). UPDATE → hanya retired_at one-way. DELETE → revoke grant.
Konfirmasi 3 DML tertutup saat modul tenant-readable-projection mendarat.draft → (validate) → published → retired → (archived). Satu draft per plan
(partial unique index ...one_draft_idx). Publish: validasi → freeze status +
offer_hash + published_at → INSERT snapshot ke projection (public price
saja). Retire: status + retired_at + projection retired_at (baris tetap
readable — "existing published versions remain readable").
bun run check PENUH dengan PostgreSQL nyata (DB terisolasi, lihat memory
scratch-db-verify-when-shared-db-poisoned + local-postgres-connection-details).
Jangan lupa regen: openapi:bundle, api:docs:generate, i18n:extract,
repo:inventory. Doc 01/13/21 + module-doc-reconciliation + module-skill- coverage di-gate saat menambah modul/migration.
Kelola/konsumsi sistem Module Management AWCMS-Mini (registry, validasi komposisi registry base, tenant lifecycle enable/disable, settings, permission sync/status, navigation, job registry, health/readiness). Gunakan saat menambah field descriptor baru (permissions/navigation/settings/jobs/health) di modul lain, saat menambah modul domain baru langsung di `src/modules/` dan perlu memverifikasi registry base tetap valid (`bun run modules:compose:check`), saat menyelidiki kenapa suatu modul terlihat degraded/orphaned, atau saat mengubah perilaku enable/disable/settings/health module_management sendiri. Sesuai src/modules/module-management/README.md, epic
Pilih dan jalankan profil deployment AWCMS-Mini (development/staging/production/offline-LAN). Gunakan saat menyiapkan deployment baru, memutuskan LAN-first vs registry-based, atau deploy ke Coolify. Sesuai doc 18 dan deployment-profiles.md/deploy-coolify.md.
Kelola sistem log/audit/metrics AWCMS-Mini yang sudah aktif — correlation ID lintas hop, retensi/purge audit log, extension point untuk consumer eksternal (alerting/export/SIEM), dan metrics port (counter/histogram/gauge berkardinalitas rendah untuk request/pool/job/provider). Gunakan saat menambah endpoint baru (correlation ID otomatis), menjadwalkan purge audit log, memasang consumer log/audit di aplikasi turunan, atau menambah/mengonsumsi metrik operasional. Berbeda dari awcms-mini-audit-log (APA yang wajib diaudit) — skill ini tentang BAGAIMANA sistem log/audit/metrics itu sendiri dikelola, sesuai Issue
Kerjakan bagian mana pun dari modul tenant_provisioning AWCMS-Mini (Issue
Triase dan perbaiki temuan CodeQL code scanning AWCMS-Mini (github.com/ahliweb/awcms-mini/security/code-scanning). Gunakan saat diminta "analisis code scanning"/"perbaiki CodeQL", saat sebuah PR gagal check CodeQL, atau saat menemukan alert baru. Mendokumentasikan satu TRUE-positive terkonfirmasi (password sungguhan → digest cepat yang dipersist = oracle cracking offline, plus dua "fix" yang membungkam alert tanpa memperbaiki apa pun) dan lima false-positive nyata (name-heuristic password, incompatible-types typeof/null, URL substring-sanitization di test mock, dua kasus dismiss resmi tanpa reformulasi kode, dan Bun.SQL tagged-template null-cast) plus pola "unused-local-variable di test kadang menandai coverage gap" — supaya tidak diinvestigasi ulang dari nol.
Daftarkan tabel bervolume tinggi ke registry lifecycle AWCMS-Mini (retensi/partisi/arsip/legal hold/purge), atau kerjakan bagian mana pun modul data_lifecycle sendiri. Gunakan saat menambah tabel baru yang tumbuh besar (log, telemetry, outbox, queue, provider attempt) dan butuh kebijakan retensi/purge aman, saat membuat/melepas legal hold, atau saat mengubah engine dry-run/archive/purge/archive-port. Sesuai Issue