Skip to content
Closed
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
2 changes: 1 addition & 1 deletion .claude/board/STATUS_BOARD.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Plan: `.claude/plans/software-awareness-graph-v1.md`. No new opcode, Quack varia
| D-SAG-1-1 | typed schema + projection of prepared methods with tiers and provenance | In PR | `tests/awareness.rs` projection test; one identity, one kind |
| D-SAG-1-2 | queries A–D through Quack; compiler verdicts as edges | In PR | answers == the programs; falsifiers 1–10, each disable-verified |
| D-SAG-1-3 | H1 candidate + tier-labelled measurement; MDL measure with counterexample | In PR | `examples/awareness_loop.rs`; MDL test |
| D-SAG-1-4 | multi-hop dependency via relation carrier / factored `EqU32Via` | Queued (needs D-CMH-G1-4) | two-hop answer == closure computed from the programs |
| D-SAG-1-4 | multi-hop dependency: `SoftwareGraph::hops(start, path)`, one Quack program per hop, survivors passed as a value set (`in_u32`), never as a produced plane | In PR | 4 paths (up to 4 hops) == row loop; follows a reprojection; 4 disable runs red. Value-set transfer reads every edge row; skipping to survivors needs a Lance index or the order witness |
| D-SAG-1-5 | G1 `CALLS` edges + method-reference refusals | In PR | `method_references_are_calls_edges_and_cycles_are_the_compilers`; callers via Quack; `MethodCycle` recorded, never re-derived |
| D-SAG-1-6 | persist as a Lance dataset; source revision from git; canonical body digest | Queued | reload equals projection; toolchain change keeps digests |

Expand Down
20 changes: 20 additions & 0 deletions .claude/board/entries/2026-10-12-sag-multi-hop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# 2026-10-12 — D-SAG-1-4 — multi-hop questions over the software graph

