diff --git a/.claude/skills/t27f/SKILL.md b/.claude/skills/t27f/SKILL.md index 918e37c92f..a77a6ab080 100644 --- a/.claude/skills/t27f/SKILL.md +++ b/.claude/skills/t27f/SKILL.md @@ -81,7 +81,7 @@ the diagram core in detail, then the other surfaces and the topic-map applicatio | 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) | | Graph view for big specs: wrap, fold, expand, hit | `specs/t27f/spec_graph_fold.t27` (t27#10218) | spec with tests: a kind column taller than N = 12 rows wraps into sub-columns so the canvas aspect is near phi (identical to the view when nothing wraps); a fold to one box per kind with the exact count and one arc per pair of kinds with the exact edge count, each labelled by its weakest member; `expand_graph` = one kind plus its direct neighbours, copied verbatim with an origin map; `wrap_hit` / `wrap_hit_line_start` / `wrap_hit_line_end` take a click to a node and its lines; numbers only, the view's colours are not touched | -| Dependencies view: the specs around one spec | `specs/t27f/spec_deps.t27` (t27#10680) | spec with tests: reads `t27c use-edges --specs-dir specs`; layer 0 is the focus, +k the specs k imports away (right), -k the specs k importers away (left), each spec once in its shortest-path layer; every edge-list line between two drawn specs is one `exact` edge, a repeated line drawn once; `cycle=1` when the focus imports itself or is imported by a spec in its own import closure; a walk past the caller's room or depth is refused whole (`refused\tfull` / `refused\tdeep`), never cut. A `use` line t27c does not resolve is not in the edge list, so `deps_unknown` (t27#10702) reads the focus source and writes each such line as `unknown\tpath`, mirroring `use_resolve.rs` (whole path, then one fallback level) without looking for a file; on master f33f269d5 that was 23 lines in 13 specs, byte for byte the `t27c gen` warnings. Limits: the view flags only the focus, and `deps_cycles` (t27#10693) lists every spec on a cycle, each once, refused whole (`cap + 1`) if anything does not fit; a spec on a cycle through the focus is drawn on both sides; no host draws it yet. Corpus on master e607228f7: 806 specs, 2461 edge lines, nothing refused at depth 32, deepest walks 6, one real cycle (igla race `backend` <-> `rtl`), which `deps_cycles` lists exactly on master f7efbc232 and lists as empty with #10689 | +| Dependencies view: the specs around one spec | `specs/t27f/spec_deps.t27` (t27#10680) | spec with tests: reads `t27c use-edges --specs-dir specs`; layer 0 is the focus, +k the specs k imports away (right), -k the specs k importers away (left), each spec once in its shortest-path layer; every edge-list line between two drawn specs is one `exact` edge, a repeated line drawn once; `cycle=1` when the focus imports itself or is imported by a spec in its own import closure; a walk past the caller's room or depth is refused whole (`refused\tfull` / `refused\tdeep`), never cut. A `use` line t27c does not resolve is not in the edge list, so `deps_unknown` (t27#10702) reads the focus source and writes each such line as `unknown\tpath`, mirroring `use_resolve.rs` (whole path, then one fallback level) without looking for a file; on master f33f269d5 that was 23 lines in 13 specs, byte for byte the `t27c gen` warnings. `deps_why a b` (t27#10705) writes one shortest chain of edge-list lines from a to b, each `exact`, a tie taken in edge-list order, `none` when a does not reach b, refused whole when it does not fit. Limits: the view flags only the focus, and `deps_cycles` (t27#10693) lists every spec on a cycle, each once, refused whole (`cap + 1`) if anything does not fit; a spec on a cycle through the focus is drawn on both sides; no host draws it yet. Corpus on master e607228f7: 806 specs, 2461 edge lines, nothing refused at depth 32, deepest walks 6, one real cycle (igla race `backend` <-> `rtl`), which `deps_cycles` lists exactly on master f7efbc232 and lists as empty with #10689 | | 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 1024 nodes or 4096 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; the derive pass below adds them. Limits: textual matching without scope or type resolution, no block comments, no struct/enum bodies, no cross-spec `use` resolution, capacity 1024 nodes and 4096 edges, u16 indices (all 2225 specs in specs/ fit, largest 775 nodes and 3194 edges after derive; anything past the cap is refused, never truncated), takes a `[]const u8` slice rather than `[65536]u8` plus a length. AST/IR audit is trinity#1650, pilot choice #1651 | | Graph IR derive pass | `specs/t27f/spec_graph_derive.t27` (t27#10191) | spec with tests: `derive_graph(g, src)` appends what the exporter must not claim and changes nothing it holds. R1 DERIVED `tested_by` = every EXACT CALLS edge from a test, invariant or bench to a fn read backwards (no line). R2 DERIVED `data_flow` = a const or `use` that an EXACT USES edge ties to a fn and that is named inside one of the fn's return statements (from `return` to the next `;`), on the line of the first such name. R3 UNKNOWN opaque node = an undeclared qualified call target of three or more segments (`std.testing.expectEqual(` names `std.testing`), spanning the lines that call through it. Exporter plus derive equal the tutorial 08 fixture as sets: 15 of 15 nodes, 16 of 16 edges, no special case. Limits: names matched as text; call results and flow through locals are not derived; R3 needs 3 or more segments (two cannot be told from a method call); a graph that does not validate, or a source of another line count, comes back unchanged; past 1024 nodes or 4096 edges the result is refused with the too-many verdict. Not wired to a host: the MCP tool does not call it | | UI plan IR + trusted widget catalog | `specs/t27f/ui_plan.t27` (t27#9962) | spec with tests; no renderer draws a plan yet | @@ -166,6 +166,7 @@ Traps from this tick (ffmpeg on the lab): - The lab cgroup has `pids.max` 1000, and a graph with many `drawtext` filters starts 400 to 700 threads. Cap them with `-threads 2 -filter_threads 2 -filter_complex_threads 2` and `-threads 1` on the output. Never run two renders at once. "ff_frame_thread_encoder_init failed" means the cap was hit. Look for a stuck ffmpeg and kill it by pid. - lavfi rejects `d=.900`. Print the duration as `$((m/1000)).$(printf %03d $((m%1000)))`. - `pkill -f` on the loop script leaves its child renders running. Find them with `ps -eo pid,args | grep '[s]h xf'` and kill them by pid. +- Through `fs-lab.sh`, `pkill -f 'why_seal'` also matches the ssh command line that carries it and kills your own session; the output comes back empty. Bracket the first letter (`pkill -f '[w]hy_seal'`) so the pattern does not match itself. A killed session leaves the detached ratchets it started running, so several end up writing one log; list them with `ps -eo pid,etimes,args | grep '[c]orpus-only'` before you read a verdict. **Holding the address when the voice runs long (t27#10629).** If an overlay must stay on screen for a minimum time and must also stay off a crossfade, then the crossfade has to move; the overlay cannot. Work out in frames the latest cut that still leaves the last scene its minimum: cut 1 is capped at `BEAT_CUT1_LAST_MS` = 13249. Prove it is the latest with a one-ms-later control. Only the voices the reel speeds up are affected, so the `cmp` diff is limited to those voices and to crossfade 1's offset. The cost is real: the crossfade starts up to 184 ms under the task's last word, and lesson 1's measured voice (1300/4200/6800) is one of the affected voices. Write a test that bounds that overlap. @@ -383,6 +384,27 @@ for byte. `specunknownX` in `git ls-files` order, which is the order gen ran in. 3. Controls: the same list minus one line must FAIL, and so must `w == 0`. On master f33f269d5 it was 23 lines in 13 specs (#7176 had counted 119 in 70), and both controls failed. +4. All 13 of those specs fail `t27c test-report` for other reasons before the dropped `use` + matters (`std::*` imports, an undeclared `gf32_from_f64`, `Float`, Rust syntax). Repairing a + dropped import alone therefore fixes none of them. Classify a spec before you promise a fix. + +### Check one chain per reached spec (t27#10705) + +`deps_why a b` answers the question that `cargo tree -i`, `npm explain` and `pnpm why` answer: +why does a import b. It writes one shortest chain of edge-list lines. The corpus check holds it +to three properties: + +1. Over the `use-edges` list, call `deps_why focus x` for every spec x the focus reaches. The + chain must have exactly `layer(x)` hops, every hop must be an edge-list line, and each hop + must start where the last one ended: at the focus first, at x last. Count the specs checked + and compare with `deps_count` of the walk, so no spec is skipped. +2. Control: expect one hop more (`zz_check(1)`). It must FAIL. +3. Mutation: drop the last edge from the chain. Two unit tests must FAIL. + +Build the scratch spec by concatenation (head of the template, the escaped edges, then the +tail). A `sed s#@EDGES@#...#` with 2466 lines fails with "Argument list too long". A focus with +no outgoing edge, such as `verified/reuse`, checks only itself, so `> 1` fails for it; that is +the probe, not the function. ## What this skill does not claim diff --git a/.trinity/seals/t27f_t27f_spec_deps.json b/.trinity/seals/t27f_t27f_spec_deps.json index 861fdba625..e9a0a1a744 100644 --- a/.trinity/seals/t27f_t27f_spec_deps.json +++ b/.trinity/seals/t27f_t27f_spec_deps.json @@ -15,23 +15,23 @@ } ], "config": "gen=c,rust,verilog,zig;tests=zig", - "gen_hash_c": "sha256:a827ac501c5bc3a6cb34fb9eed1a765efaf6c65dcaef25c64ff4ab34e053f47f", - "gen_hash_rust": "sha256:ecd73ef257fce64599a99a522559ac91b0fe6a1a364863f163584bdd253e0673", - "gen_hash_verilog": "sha256:47a568b333eb16e44c62b907f678ab98e42ea629611d36a2d52edd91a95e4023", - "gen_hash_zig": "sha256:8082c63e31383157aaa1771ec8d4feaf6b1090da42127267c41b8a8afad06251", + "gen_hash_c": "sha256:cf159312ea60356819669f1af08181d3fa7698777fc89621d64814413c9335f6", + "gen_hash_rust": "sha256:912f179d20bcb6f7af05cb7688e3b095fb2ced9c9b1449edb6322d2d3c413e61", + "gen_hash_verilog": "sha256:4950c28e69b7b88e812544714f55a9814f40eb4e94296ec5dc981759630b2750", + "gen_hash_zig": "sha256:963538cd84850fba1814350998a496856a3ba35709ab952b5f9b2965602fdcda", "module": "t27f_spec_deps", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-18T22:58:06Z", + "sealed_at": "2026-10-18T23:14:31Z", "sealed_by": "t27c-bootstrap@0.5.2", - "spec_hash": "sha256:c7b44f43776b8a7b0d21aee8a73502e996ec9fb35cd56935bd300b4358f8d4be", + "spec_hash": "sha256:c6e6c03537dd7238ce7cceb937dfe307294ab59d4946de95f7d96e40333cb070", "spec_path": "specs/t27f/spec_deps.t27", "tests": { "failed": 0, "failing": [], "forced": false, - "passed": 25, - "total": 25, + "passed": 31, + "total": 31, "vacuous": 0 }, "toolchain": { diff --git a/specs/t27f/spec_deps.t27 b/specs/t27f/spec_deps.t27 index ab0659e6c0..5add6471c0 100644 --- a/specs/t27f/spec_deps.t27 +++ b/specs/t27f/spec_deps.t27 @@ -17,6 +17,8 @@ ; the list repeats it, and labelled exact: t27c resolved it, nothing here is inferred; ; - the focus is flagged as on a cycle when it imports itself or a spec it imports imports it. ; deps_cycles asks the same question of every importer and lists each spec flagged, once; +; - deps_why a b is one shortest chain of edge-list lines from a to b, each exact, a tie taken in +; edge-list order; `none` when a does not reach b; ; - a `use` line of the focus is unknown when the focus has no edge to the spec its path names, nor ; to the one module an item form falls back to (a brace list's module, or the path one level up); ; - a neighbourhood that does not fit the caller's room, or is deeper than the depth asked for, is @@ -45,6 +47,11 @@ ; scratch test: over every spec it writes 23 unknown lines in 13 specs, byte for byte the ; `use X resolves to no spec` warnings `t27c gen` prints for every spec (#7176 counted 119 in 70). ; The control that drops one expected line fails. +; Measured for deps_why on the same kind of scratch test, 2466 edge lines: from specs/t27f/lesson_reel.t27, +; specs/t27f/spec_deps.t27 and specs/claw/t27f_gate.t27 to every spec each reaches, the chain is as +; long as the spec's layer, every hop is a line of the list, and the hops join from the focus to the +; spec. The control that expects one hop more fails. A mutation that takes the last edge into a spec +; instead of the first fails two of the tests below. ; phi^2 + 1/phi^2 = 3 | TRINITY module t27f_spec_deps; @@ -86,15 +93,20 @@ pub const DEPS_UNKNOWN : str = "unknown\t$"; /// The directory `t27c use-edges --specs-dir specs` keys its paths under. pub const DEPS_ROOT : str = "specs$"; -/// Is s[f..t] one of the newline-terminated lines of set[lo..hi]? -pub fn deps_in_range(set: []const u8, lo: usize, hi: usize, s: []const u8, f: usize, t: usize) -> bool { +/// Where s[f..t] starts as one of the newline-terminated lines of set[lo..hi], or hi when it is none. +pub fn deps_find(set: []const u8, lo: usize, hi: usize, s: []const u8, f: usize, t: usize) -> usize { var i : usize = lo; while (i < hi) { const e : usize = line_end(set, i, hi); - if (same(set, i, e, s, f, t)) { return true; } + if (same(set, i, e, s, f, t)) { return i; } i = e + 1; } - return false; + return hi; +} + +/// Is s[f..t] one of the newline-terminated lines of set[lo..hi]? +pub fn deps_in_range(set: []const u8, lo: usize, hi: usize, s: []const u8, f: usize, t: usize) -> bool { + return deps_find(set, lo, hi, s, f, t) < hi; } /// How many lines set[lo..hi] holds. @@ -333,6 +345,91 @@ pub fn deps_view(buf: []const u8, n: usize, focus: []const u8, ff: usize, ft: us return w; } +// ---- why one spec imports another (#10705) ---- +// cargo tree -i, npm explain, pnpm why and Bazel somepath answer the question a layer cannot: through +// which chain of imports does a reach b. + +pub const DEPS_WHY : str = "why\t$"; +pub const DEPS_NONE : str = "\tnone\n$"; + +/// One shortest import chain from a[af..at] to b[bf..bt] over the edge list buf[0..n], into out[0..cap]: +/// whyabhops=N, then N lines edgexyexact, in order from a to b; +/// whyabnone when nothing a imports, transitively, is b; +/// refusedfull|deepa when the walk from a does not fit set[0..scap] or DEPS_MAX_DEPTH. +/// The walk is deps_walk's, so b's layer is the length of its shortest chain. Each step back from b +/// takes the first edge-list line into it from the layer before. Every spec of a layer k > 0 has such +/// a line, since the walk added it from one. hs/he hold the chain's specs as spans of set, one per +/// layer (DEPS_BOUNDS entries). Returns the length, or cap + 1 when the answer does not fit. +pub fn deps_why(buf: []const u8, n: usize, a: []const u8, af: usize, at: usize, b: []const u8, bf: usize, bt: usize, set: []u8, scap: usize, bounds: []usize, hs: []usize, he: []usize, out: []u8, cap: usize) -> usize { + const st : u8 = deps_walk(buf, n, a, af, at, DEPS_IMPORTS, DEPS_MAX_DEPTH, set, scap, bounds); + var w : usize = 0; + if (st != DEPS_OK) { + w = deps_put_lit(out, w, cap, DEPS_REFUSED); + if (st == DEPS_ERR_FULL) { + w = deps_put_lit(out, w, cap, DEPS_WHY_FULL); + } else { + w = deps_put_lit(out, w, cap, DEPS_WHY_DEEP); + } + w = deps_put_span(out, w, cap, a, af, at); + w = put(out, w, cap, '\n'); + if (w > cap) { return cap + 1; } + return w; + } + w = deps_put_lit(out, w, cap, DEPS_WHY); + w = deps_put_span(out, w, cap, a, af, at); + w = put(out, w, cap, '\t'); + w = deps_put_span(out, w, cap, b, bf, bt); + const d : usize = deps_depth(bounds, DEPS_MAX_DEPTH); + var l : usize = 0; + var found : bool = false; + while (l <= d and found == false) { + if (deps_in_range(set, bounds[l], bounds[l + 1], b, bf, bt)) { found = true; } else { l += 1; } + } + if (found == false) { + w = deps_put_lit(out, w, cap, DEPS_NONE); + if (w > cap) { return cap + 1; } + return w; + } + w = deps_put_lit(out, w, cap, "\thops=$"); + w = deps_put_dec(out, w, cap, l); + w = put(out, w, cap, '\n'); + hs[l] = deps_find(set, bounds[l], bounds[l + 1], b, bf, bt); + he[l] = line_end(set, hs[l], bounds[l + 1]); + var k : usize = l; + while (k >= 1) { + var s : usize = 0; + var got : bool = false; + while (s < n and got == false) { + const e : usize = line_end(buf, s, n); + const t : usize = text_end(buf, s, e); + const p : usize = first_tab(buf, s, t); + if (p < t and p > s) { + if (same(buf, p + 1, t, set, hs[k], he[k])) { + const x : usize = deps_find(set, bounds[k - 1], bounds[k], buf, s, p); + if (x < bounds[k]) { + hs[k - 1] = x; + he[k - 1] = line_end(set, x, bounds[k]); + got = true; + } + } + } + s = e + 1; + } + k -= 1; + } + k = 1; + while (k <= l) { + w = deps_put_lit(out, w, cap, DEPS_EDGE); + w = deps_put_span(out, w, cap, set, hs[k - 1], he[k - 1]); + w = put(out, w, cap, '\t'); + w = deps_put_span(out, w, cap, set, hs[k], he[k]); + w = deps_put_lit(out, w, cap, DEPS_EXACT); + k += 1; + } + if (w > cap) { return cap + 1; } + return w; +} + // ---- use lines the edge list does not resolve (#10702) ---- // The resolver is bootstrap/src/use_resolve.rs. These functions read a `use` line the way its // use_path_expr, use_path, brace_module and use_target do. They never look for a file: a line counts @@ -759,3 +856,99 @@ test the_path_reader_matches_the_resolver { assert deps_mod_eq("specs/a/b/c.t27$", 0, 15, DEPS_ROOT, 5, l, 7, 13) == true; assert deps_mod_eq("specs/a/b/c.t2$", 0, 14, DEPS_ROOT, 5, l, 7, 13) == false; } + +// ---- why tests (#10705) ---- + +test why_walks_the_shortest_chain_from_a_spec_to_one_it_imports { + var set : [64]u8 = undefined; + var b : [35]usize = undefined; + var hs : [35]usize = undefined; + var he : [35]usize = undefined; + var out : [256]u8 = undefined; + const g : str = DEPS_FIXTURE; + const w : usize = deps_why(g, lit_len(g), "f", 0, 1, "d", 0, 1, &set, 64, &b, &hs, &he, &out, 256); + const want : str = "why\tf\td\thops=4\nedge\tf\te\texact\nedge\te\ta\texact\nedge\ta\tb\texact\nedge\tb\td\texact\n$"; + assert w == lit_len(want); + assert same(&out, 0, w, want, 0, lit_len(want)) == true; +} + +test why_takes_a_tie_in_edge_list_order { + var set : [64]u8 = undefined; + var b : [35]usize = undefined; + var hs : [35]usize = undefined; + var he : [35]usize = undefined; + var out : [256]u8 = undefined; + const g : str = "a\tc\na\tb\nb\td\nc\td\n$"; + const w : usize = deps_why(g, lit_len(g), "a", 0, 1, "d", 0, 1, &set, 64, &b, &hs, &he, &out, 256); + const want : str = "why\ta\td\thops=2\nedge\ta\tb\texact\nedge\tb\td\texact\n$"; + assert w == lit_len(want); + assert same(&out, 0, w, want, 0, lit_len(want)) == true; + const g2 : str = "a\tc\na\tb\nc\td\nb\td\n$"; + const w2 : usize = deps_why(g2, lit_len(g2), "a", 0, 1, "d", 0, 1, &set, 64, &b, &hs, &he, &out, 256); + const want2 : str = "why\ta\td\thops=2\nedge\ta\tc\texact\nedge\tc\td\texact\n$"; + assert w2 == lit_len(want2); + assert same(&out, 0, w2, want2, 0, lit_len(want2)) == true; +} + +test why_a_spec_reaches_itself_in_no_hops { + var set : [64]u8 = undefined; + var b : [35]usize = undefined; + var hs : [35]usize = undefined; + var he : [35]usize = undefined; + var out : [256]u8 = undefined; + const g : str = DEPS_FIXTURE; + const w : usize = deps_why(g, lit_len(g), "a", 0, 1, "a", 0, 1, &set, 64, &b, &hs, &he, &out, 256); + const want : str = "why\ta\ta\thops=0\n$"; + assert w == lit_len(want); + assert same(&out, 0, w, want, 0, lit_len(want)) == true; +} + +test negative_control_why_says_none_against_the_edges_and_off_the_graph { + var set : [64]u8 = undefined; + var b : [35]usize = undefined; + var hs : [35]usize = undefined; + var he : [35]usize = undefined; + var out : [256]u8 = undefined; + const g : str = DEPS_FIXTURE; + const w : usize = deps_why(g, lit_len(g), "d", 0, 1, "a", 0, 1, &set, 64, &b, &hs, &he, &out, 256); + const want : str = "why\td\ta\tnone\n$"; + assert w == lit_len(want); + assert same(&out, 0, w, want, 0, lit_len(want)) == true; + const w2 : usize = deps_why(g, lit_len(g), "a", 0, 1, "x", 0, 1, &set, 64, &b, &hs, &he, &out, 256); + const want2 : str = "why\ta\tx\tnone\n$"; + assert w2 == lit_len(want2); + assert same(&out, 0, w2, want2, 0, lit_len(want2)) == true; +} + +test why_crosses_a_cycle_both_ways { + var set : [64]u8 = undefined; + var b : [35]usize = undefined; + var hs : [35]usize = undefined; + var he : [35]usize = undefined; + var out : [256]u8 = undefined; + const g : str = "p\tq\nq\tp\n$"; + const w : usize = deps_why(g, lit_len(g), "p", 0, 1, "q", 0, 1, &set, 64, &b, &hs, &he, &out, 256); + const want : str = "why\tp\tq\thops=1\nedge\tp\tq\texact\n$"; + assert w == lit_len(want); + assert same(&out, 0, w, want, 0, lit_len(want)) == true; + const w2 : usize = deps_why(g, lit_len(g), "q", 0, 1, "p", 0, 1, &set, 64, &b, &hs, &he, &out, 256); + const want2 : str = "why\tq\tp\thops=1\nedge\tq\tp\texact\n$"; + assert w2 == lit_len(want2); + assert same(&out, 0, w2, want2, 0, lit_len(want2)) == true; +} + +test negative_control_a_why_that_does_not_fit_is_refused_whole { + var set : [64]u8 = undefined; + var b : [35]usize = undefined; + var hs : [35]usize = undefined; + var he : [35]usize = undefined; + var out : [256]u8 = undefined; + const g : str = DEPS_FIXTURE; + const full : usize = deps_why(g, lit_len(g), "f", 0, 1, "d", 0, 1, &set, 64, &b, &hs, &he, &out, 256); + assert deps_why(g, lit_len(g), "f", 0, 1, "d", 0, 1, &set, 64, &b, &hs, &he, &out, full - 1) == full; + assert deps_why(g, lit_len(g), "f", 0, 1, "d", 0, 1, &set, 64, &b, &hs, &he, &out, full) == full; + const w : usize = deps_why(g, lit_len(g), "f", 0, 1, "d", 0, 1, &set, 5, &b, &hs, &he, &out, 256); + const want : str = "refused\tfull\tf\n$"; + assert w == lit_len(want); + assert same(&out, 0, w, want, 0, lit_len(want)) == true; +}