[NOTICKET] docs(knowledge): bring the DDL handoff current for Harry
Rifqi Hafizuddin+1: Claude Opus 5
e433561
The header had accreted three rounds of banner and started contradicting
itself - it opened with "this file is the exact SQL that was executed" and then
carried "NOT RUN" markers further down. Rewritten as one coherent handoff.
What it now says plainly, because the copy Harry already has is incomplete in
two ways and one of them is silent:
1. Section 7 (review modes) is missing from what was sent. Without it,
POST /knowledge/ingest with review_mode=llm fails on insert and nothing is
ever auto-approved.
2. Sections 1-5 DRIFTED AFTER the 2026-09-07 run, and CREATE TABLE IF NOT
EXISTS does not repair an existing table. `kind` gained 'document' and the
two parent columns became nullable for kind='domain'. Both are in the file
below; adopting the old copy reproduces neither.
Adds an inventory with per-section adoption status, and a VERIFIED AGAINST LIVE
block read back out of dedorch rather than asserted: 5 tables, 7 of 7 section
6+7 columns, 19 indexes. The …
[NOTICKET] feat(knowledge): two review modes, recorded against a person (r1, r2, r3)
Rifqi Hafizuddin+1: Claude Opus 5
6ef7990
Closes Sistem review. Section 7 landed in dedorch, so the columns are declared
and written; verified read-only through the ORM against live before committing.
r1 `review_mode` on the batch, chosen at ingest. "expert" leaves entries
waiting for a human; "llm" approves them on arrival with NO reviewer step.
Worth being plain about what the second one is: there is no second model
call and no second opinion. The entries were written by a model already,
and this mode means accepting them without a human looking. Default is
"expert" - nobody gets LLM approval by accident.
r2/r3 What makes that offerable rather than reckless is the record.
`decided_by` says whether a human looked; `authorised_by` says on whose
authority it happened. They are separate columns because the admin who
clicks ingest and the expert who reviews can be different people, and in
"llm" mode there is no reviewer at all - so without the clicker's id
reaching the review row the de…
[NOTICKET] feat(knowledge): review diff + the rule/domain boundary (r4, o2)
Rifqi Hafizuddin+1: Claude Opus 5
5cb2e2d
r4 `changed_fields()` and `review_diff()`. The queue can now say WHAT moved
rather than only that something did - which was the ask: show before /
after, do not send the expert back to read the entry from the start.
`changed` is empty when the approval still stands, so the screen can say
"nothing to re-read" instead of rendering two identical panes. `before` is
what the expert actually had in front of them; for an `edited` decision
that is their own corrected text, not the model's claim. A test pins
changed_fields() and has_mutated() to the same answer - two notions of
"has this changed" that can disagree is the same defect as two notions of
readiness.
o2 The rule/domain boundary, written down in the output contract. The two
blurred on screen during the 8 Sep review and a boundary nobody has
recorded is one the prompts cannot hold. The test: if removing it would
change how a NUMBER IS COMPUTED OR SOURCED it is a rule; if it would only
…
The 2026-09-08 "ingest bertahap" items, against four rulings taken 2026-09-14.
i5 Identity is `doc_id`; the same document arriving with different bytes is a
NEW VERSION of it, not a new document. That is what keeps an amended file's
approvals attached - entity_id derives from doc_id, which does not move.
The reuse check moves to find_document_by_hash (any version): computing the
next version first and then looking it up would miss the row that already
holds those bytes and re-parse a document we already have, turning the
idempotency guarantee into a paid no-op. The artifact's own `version` is
ignored - parsing stamps 1 on every parse and cannot know what the tenant
already holds.
i2 A term this company has already approved is no longer re-extracted. One LLM
call per cluster is the entire cost of the glossary branch, so every
recognised term is a call not made - and it is also what stops one concept
arriving as term 1.1 and term 1.2. Ids s…
From the 2026-09-08 discussion. `label` now carries a TYPE_SUBJECT key -
GLOSSARY_PA - so an agent that meets an unknown term writes the key and hits
the row: no scan, no embedding, no model call. Searching a list is O(N),
searching a dictionary is O(1), which was the ask.
m1 `src/knowledge_extraction/keys.py`. Prefixes come from the `kind` column
vocabulary (English, singular) so the system holds one spelling per branch
rather than two to keep in sync. Subjects cap at 40 chars, cut on a word
boundary. A rule has no canonical surface of its own, so it borrows the
term it governs plus a short digest (RULE_PA_C07BE4); asking the model to
name it is the option ids.py already rules out, and a model-written
rule_id was a real defect here.
m2 A key is a LABEL, never an identity. `entity_id` stays the hash an
expert's approval hangs on; a key is built from a surface and moves when
clustering canonicalises differently. Pinned by a test: one term from two
…