@kumbatio/energy-system 0.5.4 → 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
@@ -32,6 +32,10 @@ This library gives applications a structured way to adapt to energy state instea
32
32
  - Task complexity guidance
33
33
  - Interaction forgiveness (undo windows, destructive-action confirmation)
34
34
  - Energy-aware deferral ordering
35
+ - Autonomy (how much automation may do unattended)
36
+ - **Inbound demand admission**: an energy-resolved policy for arrivals that ask
37
+ something of the user — reach them now, acknowledge and queue, or queue
38
+ silently (`demandAdmissionStrategy`, `resolveDemandOutcome`)
35
39
  - **Presence annotation**: declare which energy levels a component/view belongs
36
40
  to (`defineEnergyPresence`, `presenceAtOrAbove`, `<EnergyGate>`, `data-energy-min`)
37
41
  - **Focus sessions**: time-boxed suppression windows with auto-expiry and break nudges
@@ -254,6 +258,93 @@ const { defaultPresetId, orderedPresetIds } = engine.resolve(deferralStrategy)
254
258
  const resurfaceAt = resolveDeferral(presets, defaultPresetId) // epoch ms
255
259
  ```
256
260
 
261
+ ## Inbound demand and autonomy
262
+
263
+ **Inbound demand** is anything arriving from outside that asks for the user's
264
+ attention or action: an email, a document comment, a review request, a task
265
+ assignment, a collaboration invite.
266
+
267
+ Every triage system in general use is organised around properties of the
268
+ _message_ — who sent it, how urgent it claims to be, what category it fits.
269
+ None is organised around the state of the _recipient_, which is the thing that
270
+ actually decides whether an arrival is a small task or a crushing weight. This
271
+ is that variable, applied to the queue.
272
+
273
+ `demandAdmissionStrategy` resolves the policy; `resolveDemandOutcome` applies
274
+ it. Both are pure. The library performs no effects here — acknowledging an
275
+ originator means sending mail, posting a comment, or updating a status chip
276
+ depending on the app, and those are irreversible in ways an in-process runtime
277
+ cannot make transactional. The orchestration, with its ordering, retries, and
278
+ deduplication, belongs to the app.
279
+
280
+ ```ts
281
+ import {
282
+ autonomyStrategy,
283
+ deferralStrategy,
284
+ demandAdmissionStrategy,
285
+ resolveDemandOutcome,
286
+ } from '@kumbatio/energy-system'
287
+
288
+ const outcome = resolveDemandOutcome(
289
+ engine.resolve(demandAdmissionStrategy),
290
+ engine.resolve(autonomyStrategy),
291
+ {
292
+ originatorTier: 'unknown', // 'exempt' | 'known' | 'unknown', assigned by your app
293
+ bearsObligation: true, // does this ask something of the user?
294
+ confidence: 0.9, // how sure are you? a deterministic rule reports 1
295
+ },
296
+ )
297
+
298
+ switch (outcome.admission) {
299
+ case 'live':
300
+ return inbox.deliver(message)
301
+ case 'acknowledge':
302
+ // One act, never two. An acknowledgment without a capture is a promise
303
+ // nobody kept; a capture without an acknowledgment leaves the originator in
304
+ // silence. Capture first — it is the reversible half.
305
+ await tasks.capture(message, engine.resolve(deferralStrategy).defaultPresetId)
306
+ return replies.acknowledge(message, outcome.acknowledgment)
307
+ case 'silent':
308
+ return tasks.capture(message, engine.resolve(deferralStrategy).defaultPresetId)
309
+ }
310
+ ```
311
+
312
+ Two rules are worth stating outright, because each blocks a specific failure:
313
+
314
+ 1. **The exempt tier is never handled by machine.** At every level, an exempt
315
+ originator is admitted live. This is also what defuses the gaming risk: an
316
+ originator who learns an acknowledgment means "deprioritised" and escalates
317
+ elsewhere only succeeds if their escalation is one the user cannot ignore —
318
+ which is what makes them exempt.
319
+ 2. **Acknowledgments report state, never intent.** "Received and queued,
320
+ current response horizon early next week" is a fact. "I'll get back to you
321
+ soon" is a promise the user's Tuesday self has to keep. The horizon comes
322
+ from `deferralStrategy`, so the queue and the acknowledgment cannot disagree.
323
+
324
+ Disclosure is the app's job in the app's own medium — an `Auto-Submitted:
325
+ auto-replied` header, an "auto-queued" badge, a system-attributed status. An
326
+ automated action toward a third party must be identifiable as automated.
327
+
328
+ ### Autonomy
329
+
330
+ `autonomyStrategy` is the mirror of `interactionForgivenessStrategy`:
331
+ forgiveness protects against the _user's_ mistakes at low energy, autonomy
332
+ against the _agent's_. It is useful to any consumer with agentic surfaces, with
333
+ or without demand admission.
334
+
335
+ ```ts
336
+ const { confidenceThreshold, allowGeneratedContent, maxUnattendedSteps } =
337
+ engine.resolve(autonomyStrategy)
338
+ ```
339
+
340
+ What narrows as energy falls is _discretion_, not action. At rest the
341
+ confidence threshold is `1`, which admits only certainty — rule-based actions,
342
+ never a judgment call — and `maxUnattendedSteps` is `1`. Automation may still
343
+ take a single, certain, template-only step, which is exactly the shape of an
344
+ out-of-office reply. It may not chain steps or improvise wording. The system
345
+ acts for the user precisely when they are least able to supervise it, so the
346
+ worst day is the wrong day for it to get creative.
347
+
257
348
  ## Quick start (DOM)
258
349
 
259
350
  ```ts
