Repository navigation
A criterion stated in the corpus has nowhere to land #507
Description
Activity
Moving the design discussion from #806 here as requested in review, and closing that draft PR.
The central unresolved decision is authority after approval. If a source changes or disappears, is the approved rule a revocable projection of that source, or a human-authorized rule whose origin is the passage? In the latter case a source change prompts review without silently rescinding the human decision. In the former, suspension and retirement must explicitly explain their relationship to 0044’s protection of human decisions. The draft does not settle this choice.
A small first slice could report a general criterion over a known subject class, an existing numeric attribute with an explicit compatible unit, a constant comparison, and an existing concluded class. For example,
scratch_length > 2 mmmust preserve strict>semantics. A one-off event is not a criterion; an exception such as “unless” must not be dropped. Missing vocabulary, ambiguous units, or conditions that cannot be represented losslessly stay unresolved rather than becoming guessed executable rules.Before approval, the proposal must have no inference effect. Review should show the exact passage beside the structured interpretation. Adoption needs an atomic, idempotent operation using existing rule validation: recheck source access/version and vocabulary, create the ordinary rule, retain source and reviewer provenance, and mark the proposal adopted. Proposal storage should be chosen as part of the decision; no dedicated table is presumed necessary. The mutable chunk ID alone is not a reviewed source snapshot. Rule-source provenance is distinct from the instance facts that later satisfy that rule.
The decision also needs behavior for pending source changes, approved-source changes/deletion, access revocation, human edits, rejection/re-extraction, and disable/recompute. Existing rules and the reasoner should be reused.
run_nowwrites materialized results and is not a read-only preview.The six narrative/exception/unit/threshold examples in #806 were unexecuted seed cases, not test evidence. I will not retain them as a JSONL file under docs. For a future implementation, extraction cases need an executable extraction harness, while threshold, missing-reading, no-effect-before-approval, and withdrawal/proof behavior belong beside the rule tests that execute them. Existing engine tests do not establish the quality or correctness of this unimplemented proposal workflow.
This preserves the question for a numbered decision record and agreed cuts, without treating the design as accepted or requesting an implementation before the authority question is answered.
Picking this up. The three cuts in the body are clear; what is less clear is which cut ships first and how small each one needs to be for review. Before I write code I want to settle that.
My read of the cuts:
-
Cut 1 (decision record). A design doc answering "what is a sourced criterion, why a proposal and not a rule, what happens when its vocabulary does not resolve." I can write this in
docs/decisions/00N2-class-keeping-a-proposed-rule-pending.md. The body of this issue is most of it already; I'd just formalize the vocabulary and the queue shape. -
Cut 2 (provenance + proposal storage). This is what A criterion stated in the corpus has nowhere to land #507 is actually about: rules extracted from text need a home that does not silently re-decide a base. Two options for the storage:
-
(2a) Provenance on
attribute_rules. Addsource_kind(text/hand),source_chunk_id,source_document_id,proposed_at,proposed_byto the existing table. A row withsource_kind = 'text'is a proposal until a person nodises; the existingenabledflag becomes the explicit nodise/decline gate. No new table. -
(2b) A separate
pending_rulestable. Mirror ofattribute_rulesfor extracted-from-text rows. The nodise moves a row frompending_rulestoattribute_rules. Two tables, identical shape.
-
-
Cut 3 (Review UI). Show the sentence beside the rule it would become, nod/decline buttons.
The body says "
attribute_rules(or apending_rulesbeside it)" — explicitly leaving the choice open. My read is (2a), because:- One table is one fewer migration set to fail under any search path (Relations carry their type signature into the prompt #70-style concerns)
- The
enabledcolumn already exists; repurposing it as the nodise gate avoids new vocabulary - A
source_kindcolumn tells extraction "write here" and tells Review "show as proposal" - A separate
pending_rulestable is one more thing for the eval/derivation paths to walk past
What I would do in this PR
If you confirm (2a), the PR is:
- Migration adding
source_kind(TEXT NOT NULL DEFAULT 'hand' CHECK IN ('hand', 'text')),source_chunk_id,source_document_id,proposed_at,proposed_bytoattribute_rules. Indices on(kb_id, source_kind)and(source_chunk_id). - Migration adding the provenance invariants (0048-style composite FKs and triggers if needed to keep same-KB references; chunk and document must belong to the rule's KB).
- Store fns:
attribute_rules::set_source(pool, id, kind, chunk_id, doc_id, actor)andattribute_rules::proposals_for_review(pool, kb_id). No extraction change. - One regression per FK / trigger variant from the migration; the same shape as
migration_0070_runs_under_any_search_path.rs.
This is cut 2 minus the extraction change. Extraction emits triples today, not rules, so this PR alone does not fix the open result of #507 — but it gives cut 3 (Review UI) a concrete schema to bind to, and it gives a follow-up extraction change somewhere to write.
What I would NOT do in this PR
- No extraction change. That's a separate PR. Today's extraction does not recognise a universally-quantified conditional, and teaching it to is its own cut.
- No Review UI. Cut 3. The schema can be tested with
proposals_for_review+ manual nodise via SQL. - No design doc. I'd be happy to write cut 1 alongside or before, but I don't want to block cut 2's code on a doc review.
Two questions
- (2a) vs (2b)? I argued (2a) above. Say "use (2b)" or "use (2a) and add this constraint" and I'll adjust. I do not have a strong conviction either way; I just don't want to commit to one before asking.
- Cut ordering. Do you want cut 1 (the decision record) to land first, or is the schema-only cut 2 above acceptable on its own? I lean "schema first" because it's a small reviewable PR that doesn't need a doc to decide, but if the doc would change the schema shape I'd rather write the doc first.
If you say "go with (2a), no doc needed," I can have the migration + regression tests ready to push in a few hours. If you say "do the doc first" or "use (2b)," I'll pivot.
-
Use (2a), with one change: do not overload
enabledas the nod. A hand-written rule a person switched off and a proposal nobody has looked at both readenabled = false, and the review queue and the rules page need to tell them apart. Add an explicit state (proposed,nodded,declined) next tosource_kind;enabledkeeps its meaning for rules that are in force. The provenance columns and the same-KB invariants you list are right (chunk and document must belong to the rule's base; see #901 for the family they join).Schema first is fine, but write the record in the same PR rather than skipping it: the issue body is most of it, and the one thing it has to decide, what happens when the criterion's vocabulary does not resolve, changes the schema (hold the proposal with the unresolved words kept verbatim, or refuse it). My answer is hold: a proposal that cannot resolve keeps the sentence and the words it could not map, and the review card says so; refusing loses the source. Number the record 0061.
- added 9 commits that reference this issue
on Sep 27, 2026


A well report says: "当全烃大于 8 且解释结论为气测异常时,可判定为优秀井。" — when total hydrocarbon is above 8 and the interpretation reads as a gas anomaly, the well counts as a good well.
That sentence is a criterion, and it is exactly the kind of thing 0021 built a table for. Today it falls on the floor, and it falls necessarily rather than by oversight.
Why it cannot land
facts.subject_idisNOT NULL. Every fact hangs on an entity. This sentence has no subject — it is about a class of wells, not about any well. There is no entity to hang it on.So the likely outcome is that it is dropped. The worse outcome is that it is attached to whichever well happens to be nearby in the context, turning "here is how to judge a well" into the asserted fact "W-1 is a good well" — a claim the document never made.
Where it should land
attribute_rulesis already the right shape. It holds "an entity of this class, whose own attributes satisfy these conditions, concludes this" — which is what the sentence says, slot for slot. The class, the conditions, the conclusion are all there.What is missing is not the shape. It is the provenance.
attribute_ruleshas nochunk_id, nodocument_id, no evidence of any kind: it assumes a rule is something a person typed into the ontology page. The one in the corpus has a source — page N of a particular report.The line 0002 drew, and the half of it that does not apply
0002 decision 5 says the reasoner's criteria are written by people and rules the model out. That ban is really two claims, and only one of them is at stake here:
The line between them is whether it has a source, and a source is the thing this system has the most of.
That is also precisely the distinction 0015 drew for facts: recording a sentence is not asserting a fact, and
pending_factsis where the difference lives. A criterion found in the corpus should be to a rule what a pending fact is to a fact.0021's open question already sketched this shape before there was a reason to build it:
What it would be
A proposed rule: structured conditions, the class it concludes, and the chunk it was read from. It goes to Review, where a person sees the original sentence beside the rule it would become, and nods or declines. Before the nod it concludes nothing. After it, it is an ordinary rule that additionally names the passage it came from.
A side effect worth having: the rules page becomes the answer to "where do this domain's criteria come from" — which were somebody's judgement, which were read out of a standard, a design document or a regulation, and you can click through to the text.
Two parts that are genuinely hard
The vocabulary has to line up. "全烃大于 8" needs to resolve to the attribute predicate this base actually has, and the 8 to its unit. That is what the mapping layer (0011) and the ontology contract (0012) do for facts; nothing does it for criteria. What happens when it does not resolve — hold the proposal until someone creates the attribute, or refuse it — is an open question, not a detail.
Not every "when … then …" is a criterion. "当时钻到 3000 米的时候,发现了油气显示" is a narrative about one event, not a rule. Telling the two apart is extraction's job and it will get it wrong. Which is the strongest argument for the queue: a wrong proposal costs a glance, a wrong rule re-decides the base.
Cuts
attribute_rules(or apending_rulesbeside it) gains provenance.Related: 0021, 0029 (or), 0030 (chaining), 0032 (computed values) — this is a fourth axis on the same object, and independent of all three.