@adam0white/human-framework 2.14.1 → 2.16.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/README.md CHANGED
@@ -1,13 +1,72 @@
1
1
  # @adam0white/human-framework
2
2
 
3
- Simulated people for games and headless simulations. A `Person` has a body, needs, emotions, memory,
4
- beliefs, skills, habits, relationships, values, an understanding of moral norms, and a will. The host
5
- world offers actions; the person chooses among them, and can refuse what an outside voice suggests. Every
6
- choice comes with the terms that produced it and a first-person line, so a refusal reads as a reason rather
7
- than a bug.
3
+ HF is a TypeScript library for simulated people in games and headless simulations. Your world offers
4
+ possible actions. Each person weighs needs, habits, duties, goals and suggestions, then chooses.
5
+ A player can suggest an action; the person can defer or refuse it.
8
6
 
9
- The library is TypeScript and has no runtime dependencies. Its core loop is deterministic and does not use an LLM or touch the
10
- DOM. Person state is plain JSON.
7
+ HF 2.14.1 uses utility scoring and seeded randomness, with no LLM in the actor loop and no runtime
8
+ dependencies. Decision records expose scored terms and template narration. Person state is plain JSON.
9
+ This is an authored simulation model, not a validated predictor of real people.
10
+
11
+ ## Try the games
12
+
13
+ - [Twice at the Well](https://human.adamwhite.work/colony/): compare a colony without and with HF.
14
+ - [The Day You Say Nothing](https://human.adamwhite.work/voice/): suggest actions to one man who can refuse.
15
+ - [The Night Watch](https://human.adamwhite.work/watch/): guide a village watch across winters.
16
+ - [All lab games](https://human.adamwhite.work/games/).
17
+
18
+ ## Quick start
19
+
20
+ Use Node 24 or newer. In an empty directory, install the version this README describes:
21
+
22
+ ```sh
23
+ npm install @adam0white/human-framework@2.14.1
24
+ ```
25
+
26
+ Save these 20 lines as `quickstart.mjs`, then run `node quickstart.mjs`:
27
+
28
+ ```js
29
+ import {
30
+ createCommunity, createPerson, createVillage,
31
+ predict, stepCommunity, villagerSpec,
32
+ } from '@adam0white/human-framework';
33
+ const start = 6 * 60; // simulation minutes
34
+ const ids = ['ada', 'bora'];
35
+ const people = ids.map((id, i) => createPerson(
36
+ villagerSpec(id, id, i + 1, { now: start, others: ids }),
37
+ ));
38
+ const world = createVillage(people, { seed: 7 });
39
+ const community = createCommunity(people);
40
+ const ada = community.people[0];
41
+ const ask = { voiceId: 'player', action: 'chat', strength: 0.6 };
42
+ const hint = predict(ada, world.affordancesFor(ada), ask);
43
+ console.log('Prediction:', hint.verdict, hint.reason);
44
+ stepCommunity(community, world, start + 24 * 60, {
45
+ suggestions: { ada: ask },
46
+ });
47
+ console.log('Last decision:', ada.trace.at(-1)?.narration);
48
+ console.log('JSON snapshot characters:', JSON.stringify(ada).length);
49
+ ```
50
+
51
+ `predict` leaves the person and RNG unchanged. `stepCommunity` advances a bundled example village
52
+ for one day; the suggestion stands at Ada's decisions. For your own game, replace that village
53
+ with a `World` that supplies offers, percepts and actual outcomes (see [the host contract](#the-host-contract)).
54
+
55
+ ## What it models
56
+
57
+ - Bodily and psychological needs, affect, attention, memory, beliefs, skills, habits and relationships.
58
+ - Values, held moral norms, commitments, goals and trust in outside voices.
59
+ - Optional faculties for long runs: aging, family, gists and character change.
60
+ - Opt-in curiosity, bounded planning, self-set projects from host-shown prospects and pursuit persistence.
61
+
62
+ The host supplies the available actions and their meaning. Projects and planning do not invent actions
63
+ or solve arbitrary tasks. In the island control on 2.14.1, people made small free-time piles, but most
64
+ placements replaced earlier work and the carry task did not improve. See the
65
+ [experiment](https://github.com/adam0white/human-framework/blob/main/docs/lab/experiments/island-2026-10-20.md).
66
+
67
+ HF does not provide a map, pathfinding, free-form language, a general planner or calibrated human
68
+ behaviour. Its default norms are Islam-guided; it models a person's held understanding, never a ruling
69
+ or divine acceptance. See [the boundaries](#what-it-does-not-model).
11
70
 
12
71
  ## The spine: Urge → Assent → Act
13
72
 
@@ -38,69 +97,13 @@ never overrides `cannot` or `willNot`.
38
97
  Every resolution also carries a machine-readable `reason` (`need:food`, `norm:theft`, `asleep`, `distrust`,
39
98
  ...) and a line in the person's voice (`says`).
40
99
 
41
- ## Install
42
-
43
- The source is public on [GitHub](https://github.com/adam0white/human-framework) under the MIT license. Install
44
- it from npm (published from 2.1.3; later versions are built by the release workflow and carry an npm provenance
45
- attestation):
46
-
47
- ```sh
48
- npm install @adam0white/human-framework
49
- ```
50
-
51
- Each [GitHub release](https://github.com/adam0white/human-framework/releases) also has the tarball attached, which
52
- installs by URL:
53
-
54
- ```sh
55
- npm install https://github.com/adam0white/human-framework/releases/download/v2.2.0/adam0white-human-framework-2.2.0.tgz
56
- ```
57
-
58
- or build one from a checkout:
59
-
60
- ```sh
61
- npm pack -w packages/human # runs the build; writes adam0white-human-framework-<version>.tgz
62
- npm install /path/to/adam0white-human-framework-<version>.tgz
63
- ```
64
-
65
- The v2.1.0 release tarball (`human-framework-2.1.0.tgz`) predates the rename and installs as
66
- `@human/framework`.
67
-
68
- It ships ES modules and `.d.ts` files (`exports["."]` with `types`). It requires Node ≥ 24 or a modern
69
- browser. The library needs `structuredClone` and nothing else from the host.
70
-
71
- ## Quick start
72
-
73
- ```ts
74
- import {
75
- createCommunity, createPerson, createVillage, MINUTES_PER_DAY, predict, stepCommunity,
76
- type Suggestion, villagerSpec,
77
- } from '@adam0white/human-framework';
78
-
79
- const ids = ['ada', 'bora'];
80
- const people = ids.map((id, i) => createPerson(villagerSpec(id, id, 1 + i, { now: 6 * 60, others: ids })));
81
- const world = createVillage(people, { seed: 7 }); // a bundled World: home, field, well, kitchen, mosque, forest
82
- const community = createCommunity(people);
83
- const ada = community.people[0]!;
84
-
85
- const ask: Suggestion = { voiceId: 'player', action: 'chat', strength: 0.6 };
86
- const hint = predict(ada, world.affordancesFor(ada), ask); // pure: no state change, no RNG
87
- console.log(hint.verdict, hint.reason, hint.says);
88
-
89
- // One day; the suggestion stands at each of Ada's decisions.
90
- const events = stepCommunity(community, world, 6 * 60 + MINUTES_PER_DAY, { suggestions: { ada: ask } });
91
- const last = ada.trace.at(-1)!;
92
- console.log(last.chosenAction, last.suggestion?.verdict, last.narration);
93
- ```
94
-
95
- The runnable examples in [`examples/`](examples) go further. Run them all with
96
- `npm run examples -w packages/human`, or one at a time with `node examples/<name>.ts` after a build. A
97
- vitest (`test/examples.test.ts`) runs them, so they keep working as the code changes.
100
+ ## Package and source
98
101
 
99
- - [`minimal.ts`](examples/minimal.ts): a hand-written `World` (offers, percepts, outcomes) and one person's day.
100
- - [`suggestions.ts`](examples/suggestions.ts): one voice, five verdicts (assented, deferred, refused/willNot,
101
- complied, refused/cannot), then one recorded decision and its utility terms.
102
- - [`community.ts`](examples/community.ts): three villagers, a standing nudge, then `runSilent` with that
103
- voice muted and `diffChronicle` over the two periods.
102
+ The package ships ES modules and TypeScript declarations. It requires Node ≥24 or a modern browser
103
+ with `structuredClone`. Source, tests and runnable examples are on
104
+ [GitHub](https://github.com/adam0white/human-framework/tree/main/packages/human).
105
+ In a source checkout, `npm run examples -w packages/human` builds and runs `minimal.ts`,
106
+ `suggestions.ts` and `community.ts`. The `examples/` and `test/` directories are not in the npm package.
104
107
 
105
108
  ## The host contract
106
109
 
@@ -141,10 +144,10 @@ place. A `social` tag is also a claim about the offered activity, not a request
141
144
  contact becomes impossible, interrupt or replan the activity instead of completing the declared
142
145
  encounter. Report only the need effects that the actual interaction supports.
143
146
 
144
- The current behavior is defined in [`Affordance` and `Outcome`](src/types.ts) and
145
- [`finish`](src/person.ts). Existing controls cover [body advancement and waking after sleep](test/person.test.ts),
146
- [body recovery](test/body.test.ts), [material valuation under scarcity](test/cognition-scarcity.test.ts),
147
- [actual material in day records](test/chronicle.test.ts), and [relationship-aware social terms](test/social.test.ts).
147
+ The current behavior is defined in [`Affordance` and `Outcome`](https://github.com/adam0white/human-framework/blob/main/packages/human/src/types.ts) and
148
+ [`finish`](https://github.com/adam0white/human-framework/blob/main/packages/human/src/person.ts). Existing controls cover [body advancement and waking after sleep](https://github.com/adam0white/human-framework/blob/main/packages/human/test/person.test.ts),
149
+ [body recovery](https://github.com/adam0white/human-framework/blob/main/packages/human/test/body.test.ts), [material valuation under scarcity](https://github.com/adam0white/human-framework/blob/main/packages/human/test/cognition-scarcity.test.ts),
150
+ [actual material in day records](https://github.com/adam0white/human-framework/blob/main/packages/human/test/chronicle.test.ts), and [relationship-aware social terms](https://github.com/adam0white/human-framework/blob/main/packages/human/test/social.test.ts).
148
151
  These boundaries require correct host reports; they do not imply a framework behavior change.
149
152
 
150
153
  ### Scheduled activity reviews
@@ -162,8 +165,8 @@ Do not restart a continued activity or refund its resources. Hard capacity vetoe
162
165
  Sleep reviews can move earlier for ordinary pending appointments. The appointment's time pressure
163
166
  can permit waking; an obligatory norm is not required. A host that ignores the scheduled review can
164
167
  make someone miss an obligation HF was ready to reconsider. This does not imply advance planning
165
- for unavailable future actions. See [`reviewed` and the decision lifecycle](src/person.ts) and
166
- [`wakeReviewAt` and commitment-based waking](src/will/will.ts).
168
+ for unavailable future actions. See [`reviewed` and the decision lifecycle](https://github.com/adam0white/human-framework/blob/main/packages/human/src/person.ts) and
169
+ [`wakeReviewAt` and commitment-based waking](https://github.com/adam0white/human-framework/blob/main/packages/human/src/will/will.ts).
167
170
 
168
171
  You can drive a person by hand: `decide(p, offers, { suggestion })` → `begin(p, offer, record)` →
169
172
  `tick(p, now)` → `finish(p, outcome)`, with `perceive(p, percepts)` and `interrupt(p, now, reason)` between.
@@ -212,20 +215,20 @@ reference host: `createVillage`.
212
215
  - Hosts that live people sparsely over decades can shrink what a save carries per person with
213
216
  `setRetention(p, { trace, chronicleDays })` (or `PersonSpec.retention`): fewer decision records, and day records
214
217
  bounded by age as well as count. No decision changes. `restore` also refuses a non-finite or out-of-range `now`
215
- and holds every list to its live bound, so a crafted save cannot stall a host.
218
+ and bounds retained lists; hosts must still limit input size before parsing.
216
219
  - Version numbers are described under [Versions](#versions).
217
220
 
218
221
  ## Versions
219
222
 
220
223
  There are two version numbers, and they move independently.
221
224
 
222
- - **The release version** is the package version (`package.json`, now `2.2.1`) and `FRAMEWORK_VERSION`, the same
225
+ - **The release version** is the package version (`package.json`, now `2.14.1`) and `FRAMEWORK_VERSION`, the same
223
226
  string compiled into the build; a test keeps them equal. It versions the public API under
224
227
  [semver](https://semver.org/): a breaking change to an exported name or signature bumps the major, a new
225
228
  faculty or export the minor, a fix the patch. Each release is a tag `vX.Y.Z` and a GitHub release whose notes
226
- are that version's section of the root [CHANGELOG.md](../../CHANGELOG.md), with the package tarball attached
227
- (`npm run release`, run on a clean, pushed `main`).
228
- - **`ENGINE_VERSION`** (exported; `2.2.0` at release 2.2.1) versions simulation behaviour and the save format. It
229
+ are that version's section of the root [CHANGELOG.md](https://github.com/adam0white/human-framework/blob/main/CHANGELOG.md), with the package tarball attached
230
+ (`npm run release`, from a clean checkout of pushed `origin/main`).
231
+ - **`ENGINE_VERSION`** (exported; `2.14.1` at release 2.14.1) versions simulation behaviour and the save format. It
229
232
  changes when the same seed and inputs would give different decisions, or when person state changes shape.
230
233
  `restore` upgrades saves from engine 1.4.0 and later through `migrate` and refuses older ones. Every release's notes name the
231
234
  `ENGINE_VERSION` it ships, so a host can tell whether its saves still restore.
@@ -295,22 +298,22 @@ that place lose appeal until the fear fades. Groups and factions themselves stay
295
298
  starts with a scope paragraph that also lists what that faculty leaves out.
296
299
  - **Rulings or divine judgment.** Norms come from a host catalog with provenance (`NormDefinition.sources`;
297
300
  the bundled `DEFAULT_NORMS` is Islam-guided; each reference's verification status is listed in
298
- [research/norm-sources.md](../../research/norm-sources.md)). A host with its own catalog passes it as
301
+ [research/norm-sources.md](https://github.com/adam0white/human-framework/blob/main/research/norm-sources.md)). A host with its own catalog passes it as
299
302
  `World.catalog`, so necessity excuses and exemptions read that catalog; a norm not in it is not excused.
300
303
  A person holds an *understanding* of a norm, with a standing and a conviction. That understanding is
301
304
  not a ruling. The library never computes divine acceptance. Where schools of thought differ, the
302
305
  position the bundled catalog takes, and why, is recorded in
303
- [research/decisions.md](../../research/decisions.md).
306
+ [research/decisions.md](https://github.com/adam0white/human-framework/blob/main/research/decisions.md).
304
307
 
305
308
  ## Further reading
306
309
 
307
- - [docs/framework.md](../../docs/framework.md): the architecture, which module owns which state slice, the
310
+ - [docs/framework.md](https://github.com/adam0white/human-framework/blob/main/docs/framework.md): the architecture, which module owns which state slice, the
308
311
  utility terms, will rules, host protocols, and locked decisions.
309
- - [docs/api.md](../../docs/api.md): every export, grouped by module, generated from the built declarations.
312
+ - [docs/api.md](https://github.com/adam0white/human-framework/blob/main/docs/api.md): every export, grouped by module, generated from the built declarations.
310
313
  Helpers tagged `@internal` are stripped from the shipped declarations and are not part of the API.
311
314
  The affect module's `release` is exported as `releaseEmotion`, the agenda's as `releaseCommitment`, and the
312
315
  will's scorer as `scoreAndResolve`.
313
- - [docs/findings.md](../../docs/findings.md): negative findings recorded during development.
316
+ - [docs/findings.md](https://github.com/adam0white/human-framework/blob/main/docs/findings.md): negative findings recorded during development.
314
317
 
315
318
  ## License
316
319
 
@@ -343,7 +346,7 @@ const evidence = methodEvidence(learner, 'send-north', 'new-parcel');
343
346
  clearMethodContext(learner); // when leaving or switching the task
344
347
  ```
345
348
 
346
- Import these functions from the package. Rules/evidence live in optional `Person.methods` and survive snapshots. Current cues expire after 120 minutes. A stable demonstration id avoids counting the same example twice within the retained 256-example history, even after its rule or evidence is evicted. Once an id has left both that history and retained rule evidence, it can be admitted again. Missing observations cannot satisfy a condition. Failed demonstrations reduce support for that action. Successful alternatives retain their own support; ordinary cognition weighs the supported offers alongside other motives. A successful lecture does not certify reliable execution. [The routing control](examples/method-control.ts) demonstrates changed teaching, withheld teaching, a scalar-skill control and acquired-state ablation with two methods and an exception. [The alternatives control](examples/method-alternatives-control.ts) demonstrates two independently successful procedures under the same observations, failed practice, hunger and exact restore.
349
+ Import these functions from the package. Rules/evidence live in optional `Person.methods` and survive snapshots. Current cues expire after 120 minutes. A stable demonstration id avoids counting the same example twice within the retained 256-example history, even after its rule or evidence is evicted. Once an id has left both that history and retained rule evidence, it can be admitted again. Missing observations cannot satisfy a condition. Failed demonstrations reduce support for that action. Successful alternatives retain their own support; ordinary cognition weighs the supported offers alongside other motives. A successful lecture does not certify reliable execution. [The routing control](https://github.com/adam0white/human-framework/blob/main/packages/human/examples/method-control.ts) demonstrates changed teaching, withheld teaching, a scalar-skill control and acquired-state ablation with two methods and an exception. [The alternatives control](https://github.com/adam0white/human-framework/blob/main/packages/human/examples/method-alternatives-control.ts) demonstrates two independently successful procedures under the same observations, failed practice, hunger and exact restore.
347
350
 
348
351
 
349
352
  ## Receiving a possible future route
@@ -400,4 +403,59 @@ Only actual terminal outcomes advance terminal goals. Finishing one's own reques
400
403
 
401
404
  Knowledge lives in optional `Person.prospection` and survives snapshot/restore. A percept admits at most four additions/withdrawals combined, atomically. Invalid entries or duplicate source/id identities reject the entire batch. Equal/lower revisions do not reinforce or refresh; a higher revision replaces. There are at most 16 entries and 64 revision records. Consumed entries are evicted first, then the oldest remaining entries, while their revision records remain; a batch requiring more still-valid revision records is rejected. Entries and revision memory expire within seven days. Deduplication is bounded, not a durable audit. Capacity is shared across sources and admission does not allocate by trust. One prolific source can occupy the finite entry/revision capacity, causing other new ids to be rejected until revision space expires; this is bounded memory, not a source-fairness or trust firewall. Hosts must bound the premises they deliver. The pure query exposes retained entries, present feasibility and retained revisions.
402
405
 
403
- [The frozen control](examples/received-consequences.ts) publishes useful, late, costly, uncertain, withheld, removed-knowledge, removed-goal, temporal-off and refused arms in two renamed domains. This faculty does not search action graphs, predict future body state, price all waiting opportunities or guarantee consent or success. Its numerical bounds and control parameters are engineering assumptions.
406
+ [The frozen control](https://github.com/adam0white/human-framework/blob/main/packages/human/examples/received-consequences.ts) publishes useful, late, costly, uncertain, withheld, removed-knowledge, removed-goal, temporal-off and refused arms in two renamed domains. This one-step faculty does not search action graphs, price all waiting opportunities or guarantee consent or success. Opt-in bounded planning (2.12.0) and bodily forecast (2.5.0) extend valuation separately. Its numerical bounds and control parameters are engineering assumptions.
407
+
408
+ ## Persona projection (opt-in)
409
+
410
+ `projectPersona(source, mapping)` produces `PersonaProjectionSnapshot` (`hf-persona-state/1`).
411
+ `admitPersona(projection, targetOpening, { mappingVersion, sourceId, targetId })` returns a detached
412
+ `{ person, report }`. `validatePersonaProjection(unknown)` validates portable data without filling defaults.
413
+ These functions never enter the actor loop and never mutate the source or the host opening.
414
+
415
+ The host supplies a versioned map of skill ids, selected episode ids and every continuing obligation.
416
+ Skills keep exact levels and practice evidence. Episode action/person/place/voice references need explicit
417
+ bindings. Both evidence timestamps and birth minute shift by `targetOpening.now - source.now`; the Person
418
+ RNG carries unchanged. Commitments have explicit target-calendar windows, identical duration, kind,
419
+ importance and recurrence, and stay pending. This is a binding for a known calendar, not a calendar translator.
420
+
421
+ Admission requires the declared identity binding, matching name, sex, age, healthy compatible body,
422
+ and matching family/bond provenance. Version 1 rejects injuries, illness, downing and exposures. It also rejects pregnancy, courtships, marriages
423
+ and mourning because their dates require a separate explicit clock mapping. It rejects
424
+ running source activities, active goals/projects, owed duties and unmapped ongoing obligations, including
425
+ completed occurrences of unreleased recurring chains. The host must release finished employment in its own
426
+ replay. The host owns the cast registry, death history, branch authority, destination references and truth of
427
+ selected facts. A hash is not authentication. An old projection cannot override a dead target Person.
428
+
429
+ Target authors transient body state, needs, affect, social/conscience state and optional behaviour settings.
430
+ Portable skills and selected episodes replace target priors in those stores. The report discloses exclusions;
431
+ it does not claim that the complete source life continued. The original checkpoint belongs in the source save.
432
+ Pin the admitted Person and mapping version in the target replay. The transport owns its cryptographic hash,
433
+ branch metadata, consent and storage bounds. See `examples/persona-control.ts` for a deterministic two-host
434
+ control with preserved evidence, source immutability and an unmapped-skill comparison.
435
+
436
+ The agenda retains scheduled make-up records after the matching commitment is kept. Passport admission
437
+ recognizes that positive kept record as completed history. Missing or pruned records do not establish completion.
438
+ A host that needs durable completion evidence can explicitly call `retirePaidMakeUps(person)` before bounded
439
+ commitment history prunes it. It removes only records matched to an existing, closed, kept make-up and returns
440
+ recursively frozen, detached `MakeUpReceipt` objects. Persist those receipts in the source replay. Pending,
441
+ broken, released, exempt and missing matches remain unresolved. The normal actor loop never calls retirement;
442
+ existing games and saves keep their previous behavior until a host opts in.
443
+
444
+
445
+ ### Calendar passports (2.16.0, opt-in)
446
+
447
+ Use `projectCalendarPersona(source, { version, skills, episodes, calendar })` when declared obligations must
448
+ travel. `calendar` has version `hf-persona-calendar-map/1`, one exact clock offset, explicit injective maps
449
+ for commitment, chain, action, entity and norm references, and the source-backed norm catalog.
450
+ The result is `hf-persona-calendar/1`, wrapping the original approved Person projection and a separate
451
+ calendar. It preserves continuing commitments, every owed record, actual retained receipts, held norm
452
+ understandings and lapse state. A missing scheduled receipt remains unresolved and is disclosed; it never
453
+ becomes a paid duty. `isContinuingCommitment(agenda, commitment)` lets a host construct the required maps
454
+ using the owning agenda's existing recurrence semantics.
455
+
456
+ `validateCalendarPersona` strictly validates portable data. `admitCalendarPersona(payload, opening,
457
+ { mappingVersion, sourceId, targetId, targetNorms })` checks the offset and exact catalog semantics before
458
+ returning a detached Person. The receiving calendar must be empty; authored role goals may remain. After
459
+ admission, the host uses ordinary agenda APIs and its declared calendar. No duty is waived, no new ruling
460
+ is invented, and no obligation is converted to a score. Activities, projects and incompatible identity or
461
+ health remain unsupported. The original passport format and no-call continuation remain unchanged.
@@ -56,6 +56,8 @@ export declare function createAgenda(spec: {
56
56
  export declare function promise(p: Person, c: Omit<Commitment, 'id' | 'status'>): Commitment;
57
57
  /** Release a pending commitment (the other party let it go). Released commitments do not recur. */
58
58
  export declare function release(p: Person, id: string): boolean;
59
+ /** Whether an instance still owns a pending duty or a recurrence that can produce its next window. Read-only. */
60
+ export declare const isContinuingCommitment: (state: AgendaState, c: Commitment) => boolean;
59
61
  /**
60
62
  * Whether an activity that would keep `c` is under way at `now`: begun no later than the window's end
61
63
  * (`spanMeetsWindow`), not yet ended, not sleep, and serving `c` (listed in `fulfills` or matching its action and
@@ -182,6 +184,20 @@ export declare function applyExemptions(p: Person, now: Minute, ctx?: {
182
184
  exempted: Commitment[];
183
185
  owed: OwedMakeUp[];
184
186
  };
187
+ export interface MakeUpReceipt {
188
+ readonly owed: Readonly<OwedMakeUp>;
189
+ readonly commitment: Readonly<Commitment>;
190
+ /** Source clock when the host explicitly retired the recorded debt. */
191
+ readonly recordedAt: Minute;
192
+ }
193
+ /**
194
+ * Opt-in host archive boundary. Remove only retained make-up records with a matching existing kept instance,
195
+ * returning detached, recursively frozen receipts for the host to persist in its replay. Keep commitments and
196
+ * every unpaid, pending, released, exempt or unproven record. Call before ordinary bounded history prunes the
197
+ * evidence. No actor-loop call is added; hosts that do not call this retain exact prior state and behavior.
198
+ * Records describe simulated completion, never divine acceptance or a waiver of an unfulfilled duty.
199
+ */
200
+ export declare function retirePaidMakeUps(p: Person): readonly MakeUpReceipt[];
185
201
  /** Make-ups still owed and not yet scheduled. */
186
202
  export declare const owedMakeUps: (p: Person) => OwedMakeUp[];
187
203
  /**
@@ -231,7 +231,8 @@ export function release(p, id) {
231
231
  c.status = 'released';
232
232
  return true;
233
233
  }
234
- const sameChain = (a, b) => a.chain !== undefined && b.chain !== undefined ? a.kind === b.kind && a.chain === b.chain : sameShape(a, b);
234
+ /** Compare recurrence identity without changing agenda state. @internal */
235
+ export const sameChain = (a, b) => a.chain !== undefined && b.chain !== undefined ? a.kind === b.kind && a.chain === b.chain : sameShape(a, b);
235
236
  const sameShape = (a, b) => a.kind === b.kind &&
236
237
  (a.violatedBy ?? []).join('|') === (b.violatedBy ?? []).join('|') &&
237
238
  a.normId === b.normId &&
@@ -245,6 +246,13 @@ const sameShape = (a, b) => a.kind === b.kind &&
245
246
  const hasSuccessor = (state, c) => state.commitments.some((x) => x !== c && x.from > c.from && sameChain(x, c));
246
247
  /** Closed instances that carry their chain on: kept, broken, or released by an exemption (not by the other party). */
247
248
  const chainLink = (c) => c.status === 'kept' || c.status === 'broken' || (c.status === 'released' && c.exempt !== undefined);
249
+ /** Whether an instance still owns a pending duty or a recurrence that can produce its next window. Read-only. */
250
+ export const isContinuingCommitment = (state, c) => c.status === 'pending' ||
251
+ (c.recurEvery !== undefined &&
252
+ c.recurEvery > 0 &&
253
+ chainLink(c) &&
254
+ (c.recurUntil === undefined || c.from + c.recurEvery <= c.recurUntil) &&
255
+ !hasSuccessor(state, c));
248
256
  const needsSpawn = (state, c, now) => c.recurEvery !== undefined &&
249
257
  c.recurEvery > 0 &&
250
258
  chainLink(c) &&
@@ -888,6 +896,54 @@ function boundOwed(state) {
888
896
  owed.splice(i >= 0 ? i : 0, 1);
889
897
  }
890
898
  }
899
+ /** A kept, closed instance is positive recorded evidence; scheduling or missing history is not. */
900
+ function recordedKeptMakeUp(p, owed) {
901
+ return p.agenda.commitments.find((c) => owed.scheduledAs !== undefined &&
902
+ c.id === owed.scheduledAs &&
903
+ c.makeUpOf === owed.ofId &&
904
+ c.status === 'kept' &&
905
+ c.kind === owed.kind &&
906
+ c.normId === owed.normId &&
907
+ c.exempt === undefined &&
908
+ c.until <= p.now);
909
+ }
910
+ /**
911
+ * Whether any retained make-up record lacks a recorded kept matching instance. Scheduling alone is not
912
+ * fulfillment. Missing/pruned history remains unresolved to this reader; it does not infer payment from absence.
913
+ * Read-only; does not change the agenda's retained debt/history representation. @internal
914
+ */
915
+ export function hasUnresolvedMakeUps(p) {
916
+ return (p.agenda.owed ?? []).some((owed) => recordedKeptMakeUp(p, owed) === undefined);
917
+ }
918
+ function freezeReceipt(value) {
919
+ if (value !== null && typeof value === 'object') {
920
+ for (const child of Object.values(value))
921
+ freezeReceipt(child);
922
+ Object.freeze(value);
923
+ }
924
+ return value;
925
+ }
926
+ /**
927
+ * Opt-in host archive boundary. Remove only retained make-up records with a matching existing kept instance,
928
+ * returning detached, recursively frozen receipts for the host to persist in its replay. Keep commitments and
929
+ * every unpaid, pending, released, exempt or unproven record. Call before ordinary bounded history prunes the
930
+ * evidence. No actor-loop call is added; hosts that do not call this retain exact prior state and behavior.
931
+ * Records describe simulated completion, never divine acceptance or a waiver of an unfulfilled duty.
932
+ */
933
+ export function retirePaidMakeUps(p) {
934
+ const receipts = [];
935
+ const unresolved = [];
936
+ for (const owed of p.agenda.owed ?? []) {
937
+ const commitment = recordedKeptMakeUp(p, owed);
938
+ if (commitment)
939
+ receipts.push(structuredClone({ owed, commitment, recordedAt: p.now }));
940
+ else
941
+ unresolved.push(owed);
942
+ }
943
+ if (receipts.length)
944
+ p.agenda.owed = unresolved;
945
+ return freezeReceipt(receipts);
946
+ }
891
947
  /** Make-ups still owed and not yet scheduled. */
892
948
  export const owedMakeUps = (p) => (p.agenda.owed ?? []).filter((o) => o.scheduledAs === undefined);
893
949
  /**
package/dist/index.d.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  /** Human Framework public surface. */
2
- export declare const FRAMEWORK_VERSION = "2.14.1";
2
+ export declare const FRAMEWORK_VERSION = "2.16.0";
3
3
  export type { CrisisEvent, PracticeKind } from './affect/index.ts';
4
4
  export { AFFECT_DEFAULTS, actionTendencies, advanceAffect, appraise, breakAllows, breakBehaviour, breakHazard, CRISIS_DEFAULTS, checkCrisis, createAffect, easeBreak, emotionLevel, enableBreaks, feel, inBreak, readAffect, regulate, release as releaseEmotion, skipCrisis, strain, tendencyEmotions, } from './affect/index.ts';
5
- export type { GoalTemplate, PrayerCalendar, PrayerSchedule, PrayerTimes, Retimer, } from './agenda/index.ts';
6
- export { AGENDA_DEFAULTS, abandonGoal, adoptGoal, advanceAgenda, agendaTerms, applyExemptions, CARE_DEFAULTS, calendarRetimer, careDuty, commitmentPressure, createAgenda, DEFAULT_PRAYER_TIMES, EID_PRAYER_IMPORTANCE, EXEMPTION_DEFAULTS, eidPrayer, eidWindow, fastWindow, iftarWindow, inMakruhTime, isUnderWay, MAKRUH_DEFAULTS, makruhWindows, missedExcuse, onFinished, owedMakeUps, PRAYER_IMPORTANCE, PURPOSE_DEFAULTS, prayerWindows, pressureReachedAt, promise, proposeGoals, RAMADAN_DEFAULTS, ramadanFast, ramadanMeals, release as releaseCommitment, retimeCommitments, revisePurposes, scheduleMakeUp, spanMeetsWindow, suhoorWindow, timesFor, violatesAbstention, } from './agenda/index.ts';
5
+ export type { GoalTemplate, MakeUpReceipt, PrayerCalendar, PrayerSchedule, PrayerTimes, Retimer, } from './agenda/index.ts';
6
+ export { AGENDA_DEFAULTS, abandonGoal, adoptGoal, advanceAgenda, agendaTerms, applyExemptions, CARE_DEFAULTS, calendarRetimer, careDuty, commitmentPressure, createAgenda, DEFAULT_PRAYER_TIMES, EID_PRAYER_IMPORTANCE, EXEMPTION_DEFAULTS, eidPrayer, eidWindow, fastWindow, iftarWindow, inMakruhTime, isContinuingCommitment, isUnderWay, MAKRUH_DEFAULTS, makruhWindows, missedExcuse, onFinished, owedMakeUps, PRAYER_IMPORTANCE, PURPOSE_DEFAULTS, prayerWindows, pressureReachedAt, promise, proposeGoals, RAMADAN_DEFAULTS, ramadanFast, ramadanMeals, release as releaseCommitment, retimeCommitments, retirePaidMakeUps, revisePurposes, scheduleMakeUp, spanMeetsWindow, suhoorWindow, timesFor, violatesAbstention, } from './agenda/index.ts';
7
7
  export * from './beliefs/index.ts';
8
8
  export * from './body/index.ts';
9
9
  export * from './character/index.ts';
@@ -30,6 +30,8 @@ export * from './needs/index.ts';
30
30
  export * from './partnering/index.ts';
31
31
  export { enablePersistence, PERSISTENCE_DEFAULTS, pursuitOf } from './persistence/index.ts';
32
32
  export * from './person.ts';
33
+ export * from './persona.ts';
34
+ export * from './persona-calendar.ts';
33
35
  export { considerProjects, enableProjects, PROJECTS_DEFAULTS, projectAppeal } from './projects/index.ts';
34
36
  export * from './promptings/index.ts';
35
37
  export type { ConsequenceEvidence, PlanRoute, PlanStep } from './prospection/index.ts';
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  /** Human Framework public surface. */
2
- export const FRAMEWORK_VERSION = '2.14.1';
2
+ export const FRAMEWORK_VERSION = '2.16.0';
3
3
  export { AFFECT_DEFAULTS, actionTendencies, advanceAffect, appraise, breakAllows, breakBehaviour, breakHazard, CRISIS_DEFAULTS, checkCrisis, createAffect, easeBreak, emotionLevel, enableBreaks, feel, inBreak, readAffect, regulate, release as releaseEmotion, skipCrisis, strain, tendencyEmotions, } from './affect/index.js';
4
4
  // `agenda.release` collides with `affect.release`; the agenda one is re-exported under a clearer name.
5
- export { AGENDA_DEFAULTS, abandonGoal, adoptGoal, advanceAgenda, agendaTerms, applyExemptions, CARE_DEFAULTS, calendarRetimer, careDuty, commitmentPressure, createAgenda, DEFAULT_PRAYER_TIMES, EID_PRAYER_IMPORTANCE, EXEMPTION_DEFAULTS, eidPrayer, eidWindow, fastWindow, iftarWindow, inMakruhTime, isUnderWay, MAKRUH_DEFAULTS, makruhWindows, missedExcuse, onFinished, owedMakeUps, PRAYER_IMPORTANCE, PURPOSE_DEFAULTS, prayerWindows, pressureReachedAt, promise, proposeGoals, RAMADAN_DEFAULTS, ramadanFast, ramadanMeals, release as releaseCommitment, retimeCommitments, revisePurposes, scheduleMakeUp, spanMeetsWindow, suhoorWindow, timesFor, violatesAbstention, } from './agenda/index.js';
5
+ export { AGENDA_DEFAULTS, abandonGoal, adoptGoal, advanceAgenda, agendaTerms, applyExemptions, CARE_DEFAULTS, calendarRetimer, careDuty, commitmentPressure, createAgenda, DEFAULT_PRAYER_TIMES, EID_PRAYER_IMPORTANCE, EXEMPTION_DEFAULTS, eidPrayer, eidWindow, fastWindow, iftarWindow, inMakruhTime, isContinuingCommitment, isUnderWay, MAKRUH_DEFAULTS, makruhWindows, missedExcuse, onFinished, owedMakeUps, PRAYER_IMPORTANCE, PURPOSE_DEFAULTS, prayerWindows, pressureReachedAt, promise, proposeGoals, RAMADAN_DEFAULTS, ramadanFast, ramadanMeals, release as releaseCommitment, retimeCommitments, retirePaidMakeUps, revisePurposes, scheduleMakeUp, spanMeetsWindow, suhoorWindow, timesFor, violatesAbstention, } from './agenda/index.js';
6
6
  export * from './beliefs/index.js';
7
7
  export * from './body/index.js';
8
8
  export * from './character/index.js';
@@ -27,6 +27,8 @@ export * from './needs/index.js';
27
27
  export * from './partnering/index.js';
28
28
  export { enablePersistence, PERSISTENCE_DEFAULTS, pursuitOf } from './persistence/index.js';
29
29
  export * from './person.js';
30
+ export * from './persona.js';
31
+ export * from './persona-calendar.js';
30
32
  export { considerProjects, enableProjects, PROJECTS_DEFAULTS, projectAppeal } from './projects/index.js';
31
33
  export * from './promptings/index.js';
32
34
  export { bodyForecastFor, bodyForecastTerm, consequenceEvidence, enablePlanning, FORECAST_DEFAULTS, forecastLine, forecastThresholds, PROSPECTION_DEFAULTS, seedConsequences, } from './prospection/index.js';
@@ -0,0 +1,39 @@
1
+ import { type PersonaAdmission, type PersonaAdmissionResult, type PersonaProjection, type PersonaProjectionSnapshot } from './persona.ts';
2
+ import { type Commitment, type HeldNorm, type NormDefinition, type OwedMakeUp, type Person } from './types.ts';
3
+ export interface PersonaCalendarMap {
4
+ version: 'hf-persona-calendar-map/1';
5
+ /** Exactly target opening minus source departure. All retained calendar timestamps use this offset. */
6
+ offset: number;
7
+ references: {
8
+ commitments: Record<string, string>;
9
+ chains: Record<string, string>;
10
+ actions: Record<string, string>;
11
+ entities: Record<string, string>;
12
+ norms: Record<string, string>;
13
+ };
14
+ sourceNorms: NormDefinition[];
15
+ }
16
+ export interface CalendarPersonaProjection extends Omit<PersonaProjection, 'commitments'> {
17
+ calendar: PersonaCalendarMap;
18
+ }
19
+ export interface CalendarPersonaSnapshot {
20
+ format: 'hf-persona-calendar/1';
21
+ person: PersonaProjectionSnapshot;
22
+ calendar: {
23
+ offset: number;
24
+ commitments: Commitment[];
25
+ owed: OwedMakeUp[];
26
+ lapse: Person['agenda']['lapse'] | null;
27
+ heldNorms: HeldNorm[];
28
+ norms: NormDefinition[];
29
+ };
30
+ }
31
+ export interface CalendarPersonaAdmission extends PersonaAdmission {
32
+ targetNorms: NormDefinition[];
33
+ }
34
+ /** Validate portable calendar data strictly; no restore defaults or debt settlement. */
35
+ export declare function validateCalendarPersona(input: unknown): CalendarPersonaSnapshot;
36
+ /** Source declares a complete semantic ID map, a clock offset and source-backed norm catalog. */
37
+ export declare function projectCalendarPersona(source: Person, mapping: CalendarPersonaProjection): CalendarPersonaSnapshot;
38
+ /** Receiver declares identical norm meanings and a clean calendar at the exact declared opening clock. */
39
+ export declare function admitCalendarPersona(input: unknown, target: Person, policy: CalendarPersonaAdmission): PersonaAdmissionResult;