@kumbatio/energy-system 0.6.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,111 @@
1
+ # Changelog
2
+
3
+ All notable changes to `@kumbatio/energy-system` are documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and from `1.0.0` this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ For this package, semver covers more than type signatures. **A shipped strategy
9
+ table's values are API**: changing what `notificationStrategy` returns at level
10
+ 50 changes how every consumer behaves, so it is a major-version change — and a
11
+ change to [SPEC.md](./SPEC.md), not only to this library. The same is true of the
12
+ reconciliation rule, which two implementations must agree on to share state at
13
+ all. Prose returned by `describe()` is not covered; wording is a product decision.
14
+
15
+ ## [1.0.0]
16
+
17
+ The API is frozen. Everything below documents what that commitment now covers.
18
+
19
+ ### Added
20
+
21
+ - **[SPEC.md](./SPEC.md)** — the model specified independently of this
22
+ implementation: levels, energy state, reconciliation, the strategy contract,
23
+ autonomy, inbound demand, the runtime invariants, and the accessibility
24
+ requirements. Language-independent and RFC 2119-worded, so an implementation in
25
+ another language is an implementation of the same model rather than a port of
26
+ this one.
27
+ - **[spec/energy-state.schema.json](./spec/energy-state.schema.json)** — the
28
+ interchange format. Sharing one person's energy state across processes or
29
+ languages is a wire-format problem, and this is the wire format.
30
+ - **[conformance.json](./conformance.json)** — 252 vectors plus every strategy
31
+ table, generated from the built library on each build and shipped in the
32
+ package. An implementation passes by loading and replaying them; a stale file
33
+ fails this package's own build.
34
+ - `isPreferredEnergyState(candidate, current)` — the reconciliation rule, which
35
+ was internal to the engine. It is the hardest part of the model to reimplement
36
+ correctly, so it is now readable, testable, and covered by vectors on its own.
37
+ - Accessibility handling in the reference stylesheet for
38
+ `prefers-contrast: more` and `forced-colors: active`. Both declare
39
+ `!important` deliberately: `applyEnergyLevel()` writes the same custom
40
+ properties inline, and a preference the person set must outrank a value the
41
+ program computed.
42
+ - Coverage across every one of the 20 level transitions, in both directions, and
43
+ across strategy composition — including the model's directional invariants
44
+ (protection never decreases and automation never gains discretion as capacity
45
+ falls), which a future edit to one table would otherwise break silently.
46
+
47
+ ### Changed
48
+
49
+ - The reference stylesheet is explicit that its resting chrome opacities at Low
50
+ and Rest do not meet WCAG 1.4.11, that this is a design default rather than a
51
+ conformance claim, and that every value is an overridable custom property.
52
+ Saying so is more useful than quietly shipping numbers that imply otherwise.
53
+ - `readOnlyCursor` is documented as a hint that reaches pointer users only, and
54
+ which must be paired with actually disabling controls if "read only" is meant.
55
+
56
+ ## [0.6.0]
57
+
58
+ ### Added
59
+
60
+ - `autonomyStrategy` / `AutonomyConfig` — what automation may do unattended:
61
+ confidence threshold, whether wording may be composed, and how many steps may
62
+ chain. The mirror of interaction forgiveness, which protects against the
63
+ _user's_ mistakes at low energy where this protects against the _agent's_.
64
+ What narrows as energy falls is discretion, not action: at Rest the threshold
65
+ is 1, admitting only certainty, and a single templated step is still allowed.
66
+ - `demandAdmissionStrategy` and the pure `resolveDemandOutcome` — an
67
+ energy-resolved policy for arrivals that ask something of the person: reach
68
+ them now, acknowledge and queue, or queue silently. Originator tiers, the
69
+ exempt-tier invariant, and escalation to silence when a classification is not
70
+ confident enough to speak. Policy only; the effects it implies leave the
71
+ process and cannot be made transactional by an in-process runtime, so the
72
+ orchestration belongs to the consuming app.
73
+
74
+ ## [0.5.4]
75
+
76
+ ### Added
77
+
78
+ - `createEnergyOrigin` exported from the index.
79
+
80
+ ## [0.4.0]
81
+
82
+ Patterns studied in a field ADHD app and reimplemented as first-class, tested
83
+ primitives — including guarantees against the two failure modes observed in the
84
+ wild: suppressed reminders destroyed instead of deferred, and focus suppression
85
+ that never auto-expired.
86
+
87
+ ### Added
88
+
89
+ - Presence annotation: `defineEnergyPresence`, `presenceAtOrAbove`,
90
+ `presenceAtOrBelow`, `createPresenceStrategy`, `useEnergyPresence`,
91
+ `<EnergyGate>`, and the CSS-only `data-energy-min` / `data-energy-max` path.
92
+ - Notification gate: runtime enforcement of `NotificationConfig` with a
93
+ defer-not-drop guarantee.
94
+ - Focus sessions: time-boxed suppression with auto-expiry events and break nudges.
95
+ - Deferral presets with energy-aware ordering (`deferralStrategy`).
96
+ - Interaction forgiveness strategy.
97
+ - `prefers-reduced-motion` handling for the stylesheet's own transitions.
98
+
99
+ ## [0.1.0]
100
+
101
+ ### Changed
102
+
103
+ - Renamed to `@kumbatio/energy-system`; the previous package name is deprecated.
104
+
105
+ ## [0.0.x]
106
+
107
+ Foundations: the 5-level model with immutable revisioned state, the
108
+ framework-agnostic engine with strategy resolution, the first three built-in
109
+ strategies, the DOM adapter, the React provider and hooks, persistence adapters
110
+ with external observation, deterministic clocks, derived metrics, and legacy
111
+ level compatibility mapping.
package/README.md CHANGED
@@ -513,11 +513,54 @@ The engine orders concurrent writes by timestamp, logical revision, source prior
513
513
  origin. This gives every context the same deterministic winner even when two writes share a wall