@@ -308,7 +399,8 @@ animating presence changes should do the same.
308
399
  - `mapToNearestDiscreteLevel(value, levels, fallback)`
309
400
  - `mapToNearestEnergyLevel(value)`
310
401
  - Strategies: `uiVisibilityStrategy`, `notificationStrategy`,
311
- `taskComplexityStrategy`, `interactionForgivenessStrategy`, `deferralStrategy`
402
+ `taskComplexityStrategy`, `interactionForgivenessStrategy`, `deferralStrategy`,
403
+ `autonomyStrategy`, `demandAdmissionStrategy`
312
404
  - Presence: `defineEnergyPresence(spec)`, `presenceAtOrAbove(min, below?)`,
313
405
  `presenceAtOrBelow(max, above?)`, `resolveEnergyPresence(map, level)`,
314
406
  `isPresenceVisible(presence)`, `isEnergyPresence(value)`,
@@ -320,6 +412,8 @@ animating presence changes should do the same.
320
412
  `isNotificationPriority(value)`
321
413
  - Deferral: `createDeferralPresets(options?)`, `resolveDeferral(presets, id, now?)`,
322
414
  `DEFERRAL_PRESET_IDS`
415
+ - Demand admission: `resolveDemandOutcome(config, autonomy, demand)`,
416
+ `isOriginatorTier(value)`
323
417
  - Types: `EnergyLevel`, `EnergyState`, `EnergyPresence`, `EnergyPresenceMap`,
324
418
  `AdaptationStrategy`, `FocusSession`, `NotificationDelivery`, etc.
325
419
 
@@ -419,11 +513,54 @@ The engine orders concurrent writes by timestamp, logical revision, source prior
419
513
  origin. This gives every context the same deterministic winner even when two writes share a wall
420
514
  clock timestamp. Local writes advance the logical revision when the clock does not advance.
421
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
+
422
520
  `setLevel()` updates in-memory subscribers synchronously. Persistence runs in the background with
423
521
  bounded exponential backoff. Call `await engine.flush()` when a workflow must wait for durable
424
522
  storage before reporting completion. An initial `flush()` waits for hydration before writing the
425
523
  default state, and rejects rather than overwriting unread storage if that hydration read failed.
426
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
+
427
564
  ## Development
428
565
 
429
566
  ```bash
@@ -442,7 +579,7 @@ adapters (e.g., SQLite-backed desktop stores) should live in consuming apps.
442
579
  ## Who uses this
443
580
 
444
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.
445
- - **[Meltemi](https://meltemi.app)** — an email client in private beta, 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.
446
583
  - **[kumbat.io](https://kumbat.io)** — the site itself runs on this model; change the energy level there and watch the interface adapt.
447
584
 
448
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).