Every read tool (entity_context, state_at, was_true_at, ancestors_of,
descendants_of, lineage_of, recall_images, search_images_by_caption,
register_image) now accepts an optional world_id parameter, defaulting
to 'default' so v1 callers see no change.
* Neo4j: every Person/Faction/Location/Item/Event/Lineage/Image node
carries a world_id string property. seed.py backfills existing nodes
to 'default' and writes a second minimal world 'arda_greyscale'
(2 people, 1 faction, 1 location, 4 relations, 1 event, 1 era).
* Cypher: every MATCH in the world/lineage/images plugins filters by
world_id on the right node type.
* New admin tool list_worlds() — runs
MATCH (n) WHERE n.world_id IS NOT NULL RETURN DISTINCT n.world_id
to expose the namespace.
* Postgres image_manifest gains a world_id column (init.sql).
* 4 placeholder images generated for the greyscale world (portraits of
Mael and Sira, Ashen Hall, the Ashen Oath).
* test.sh now calls every tool with world_id='default' to verify v1
behaviour, plus a new section 12 that calls list_worlds().
* tests/test_multi_world.py: 14 pytest cases covering list_worlds,
entity_context/world isolation, was_true_at, state_at, lineage
filter, image recall isolation, and the image_manifest schema.
Verification:
pytest tests/test_multi_world.py 14/14 pass
bash test.sh 12/12 sections green,
MinIO image bytes flow 200 OK
list_worlds() returns [{arda_greyscale}, {default}]
55 lines
2.1 KiB
SQL
55 lines
2.1 KiB
SQL
-- Lore Engine POC — minimal Postgres schema.
|
|
-- Operational data that doesn't belong in the world graph.
|
|
|
|
-- pgvector: 384-dim embeddings for semantic image search.
|
|
-- (Requires the `pgvector` image or installed extension on the host OS.)
|
|
CREATE EXTENSION IF NOT EXISTS vector;
|
|
|
|
CREATE TABLE IF NOT EXISTS trade_log (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
world_id TEXT NOT NULL DEFAULT 'default',
|
|
occurred_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
in_fiction_time TEXT,
|
|
buyer_id TEXT,
|
|
seller_id TEXT,
|
|
item_id TEXT,
|
|
quantity NUMERIC,
|
|
unit TEXT,
|
|
unit_price NUMERIC,
|
|
total_price NUMERIC,
|
|
location_id TEXT,
|
|
notes TEXT
|
|
);
|
|
CREATE INDEX IF NOT EXISTS trade_log_time ON trade_log (occurred_at DESC);
|
|
CREATE INDEX IF NOT EXISTS trade_log_buyer ON trade_log (buyer_id);
|
|
|
|
-- Image manifests. The actual bytes live in MinIO; this is metadata + tags
|
|
-- that the LLM can query to know what images exist.
|
|
CREATE TABLE IF NOT EXISTS image_manifest (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
image_id TEXT NOT NULL UNIQUE,
|
|
world_id TEXT NOT NULL DEFAULT 'default',
|
|
object_key TEXT NOT NULL, -- the MinIO object key
|
|
entity_id TEXT, -- linked LoreEntity (e.g. Person.id)
|
|
entity_type TEXT, -- Person / Location / Event / Item
|
|
caption TEXT NOT NULL,
|
|
tags TEXT[],
|
|
era TEXT,
|
|
uploaded_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
width INT,
|
|
height INT,
|
|
bytes BIGINT
|
|
);
|
|
CREATE INDEX IF NOT EXISTS image_manifest_entity ON image_manifest (entity_id);
|
|
CREATE INDEX IF NOT EXISTS image_manifest_tags ON image_manifest USING GIN (tags);
|
|
CREATE INDEX IF NOT EXISTS image_manifest_era ON image_manifest (era);
|
|
CREATE INDEX IF NOT EXISTS image_manifest_world ON image_manifest (world_id);
|
|
|
|
-- Image embeddings (pgvector). One row per embedded image. Filled by
|
|
-- plugins/embeddings.py `embed_images` (idempotent on image_id).
|
|
CREATE TABLE IF NOT EXISTS image_embedding (
|
|
image_id TEXT PRIMARY KEY REFERENCES image_manifest(image_id) ON DELETE CASCADE,
|
|
embedding vector(384) NOT NULL,
|
|
embedded_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|