514
514
  clock timestamp. Local writes advance the logical revision when the clock does not advance.
515
515
 
516
+ The rule is exported as `isPreferredEnergyState(candidate, current)` and specified normatively in
517
+ [SPEC.md §4](./SPEC.md). It is the hardest part of the model to reimplement correctly, so it is
518
+ readable, testable, and covered by conformance vectors on its own rather than buried in the engine.
519
+
516
520
  `setLevel()` updates in-memory subscribers synchronously. Persistence runs in the background with
517
521
  bounded exponential backoff. Call `await engine.flush()` when a workflow must wait for durable
518
522
  storage before reporting completion. An initial `flush()` waits for hydration before writing the
519
523
  default state, and rejects rather than overwriting unread storage if that hydration read failed.
520
524
 
525
+ ## Specification and conformance
526
+
527
+ The model is specified independently of this implementation.
528
+
529
+ - **[SPEC.md](./SPEC.md)** — the normative model: levels, state, reconciliation, the strategy
530
+ contract, autonomy, inbound demand, the runtime invariants, and the accessibility requirements.
531
+ Language-independent, RFC 2119 wording.
532
+ - **[spec/energy-state.schema.json](./spec/energy-state.schema.json)** — the interchange format, so
533
+ two processes (or two languages) can share one person's energy state.
534
+ - **[conformance.json](./conformance.json)** — every table and every decision above, as vectors.
535
+ Ships in the package.
536
+
537
+ ```ts
538
+ import conformance from '@kumbatio/energy-system/conformance.json' with { type: 'json' }
539
+ ```
540
+
541
+ An implementation in another language passes by loading the vectors and replaying them; this
542
+ package's own `test/conformance.test.ts` does exactly that and is a reasonable model to copy. The
543
+ file is generated from the built library, so the vectors cannot drift from the behavior they
544
+ describe — and a stale `conformance.json` fails the build.
545
+
546
+ Vectors cover the pure surface: tables, and functions of their arguments alone. The stateful
547
+ guarantees — defer-never-drop, session auto-expiry, persistence ordering — are normative in SPEC.md
548
+ and checked by this package's suite, because no vector can express _and it must never drop one_.
549
+
550
+ ## Stability
551
+
552
+ `1.0.0` is an API freeze. From here the package follows semver strictly, and for this package that
553
+ means more than the type signatures:
554
+
555
+ - **A shipped strategy table's values are API.** Changing what `notificationStrategy` returns at
556
+ level 50 changes how every consumer behaves, so it is a major-version change — and a change to
557
+ [SPEC.md](./SPEC.md), not just to this library.
558
+ - **The reconciliation rule is API.** Two implementations that disagree about it cannot share state.
559
+ - **The conformance vectors are the contract in machine-readable form.** Within a major version,
560
+ existing vectors do not change meaning; new sections and new vectors may be added.
561
+ - Prose from `describe()` is _not_ covered. Wording is a product decision and may change in a patch.
562
+ - Adding a strategy, an option with a default, or a new export is a minor release.
563
+
521
564
  ## Development
