Files
halobestie-clone/requirement/phase3.3-plan.md
ramadhan sjamsani 780cade3db Phase 3.3: topic sensitivity + Phase 3.4: auth foundation
Phase 3.3 — Session Topic Sensitivity (complete):
- Backend: topic_sensitivity column + session_sensitivity_log, sensitivity service
  (flip with one-way-latch + audit), PATCH /api/shared/chat/sessions/:id/topic,
  topic carried in pairing + extension WS payloads, CC filter + sensitive stats
  + per-mitra sensitive columns on activity page
- client_app: TopicSelectionBottomSheet before pricing, topic flows through
  pairing request, silent WS handler for session_topic_updated
- mitra_app: SensitivityBadge + SensitivityTheme + sensitivityConfigProvider,
  overlay badge + yellow accent, chat screen app-bar toggle with configurable
  confirmation + latch, extension card shows current flag, history + transcript
  yellow theme
- control_center: Sensitivitas Topik settings section, topic filter + column
  with inline audit log, sensitive stats dashboard card, mitra activity
  sensitive columns with QC flag

Phase 3.4 — Self-Managed Auth (foundation only):
- Migration: auth_sessions + otp_requests tables, social identity columns on
  customers, password_hash + lockout on control_center_users, OTP + CC lockout
  app_config keys
- New services: password (bcrypt + complexity), token (JWT HS256 + refresh
  rotation, session_id claim pre-wires future Valkey revocation),
  social-identity (Google + Apple JWKS), OTP (Fazpass stub — real API TBD)
- Constants: AuthProvider + OtpChannel
- Middleware, auth route rewrites, WS auth update, Firebase → FCM isolation
  still pending (next chunk); Fazpass docs + Apple Developer setup still
  required before E2E testing

Docs:
- requirement/phase3.3.md, phase3.3-plan.md, phase3.3-testing.md
- requirement/phase3.4.md, phase3.4-plan.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 10:15:12 +08:00

26 KiB
Raw Permalink Blame History

Phase 3.3 Implementation Plan: Session Topic Sensitivity

Summary of Clarified Requirements

Topic Decision
Customer selection UX Required bottom sheet after "Mulai Curhat", before pricing; two buttons (not toggle)
Customer flag mutability Locked for session duration — customer cannot change
Mitra flag mutability Can flip mid-session via app-bar toggle
Flip confirmation dialog Default true, disableable via app_config key sensitive_flip_confirmation_enabled
One-way latch Default false, enableable via app_config key sensitive_flag_one_way_latch
Visual cue for mitra Label "Topik sensitif" + warning-yellow doodle background
Customer visual cue None — customer UI stays pink always
Storage chat_sessions.topic_sensitivity enum + mirrored on chat_requests-like flow (actual rows live in chat_sessions before acceptance; see Section 1.1)
Audit trail New session_sensitivity_log table — logs every mitra flip
WS event to customer Sent but silently ignored client-side (future-proofing)
Extension Shows current flag; flag carries over unchanged on acceptance
Control center Sensitive stats panel + filter on sessions page + 2 new config keys
Pricing Unchanged — schema leaves room for future differentiation
Push notification content Stays generic — label only surfaces in overlay
Multi-device mitra sync Out of scope
Auto-moderation Out of scope

Data Model Note

After reviewing the codebase, chat_requests is NOT a separate table — pairing requests live as rows in chat_sessions with status transitions (SEARCHINGPENDING_ACCEPTANCEACTIVE → ...). Mitra notifications are tracked in chat_request_notifications. Therefore, one column on chat_sessions is sufficient — no mirroring needed.


Work Stream 1: Backend — Schema, Services, Routes

1.1 DB Migration

File: backend/src/db/migrate.js

-- New column on chat_sessions
ALTER TABLE chat_sessions
  ADD COLUMN IF NOT EXISTS topic_sensitivity VARCHAR(16) NOT NULL DEFAULT 'regular';

-- Optional index if CC filter needs it
CREATE INDEX IF NOT EXISTS idx_chat_sessions_topic_sensitivity
  ON chat_sessions (topic_sensitivity);

