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
4 changes: 2 additions & 2 deletions .claude/skills/t27f/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ the reels. Describing T27F as #1677 is the error this section exists to stop (ow
|---|---|---|
| Spec -> graph IR schema (the core) | `specs/t27f/spec_graph.t27` (t27#10020) | spec with tests: node / edge kinds, labels, spans, `validate_graph`, line -> node mapping, node -> ui_plan widget, one hand-read fixture (`specs/tutorial/08_modules_and_arrays.t27`) |
| Graph view: layout of a spec graph | `specs/t27f/spec_graph_view.t27` (t27#10034) | spec with tests: one column per node kind, size / colour / dash / row order from the inference label, UNKNOWN nodes alone in a band, every edge with a source line, node -> widget through `node_widget`; numbers only, laid out on the hand-read fixture; drawn by the MCP tool `t27f_graph` (gHashTag/t27-mcp#9) |
| Spec -> graph IR exporter | `specs/t27f/spec_graph_export.t27` (t27#10162) | spec with tests: `export_graph(path, src)` is a deterministic byte scanner over the source, `export_verdict` is `validate_graph` of the result. One node per top-level declaration (a leading `pub ` is allowed), spans to the matching closing brace, every node and edge EXACT or (unclassifiable) OPAQUE + UNKNOWN, CALLS / USES / CONSTRAINS edges with the line of the use, refusal past 20 nodes or 24 edges. On the tutorial 08 snapshot it equals the hand-read fixture on all 14 declaration nodes and all 9 EXACT edges; it does not emit the fixture's `std.testing` UNKNOWN node, nor its 7 DERIVED edges (4 data flow, 3 tested_by), because a scanner never derives. Limits: textual matching without scope or type resolution, no block comments, no struct/enum bodies, no cross-spec `use` resolution, capacity of IR version 1 (most real specs are refused), takes a `[]const u8` slice rather than `[65536]u8` plus a length. Not wired to a host: the MCP tool does not call it. AST/IR audit is trinity#1650, pilot choice #1651 |
| Spec -> graph IR exporter | `specs/t27f/spec_graph_export.t27` (t27#10162) | spec with tests: `export_graph(path, src)` is a deterministic byte scanner over the source, `export_verdict` is `validate_graph` of the result. One node per top-level declaration (a leading `pub ` is allowed), spans to the matching closing brace, every node and edge EXACT or (unclassifiable) OPAQUE + UNKNOWN, CALLS / USES / CONSTRAINS edges with the line of the use, refusal past 192 nodes or 254 edges. On the tutorial 08 snapshot it equals the hand-read fixture on all 14 declaration nodes and all 9 EXACT edges; it does not emit the fixture's `std.testing` UNKNOWN node, nor its 7 DERIVED edges (4 data flow, 3 tested_by), because a scanner never derives. Limits: textual matching without scope or type resolution, no block comments, no struct/enum bodies, no cross-spec `use` resolution, capacity 192 nodes and 254 edges (about 98 percent of specs in specs/ fit; the rest are refused), takes a `[]const u8` slice rather than `[65536]u8` plus a length. Not wired to a host: the MCP tool does not call it. AST/IR audit is trinity#1650, pilot choice #1651 |
| UI plan IR + trusted widget catalog | `specs/t27f/ui_plan.t27` (t27#9962) | spec with tests; no renderer draws a plan yet |
| Honeycomb focus+context geometry | `specs/t27f/reel.t27` | spec; stills drawn by the MCP tool `t27f_view` |
| Renderer in the Trinity UI | gHashTag/trinity | **missing**; no T27F widget is live |
Expand All @@ -85,7 +85,7 @@ is not progress on the core.
row of that table. Nothing is drawn that the table does not back.
4. Say plainly that this is a manual reading unless a graph came out of `export_graph`
(`specs/t27f/spec_graph_export.t27`). The exporter reads declarations, spans and exact
CALLS / USES / CONSTRAINS edges only, within 20 nodes and 24 edges; DERIVED edges (tested_by,
CALLS / USES / CONSTRAINS edges only, within 192 nodes and 254 edges; DERIVED edges (tested_by,
data flow), UNKNOWN regions and anything larger are still a manual reading, and no host runs
the exporter yet. Name that gap rather than hide it.

Expand Down
107 changes: 87 additions & 20 deletions specs/t27f/spec_graph.t27
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,18 @@ pub const EDGE_FROM_KINDS : [5]u32 = [60, 61, 4, 16, 198];
pub const EDGE_TO_KINDS : [5]u32 = [4, 66, 56, 6, 188];

/// Capacity. Line 0 means "no source line": a span with line_start 0 is no span.
pub const MAX_NODES : u32 = 20;
pub const MAX_EDGES : u32 = 24;
/// Measured 2026-10-18 on the Railway lab, over the 2204 specs/**/*.t27 files of gHashTag/t27
/// master (t27#10173 tree): top-level declarations per spec (nodes) p50 20, p90 66, p95 96,
/// p98 183, p99 436, max 678. Call/use/constrain edges, read through the exporter on an 86-spec
/// stratified sample: p50 at most 12, p95 at most 128 (82 of 86 specs), 85 of 86 at most 192, the
/// largest between 193 and 256. The caps cover the node p98 and the edge p95. Node and edge
/// endpoints are u8 and NO_INDEX is 255, so both caps must stay below 255; MAX_EDGES is the
/// largest edge count that does, MAX_NODES is the 192 that covers p98 under the same
/// limit. A larger graph is refused, never truncated (validate_graph).
pub const MAX_NODES : u32 = 192;
pub const MAX_EDGES : u32 = 254;
pub const MEASURED_NODES_P95 : u32 = 96;
pub const MEASURED_EDGES_P95 : u32 = 128;
pub const NO_LINE : u32 = 0;
pub const NO_INDEX : u32 = 255;

Expand Down Expand Up @@ -143,17 +153,17 @@ pub struct SpecGraph {
spec_path: str,
line_count: u32,
node_count: u32,
node_kind: [20]u8,
node_label: [20]u8,
node_name: [20]str,
line_start: [20]u32,
line_end: [20]u32,
node_kind: [192]u8,
node_label: [192]u8,
node_name: [192]str,
line_start: [192]u32,
line_end: [192]u32,
edge_count: u32,
edge_kind: [24]u8,
edge_from: [24]u8,
edge_to: [24]u8,
edge_label: [24]u8,
edge_line: [24]u32,
edge_kind: [254]u8,
edge_from: [254]u8,
edge_to: [254]u8,
edge_label: [254]u8,
edge_line: [254]u32,
}

/// One node read out of a graph: the record a node inspector shows.
Expand Down Expand Up @@ -501,7 +511,26 @@ pub fn all_bindings_trusted() -> bool {

/// An empty graph of the current version: no path, no nodes, no edges.
pub fn blank_graph() -> SpecGraph {
return SpecGraph { ir_version: GRAPH_IR_VERSION, spec_path: "", line_count: 0, node_count: 0, node_kind: [0; 20], node_label: [0; 20], node_name: ["", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", ""], line_start: [0; 20], line_end: [0; 20], edge_count: 0, edge_kind: [0; 24], edge_from: [0; 24], edge_to: [0; 24], edge_label: [0; 24], edge_line: [0; 24] };
return SpecGraph {
ir_version: GRAPH_IR_VERSION, spec_path: "", line_count: 0, node_count: 0,
node_kind: [0; 192], node_label: [0; 192],
node_name: [
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", ""
],
line_start: [0; 192], line_end: [0; 192],
edge_count: 0, edge_kind: [0; 254], edge_from: [0; 254], edge_to: [0; 254], edge_label: [0; 254], edge_line: [0; 254]
};
}

/// g with one more node. Past MAX_NODES the node is dropped and node_count still counts it, so
Expand Down Expand Up @@ -717,24 +746,59 @@ test header_rules_come_first {

test capacity_is_refused_not_truncated {
var g : SpecGraph = tutorial_08_graph();
var i : u32 = 0;
while (i < 6) {
while (g.node_count < MAX_NODES + 1) {
g = with_node(g, NODE_CONST, LABEL_DERIVED, "extra", NO_LINE, NO_LINE);
i = i + 1;
}
// 21 nodes declared, 20 stored: the graph is refused, not cut short
// MAX_NODES + 1 nodes declared, MAX_NODES stored: the graph is refused, not cut short
assert g.node_count == MAX_NODES + 1;
assert validate_graph(g) == ERR_TOO_MANY_NODES;
var h : SpecGraph = tutorial_08_graph();
var j : u32 = 0;
while (j < 9) {
while (h.edge_count < MAX_EDGES + 1) {
h = with_edge(h, EDGE_TESTED_BY, 9, 13, LABEL_DERIVED, NO_LINE);
j = j + 1;
}
assert h.edge_count == MAX_EDGES + 1;
assert validate_graph(h) == ERR_TOO_MANY_EDGES;
}

test a_graph_exactly_at_capacity_is_valid {
// 16 fns and 16 tests after the fixture's 15 nodes, then consts up to MAX_NODES; every
// test calls every fn, so the CALLS edges are distinct (a repeated edge is a duplicate)
var g : SpecGraph = tutorial_08_graph();
var a : u32 = 0;
while (a < 16) {
g = with_node(g, NODE_FN, LABEL_EXACT, "f", 1, 1);
a = a + 1;
}
a = 0;
while (a < 16) {
g = with_node(g, NODE_TEST, LABEL_EXACT, "t", 1, 1);
a = a + 1;
}
while (g.node_count < MAX_NODES) {
g = with_node(g, NODE_CONST, LABEL_EXACT, "extra", 1, 1);
}
var t : u32 = 0;
while (t < 16) {
var f : u32 = 0;
while (f < 16) {
if (g.edge_count < MAX_EDGES) {
g = with_edge(g, EDGE_CALLS, (31 + t) as u8, (15 + f) as u8, LABEL_EXACT, 1);
}
f = f + 1;
}
t = t + 1;
}
assert g.node_count == MAX_NODES;
assert g.edge_count == MAX_EDGES;
assert validate_graph(g) == GRAPH_OK;
// the last stored node and edge are real cells, not clipped
assert g.node_kind[MAX_NODES as usize - 1] == NODE_CONST;
assert g.edge_kind[MAX_EDGES as usize - 1] == EDGE_CALLS;
// negative control: one more edge or node makes the same graph refused
assert validate_graph(with_edge(g, EDGE_CALLS, 31, 15, LABEL_EXACT, 1)) == ERR_TOO_MANY_EDGES;
assert validate_graph(with_node(g, NODE_CONST, LABEL_EXACT, "extra", 1, 1)) == ERR_TOO_MANY_NODES;
}

test node_rules {
var k : SpecGraph = tutorial_08_graph();
k.node_kind[3] = NODE_KINDS;
Expand Down Expand Up @@ -1000,6 +1064,9 @@ invariant reserved_codes_stay_outside {
assert NO_CODE >= EDGE_KINDS;
assert NO_CODE >= LABEL_KINDS;
assert MAX_NODES < NO_INDEX;
assert MAX_EDGES < NO_INDEX;
assert MAX_NODES >= MEASURED_NODES_P95;
assert MAX_EDGES >= MEASURED_EDGES_P95;
assert (NODE_KINDS as u32) < 32;
assert ERR_DUPLICATE_EDGE + 1 == VERDICT_KINDS;
}
Loading
Loading