## Context See `proposal.md` for motivation. `TickerService` and `TickerServiceImpl` already exist as empty shells in AssetAZ-owned packages. `AssetPrice` wraps `ΩPriceΩ` and `TradingPair`; the latter identifies currencies structurally rather than by raw symbol. The repository temporarily hosts AssetAZ code as Cauldron, and NenjimHub's current autorun list directly constructs services. ## Goals / Non-Goals **Goals:** Establish a small reusable ticker contract, deterministic file-backed startup, atomic persist-before-publish behavior, and temporary autorun wiring while retaining AssetAZ ownership. **Non-Goals:** Datasource plugins, real market data, pairs other than `EVE_USDC`, consumers, subscriptions/callbacks, history queries, retention, compaction, or automated tests. ## Decisions - Define `PriceObservation` in `com.r35157.assetaz.core.service.ticker` exactly as a record of `AssetPrice price` and `Instant observedAt`. Extend `TickerService` with `start()` and `PriceObservation getLatestPrice(TradingPair tradingPair)`. Use clear exceptions for unsupported/inactive pairs and enabled pairs without a persisted observation; `Optional` and `null` were rejected because issue #60 explicitly requires exceptions for unavailable prices. Raw symbol strings were rejected because callers must use the established ValueTypes. - Add stable EVE and USDC currency definitions and an `EVE_USDC` well-known `TradingPair`, then construct the sample price with `new ΩPriceΩ("14.85")`. This keeps identity consistent with other typed prices instead of parsing pair names at the API boundary. - Keep the public API and `PriceObservation` in the AssetAZ API package and implementation details in `com.r35157.assetaz.core.service.ticker.impl.ref`. Their current repository location is temporary Cauldron placement and does not imply Evelyn or NenjimHub ownership. - Use the fixed path `data/assetaz/ticker/EVE_USDC.prices`; do not create its directory or file. Check existence when initializing the pair. Missing means inactive, emits a warning containing the pair and expected path, and does not prevent the rest of Ticker startup; an existing zero-length, blank-only, or comment-only file is active with no latest value. - Store one observation per data line as `:`, using a strict formatter fixed to UTC. Read line by line, remove text from the first `#`, trim, and skip empty results. Split the remaining data at the required colon and parse it into `Instant`, `ΩPriceΩ`, and the fixed `EVE_USDC` pair. Wrap any structural, timestamp, or price failure with the filename, one-based physical line number, and identifying invalid content. - Load history synchronously during `start()` and select the parsed observation with the greatest `observedAt` timestamp rather than assuming the file is chronologically ordered. After loading, generate the first hardcoded observation immediately, then use a single-thread scheduled executor with fixed-delay scheduling: each subsequent attempt begins one minute after the preceding attempt completes. Fixed-rate scheduling was rejected because a long pause can cause rapid catch-up attempts. Do not schedule generation for an inactive pair. The scheduler is an implementation mechanism, not a public lifecycle or subscription API. - Serialize generation and publication, and keep the published latest value behind a thread-safe reference. Validate and encode every generated observation, append all encoded bytes through a `FileChannel`, and call `force(true)`. Only after successful persistence, compare its `observedAt` with the current latest and replace the in-memory reference only when the new timestamp is later. Thus concurrent callers see the successfully persisted observation with the greatest timestamp across loaded history and newly persisted observations, and a manually inserted future-dated observation remains latest until a later observation is persisted. A buffered-writer `flush()` alone was rejected because it does not satisfy the issue's explicit flush/force-to-persistence ordering. On open, write, or force failure, log/report the failure and retain the prior latest value; never publish the attempted observation. - Add a temporary `startAssetAZTickerService()` entry to `NenjimHubImpl` following its direct autorun pattern. It constructs and starts `TickerServiceImpl`; no Ticker dependency is passed to Evelyn, EMC, alarms, portfolios, or other services. ## Risks / Trade-offs - [Filesystem or device guarantees can still limit crash durability after `FileChannel.force(true)`] → Force each complete appended record before publication and document that the implementation uses the strongest standard Java file-channel guarantee available here. - [An observation attempt can take arbitrarily long] → Fixed-delay scheduling waits until the attempt completes and then waits one minute, preventing overlapping or catch-up attempts. - [Malformed history prevents the service from starting] → Include exact filename and line number so operators can repair the activation file safely. - [Hardcoded identifiers and price are temporary] → Isolate them behind well-known ValueTypes and the reference implementation for later source replacement. ## Migration Plan Operators opt in by creating `data/assetaz/ticker/EVE_USDC.prices`, optionally pre-populated in the documented format. Deploy the service and temporary autorun wiring together. Rollback removes the autorun call and ticker implementation while leaving the append-only data file intact.