Aller au contenu

Architecture - Cross-Module Progress Log

Living document. Each ARCHITECT session appends a module entry and updates the cross-module decision table at the top.
Read this BEFORE every new architecture session - do NOT re-decide locked patterns.


Cross-Module Decisions (LOCKED)

# Decision Locked in Rationale
D1 Two logical DBs: platformDataSource (tenant directory, country profile, billing master) + tenantDataSource (business data). TENANT-arch section 2.1 Per multi-datasource-patterns.md. Tenant directory cannot live in a tenant DB (chicken-and-egg).
D2 Platform tables accessed via @Qualifier("platformJdbcClient") JdbcClient only - never CrudRepository. TENANT-arch section 4.3 Spring Data JDBC silently routes CrudRepository to @Primary (tenant DB).
D3 Tenant resolution: Keycloak JWT claim tenant_id (operator); signed-link decode (customer, owned by AUTH). blueprint section 3.2 + TENANT section 4.1 Platform-admin sentinel context for cross-tenant queries; emits PLATFORM_ADMIN_QUERY audit.
D4 Domain events: Spring ApplicationEventPublisher + @TransactionalEventListener(AFTER_COMMIT) + spring-modulith-events-jdbc outbox for cross-module reactions that must survive a crash. TENANT-arch section 1.3 Single deployable, no broker on critical path.
D5 API namespaces: /api/control/... for platform-admin (cross-tenant); /api/tenant/me/... for tenant-scoped self-service; /api/... for operator-scoped business modules. (API version travels in the X-API-Version header per D31 — no /v1 path segment; the namespace structure stands) TENANT-arch section 3.1 Discoverability + role gating + audit clarity.
D6 Cursor-based pagination on every list endpoint. Default 25, max 100. Cursor opaque base64 of (sort_key, id). TENANT-arch section 3.2 api-guidelines.md rule.
D7 Audit writes via outbox; failure to audit MUST NOT roll back the business action. TENANT-arch section 7.3 NFR-09 across modules.
D8 BYO-DB credentials envelope-encrypted (KEK env / hosted KMS in prod; per-tenant DEK). Pattern reused by AUDIT for per-tenant pseudonymization salts. TENANT-arch section 7.2 + AUDIT-arch section 7.3 Defence-in-depth; no plaintext anywhere.
D9 Flyway split: db/migration/platform/ and db/migration/tenant/. Tenant migrations applied to shared tenant DB on startup AND to BYO-DB during provisioning. AUDIT contributes only to platform/ (AUDIT-ADR-01). TENANT-arch section 9.1 + AUDIT-arch section 9.1 Identical schema across V1 profiles.
D10 Daily lifecycle / compliance jobs run at 02:00 Africa/Abidjan (UTC+0, no DST). TENANT-arch section 6.3 Stable timezone for all CI cron jobs.
D11 Frontend design system: custom CSS Modules + design tokens. No MUI/Tailwind/Shadcn. Locked in docs/standards/design-system.md. standards (pre-existing) Dual-brand support, low-bandwidth, customization.
D12 Audit data lives on the platform DB for every tenant, regardless of db_profile. BYO-DB tenants still have their audit rows centralized. V2 may revisit (TV-05). AUDIT-arch section 4.2 (AUDIT-ADR-01) Operational reliability (no hostage to tenant-side outage); cross-tenant platform-admin queries need a single source; single regulator-facing export.
D13 Audit event_publication outbox redelivery is idempotent via composite (producer_module, producer_outbox_id) unique partial index on audit_log. Replay safety is structural. AUDIT-arch section 2.3 (FR-06) Crash-mid-deliver does not duplicate rows; spring-modulith library retries without coordination.
D14 AUDIT_SCHEMA_MIGRATED is emitted by a Flyway Java callback running under the deploy-only papillon_audit_admin Postgres role. The runtime application role (papillon_app) structurally cannot write this event_type. AUDIT-arch section 9.2 (AUDIT-ADR-06) AC-08.3: "the application path cannot write this event; only the deploy pipeline can" - enforced at the DB role level, not by policy.
D15 Three Postgres roles for audit data: papillon_app (INSERT+SELECT on audit_log, no UPDATE/DELETE, v0.0.0); papillon_audit_reader (SELECT only, V1); papillon_audit_admin (ALL incl. DDL, deploy-only, v0.0.0). AUDIT-arch section 7.2 (AUDIT-ADR-07) DB-level append-only is a Day-1 structural guarantee, not an application convention. The PRD V0 banner said "convention applicative in v0.0.0"; the architecture goes one step further.
D16 correlation_id origin = OP at OP_CAMPAIGN_VALIDATED, propagated downstream through AUTH (signed-link issued) -> NOTIF (sent) -> CP (link opened) -> PAY (initiated/confirmed). Operator/admin standalone events do NOT carry one. Confirmed by NOTIF (NOTIF-ADR-07). AUDIT-arch section 1.5 (AUDIT-ADR-08) + NOTIF-arch Appendix A Resolves PRD A.6 OQ-05 + OQ-X3 (NOTIF leg).
D17 NOTIF cascade (v0.0.0) is SMS-first at T0 → WhatsApp at T+5d if no link click, NOT parallel send. Cancels on customer.session_opened Spring event. WA carries a freshly re-issued signed link via AUTH.SignedLinkService.reissueForCascade. NOTIF-arch section 0 + ADR-02 + ADR-03 Founder decision 2026-05-21. Cost-positive (one channel per renewal in happy case), reduces cross-border WA noise (ARTCI gap #6). Overrides NOTIF PRD V0 envelope line 19 — ANALYST PRD rework owed (OQ-NOTIF-12).
D18 NOTIF tables on platform DB for both SHARED and BYO profiles (TV-05; mirrors AUDIT D12). NOTIF-arch ADR-01 Operational resilience: a BYO-DB outage must not stop ARTCI-required outbound notifications.
D19 NOTIF dispatch substrate = @Async post-commit SMS submit (no polling for general work) + single scheduled task WhatsAppCascadeFirer (every 5 min, partial-index scan on AWAITING_CASCADE) + SmsSubmitRecovery startup job. NOTIF-arch ADR-04 + section 5 No new infra in v0.0.0. v0.1.0 layers Redis token-bucket on the same substrate without rewrite.
D20 Two frontend apps, not three: frontend/customer/ (assuré portal) + frontend/operator/ (broker console). Platform admin is a role-gated section within frontend/operator/ revealed when Keycloak JWT contains PLATFORM_ADMIN role. No separate frontend/platform/ app. chore/frontend-scaffold (PR #35) Platform admin surface is small; sharing the operator shell avoids a third deploy target and a third design system instantiation.
D21 Frontend stack (locked): Vite + React 19.2 + TypeScript + React Router v7 + CSS Modules. No framework beyond React. No Next.js, no Remix, no Astro. chore/frontend-scaffold (PR #35) Low-bandwidth constraint, simple static deploy on Coolify, no SSR needed for either portal.
D22 Canonical design tokens in frontend/_shared/tokens.css. Each app copies this file into src/theme/tokens.css and adds a per-frontend overlay (theme-customer.css / theme-operator.css). No symlinks, no monorepo tooling, no shared npm package. chore/frontend-scaffold (PR #35) Simplest sharing model for a small team; avoids Turborepo/pnpm workspaces overhead. If token drift becomes a problem, a pre-commit script can enforce parity.
D23 Customer portal gzip budget baseline: 74.55 KB (React + React Router, zero other dependencies). Budget ceiling: 200 KB. Every new dependency for frontend/customer/ must be justified against this ceiling. Operator app has no hard ceiling (desktop network assumed). chore/frontend-scaffold (PR #35) Architecture constraint: V1 PRD §30 low-bandwidth requirement.
D28 CP customer XHR auth = HttpOnly cp_session cookie (SHA-256 hash stored on customer_sessions.session_token_hash), issued only after AUTH.SignedLinkService.verifyAndOpen. SameSite=Lax, Secure, 72 h Max-Age ceiling. CP-13 receipt re-download uses a separate cp_readonly cookie backed by cp_readonly_sessions (5-min sliding, IP+UA-hash bound, parent-link-TTL ceiling). CP-arch ADR-CP-01 + ADR-CP-04 Server-stored opaque tokens; no JWT refresh dance; trivially revokable; mirrors main-session pattern post-AUTH-revoke.
D29 CP session storage = Postgres only on customer_sessions row (selection UUID[], consent, in-flight tx). No Redis fast-path in V0/V1. Partial UNIQUE on inflight_transaction_id enforces ≤1 non-terminal tx per session (CP-NFR-15). CP-arch ADR-CP-02 Negligible write rate at V0 scale; removes a coherence problem; one fewer failure mode. CP-TV-06 explicitly leaves this to ARCHITECT.
D30 CP polling = TanStack Query v5 refetchInterval (5 s × 12 then 30 s) with refetchIntervalInBackground=false. Adds ~13 KB gzipped to the customer bundle (105 KB total landing path budget, well under 200 KB ceiling). CP-arch ADR-CP-03 Battle-tested cadence + visibility handling; foundation reusable across V1 file-detail surfaces; visibility-aware to spare data/battery.
D31 API versioning: Header-based (X-API-Version: <integer>) via Spring Boot 4 native ApiVersionConfigurer. Default to latest when header absent (V0/V1 = version 1). Deprecation via Deprecation + Sunset response headers (RFC 8594); removed versions return 410 Gone. Exception: webhook ingress URLs registered with external providers (PSP, FNE) retain URI versioning — documented carve-out for third-party contracts. React clients inject header via single apiClient.ts wrapper; raw fetch() to /api/... is a lint violation. customer-portal-arch §3.0 (this session) REST principles (resource identity unchanged across versions); Spring Boot 4 native support (no custom RequestCondition); operationally clean for internal APIs. Webhook exception: external providers register callback URLs as third-party contracts, cannot reliably inject custom headers.
D24 ING v0.0.0 = cron-only, no HTTP endpoint. Single Spring @Scheduled at 0 0 2 * * * Africa/Abidjan iterating active tenants sequentially. File source = platform MinIO bucket papillon-ingestion-inbox with per-tenant prefix /<tenant_id>/{inbox,processed,rejected,duplicate}/. UI upload + atelier deferred to v0.0.3. ingestion-arch §0 + ING-ADR-02/03 V0 PRD §4 ING simplification. Reuses existing MinIO (no new infra).
D25 ING v0.0.0 anomaly handling = all-or-nothing reject. Any blocking anomaly → entire file moved to /<tid>/rejected/<ts>__<filename> with .errors.json sidecar, ING_PARSE_FAILED audit, no commit. Operator feedback = the sidecar JSON until v0.0.3 ships the atelier. ingestion-arch §0 + ING-ADR-01 No atelier in v0.0.0; matches V1 ING-R-014 all-or-nothing commit semantics.
D26 ING audit correlation_id = import_id (batch scope), does not extend D16 renewal chain. OP mints a fresh correlation_id when consuming ImportCommitted. ING events occur upstream of any campaign — there is no renewal correlation to inherit. ingestion-arch §1.3 + ING-ADR-09 Clarifies D16 boundary.
D27 ING tables (ingestion_import, ingestion_import_row) on tenant DB (business data per D1). ING audit emissions land on platform DB per D12. v0.0.0 schema is forward-compatible: ingestion_staged_row table arrives additively v0.0.3; agency_id column on contract arrives v0.0.2 via TENANT-002. ingestion-arch §2 + ING-ADR-04/10 D1 + D12 + AUDIT-ADR-03 pattern.
D32 Spring Modulith business modules are Java packages within the single :backend Gradle subproject (the one JVM Spring Boot application), NOT separate Gradle subprojects each. One backend/build.gradle.kts, one src/main, one src/test; module boundaries enforced by package structure + ApplicationModules.verify(). Build/test gate = ./gradlew :backend:check (no per-business-module :<module>: Gradle task). Per-business-module Gradle subprojects rejected. java-spring-guidelines.md §6 + blueprint §1/§11 (this session) Canonical Spring Modulith model; lighter to refactor boundaries pre-MVP; matches ingestion/tenant-admin/notifications arch docs (all use :backend:). Corrects the earlier :audit-log: / :customer-portal: subproject framing in audit-log-arch + customer-portal-arch (this session). NOTE follow-up: NOTIF-S0 still has a nested :backend:notif:test of the same defect family.

Shared Entities (canonical owners)

Entity Owner module DB Notes
tenant TENANT Platform Tenant directory. Read by every module via TenantConfigCache.
country_profile TENANT Platform Seed-only V1 (CI). Read by PAY (provider list), BILL (e-invoicing), NOTIF, CP.
agency TENANT Tenant Read by ING, OP, AUTH, CP for branding.
tenant_settings TENANT Tenant Read by OP (renewal_window_days), NOTIF (default_channel, whatsapp_template_id), AUTH (min_auth_level).
tenant_compliance_doc TENANT Tenant DPA, agreement, FNE registration. Metadata in DB, binary in MinIO.
audit_log AUDIT Platform Append-only, tenant-scoped. PRD section B.5 schema frozen Day 1 (v0.0.0). No UPDATE/DELETE from app role.
audit_tenant_salt AUDIT Platform 1:1 with tenant. Envelope-encrypted (DEK per tenant, KEK env / KMS). Consumed via PiiHasher named-interface.
audit_export AUDIT Platform Export job tracking (status, MinIO object keys, SHA-256). Bundles materialized in MinIO bucket audit-exports.
event_publication (spring-modulith-events-jdbc library) Platform Outbox table. AUDIT consumes via @ApplicationModuleListener. Not owned by any module.
notification NOTIF Platform One row per dispatch request. Carries tenant_id, country_code (snapshot), correlation_id, signed-link refs, status. v0.0.0+.
delivery_attempt NOTIF Platform 1..N per notification, one per channel (SMS, WA). UNIQUE (provider_id, provider_message_id) enforces webhook idempotency. v0.0.0+.
suppression NOTIF Platform Per-tenant opt-out (channel-scoped). Phone-keyed (customer_id nullable). v0.0.0+. STOP detection + admin override.
ingestion_import ING Tenant Header row per file picked up by the cron. UNIQUE (tenant_id, file_hash_sha256). Status FSM: PICKED_UP → COMMITTED / PARSE_FAILED / DUPLICATE. v0.0.0+.
ingestion_import_row ING Tenant One row per committed line (trace). FK to customer + contract. v0.0.0+.
ingestion_staged_row ING Tenant NOT created in v0.0.0. Schema arrives v0.0.3 with the atelier de correction (UI upload + per-row corrections).
customer TENANT (schema) / ING (writer) Tenant ING writes source_customer_id (dedup key), nom, prenoms, telephone (E.164), email, date_naissance. UNIQUE (tenant_id, source_customer_id).
contract TENANT (schema) / ING (writer) Tenant ING writes source_contract_id (dedup key with tenant_id + customer_id), date_echeance, montant_du, numero_police, etc. UNIQUE (tenant_id, customer_id, source_contract_id). agency_id column added by TENANT-002 v0.0.2.
customer_sessions CP Tenant One row per signed-link visit. Holds selection UUID[], consent, in-flight tx ref, session cookie SHA-256 hash, correlation_id (from signed_link), country_code. Partial UNIQUE on inflight_transaction_id (CP-NFR-15). v0.0.0+.
payment_attempts (CP projection) CP Tenant Local projection of PAY's transaction for CP screen routing without round-trips. Updated by PaymentConfirmed / PaymentFailed / PaymentExpiredPending listeners. v0.0.1+.
cp_readonly_sessions CP Tenant Ephemeral 5-min sliding cookie-bound row for receipt re-download after AUTH revoked link (reason=ALL_PAID). Bounded by parent link 72 h TTL. V1 (CP-006).
mentions_legales_versions CP Tenant Read-only projection from TENANT; consumed at consent-tick time per CP-OQ-07 default. v0.0.0+.

Event Contracts (canonical registry)

Inbound to AUDIT (consumed)

Event Owner Payload Available
TenantOnboarded TENANT {tenantId, countryCode, plan, primaryContact, defaultAgencyId, byoDb, occurredAt} v0.0.0 (TENANT-001)
TenantSuspended TENANT {tenantId, reasonCode, suspendedAt} v0.1.0
TenantReactivated TENANT {tenantId, reactivatedAt} v0.1.0
TenantSoftDeleted TENANT {tenantId, deletedAt} V1
AgencyDisabled TENANT {tenantId, agencyId, disabledAt} v0.0.2
TenantSettingsChanged TENANT {tenantId, changedKeys[], occurredAt} v0.1.0
(all V1 PRD section 18 audit event types, via outbox) every producer per AUDIT-arch section B.3 decision table Phased per producer (see AUDIT V0 banner)
NOTIF_SENDING, NOTIF_SENT, NOTIF_FAILURE, NOTIF_SUPPRESSED_OPT_OUT NOTIF per audit-log §B.3 NOTIF row, with PiiHasher applied to phone + name v0.0.0 (AUDIT-002 ships NOTIF_SUPPRESSED_OPT_OUT catalog entry; others land at v0.1.0: NOTIF_WEBHOOK_REJECTED, NOTIF_OPTED_OUT, NOTIF_CAP_EXCEEDED, NOTIF_KILL_SWITCH_*, NOTIF_MANUAL_RETRY, NOTIF_SUPPRESSION_REMOVED)
ING_UPLOAD_RECEIVED, ING_PARSE_FAILED, ING_DUPLICATE_FILE, ING_COMMITTED, ING_CUSTOMER_CREATED, ING_CUSTOMER_UPDATED, ING_CONTRACT_CREATED, ING_CONTRACT_UPDATED ING per ingestion-arch §7.4; PiiHasher applied to name + phone; DOB never plaintext (only year in V1, omitted in v0.0.0) v0.0.0 (AUDIT catalog must admit these before ING-001 dev-start — OQ-X10)

Outbound from AUDIT (emitted)

Event Trigger Consumers Available
AUDIT_INGEST_DEGRADED FR-10 threshold breach ops (alert) v0.1.0
AUDIT_RECONCILED Drain queue back to 0 ops (auto-resolve) v0.1.0
AUDIT_EXPORT_COMPLETED Export worker finishes self-audit + admin v0.1.0
AUDIT_QUERY_EXECUTED Query API hit (deduplicated 60s, FR-09) self-audit V1
AUDIT_SCHEMA_MIGRATED Flyway callback fires under papillon_audit_admin self-audit v0.0.0

TENANT outbound (unchanged, retained for reference)

Event Consumers
TenantOnboarded AUTH, BILL, NOTIF, AUDIT
TenantSuspended AUTH, CP, PAY, OP, AUDIT
TenantReactivated AUTH, CP, PAY, OP, AUDIT
TenantSoftDeleted ING, CP (purge), AUDIT
AgencyDisabled OP, ING, AUTH, AUDIT
TenantSettingsChanged OP, AUTH, NOTIF, AUDIT

API Namespaces (claimed)

Note (D31): API version travels in the X-API-Version request header — not in the URL path. The namespaces below are the canonical path shapes (no /v1 segment). Per-module arch docs have been aligned to the header-versioned shape (no /v1 path segment); the registry below is the source of truth. Webhook ingress URLs registered with external providers (PSP, FNE, SMS gateways) are the documented carve-out and DO retain URI versioning — they are marked explicitly below.

Namespace Module Available
/api/control/tenants/... TENANT v0.0.0+
/api/tenant/me/... TENANT v0.0.0+
/api/control/tenants/{id}/compliance-docs/... TENANT V1
/api/control/audit/exports, /exports/{id} AUDIT v0.1.0 (CSV stub) / V1 (JSONL+manifest)
/api/control/audit/query AUDIT V1
/api/control/audit/cases/{type}/{key} AUDIT V1
/api/notifications/dispatch NOTIF v0.0.0
/api/notifications/{id} NOTIF v0.0.0
/api/v1/notifications/webhooks/{providerId}/{status\|inbound} (webhook carve-out — URI-versioned) NOTIF v0.0.0
/api/portail/{token} (entry) + /api/portail/session/... (cookie-bound XHRs) CP v0.0.0
/api/portail/{token}/recus (CP-13 readonly re-download) CP V1

Reserved for upcoming modules (do not collide): - /api/auth/..., /api/control/auth/... -> AUTH - /api/control/billing/... -> BILL - /api/notifications/admin/... -> NOTIF (v0.1.0 — kill-switch + suppression admin) - /api/notifications/customers/{id}/timeline -> NOTIF (V1 — operator timeline) - /api/ingestion/... -> ING (claimed but unused in v0.0.0 — cron only; first endpoints land v0.0.3 with UI upload + atelier) - /api/operator/... -> OP - /api/payments/... -> PAY - PSP webhook ingress URLs (PAY) and FNE webhook ingress URLs (BILL) will be registered under /api/v1/... as additional webhook carve-outs when those modules land.


Shared Beans / Named Interfaces (Spring Modulith)

Bean Owner Consumers Available
PiiHasher AUDIT NOTIF, CP, PAY, AUTH, ING, OP, TENANT v0.0.0
AuditEventType enum + AuditEventBuilder AUDIT every producer v0.0.0
SignedLinkService AUTH CP, OP, ING v0.0.0
TenantConfigCache TENANT every module v0.0.0
SignedLinkService.reissueForCascade(originalLinkId, ttl) AUTH NOTIF (cascade firer) v0.0.0 (NEW — flagged in NOTIF-arch; AUTH must add a slice)
customer.session_opened Spring application event CP NOTIF (cascade cancel listener) v0.0.0 (confirmed — CP-arch §1.2 + CP-001 publishes on first authenticated contract-list render; payload {tenant_id, customer_id, signed_link_id, opened_at, correlation_id})
CustomerSessionService (in-process Spring named interface for CP session create/lookup/revoke) CP (internal to CP; AUTH cooperates via SignedLinkService) v0.0.0
NotificationDispatchService (in-process Spring named interface) NOTIF OP, PAY (V1), AUTH (V1) v0.0.0

Open Questions (cross-module)

ID Question Origin Owner
OQ-A1 Retain tenant_provisioning_run indefinitely vs prune to audit at 90d? TENANT-arch section 16 Founder
OQ-A2 BYO-DB: ship Flyway DDL pack to tenant DBA, or apply blind under contract clause? TENANT-arch section 16 Founder + Legal
OQ-A3 papillon-platform-admin realm role vs separate papillon-admin realm? TENANT-arch section 16 Founder
OQ-X1 Will AUTH consume TenantOnboarded synchronously (block ACTIVE) or asynchronously (initial admin user appears later)? TENANT <-> AUTH RESOLVED: AUTH consumes async; brief gap window documented in identity-arch.
OQ-X2 Will BILL need a country_profile.fne_endpoint field, or hard-code the CI FNE URL? (V1 = CI only.) TENANT <-> BILL BILL ARCHITECT
OQ-X3 correlation_id origin = OP at OP_CAMPAIGN_VALIDATED, propagated through AUTH -> NOTIF -> CP -> PAY. Confirm in each downstream module's arch session. AUDIT <-> OP/NOTIF/CP/PAY/AUTH NOTIF + CP + OP confirmed (NOTIF-ADR-07, customer-portal-arch §1.3, OP-ADR-05): correlation_id = campaign_contract.id; minted at row-insert by OP; propagated through AUTH + NOTIF + CP. Still pending: PAY/AUTH ARCHITECTS.
OQ-X6 AUTH must expose SignedLinkService.reissueForCascade(originalLinkId, ttl) for NOTIF's T+5d WA cascade. AUTH session needs a new slice. NOTIF <-> AUTH AUTH ARCHITECT (next session)
OQ-X7 CP must publish customer.session_opened Spring application event on first signed-link-authenticated portal view, with {tenant_id, customer_id, signed_link_id, opened_at}. CP session needs a slice. NOTIF <-> CP RESOLVED: customer-portal-arch §1.2 + CP-001 publishes the event in v0.0.0; payload extended with correlation_id.
OQ-X8 NOTIF PRD V0 envelope line 19 ("envoi parallèle SMS+WA") is overridden by founder decision to SMS-first → WA at T+5d. ANALYST PRD rework owed. NOTIF <-> ANALYST ANALYST (next NOTIF PRD pass)
OQ-X9 AUDIT V1 catalog must add NOTIF_SUPPRESSED_OPT_OUT to the v0.0.0 slice (folded into AUDIT-002). Other NOTIF self-events (NOTIF_WEBHOOK_REJECTED, NOTIF_OPTED_OUT, NOTIF_CAP_EXCEEDED, NOTIF_KILL_SWITCH_*, NOTIF_MANUAL_RETRY, NOTIF_SUPPRESSION_REMOVED) land at v0.1.0. NOTIF <-> AUDIT AUDIT ANALYST (next AUDIT rework — blocks NOTIF-001 dev-start)
OQ-X10 AUDIT catalog must admit ING event types ING_UPLOAD_RECEIVED, ING_PARSE_FAILED, ING_DUPLICATE_FILE, ING_COMMITTED, ING_CUSTOMER_CREATED, ING_CUSTOMER_UPDATED, ING_CONTRACT_CREATED, ING_CONTRACT_UPDATED before ING-001 dev-start. ING <-> AUDIT AUDIT ARCHITECT/ANALYST (catalog rework — blocks ING-001 dev-start)
OQ-X11 TENANT S0 must provision MinIO bucket papillon-ingestion-inbox and issue per-tenant scoped credentials (prefix-bound RW on /<tid>/inbox/, RO on /<tid>/{processed,rejected,duplicate}/). Currently TENANT provisions MinIO for compliance docs only. ING <-> TENANT TENANT ARCHITECT (next session)
OQ-X12 Ownership + migration order for customer.source_customer_id / contract.source_contract_id / contract.last_import_id + their UNIQUE indexes. ING needs them but does not own the base schema. Coordinate Flyway versioning between TENANT-001 (base tables) and ING-001 (additive ALTER). ING <-> TENANT TENANT ARCHITECT (next session)
OQ-X13 OP must declare a (potentially no-op in v0.0.0) @ApplicationModuleListener for ImportCommitted {importId, tenantId, rowsCreated, rowsUpdated, rowsSkipped, occurredAt}. Useful for cohort cache invalidation when OP UI lands v0.0.1. ING <-> OP RESOLVED: no-op listener delivered in OP-S0 (docs/architecture/operator-console/part2-integrations-build-ops.md §16 Tier 0).
OQ-X14 AUDIT catalog (AuditEventType enum) must admit the 14 OP event types (OP_CAMPAIGN_VALIDATED, CAMPAIGN_CANCELLED, CONTRACT_EXCLUDED, CONTRACT_ADDED_TO_CAMPAIGN, FIELD_CORRECTED, HORS_PLATEFORME_MARKED, NOTIFICATION_RESENT, ANOMALY_RESOLVED, OP_USER_CREATED, OP_USER_ROLE_CHANGED, OP_USER_DEACTIVATED, SETTINGS_CHANGED, DATA_EXPORTED, IMPORT_TRIGGERED) before OP-001 dev-start. Same pattern as OQ-X9 (NOTIF) and OQ-X10 (ING). OP <-> AUDIT AUDIT ARCHITECT/ANALYST (catalog rework — blocks OP-001 dev-start)
OQ-X4 AUDIT query API filter semantics on acting_on_tenant_id: response includes rows where tenant_id = :scope OR acting_on_tenant_id = :scope. Confirm at V1 implementation time. AUDIT internal ARCHITECT review at V1 (AUDIT-004 story)
OQ-X5 Defensive raw-PII scan at AUDIT INSERT boundary (FR-03 alinea 2): V1 only (AUDIT-008). Confirm pilot acceptance of v0.0.0-v0.1.0 lacking this backstop. AUDIT internal Founder

Carried PRD-level OQs: - TENANT: OQ-01..OQ-06 in docs/prd/tenant-admin-prd.md section A.6. - AUTH: AUTH-OQ-01..06, AUTH-OQ-09 in docs/architecture/identity-arch.md Appendix B. - AUDIT: A.6 OQ-01 (BILL fiscal events), OQ-02 (erasure-right policy), OQ-03 (file-level vs per-field on manual modification), OQ-04 (alert routing recipient), OQ-06 (DB-level read auditing) in docs/prd/audit-log-prd.md. - OP: OQ-OP-01..05 in docs/architecture/operator-console/part2-integrations-build-ops.md Appendix B.


Module Entries

TENANT (tenant-admin) - V1 (V0 deltas at section 0)

  • Architecture doc: docs/architecture/tenant-admin-arch.md
  • PRD: docs/prd/tenant-admin-prd.md
  • Status: Awaiting founder approval (workflow stage 4).
  • Vertical slices: 11 slices (S0-S11) + V0 deltas (TENANT-001 v0.0.0, TENANT-002 v0.0.2, TENANT-003 v0.0.4). S3 marked split candidate (3 sub-slices proposed).
  • External deps introduced: Postgres x2 logical DBs, Keycloak realm, Redis, MinIO bucket - all in S0.
  • Cross-module impacts: 6 outbound events (see registry); Keycloak admin client port + MinIO object store port shared with later modules.
  • V2 forward-compat checks: country_code NOT NULL no-default; group_id UUID NULL present; no UNIQUE on legal_name; partial UK on ncc WHERE status<>'DELETED'.

AUTH (identity) - V1 (V0 deltas at section 0)

  • Architecture doc: docs/architecture/identity-arch.md
  • PRD: docs/prd/identity-prd.md
  • Status: Draft - awaiting founder approval (workflow stage 4).
  • Vertical slices: 8 slices (S0-S7) + V0 deltas. S1, S3, S6 marked split candidates.
  • External deps introduced: Keycloak realm config (on top of TENANT S0's docker-compose).
  • Cross-module impacts: consumes 6 TENANT events + AllContractsPaid from PAY; exposes SignedLinkService, ChallengeVerificationService, CustomerSessionService to CP via Spring boundary; exposes AuthProvisioningService.retriggerCabinetAdminInvitation to TENANT.
  • Locked decisions (AUTH-specific):
  • All AUTH tables on platform DB (incl. signed_links, customer_auth_attempts) - ADR-AUTH-01.
  • Signed-link token = 32-byte SecureRandom -> Base64url; SHA-256 stored; no HMAC - ADR-AUTH-02.
  • Attempt counters Postgres-only + optimistic concurrency (version col) - ADR-AUTH-03.
  • Keycloak Direct Grant (ROPC, confidential client) for operator login - ADR-AUTH-04.
  • Keycloak access token TTL = 5 minutes; remember-device is application-layer (NOT Keycloak's keep-me-logged-in).
  • challenge_hmac stored on signed_links (computed by ING/OP at issuance; AUTH never holds raw DOB/RCCM).
  • /api/portail/{token}/... URL shape owned by CP; AUTH exposes in-process Spring module APIs.
  • OQ-X1 resolved: AUTH consumes TenantOnboarded asynchronously; brief gap window documented.
  • Open OQs carried: AUTH-OQ-01..06, AUTH-OQ-09 (see arch doc Appendix B).
  • Feature flags: feature.whatsapp.operator-mfa.enabled=false [LEGAL-REVIEW] until ARTCI authorization (AUTH-OQ-04).

AUDIT (audit-log) - V1 (V0 deltas at section 0)

  • Architecture doc: docs/architecture/audit-log-arch.md
  • PRD: docs/prd/audit-log-prd.md
  • Background KB: docs/kb/audit-salt-storage-options.md (per-tenant salt store decision).
  • Status: Awaiting founder approval (workflow stage 4).
  • Vertical slices V0: AUDIT-S0 (Tier 0: dev env + testcontainers), AUDIT-001 (v0.0.0 schema + outbox + PiiHasher + Flyway callback), AUDIT-002 (v0.0.1 real emission on PAY + NOTIF), AUDIT-003 (v0.1.0 export CSV stub + metrics).
  • Vertical slices V1: AUDIT-004 (query API), AUDIT-005 (by-case lookup), AUDIT-006 (full JSONL+manifest export), AUDIT-007 (papillon_audit_reader role), AUDIT-008 (defensive PII scan), AUDIT-009 (real ingest-degradation alerting).
  • External deps introduced: MinIO bucket audit-exports; new Postgres roles papillon_audit_admin (v0.0.0) and papillon_audit_reader (V1). No new container.
  • Cross-module impacts:
  • Consumes TenantOnboarded (provisions salt row).
  • Consumes ALL V1 PRD section 18 events from every producer module via outbox.
  • Exposes PiiHasher named-interface bean + AuditEventType enum + AuditEventBuilder to every producer.
  • Emits 5 self-events (AUDIT_INGEST_DEGRADED, AUDIT_RECONCILED, AUDIT_EXPORT_COMPLETED, AUDIT_QUERY_EXECUTED, AUDIT_SCHEMA_MIGRATED).
  • Locked decisions (AUDIT-specific):
  • AUDIT-ADR-01: audit data on platform DB regardless of tenant db_profile (TV-05; V2 may revisit). -> D12
  • AUDIT-ADR-02: producer-to-AUDIT via spring-modulith-events-jdbc outbox (D4 + D7).
  • AUDIT-ADR-03: full V1 (PRD section B.5) schema frozen Day 1 (v0.0.0). No ALTER TABLE audit_log between v0.0.0 and v0.1.0.
  • AUDIT-ADR-04: per-tenant salt in AUDIT-owned audit_tenant_salt table, envelope-encrypted (D8 pattern). KB rationale in docs/kb/audit-salt-storage-options.md.
  • AUDIT-ADR-05: export bundles in MinIO bucket audit-exports, streaming digest, presigned URL (15 min TTL).
  • AUDIT-ADR-06: AUDIT_SCHEMA_MIGRATED emitted by Flyway Java callback under deploy-only papillon_audit_admin role. -> D14
  • AUDIT-ADR-07: three Postgres roles (papillon_app INSERT+SELECT; papillon_audit_reader SELECT V1; papillon_audit_admin DDL deploy-only). -> D15
  • AUDIT-ADR-08: correlation_id origin = OP at OP_CAMPAIGN_VALIDATED. -> D16 (pending OQ-X3 confirmation downstream).
  • Open OQs raised: OQ-X3, OQ-X4, OQ-X5 (see cross-module table above).
  • PRD OQs carried: A.6 OQ-01..04, OQ-06 (OQ-05 resolved via AUDIT-ADR-08).
  • Honest-scope notes (PRD AC-05.4): V1 does NOT defend against malicious DBA, compromised infrastructure operator, or KEK exfiltration. V2 introduces hash-chain tamper-evidence + HSM-backed key custody.

NOTIF (notifications) - v0.0.0

  • Architecture doc: docs/architecture/notifications-arch.md
  • PRD: docs/prd/notifications-prd.md (V0 envelope §"V0 Envelope Scope" + V1 §§A/B/C as forward-compat reference)
  • Status: Awaiting founder approval (workflow stage 4). Document covers v0.0.0 only; v0.0.1 → V1 features designed-for but not architected in detail.
  • Vertical slices v0.0.0: NOTIF-S0 (Tier 0: dev env + WireMock + testcontainers), NOTIF-001 (dispatch + SMS — split candidate into 4 sub-slices), NOTIF-002 (inbound STOP), NOTIF-003 (WhatsApp cascade at T+5d), NOTIF-004 (SmsSubmitRecovery crash-recovery startup job).
  • External deps introduced: WireMock service in docker-compose (test-only); SMS provider HTTPS endpoint (LetsTalk or Hub2 — OQ-NOTIF-01); WhatsApp Cloud API endpoint (Meta direct or via BSP — OQ-NOTIF-01). No new persistent infra beyond what TENANT S0 + AUDIT S0 already provision.
  • Cross-module impacts (NEW from this session):
  • AUDIT (blocker): catalog must accept NOTIF_SUPPRESSED_OPT_OUT before NOTIF-001 dev-start (OQ-X9, folded into AUDIT-002).
  • AUTH (blocker for NOTIF-003): add SignedLinkService.reissueForCascade(originalLinkId, ttl) Spring named interface (OQ-X6). Re-issued links carry lineage_of=<original> and same correlation_id per D16 chain.
  • CP (blocker for NOTIF-003): publish customer.session_opened {tenant_id, customer_id, signed_link_id, opened_at} Spring application event (OQ-X7).
  • TENANT (closure): country_profile must seed notif_provider_sms, notif_provider_wa, sms_sender_id_default for CI. Listed in TENANT migration path; flagged for TENANT ARCHITECT confirmation.
  • ANALYST (PRD rework): NOTIF PRD V0 envelope line 19 ("envoi parallèle") overridden by SMS-first cascade decision (OQ-X8).
  • OP: subscribes to notification.dispatched/sent/failed/cascade_cancelled Spring events for the file timeline (UI in V1; events emitted v0.0.0).
  • Locked decisions (NOTIF-specific):
  • NOTIF-ADR-01: NOTIF tables on platform DB for both SHARED and BYO profiles. -> D18
  • NOTIF-ADR-02: cascade is SMS-first → WA at T+5d. -> D17
  • NOTIF-ADR-03: WA carries a re-issued signed link (AUTH reissueForCascade).
  • NOTIF-ADR-04: dispatch substrate = @Async post-commit + WhatsAppCascadeFirer scheduled task (no continuous polling) + SmsSubmitRecovery startup job. -> D19
  • NOTIF-ADR-05: country_profile provider lookup wired Day 1; CI seeded only.
  • NOTIF-ADR-06: NOTIF_WEBHOOK_REJECTED is log-only in v0.0.0 (audit catalog admits it at v0.1.0).
  • NOTIF-ADR-07: correlation_id propagates through MDC + async + scheduled tasks; never minted by NOTIF. Confirms D16 chain.
  • NOTIF-ADR-08: inbound STOP with no recent notification for the phone → log + drop, no suppression row (avoids cross-tenant leak).
  • OQ-X3 resolved (NOTIF leg): confirmed D16 chain.
  • OQs raised: OQ-X6, OQ-X7, OQ-X8, OQ-X9 (see cross-module table).
  • PRD OQs carried: OQ-NOTIF-01 (provider choice — blocks NOTIF-001 dev-start), OQ-NOTIF-02 (ARTCI WA filing — feature-flagged), OQ-NOTIF-03 (mandatory-clause in body — non-blocking), OQ-NOTIF-04 (resolved for v0.0.0 scope, remaining 7 self-events land v0.1.0).
  • Feature flags: FEATURE_NOTIF_WHATSAPP_CASCADE_ENABLED (default true; flip to false if ARTCI B5+B10 filing pending — keeps SMS-only path live).
  • Honest-scope notes: v0.0.0 has NO transient-error retry, NO webhook-loss reconciliation polling, NO operator UI, NO manual retry. A stuck SMS_SENT row without delivery webhook stays stuck until V1 NOTIF-US-06 lands; the cascade still fires at T+5d if no customer_clicked_at. ARTCI proof obligation is satisfied by NOTIF_SENT audit (proof of submission), not delivery.

ING (ingestion) - v0.0.0

  • Architecture doc: docs/architecture/ingestion-arch.md
  • PRD: docs/prd/ingestion-prd.md (V0 envelope §"V0 Envelope Scope" + V1 §§A/B/C as forward-compat reference)
  • Status: Awaiting founder approval (workflow stage 4). Document covers v0.0.0 only; v0.0.1 → V1 features (UI upload, atelier de correction, full validation set, history UI, template DL, multi-agency code_agence) designed-for but not architected in detail.
  • Vertical slices v0.0.0: ING-S0 (Tier 0: dev env + MinIO bucket layout + Flyway scaffolding + TestContainers fixtures), ING-001 (cron + pickup + parse + validate + commit + audit + reject — split candidate into 5 sub-slices a..e).
  • External deps introduced: none new. Reuses MinIO (TENANT S0), Postgres x2 (TENANT S0), spring-modulith outbox (AUDIT S0).
  • Cross-module impacts (NEW from this session):
  • AUDIT (blocker for ING-001 dev-start): catalog must admit 8 new ING_* event types (OQ-X10).
  • TENANT (blocker for ING-001 dev-start): provision MinIO bucket papillon-ingestion-inbox + per-tenant scoped credentials + per-tenant prefix layout (OQ-X11); confirm Flyway migration ordering for additive ALTER on customer/contract for ING dedup columns (OQ-X12).
  • OP: declare listener for ImportCommitted (no-op in v0.0.0; used v0.0.1+ for cohort cache invalidation) (OQ-X13).
  • AUTH: none. ING v0.0.0 cron runs as SYSTEM, no JWT, no SignedLinkService use.
  • Locked decisions (ING-specific):
  • ING-ADR-01: v0.0.0 = all-or-nothing reject; no atelier. -> D25
  • ING-ADR-02: file source = platform MinIO papillon-ingestion-inbox per-tenant prefix. -> D24
  • ING-ADR-03: cron = single @Scheduled iterating active tenants. -> D24
  • ING-ADR-04: ING tables on tenant DB; audit on platform DB. -> D27 (reaffirms D1 + D12)
  • ING-ADR-05: all-or-nothing single-transaction commit (V1 ING-R-014 semantics).
  • ING-ADR-06: idempotency via SHA-256 dedup + per-file FSM; no extra outbox/queue beyond spring-modulith.
  • ING-ADR-07: XLSX parse via Apache POI SAX; CSV via Jackson CSV streaming.
  • ING-ADR-08: production gate via feature.artci.phone-auth.granted (default false on prod profile, ignored elsewhere).
  • ING-ADR-09: correlation_id for ING audit = import_id (batch scope); does not extend D16 renewal chain. -> D26
  • ING-ADR-10: v0.0.0 schema forward-compatible (ingestion_staged_row table additive in v0.0.3; agency_id column on contract additive in v0.0.2). -> D27
  • OQ-X3 status: N/A for ING — ING events upstream of campaign chain; ING does not propagate the OP-originated correlation_id.
  • OQs raised: OQ-X10 (AUDIT catalog), OQ-X11 (TENANT MinIO bucket), OQ-X12 (TENANT migration ordering), OQ-X13 (OP listener).
  • PRD OQs carried: A.6 #1 (echeance dépassée — v0.0.0 keeps required + parseable only, skips >30d-past rule), #2 (branche referential — v0.0.0 makes branche optional), #3 (notification channel — N/A, no notifications in ING), #4 (row delete in atelier — N/A, no atelier in v0.0.0).
  • Feature flags: feature.artci.phone-auth.granted=false (default; BLOQUANT V0/V1 production per V0 PRD §6.4). Pre-prod ingestion authorized. [LEGAL-REVIEW] startup warning emitted while flag is false.
  • Honest-scope notes: v0.0.0 has NO upload UI, NO atelier de correction, NO history UI, NO template download, NO operator feedback channel except the .errors.json sidecar in /rejected/. Any blocking anomaly on any row rejects the entire file. Operator notification of rejection is out of band (operator must check the MinIO /rejected/ prefix). v0.0.1 brings the badge; v0.0.3 brings the full atelier. date_naissance and branche are optional in v0.0.0 validation (will become required again v0.0.2 per V0 PRD §4 ING "v0.0.2 reintroduit règles V1").

CP (customer-portal) - v0.0.0 → V1

  • Architecture doc: docs/architecture/customer-portal-arch.md
  • PRD: docs/prd/customer-portal-prd.md (V0 envelope §0 + V1 §§A/B/C)
  • Status: Awaiting founder approval (workflow stage 4). Document covers v0.0.0 → V1 with explicit sub-version deltas at §0.
  • Vertical slices: CP-S0 (Tier 0: dev env + WireMock for PAY sandbox), CP-001 (v0.0.0 landing + list + cookie + customer.session_opened emission), CP-002 (v0.0.1 payment + return + reçu — split candidate into 002a/002b/002c), CP-003 (v0.0.2 polling + phone-last-4 + per-contract toggle), CP-004 (v0.0.3 lazy-load + bundle budget gate), CP-005 (v0.1.0 TTL + attempt counter + rate-limit), CP-006 (V1 hardening: lien remplacé + déjà réglé CP-13 + mentions-légales page).
  • External deps introduced: WireMock service in docker-compose.yml (test-only profile) for PAY sandbox. No new persistent infra (reuses TENANT S0 Postgres + Keycloak + MinIO; Redis present but unused by CP per D29).
  • Cross-module impacts:
  • NOTIF: CP-001 publishes customer.session_opened Spring event (closes OQ-X7).
  • AUTH: CP relies on SignedLinkService.verifyAndOpen(token) (existing v0.0.0 named iface).
  • PAY: CP-002+ consumes PaymentClient (initiate, status, receipts) and listens to PaymentConfirmed / PaymentFailed / PaymentExpiredPending. PAY ARCHITECT to confirm event payloads at PAY-arch session.
  • TENANT: CP reads TenantConfigCache for broker identity surface; invalidates on TenantSettingsChanged.
  • ING: CP reads-only via ContractRepository.findEligibleForFile.
  • AUDIT: 17 event types emitted via outbox (PRD §B.3). All carry correlation_id from signed_link (closes OQ-X3 CP leg).
  • OP: indirect — OP queries AUDIT for CP timeline events (no API surface from CP).
  • Locked decisions (CP-specific):
  • ADR-CP-01: HttpOnly cp_session cookie + server-stored SHA-256 hash on customer_sessions.session_token_hash (no JWT, no token-replay on every XHR). → D28.
  • ADR-CP-02: Selection + consent + in-flight tx on a single customer_sessions row in Postgres; no Redis fast-path in V0/V1. → D29.
  • ADR-CP-03: Polling via TanStack Query v5 refetchInterval (5 s × 12 then 30 s, refetchIntervalInBackground=false). +13 KB gzip on customer bundle (landing path ~105 KB, under 200 KB ceiling per D23). → D30.
  • ADR-CP-04: CP-13 read-only re-download via cp_readonly_sessions row + separate HttpOnly cp_readonly cookie, 5-min sliding, IP+UA-hash bound, parent-link-TTL ceiling. → D28.
  • ADR-CP-05: Schema frozen v0.0.0 → V1 (no ALTER TABLE between sub-versions); unused columns nullable. Mirrors AUDIT-ADR-03.
  • OQ-X3 (CP leg) RESOLVED: correlation_id read from signed_link → stamped on customer_sessions.correlation_id → propagated to PAY initiation requests + every CP audit event.
  • OQ-X7 RESOLVED: customer.session_opened event emission spec'd in §1.2 + slice CP-001.
  • OQs raised: none (delegates CP-OQ-03 receipt retention to PAY ARCHITECT and CP-OQ-07 content-version timing decided as latest-at-tick).
  • PRD OQs carried: CP-OQ-01 (CIMA mandatory fields ⇒ lawyer), CP-OQ-02 (probative value of checkbox consent ⇒ lawyer), CP-OQ-05 (anonymous mandataire receipt acceptability ⇒ lawyer), CP-OQ-06 (refund/dispute UI ⇒ DESIGNER, default operational-only).
  • Feature flags: none CP-side in V0; bundle-budget enforcement is a CI gate, not a runtime flag.
  • Honest-scope notes: v0.0.0 has NO payment, NO polling, NO link TTL enforcement, NO challenge replay (AUTH still ships AUTH-CP-02/03 DOB/RCCM challenges at V1). v0.0.1 brings payment + reconciliation. v0.0.2 adds polling + per-contract toggle (replaces all-or-none from v0.0.0). v0.1.0 brings TTL + rate-limit hardening. CP-13 receipt re-download ships V1 (CP-006). Refund/dispute flow remains "call the agency" through V1.

OP (operator-console) - V1 (V0 envelope active)

  • Architecture doc: docs/architecture/operator-console/README.md (split into part 1 — domain & API and part 2 — integrations, build & ops)
  • PRD: docs/prd/operator-console-prd.md
  • Status: Draft — awaiting founder approval (workflow stage 4). Document covers full V1 spec; V0 sub-versions OP-001..OP-005 designed-for in detail. 2026-05-23 review pass against prd-v0.md: (a) merged two parallel section-numbering streams into a single canonical TOC (§0–§21 + Appendices A/B); (b) lifted agency_operator inline-edit RBAC at v0.0.2 (OP-003) per PRD V0 §4 OP "branch operators can edit"; (c) moved POST /campaigns/{id}/contracts/{ccId}/remind from v0.0.3 to V1 (consistent with OP-013); (d) moved POST /campaigns/{id}/contracts/{ccId}/mark-offplatform from v0.0.3 to V1 (consistent with OP-014); (e) OP-004 split candidate reduced from 4 to 3 sub-slices (cancel, anomalies, import UI). Document version 1.1.
  • Vertical slices V0: OP-S0 (Tier 0: Flyway migration + module boundary + no-op ImportCommitted listener + Recovery stub), OP-001 (v0.0.0: campaign create + send, CampaignSendWorker + CampaignDispatchRecovery — split candidate into 3 sub-slices), OP-002 (v0.0.1: pre-send edit + status polling — admin only), OP-003 (v0.0.2: multi-agency routing + extend inline-edit to agency_operator), OP-004 (v0.0.3: cancel + anomalies + import UI — split candidate 3 sub-slices; remind & mark-offplatform deferred to V1), OP-005 (v0.1.0: observability).
  • Vertical slices V1: OP-006 (dashboard) through OP-017 (notes offline sync).
  • External deps introduced: None new. Reuses Postgres x2 (TENANT S0), Keycloak (AUTH S0), spring-modulith outbox (AUDIT S0), WireMock (NOTIF S0).
  • Cross-module impacts (NEW from this session):
  • AUDIT (blocker for OP-001 dev-start): catalog must admit 14 OP event types (OQ-X14).
  • AUTH: SignedLinkService.createLink called at campaign send (v0.0.0); reissueForCascade at send-reminder (V1, OQ-X6 already raised).
  • NOTIF: NotificationDispatchService.dispatch() must be idempotent via UNIQUE(campaign_contract_id) on notifications table (OQ-OP-04 — confirm with NOTIF ARCHITECT before OP-001 dev-start).
  • ING: OQ-X13 resolved — no-op ImportCommitted listener in OP-S0.
  • TENANT: cabinet_settings table dropped from OP; OP reads renewal_window_days + CIMA fields via TenantConfigCache (OP-ADR-03). SETTINGS_CHANGED audit event emitted by OP when operator updates settings via TENANT API.
  • OP-specific ADRs:
  • OP-ADR-01: Fan-out = CampaignSendWorker (async @ApplicationModuleListener outbox-backed, TX_i per contract) + CampaignDispatchRecovery (startup + every 5 min, idempotent via UNIQUE notifications.campaign_contract_id).
  • OP-ADR-02: Full V1 status CHECK constraints on campaigns + campaign_contracts from v0.0.0 (forward-compat, mirrors AUDIT-ADR-03).
  • OP-ADR-03: Settings owned by TENANT; OP reads via TenantConfigCache; no cabinet_settings table.
  • OP-ADR-04: v0.0.0 cohort = operator on-demand; @Scheduled cron deferred to v0.0.2+.
  • OP-ADR-05: correlation_id = campaign_contract.id. D16 chain confirmed (OQ-X3 OP leg resolved).
  • OP-ADR-06: V1 real-time = SSE; v0.0.0–v0.0.5 = 30s polling.
  • OQ-X3 status: OP + CP + NOTIF confirmed (see OQ-X3 table entry). Still pending: PAY/AUTH.
  • OQ-X13 status: RESOLVED — no-op listener in OP-S0.
  • OQs raised: OQ-X14 (AUDIT catalog for 14 OP event types — blocks OP-001 dev-start), OQ-OP-01..05 (see arch Appendix B).
  • PRD OQs carried: OQ-01..06 from docs/prd/operator-console-prd.md §A.6. OQ-04 resolved (cohort timezone = UTC+0 v0.0.0, ZoneId from TenantConfigCache wired from day 1 for V2 readiness). OQ-06 resolved (≥ 1 active cabinet_admin guard at service layer).
  • Tables owned (Tenant DB per D1): campaigns, campaign_contracts, operator_notes. cabinet_settings dropped (OP-ADR-03).
  • V2 forward-compat: No UNIQUE on campaigns.name; cohort_params JSONB absorbs V2 scheduling fields; send_attempt_count supports V2 auto-retry; full status enums from v0.0.0.

Update this file after every ARCHITECT session.