@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 +111 -0
- package/README.md +44 -1
- package/SPEC.md +439 -0
- package/api-surface.json +2 -1
- package/conformance.json +3176 -0
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +3 -32
- package/dist/engine.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/reconcile.d.ts +26 -0
- package/dist/reconcile.d.ts.map +1 -0
- package/dist/reconcile.js +75 -0
- package/dist/reconcile.js.map +1 -0
- package/package.json +12 -5
- package/spec/energy-state.schema.json +53 -0
- package/src/energy.css +77 -0
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,
|
|
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.
|
|
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",
|