run-dmcp 0.6.0 → 0.7.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/dist/index.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ export { timelineDivergences } from "./timeline/checkpoint.js";
|
|
|
14
14
|
export type { Divergence } from "./timeline/checkpoint.js";
|
|
15
15
|
export { declareIrreversible, irreversibleFactFor, listIrreversibleFacts, } from "./timeline/irreversible.js";
|
|
16
16
|
export type { IrreversibleFact } from "./timeline/irreversible.js";
|
|
17
|
+
export type { SetTransition } from "./timeline/constrained.js";
|
|
17
18
|
export { openingEventId } from "./timeline/provenance.js";
|
|
18
19
|
export type { FactProvenance } from "./timeline/provenance.js";
|
|
19
20
|
export { narrationConstraintAt, contradictions, NARRATION_CONSTRAINT_FORMAT_VERSION, } from "./timeline/narration.js";
|
|
@@ -22,7 +23,7 @@ export { writeConstrainedValue, transferConstrainedValue, valueHistory, } from "
|
|
|
22
23
|
export type { ValueTransition } from "./timeline/constrained.js";
|
|
23
24
|
export { ConstraintViolationError, constraintsFor, conservedConstraintFor } from "./timeline/registry.js";
|
|
24
25
|
export { createResolver, ResolveProtocolError } from "./timeline/resolve.js";
|
|
25
|
-
export type { Mechanic, Resolver, Proposal, Expectation, AdjudicationInput, Adjudication, IntendedChange, IntendedWrite, IntendedTransfer, Outcome, ResolveRefusalReason, } from "./timeline/resolve.js";
|
|
26
|
+
export type { Mechanic, Resolver, Proposal, Expectation, AdjudicationInput, Adjudication, IntendedChange, IntendedWrite, IntendedTransfer, IntendedSet, Outcome, ResolveRefusalReason, } from "./timeline/resolve.js";
|
|
26
27
|
export { createStateRenderer } from "./timeline/render.js";
|
|
27
28
|
export type { RenderVocabulary, VocabularyEntry, StateRenderer, RenderedState, RenderedNoun, UnnamedFact, } from "./timeline/render.js";
|
|
28
29
|
export { createTurnReader } from "./reader/turnReader.js";
|
package/dist/mcp-server.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ export declare const SERVER_NAME = "dmcp";
|
|
|
6
6
|
* the published package version by src/__tests__/serverVersion.test.ts --
|
|
7
7
|
* this said "0.3.0" for the whole of 0.4.0, because a release bumps
|
|
8
8
|
* package.json and nothing was watching this. */
|
|
9
|
-
export declare const SERVER_VERSION = "0.
|
|
9
|
+
export declare const SERVER_VERSION = "0.7.0";
|
|
10
10
|
/**
|
|
11
11
|
* Build an MCP server with every CORE tool, resource and prompt this engine
|
|
12
12
|
* serves -- entities, facts, events, the timeline, and the entity/property
|
package/dist/mcp-server.js
CHANGED
|
@@ -45,7 +45,7 @@ export const SERVER_NAME = "dmcp";
|
|
|
45
45
|
* the published package version by src/__tests__/serverVersion.test.ts --
|
|
46
46
|
* this said "0.3.0" for the whole of 0.4.0, because a release bumps
|
|
47
47
|
* package.json and nothing was watching this. */
|
|
48
|
-
export const SERVER_VERSION = "0.
|
|
48
|
+
export const SERVER_VERSION = "0.7.0";
|
|
49
49
|
/**
|
|
50
50
|
* Build an MCP server with every CORE tool, resource and prompt this engine
|
|
51
51
|
* serves -- entities, facts, events, the timeline, and the entity/property
|
|
@@ -218,3 +218,42 @@ export declare function transferConstrainedValue(params: {
|
|
|
218
218
|
* discipline. `limit` applies after ordering, never before.
|
|
219
219
|
*/
|
|
220
220
|
export declare function valueHistory(entityId: string, key: string, limit?: number): ValueTransition[];
|
|
221
|
+
/**
|
|
222
|
+
* One non-numeric column set, as `resolve()`'s `set` intent applies it (issue
|
|
223
|
+
* #32). A row, never a verdict: what the column held, what it holds now, and
|
|
224
|
+
* the fact the write opened -- `null` when the value did not move, because an
|
|
225
|
+
* unchanged value opens no new interval (projection.ts's update trigger).
|
|
226
|
+
*/
|
|
227
|
+
export interface SetTransition {
|
|
228
|
+
entityId: string;
|
|
229
|
+
key: string;
|
|
230
|
+
previousValue: string | number | null;
|
|
231
|
+
newValue: string | number | null;
|
|
232
|
+
t: T;
|
|
233
|
+
factId: string | null;
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Sets one live column of an entity's projected table to `value` (issue #32).
|
|
237
|
+
* The engine stores what it is handed and never learns what the column means;
|
|
238
|
+
* the projection triggers version it exactly as they version every other
|
|
239
|
+
* column write, so nothing here touches `facts` directly.
|
|
240
|
+
*
|
|
241
|
+
* The same choke point as numeric writes, and the same checks in the same
|
|
242
|
+
* voice: the entity and column are resolved against `PROJECTED_TABLES` /
|
|
243
|
+
* `liveColumns` (never a caller-supplied table name); the column that places
|
|
244
|
+
* the entity in its game is refused; a key carrying a numeric constraint is
|
|
245
|
+
* refused, because that value changes by a write where the constraint is
|
|
246
|
+
* evaluated; `resolve_only` asks the one question it always asks, whether an
|
|
247
|
+
* adjudication window is open; and a contradicted irreversible fact is
|
|
248
|
+
* translated into a typed `ConstraintViolationError` with the one hop
|
|
249
|
+
* attached, by `translateIrreversibleFailure`, never by reading the trigger's
|
|
250
|
+
* message.
|
|
251
|
+
*
|
|
252
|
+
* Not exported from the library: `resolve()` is its only caller, so a
|
|
253
|
+
* non-numeric consequence reaches storage through a resolution or not at all.
|
|
254
|
+
*/
|
|
255
|
+
export declare function setProjectedValue(params: {
|
|
256
|
+
entityId: string;
|
|
257
|
+
key: string;
|
|
258
|
+
value: string | number | null;
|
|
259
|
+
}): SetTransition;
|
|
@@ -669,3 +669,81 @@ export function valueHistory(entityId, key, limit) {
|
|
|
669
669
|
const limited = limit !== undefined ? ranked.slice(0, limit) : ranked;
|
|
670
670
|
return limited.map((r) => r.transition);
|
|
671
671
|
}
|
|
672
|
+
/** The numeric members of the constraint family. A key carrying one of them
|
|
673
|
+
* changes by a write (`writeConstrainedValue`), where the constraint is
|
|
674
|
+
* evaluated; a `set` would step round it. */
|
|
675
|
+
const NUMERIC_CONSTRAINT_KINDS = new Set(["monotonic", "bounded", "conserved"]);
|
|
676
|
+
function openFactId(db, entityId, key) {
|
|
677
|
+
const row = db
|
|
678
|
+
.prepare(`SELECT id FROM facts WHERE entity_id = ? AND key = ? AND valid_to_t IS NULL ORDER BY valid_from_t DESC, id DESC LIMIT 1`)
|
|
679
|
+
.get(entityId, key);
|
|
680
|
+
return row?.id ?? null;
|
|
681
|
+
}
|
|
682
|
+
/**
|
|
683
|
+
* Sets one live column of an entity's projected table to `value` (issue #32).
|
|
684
|
+
* The engine stores what it is handed and never learns what the column means;
|
|
685
|
+
* the projection triggers version it exactly as they version every other
|
|
686
|
+
* column write, so nothing here touches `facts` directly.
|
|
687
|
+
*
|
|
688
|
+
* The same choke point as numeric writes, and the same checks in the same
|
|
689
|
+
* voice: the entity and column are resolved against `PROJECTED_TABLES` /
|
|
690
|
+
* `liveColumns` (never a caller-supplied table name); the column that places
|
|
691
|
+
* the entity in its game is refused; a key carrying a numeric constraint is
|
|
692
|
+
* refused, because that value changes by a write where the constraint is
|
|
693
|
+
* evaluated; `resolve_only` asks the one question it always asks, whether an
|
|
694
|
+
* adjudication window is open; and a contradicted irreversible fact is
|
|
695
|
+
* translated into a typed `ConstraintViolationError` with the one hop
|
|
696
|
+
* attached, by `translateIrreversibleFailure`, never by reading the trigger's
|
|
697
|
+
* message.
|
|
698
|
+
*
|
|
699
|
+
* Not exported from the library: `resolve()` is its only caller, so a
|
|
700
|
+
* non-numeric consequence reaches storage through a resolution or not at all.
|
|
701
|
+
*/
|
|
702
|
+
export function setProjectedValue(params) {
|
|
703
|
+
const resolved = resolveProjection(params.entityId, params.key);
|
|
704
|
+
const db = getDatabase();
|
|
705
|
+
const projected = PROJECTED_TABLES.find((p) => p.table === resolved.table);
|
|
706
|
+
if (projected && projected.gameIdColumn === params.key) {
|
|
707
|
+
throw new Error(`timeline: '${params.key}' places entity '${params.entityId}' in its game and cannot be set -- ` +
|
|
708
|
+
`an entity does not move between games`);
|
|
709
|
+
}
|
|
710
|
+
for (const constraint of constraintsFor(params.entityId, params.key)) {
|
|
711
|
+
if (NUMERIC_CONSTRAINT_KINDS.has(constraint.kind)) {
|
|
712
|
+
throw new ConstraintViolationError(constraint.kind, params.entityId, `Entity '${params.entityId}' is ${constraint.kind}-constrained for key '${params.key}', which a set does not ` +
|
|
713
|
+
`evaluate; this value changes by a write, where the constraint is checked.`);
|
|
714
|
+
}
|
|
715
|
+
if (constraint.kind === "resolve_only" && !adjudicationOpen()) {
|
|
716
|
+
throw new ConstraintViolationError("resolve_only", params.entityId, `Entity '${params.entityId}' is resolve_only-constrained for key '${params.key}'; direct writes are refused. ` +
|
|
717
|
+
`This value can only change through the adjudicating call that opens the resolution window.`);
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
const row = db.prepare(`SELECT ${resolved.key} AS value FROM ${resolved.table} WHERE id = ?`).get(resolved.entityId);
|
|
721
|
+
if (!row) {
|
|
722
|
+
throw new Error(`timeline: no live row in '${resolved.table}' for entity '${resolved.entityId}' -- ` +
|
|
723
|
+
`it may have been destroyed since it was last confirmed to exist`);
|
|
724
|
+
}
|
|
725
|
+
try {
|
|
726
|
+
return withTransaction(() => {
|
|
727
|
+
const before = openFactId(db, resolved.entityId, resolved.key);
|
|
728
|
+
db.prepare(`UPDATE ${resolved.table} SET ${resolved.key} = ? WHERE id = ?`).run(params.value, resolved.entityId);
|
|
729
|
+
const after = openFactId(db, resolved.entityId, resolved.key);
|
|
730
|
+
const story = currentStoryTime(resolved.gameId);
|
|
731
|
+
if (!story) {
|
|
732
|
+
throw new Error(`timeline: game '${resolved.gameId}' has no timeline clock -- a set has no t to attach to`);
|
|
733
|
+
}
|
|
734
|
+
return {
|
|
735
|
+
entityId: resolved.entityId,
|
|
736
|
+
key: resolved.key,
|
|
737
|
+
previousValue: row.value,
|
|
738
|
+
newValue: params.value,
|
|
739
|
+
t: story.t,
|
|
740
|
+
factId: after !== before ? after : null,
|
|
741
|
+
};
|
|
742
|
+
});
|
|
743
|
+
}
|
|
744
|
+
catch (err) {
|
|
745
|
+
if (params.value === null)
|
|
746
|
+
throw err;
|
|
747
|
+
translateIrreversibleFailure(db, [{ entityId: params.entityId, key: params.key, table: resolved.table, attemptedValue: params.value }], err);
|
|
748
|
+
}
|
|
749
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type T } from "./t.js";
|
|
2
2
|
import { type NarrationConstraint, type Contradiction } from "./narration.js";
|
|
3
|
-
import { type ValueTransition } from "./constrained.js";
|
|
3
|
+
import { type ValueTransition, type SetTransition } from "./constrained.js";
|
|
4
4
|
/**
|
|
5
5
|
* The inbound half of authority (design §5.2a, GitHub issue #10): propose ->
|
|
6
6
|
* adjudicate -> outcome. The engine enforces the PROTOCOL -- resolution
|
|
@@ -71,8 +71,9 @@ import { type ValueTransition } from "./constrained.js";
|
|
|
71
71
|
* INSIDE it (adjudication.ts's own doc comment asks for exactly this
|
|
72
72
|
* nesting, so the window row rolls back with the writes it
|
|
73
73
|
* authorized). Every change goes through `writeConstrainedValue` /
|
|
74
|
-
* `transferConstrainedValue`
|
|
75
|
-
*
|
|
74
|
+
* `transferConstrainedValue` / `setProjectedValue` (issue #32, a
|
|
75
|
+
* non-numeric column) -- the one choke point (root CLAUDE.md hard
|
|
76
|
+
* rule 7) -- never a direct write. A constraint violation
|
|
76
77
|
* anywhere in the list propagates out of the transaction untouched
|
|
77
78
|
* (never caught and re-labelled here) and rolls back EVERY change the
|
|
78
79
|
* transaction made, including ones that individually would have
|
|
@@ -169,7 +170,17 @@ export interface IntendedTransfer {
|
|
|
169
170
|
maxValue: number | null;
|
|
170
171
|
};
|
|
171
172
|
}
|
|
172
|
-
|
|
173
|
+
/** One intended set of a non-numeric column on an entity's projected row
|
|
174
|
+
* (issue #32) -- a thing changing owner, a character changing place. The
|
|
175
|
+
* engine stores `value` and never learns what `key` means; see
|
|
176
|
+
* `setProjectedValue` (constrained.ts) for what it refuses. */
|
|
177
|
+
export interface IntendedSet {
|
|
178
|
+
kind: "set";
|
|
179
|
+
entityId: string;
|
|
180
|
+
key: string;
|
|
181
|
+
value: string | number | null;
|
|
182
|
+
}
|
|
183
|
+
export type IntendedChange = IntendedWrite | IntendedTransfer | IntendedSet;
|
|
173
184
|
/**
|
|
174
185
|
* What a mechanic returns. `changes` are intents, not writes -- `resolve()`
|
|
175
186
|
* applies every one of them through the one choke point (step 5); the
|
|
@@ -201,6 +212,9 @@ export interface Outcome {
|
|
|
201
212
|
t: T;
|
|
202
213
|
result: Record<string, unknown>;
|
|
203
214
|
transitions: ValueTransition[];
|
|
215
|
+
/** Every `set` this resolution applied, in order (issue #32). Kept apart
|
|
216
|
+
* from `transitions`, whose values are numbers. */
|
|
217
|
+
sets: SetTransition[];
|
|
204
218
|
constraint: NarrationConstraint;
|
|
205
219
|
eventId: string;
|
|
206
220
|
}
|
package/dist/timeline/resolve.js
CHANGED
|
@@ -3,7 +3,7 @@ import { getDatabase, withTransaction } from "../db/connection.js";
|
|
|
3
3
|
import { currentStoryTime } from "./clock.js";
|
|
4
4
|
import { narrationConstraintAt, contradictions } from "./narration.js";
|
|
5
5
|
import { withAdjudicationOpen } from "./adjudication.js";
|
|
6
|
-
import { writeConstrainedValue, transferConstrainedValue } from "./constrained.js";
|
|
6
|
+
import { writeConstrainedValue, transferConstrainedValue, setProjectedValue } from "./constrained.js";
|
|
7
7
|
/**
|
|
8
8
|
* Refused before dispatch, before any write, or (never, by construction --
|
|
9
9
|
* see step 5 above) mid-apply. `reason` is the discriminant a caller
|
|
@@ -81,6 +81,9 @@ export function createResolver(params) {
|
|
|
81
81
|
};
|
|
82
82
|
}
|
|
83
83
|
function applyChange(change) {
|
|
84
|
+
if (change.kind === "set") {
|
|
85
|
+
return setProjectedValue({ entityId: change.entityId, key: change.key, value: change.value });
|
|
86
|
+
}
|
|
84
87
|
if (change.kind === "write") {
|
|
85
88
|
return [
|
|
86
89
|
writeConstrainedValue({
|
|
@@ -180,8 +183,13 @@ function resolveProposal(mechanicsByName, proposal) {
|
|
|
180
183
|
// is never relabelled as a ResolveProtocolError).
|
|
181
184
|
const applied = withTransaction(() => withAdjudicationOpen(gameId, () => {
|
|
182
185
|
const transitions = [];
|
|
186
|
+
const sets = [];
|
|
183
187
|
for (const change of changes) {
|
|
184
|
-
|
|
188
|
+
const applied = applyChange(change);
|
|
189
|
+
if (Array.isArray(applied))
|
|
190
|
+
transitions.push(...applied);
|
|
191
|
+
else
|
|
192
|
+
sets.push(applied);
|
|
185
193
|
}
|
|
186
194
|
// Re-read the clock AFTER every write has landed, inside this same
|
|
187
195
|
// transaction -- a sequence-axis game advances its own t once per
|
|
@@ -206,7 +214,7 @@ function resolveProposal(mechanicsByName, proposal) {
|
|
|
206
214
|
getDatabase()
|
|
207
215
|
.prepare(`INSERT INTO events (id, game_id, at_t, kind, description, causes) VALUES (?, ?, ?, 'resolution.recorded', ?, ?)`)
|
|
208
216
|
.run(eventId, gameId, postStory.t, adjudication.description ?? null, JSON.stringify(causes));
|
|
209
|
-
return { transitions, eventId, postT: postStory.t };
|
|
217
|
+
return { transitions, sets, eventId, postT: postStory.t };
|
|
210
218
|
}));
|
|
211
219
|
// 7. The outcome's constraint, built AFTER the writes landed and the
|
|
212
220
|
// transaction holding them has already committed -- reachable only from a
|
|
@@ -220,6 +228,7 @@ function resolveProposal(mechanicsByName, proposal) {
|
|
|
220
228
|
t,
|
|
221
229
|
result: adjudication.result ?? {},
|
|
222
230
|
transitions: applied.transitions,
|
|
231
|
+
sets: applied.sets,
|
|
223
232
|
constraint: postConstraint,
|
|
224
233
|
eventId: applied.eventId,
|
|
225
234
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "run-dmcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "An MCP server for LLM-run interactive fiction where the server owns what is true - including when it was true. A continuation of DMCP.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Derek Ferguson",
|