From 2f8880d7852f6ac474e6602f938c788d0f5ea04d Mon Sep 17 00:00:00 2001 From: jenstandstad Date: Mon, 20 Apr 2026 16:16:46 +0200 Subject: [PATCH] View of rewrites --- plugins/festinger/db/schema.sql | 30 ++++ plugins/festinger/festinger/main.py | 132 ++++++++++++++++++ plugins/festinger/festinger/resolution_job.py | 31 +++- plugins/festinger/festinger/urd_writer.py | 41 ++++++ 4 files changed, 233 insertions(+), 1 deletion(-) diff --git a/plugins/festinger/db/schema.sql b/plugins/festinger/db/schema.sql index 055e825..6140c7b 100644 --- a/plugins/festinger/db/schema.sql +++ b/plugins/festinger/db/schema.sql @@ -81,3 +81,33 @@ CREATE TABLE IF NOT EXISTS resolution_queue ( CREATE INDEX IF NOT EXISTS rq_status_idx ON resolution_queue (status); CREATE INDEX IF NOT EXISTS rq_concept_idx ON resolution_queue (concept_id); + +-- --------------------------------------------------------------------------- +-- kg_write_log — immutable audit log of every knowledge graph write +-- --------------------------------------------------------------------------- +-- op values: +-- 'insert' — new URD edge written for the first time +-- 'rewrite' — existing edge replaced (ispart_ispart update) +-- 'decompose' — isa_isa collision resolved by splitting into two dimensions +-- 'reclassify' — concept re-inserted in the correct dimension +CREATE TABLE IF NOT EXISTS kg_write_log ( + id SERIAL PRIMARY KEY, + op VARCHAR(16) NOT NULL, + concept_id INT NOT NULL REFERENCES soas(id), + concept_token TEXT NOT NULL, + parent_id INT NOT NULL REFERENCES soas(id), + parent_token TEXT NOT NULL, + prev_parent_id INT REFERENCES soas(id), + prev_parent_token TEXT, + dim_id INT NOT NULL REFERENCES soas(id), + dim_token TEXT NOT NULL, + is_isa BOOLEAN NOT NULL DEFAULT false, + confidence FLOAT NOT NULL DEFAULT 1.0, + source VARCHAR(32) NOT NULL DEFAULT 'cloud_llm', + resolution_queue_id INT REFERENCES resolution_queue(id), + created_at TIMESTAMPTZ NOT NULL DEFAULT now() +); + +CREATE INDEX IF NOT EXISTS kwl_created_idx ON kg_write_log (created_at DESC); +CREATE INDEX IF NOT EXISTS kwl_concept_idx ON kg_write_log (concept_id); +CREATE INDEX IF NOT EXISTS kwl_op_idx ON kg_write_log (op); diff --git a/plugins/festinger/festinger/main.py b/plugins/festinger/festinger/main.py index de46ea4..fb04578 100644 --- a/plugins/festinger/festinger/main.py +++ b/plugins/festinger/festinger/main.py @@ -611,6 +611,49 @@ async def conflicts(request: Request) -> dict: return {"conflicts": [format_row(r) for r in rows]} +@app.get("/kg-log") +async def kg_log(request: Request, limit: int = 100, offset: int = 0, op: str = "") -> dict: + """Return recent knowledge graph write log entries, newest first.""" + pool = request.app.state.pool + query = """ + SELECT id, op, concept_token, parent_token, prev_parent_token, + dim_token, is_isa, confidence, source, resolution_queue_id, created_at + FROM kg_write_log + {where} + ORDER BY created_at DESC + LIMIT $1 OFFSET $2 + """ + count_query = "SELECT COUNT(*) FROM kg_write_log {where}" + + if op: + where = "WHERE op = $3" + async with pool.acquire() as conn: + rows = await conn.fetch(query.format(where=where), limit, offset, op) + total = await conn.fetchval(count_query.format(where=where), op) + else: + where = "" + async with pool.acquire() as conn: + rows = await conn.fetch(query.format(where=where), limit, offset) + total = await conn.fetchval(count_query.format(where=where)) + + def fmt(r): + return { + "id": r["id"], + "op": r["op"], + "concept": r["concept_token"], + "parent": r["parent_token"], + "prev_parent": r["prev_parent_token"], + "dimension": r["dim_token"], + "is_isa": r["is_isa"], + "confidence": round(r["confidence"], 3), + "source": r["source"], + "resolution_queue_id": r["resolution_queue_id"], + "created_at": r["created_at"].isoformat() if r["created_at"] else None, + } + + return {"total": total, "offset": offset, "limit": limit, "entries": [fmt(r) for r in rows]} + + # --------------------------------------------------------------------------- # /test — scenario seeding and reset (for integration testing) # --------------------------------------------------------------------------- @@ -679,6 +722,17 @@ ADMIN_HTML = """ pre {{ background: #f4f4f4; border: 1px solid #e0e0e0; border-radius: 3px; padding: 1em; overflow: auto; font-size: 0.85em; max-height: 400px; }} .status-ok {{ color: #2a7a2a; }} .status-err {{ color: #b00; }} + table {{ width: 100%; border-collapse: collapse; font-size: 0.82em; }} + th {{ text-align: left; border-bottom: 2px solid #ddd; padding: 4px 8px; font-size: 0.75em; text-transform: uppercase; letter-spacing: 0.04em; color: #666; }} + td {{ border-bottom: 1px solid #f0f0f0; padding: 4px 8px; vertical-align: top; }} + tr:hover td {{ background: #fafafa; }} + .op-insert {{ color: #2a7a2a; font-weight: bold; }} + .op-rewrite {{ color: #c07000; font-weight: bold; }} + .op-decompose {{ color: #5050cc; font-weight: bold; }} + .op-reclassify {{ color: #c02060; font-weight: bold; }} + .log-controls {{ display: flex; gap: 1em; align-items: center; margin: 0.5em 0 1em; }} + .log-controls select, .log-controls button {{ font-family: monospace; padding: 4px 10px; }} + .log-nav {{ margin-top: 0.5em; display: flex; gap: 1em; align-items: center; font-size: 0.85em; }} footer {{ margin-top: 3em; padding-top: 1em; border-top: 1px solid #ddd; font-size: 0.78em; color: #888; }} footer a {{ color: #888; }} @@ -705,6 +759,26 @@ ADMIN_HTML = """

Pending conflicts

Loading…
+

Knowledge graph write log

+
+ + +
+
Loading…
+ +