A relation carries its own attributes - #598
Merged
Merged
Conversation
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: WaylandYang <wayland0916@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements 0037 (new record); its status line and the README index are written.
The problem
A fact has one shape — subject, predicate, and either an entity or a literal on the other side — so this sentence has to lose one half. The extractor kept the edge (
Meridian invested Kestrel) and the four billion went nowhere. In the next document it kept the other half:Northwind investment_amount $5 billion, on the company, with no way back to which investment it was. Before #586 the amount had a third fate: a node called$5 billion, shared by every company that ever raised that sum.The amount is not the object of the edge and not an attribute of the company. It is an attribute of the edge, and the ledger had no place for it — while everything else about an edge was already first-class: a fact has an id, evidence / conflicts / derivations hang off it, and the RDF export reifies it as an
rdf:Statement. The one thing missing was "carry an attribute".The change
A relation declares which attributes its edges may carry; the values live beside the fact, not in it.
migrations/0049:relation_type_qualifiers(relation → attribute definition) andfact_qualifiers(fact → value).factsdoes not change. Attribute definitions are reused fromrelation_typesrows withkind = 'attribute'— datatype, unit, normalisation and export IRI all exist; the only new thing is a domain that is a relation instead of a class. Anentity_idcolumn is reserved ("A invested in B through C"), one-of-twoCHECK; nothing writes it in this cut.{amount: number $}; new rule 8c — when one sentence gives both the entity and a figure, writeobjectandqualifiers, keyed exactly as listed, never dropping the figure to keep the edge. On write the value is converted by the attribute's datatype and the unit recorded; an unknown key, an unconvertible value, and a disagreement with what is already recorded all go to the drop report, never silently.insert_fact, so it applies when that call reused an existing row — a second mention with the amount adds the amount to the same edge. When two mentions disagree, the first value stays andqualifier_conflictis recorded; the ledger's proper answer is two rows and afact_conflictsentry, which needs a write path that bypasses dedup on purpose and is the next cut (recorded).FromRowstructs gain a#[sqlx(skip)]field filled after loading by fact id; the rows themselves are unchanged.End to end
Isolated base, six documents. Created class
organization, attributeamount (number, $), relationinvested_in [event]declaringamount, then ran extraction:Exactly the two sentences that used to lose their number. No
qualifier_*drops, zero quantity entities. The three read paths:"qualifiers":[{"key":"amount","value":{"unit":"$","value":5000000000}}]<…fact:…> a rdf:Statement ; … <…relation:amount> "5000000000"^^xsd:decimal .Vega Capital [amount: 5000000000 $] (2025-05-02 → 2025-05-02) [90%]Tests
a_qualifier_is_not_the_edges_identity— a declaration reads back, a relation cannot be its own qualifier, only an attribute qualifies; writes go Set → Same → Conflict and the first value survives a disagreement.(signature) [event] {qualifiers}, no braces without qualifiers,qualifiersparses on a fact, older replies are unaffected.Out of this cut (open questions in the record)
An amount on a relation that is not adopted yet (
predicate_idnull) goes to the drop report; the canvas edge label; the bootstrap proposing a relation's qualifiers along with the relation.🤖 Generated with Claude Code