Skip to content

A criterion stated in the corpus has nowhere to land #507

Description

@WaylandYang

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_id is NOT 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.
  • Extraction emits triples. A universally quantified conditional does not fit in three slots.

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_rules is 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_rules has no chunk_id, no document_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 model invents a criterion — refused, and it stays refused. One authored criterion re-decides the whole base, and nobody can say where it came from.
  • The model reports that a document states a criterion — this is a fact about a document. It has a sentence, a page, a filename, exactly like every other thing extraction produces.

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_facts is 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:

If this opens later, it wants a pending queue of the shape pending_facts has, so a person nods before a rule takes effect.

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

  1. A decision record: what a sourced criterion is, why it is a proposal and not a rule, and what happens when its vocabulary does not resolve.
  2. Extraction recognises a criterion and emits a proposal with its chunk; attribute_rules (or a pending_rules beside it) gains provenance.
  3. Review: the sentence beside the rule it would become, nod or decline.

Related: 0021, 0029 (or), 0030 (chaining), 0032 (computed values) — this is a fourth axis on the same object, and independent of all three.

Activity

  1. Maya-Kid commented on Sep 19, 2026

    @Maya-Kid
    Contributor

    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 mm must 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_now writes 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.

  2. rollroyces commented on Sep 23, 2026

    @rollroyces
    Contributor

    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. Add source_kind (text / hand), source_chunk_id, source_document_id, proposed_at, proposed_by to the existing table. A row with source_kind = 'text' is a proposal until a person nodises; the existing enabled flag becomes the explicit nodise/decline gate. No new table.

      • (2b) A separate pending_rules table. Mirror of attribute_rules for extracted-from-text rows. The nodise moves a row from pending_rules to attribute_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 a pending_rules beside 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 enabled column already exists; repurposing it as the nodise gate avoids new vocabulary
    • A source_kind column tells extraction "write here" and tells Review "show as proposal"
    • A separate pending_rules table 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:

    1. Migration adding source_kind (TEXT NOT NULL DEFAULT 'hand' CHECK IN ('hand', 'text')), source_chunk_id, source_document_id, proposed_at, proposed_by to attribute_rules. Indices on (kb_id, source_kind) and (source_chunk_id).
    2. 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).
    3. Store fns: attribute_rules::set_source(pool, id, kind, chunk_id, doc_id, actor) and attribute_rules::proposals_for_review(pool, kb_id). No extraction change.
    4. 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

    1. (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.
    2. 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.

  3. WaylandYang commented on Sep 25, 2026

    @WaylandYang
    ContributorAuthor

    Use (2a), with one change: do not overload enabled as the nod. A hand-written rule a person switched off and a proposal nobody has looked at both read enabled = false, and the review queue and the rules page need to tell them apart. Add an explicit state (proposed, nodded, declined) next to source_kind; enabled keeps 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.

  4. added 9 commits that reference this issue on Sep 27, 2026
    4d4d2fd
    86f5a93
    0afed4f
    838a4a6
    35ae666
    a113a9f
    c3c93c6
    1db07da
    2cfa607
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions