一键导入
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 职业分类
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
| 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.