Project artifacts
The stage-level files that sit alongside the atomic artifacts: the vocabulary they are written in, what was assumed, and what is still open.
Architecture drivers
Architecturally Significant Requirements
- FR-001: high_impact_function (significance: Writing on every stat change, not on a timer, fixes the write cadence as event-driven and makes a mid-write kill an ordinary event rather than an exotic one — which is what forces commit atomicity into the store rather than leaving it to the caller. It also puts the only path that can destroy accumulated state behind one contract, which is the structural precondition for BR-002’s “inspect every state-writing path” being a finite inspection at all.)
- FR-002: high_impact_function (significance: Places a computation whose input is elapsed real time on the critical launch path ahead of first render, which separates the launch sequence into ordered stages (read, validate, decay, evaluate, render) and forces the wall clock to be an injected dependency rather than an ambient call — a decay function that reads the clock itself cannot be driven through NFR-001’s negative-interval matrix.)
- FR-008: high_impact_function (significance: Requires a per-stat below-threshold clock that runs continuously across an application close, while the glossary’s Pet state entry — the maintained enumeration of what is persisted — carries no such field. Either the clock is reconstructed from the decay curve at each evaluation or a new persisted field is introduced; the choice is structural and is not settled by any requirement.)
- FR-009: high_impact_function (significance: The only requirement that crosses the process boundary out of the application, into the OS notification service. It forces an external component and a platform-adapter seam, and it is the one place a “local” delivery mechanism could quietly acquire a network dependency, which CON-002 forbids outright.)
- FR-011: high_impact_function (significance: Introduces a second, incompatible non-positive-interval rule alongside FR-002’s: a deadline must re-base its origin where a quantity merely clamps. Two rules over the same clock reading means the clock contract cannot expose a single “elapsed since” and be done; the discriminator has to live somewhere the design names. It also makes the pet’s state advance with no owner input at all, which is what forces a recurring evaluation driver to exist as its own unit rather than as a side effect of rendering.)
- FR-012: high_impact_function (significance: Splits the load path in two — a read that succeeds and a read that fails validation and must preserve its evidence byte-identically before anything else writes — so integrity validation and quarantine cannot be folded into the same unit that hands back a decoded pet state. The byte-identical move is also one of the two most platform-divergent operations in the product (NFR-006 names it), so it must land inside the adapter seam.)
- NFR-001: quality_attribute (significance: Correctness is expressed against an external oracle rather than against hardcoded rates, so the production decay computation must be substitutable with, and comparable to, a reference model over the same inputs. That makes decay a pure mapping from starting state and interval to decayed state rather than a routine that mutates ambient state.)
- NFR-002: quality_attribute (significance: Budgets the whole delivered runtime, including “whatever background timer machinery the chosen implementation uses”, at idle. It is the counter-force on FR-011’s evaluation driver: the cadence that makes wake and neglect progression prompt is the same cadence that spends the idle CPU budget, so the driver’s period is a first-class architectural parameter and not an implementation detail.)
- NFR-003: quality_attribute (significance: Requires mood and health status to be exposed as text through each platform’s native accessibility API. That makes the presentation surface a consumer of semantic state rather than of rendered pixels, and puts accessibility integration inside the same platform-adapter seam as notifications and paths.)
- NFR-004: quality_attribute (significance: The discriminating clause — a surviving committed state must be the one restored, and recovery to a default pet must occur in 0% of such trials — rules out any design in which a failed read falls through to the default-pet path without first establishing that no committed state survives. Atomicity has to be a property of the write primitive, because no caller-side ordering can supply it.)
- NFR-005: quality_attribute (significance: Removes an entire tier from the design space: there is no client, no sync, no remote error reporting, and the assertion is checked at the network interface rather than in review. Combined with CON-002 it means the only component permitted to touch anything outside the process is the platform adapter, and what it touches is enumerable.)
- NFR-006: quality_attribute (significance: The most directly structural requirement in the set: it names a layer and then counts violations of it (0 platform conditionals outside the platform-adapter layer). Every platform-touching seam it enumerates — data paths, clock, notifications, accessibility, rendering, the quarantine move — is an allocation decision this decomposition must make explicitly rather than discover later.)
- NFR-007: quality_attribute (significance: Requires exactly one structured record per decay computation and per lifecycle transition, carrying pre-state, post-state and deltas, and replayable through the reference model. “Exactly one” is a structural claim: the emit points must be singular and owned, which rules out both incidental logging scattered through callers and a component that computes a transition without announcing it.)
- NFR-008: quality_attribute (significance: Carries an explicit testability constraint on the design (requirements A-17): the decay computation must be independently invocable — callable 1,000 times in a loop with an in-memory starting state, no save file read and no application launch. That forbids decay being a stage inside the launch routine and forces it to be its own unit that the launch routine calls. The one-bounded-step response additionally forbids a tick-walking implementation.)
- NFR-009: quality_attribute (significance: Bounds the same computation in place, and does so over a window defined structurally — between the completion of the committed-save read and the first render. That the window is expressible at all requires the launch sequence to have those two events as distinct, observable boundaries rather than as one opaque startup.)
- CON-001: constraint (significance: Excludes whole runtime families from the design space at selection time rather than tuning a figure afterwards, and requires a runtime decision record that names what was rejected. It is a boundary on the shell everything else is built inside, so it is settled before any component exists and cannot be revisited by optimising component code.)
- CON-002: constraint (significance: An absolute structural boundary rather than a target: no core function may depend on a remote service, so no component may hold a network client at all. It is what makes the OS notification service the only permissible reminder delivery mechanism and the local file the only permissible system of record.)
- CON-003: constraint (significance: Bounds the design space rather than the release plan — Windows-first shipping does not relax it. Any platform-exclusive API adopted anywhere outside the adapter layer forecloses a named target, so the adapter boundary must be drawn now, while only one platform is being built.)
- BR-001: business_rule (significance: “0 terminal transitions arise from an application fault” is an allocation rule, not a behaviour: the recovery paths must be structurally incapable of producing a terminal pet, which means health-status advancement has exactly one writer and that writer is not on the recovery path.)
- BR-002: business_rule (significance: Verified by inspecting EVERY code path that writes, clears or replaces persisted pet state — a verification method that is only affordable if those paths converge on one contract. It also forbids a disposition existing anywhere, so the terminal status must be a state the progression can reach and nothing downstream is permitted to consume.)
Tradeoffs
- Recurring-timer access is allocated to the platform-adapter layer as its own contract (IF-031,
start_recurring+cancel), with CMP-010 keeping only the cadence — its period, its coalescing rule, and when it starts and stops — and consuming the tick. The timer is deliberately a separate contract from the wall clock (IF-018) rather than an operation on it, and is deliberately NOT declared as an outward need on an external component the way the clock (IF-024/CMP-019) and the notification service (IF-023/CMP-018) are. New this round; it is the F-14 fix. Gains: NFR-006’s sixth enumerated seam moves inside the layer it names, so the zero-conditionals-outside-the-layer count becomes achievable for the one component whose whole purpose is to be driven by the platform, and CMP-017’s claim to take NFR-006’s enumeration in full becomes true. IF-010 gains a structural source for the failure it was already declaring: it can report an unestablishable timer facility because its provider asks a contract that can refuse, which was not true of it before. Quiescence on the close path gains a second, stronger half — IF-031’scancelconfirms that no further tick will be delivered, which is what IF-029’s ordering obligation actually needs. CON-003’s boundary is drawn now, while only Windows is being built, which is when it is cheap. Separating the timer from the clock keeps a timer out of the dependency surface of the five components that only read the clock. Costs: Every tick now crosses the adapter boundary, and NFR-002’s measure budgets exactly that machinery by name — “whatever background timer machinery the chosen implementation uses” — so the indirection lands on the one path the budget is measured on, and its cost scales with the same cadence period that is itself undecided. Where a direct runtime timer would have been one call inside one component, the arrangement is now two contracts, a handler passed across a layer boundary, and a two-party quiescence protocol whose two halves can each fail to confirm within a bounded wait (IF-031’s last-but-one mode and IF-010’s matching one), so close latency includes an adapter cancel round-trip on top of the in-flight evaluation tail. The seam’s shape is also the least settled thing in the set: IF-031 concedes that if Q-10 resolves to an adaptive or event-driven cadence, the period argument — and possibly whether a recurring-tick primitive is the right primitive at all — changes with it, so this is a boundary fixed ahead of the decision that determines what should sit on it. And the modelling asymmetry has to be argued rather than read off: the clock gets an external component and the timer does not, which is defensible on the inherited two-item integration-point list and on the timer carrying no readings, but it is a judgment the design states rather than a distinction the requirements draw. Affected: NFR-006, NFR-002, CON-003, FR-011, FR-008. - Rendering and accessibility integration are routed through one adapter contract (IF-027,
draw_view+announce_status_text) consumed only by the presentation shell, rather than riding on the shared runtime surface — with the modelling decision now resting on the inherited integration-point list rather than on a claim that the far end is in-process. Gains: NFR-006’s zero-conditionals target becomes structurally achievable and NFR-003’s three distinct native accessibility stacks get one place to diverge behind. CMP-016 writes the semantic content once and only the adapter changes when macOS and Linux follow Windows. The premise correction is itself a gain: the design now says that an accessibility stack can be a service in its own right, so a later reader deciding whether an accessibility outage is a local fault or an external dependency is told the right answer, and the synchronous choice is argued the stronger way — it is exactly when there is a party on the other end that can be missing that the caller must learn the announcement did not land. Costs: At v1 IF-027 has exactly one implementation, so the seam is pure overhead against the stated team constraint of no dedicated platform specialists; the cross-platform value is entirely deferred to a staged release. Every frame crosses a layer boundary, and IF-030 asks for a redraw on every cadence tick, so the indirection lands on the same idle path NFR-002 budgets — though this round’s announcement suppression removes the announce half of that per-tick crossing. The layer keeps a facility whose far end can be a separate service without an external component to hold assumptions about it, which is the same threshold the set applies to the filesystem and now to the timer: consistent, but it means three of the adapter’s platform dependencies have no modelled counterparty. Affected: NFR-006, NFR-003, CON-003, NFR-002, CON-001. - IF-030
refreshtakes the pre-advance pet state and suppresses the accessibility announcement where neither the mood expression nor the health status changed, while the visual redraw runs unconditionally. Rewritten this round; it is the F-13 fix, and the previous shape announced on every tick. Gains: A screen-reader user no longer hears an unchanged mood and health status repeated at cadence frequency, which is what NFR-003’s scripted walkthrough is run to catch, and the suppression is a property of the contract rather than a discipline the caller keeps. Conditioning only the announcement keeps the frame tracking the live pet state, so nothing visual is traded away to get it. The parameter costs no new state and no new dependency: it is the same pre/post pair CMP-009 already hands IF-007selectand IF-015raise_for_stat_change, and taking it keeps CMP-016 a pure consumer rather than making it remember what it last announced. Costs: Correctness of the suppression is a caller obligation no contract can enforce, and IF-030’s last error mode concedes it: a pre-state that is not the one the pet actually held silently suppresses a real change, which is an NFR-003 failure that is invisible in the direction that matters most — a missed announcement is worse than a repeated one, and the design accepts that exposure in order to remove the repetition. The mood-changed determination now exists in two places computed from the same pair by different providers, IF-007’s changed flag and IF-030’s own comparison, with nothing requiring them to agree. And the comparison presumes the live pet state carries the derived mood expression that the persisted enumeration does not, which nothing in the set states outright — see the sensitivity point below. Affected: NFR-003, FR-007, FR-008, FR-011, NFR-002. - IF-018 hands out a signed interval and applies neither of requirements A-6’s non-positive-interval rules, pushing the discriminator to each consumer: IF-003 clamps as a quantity, IF-006 re-bases the sleep-entry origin, and IF-004 re-bases the per-stat below-threshold origin. Gains: Neither rule is silently applied to the wrong consumer. A single “elapsed since” contract would have to pick one and would give the other consumers a wrong answer that passes every functional test: clamping alone defers FR-011’s wake and FR-008’s progression without bound, and re-basing alone would let FR-002 lose elapsed time. Every consumer now names its rule at its own point of consumption, and each states the cost of its choice rather than asserting it. Costs: Correctness is a distributed obligation no contract can enforce: every present and future consumer of an elapsed interval must independently know which rule its own requirement names. Each rule-applying consumer is also its own clock reader, so a single pet-state evaluation takes three independent readings of the wall clock, and nothing in the design makes them one instant — under exactly the backward-clock conditions the rule machinery exists for, those readings can straddle the jump and have decay clamp against one while two deadlines re-base against others. The glossary defines a pet-state evaluation as an occasion on which the system reads the current wall clock, singular. Affected: FR-002, FR-008, FR-011, NFR-001.
- The balance parameter set is supplied by one component read by seven, and is loaded from its configuration source exactly once at launch (IF-026) with every subsequent read served from memory (IF-001), rather than being compiled in or lazily loaded. Gains: Q-1’s still-open balance loop — decay rates, both neglect thresholds, both sustained-neglect durations, the sleep duration, the mood bands, the care increments — can run without touching the decay, progression, mood, care or reminder code. Load-once is what makes NFR-008’s measure executable at all: the whole operation-level read set of IF-003
decayis file-free, so 1,000 repetitions reach no file. A source-level failure fails once, at launch, in front of the only caller positioned to abandon the launch, rather than one reader at a time far from the cause. Costs: The parameter set is frozen for the session — IF-026 refuses a second load and does not observe an edit made while running — so each iteration of the tuning loop the requirement set calls live and ongoing costs a relaunch. It is the widest fan-in in the set, and its source-missing mode abandons the launch outright, which makes a hand-edited or corrupt configuration file a total launch outage rather than a degraded run. All seven readers must additionally handle IF-001’s read-before-load mode, a failure that exists only because of the ordering this decision introduces. Affected: NFR-008, NFR-001, NFR-009, FR-010, FR-007, FR-008. - FR-001’s close trigger is discharged by an explicitly ordered close path — IF-010
stop, then IF-029commit_before_close, then exit — with the commit routed through the session (CMP-003) rather than called directly on the store. Gains: FR-001’s second named trigger gets an owning element and AC-3’s Sleeping-state survival gets a place it is satisfied. No in-flight evaluation can install a re-derived state after the final commit has read the live one, which would otherwise persist a state the owner never saw and leave the next launch re-deriving decay from a timestamp that was stale when written. Routing through CMP-003 keeps BR-002’s inspection converging on one in-memory custodian and one durable one — and CMP-003’s responsibility now states that, so the convergence argument no longer depends on reading IF-029’s body to find it. Costs: Close latency now includes the tail of an in-flight evaluation plus, since this round, an adapter-level cancel confirmation, because quiescence has two halves andstopreturns only once both hold. Both halves can fail to confirm within a bounded wait, leaving the caller to decide whether to commit and exit anyway. The ordering itself is enforced by nothing: it is stated as IF-029’s second error mode, and the body admits the reverse order is “silently wrong rather than loudly broken”, so the one path where getting it wrong is invisible is also the one path no contract guards. Affected: FR-001, NFR-004, NFR-007, NFR-002. - IF-002
replaceis synchronous and carries the durable commit behind it, so a care action and an evaluation both block on a file write rather than the store committing behind the caller’s back. Argued explicitly as a close call in IF-002’s Interaction section. Gains: A caller is never told a change succeeded that never reached disk, which is the outcome NFR-004’s discriminating clause is written to catch, and FR-001’s persist-on-stat-change becomes a property of one contract rather than a discipline every caller must keep. Costs: The store’s latency lands on the care-action path and on every evaluation that changes anything. Because FR-001’s trigger is combined with a running cadence, write frequency is a function of the cadence period, so the same undecided parameter that spends NFR-002’s idle CPU budget also spends idle I/O. NFR-003’s requirement that a care action’s state change be announced means the announcement waits on the write too. Affected: FR-001, NFR-004, NFR-002, NFR-003. - IF-015
raise_for_stat_changeis asynchronous — the evaluation hands the crossing over and continues — while its sibling IF-028set_preferenceon the same provider is synchronous and confirmed durable. Argued as a close call in IF-015’s Interaction section. Gains: The OS notification service’s latency, which is outside the application’s control, never lands inside NFR-009’s launch measurement window, and a missed or slow reminder can never delay or fail a pet-state evaluation. The synchronous half is where it matters: an owner who toggles reminders on is told whether the choice survives the session. Costs: The evaluation cannot report a reminder failure to anyone, so a dropped reminder is invisible to the path that caused it and FR-009’s “presented in 100% of test trials” arm has no in-product observation point. The owner’s toggle blocks on a file write, accepted only because it is a deliberate one-off action rather than a hot path. Affected: FR-009, NFR-009, NFR-002. - IF-016’s two recording operations are asynchronous to their callers while IF-021’s three file operations are synchronous to CMP-015 — the asynchrony is absorbed at the log component rather than pushed down to the file primitive. Gains: No lifecycle transition is ever delayed or failed by a log write; NFR-007’s record is a consequence of the transition, never a precondition. CMP-015 still learns how many bytes landed and what the file’s size is, which the rotation accounting requires, and
roll_overin particular must be synchronous because the next append goes to the path it just freed. Costs: An emitting component cannot know its record landed, so NFR-007’s “exactly one structured record” is unverifiable from the emit side and a dropped record is indistinguishable from one never emitted. IF-016 has to state record-dropped as its own failure mode precisely because no consumer can state it. Affected: NFR-007, NFR-004. - One committed generation is retained rather than two (Q-9, resolved), on the argument that atomic write-then-rename already prevents an interrupted write from replacing a committed file. Gains: A simpler store contract, one write per commit rather than a rotation, and less idle I/O and disk against NFR-002’s footprint budget. Costs: Post-commit media corruption has no second generation to fall back to; it resolves to FR-012’s quarantine plus FR-010’s default pet, which is total loss of accumulated state, and BR-002’s preservation clause is then satisfied only in the weak sense that the corrupt bytes are preserved at the quarantine location. IF-025’s fourth error mode concedes that the atomicity the whole argument rests on is a property of the platform and is not detectable from inside the contract. Affected: NFR-004, FR-012, BR-002, NFR-002.
- Tauri — a Rust core with a system webview — over an Electron-class shell and over native-per-platform (Q-4, resolved). Gains: CON-001’s empty-shell exclusion screen is passable on published baselines, and NFR-006’s single shared codebase stays viable for a team with no platform specialists, which is the stated constraint that ruled out native-per-platform. Costs: The selection rests on published baselines rather than on this project’s own measurement, because Q-5 has not recorded a reference machine — so CON-001’s exclusion is asserted rather than executed, and CON-001 requires a runtime decision record naming rejected candidates that does not yet exist. Every downstream footprint argument inherits that provisionality, including the cadence period and now the per-tick cost of the timer seam. Affected: CON-001, NFR-002, NFR-006, CON-003.
- Q-2 resolved: the terminal end-of-life status is permanent. One terminal lifecycle path, no reset setting, and no disposition that discards a terminal pet’s accumulated state. Gains: Gives BR-002 the single project-wide policy it requires the answer to take, and keeps the emotional weight that makes the daily-return habit carry stakes. The design needs no disposition component at all: the terminal status becomes a health status the progression can reach and nothing downstream consumes, which is why BR-002’s prohibition survives as a structural property rather than as a rule someone must remember. Costs: Forecloses the configurable soft reset Q-2 held open, and accepts the abandonment risk that option existed to mitigate — an owner who loses a pet after one bad week has no in-product recovery. BR-002 was written to keep both answers reachable; taking the permanent answer spends that optionality. Affected: BR-002, BR-001, FR-008.
- Q-3 resolved: Windows ships first for v1; macOS and Linux are staged after it. Gains: Concentrates the v1 acceptance surface on one platform’s accessibility stack and one set of path and file-move semantics, which is what a team with no platform specialists can actually verify. Costs: CON-003’s boundary still binds in full while only one target is being built, so the platform-adapter seam earns nothing measurable at v1 — NFR-006’s zero-conditionals-outside-the-layer measure has no second platform to be checked against until after v1, and a seam drawn wrong stays undetected until then. Affected: CON-003, NFR-006, NFR-003, FR-009, FR-012.
- Q-4 resolved: Tauri — a Rust core with a system webview — rejecting an Electron-class shell on its empty-shell baseline against CON-001, and native-per-platform on the team constraint. Gains: Keeps CON-001’s exclusion screen passable on published baselines and NFR-006’s single codebase viable without platform specialists. It also fixes the storage mechanism requirements D-14 left open — a JSON save file written by atomic write-then-rename — which is what lets integrity validation and the atomic-replace contract be specified at all. Costs: The selection is provisional against measurement rather than settled by it: CON-001’s screen cannot be executed until Q-5 records a reference machine, so this rests on published baselines and not on this project’s own. It also puts rendering and native accessibility on a shared webview, which is why both had to be pulled explicitly back inside the platform-adapter layer rather than being allowed to ride the runtime. Affected: CON-001, NFR-002, NFR-006, NFR-003, FR-001, FR-012.
- Q-9 resolved: one committed generation of the save file is retained, not two. Gains: A simpler store contract — one write per commit rather than a rotation — and less idle I/O against NFR-002’s footprint budget. Atomic write-then-rename already prevents an interrupted write from replacing a committed file, which is the failure mode NFR-004 actually measures. Costs: Post-commit media corruption has no second generation to fall back to and resolves to FR-012’s quarantine plus FR-010’s default pet — total loss of accumulated state. The requirements stage recorded this as the residual gap requirements A-15 names and R-1 recommends against; taking the one-generation answer accepts it for v1. Affected: NFR-004, FR-012, BR-002, NFR-002.
Sensitivity Points
- The running application’s pet-state evaluation cadence period (CMP-010 / IF-010 / IF-031), which Q-10 leaves entirely unfixed. — affected: NFR-002, FR-011, FR-008, FR-007, FR-001, NFR-006. Still the sharpest single parameter in the set. Shorten it and FR-011’s wake, FR-008’s neglect progression and FR-007’s mood update all become prompt, while the timer machinery and the write traffic it triggers spend NFR-002’s 1%-of-one-core budget — which the measure explicitly says includes “whatever background timer machinery the chosen implementation uses”. Lengthen it and the budget is safe while a woken pet stays Sleeping on screen for up to a full period and a neglect transition lands late by the same margin. Because IF-002’s replace commits synchronously, halving the period roughly doubles idle write frequency, so the parameter moves CPU and I/O together. Two things changed this round, in opposite directions. The announce-rate arm recorded in round 2 is gone: IF-030 now suppresses an unchanged announcement, so the period no longer sets the rate at which a screen-reader user hears the same mood and health status. But a new arm appears: every tick now crosses the adapter boundary through IF-031, so the period also sets the frequency of a layer crossing that did not exist before, on precisely the path NFR-002 measures. IF-031 defends the boundary as far as a contract can — it rejects a non-positive or sub-resolution period rather than coercing it to the fastest tick the host can deliver — but it cannot supply the value. Nothing in the requirement set bounds it, and NFR-002, the constraint that would bound it from the other side, is not evaluable until Q-5 records a reference machine, so today the flip cannot be measured, only argued.
- Whether FR-008’s per-stat below-threshold clock is reconstructed from the decay curve at each evaluation or persisted as a new pet-state field (CMP-005 / IF-004, tracked under Q-10 by requirements A-24), interacting with the deadline rule the design has now settled. — affected: FR-008, FR-001, BR-001, NFR-004, NFR-001. A small change flips a lot. Persisting adds a field to the glossary’s Pet state entry — the single maintained enumeration requirements A-22 governs — widening CMP-001’s field set, FR-001’s 50-cycle round-trip measure and the field set IF-014’s validation must account for; and because requirements A-6’s deadline rule RE-BASES the origin, every backward-clock observation becomes a mutation of persisted pet state, which is a new class of write on a path BR-002 requires to be inspected. Reconstructing avoids all of that but makes the neglect clock a function of the decay curve, so any Q-1 re-tuning silently rewrites how long a pet has “already” been neglected — a balance change that quietly moves a health-status transition, which is exactly what BR-001 says must arise only from sustained unremedied neglect. Reconstruction also leaves the deadline rule with little to act on: an origin derived from the current stat value cannot read as being in the future, so the non-positive interval IF-004 re-bases against would not arise in the same way. IF-004’s body asserts that “the deadline-rule choice above survives either answer”; that is the claim most worth re-examining when Q-10 is decided, and it is recorded here rather than as a finding because both readings are defensible until the decision is actually taken.
- The live pet state is presumed to carry the derived mood expression that the persisted pet state does not (CMP-001 / IF-002 / IF-030 / CMP-016). — affected: NFR-003, FR-007, FR-001. New this round, and it is the residual of the F-13 fix rather than a defect the fix introduced. IF-030
refreshnow compares the pet’s mood expression against “the one the supplied pre-advance pet state carried”. Mood expression is not in the glossary’s Pet state enumeration — it is derived by CMP-007 from the stat values — so that phrasing is true only if the in-memory live pet state carries a selected mood expression alongside the persisted fields. The rest of the set is consistent with that reading and in fact requires it already: CMP-016 renders “the pet with its mood expression, health status and stats” while depending on IF-002, IF-008, IF-027 and IF-028 and on neither IF-007 nor IF-001, so the only way a mood reaches the surface at all is riding on the live state; and both IF-008’s care operations and IF-009’s evaluate re-select the mood and then “install the result as the live pet state”. So the design is coherent under the natural reading, and that is why this is not a finding. It is fragile in a specific way. Nothing states the arrangement: CMP-001’s description enumerates only the persisted fields, and requirements A-22 governs what is persisted rather than what the in-memory representation carries, so there is no place a reader is told that the live state has a derived field on it. If a later decision makes the live pet state exactly the persisted enumeration — a natural simplification, and one nothing in the set argues against — then IF-030’s suppression comparison and CMP-016’s rendering both become unimplementable without new dependencies, and giving CMP-016 either IF-007 or IF-001 to repair it would put the mood rule in two places and undo the separation CMP-007’s body says NFR-003 needs. One sentence in CMP-001 stating that the live representation carries the currently selected mood expression would close it; until then the property is load bearing and unwritten. - Host tick delivery is not guaranteed, and IF-031 says so: ticks are late, dropped or not delivered at all across machine sleep and background throttling, are never back-filled, and the delivered period is approximate (IF-031 / IF-010 / CMP-010). — affected: FR-011, FR-008, FR-007, NFR-002, NFR-006. New this round, because the dependency only became explicit when the tick was given a contract. Correctness survives it by construction and that is the important half: elapsed time is derived from the wall clock and never from a tick count — IF-031’s third error mode says so outright — so one evaluation after the host resumes derives from the whole elapsed interval, which is the same path FR-002’s offline decay already takes, and IF-010 states that it originates none of that and decides only not to queue what was never delivered. What does not survive is promptness. Every claim about the running application advancing the pet — FR-011’s wake, FR-008’s progression, FR-007’s mood update — degrades from “within one cadence period” to “at the first evaluation after the host resumes”, and no requirement bounds that. FR-011’s measure bounds the closed-application case and the backward-clock case but is silent on the machine-slept-while-running case, so a long gap is neither a test failure nor a design guarantee — it is simply outside what anything states. The pressure runs the wrong way, which is what makes it sensitive rather than merely conceded: NFR-002’s budget pushes the period longer, and a longer period makes throttling harder to distinguish from normal operation. Throttling policy is also per-platform, so this can behave one way on Windows at v1 and differently on a staged target, which is NFR-006’s cross-platform acceptance run finding it late. If Q-10 resolves to something other than a fixed period, IF-031 concedes the seam itself may be the wrong primitive, and this is the fragility that would drive that.
- NFR-008’s file-free property for IF-003 is held by a contract guarantee, not by structure (CMP-002 / IF-001 / IF-026). — affected: NFR-008, NFR-001, NFR-009. CMP-002 genuinely holds a file dependency (IF-019), so at the component level a file IS in CMP-004’s transitive closure. What keeps
decayfile-free across 1,000 repetitions is IF-001’s first error mode reporting the unloaded case rather than repairing it — one sentence standing between a measurable NFR-008 and an unmeasurable one. Turning that report into a lazy load is a small, locally reasonable change that no functional test in the set would catch: the decay results stay identical and only a file-access trace would show it. The design put the guarantee in the strongest place available short of splitting CMP-002 into a loader and a supplier, and it states its reasoning in both IF-001 and IF-026, so the fragility is documented rather than hidden — which is why it is a sensitivity point and not a finding. - requirements A-7 — the assumption that the host exposes backward wall-clock movement observably, as a non-positive interval, rather than smoothing it into slow-forward time (CMP-019 / IF-024 / IF-018). — affected: FR-002, FR-011, FR-008, NFR-001, NFR-006. Both of requirements A-6’s rules trigger on observing a non-positive interval, so a host that smooths defeats FR-002’s clamp, FR-011’s re-basing and FR-008’s re-basing simultaneously. IF-018’s third error mode and IF-024’s third both concede the condition is not detectable through the contract. FR-011’s backward-clock trials — 1 hour, 1 day and 30 day jumps with no forward correction — would simply never fire the behaviour they measure, and would fail silently rather than loudly. One unverified assumption about an external component sits under three consumers, and it is per-platform, so it can hold on Windows for v1 and fail on a staged target.
- IF-003’s one-bounded-step, closed-form decay mapping. — affected: NFR-008, NFR-009, NFR-001. NFR-008’s whole response — p95 for a 30-day interval within 5x the p95 for a 1-hour interval — holds only while the mapping is closed-form over the interval. Any implementation that walks the interval, or even one that decomposes it per-day to make a piecewise curve tractable, flips the 30-day arm immediately and takes NFR-009’s 250 ms launch-path budget with it, since NFR-009 bounds the same computation in place. The contract states the obligation in the operation summary rather than leaving it to the implementation, which is the right defence — but it is a single sentence standing between a passing and a failing quality attribute, and Q-1’s curve shape is still open.
- The diagnostic log’s rotation cap value, and the approximate enforcement IF-021 concedes (CMP-015 / IF-016 / IF-021). — affected: NFR-007, NFR-002, NFR-005. The mechanism exists but the parameter does not: no requirement fixes the cap’s value or the number of rolled copies retained, and both are caller-supplied. Set it small and NFR-007’s replay check — which drives at least 100 recorded decay events through the reference model — can outrun the retention window, so the records the measure needs are rolled away before it runs. Set it large and “total log size stays under a fixed rotation cap indefinitely” is nominally true while the log competes with NFR-002’s resident-memory and disk expectations. IF-021’s last error mode adds a second axis: the size measured by
file_sizeis stale by the timeroll_overis asked for, so the cap is enforced approximately and can be exceeded transiently — a reading of “indefinitely” rather than “at every instant” that is correct but that a strict reading of the measure could reject. - IF-013’s refusal to commit without a last-saved timestamp when the host clock read fails. — affected: FR-001, FR-002, NFR-004. The reasoning is sound in isolation — a committed state with no last-saved timestamp leaves FR-002 with no origin to measure the offline interval from, so dating it with a guess is worse. But the blast radius is disproportionate to the trigger: a single clock-unavailable moment makes every subsequent stat change in the session non-durable, IF-002’s replace reports each one as not durable while the care loop keeps accepting actions, and IF-029’s close commit fails for the same reason at the one moment the session has no further chance. A narrow, transient external failure becomes a session-long loss of persistence, and the design still does not say what the owner-facing surface does with a stream of not-durable results.
- BR-002’s inspection set is three contracts plus a scoping argument, and the primitive underneath still carries no BR-002 clause (IF-002 / IF-013 / IF-029 / IF-025). — affected: BR-002, FR-001, NFR-004. IF-002
replace, IF-013commitand IF-029 all carry explicit terminal-pet clauses, and CMP-003’s responsibility now states the custodianship the convergence argument rests on, so the statement side is as strong as it has been. The structural residual is unchanged: IF-025replace_file_atomicallyitself carries no BR-002 clause, so the rule’s verification depends on CMP-014’s writes staying scoped to the preference file — true as designed, unenforced by any contract. The IF-019/IF-025 split holds the atomic-replace holders at exactly two, CMP-012 and CMP-014; one additional holder, or one widening of CMP-014’s file usage, moves BR-002 from a three-contract inspection back to an open-ended one.
Assumptions
Assumptions
- A-1: The application has exactly one live pet at a time. Every requirement in the set is written in the singular and none describes selecting among pets.
- A-2: The pet’s balance and tuning parameters live in a local configuration file, read from that source exactly once per session at launch and served from memory for the rest of it. No requirement says where balance values live; a file is what lets Q-1’s tuning loop run without a rebuild, and loading it once is what keeps the decay computation’s whole transitive read set free of file access, which NFR-008 and requirements A-17’s 1,000 in-memory repetitions require. A parameter read after the load cannot fall back to loading its source, and the source is not re-read when it changes on disk.
- A-3: The balance configuration source is separate from the pet state save file, so a save file quarantined under FR-012 does not take the tuning values with it.
- A-4: The offline-decay computation accepts the signed elapsed interval and applies FR-002’s clamp-to-zero rule itself rather than being handed a pre-clamped interval. This is what makes NFR-001’s non-positive-interval matrix drivable directly against the decay unit, and it keeps the platform clock contract free of both of requirements A-6’s rules.
- A-5: FR-008’s per-stat below-threshold clock applies requirements A-6’s DEADLINE rule — re-basing its origin to the current reading when the interval computes as non-positive — rather than the clamp the decay computation applies. FR-008 does not say which. A below-threshold-since origin is a boundary rather than an accumulating quantity, and a recovery above threshold clears it outright, so nothing survives to clamp; clamping alone would let a backward clock defer a sustained-neglect transition without bound, while re-basing bounds the deferral at one further duration. The accepted cost is that repeated backward clock moves hold the progression off one duration at a time — the same exposure FR-011’s own wake criterion already carries.
- A-6: Whether that below-threshold clock is reconstructed from the decay curve at each evaluation or persisted as a new pet-state field is deliberately left open. Persisting it would add a field to the Pet state glossary entry, which requirements A-22 makes the single maintained enumeration of what is persisted, and would widen FR-001’s round-trip measure with it.
- A-7: The offline-decay computation emits no diagnostic record of its own; NFR-007’s single record per decay computation is emitted by the pet-state evaluator, the only component that applies a decay computation to the live pet. This is what keeps NFR-008’s 1,000-repetition batch timing the computation alone.
- A-8: Installing a replacement pet state is the single trigger for a durable commit, so FR-001’s persist-on-stat-change obligation is discharged by the live pet state contract rather than by each caller remembering to persist after it changes something.
- A-9: A pet-state evaluation that leaves every pet state field unchanged does not trigger a commit. Otherwise FR-001’s per-stat-change trigger combined with a running evaluation cadence would produce a write per tick, directly against NFR-002’s idle budget.
- A-10: The host delivers an application-close signal the application can act on, with enough time to stop the evaluation cadence and commit the live pet state before the process exits. FR-001 names the owner closing the application as a persist trigger, which presupposes such a signal; a kill with no signal is NFR-004’s crash case and is served by the last stat-change commit instead.
- A-11: The close path is ordered: stop the evaluation cadence, wait for quiescence, commit the live pet state, then exit. Nothing in FR-001 fixes that ordering, but the reverse admits an evaluation that installs a re-derived state after the final commit has already read the live one.
- A-12: FR-001’s close-triggered commit is asked of the component holding the live pet state rather than of the store directly, so BR-002’s inspection of every path that writes, clears or replaces persisted pet state still converges on one custodian.
- A-13: The mood selector is given the pet’s prior stat values by whichever component caused the change, rather than remembering the expression it last selected. This keeps it a pure mapping and supplies NFR-007’s mood-change record with a pre-state, at the cost that a caller supplying wrong prior values produces a wrong record the contract cannot detect.
- A-14: The owner’s care-reminder preference is stored in its own small file in the local data directory, owned by the care-reminder service, written only by the owner-facing surface, and defaulting to disabled. FR-009 requires the feature to be owner-enabled but no requirement says where the setting lives, and the Pet state enumeration does not include it; keeping it out of the save file also means quarantining a failing save under FR-012 does not silently reset a choice the owner made.
- A-15: An unreadable care-reminder preference reads as disabled to the path that raises reminders and as unreadable to the owner-facing surface. The asymmetry is deliberate: disabled is the safe default where the consequence is an unwanted notification, and the wrong default where the consequence is showing the owner a setting they never chose.
- A-16: Care reminders are raised from the pre-change and post-change stat values the evaluation supplies, so a crossing caused by a care action raising a stat back above its threshold is observable but produces no reminder. FR-009 names only the crossing, not its direction.
- A-17: Reminder de-duplication is required — a stat sitting just below its threshold must not remind on every tick — but no requirement fixes the window over which a repeat crossing is suppressed, so the contract carries the obligation without its period.
- A-18: The owner’s view is drawn and its status text announced through the same platform-adapter contract, in the same act, so no render reaches the owner without the non-colour textual equivalent NFR-003 requires. Nothing in the requirement set forces the two into one contract; it is what makes ‘exposed to the platform accessibility API’ checkable once per render rather than tracked separately.
- A-19: The rendering surface, the native accessibility mapping and the host timer facility are held as behaviour of the platform-adapter layer rather than modelled as external components. NFR-006 names all three as seams that must live in that layer. This is a scoping choice about the inherited integration-point list — which names only the notification service and the wall clock — and not a claim that these facilities are in-process: each can be absent or not running in a session, and the layer’s contracts report that as a failure.
- A-20: The diagnostic log’s rotation cap is enforced by the log component measuring the live file and asking the platform adapter to roll it over, retaining a bounded number of rolled copies. The cap therefore holds approximately rather than at every instant and can be exceeded transiently between a measurement and the roll that follows it. NFR-007 fixes that a cap exists but neither its value nor the number of copies retained.
- A-21: A save file that is present but unreachable — permission denied, device error — is treated as an unreadable committed state rather than as an absent one, so NFR-004’s prohibition on falling through to a default pet where a committed state may survive holds for that case too. No requirement states this explicitly.
- A-22: A tick the host does not deliver, because the machine slept or background work was throttled, is not back-filled on resume: the cadence takes one late tick rather than a burst of missed ones, and the evaluation it causes derives from the whole elapsed interval by the same path FR-002’s offline decay already takes.
Dependencies
- D-1: Q-1 must settle the decay curve, each stat’s neglect threshold, the mood-band boundaries, the care increments, both sustained-neglect durations and the sleep duration before the balance configuration carries real values. Because the parameter set is loaded once per session, a tuning change requires a relaunch to take effect.
- D-2: Q-5 must record a reference-machine specification before NFR-002, NFR-009 and CON-001 can be measured against this decomposition. All three are structurally accounted for here; none is evaluable yet.
- D-3: Q-10 must settle the running evaluation cadence and what bounds it before the scheduler’s period — now a named parameter on the platform recurring-timer contract — can be fixed. NFR-002’s idle budget constrains the same parameter from the other side.
- D-4: Q-10 also governs whether FR-008’s per-stat below-threshold clock is reconstructed or persisted, which is what holds the health-progression component and its contract at low confidence.
- D-5: Q-8 must settle whether the Sleeping state is rendered to the owner. Three contracts survive either answer — the first display, the recurring refresh and the platform presentation surface — but their content is decided by it.
- D-6: The host wall clock must expose backward movement observably, as a non-positive interval, rather than smoothing it into slow-forward time (requirements A-7). All three non-positive-interval rules in this design — the decay clamp, the wake re-base and the neglect-clock re-base — trigger on observing that interval, and no operation in either clock contract can detect a host that smooths.
- D-7: Each target platform must expose a usable native notification API and a native accessibility API reachable with no network connection (requirements A-21). FR-009’s entire delivery path and NFR-003’s announcement path rest on them.
- D-8: An executable reference decay model and a separate executable reference progression model must each exist, independent of the production implementation, as the oracles for FR-002/NFR-001 and FR-008 respectively (requirements D-1, D-2).
- D-9: NFR-007 names a fixed log rotation cap but never sizes it, and does not fix how many rolled copies are retained. Both must be set before the log’s roll-over behaviour is testable against the requirement’s indefinite-retention clause.
- D-10: NFR-003’s measure runs a scripted screen-reader walkthrough through each platform’s own native accessibility stack, and CON-003 stages the platforms Windows-first. The platform presentation contract therefore has one implementation at v1 and two more later, so its cross-platform value is realised after v1 — a reason to fix the seam now rather than defer it.
- D-11: Q-4’s resolution fixes the storage format the integrity-validation check is defined over, but the concrete mechanism — structural decode alone, or a recorded checksum — is still unfixed and bounds what an inconclusive validation result can mean. The atomic-replace contract’s platform-level atomicity guarantee rests on the same resolution.
- D-12: CON-001 mandates that a runtime decision record exist recording rejected candidates. It is emitted here as an ADR rather than as a component, and it cannot record a pass or fail against the exclusion screen until Q-5 lands.
Open Questions
- Q-1: What is the exact decay-curve and balance tuning — rates, thresholds, increments, both sustained-neglect durations, and the sleep duration? (owner: product/design)
- Q-5: What is the reference-machine specification against which the idle-footprint budget and launch-latency figures are measured? (owner: engineering)
- Q-6: What are the release, auto-update and rollback strategies for desktop distribution? (owner: engineering)
- Q-7: Should v1 carry explicit maintainability targets (modularity / modifiability), given that Q-1’s decay-balance tuning is a live, ongoing loop? (owner: engineering)
- Q-8: Should the Sleeping state be rendered to the owner? (owner: product)
- Q-10: How often does the running application re-evaluate pet state, and what bounds that interval? (owner: engineering)
- Q-11: Is FR-008’s per-stat below-threshold clock reconstructed from the decay curve at each evaluation, or persisted as a new pet-state field? Raised by this stage as the design-side refinement of Q-10: the decomposition is correct under either answer, but persisting adds a field to the Pet state glossary entry — the single maintained enumeration requirements A-22 governs — and widens FR-001’s round-trip measure with it. Deferred at the ATAM-lite review as the FR-008 coverage gap. (owner: engineering)
- Q-12: What period does the recurring pet-state evaluation cadence run at, and what bounds it? Raised by this stage as the design-side refinement of Q-10, now that the period is a named parameter on the platform recurring-timer contract rather than an unstated implementation detail. It is the sharpest parameter in the design: shorten it and FR-011’s wake, FR-008’s progression and FR-007’s mood update all become prompt while NFR-002’s idle budget is spent on the exact machinery that requirement names; lengthen it and the budget is safe while every time-driven behaviour becomes stale. Deferred at the ATAM-lite review as the NFR-002 coverage gap. (owner: engineering)
- Q-13: Has CON-001’s runtime baseline-overhead exclusion screen actually been executed, and where is the runtime decision record the constraint mandates? Q-4 selected Tauri against published baselines rather than against this project’s own measurement, and CON-001 states that a candidate measured against an unrecorded baseline counts as unassessed rather than admitted. The screen cannot be run until Q-5 records a reference machine. Deferred at the ATAM-lite review as the CON-001 coverage gap. (owner: engineering)
Last updated on