-- New audit table
CREATE TABLE IF NOT EXISTS session_sensitivity_log (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  session_id UUID NOT NULL REFERENCES chat_sessions(id) ON DELETE CASCADE,
  changed_by_mitra_id UUID NOT NULL REFERENCES mitras(id),
  from_value VARCHAR(16) NOT NULL,
  to_value VARCHAR(16) NOT NULL,
  created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE INDEX IF NOT EXISTS idx_session_sensitivity_log_session
  ON session_sensitivity_log (session_id);

Seed new app_config keys (in the same migration or the seed script):

INSERT INTO app_config (key, value) VALUES
  ('sensitive_flip_confirmation_enabled', 'true'::jsonb),
  ('sensitive_flag_one_way_latch', 'false'::jsonb)
ON CONFLICT (key) DO NOTHING;

1.2 Constants

File: backend/src/constants.js

const TopicSensitivity = Object.freeze({
  REGULAR: 'regular',
  SENSITIVE: 'sensitive',
});

const WsMessage = Object.freeze({
  // ... existing
  SESSION_TOPIC_UPDATED: 'session_topic_updated',
});

1.3 Config Service

File: backend/src/services/config.service.js

Add:

  • getSensitiveFlipConfirmationEnabled() → bool (default true)
  • getSensitiveFlagOneWayLatch() → bool (default false)
  • setSensitiveConfig({ flipConfirmationEnabled, oneWayLatch }) — used by control center

1.4 Pairing Service — Accept Topic Flag From Customer

File: backend/src/services/pairing.service.js

  • createPairingRequest({ customerId, durationMinutes, isFreeTrial, topicSensitivity }) — new param, validated against TopicSensitivity enum; defaults to regular if omitted.
  • Insert topic_sensitivity column when creating the chat_sessions row.
  • When broadcasting the chat_request WS message to candidate mitras, include topic_sensitivity in the payload (alongside duration_minutes, is_free_trial).

1.5 New Service: Sensitivity Service

New file: backend/src/services/sensitivity.service.js

Exports:

  • flipSessionSensitivity({ sessionId, mitraId, toValue })
    • Load session; verify ownership (mitra_id matches).
    • Verify session status is ACTIVE or EXTENDING (not flippable if CLOSING, COMPLETED, etc.).
    • Read sensitive_flag_one_way_latch from config. If true and current value is sensitive, reject with 409.
    • No-op if fromValue === toValue.
    • Transaction: UPDATE chat_sessions.topic_sensitivity, INSERT session_sensitivity_log row.
    • Broadcast WS message session_topic_updated to both participants: { session_id, topic_sensitivity, changed_at }.
    • Return updated session.
  • getSessionSensitivityLog(sessionId) — used by control center session detail (future).

1.6 Extension Service — Expose Current Flag

File: backend/src/services/extension.service.js

  • requestExtension does not change the flag; flag carries over unchanged.
  • When broadcasting the extension request WS message to the mitra, include topic_sensitivity from the session row.

1.7 New Routes

File: backend/src/routes/public/shared.chat.routes.js (or the existing shared chat routes file — confirm name at implementation)

Method Path Purpose Auth
PATCH /api/shared/chat/sessions/:sessionId/topic Mitra flips flag Firebase (mitra)

Body: { topic_sensitivity: 'regular' \| 'sensitive' }

Response: { session_id, topic_sensitivity, changed_at }

1.8 Client Chat Request Route — Accept Topic Param

File: backend/src/routes/public/client.chat.routes.jsPOST /api/client/chat/request

Extend request body schema:

{
  duration_minutes: Joi/ajv,
  is_free_trial: bool,
  topic_sensitivity: enum('regular', 'sensitive') // required
}

Pass through to createPairingRequest.

1.9 Internal Routes — Config + Stats

File: backend/src/routes/internal/config.routes.js

Add:

  • GET /internal/config/sensitivity{ flip_confirmation_enabled, one_way_latch }
  • PATCH /internal/config/sensitivity → accept same shape

File: backend/src/routes/internal/session.routes.js

  • Extend session list query to accept topic_sensitivity filter param (all / regular / sensitive).
  • Extend session detail response to include topic_sensitivity and recent session_sensitivity_log entries.

File: backend/src/routes/internal/dashboard.routes.js (or wherever dashboard stats live)

  • Add sensitive_session_stats to the dashboard payload: { total_sessions, sensitive_count, sensitive_percent } over the last N days (match existing stat windows).

1.10 WebSocket — New Message Type

File: backend/src/plugins/websocket.js

No plugin change needed if broadcast is done via existing sendToSessionParticipant helper — just a new WsMessage.SESSION_TOPIC_UPDATED constant.


Work Stream 2: client_app — Topic Selection

2.1 New Widget: Topic Selection Bottom Sheet

New file: client_app/lib/features/chat/widgets/topic_selection_bottom_sheet.dart

A non-dismissible DraggableScrollableSheet (or showModalBottomSheet with isDismissible: false and enableDrag: false) containing:

  • Title, body, sub-question (copy as in PRD Section 1)
  • Two equal-width buttons: "Topik umum" (primary) + "Topik sensitif" (secondary style, equal height)
  • Helper line below buttons
  • System back / back button cancels and returns to home

Return: Future<TopicSensitivity?> — null if cancelled, otherwise the selected enum.

2.2 Topic Sensitivity Enum

New file: client_app/lib/core/constants/topic_sensitivity.dart

enum TopicSensitivity {
  regular('regular'),
  sensitive('sensitive');

  final String value;
  const TopicSensitivity(this.value);

  static TopicSensitivity fromString(String v) =>
    values.firstWhere((e) => e.value == v, orElse: () => TopicSensitivity.regular);
}

2.3 Home Screen — Wire Bottom Sheet Into Flow

File: client_app/lib/features/home/home_screen.dart

Current "Mulai Curhat" → opens pricing sheet directly.

New flow:

  1. Tap "Mulai Curhat"
  2. Show TopicSelectionBottomSheet → await result
  3. If result is null → no-op
  4. If result is non-null → open existing PricingBottomSheet, passing selected topic as param

2.4 Pricing Bottom Sheet — Carry Topic

File: client_app/lib/features/chat/widgets/pricing_bottom_sheet.dart

  • Accept TopicSensitivity topicSensitivity constructor param.
  • When firing the pairing request (via PairingNotifier), pass through topic_sensitivity to the request body.

2.5 Pairing Notifier — Pass Topic to Backend

File: client_app/lib/core/pairing/pairing_notifier.dart

  • Extend startPairing({ int durationMinutes, bool isFreeTrial, required TopicSensitivity topicSensitivity }) to include topic_sensitivity in the API call body.

2.6 No Customer UI Changes Beyond Selection

  • Chat screen remains pink doodle — no conditional coloring.
  • WS listener for session_topic_updated is wired but silently no-ops (so the server-side event doesn't cause errors).

File: client_app/lib/core/chat/chat_notifier.dart (or wherever WS messages are dispatched)

Add a switch case for session_topic_updated that logs and returns — no state update.


Work Stream 3: mitra_app — Flag Display + Mid-Session Flip

3.1 Shared Enum + Widget: Sensitivity Badge

New file: mitra_app/lib/core/constants/topic_sensitivity.dart — same shape as client_app version.

New file: mitra_app/lib/core/chat/widgets/sensitivity_badge.dart

A small pill widget rendering "Topik sensitif" on yellow background. Configurable size. Reused across overlay, chat header, extension card, history list.

3.2 Chat Request Overlay — Label + Color

File: mitra_app/lib/core/chat/widgets/chat_request_overlay.dart

  • Extend ChatRequestIncomingData (in mitra_app/lib/core/chat/chat_request_notifier.dart) with TopicSensitivity topicSensitivity field.
  • Parse from WS chat_request message payload (topic_sensitivity).
  • When sensitive: show yellow accent (e.g., left-border or top-strip) + SensitivityBadge near session metadata.

3.3 Chat Request Notifier — Parse Topic

File: mitra_app/lib/core/chat/chat_request_notifier.dart

Extend ChatRequestIncomingData constructor + WS handler to populate topicSensitivity.

3.4 Mitra Chat Notifier — Track + Flip Topic

File: mitra_app/lib/core/chat/mitra_chat_notifier.dart

  • Add TopicSensitivity topicSensitivity to the chat state (populated from session fetch on connect).
  • Handle incoming WS session_topic_updated → update state (authoritative from backend — handles the case where mitra flipped on another device in the future).
  • New method flipTopic():
    • Read sensitive_flip_confirmation_enabled from app_config provider (fetched on app start or via dedicated provider)
    • Show confirmation dialog if enabled
    • Call PATCH /api/shared/chat/sessions/:sessionId/topic via existing ApiClient
    • Show toast on success; show error dialog on failure (e.g., one-way latch violation → "Sesi sudah ditandai sensitif dan tidak bisa diubah kembali.")

3.5 Mitra Chat Screen — Yellow Doodle + Toggle

File: mitra_app/lib/features/chat/screens/mitra_chat_screen.dart

  • Conditional background: watch topicSensitivity from mitraChatNotifier; swap doodle asset (pink ↔ yellow).
  • Header banner: persistent small strip showing "Topik sensitif" label when sensitive.
  • App-bar toggle icon (e.g., Icons.flag / Icons.flag_outlined) wired to flipTopic().
    • If one_way_latch is enabled and current is sensitive, disable the icon and show tooltip "Sesi sudah terkunci sensitif."

3.6 Doodle Assets

New files: mitra_app/assets/doodle_yellow.png (or SVG) — warning-yellow version of existing pink doodle pattern.

  • Exact yellow hex: proposed #FFC107 (Material amber) or #F7B500 — confirm with designer at implementation. Choose whichever contrasts best with white text/dark badges.
  • Update mitra_app/pubspec.yaml assets section.

3.7 Extension UI — Current Flag

File: mitra_app/lib/features/chat/widgets/extension_request_card.dart (or wherever the extension request UI lives)

  • Read topicSensitivity from the extension WS payload / session state.
  • If sensitive: yellow accent + SensitivityBadge on the extension card.

3.8 Chat History List + Transcript

File: mitra_app/lib/features/chat/screens/chat_history_screen.dart (confirm name)

  • List row: add SensitivityBadge when session is sensitive.
  • Transcript detail: yellow doodle background for sensitive sessions (pink otherwise).

3.9 Mitra API Client

File: mitra_app/lib/core/network/api_client.dart (confirm name)

Add:

Future<Session> flipSessionTopic(String sessionId, TopicSensitivity to);

Work Stream 4: control_center — Config + Stats + Filter

4.1 Settings Page — New Sensitivity Section

File: control_center/src/pages/settings/SettingsPage.jsx

Add new section "Sensitivitas Topik":

  • Checkbox: "Aktifkan dialog konfirmasi saat Mitra menandai topik sensitif" (sensitive_flip_confirmation_enabled)
  • Checkbox: "Kunci searah — Mitra tidak bisa membatalkan tanda topik sensitif" (sensitive_flag_one_way_latch)

Wire to new endpoints: GET/PATCH /internal/config/sensitivity.

4.2 Sessions Page — Filter

File: control_center/src/pages/sessions/SessionsPage.jsx

  • Add dropdown filter: "Semua / Umum / Sensitif".
  • Pass topic_sensitivity query param to session list endpoint.
  • Add column "Topik" with badge (green "Umum" / yellow "Sensitif").

4.3 Session Detail — Show Audit Trail

File: control_center/src/pages/sessions/SessionDetailPage.jsx (confirm name)

  • Show current topic_sensitivity.
  • Show compact timeline of session_sensitivity_log entries: "Mitra X menandai topik sebagai sensitif pada HH:MM".

4.4 Dashboard — Sensitive Stats Panel

File: control_center/src/pages/dashboard/DashboardPage.jsx

  • New card: "Sesi Sensitif" with count + percentage over selected date range.

4.5 Mitra Activity — Per-Mitra Sensitive Breakdown

Backend file: backend/src/services/mitra-activity.service.js

Extend getMitraActivitySummary SQL to join chat_request_notificationschat_sessions on session_id and group per-mitra, returning additional columns:

  • sensitive_total — count of notifications where joined session had topic_sensitivity = 'sensitive'
  • sensitive_accepted — count of those with response = 'accepted'
  • sensitive_rejected — count with response = 'rejected'
  • sensitive_acceptance_ratesensitive_accepted / sensitive_total (null if sensitive_total = 0)

This is a pure join — no new column needed on chat_request_notifications because the flag is derivable from the parent session row.

Backend file: backend/src/routes/internal/mitra-activity.routes.js

GET /internal/mitra-activity/summary response includes the new fields above.

Control center file: control_center/src/pages/mitra-activity/MitraActivityPage.jsx

Extend summary table with additional columns (may need a sub-grouping to keep it readable):

| Mitra | Total | Accepted | Rejected | Missed | Ignored | Rate (%) | Avg Response (s) | Sensitive Total | Sensitive Accepted | Sensitive Rate (%) |

Optionally: color the "Sensitive Rate" cell red when significantly lower than the overall rate — useful for spotting mitras who accept regular requests but consistently skip sensitive ones (potential QC signal).

Optional detail view: on the detail log table, add a "Topik" column with the session's sensitivity badge.


5. Implementation Order

Step What Apps Affected Dependencies
Work Stream 1: Backend
1 DB migration: topic_sensitivity column, session_sensitivity_log table, 2 new app_config keys Backend None
2 Constants: TopicSensitivity, WsMessage.SESSION_TOPIC_UPDATED Backend None
3 Config service: getters + setter for sensitivity flags Backend Step 1
4 Pairing service: accept + persist + broadcast topic_sensitivity Backend Steps 12
5 New sensitivity service: flipSessionSensitivity, getSessionSensitivityLog Backend Steps 14
6 Extension service: include topic_sensitivity in WS payload Backend Step 4
7 New route: PATCH /api/shared/chat/sessions/:sessionId/topic Backend Step 5
8 Extend POST /api/client/chat/request schema Backend Step 4
9 Internal routes: /internal/config/sensitivity, filter on sessions, dashboard sensitive stats Backend Step 3, 5
Work Stream 2: client_app
10 TopicSensitivity enum client_app None
11 TopicSelectionBottomSheet widget client_app Step 10
12 Home screen: new flow (topic sheet → pricing) client_app Step 11
13 Pricing sheet + PairingNotifier: accept + forward flag client_app Steps 10, 12
14 ChatNotifier: silently handle session_topic_updated WS client_app None
Work Stream 3: mitra_app
15 TopicSensitivity enum + SensitivityBadge widget + yellow doodle asset mitra_app None
16 ChatRequestNotifier + overlay: parse topic, show badge + yellow accent mitra_app Step 15
17 MitraChatNotifier: track topic, handle session_topic_updated, flipTopic() mitra_app Step 15, backend step 7
18 Mitra chat screen: yellow doodle + header + app-bar toggle + config-driven dialog/latch mitra_app Step 17
19 Extension card: show current flag mitra_app Step 15, backend step 6
20 History list + transcript: badge + yellow doodle mitra_app Step 15
Work Stream 4: control_center
21 Settings page: sensitivity config section control_center Backend step 9
22 Sessions list: filter + column control_center Backend step 9
23 Session detail: audit trail timeline control_center Backend step 9
24 Dashboard: sensitive stats card control_center Backend step 9
25 Mitra Activity: extend summary SQL + API + table with sensitive columns Backend + control_center Backend step 1
Testing
26 E2E: customer selects sensitive → mitra sees label + yellow → mitra flips → audit + stats reflect All All above

6. New Files

File Purpose
backend/src/services/sensitivity.service.js Flip + audit + one-way-latch enforcement
client_app/lib/core/constants/topic_sensitivity.dart Enum
client_app/lib/features/chat/widgets/topic_selection_bottom_sheet.dart Customer topic-selection UI
mitra_app/lib/core/constants/topic_sensitivity.dart Enum
mitra_app/lib/core/chat/widgets/sensitivity_badge.dart Reusable badge
mitra_app/assets/doodle_yellow.png Yellow doodle for sensitive sessions

7. Modified Files (Primary)

File Change
backend/src/db/migrate.js Migration + seed
backend/src/constants.js TopicSensitivity + WsMessage.SESSION_TOPIC_UPDATED
backend/src/services/config.service.js Getters/setter
backend/src/services/pairing.service.js Accept + persist + broadcast topic
backend/src/services/extension.service.js Include topic in WS payload
backend/src/routes/public/client.chat.routes.js Request body schema
backend/src/routes/public/shared.chat.routes.js New PATCH topic route
backend/src/routes/internal/config.routes.js GET/PATCH sensitivity config
backend/src/routes/internal/session.routes.js Filter + audit in detail
backend/src/routes/internal/dashboard.routes.js Sensitive stats
client_app/lib/features/home/home_screen.dart New pre-pricing step
client_app/lib/features/chat/widgets/pricing_bottom_sheet.dart Accept + forward topic
client_app/lib/core/pairing/pairing_notifier.dart API body
client_app/lib/core/chat/chat_notifier.dart No-op WS handler
mitra_app/lib/core/chat/chat_request_notifier.dart Parse topic
mitra_app/lib/core/chat/widgets/chat_request_overlay.dart Badge + yellow accent
mitra_app/lib/core/chat/mitra_chat_notifier.dart State + flip method + WS handler
mitra_app/lib/features/chat/screens/mitra_chat_screen.dart Yellow doodle + app-bar toggle
mitra_app/lib/features/chat/widgets/extension_request_card.dart Badge + accent
mitra_app/lib/features/chat/screens/chat_history_screen.dart Badge on row + yellow transcript
mitra_app/pubspec.yaml Register new asset
control_center/src/pages/settings/SettingsPage.jsx Sensitivity config section
control_center/src/pages/sessions/SessionsPage.jsx Filter + column
control_center/src/pages/dashboard/DashboardPage.jsx Sensitive stats card

8. Risks & Mitigations

Risk Mitigation
Existing sessions have no topic_sensitivity value after migration Column has DEFAULT 'regular' + NOT NULL — backfill automatic
Mitra flips topic but WS delivery fails to customer Customer silently ignores anyway; backend state is source of truth, no user-facing consequence
Race: mitra flips to sensitive, then to regular while latch config toggles mid-session flipSessionSensitivity reads latch config at flip-time; flip is atomic in a DB transaction
Customer cancels topic-selection sheet → home screen flashes Confirm sheet result is null and return cleanly to home — no pairing request created
Yellow color clashes with existing pink UI elements Restrict yellow to chat screen background + extension card; test contrast with message bubbles and badges
Mitra flips flag after session is CLOSING Backend rejects with 409; mitra UI gracefully shows toast "Sesi sudah berakhir"
Old sessions in history have no audit log entries No migration needed — history just shows current value with no log entries

9. Testing Checklist

Customer flow (client_app):

  • Tap "Mulai Curhat" → topic sheet appears, cannot dismiss by tap-outside
  • System back cancels entire flow
  • Topik umum → pricing sheet shows, pairing request sent with topic_sensitivity: regular
  • Topik sensitif → pricing sheet shows, pairing request sent with topic_sensitivity: sensitive
  • Chat screen stays pink regardless of flag
  • Customer receives session_topic_updated WS message after mitra flip → no UI change, no error

Mitra flow (mitra_app):

  • Sensitive incoming request → yellow accent + "Topik sensitif" badge on overlay
  • Regular incoming request → no badge, no color change
  • Sensitive active session → yellow doodle background + header label
  • Flip toggle (confirmation enabled): dialog shows, Batal cancels, Tandai flips
  • Flip toggle (confirmation disabled via CC): flips instantly with toast
  • One-way latch enabled + session already sensitive: toggle disabled with tooltip
  • One-way latch enabled + session regular: can flip to sensitive, then toggle locks
  • Mitra requests extension on sensitive session → extension card shows badge + yellow
  • Mitra requests extension after mid-session flip to sensitive → extension card reflects current flag
  • History list: sensitive sessions show badge
  • Transcript: sensitive sessions show yellow doodle

Control center:

  • Settings page: toggle sensitive_flip_confirmation_enabled → mitra app behavior changes on next flip
  • Settings page: toggle sensitive_flag_one_way_latch → mitra app behavior changes
  • Sessions page: filter "Sensitif" only shows sensitive sessions
  • Session detail: audit trail shows all flips with mitra name + timestamp
  • Dashboard: sensitive session count + % matches manual query
  • Mitra Activity: per-mitra sensitive total / accepted / rate columns populate correctly
  • Mitra with 0 sensitive requests shows (not 0%) for sensitive rate

Backend:

  • flipSessionSensitivity logs every call in session_sensitivity_log
  • Ownership check: mitra A cannot flip mitra B's session
  • Status check: flip on CLOSING/COMPLETED session → 409
  • Latch violation returns 409 with clear error code
  • No-op flip (same value) does not create log entry