holonovel 2026.9.10 → 2026.9.27
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 +95 -0
- package/dist/core/agent.d.ts +25 -0
- package/dist/core/agent.js +48 -0
- package/dist/core/agent.js.map +1 -0
- package/dist/core/belief.d.ts +39 -0
- package/dist/core/belief.js +113 -0
- package/dist/core/belief.js.map +1 -0
- package/dist/core/causal.d.ts +49 -0
- package/dist/core/causal.js +79 -0
- package/dist/core/causal.js.map +1 -0
- package/dist/core/corpus.d.ts +33 -0
- package/dist/core/corpus.js +43 -0
- package/dist/core/corpus.js.map +1 -0
- package/dist/core/event-log.d.ts +23 -0
- package/dist/core/event-log.js +58 -0
- package/dist/core/event-log.js.map +1 -0
- package/dist/core/graph.d.ts +55 -0
- package/dist/core/graph.js +83 -0
- package/dist/core/graph.js.map +1 -0
- package/dist/core/identity.d.ts +45 -0
- package/dist/core/identity.js +79 -0
- package/dist/core/identity.js.map +1 -0
- package/dist/core/limits.d.ts +12 -0
- package/dist/core/limits.js +41 -0
- package/dist/core/limits.js.map +1 -0
- package/dist/core/perception.d.ts +11 -0
- package/dist/core/perception.js +15 -0
- package/dist/core/perception.js.map +1 -0
- package/dist/core/semantic.d.ts +52 -0
- package/dist/core/semantic.js +109 -0
- package/dist/core/semantic.js.map +1 -0
- package/dist/core/state.d.ts +63 -4
- package/dist/core/state.js +217 -14
- package/dist/core/state.js.map +1 -1
- package/dist/core/wisdom.d.ts +5 -0
- package/dist/core/wisdom.js +38 -0
- package/dist/core/wisdom.js.map +1 -1
- package/dist/generated/contract-fingerprints.d.ts +2 -2
- package/dist/generated/contract-fingerprints.js +2 -2
- package/dist/index.js +2910 -515
- package/dist/index.js.map +1 -1
- package/dist/rulesets.d.ts +1 -0
- package/dist/rulesets.js +31 -2
- package/dist/rulesets.js.map +1 -1
- package/package.json +17 -2
package/README.md
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# holonovel
|
|
2
|
+
|
|
3
|
+
**The Holodeck for your rulebooks — rules enforced, worlds remembered.**
|
|
4
|
+
|
|
5
|
+
A world-model MCP server for tabletop RPG play: rooms, things, exits, a
|
|
6
|
+
parser command layer, and narrative tools, with out-of-the-box mechanics from
|
|
7
|
+
Fate, Ironsworn, and Blades in the Dark (Fudge dice, momentum, and stress
|
|
8
|
+
tracks — no ruleset required). Install it, then drop in any number of ruleset
|
|
9
|
+
packages; each loads alongside the base and never modifies it.
|
|
10
|
+
|
|
11
|
+
## Requirements
|
|
12
|
+
|
|
13
|
+
- Node.js 20+
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
npm install
|
|
19
|
+
npm run start
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Or run the published package:
|
|
23
|
+
|
|
24
|
+
```sh
|
|
25
|
+
npx -y holonovel
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## MCP client configuration
|
|
29
|
+
|
|
30
|
+
```json
|
|
31
|
+
"holonovel": {
|
|
32
|
+
"type": "local",
|
|
33
|
+
"command": ["npx", "tsx", "src/index.ts"],
|
|
34
|
+
"cwd": "<path>/holonovel",
|
|
35
|
+
"environment": {
|
|
36
|
+
"TTRPG_NOVEL": "default"
|
|
37
|
+
},
|
|
38
|
+
"enabled": true
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Tool surface
|
|
43
|
+
|
|
44
|
+
The host exposes 34 action-discriminator tools — one per persisted entity
|
|
45
|
+
type — plus a parser command tool and three base-mechanics resolvers. Every
|
|
46
|
+
tool carries all four MCP mutation-class hints and a three-clause description
|
|
47
|
+
(see `AGENTS.md` for the full surface and the tool-definition authoring
|
|
48
|
+
standard).
|
|
49
|
+
|
|
50
|
+
Categories:
|
|
51
|
+
|
|
52
|
+
- **Badges & Workflow** — badge switching, workflow response, undo/redo
|
|
53
|
+
- **Characters & Cast** — player characters, NPCs, identity, beliefs
|
|
54
|
+
- **World & Scene** — world model, scene framing, perception, causal state
|
|
55
|
+
- **Narrative** — story journal, lore, notes, vows, factions, countdowns
|
|
56
|
+
- **Session** — recap, health, discovery, event log, audit
|
|
57
|
+
- **Mechanics** — Fate, Ironsworn, and Forged in the Dark resolvers
|
|
58
|
+
- **Content** — adventures, codex, ruleset packages, synthesis
|
|
59
|
+
|
|
60
|
+
## Configuration
|
|
61
|
+
|
|
62
|
+
Behavioral switches read from the environment; every switch is inert by
|
|
63
|
+
default. Common values:
|
|
64
|
+
|
|
65
|
+
| Variable | Purpose |
|
|
66
|
+
|---|---|
|
|
67
|
+
| `TTRPG_DATA_DIR` | State directory (default: `.holonovel-state`) |
|
|
68
|
+
| `TTRPG_NOVEL` | Novel to activate at startup |
|
|
69
|
+
| `TTRPG_SEED` | Deterministic PRNG seed |
|
|
70
|
+
| `TTRPG_RULESETS` | Ruleset slugs to load eagerly |
|
|
71
|
+
| `TTRPG_RULESET_DIRS` | Ruleset install directories |
|
|
72
|
+
| `TTRPG_NPC_AUTONOMY`, `TTRPG_NPC_MIND` | NPC autonomy behaviors |
|
|
73
|
+
| `TTRPG_WORLD_REACTIVITY` | World-in-Motion reactivity (default on) |
|
|
74
|
+
| `TTRPG_NARRATION_VALIDATION` | Narration grounding validation |
|
|
75
|
+
| `TTRPG_GUIDANCE_PROFILE` | `full` or `lean` guidance |
|
|
76
|
+
|
|
77
|
+
## State
|
|
78
|
+
|
|
79
|
+
A Novel is a structured save file under `.holonovel-state/novels/<slug>.json`
|
|
80
|
+
with atomic saves, per-badge undo/redo snapshots, and an append-only audit
|
|
81
|
+
log. Campaign data and installed packages live under `.holonovel-state/`,
|
|
82
|
+
outside the server tree, so updating holonovel never touches them.
|
|
83
|
+
|
|
84
|
+
## Development
|
|
85
|
+
|
|
86
|
+
```sh
|
|
87
|
+
npm run typecheck # TypeScript type checking
|
|
88
|
+
npm run test:all # verification harnesses
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Tests are `tsx` harnesses under `scripts/` (there is no `test/` directory).
|
|
92
|
+
|
|
93
|
+
## License
|
|
94
|
+
|
|
95
|
+
MIT. See `LICENSE.md`.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export type AgentTaskStatus = "queued" | "active" | "done" | "failed" | "cancelled";
|
|
2
|
+
export type AgentAutonomy = "advisory" | "prompt" | "auto";
|
|
3
|
+
export interface AgentAction {
|
|
4
|
+
seq: number;
|
|
5
|
+
description: string;
|
|
6
|
+
at: string;
|
|
7
|
+
}
|
|
8
|
+
export interface AgentTask {
|
|
9
|
+
id: string;
|
|
10
|
+
subject: string;
|
|
11
|
+
goal: string;
|
|
12
|
+
status: AgentTaskStatus;
|
|
13
|
+
autonomy: AgentAutonomy;
|
|
14
|
+
actions: AgentAction[];
|
|
15
|
+
source_goal?: string;
|
|
16
|
+
created_at: string;
|
|
17
|
+
updated_at: string;
|
|
18
|
+
}
|
|
19
|
+
export declare const AUTONOMY_LEVELS: AgentAutonomy[];
|
|
20
|
+
export declare function isTerminal(status: AgentTaskStatus): boolean;
|
|
21
|
+
export declare function canTransition(from: AgentTaskStatus, to: AgentTaskStatus): boolean;
|
|
22
|
+
export declare function transition(task: AgentTask, to: AgentTaskStatus, at: string): boolean;
|
|
23
|
+
export declare function recordAction(task: AgentTask, description: string, at: string): AgentAction;
|
|
24
|
+
export declare function nextTaskId(tasks: AgentTask[]): string;
|
|
25
|
+
export declare function isMutable(task: AgentTask): boolean;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// Durable Agent Tasks — a persistent task and action lifecycle for NPCs and
|
|
2
|
+
// entities, with an autonomy policy. Tasks are durable Novel state; only
|
|
3
|
+
// admitted lifecycle transitions are permitted.
|
|
4
|
+
// REQ-522, REQ-523, REQ-524, REQ-525, REQ-526, REQ-527, REQ-528, REQ-529, REQ-530
|
|
5
|
+
export const AUTONOMY_LEVELS = ["advisory", "prompt", "auto"];
|
|
6
|
+
// REQ-524 — only these lifecycle transitions are permitted.
|
|
7
|
+
const TRANSITIONS = {
|
|
8
|
+
queued: ["active", "cancelled"],
|
|
9
|
+
active: ["done", "failed", "cancelled"],
|
|
10
|
+
done: [],
|
|
11
|
+
failed: [],
|
|
12
|
+
cancelled: [],
|
|
13
|
+
};
|
|
14
|
+
export function isTerminal(status) {
|
|
15
|
+
return TRANSITIONS[status].length === 0;
|
|
16
|
+
}
|
|
17
|
+
export function canTransition(from, to) {
|
|
18
|
+
return TRANSITIONS[from].includes(to);
|
|
19
|
+
}
|
|
20
|
+
// REQ-522/REQ-523/REQ-524 — apply a lifecycle transition; returns false if illegal.
|
|
21
|
+
export function transition(task, to, at) {
|
|
22
|
+
if (!canTransition(task.status, to))
|
|
23
|
+
return false;
|
|
24
|
+
task.status = to;
|
|
25
|
+
task.updated_at = at;
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
// REQ-525 — append an action to the task's log.
|
|
29
|
+
export function recordAction(task, description, at) {
|
|
30
|
+
const action = { seq: task.actions.length + 1, description, at };
|
|
31
|
+
task.actions.push(action);
|
|
32
|
+
task.updated_at = at;
|
|
33
|
+
return action;
|
|
34
|
+
}
|
|
35
|
+
export function nextTaskId(tasks) {
|
|
36
|
+
let max = 0;
|
|
37
|
+
for (const t of tasks) {
|
|
38
|
+
const m = t.id.match(/^task-(\d+)$/);
|
|
39
|
+
if (m)
|
|
40
|
+
max = Math.max(max, parseInt(m[1], 10));
|
|
41
|
+
}
|
|
42
|
+
return `task-${max + 1}`;
|
|
43
|
+
}
|
|
44
|
+
// REQ-528 — a terminal task is immutable; only actions may still be appended.
|
|
45
|
+
export function isMutable(task) {
|
|
46
|
+
return !isTerminal(task.status);
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=agent.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent.js","sourceRoot":"","sources":["../../src/core/agent.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,yEAAyE;AACzE,gDAAgD;AAChD,kFAAkF;AAuBlF,MAAM,CAAC,MAAM,eAAe,GAAoB,CAAC,UAAU,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;AAE/E,4DAA4D;AAC5D,MAAM,WAAW,GAA+C;IAC9D,MAAM,EAAE,CAAC,QAAQ,EAAE,WAAW,CAAC;IAC/B,MAAM,EAAE,CAAC,MAAM,EAAE,QAAQ,EAAE,WAAW,CAAC;IACvC,IAAI,EAAE,EAAE;IACR,MAAM,EAAE,EAAE;IACV,SAAS,EAAE,EAAE;CACd,CAAC;AAEF,MAAM,UAAU,UAAU,CAAC,MAAuB;IAChD,OAAO,WAAW,CAAC,MAAM,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC;AAC1C,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,IAAqB,EAAE,EAAmB;IACtE,OAAO,WAAW,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;AACxC,CAAC;AAED,oFAAoF;AACpF,MAAM,UAAU,UAAU,CAAC,IAAe,EAAE,EAAmB,EAAE,EAAU;IACzE,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC;QAAE,OAAO,KAAK,CAAC;IAClD,IAAI,CAAC,MAAM,GAAG,EAAE,CAAC;IACjB,IAAI,CAAC,UAAU,GAAG,EAAE,CAAC;IACrB,OAAO,IAAI,CAAC;AACd,CAAC;AAED,gDAAgD;AAChD,MAAM,UAAU,YAAY,CAAC,IAAe,EAAE,WAAmB,EAAE,EAAU;IAC3E,MAAM,MAAM,GAAgB,EAAE,GAAG,EAAE,IAAI,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,WAAW,EAAE,EAAE,EAAE,CAAC;IAC9E,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC1B,IAAI,CAAC,UAAU,GAAG,EAAE,CAAC;IACrB,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,KAAkB;IAC3C,IAAI,GAAG,GAAG,CAAC,CAAC;IACZ,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;QACtB,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC;QACrC,IAAI,CAAC;YAAE,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;IACjD,CAAC;IACD,OAAO,QAAQ,GAAG,GAAG,CAAC,EAAE,CAAC;AAC3B,CAAC;AAED,8EAA8E;AAC9E,MAAM,UAAU,SAAS,CAAC,IAAe;IACvC,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;AAClC,CAAC"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export type EvidenceStatus = "active" | "unresolved" | "suppressed";
|
|
2
|
+
export type Polarity = "positive" | "negative";
|
|
3
|
+
export type BeliefStance = "positive" | "negative" | "unresolved";
|
|
4
|
+
export interface EvidenceRecord {
|
|
5
|
+
id: string;
|
|
6
|
+
entity_id: string;
|
|
7
|
+
subject: string;
|
|
8
|
+
predicate: string;
|
|
9
|
+
object: string;
|
|
10
|
+
polarity: Polarity;
|
|
11
|
+
status: EvidenceStatus;
|
|
12
|
+
weight: number;
|
|
13
|
+
source: string;
|
|
14
|
+
source_ordinals: number[];
|
|
15
|
+
at: string;
|
|
16
|
+
}
|
|
17
|
+
export interface BeliefRecord {
|
|
18
|
+
entity_id: string;
|
|
19
|
+
question: string;
|
|
20
|
+
subject: string;
|
|
21
|
+
predicate: string;
|
|
22
|
+
object: string;
|
|
23
|
+
stance: BeliefStance;
|
|
24
|
+
support: number;
|
|
25
|
+
opposition: number;
|
|
26
|
+
accepted: boolean;
|
|
27
|
+
family: "durable" | "volatile" | "single_value";
|
|
28
|
+
current: boolean;
|
|
29
|
+
evidence_ids: string[];
|
|
30
|
+
at: string;
|
|
31
|
+
}
|
|
32
|
+
export declare function normalizeToken(value: string): string;
|
|
33
|
+
export declare function questionKey(subject: string, predicate: string, object: string): string;
|
|
34
|
+
export declare function beliefFamily(predicate: string): BeliefRecord["family"];
|
|
35
|
+
export interface ReconcileOptions {
|
|
36
|
+
acceptThreshold: number;
|
|
37
|
+
decisionMargin: number;
|
|
38
|
+
}
|
|
39
|
+
export declare function reconcileEntity(entityId: string, evidence: EvidenceRecord[], opts: ReconcileOptions, at: string): BeliefRecord[];
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// Belief & Evidence — deterministic per-entity reconciliation of admitted
|
|
2
|
+
// evidence into belief stances, preserving all evidence and disagreement.
|
|
3
|
+
// REQ-461, REQ-462, REQ-463, REQ-464, REQ-465, REQ-466, REQ-467, REQ-468,
|
|
4
|
+
// REQ-469, REQ-470, REQ-471, REQ-472
|
|
5
|
+
// REQ-469 — predicate family policy. Deterministic default mapping; a ruleset
|
|
6
|
+
// or builder may extend it, but the mapping used is always reported.
|
|
7
|
+
const VOLATILE_PREDICATES = new Set(["status", "mood", "location", "condition", "activity"]);
|
|
8
|
+
const SINGLE_VALUE_PREDICATES = new Set(["location", "wearing", "holding", "carrying"]);
|
|
9
|
+
export function normalizeToken(value) {
|
|
10
|
+
return value.trim().toLowerCase();
|
|
11
|
+
}
|
|
12
|
+
// REQ-463 — polarity-independent question identity.
|
|
13
|
+
export function questionKey(subject, predicate, object) {
|
|
14
|
+
return `${normalizeToken(subject)}|${normalizeToken(predicate)}|${normalizeToken(object)}`;
|
|
15
|
+
}
|
|
16
|
+
export function beliefFamily(predicate) {
|
|
17
|
+
const p = normalizeToken(predicate);
|
|
18
|
+
if (SINGLE_VALUE_PREDICATES.has(p))
|
|
19
|
+
return "single_value";
|
|
20
|
+
if (VOLATILE_PREDICATES.has(p))
|
|
21
|
+
return "volatile";
|
|
22
|
+
return "durable";
|
|
23
|
+
}
|
|
24
|
+
// REQ-466 — noisy-OR over independent sources; evidence sharing a source key is
|
|
25
|
+
// correlated and does not compound.
|
|
26
|
+
function sideSupport(records) {
|
|
27
|
+
const bySource = new Map();
|
|
28
|
+
for (const r of records) {
|
|
29
|
+
const key = r.source || "unknown";
|
|
30
|
+
bySource.set(key, Math.max(bySource.get(key) ?? 0, r.weight));
|
|
31
|
+
}
|
|
32
|
+
let product = 1;
|
|
33
|
+
for (const w of bySource.values())
|
|
34
|
+
product *= 1 - w;
|
|
35
|
+
return 1 - product;
|
|
36
|
+
}
|
|
37
|
+
function activeFor(records) {
|
|
38
|
+
return records.filter((r) => r.status === "active");
|
|
39
|
+
}
|
|
40
|
+
// REQ-468/REQ-465/REQ-469/REQ-470 — recompute every question for one entity.
|
|
41
|
+
export function reconcileEntity(entityId, evidence, opts, at) {
|
|
42
|
+
const mine = activeFor(evidence).filter((e) => e.entity_id === entityId);
|
|
43
|
+
const questions = new Map();
|
|
44
|
+
for (const e of mine) {
|
|
45
|
+
const key = questionKey(e.subject, e.predicate, e.object);
|
|
46
|
+
const list = questions.get(key) ?? [];
|
|
47
|
+
list.push(e);
|
|
48
|
+
questions.set(key, list);
|
|
49
|
+
}
|
|
50
|
+
const records = [];
|
|
51
|
+
for (const [key, list] of questions) {
|
|
52
|
+
const first = list[0];
|
|
53
|
+
const family = beliefFamily(first.predicate);
|
|
54
|
+
const positive = list.filter((e) => e.polarity === "positive");
|
|
55
|
+
const negative = list.filter((e) => e.polarity === "negative");
|
|
56
|
+
let support;
|
|
57
|
+
let opposition;
|
|
58
|
+
let stance;
|
|
59
|
+
if (family === "volatile") {
|
|
60
|
+
// REQ-469 — most recent acquisition wins; ties go to the later id.
|
|
61
|
+
const latest = [...list].sort((a, b) => (a.at < b.at ? -1 : a.at > b.at ? 1 : a.id.localeCompare(b.id))).pop();
|
|
62
|
+
support = latest.polarity === "positive" ? latest.weight : 0;
|
|
63
|
+
opposition = latest.polarity === "negative" ? latest.weight : 0;
|
|
64
|
+
stance = latest.weight >= opts.acceptThreshold ? latest.polarity : "unresolved";
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
support = sideSupport(positive);
|
|
68
|
+
opposition = sideSupport(negative);
|
|
69
|
+
if (support >= opts.acceptThreshold && support - opposition >= opts.decisionMargin)
|
|
70
|
+
stance = "positive";
|
|
71
|
+
else if (opposition >= opts.acceptThreshold && opposition - support >= opts.decisionMargin)
|
|
72
|
+
stance = "negative";
|
|
73
|
+
else
|
|
74
|
+
stance = "unresolved";
|
|
75
|
+
}
|
|
76
|
+
records.push({
|
|
77
|
+
entity_id: entityId,
|
|
78
|
+
question: key,
|
|
79
|
+
subject: first.subject,
|
|
80
|
+
predicate: first.predicate,
|
|
81
|
+
object: first.object,
|
|
82
|
+
stance,
|
|
83
|
+
support: Number(support.toFixed(4)),
|
|
84
|
+
opposition: Number(opposition.toFixed(4)),
|
|
85
|
+
accepted: stance !== "unresolved",
|
|
86
|
+
family,
|
|
87
|
+
current: family !== "single_value",
|
|
88
|
+
evidence_ids: list.map((e) => e.id),
|
|
89
|
+
at,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
// REQ-470 — single-value predicates: only the highest-support object for a
|
|
93
|
+
// subject+predicate is current; a tie-contested slot yields no current value.
|
|
94
|
+
const slots = new Map();
|
|
95
|
+
for (const r of records) {
|
|
96
|
+
if (r.family !== "single_value")
|
|
97
|
+
continue;
|
|
98
|
+
const slot = `${normalizeToken(r.subject)}|${normalizeToken(r.predicate)}`;
|
|
99
|
+
const group = slots.get(slot) ?? [];
|
|
100
|
+
group.push(r);
|
|
101
|
+
slots.set(slot, group);
|
|
102
|
+
}
|
|
103
|
+
for (const group of slots.values()) {
|
|
104
|
+
const accepted = group.filter((r) => r.accepted);
|
|
105
|
+
const ranked = [...accepted].sort((a, b) => b.support - a.support);
|
|
106
|
+
const top = ranked[0];
|
|
107
|
+
const tie = top && ranked.filter((r) => Math.abs(r.support - top.support) < opts.decisionMargin).length > 1;
|
|
108
|
+
for (const r of group)
|
|
109
|
+
r.current = !!top && !tie && r === top;
|
|
110
|
+
}
|
|
111
|
+
return records.sort((a, b) => a.question.localeCompare(b.question));
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=belief.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"belief.js","sourceRoot":"","sources":["../../src/core/belief.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
export type AdmissionDecision = "admitted" | "admitted_with_latent_transition" | "rejected_impossible" | "conflict" | "underdetermined" | "fork_required" | "epistemic_only";
|
|
2
|
+
export type CausalDomain = "location" | "scalar";
|
|
3
|
+
export type OriginSource = "narrative" | "machine" | "ruleset";
|
|
4
|
+
export type CausalValue = string | number | boolean | null;
|
|
5
|
+
export interface TransitionProposal {
|
|
6
|
+
id: string;
|
|
7
|
+
scope: string;
|
|
8
|
+
domain: CausalDomain;
|
|
9
|
+
entity: string;
|
|
10
|
+
key: string;
|
|
11
|
+
value: CausalValue;
|
|
12
|
+
from?: CausalValue;
|
|
13
|
+
expected_version?: number;
|
|
14
|
+
origin_source: OriginSource;
|
|
15
|
+
source_ordinal: number | null;
|
|
16
|
+
at: string;
|
|
17
|
+
}
|
|
18
|
+
export interface TransitionRecord extends TransitionProposal {
|
|
19
|
+
decision: AdmissionDecision;
|
|
20
|
+
reason: string;
|
|
21
|
+
}
|
|
22
|
+
export interface CausalSlot {
|
|
23
|
+
scope: string;
|
|
24
|
+
domain: CausalDomain;
|
|
25
|
+
entity: string;
|
|
26
|
+
key: string;
|
|
27
|
+
value: CausalValue;
|
|
28
|
+
version: number;
|
|
29
|
+
at: string;
|
|
30
|
+
}
|
|
31
|
+
export interface CausalOptions {
|
|
32
|
+
scope: string;
|
|
33
|
+
latentTransitions: boolean;
|
|
34
|
+
}
|
|
35
|
+
export declare function slotKey(s: {
|
|
36
|
+
scope: string;
|
|
37
|
+
domain: CausalDomain;
|
|
38
|
+
entity: string;
|
|
39
|
+
key: string;
|
|
40
|
+
}): string;
|
|
41
|
+
export declare function findSlot(slots: CausalSlot[], p: TransitionProposal): CausalSlot | undefined;
|
|
42
|
+
export declare function evaluate(p: TransitionProposal, slots: CausalSlot[], opts: CausalOptions): {
|
|
43
|
+
decision: AdmissionDecision;
|
|
44
|
+
reason: string;
|
|
45
|
+
};
|
|
46
|
+
export declare function applySlot(slots: CausalSlot[], p: TransitionProposal, at: string): boolean;
|
|
47
|
+
export declare function nextProposalId(ledger: TransitionRecord[]): string;
|
|
48
|
+
export declare function isProvisional(decision: AdmissionDecision): boolean;
|
|
49
|
+
export declare const CAUSAL_DOMAINS: CausalDomain[];
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// Causal Transition Validation — decides whether a proposed objective-state
|
|
2
|
+
// transition can coexist with the committed history of one scope, preserving
|
|
3
|
+
// every proposal (admitted or not) as auditable evidence.
|
|
4
|
+
// REQ-484, REQ-485, REQ-486, REQ-487, REQ-488, REQ-489, REQ-490, REQ-491,
|
|
5
|
+
// REQ-492, REQ-493, REQ-494, REQ-495
|
|
6
|
+
export function slotKey(s) {
|
|
7
|
+
return `${s.scope}|${s.domain}|${s.entity}|${s.key}`;
|
|
8
|
+
}
|
|
9
|
+
export function findSlot(slots, p) {
|
|
10
|
+
const k = slotKey(p);
|
|
11
|
+
return slots.find((s) => slotKey(s) === k);
|
|
12
|
+
}
|
|
13
|
+
// REQ-485 — the admission decision; REQ-486–REQ-490 set the outcome.
|
|
14
|
+
export function evaluate(p, slots, opts) {
|
|
15
|
+
// REQ-488 — a proposal must co-reside on the active scope coordinate.
|
|
16
|
+
if (!p.scope)
|
|
17
|
+
return { decision: "rejected_impossible", reason: "missing scope coordinate" };
|
|
18
|
+
if (p.scope !== opts.scope) {
|
|
19
|
+
return { decision: "rejected_impossible", reason: `scope coordinate mismatch: '${p.scope}' is not the active scope '${opts.scope}'` };
|
|
20
|
+
}
|
|
21
|
+
const slot = findSlot(slots, p);
|
|
22
|
+
if (!slot)
|
|
23
|
+
return { decision: "admitted", reason: "first write at this coordinate" };
|
|
24
|
+
// REQ-489 — optimistic version guard.
|
|
25
|
+
if (p.expected_version !== undefined && p.expected_version !== slot.version) {
|
|
26
|
+
return { decision: "conflict", reason: `stale expected version ${p.expected_version} (current ${slot.version})` };
|
|
27
|
+
}
|
|
28
|
+
// REQ-489 — idempotent when the value is unchanged.
|
|
29
|
+
if (slot.value === p.value)
|
|
30
|
+
return { decision: "admitted", reason: "idempotent — value unchanged" };
|
|
31
|
+
// REQ-486 — an incompatible prior value is a conflict unless a latent transition is allowed.
|
|
32
|
+
if (p.from !== undefined && p.from !== slot.value) {
|
|
33
|
+
return opts.latentTransitions
|
|
34
|
+
? { decision: "admitted_with_latent_transition", reason: "incompatible prior value; latent transition admitted" }
|
|
35
|
+
: { decision: "conflict", reason: "incompatible prior value" };
|
|
36
|
+
}
|
|
37
|
+
// REQ-487 — ordered scalar state admits a later value, refuses an incompatible one.
|
|
38
|
+
if (p.domain === "scalar" && typeof slot.value === "number" && typeof p.value === "number") {
|
|
39
|
+
return p.value >= slot.value
|
|
40
|
+
? { decision: "admitted", reason: "ordered scalar advance" }
|
|
41
|
+
: { decision: "conflict", reason: "concurrent incompatible scalar value" };
|
|
42
|
+
}
|
|
43
|
+
// A continuous transition (from matches) is admitted; an unconstrained write is not.
|
|
44
|
+
if (p.from !== undefined && p.from === slot.value) {
|
|
45
|
+
return { decision: "admitted", reason: "continuous transition from prior value" };
|
|
46
|
+
}
|
|
47
|
+
return opts.latentTransitions
|
|
48
|
+
? { decision: "admitted_with_latent_transition", reason: "unconstrained incompatible write; latent transition admitted" }
|
|
49
|
+
: { decision: "conflict", reason: "unconstrained incompatible write" };
|
|
50
|
+
}
|
|
51
|
+
// REQ-489 — apply an admitted value; returns false (no version bump) when idempotent.
|
|
52
|
+
export function applySlot(slots, p, at) {
|
|
53
|
+
const idx = slots.findIndex((s) => slotKey(s) === slotKey(p));
|
|
54
|
+
if (idx === -1) {
|
|
55
|
+
slots.push({ scope: p.scope, domain: p.domain, entity: p.entity, key: p.key, value: p.value, version: 1, at });
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
if (slots[idx].value === p.value)
|
|
59
|
+
return false;
|
|
60
|
+
slots[idx] = { ...slots[idx], value: p.value, version: slots[idx].version + 1, at };
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
export function nextProposalId(ledger) {
|
|
64
|
+
let max = 0;
|
|
65
|
+
for (const r of ledger) {
|
|
66
|
+
const m = r.id.match(/^tp-(\d+)$/);
|
|
67
|
+
if (m)
|
|
68
|
+
max = Math.max(max, parseInt(m[1], 10));
|
|
69
|
+
}
|
|
70
|
+
return `tp-${max + 1}`;
|
|
71
|
+
}
|
|
72
|
+
// REQ-490 — a latent transition is provisional: it is applied but flagged.
|
|
73
|
+
export function isProvisional(decision) {
|
|
74
|
+
return decision === "admitted_with_latent_transition";
|
|
75
|
+
}
|
|
76
|
+
// REQ-492 — the causal layer never writes epistemic or identity state. This
|
|
77
|
+
// module has no access to those stores; the firewall is structural.
|
|
78
|
+
export const CAUSAL_DOMAINS = ["location", "scalar"];
|
|
79
|
+
//# sourceMappingURL=causal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"causal.js","sourceRoot":"","sources":["../../src/core/causal.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export type ConsumptionMode = "read" | "research" | "taught" | "import";
|
|
2
|
+
export interface CorpusDocument {
|
|
3
|
+
id: string;
|
|
4
|
+
title: string;
|
|
5
|
+
domain: string;
|
|
6
|
+
body: string;
|
|
7
|
+
source_profile: string;
|
|
8
|
+
access_public: boolean;
|
|
9
|
+
access_domains: string[];
|
|
10
|
+
access_grants: string[];
|
|
11
|
+
access_denies: string[];
|
|
12
|
+
registered_at: string;
|
|
13
|
+
}
|
|
14
|
+
export interface CorpusAccess {
|
|
15
|
+
entity_id: string;
|
|
16
|
+
domains: string[];
|
|
17
|
+
}
|
|
18
|
+
export interface CorpusConsumption {
|
|
19
|
+
id: string;
|
|
20
|
+
entity_id: string;
|
|
21
|
+
document_id: string;
|
|
22
|
+
domain: string;
|
|
23
|
+
mode: ConsumptionMode;
|
|
24
|
+
deixis: "unresolved" | "resolved";
|
|
25
|
+
excerpt: string;
|
|
26
|
+
at: string;
|
|
27
|
+
}
|
|
28
|
+
export declare const CONSUMPTION_MODES: ConsumptionMode[];
|
|
29
|
+
export declare function deixisOf(body: string): "unresolved" | "resolved";
|
|
30
|
+
export declare function canAccess(doc: CorpusDocument, entityId: string, access: CorpusAccess[]): boolean;
|
|
31
|
+
export declare function nextDocumentId(docs: CorpusDocument[]): string;
|
|
32
|
+
export declare function nextConsumptionId(records: CorpusConsumption[]): string;
|
|
33
|
+
export declare function excerptOf(body: string, limit?: number): string;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// Knowledge Corpus — cold reference material that stays quarantined until an
|
|
2
|
+
// entity explicitly consumes it, then records exactly what was acquired.
|
|
3
|
+
// REQ-496, REQ-497, REQ-498, REQ-499, REQ-500, REQ-501, REQ-502, REQ-503
|
|
4
|
+
export const CONSUMPTION_MODES = ["read", "research", "taught", "import"];
|
|
5
|
+
const DEIXIS_RE = /\b(i|you|my|your|me|we|our)\b/i;
|
|
6
|
+
// REQ-502 — first/second-person reference material cannot silently become
|
|
7
|
+
// self-knowledge: consumption records the deixis as unresolved.
|
|
8
|
+
export function deixisOf(body) {
|
|
9
|
+
return DEIXIS_RE.test(body) ? "unresolved" : "resolved";
|
|
10
|
+
}
|
|
11
|
+
// REQ-498 — public, OR domain match, OR explicit grant, minus explicit deny.
|
|
12
|
+
export function canAccess(doc, entityId, access) {
|
|
13
|
+
if (doc.access_denies.includes(entityId))
|
|
14
|
+
return false;
|
|
15
|
+
if (doc.access_public)
|
|
16
|
+
return true;
|
|
17
|
+
if (doc.access_grants.includes(entityId))
|
|
18
|
+
return true;
|
|
19
|
+
const profile = access.find((a) => a.entity_id === entityId);
|
|
20
|
+
return !!profile && profile.domains.includes(doc.domain);
|
|
21
|
+
}
|
|
22
|
+
export function nextDocumentId(docs) {
|
|
23
|
+
let max = 0;
|
|
24
|
+
for (const d of docs) {
|
|
25
|
+
const m = d.id.match(/^doc-(\d+)$/);
|
|
26
|
+
if (m)
|
|
27
|
+
max = Math.max(max, parseInt(m[1], 10));
|
|
28
|
+
}
|
|
29
|
+
return `doc-${max + 1}`;
|
|
30
|
+
}
|
|
31
|
+
export function nextConsumptionId(records) {
|
|
32
|
+
let max = 0;
|
|
33
|
+
for (const r of records) {
|
|
34
|
+
const m = r.id.match(/^cc-(\d+)$/);
|
|
35
|
+
if (m)
|
|
36
|
+
max = Math.max(max, parseInt(m[1], 10));
|
|
37
|
+
}
|
|
38
|
+
return `cc-${max + 1}`;
|
|
39
|
+
}
|
|
40
|
+
export function excerptOf(body, limit = 200) {
|
|
41
|
+
return body.length > limit ? body.slice(0, limit) : body;
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=corpus.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"corpus.js","sourceRoot":"","sources":["../../src/core/corpus.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,yEAAyE;AACzE,yEAAyE;AAuCzE,MAAM,CAAC,MAAM,iBAAiB,GAAsB,CAAC,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC;AAE7F,MAAM,SAAS,GAAG,gCAAgC,CAAC;AAEnD,0EAA0E;AAC1E,gEAAgE;AAChE,MAAM,UAAU,QAAQ,CAAC,IAAY;IACnC,OAAO,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,UAAU,CAAC;AAC1D,CAAC;AAED,6EAA6E;AAC7E,MAAM,UAAU,SAAS,CAAC,GAAmB,EAAE,QAAgB,EAAE,MAAsB;IACrF,IAAI,GAAG,CAAC,aAAa,CAAC,QAAQ,CAAC,QAAQ,CAAC;QAAE,OAAO,KAAK,CAAC;IACvD,IAAI,GAAG,CAAC,aAAa;QAAE,OAAO,IAAI,CAAC;IACnC,IAAI,GAAG,CAAC,aAAa,CAAC,QAAQ,CAAC,QAAQ,CAAC;QAAE,OAAO,IAAI,CAAC;IACtD,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,KAAK,QAAQ,CAAC,CAAC;IAC7D,OAAO,CAAC,CAAC,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;AAC3D,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,IAAsB;IACnD,IAAI,GAAG,GAAG,CAAC,CAAC;IACZ,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;QACrB,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC;QACpC,IAAI,CAAC;YAAE,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;IACjD,CAAC;IACD,OAAO,OAAO,GAAG,GAAG,CAAC,EAAE,CAAC;AAC1B,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,OAA4B;IAC5D,IAAI,GAAG,GAAG,CAAC,CAAC;IACZ,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;QACxB,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;QACnC,IAAI,CAAC;YAAE,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;IACjD,CAAC;IACD,OAAO,MAAM,GAAG,GAAG,CAAC,EAAE,CAAC;AACzB,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,IAAY,EAAE,KAAK,GAAG,GAAG;IACjD,OAAO,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AAC3D,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export type EventSource = "player" | "game_master" | "system" | "machine";
|
|
2
|
+
export interface EventLogEntry {
|
|
3
|
+
ordinal: number;
|
|
4
|
+
source: EventSource;
|
|
5
|
+
kind: string;
|
|
6
|
+
text: string;
|
|
7
|
+
at: string;
|
|
8
|
+
superseded?: boolean;
|
|
9
|
+
superseded_by?: number;
|
|
10
|
+
alternatives?: number[];
|
|
11
|
+
}
|
|
12
|
+
export interface BranchLineage {
|
|
13
|
+
parent_slug: string | null;
|
|
14
|
+
branch_point: number | null;
|
|
15
|
+
}
|
|
16
|
+
export declare function emptyBranchLineage(): BranchLineage;
|
|
17
|
+
export declare function nextOrdinal(log: EventLogEntry[]): number;
|
|
18
|
+
export declare function appendEvent(log: EventLogEntry[], source: EventSource, kind: string, text: string, at: string): EventLogEntry;
|
|
19
|
+
export declare function supersedeEvent(log: EventLogEntry[], ordinal: number, source: EventSource, kind: string, text: string, at: string): EventLogEntry | null;
|
|
20
|
+
export declare function sliceThrough(log: EventLogEntry[], ordinal: number | null): EventLogEntry[];
|
|
21
|
+
export declare function describeEntry(e: EventLogEntry): string;
|
|
22
|
+
export declare function entriesByOrdinals(log: EventLogEntry[], ordinals: number[]): EventLogEntry[];
|
|
23
|
+
export declare function evictToCap(log: EventLogEntry[], cap: number): EventLogEntry[];
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// Event Log — Novel-scoped append-only observational record with deterministic
|
|
2
|
+
// ordering, provenance, supersession, and branch slicing.
|
|
3
|
+
// REQ-455, REQ-456, REQ-457, REQ-458, REQ-459, REQ-460
|
|
4
|
+
export function emptyBranchLineage() {
|
|
5
|
+
return { parent_slug: null, branch_point: null };
|
|
6
|
+
}
|
|
7
|
+
// REQ-455 — deterministic ordinal: monotonic within a Novel, independent of
|
|
8
|
+
// wall-clock time, so replaying the same inputs reproduces the same ordinals.
|
|
9
|
+
export function nextOrdinal(log) {
|
|
10
|
+
if (log.length === 0)
|
|
11
|
+
return 1;
|
|
12
|
+
return log[log.length - 1].ordinal + 1;
|
|
13
|
+
}
|
|
14
|
+
// REQ-455 — append. Returns the new entry. Callers persist the Novel after.
|
|
15
|
+
export function appendEvent(log, source, kind, text, at) {
|
|
16
|
+
const entry = { ordinal: nextOrdinal(log), source, kind, text, at };
|
|
17
|
+
log.push(entry);
|
|
18
|
+
return entry;
|
|
19
|
+
}
|
|
20
|
+
// REQ-457 — append a replacement and mark the prior entry superseded. The prior
|
|
21
|
+
// entry remains readable; the replacement is linked as an alternative.
|
|
22
|
+
export function supersedeEvent(log, ordinal, source, kind, text, at) {
|
|
23
|
+
const prior = log.find((e) => e.ordinal === ordinal);
|
|
24
|
+
if (!prior)
|
|
25
|
+
return null;
|
|
26
|
+
const replacement = appendEvent(log, source, kind, text, at);
|
|
27
|
+
prior.superseded = true;
|
|
28
|
+
prior.superseded_by = replacement.ordinal;
|
|
29
|
+
if (!prior.alternatives)
|
|
30
|
+
prior.alternatives = [];
|
|
31
|
+
prior.alternatives.push(replacement.ordinal);
|
|
32
|
+
return replacement;
|
|
33
|
+
}
|
|
34
|
+
// REQ-458 — branch slice: entries up to and including the branch point.
|
|
35
|
+
export function sliceThrough(log, ordinal) {
|
|
36
|
+
if (ordinal === null)
|
|
37
|
+
return log.map((e) => ({ ...e }));
|
|
38
|
+
return log.filter((e) => e.ordinal <= ordinal).map((e) => ({ ...e }));
|
|
39
|
+
}
|
|
40
|
+
// REQ-456 — non-semantic view: ordering, provenance, and supersession only.
|
|
41
|
+
export function describeEntry(e) {
|
|
42
|
+
const tag = e.superseded ? " [superseded]" : "";
|
|
43
|
+
return `#${e.ordinal} (${e.source}/${e.kind})${tag} ${e.text}`;
|
|
44
|
+
}
|
|
45
|
+
// REQ-460 — contributing entries for a derived record, by ordinal.
|
|
46
|
+
export function entriesByOrdinals(log, ordinals) {
|
|
47
|
+
const set = new Set(ordinals);
|
|
48
|
+
return log.filter((e) => set.has(e.ordinal));
|
|
49
|
+
}
|
|
50
|
+
// REQ-455 — configured cap: evict oldest entries when the cap is exceeded,
|
|
51
|
+
// preserving the append-only contract for entries still retained.
|
|
52
|
+
export function evictToCap(log, cap) {
|
|
53
|
+
if (cap > 0 && log.length > cap) {
|
|
54
|
+
return log.slice(log.length - cap);
|
|
55
|
+
}
|
|
56
|
+
return log;
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=event-log.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"event-log.js","sourceRoot":"","sources":["../../src/core/event-log.ts"],"names":[],"mappings":"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"}
|