522
565
 
523
566
  ```bash
@@ -536,7 +579,7 @@ adapters (e.g., SQLite-backed desktop stores) should live in consuming apps.
536
579
  ## Who uses this
537
580
 
538
581
  - **[Anasa](https://anasa.md)** — Kumbatio's local-first writing and thinking workspace, in public alpha. Runs its entire adaptive shell on the engine: custom settings-backed persistence, energy-gated AI surfaces, notification filtering, and task-complexity guidance.
539
- - **[Meltemi](https://meltemi.app)** — an email client in private beta from [entro314 labs](https://github.com/entro314-labs) (the studio behind Kumbatio), built outside the Kumbatio product line. Uses the notification gate (defer, never drop), focus sessions, deferral ordering, and interaction forgiveness — integrated without the React adapter.
582
+ - **[Meltemi](https://meltemi.app)** — an email client in private beta from [entro314 labs](https://github.com/entro314-labs) (the studio behind Kumbatio), built outside the Kumbatio product line. Uses the notification gate (defer, never drop), focus sessions, deferral ordering, interaction forgiveness, and demand admission — integrated without the React adapter. Its demand binding is the reference one: originator tiers come from its VIP list, obligation is classified from RFC 3834 header evidence and its own sender lanes, acknowledgments go out as `Auto-Submitted: auto-replied` auto-replies, and the capture is a snooze to the horizon `deferralStrategy` picked.
540
583
  - **[kumbat.io](https://kumbat.io)** — the site itself runs on this model; change the energy level there and watch the interface adapt.
541
584
 
542
585
  The integration patterns these apps proved out are documented in the [Production Patterns guide](https://docs.kumbat.io/docs/energy-system/guides/production-patterns). If you ship something with `energy-system`, tell us: [hello@kumbat.io](mailto:hello@kumbat.io).
package/SPEC.md ADDED
@@ -0,0 +1,439 @@
1
+ # The Energy Model — Specification
2
+
3
+ **Version 1 · normative · language-independent**
4
+
5
+ This document specifies the energy model: what an energy state is, how two of
6
+ them are ordered when they meet, and what each of the five levels means for the
7
+ behaviors built on top. `@kumbatio/energy-system` is the reference
8
+ implementation, not the definition. Anything that implements what is written
9
+ here — in Swift, Kotlin, Rust, Python, Go, or another JavaScript library — is an
10
+ implementation of the same model, and states produced by one may be read by
11
+ another.
12
+
13
+ There is a machine-readable half. [`conformance.json`](./conformance.json) ships
14
+ in this package and encodes every table and every decision below as vectors.
15
+ Load it, replay it, and you have a conformance suite without writing one. It is
16
+ generated from the built library, so the vectors and the reference
17
+ implementation cannot disagree.
18
+
19
+ Key words follow RFC 2119: **MUST**, **MUST NOT**, **SHOULD**, **MAY**.
20
+
21
+ ---
22
+
23
+ ## 1. Why this exists as a spec at all
24
+
25
+ The claim the model makes is not "here is a nice API". It is that **capacity is
26
+ first-class application state** — as real as the current user or the current
27
+ document, and as deserving of a stable representation.
28
+
29
+ That claim only pays off if one person's energy state can be shared by
30
+ everything they use: a mail client, a writing tool, a coordination app, a phone.
31
+ Sharing state across processes and languages is an interchange problem, and
32
+ interchange needs a specification rather than a port. Five hand-written ports of
33
+ the same tables will drift within two releases, and on the day they disagree the
34
+ promise quietly becomes false.
35
+
36
+ So the portable artifact is this document plus the vectors. An implementation is
37
+ then a small amount of local code around a shared, checkable definition.
38
+
39
+ ---
40
+
41
+ ## 2. Levels
42
+
43
+ The model has exactly **five** levels, and they are discrete.
44
+
45
+ | Value | Key | Label | Meaning |
46
+ | ----- | -------- | ------ | ---------------------------------------------------------- |
47
+ | `100` | `peak` | Peak | High capacity. Planning, complex decisions, creative work. |
48
+ | `75` | `active` | Active | Good capacity. Focused execution, problem-solving. |
49
+ | `50` | `steady` | Steady | Moderate capacity. Routine tasks, familiar work. |
50
+ | `25` | `low` | Low | Limited capacity. Simple tasks, review, light work. |
51
+ | `0` | `rest` | Rest | Depleted. Consumption only — reading, reflecting. |
52
+
53
+ An implementation **MUST** use exactly these five values and **MUST NOT** admit
54
+ intermediate ones. This is a design commitment, not an arbitrary limit: a
55
+ continuous slider asks for precision nobody has about their own state, and it
56
+ turns a one-second act into a judgement call at exactly the moment judgement is
57
+ expensive. Five is also few enough to cycle through with one key.
58
+
59
+ Each level carries a **cognitive profile** — `decisionCapacity`,
60
+ `focusDuration`, `taskComplexity`, `interruptionTolerance` — enumerated in the
61
+ vectors under `levels`. Implementations **MUST** reproduce these values;
62
+ consumers branch on them.
63
+
64
+ **Cycling** is `100 → 75 → 50 → 25 → 0 → 100`. Downward by default, because the
65
+ common act is admitting depletion, and the wrap gives one control both
66
+ directions. See `cycle` in the vectors.
67
+
68
+ ### 2.1 What the levels are not
69
+
70
+ They are not a scale of worth, and an implementation **SHOULD NOT** present them
71
+ as a score, a streak, or something to optimise. Rest is a valid state to be in
72
+ and the interface **SHOULD** be usable there. An implementation **MUST NOT**
73
+ make Rest a degraded or punitive mode.
74
+
75
+ ---
76
+
77
+ ## 3. Energy state
78
+
79
+ The unit of interchange is an **energy state**: one producer's point-in-time
80
+ claim about a person's capacity. Its serialized form is specified by
81
+ [`spec/energy-state.schema.json`](./spec/energy-state.schema.json).
82
+
83
+ | Field | Type | Role |
84
+ | ----------- | --------------------------------- | -------------------------------------------- |
85
+ | `level` | `0 \| 25 \| 50 \| 75 \| 100` | The declared capacity. |
86
+ | `timestamp` | integer, epoch milliseconds | When it was produced. Primary ordering key. |
87
+ | `source` | `manual \| scheduled \| inferred` | How it was arrived at. |
88
+ | `revision` | non-negative safe integer | Sequence among states sharing one timestamp. |
89
+ | `origin` | non-empty string | Stable identity of the producer. |
90
+
91
+ A state **MUST** be immutable once produced. Implementations **SHOULD** enforce
92
+ this with whatever their language offers.
93
+
94
+ ### 3.1 Source
95
+
96
+ `source` is not decoration; §4 gives it authority.
97
+
98
+ - `manual` — the person set it. **The default, and the trust anchor.**
99
+ - `scheduled` — a rule the person configured applied it (a calendar, a time of day).
100
+ - `inferred` — the system worked it out from behavior.
101
+
102
+ An implementation **MUST** support `manual`. Inference **MAY** be offered but
103
+ **MUST** be opt-in, and a system that infers **SHOULD** present the result as a
104
+ suggestion the person confirms rather than applying it silently. A model of
105
+ someone's capacity that overrides what they said about it recreates the loss of
106
+ control the model exists to answer.
107
+
108
+ ### 3.2 The unproduced sentinel
109
+
110
+ An implementation needs a state before anyone has set one. That default **MUST**
111
+ be distinguishable from a real state, because "nobody has chosen yet" and
112
+ "someone chose Peak" are different facts and only the second should survive
113
+ reconciliation.
114
+
115
+ The sentinel is `timestamp = 0` together with `origin = "0-initial"`. Both sort
116
+ below any real value, so §4 replaces the default unconditionally.
117
+
118
+ Producing the sentinel **MUST NOT** require reading the clock or a random
119
+ source. This is not a micro-optimisation: constructing a state during a
120
+ server-side render otherwise bakes an unstable value into static output, which
121
+ some frameworks fail the build on outright.
122
+
123
+ ### 3.3 Rejecting implausible states
124
+
125
+ An implementation that accepts states from outside itself **SHOULD** reject any
126
+ whose `timestamp` exceeds local time by more than a bounded skew budget. Without
127
+ it, one context with a badly-set clock wins every comparison until real time
128
+ catches up to its timestamp — which may be years.
129
+
130
+ The reference default is **5 minutes**. The budget **SHOULD** be configurable,
131
+ including "accept anything finite" for controlled environments.
132
+
133
+ ---
134
+
135
+ ## 4. Reconciliation
136
+
137
+ > This is the section to get right. Everything else is a table.
138
+
139
+ Two states for the same person meet constantly: a second tab writes shared
140
+ storage, two windows each hold a producer, a sync layer returns what another
141
+ device recorded. Every path needs the same answer to _which of these is
142
+ current_, and the answer **MUST** be a deterministic function of the two states
143
+ alone. "Last write wins by arrival order" is not acceptable: it converges on
144
+ different values depending on timing, so two contexts disagree permanently.
145
+
146
+ Given a `candidate` and a `current`, the candidate replaces the current if and
147
+ only if the first differing key below favours it:
148
+
149
+ 1. **`timestamp`** — greater wins.
150
+ 2. **`revision`** — greater wins.
151
+ 3. **`source`** — `manual` (3) > `scheduled` (2) > `inferred` (1).
152
+ 4. **`origin`** — greater by lexicographic comparison of the string.
153
+
154
+ If all four are equal, the states are equal: the candidate **MUST NOT** replace
155
+ the current, and no change **MUST** be reported.
156
+
157
+ Key 4 is arbitrary and deliberately so. When two producers write the same
158
+ instant, the same revision, and the same class of source, there is no principled
159
+ winner — and an arbitrary rule every context computes _identically_ beats a coin
160
+ flip each context tosses separately. Convergence is the property that matters.
161
+
162
+ An implementation **MUST** additionally treat a `level` difference as a
163
+ tiebreaker below key 4 (greater wins). This is unreachable for conforming
164
+ producers, since it requires one `origin` to have produced two different states
165
+ with identical timestamps and revisions. It exists so a buggy producer causes a
166
+ wrong answer rather than an oscillation.
167
+
168
+ **Antisymmetry is required.** For any two states, both directions **MUST NOT**
169
+ report "preferred". Two contexts observing each other would otherwise swap
170
+ states forever. The vectors assert both directions of every pair for this
171
+ reason.
172
+
173
+ Reference: `isPreferredEnergyState`; vectors under `reconciliation`.
174
+
175
+ ---
176
+
177
+ ## 5. Adaptation strategies
178
+
179
+ A **strategy** is a pure total function from level to a configuration value. It
180
+ **MUST** be free of side effects, **MUST** be defined for all five levels, and
181
+ **MUST** return the same value for the same level every time.
182
+
183
+ The distinction that keeps the model honest is **strategies describe, runtimes
184
+ enforce**. A strategy says "at Steady, notifications batch every five minutes";
185
+ something else does the batching. Keeping the description pure is what makes it
186
+ portable, testable, and readable by a person deciding whether to trust it.
187
+
188
+ Seven strategies are specified. Their complete per-level tables are in the
189
+ vectors under `strategies`; they are not reproduced here, because a table
190
+ duplicated in prose is a table that will disagree with itself.
191
+
192
+ | Name | Governs |
193
+ | ------------------------- | ------------------------------------------------------------------ |
194
+ | `ui-visibility` | Which chrome is shown, its opacity, content width and font scale. |
195
+ | `notifications` | Channels, batching interval, and minimum priority. |
196
+ | `task-complexity` | Ceiling on surfaced task complexity; break cadence. |
197
+ | `interaction-forgiveness` | Undo window, destructive-action confirmation, autosave cadence. |
198
+ | `deferral` | Ordering of "not now" presets, and the one-tap default. |
199
+ | `autonomy` | What automation may do unattended (§7). |
200
+ | `demand-admission` | What happens to an arrival that asks something of the person (§8). |
201
+
202
+ An implementation **MUST** provide `ui-visibility`, `notifications`, and
203
+ `interaction-forgiveness` to claim conformance; the rest are **SHOULD**, since
204
+ not every host has deferral or automation. Whatever it provides **MUST** match
205
+ the vectors exactly.
206
+
207
+ Implementations **MAY** ship additional strategies and **SHOULD** allow
208
+ consumers to supply their own — the type is a contract, not a closed set.
209
+
210
+ ### 5.1 Human-readable descriptions
211
+
212
+ The reference implementation pairs each strategy with a `describe(level)`
213
+ returning English prose. This is **NOT** normative and conformance does not
214
+ check it. Wording is a product and localisation decision.
215
+
216
+ ---
217
+
218
+ ## 6. Direction
219
+
220
+ Two rules constrain every table, and an implementation adding a strategy
221
+ **SHOULD** obey them:
222
+
223
+ 1. **Lower energy means less demanded of the person, not less capability.**
224
+ Chrome recedes, notifications batch, defaults lengthen. Features are not
225
+ removed as punishment. The one exception is destructive capability, which
226
+ §5's forgiveness table deliberately makes _harder_ to reach.
227
+ 2. **Protection scales inversely with capacity.** Undo windows widen,
228
+ confirmations appear, autosave quickens as energy falls. Slower error
229
+ detection is met with more room to catch errors.
230
+
231
+ ---
232
+
233
+ ## 7. Autonomy
234
+
235
+ `autonomy` governs what automated systems may do on the person's behalf without
236
+ being asked. It is the mirror of interaction forgiveness: forgiveness protects
237
+ against the _person's_ mistakes at low energy, autonomy against the _agent's_.
238
+
239
+ Three fields: `confidenceThreshold` (0–1, the minimum confidence to act
240
+ unattended), `allowGeneratedContent` (whether wording may be composed or must
241
+ come from fixed templates), and `maxUnattendedSteps` (how many automated steps
242
+ may chain before control returns).
243
+
244
+ The rule the numbers encode: **what narrows as energy falls is discretion, not
245
+ action.** At Rest the threshold is `1`, admitting only certainty — rule-based
246
+ decisions, never judgement calls — with one step and no composition. Automation
247
+ **MAY** still take a single certain templated action there; an out-of-office
248
+ reply is exactly that shape, and it is _safest_ at Rest precisely because it has
249
+ stopped improvising.
250
+
251
+ A comparison against the threshold **MUST** admit equality: confidence `0.8` at
252
+ a threshold of `0.8` acts. Vectors sample every threshold boundary for this
253
+ reason.
254
+
255
+ ---
256
+
257
+ ## 8. Inbound demand
258
+
259
+ **Inbound demand** is anything arriving from outside that asks for the person's
260
+ attention or action: an email, a comment, a review request, an assignment, an
261
+ invitation.
262
+
263
+ This is where the model earns its keep. Every triage system in general use is
264
+ organised around properties of the _message_ — who sent it, what it claims about
265
+ its own urgency, what category it fits. None is organised around the state of
266
+ the _recipient_, which is what actually decides whether an arrival is a small
267
+ task or a crushing weight.
268
+
269
+ ### 8.1 The decision
270
+
271
+ Given the level's `demand-admission` config, its `autonomy` config, and an
272
+ arrival described by `originatorTier` (`exempt` / `known` / `unknown`),
273
+ `bearsObligation`, and `confidence` (default `1`), the outcome **MUST** be
274
+ computed in this order:
275
+
276
+ 1. `originatorTier` is `exempt` → **live**, no acknowledgment. Unconditional, at
277
+ every level.
278
+ 2. The tier passes the level's `originatorThreshold` → **live**.
279
+ 3. `bearsObligation` is false → **live**. Informational arrivals ask nothing;
280
+ whether they should _interrupt_ is the notification question, not this one.
281
+ 4. The level does not acknowledge → **silent** (captured, no acknowledgment).
282
+ 5. `confidence` is below the autonomy threshold → **silent**.
283
+ 6. Otherwise → **acknowledge**, at the level's detail, composed only if autonomy
284
+ permits generated content.
285
+
286
+ Reference: `resolveDemandOutcome`; 180 vectors under `decisions.demand`.
287
+
288
+ ### 8.2 Invariants for anything acting on the decision
289
+
290
+ The decision is pure. Acting on it is not, and four rules bound what acting may
291
+ look like. An implementation performing these effects **MUST** observe them.
292
+
293
+ 1. **Acknowledgment and capture are one act.** An acknowledgment without a
294
+ captured obligation is a promise nobody kept — strictly worse than silence,
295
+ because it converts ambient guilt into explicit written debt. A capture
296
+ without an acknowledgment leaves the originator in silence, which is the
297
+ problem being solved. Where the two cannot be made atomic — and across a
298
+ network they cannot — the capture **MUST** be performed first, because it is
299
+ the reversible half, and **MUST** be rolled back if the acknowledgment fails.
300
+ 2. **Acknowledgments state, never promise.** "Received and queued, current
301
+ response horizon Thursday" is a fact. "I'll get back to you soon" is a
302
+ commitment the person's Tuesday self has to keep. Implementations **MUST
303
+ NOT** emit commitments. The horizon **SHOULD** come from the deferral
304
+ strategy, so the queue and the acknowledgment cannot disagree.
305
+ 3. **Automated action toward a third party MUST be disclosed as automated.** The
306
+ medium decides the mechanism — `Auto-Submitted: auto-replied` on email
307
+ (RFC 3834, which also prevents responder loops), a system-attributed badge in
308
+ an app. Undisclosed automation speaking in a person's name is the failure
309
+ this design exists to avoid.
310
+ 4. **At most once per originator, per window.** Five arrivals from one sender
311
+ while someone is depleted are one social debt, not five. Answering each is
312
+ the volume asymmetry the model is trying to correct.
313
+
314
+ ### 8.3 Tiers
315
+
316
+ Tier assignment is the host's: an approved-senders list, a contacts database, an
317
+ org chart. The model consumes the tier and does not compute it.
318
+
319
+ The exempt tier is also what defuses the obvious gaming risk. An originator who
320
+ learns that an acknowledgment means "deprioritised" and escalates through
321
+ another channel only succeeds if their escalation is one the person cannot
322
+ ignore — which is what would have made them exempt in the first place.
323
+
324
+ ---
325
+
326
+ ## 9. Runtime invariants
327
+
328
+ Some requirements cannot be expressed as a vector, because they are about
329
+ sequences rather than functions. They are normative regardless, and they are the
330
+ requirements most often got wrong — each is here because it was observed failing
331
+ in a shipped product.
332
+
333
+ ### 9.1 Notification gating
334
+
335
+ An implementation that gates notifications by level **MUST NOT** silently drop
336
+ one. Anything not deliverable now is **deferred** and released when the level
337
+ rises, suppression lifts, or the gate is torn down. A gate being disposed
338
+ **MUST** surface what it still holds.
339
+
340
+ _Why:_ a field client's scheduler destroyed reminders that came due while
341
+ suppressed. From the user's side that is indistinguishable from the app losing
342
+ their data, and it is the fastest way to lose trust in adaptive behavior
343
+ entirely.
344
+
345
+ ### 9.2 Time-boxed suppression
346
+
347
+ Any suppression window **MUST** expire on its own. Expiry **MUST** be an emitted
348
+ event, not a condition the host is expected to poll, and suppression **MUST** be
349
+ lifted _before_ the end-of-window event is emitted — otherwise the window
350
+ swallows its own completion notice.
351
+
352
+ _Why:_ focus modes that suppress until manually cleared strand the person on
353
+ exactly the day they forget, and the cost lands on someone who already had none.
354
+
355
+ ### 9.3 Deferral
356
+
357
+ Deferral presets **MUST** compute in the person's local time — "tomorrow
358
+ morning" means their morning. The ordering **SHOULD** follow the level: at low
359
+ capacity the one-tap default resurfaces work _later_, not in an hour, because
360
+ resurfacing into the same depletion helps nobody.
361
+
362
+ Because presets are local-time, conformance vectors for them are generated under
363
+ UTC and an implementation replaying them **MUST** do the same.
364
+
365
+ ### 9.4 Persistence
366
+
367
+ Persisted state **MUST** round-trip verbatim — every field, not just the level.
368
+ An implementation that stores only the level and rebuilds the rest on read
369
+ produces a new `timestamp` and `origin` on every load, which reads as a fresh
370
+ write to §4 and causes contexts to fight.
371
+
372
+ ---
373
+
374
+ ## 10. Accessibility requirements
375
+
376
+ Adaptive interfaces can fail people in ways static ones cannot. These are
377
+ requirements, not suggestions.
378
+
379
+ 1. **Hiding MUST remove from the accessibility tree.** A level-hidden element
380
+ **MUST NOT** remain focusable or reachable by assistive technology. Visual
381
+ hiding alone leaves a keyboard trap into content that isn't there.
382
+ 2. **Focus MUST survive a level change.** If the focused element is hidden by a
383
+ transition, focus **MUST** be moved somewhere sensible and **MUST NOT** be
384
+ left on a removed node.
385
+ 3. **Faded chrome MUST reveal on focus, not only on hover.** A control at 10%
386
+ opacity that a keyboard user can tab into but cannot see fails WCAG 2.4.7.
387
+ Every hover-reveal rule **MUST** have a `:focus-within` twin.
388
+ 4. **Level transitions MUST be announceable.** A person using a screen reader
389
+ **MUST** have some way to know the interface changed. The host chooses the
390
+ mechanism; silence is not an option.
391
+ 5. **Reduced motion MUST be honored.** Transitions between levels **MUST** be
392
+ suppressed under `prefers-reduced-motion: reduce`.
393
+ 6. **Contrast obligations are the host's and MUST be reachable.** The reference
394
+ opacity values are a design default, not a conformance claim: at Low and Rest
395
+ the resting chrome opacity does not meet WCAG 1.4.11 for non-text contrast.
396
+ An implementation **MUST** make these values overridable and **SHOULD**
397
+ raise them under `prefers-contrast: more`.
398
+
399
+ ---
400
+
401
+ ## 11. Conformance
402
+
403
+ An implementation conforms if:
404
+
405
+ 1. It represents state per §3 and serializes per the schema.
406
+ 2. It reconciles per §4, including antisymmetry.
407
+ 3. It provides at least the required strategies of §5, matching the vectors exactly.
408
+ 4. It observes the runtime invariants of §9 for whichever runtimes it provides.
409
+ 5. It meets §10 for whichever surfaces it renders.
410
+
411
+ The vectors are the mechanical part. Load `conformance.json`, replay each
412
+ section against your implementation, and compare. The reference implementation
413
+ does exactly this in `test/conformance.test.ts`, which is a reasonable model for
414
+ a port's own suite.
415
+
416
+ Vector sections: `levels`, `cycle`, `strategies`, `presence`,
417
+ `decisions.notification`, `decisions.demand`, `deferral`, `reconciliation`,
418
+ `metrics`, `externalLevelMapping`.
419
+
420
+ ### 11.1 Versioning
421
+
422
+ The vectors carry the reference implementation's version. Within a major
423
+ version, existing vectors **MUST NOT** change meaning; sections and vectors
424
+ **MAY** be added. A change to a shipped table's values is a breaking change to
425
+ this specification, not just to the library.
426
+
427
+ ---
428
+
429
+ ## 12. What is deliberately not specified
430
+
431
+ - **Wording.** Every user-facing string is a product decision.
432
+ - **Visual design.** The reference stylesheet is one interpretation.
433
+ - **How a level is chosen.** A dial, a keystroke, a menu — the model requires
434
+ only that the person can set it directly.
435
+ - **Classification.** How a host decides that an arrival bears an obligation, or
436
+ what tier an originator is in, is the host's problem entirely. The model
437
+ consumes the answer and the confidence attached to it.
438
+ - **Transport.** How states reach each other — shared storage, IPC, a sync
439
+ server — is out of scope. The schema and §4 are what make any transport work.
package/api-surface.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.6.0",
2
+ "version": "1.0.0",
3
3
  "entryPoints": [
4
4
  {
5
5
  "specifier": "@kumbatio/energy-system",
@@ -34,6 +34,7 @@
34
34
  "isHigherEnergy",
35
35
  "isNotificationPriority",
36
36
  "isOriginatorTier",
37
+ "isPreferredEnergyState",
37
38
  "isPresenceVisible",
38
39
  "isSessionExpired",
39
40
  "isUnproducedState",