/** * SceneRulebook — data-driven validation rules for the truth engine. * * Rules are stored per-scene in the database and evaluated by rulebookEngine.ts. * The default set is seeded from defaultRulebook.ts and mirrors the original * hardcoded logic in truthEngine.ts. */ /** Which entity in the action context a condition refers to. */ export type EntityRole = "actor" | "target" | "actorRoom" | "targetRoom"; /** * A composable, JSON-serialisable condition expression. * * Combinators: and | or | not * Predicates: * entityExists — entity referenced by role is present in world state * entityExistsOrWillBeCreated — entity exists OR will be created earlier in this turn * entityType — entity.type === requiredType * eq / neq — entity field comparison (id, name, type, or attributes[attribute]) * attributeExists — entity.attributes[attribute] is not undefined * sameLocation — two entities share the same location attribute value * actorIdIn — action.actorId is included in an allowed list * actorNameIn — actor.name matches one of an allowed list (case-insensitive) * attributeRef — entities[checkRole].attributes[prefix + entities[refRole].attributes[refAttribute]] === true * metaValueNotInRoom — no entity of entityType in actor's room has name === action.metadata[metaKey] */ export type ConditionExpr = | { op: "and"; conditions: ConditionExpr[] } | { op: "or"; conditions: ConditionExpr[] } | { op: "not"; condition: ConditionExpr } | { op: "entityExists"; role: EntityRole } | { op: "entityExistsOrWillBeCreated"; role: EntityRole } | { op: "entityType"; role: EntityRole; requiredType: string } | { op: "eq"; role: EntityRole; attribute: string; value: unknown } | { op: "neq"; role: EntityRole; attribute: string; value: unknown } | { op: "attributeExists"; role: EntityRole; attribute: string } | { op: "sameLocation"; roleA: EntityRole; roleB: EntityRole } | { op: "actorIdIn"; allowedIds: string[] } | { op: "actorNameIn"; allowedNames: string[] } | { op: "attributeRef"; /** Entity whose attribute is being tested */ checkRole: EntityRole; /** Optional string prepended to the resolved key (e.g. "has_") */ prefix?: string; /** Entity that provides the dynamic attribute name */ refRole: EntityRole; /** Attribute on refRole whose value supplies the key name */ refAttribute: string; } | { op: "metaValueNotInRoom"; /** Key in action.metadata whose value to match against entity names */ metaKey: string; /** Only match entities of this type */ entityType: string; }; /** A single named check within an action rule set. */ export type RuleCheck = { id: string; /** Human-readable label shown in the rulebook editor. */ description: string; condition: ConditionExpr; failReason: string; /** * Failure message template. * Supports: {actor.id}, {actor.name}, {target.id}, {target.name} */ failMessage: string; }; /** All checks that apply to a specific action type. */ export type ActionRuleSet = { actionType: string; /** * When false, all checks are skipped and the action always passes. * Useful for quickly disabling enforcement without deleting the rules. */ enabled: boolean; checks: RuleCheck[]; }; /** The full rulebook attached to a scene/world. */ export type SceneRulebook = { id: string; worldId: string; name: string; description?: string; rules: ActionRuleSet[]; createdAt: number; updatedAt: number; };