**Status:** In PR (stacked on #1519).

`SoftwareGraph::hops(start, path)` walks `(relation, direction)` hops with set
semantics per hop. Each hop is one Quack program over the resident edge lanes,
`rel = R AND from IN (S)`, keeping its rows. The previous hop's survivors arrive
as a value set the size of those survivors; no hop reads a plane another
program produced. This is the operator's reading for chained hops: the masks
of one query fold into one materialisation, each hop filtered at the source.

MEASURED (tests/awareness.rs): co-readers, field → claim, a four-hop claim
co-dependency and a four-hop shared-primitive path equal a row loop over the
edges; a hop path follows a reprojection; four disable runs red (relation
filter, direction, per-hop dedup, unknown start).

OPEN: a value set still reads every edge row. Skipping straight to the
survivors' rows needs a Lance B-tree filter (D-TELEPORT-2 arm Db) or address
ranges over a key-ordered projection, which waits on
ISS-WITNESSED-RANGE-DOES-NOT-ATTEST-PLANE-ORDER.
21 changes: 21 additions & 0 deletions .claude/board/entries/2026-10-12-sag-reprojection-defects.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# 2026-10-12 — D-SAG-1R — SAG reprojection defects A and B, found by #1510

**Status:** In PR. **Found by:** #1510's audit of #1509.

A: `SoftwareGraph::link` deduplicated on `(src, rel, dst, tier)`, so a fact two
methods share (the claim behind a field both read) belonged to whichever wrote
it first. When that method stopped reading the field, its reprojection
retracted the fact for both. `ctx` is now part of the key; `out` and
`capability` read each fact once.

B: `project_compute` never retracted the DAG it replaced. Cyclic then acyclic
kept the `ComputeCycle` refusal and the stale `ComputesFrom` edges. It now
retracts its own class's compute edges and verdict first.

MEASURED (tests/awareness.rs): for every method, reprojecting it with a body
that reads nothing gives the same facts as a fresh projection; three DAG
sequences give the expected edge count and verdict, the other class untouched.
Three disable runs red on the tests that name each guard.

OPEN: C, `certify` accepts any test name. Who may issue a verified receipt is
an operator decision, not a code fix.
4 changes: 3 additions & 1 deletion .claude/board/entries/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,13 @@ 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.

317 entries, 2026-08-06 .. 2026-10-12.
319 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 | `D-SAG-1R` | SAG reprojection defects A and B, found by #1510 | [2026-10-12-sag-reprojection-defects.md](2026-10-12-sag-reprojection-defects.md) |
| 2026-10-12 | `D-SAG-1-4` | multi-hop questions over the software graph | [2026-10-12-sag-multi-hop.md](2026-10-12-sag-multi-hop.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) |
Expand Down
136 changes: 122 additions & 14 deletions crates/lance-graph-glove-parity/src/awareness.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ use lance_graph_mask_risc::{
Operand, Planes, Pred, Program, Scratch, Terminal,
};
use lance_graph_quack::bind::{BindError, Binder, BoundField, Draft, FieldKind, TableId};
use lance_graph_quack::{lower, Agg, Col, LowerError, Mask, Query};
use lance_graph_quack::{lower, Agg, Cmp, Col, Filter, LowerError, Mask, Query};

use crate::basis::{FieldMap, Grade};
use crate::method::{
Expand Down Expand Up @@ -104,6 +104,14 @@ pub enum Rel {
VerifiedBy,
}

/// Which end of an edge a hop leaves from: `Out` follows `src -> dst`,
/// `In` follows `dst -> src`.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Dir {
Out,
In,
}

/// How an edge is known.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Tier {
Expand Down Expand Up @@ -518,12 +526,18 @@ impl SoftwareGraph {
(self.nodes.len() - 1) as u32
}

/// Add an edge unless the same `(src, rel, dst, tier)` is already there.
/// Add an edge unless the same `(src, rel, dst, tier, ctx)` is already
/// there.
///
/// `ctx` is part of the key: a fact two methods contribute (the claim
/// behind a field both read) is one row per method, so a method's
/// reprojection retracts only its own row and the other method's
/// survives. Readers that ask for facts ([`Self::out`]) see one fact.
pub fn link(&mut self, src: u32, rel: Rel, dst: u32, tier: Tier, ctx: u32, prov: u32) {
let dup = self
.edges
.iter()
.any(|e| (e.src, e.rel, e.dst, e.tier) == (src, rel, dst, tier));
.any(|e| (e.src, e.rel, e.dst, e.tier, e.ctx) == (src, rel, dst, tier, ctx));
if !dup {
self.edges.push(Edge {
src,
Expand All @@ -536,17 +550,21 @@ impl SoftwareGraph {
}
}

/// The edges `src REL ?`, any tier.
/// The facts `src REL ?`, any tier, each once however many contexts
/// wrote it, in first-written order.
#[must_use]
pub fn out(&self, src: &str, rel: Rel) -> Vec<(&str, Tier)> {
let Some(s) = self.node(src) else {
return Vec::new();
};
self.edges
.iter()
.filter(|e| e.src == s && e.rel == rel)
.map(|e| (self.nodes[e.dst as usize].ident.as_str(), e.tier))
.collect()
let mut v: Vec<(&str, Tier)> = Vec::new();
for e in self.edges.iter().filter(|e| e.src == s && e.rel == rel) {
let f = (self.nodes[e.dst as usize].ident.as_str(), e.tier);
if !v.contains(&f) {
v.push(f);
}
}
v
}

/// The current body digest of a projected method.
Expand Down Expand Up @@ -804,6 +822,15 @@ impl SoftwareGraph {
prov: u32,
) {
let class = self.intern(Kind::Class, format!("class:{concept:#06x}"));
// A class's DAG projection replaces its earlier one: its edges and
// its cycle verdict described the DAG it had then. Other facts
// written in the class's context (a note from it) are kept.
let cycle = self.node("refusal:ComputeCycle");
self.edges.retain(|e| {
e.ctx != class
|| !(e.rel == Rel::ComputesFrom
|| (e.rel == Rel::RefusedFor && Some(e.dst) == cycle))
});
let name = |p: u8| {
basis
.get(usize::from(p))
Expand Down Expand Up @@ -939,6 +966,84 @@ impl SoftwareGraph {
.collect())
}

/// The nodes reached from `start` along `path`, each hop over the edges
/// of one relation in one direction, any tier. Set semantics per hop:
/// each node once, in node order.
///
/// Each hop is one Quack program over the resident edge lanes:
/// `rel = R AND from IN (S)`, keeping its rows. The previous hop's
/// survivors reach it as a VALUE set `S` the size of those survivors,
/// read from the kept rows' `to` lane; no hop reads a plane another
/// program produced. An empty set ends the walk without running a
/// program.
///
/// # Errors
///
/// [`QueryError::Bind`] for a start node the graph does not have (never
/// an empty answer); otherwise lowering or execution errors.
pub fn hops(&self, start: &[&str], path: &[(Rel, Dir)]) -> Result<Vec<&str>, QueryError> {
let mut set: Vec<u32> = start
.iter()
.map(|s| {
self.node(s).ok_or_else(|| {
QueryError::Bind(BindError::UnknownValue {
field: "src".into(),
value: (*s).into(),
})
})
})
.collect::<Result<_, _>>()?;
set.sort_unstable();
set.dedup();
let (cols, live) = self.columns();
let n = self.edges.len();
for &(rel, dir) in path {
if set.is_empty() {
break;
}
let (from, to) = match dir {
Dir::Out => (SRC, DST),
Dir::In => (DST, SRC),
};
let q = Query {
filter: Filter::and([
Filter::plane(Mask(0)),
Filter::cmp(Col(REL), Cmp::EqU32(rel_code(rel))),
Filter::in_u32(Col(from), set.iter().copied()),
]),
agg: Agg::Rows,
};
let program = lower(&q).map_err(QueryError::Lower)?;
let lanes: Vec<LaneRef<'_>> = cols.iter().map(|c| LaneRef::U32(c)).collect();
let planes = Planes {
n_rows: n,
masks: &[live.as_slice()],
lanes: &lanes,
};
let mut scratch = Scratch::for_program(&program, n).map_err(QueryError::Exec)?;
let mut kept = vec![0u64; words_for(n)];
execute_into(
&program,
&planes,
&Foreign::NONE,
&mut scratch,
lance_graph_mask_risc::Out::Mask(&mut kept),
)
.map_err(QueryError::Exec)?;
let mut next: Vec<u32> = materialize_rows(&kept, n)
.into_iter()
.map(|r| cols[to as usize][r])
.collect();
next.sort_unstable();
next.dedup();
set = next;
}
Ok(set
.into_iter()
.map(|i| self.nodes[i as usize].ident.as_str())
.collect())
}

/// Query A: the methods whose compiled programs use `primitive`.
///
/// # Errors
Expand Down Expand Up @@ -996,11 +1101,14 @@ impl SoftwareGraph {
return CapStatus::Unknown;
};
let into = |p: &dyn Fn(&Edge) -> bool| -> Vec<String> {
self.edges
.iter()
.filter(|e| e.dst == c && p(e))
.map(|e| self.nodes[e.src as usize].ident.clone())
.collect()
let mut v: Vec<String> = Vec::new();
for e in self.edges.iter().filter(|e| e.dst == c && p(e)) {
let s = &self.nodes[e.src as usize].ident;
if !v.contains(s) {
v.push(s.clone());
}
}
v
};
if !into(&|e| e.rel == Rel::UsesPrimitive && e.tier == Tier::Compiled).is_empty() {
return CapStatus::Shipped;
Expand Down
Loading
Loading