Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions .claude/board/STATUS_BOARD.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,12 @@ Plan: `.claude/plans/gremlin-composable-algebra-v1.md`.

| D-id | scope | status | gate / falsifier |
|---|---|---|---|
| D-CMH-G1-0 | vocabulary → algebra matrix, TinkerPop 3.7.7 | In PR | every row cites a test, a refusal or the missing capability |
| D-CMH-G1-1 | predicate methods + method references | In PR | composed `programs()` == hand conjunction; cycles, contract, cross-class refused |
| D-CMH-G1-2 | typed Gremlin steps → method body | In PR | == direct Quack program; both frontends == row oracle |
| D-CMH-G1-3 | Gremlin-shaped runtime declarations | In PR | loads after the build; effects refused at load |
| D-CMH-G1-4 | relation carrier in the glove; anchor rule in `compose` | Queued | #1311 bulk oracle + DuckDB through the method layer |
| D-CMH-G1-0 | vocabulary → algebra matrix, TinkerPop 3.7.7 | Shipped (#1508) | every row cites a test, a refusal or the missing capability |
| D-CMH-G1-1 | predicate methods + method references | Shipped (#1508) | composed `programs()` == hand conjunction; cycles, contract, cross-class refused |
| D-CMH-G1-2 | typed Gremlin steps → method body | Shipped (#1508) | == direct Quack program; both frontends == row oracle |
| D-CMH-G1-3 | Gremlin-shaped runtime declarations | Shipped (#1508) | loads after the build; effects refused at load |
| D-CMH-G1-4a | functional relation carrier: `out(rel)`, `out(rel).has`, `count()` | In PR | row loop over the text at n ∈ {1,64,65,4097}; 6 disable runs red; R2IL refuses (no `GATHER`) |
| D-CMH-G1-4 | rest of the relation carrier (fan-out moves the anchor; `E()`, `outE/inV`) | Queued | #1311 bulk oracle + DuckDB through the method layer | #1311 bulk oracle + DuckDB through the method layer |
| D-CMH-G1-5 | FirstOfRun | Queued | insufficiency proof first; Quack + R2IL vs independent oracle |

## D-NALP — `NAL::narsese()` polyfill over R2IL procedures and mask-risc (2026-10-12)
Expand Down
19 changes: 19 additions & 0 deletions .claude/board/entries/2026-10-12-functional-relation-carrier.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# 2026-10-12 — D-CMH-G1-4a: functional relation carrier in the method glove

**Status:** In PR. **Plan:** `.claude/plans/gremlin-composable-algebra-v1.md` §8.

A `Glove` carries `Relation`s (key field, target glove, foreign plane and lane
indices). `out(rel)` composes to `Cond::Reaches` → `Filter::Semijoin` over the
target's live plane; `out(rel).has(f, eq/neq)` adds `Filter::EqU32Via`. The
anchor stays, so `count()` after the hop counts anchor rows. No new op in
Quack, mask-risc or R2IL.

MEASURED (tests/relation.rs, row loop over field text, n ∈ {1,64,65,4097}):
all answers equal; six disable runs red on the test that names each guard.
Removing the liveness test fails the row oracle, so `EqU32Via` alone is not
`out(rel)`: it keeps rows whose key names a dead target.

OPEN: R2IL's fold band has `VIA` but no `GATHER`, so it cannot test that a key
names a live row; relation methods return `CallError::FrontendGap { R2il,
"GATHER" }`. Fan-out (`in`, M:N, `E()`) and a second hop stay refused;
D-SAG-1-4's multi-hop dependency is still blocked on the second hop.
3 changes: 2 additions & 1 deletion .claude/board/entries/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,15 @@ index row, (3) no duplicate entry id. Checks 1 and 2 are deliberately
opposite directions; the stranding this convention prevents shows up in
exactly one of them, never both.

316 entries, 2026-08-06 .. 2026-10-12.
317 entries, 2026-08-06 .. 2026-10-12.

| date | entry id | finding | file |
|---|---|---|---|
| 2026-10-12 | `D-SAG-1` | | [2026-10-12-software-awareness-graph.md](2026-10-12-software-awareness-graph.md) |
| 2026-10-12 | `opencog-pln-entropy-research-map` | Source inventory and falsifiable PLN/ECAN/Shannon/Moore attention comparison: entropy target, evidence overlap, graph projection, resource fairness | [2026-10-12-opencog-pln-entropy-research-map.md](2026-10-12-opencog-pln-entropy-research-map.md) |
| 2026-10-12 | `nars-self-evidence-literature` | NARS internal trace versus evidence, Jøsang dependent/independent fusion and fission, and the snapshot-stable fold assumption; source-grounded red tests for merged #1496 | [2026-10-12-nars-self-evidence-literature.md](2026-10-12-nars-self-evidence-literature.md) |
| 2026-10-12 | `hyperon-metta-lg-translation` | Hyperon/MeTTa to LG sibling map: atom/hypergraph roundtrip, binding and rewrite parity, typed folds, scoped effects and 15 open questions | [2026-10-12-hyperon-metta-lg-translation.md](2026-10-12-hyperon-metta-lg-translation.md) |
| 2026-10-12 | `D-CMH-G1-4a` | | [2026-10-12-functional-relation-carrier.md](2026-10-12-functional-relation-carrier.md) |
| 2026-10-12 | `D-CMH-0` | Class × Schema × Methods activation connects HotPlug, WideFieldMask, Quack and R2IL without a second registry | [2026-10-12-d-cmh-0-class-method-hotplug-activation.md](2026-10-12-d-cmh-0-class-method-hotplug-activation.md) |
| 2026-10-12 | `D-CMH-1` | | [2026-10-12-class-method-polyfill-ledger.md](2026-10-12-class-method-polyfill-ledger.md) |
| 2026-10-12 | `D-HL-0` | CE64 hypothesis layers: plateau of the design (opaque LE bytes, 64 sparse-delta layers merged on read, Narsese-parallel validation), 8 design-vs-code conflicts, 9 unification hypotheses | [2026-10-12-ce64-hypothesis-layers-plateau.md](2026-10-12-ce64-hypothesis-layers-plateau.md) |
Expand Down
58 changes: 49 additions & 9 deletions .claude/plans/gremlin-composable-algebra-v1.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ Rows group steps of one mechanism, so percentages are of rows, not of step names
| hasKey/hasValue/hasNot | selection | V->V | keeps | null | pure | YET | nullable fields refused (`a_nullable_field_is_refused`) | three-valued condition + null policy |
| is | selection | value->value | keeps | — | pure | COMPOSE | = has(f,p) when the value is an anchor field | value-cursor filter |
| where(method()) | selection | V->V | keeps | — | pure | DIRECT | inlined predicate (tests/compose.rs) | — |
| where(traversal) functional | selection | V->V | keeps | — | pure | COMPOSE | #1311 `functional_where_equals_sql_join_sum` (EqU32Via) | relation carrier |
| where(traversal) functional | selection | V->V | keeps | — | pure | DIRECT (G1-4a) | a predicate method over `out(rel).has(..)`, referenced by `where(method())` (tests/relation.rs `a_predicate_through_the_relation_is_reused_by_reference`) | — |
| where(traversal) fan-out | selection | V->V (set) | set over fk | ordered fk | pure | COMPOSE | #1311 `exists_over_children…` (CountDistinctOrdered; refuses unordered) | relation carrier + order witness |
| and | boolean | pred,pred->pred | keeps | — | pure | DIRECT | conjunction of Conds | — |
| or | boolean | pred,pred->pred | keeps | — | pure | COMPOSE | Filter::Or | Cond::Or |
Expand All @@ -138,7 +138,7 @@ Rows group steps of one mechanism, so percentages are of rows, not of step names
| range/limit/skip/tail | ordered | any | prefix of an order | order | pure | YET | Cmp::Range is row-ordinal only | ordered population op |
| none | selection | any->∅ | — | — | pure | COMPOSE | empty filter | — |
| all/any (list) | selection | list->list | — | — | pure | OUT | no list type | list values |
| out(rel) functional | traversal | V->V' | keeps (anchor stays) | — | pure | COMPOSE | #1311 functional hop: Semijoin + EqU32Via | relation carrier |
| out(rel) functional | traversal | V->V' | keeps (anchor stays) | — | pure | DIRECT (G1-4a) | `Cond::Reaches` (Semijoin over the target's live plane) + `Cond::Via` (EqU32Via); tests/relation.rs vs a row loop over the text; R2IL refuses by name (no `GATHER`) | R2IL `GATHER` |
| in(rel) reverse fk | traversal | V->V' | re-anchor on child rows | — | pure | COMPOSE | #1311 `reverse_fanout_reanchors…` | relation carrier |
| out/in(rel) M:N | traversal | V->V' | re-anchor on edge rows | — | pure | COMPOSE | #1311 `many_to_many_one_hop…` | relation carrier |
| both(rel) | traversal | V->V' | union of two bags | — | pure | YET | no bag union across anchors | typed bag merge |
Expand Down Expand Up @@ -191,12 +191,12 @@ Rows group steps of one mechanism, so percentages are of rows, not of step names
| TextP.startingWith | COMPOSE | PrefixLowering over an ordered dictionary lane |
| TextP.endingWith/containing/regex and negations | OUT | strings are codes |

Step rows: 67. DIRECT 10 (15%), COMPOSE 22 (33%), YET 19 (28%), EFFECT 4 (6%), OUT 12 (18%)
Step rows: 67. DIRECT 12 (18%), COMPOSE 20 (30%), YET 19 (28%), EFFECT 4 (6%), OUT 12 (18%) — after G1-4a; at #1508: DIRECT 10, COMPOSE 22
Predicate rows: 7. DIRECT 1, COMPOSE 4, YET 1, OUT 1

YET rows by missing capability: row answer type ×3; ordered population op ×2; typed bag merge ×2; ordered u32 compare (G5) ×1; three-valued condition + null policy ×1; ordered first-occurrence mask (FirstOfRun) ×1; composed fk read (G1) ×1; barrier / fan-in operand (G2) ×1; binding environment ×1; binding environment + join ×1; field-reading count fold ×1; masked_sum_i32_via ×1; typed branch + merge ×1; bounded fixpoint ×1; provenance-carrying path ×1

COMPOSE rows by what the method layer lacks: relation carrier ×8; Cond::Or ×3; Cond ordered compare ×2; — ×2; id↔row binding in Glove ×1; value-cursor filter ×1; relation carrier + order witness ×1; Cond::Not ×1; Fold::Min/Max ×1; a law over two folds ×1; Fold::GroupCount ×1
COMPOSE rows by what the method layer lacks: relation carrier ×6 (×8 at #1508; G1-4a moved functional `out` and `where` over it to DIRECT); Cond::Or ×3; Cond ordered compare ×2; — ×2; id↔row binding in Glove ×1; value-cursor filter ×1; relation carrier + order witness ×1; Cond::Not ×1; Fold::Min/Max ×1; a law over two folds ×1; Fold::GroupCount ×1

## §5 — What the matrix says to build next

Expand Down Expand Up @@ -233,13 +233,53 @@ traversal grants no authority. Not exercised here: no write of any kind.
4. `dedup` or `count` composed past `values(f)` without a refusal.
5. A declaration that loads an effect.

## §8 — D-CMH-G1-4a: the functional relation carrier

A [`Glove`] now carries [`Relation`]s: the canonical key field on its concept,
the target concept's glove, and where a call supplies the target's live plane
and value lanes (`Foreign::planes` / `Foreign::lanes`). That is #1311's
adapter, minus the relations that fan out.

- `out(rel)` composes to `Cond::Reaches { rel }` and binds to
`Filter::Semijoin(fk, target live plane)`. Gremlin drops a traverser whose
edge leads nowhere, so a dangling key and a key to a dead row both fail it.
`EqU32Via` alone drops only the first, so the liveness test is not
optional: removing it fails the row oracle.
- `out(rel).has(f, eq/neq)` composes to `Cond::Via` and adds
`Filter::EqU32Via` (negated for `neq`) on the target's lane. The liveness
test is emitted once per relation, wherever it is first named.
- `count()` after `out(rel)` counts anchor rows: a functional relation yields
at most one traverser per anchor row, so the anchor does not move.
- Refused by name: a second hop (`SecondHop`), `values(f)` on the target
(`ValueThroughRelation`; no sum through a key in Quack), `dedup()` on the
target (`DedupThroughRelation`; a set count), `where(method())` on the
target (`WhereThroughRelation`), and `in`/`both` (`FanOut`).
- Preparation binds the target field against the target's own basis, claims
and binder, and names errors `relation.field`. The key joins `required`, so
an unproven key refuses the method like any field it reads.
- A call names each target concept's population; another population, or
none, is `CallError::ForeignUniverse`.
- **R2IL cannot express it.** The fold band has `VIA` but no `GATHER`: it can
read a target field through a key, but not test that the key names a live
row. A method with a relation condition returns `CallError::FrontendGap {
R2il, "GATHER" }`; methods without one still run on both frontends.

Evidence: `tests/relation.rs` (7 tests). FIXTURE: a synthetic `line -billed_to->
partner` pair (37 partners, 4 dangling key values, every 7th partner dead,
every 13th line dead); every answer is checked against a row loop over the
field text at n ∈ {1, 64, 65, 4097}. Six disable runs, each red on the test
that names it: liveness test removed, `neq` ignored, population check removed,
key dropped from `required`, R2IL refusal turned into a silent pass, liveness
emitted per condition.

## D-ids

| D-id | scope | status |
|---|---|---|
| D-CMH-G1-0 | vocabulary matrix (§4) | Shipped (this PR) |
| D-CMH-G1-1 | predicate methods + method references | Shipped (this PR) |
| D-CMH-G1-2 | typed Gremlin steps → method body | Shipped (this PR) |
| D-CMH-G1-3 | Gremlin-shaped runtime declarations | Shipped (this PR) |
| D-CMH-G1-4 | relation carrier in the `Glove`; #1311 anchor rule moved into `compose` | Queued |
| D-CMH-G1-0 | vocabulary matrix (§4) | Shipped (#1508) |
| D-CMH-G1-1 | predicate methods + method references | Shipped (#1508) |
| D-CMH-G1-2 | typed Gremlin steps → method body | Shipped (#1508) |
| D-CMH-G1-3 | Gremlin-shaped runtime declarations | Shipped (#1508) |
| D-CMH-G1-4a | functional relation carrier: `out(rel)`, `out(rel).has`, `count()` after it (§8) | In PR |
| D-CMH-G1-4 | the rest of the carrier: `in`/M:N (anchor moves to child or edge rows), `E()`, `outE/inV`, `dedup` after a fan-out | Queued |
| D-CMH-G1-5 | FirstOfRun (insufficiency proof first; separate PR #3) | Queued |
1 change: 1 addition & 0 deletions crates/lance-graph-glove-parity/examples/awareness_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ fn main() {
table: TABLE,
reading: ReadingId(1),
universe: Universe(42),
relations: &[],
};
let table = MethodTable::prepare(&glove, &specs);
let mut g = SoftwareGraph::new();
Expand Down
1 change: 1 addition & 0 deletions crates/lance-graph-glove-parity/examples/compose_cost.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ fn main() {
table: TABLE,
reading: ReadingId(1),
universe: Universe(42),
relations: &[],
};
let live = live_words(n);
let lanes = batch.lanes();
Expand Down
1 change: 1 addition & 0 deletions crates/lance-graph-glove-parity/examples/method_cost.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ fn main() {
table: TABLE,
reading: ReadingId(1),
universe: Universe(42),
relations: &[],
};
let specs = [("billable_hours", spec)];
let sql = "SELECT billable_work_entry::billable_hours(cats) FROM cats";
Expand Down
1 change: 1 addition & 0 deletions crates/lance-graph-glove-parity/examples/method_load.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ fn main() {
table: TABLE,
reading: ReadingId(1),
universe: Universe(42),
relations: &[],
};
let t1 = Instant::now();
let table = MethodTable::prepare_with(&glove, &[], &decls);
Expand Down
21 changes: 21 additions & 0 deletions crates/lance-graph-glove-parity/src/awareness.rs
Original file line number Diff line number Diff line change
Expand Up @@ -629,6 +629,27 @@ impl SoftwareGraph {
}
continue;
}
// A hop's key and target field belong to the glove, not
// the spec: the condition is recorded as written.
Cond::Reaches { rel } => {
let n = self.intern(Kind::Cond, format!("cond:out({rel})@r{r}"));
self.link(m, Rel::Filters, n, Tier::Declared, m, prov);
continue;
}
Cond::Via {
rel,
field,
literal,
negate,
} => {
let op = if negate { "!=" } else { "=" };
let n = self.intern(
Kind::Cond,
format!("cond:out({rel}).{field}{op}{literal}@r{r}"),
);
self.link(m, Rel::Filters, n, Tier::Declared, m, prov);
continue;
}
};
let f = field(self, name);
let n = self.intern(Kind::Cond, format!("cond:{f}{op}{lit}@r{r}"));
Expand Down
33 changes: 33 additions & 0 deletions crates/lance-graph-glove-parity/src/decl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,15 @@ enum DeclCond {
},
/// A predicate method, by label; resolved at preparation.
Ref(String),
/// `out(rel)`.
Reaches(String),
/// `out(rel).has(field, literal)`.
Via {
rel: String,
field: String,
literal: String,
negate: bool,
},
}

#[derive(Debug, Clone, PartialEq, Eq)]
Expand All @@ -83,6 +92,18 @@ impl MethodDecl {
} => Cond::Ne { field, literal },
DeclCond::Field { field, literal, .. } => Cond::Eq { field, literal },
DeclCond::Ref(label) => Cond::Method(MethodRef::Label(label)),
DeclCond::Reaches(rel) => Cond::Reaches { rel },
DeclCond::Via {
rel,
field,
literal,
negate,
} => Cond::Via {
rel,
field,
literal,
negate: *negate,
},
})
.collect()
}
Expand Down Expand Up @@ -650,6 +671,18 @@ fn traversal(
negate: true,
},
Cond::Method(MethodRef::Label(l)) => DeclCond::Ref(l.to_owned()),
Cond::Reaches { rel } => DeclCond::Reaches(rel.to_owned()),
Cond::Via {
rel,
field,
literal,
negate,
} => DeclCond::Via {
rel: rel.to_owned(),
field: field.to_owned(),
literal: literal.to_owned(),
negate,
},
Cond::Method(MethodRef::Key(_)) => unreachable!("text names methods by label"),
})
.collect();
Expand Down
Loading
Loading