# Time-domain inventory This document is the authoritative classification of persistent timestamps, deadlines, cooldowns, and process-local elapsed-time rules. Classification is per rule, not per table. A feature may record both a wall occurrence and a game effect; those are two facts, never one fallback clock. ## Domain contract | Domain | Authority | Advances while suspended/reconciling | Reconciliation | | ------------------------ | -------------------------------------------------------------- | ------------------------------------ | ----------------------------- | | `GAME_TIME` | `world_state.clock_tick` under phase/revision/generation fence | no | `SHIFT`, `KEEP`, or `REBUILD` | | `WALL_TIME` | PostgreSQL UTC `CURRENT_TIMESTAMP` for persistent decisions | yes | never | | `MONOTONIC_ELAPSED_TIME` | `performance.now()` / monotonic process clock | process-local only | never persisted | `GameTick`, `ClockRevision`, `DeadlineGeneration`, `WallInstant`, and `MonotonicDuration` name these meanings in new/refactored APIs. Existing `createdAt`/`updatedAt` fields remain wall audit timestamps unless this inventory explicitly calls them game projections. ## Game database inventory `Pause` means whether the rule continues to age during `SUSPENDED` or `RECONCILING`. `Projection` means a non-authoritative compatibility/display representation. | Table / rule / field(s) | Current meaning | Domain and authority | Pause | Reconcile / projection | Decision and reason | | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------- | ------------------------------------ | -------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- | | `world_state.clock_tick` | observed world coordinate | GAME, self-authoritative | stop | REBUILD | root of all game-time decisions | | `world_state.last_turn_tick` | executed turn cursor | GAME | stop | SHIFT | execution order must preserve skipped-turn policy | | `world_state.clock_revision`, `deadline_generation` | clock/future-deadline generations | GAME metadata | stop | REBUILD | stale commands/workers must fail their fence | | `world_state.clock_base_time`, `clock_wall_anchor` | tick-to-date mapping and wall observation anchor | GAME projection metadata | n/a | REBUILD | not business wall deadlines | | `world_state.updated_at` | row audit | WALL, DB UTC | advance | excluded | operational history is not shifted | | `clock_suspension.source_revision`, `target_revision`, `cut_tick`, `catch_up_ticks`, `gap_ticks`, `shift_ticks`, `aligned_tick` | reconciliation plan/audit | GAME metadata | stop | KEEP | immutable clock operation facts | | `clock_suspension.cut_wall_at`, `resume_wall_at`, `created_at`, `updated_at` | operator/runtime occurrence audit | WALL, DB UTC | advance | excluded | records when the real operation occurred | | `clock_projection_outbox.target_revision` | target GAME generation | GAME metadata | stop | KEEP | projection fence | | `clock_projection_outbox.available_at`, `locked_at`, `applied_at`, `created_at`, `updated_at` | retry/lease/audit | WALL, DB UTC | advance | excluded | worker control cannot pause with game time | | `clock_reconciliation_participant` checksum/count/policy | immutable operation evidence | GAME operation metadata | n/a | KEEP | evidence, not a deadline | | `input_event.accepted_game_tick`, `accepted_clock_revision`, `accepted_deadline_generation` | daemon-claim boundary | GAME metadata | stop | KEEP across matching revision; rebase pending legacy rows only | API does not pre-stamp these; daemon owns acceptance | | `input_event.processing_game_tick`, `processing_clock_revision`, `processing_deadline_generation` | actual mutation boundary | GAME metadata | stop | KEEP | effect validation/RNG uses this coordinate | | `input_event.created_at`, `processing_at`, `completed_at`, `lease_until` | request receipt, processing audit, lease | WALL, DB UTC | advance | excluded | external occurrence and worker lease | | `read_model_outbox.*_at`, `web_push_outbox.*_at` | availability, claim, delivery, audit | WALL, DB UTC | advance | excluded | retry and notification delivery are operational | | `turn_daemon_lease.lease_until`, `heartbeat_at` | daemon liveness | WALL, DB UTC | advance | excluded | acquire/renew/assert/release use `CURRENT_TIMESTAMP AT TIME ZONE 'UTC'`, including KST sessions | | `general.turn_tick` | next general turn | GAME | stop | SHIFT; `turn_time` projection | determines engine order | | `general.recent_war_tick` | past battle occurrence | GAME | stop | KEEP; `recent_war_time` projection | historical event does not move | | `general.meta.next_change_tick` | N-turn reselection cooldown | GAME | stop | SHIFT; `next_change`/`nextChangeAt` projections | expressed in turns; missing tick fails closed | | `general.created_at`, `updated_at` | entity audit | WALL, DB UTC | advance | excluded | no gameplay deadline meaning | | `select_pool.reserved_until_tick` | selection reservation deadline | GAME | stop | SHIFT; `reserved_until` projection | reservation is measured in game turns | | `select_npc_token.valid_until_tick`, `pick_more_from_tick` | NPC selection windows | GAME | stop | SHIFT; DateTime projections | token is a game selection schedule; missing ticks fail closed | | `general_access_log.last_refresh`, `last_action_at`; `general_access_batch.created_at`; `traffic_period.started_at`, `last_refresh`; `traffic_period_general.last_refresh` | traffic/access accounting | WALL, DB UTC | advance | excluded | community/operations usage, not world progression | | `message.created_at_wall`, `delete_until_wall`, `tombstoned_at_wall` | envelope send/delete lifecycle | WALL, DB UTC | advance | excluded | normal messages work while the game is paused; deletion is real five minutes | | `message.occurred_game_tick` | optional game context | GAME occurrence | stop | KEEP | context only, never deletion authority | | `message.time`, `time_tick`, `valid_until`, `valid_until_tick` | rolling compatibility projections | projection only | n/a | recompute only for `message_action`; general-envelope values never decide lifecycle | old columns are not fallback authority | | `message_action.created_game_tick`, `resolved_game_tick` | action occurrence/resolution | GAME occurrence | stop | KEEP | actionable message lifecycle is separate from envelope | | `message_action.expires_game_tick` | proposal response deadline | GAME | stop | SHIFT | remaining game duration survives pause | | `message_action.clock_revision`, `deadline_generation` | response fence | GAME metadata | stop | REBUILD | stale responses are rejected | | `message_action.created_at_wall`, `updated_at_wall`; `message_read_state.updated_at` | audit/read occurrence | WALL, DB UTC | advance | excluded | community UX state | | `diplomacy_letter.date` | document authored/sent time | WALL, DB UTC | advance | excluded | game effect dates live in diplomacy/action state, not the document timestamp | | diplomacy war/nonaggression start/end month data | diplomatic effect schedule | GAME calendar | stop | handled by engine schedule | affects world turns and war validity | | `inheritance_point.updated_at`, `inheritance_log.created_at`, `inheritance_result.created_at`, baseline/user-state audit fields | account ledger/result audit | WALL, DB UTC | advance | excluded | account/external-currency history | | `inheritance_ledger.requested_at_wall`, `consumed_at_wall`, `created_at_wall` | direct purchase receipt | WALL, DB UTC | advance | excluded | real request/debit receipt | | `inheritance_ledger.applied_clock_revision`, `applied_deadline_generation` | game-effect fence metadata | GAME metadata | stop | KEEP | no `applied_game_tick`: current direct effects are timeless immediate state changes | | inheritance command `input_event` | durable effect state/idempotency | WALL receipt + GAME processing fence | mixed, separated | only GAME coordinate participates | one transaction commits debit, receipt, effect, and command success; failure leaves durable input event and no debit | | `auction.open_tick`, `auction_bid.occurred_game_tick` | open/bid game occurrence | GAME | stop | KEEP; bid `event_at` is projection | event order/RNG/replay context | | `auction.close_tick` | in-world close deadline | GAME | stop | SHIFT; `close_at` projection | authoritative worker/finalizer deadline; missing tick fails closed | | `auction_bid.requested_at_wall`, `created_at`; `auction.finalizing_at`, `finished_at`, `created_at`, `updated_at` | request/processing/audit | WALL, DB UTC | advance | excluded | real action and recovery history | | `auction.latest_event_at` | optimistic compatibility projection of latest game event | GAME projection | stop | follows authoritative event tick | never used as wall deadline | | `vote_poll.start_tick`, `end_tick` | poll occurrence/deadline | GAME | stop | KEEP/SHIFT; `start_at`/`end_at` projections | poll is an in-world survey; missing deadline tick fails closed | | `vote_poll.closed_at`, `created_at`, `updated_at`; `vote.created_at`; `vote_comment.created_at` | closure/user/audit occurrence | WALL, DB UTC | advance | excluded | closure receipt and community content history | | tournament `nextTick`, `bettingCloseTick` in Redis | stage/betting deadlines | GAME | stop | REBUILD; `nextAt`/`bettingCloseAt` projections | stages advance with the world; legacy date-only state fails closed | | nation betting open/close year-month and tournament phase | in-world availability | GAME calendar/tick | stop | engine/Redis participant | tied to tournament turns | | tournament/nation bet submission | user WALL request + effect at the current frozen GAME coordinate | WALL + GAME, separated | submission is allowed during `SUSPENDED`; GAME deadline does not age | receipt excluded; GAME availability/fence retained | pausing stage progress must not close an already-open betting window | | `nation_betting.*_at`, `nation_bet.*_at` | user/audit occurrence | WALL, DB UTC | advance | excluded | receipts, not close authority | | `game_history.date`, old-general `turntime`, archived projected dates | archived game-calendar projection | GAME historical display | stop | KEEP, never shifted | immutable archive/replay record | | archive/entity `created_at`, cancellation `opened_at`/`cancelled_at`, unification `completed_at` | operation/archive audit | WALL, DB UTC | advance | excluded | real creation/completion facts | | `general_turn_revision.lease_expires_at`, `nation_turn_revision.lease_expires_at` and audit timestamps | edit lease/revision audit | WALL, DB UTC | advance | excluded | editor concurrency timeout | | board post/comment, log/error/event, legacy migration timestamps | content/audit/migration history | WALL, DB UTC | advance | excluded | community and operational evidence | The year-9999 message sentinel remains a legacy projection only. `MAX_SAFE_GAME_TICK` is the separate GAME-domain infinite sentinel. Neither is converted into or used as the other domain's ordinary deadline. ## Gateway database inventory Gateway has no gameplay clock authority. Every Gateway `DateTime` is WALL_TIME: - `app_user`: identity/session/icon/terms/privacy/Kakao/grace/deletion/login and `created_at`/`updated_at` fields. - access grants, retired identities, admin audits, user icons, legacy member logs, and migration timestamps. - profile lifecycle `preopen_at`, `open_at`, `scheduled_start_at`, build request, start/completion/last-used, and row audit timestamps. These are real control plane schedules; they do not replace a profile's `world_state.clock_tick`. - subscriptions/preferences/receipts/notifications and web-push delivery `available_at`, `locked_at`, `delivered_at`, expiration and audit fields. - runtime actions, operations, releases, and bulk releases: schedule, start, completion, retry, lease, heartbeat, successful and audit timestamps. Competitive Gateway operation/release claim and lease renewal decisions read PostgreSQL `CURRENT_TIMESTAMP` inside the persistence transaction; the caller's poll timestamp is not authoritative. A Gateway PREOPEN wall schedule is an operational request; once the game exists, gameplay schedules use the game database tick. ## Process-local monotonic inventory The following are `MONOTONIC_ELAPSED_TIME` and are never persisted: daemon/RPC wait budgets, turn processing budgets, worker poll/resync intervals, lock acquisition waits, readiness loops, short-lived cache TTLs, latency metrics, and test wait loops. Production implementations use `performance.now()` where an elapsed duration is measured. `Date.now()`/`new Date()` remains valid only when creating or formatting a WALL occurrence, calculating a non-competitive auth TTL for an external protocol, or providing an explicit test clock. ## API phase policy | Operation | During `SUSPENDED` / `RECONCILING` | Fence | | -------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------- | ----------------------------------------------------------------------- | | normal public/private/nation message send/read/delete, including receiving/reading an existing recruitment letter envelope | allowed | WALL DB transaction only; actionable deadline remains frozen GAME state | | notification/account/inheritance history/audit reads | allowed | WALL | | actionable message response | rejected/queued except the explicitly authorized unification response | daemon clock phase/revision/generation | | tournament/nation bet submission while its GAME window is open | allowed in `SUSPENDED`; rejected in `RECONCILING` | frozen GAME deadline + phase/revision/generation fence | | tournament stage transition/close/settlement and nation-bet close/settlement | not applied | daemon GAME fence | | turn, reservation, war/diplomacy effect, auction, vote, other tournament mutation | not applied | daemon GAME fence | | direct inheritance state mutation | rejected if the daemon GAME fence cannot commit | atomic input-event + revision/generation fence | There is currently no account-only inheritance purchase endpoint. Direct inheritance commands use policy 1 (no debit if game mutation cannot commit), not an ambiguous partially-applied state. Their `InputEvent.requestId` is the durable idempotency/effect state; `InheritanceLedger.requestId` proves the one successful receipt. ## `loadCurrentGameTime` call-site audit All production call sites were reviewed. The remaining uses are GAME-only: - `messages/store`: create/read/invalidate the separate `message_action`; normal envelope creation, display, read state, and deletion do not load game time. - `messages/diplomaticResponse`: game-effect/log calendar context after an actionable response fence; the letter's authored date is DB WALL_TIME. - auction `open`, `scheduler`, `worker`, and router: open/close tick projection, due evaluation, and fence context; bid receipt time is separate WALL_TIME. - vote router, tournament router/worker: GAME poll/stage deadlines and Redis projection fences. - troop/general/join selection routers: current world turn/schedule context; the NPC reservation mutation holds the clock advisory lock and `world_state` row fence before reading it. - lobby: display-only projected server game time and phase. No inheritance receipt, ordinary message timestamp/delete rule, audit log, lease, outbox retry, API timeout, or worker budget calls this helper. Ordinary message send/read-state/delete mutations retain their durable API `InputEvent` transaction and read-model journal, but use the WALL-only input boundary. That boundary deliberately does not acquire the game clock advisory lock, so a reconciliation transaction cannot unnecessarily serialize community messaging. Actionable responses continue to use the GAME-fenced boundary. ## Migration boundary Migration `20260903140000_split_message_wall_and_game_time` adds and backfills the explicit message, action, auction-bid, inheritance receipt, and selection cooldown authorities. It never extends an already-expired message delete window. Old projection columns remain during rolling deployment, but new code never chooses a clock by NULL fallback: GAME rules require their tick; WALL rules use their wall column. The disposable migration verifier covers populated upgrade, indexes/constraints, replay safety, and a second no-op deploy. Migration `20260903183000_turn_daemon_lease_utc_wall` treats daemon leases as ephemeral WALL authority: it expires pre-deployment rows, changes the heartbeat default to DB UTC, and requires the new daemon to acquire a fresh fencing epoch. Migration `20260903201500_complete_invader_game_clock` moves historical `isUnited>=3` worlds from `RUNNING`/`MANUAL` to `COMPLETED` and resolves pending unchosen invader actions at `max(created_game_tick, world_state.clock_tick)`. Neither migration shifts a WALL occurrence or turns one rule into a clock fallback.