feat(query-corpus): iteration 9g corpus #1 — skillhost needs no new query grammar

- resolved 9g's forks EMPIRICALLY against the running compiler:
  - count(<query>) and len(<query>) already work (fork 2 collapses to
    zero code)
  - skillhost's correlated NOT EXISTS is a backlink emptiness in
    writeonce (`where len(x.children) == 0`), using only 9b machinery
    (fork 1) — verified on a self-referential ?ref/backlink table
  => corpus #1 forces NO new grammar; per the method ("add only what a
     corpus uses"), exists/not-exists was NOT built
- docs/examples/skill-catalog: mirrors skillhost's `skills` table
  (name @unique, description/location/root, parent ?ref Skill, children
  backlink) and translates all five of its SQL statements 1:1
  (insert+dup-trap, get-by-name, list, roots via backlink-emptiness,
  count); scripts/skill-catalog-accept.sh 7/0, WAL-durable, dup trap
  persists across restart
- fixture run/db-query-corpus (count(query) + backlink NOT EXISTS);
  just skill-catalog module; target/ gitignored
- general exists/not-exists left unbuilt and recorded as "enters when a
  corpus forces a non-relation correlation"
- gates: oop-e2e 80/0, woc-test 566/0, skill-catalog 7/0; story + board
  record the finding

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 4c82461634d45f11eca1a252702031c03d999c7f)
This commit is contained in:
shoney.arickathil 2026-08-29 21:05:33 +02:00
parent e159b5a754
commit 5941a092f7
11 changed files with 209 additions and 0 deletions

1
.gitignore vendored
View file

@ -4,6 +4,7 @@
# `woc .` manifest builds (wo.toml [build] target)
/docs/examples/log-watcher/target
/docs/examples/employee/target
/docs/examples/skill-catalog/target
# Rust runtime (crates/rt/): compiled binary + build artifacts
/crates/rt/target

View file

@ -0,0 +1,30 @@
# skill-catalog — the query grammar corpus (iteration 9g)
> Corpus #1 for the query-grammar method (story
> [09g](../../stories/language-runtime-database/09g-query-grammar-corpus.md)):
> take a real application backed by an embedded SQL database, translate its
> every statement to the writeonce query surface, and add only the grammar it
> forces. The application is `~/projects/skillhost` (a C++ MCP host whose
> in-memory SQLite holds its skill catalog).
**Result: skillhost forced no new grammar.** Its entire SQL footprint — one
table, a single-row `INSERT`, and four `SELECT`s (whose only non-trivial
features are `COUNT(*)` and a correlated `NOT EXISTS`) — is expressible on the
surface iteration 9b already shipped. The five statements, translated:
| skillhost SQL (`src/catalog/catalog.cpp`) | writeonce | mode |
| --- | --- | --- |
| `INSERT INTO skills (…) VALUES (?,…)` + `SQLITE_CONSTRAINT` dup check | `insert Skill { … }` + `try…catch` on the `@unique` trap | `seed` |
| `SELECT … WHERE name = ?` | `from x in Skill where x.name == n take 1 select x` | `get <name>` |
| `SELECT … ORDER BY name` | `from x in Skill order by x.name select x` | `list` |
| `SELECT COUNT(*) FROM skills` | `count(from x in Skill select x)` | `count` |
| `SELECT … WHERE NOT EXISTS (SELECT 1 FROM skills c WHERE c.parent = s.name) ORDER BY name` | `from x in Skill where len(x.children) == 0 order by x.name select x` | `roots` |
The one translation choice: skillhost's correlated `NOT EXISTS` (skills that
are nobody's parent) becomes a **backlink emptiness** — `Skill.children` is the
inverse of `parent`, and `len(x.children) == 0` is the childless test. No
subquery construct is needed; the general `exists`/`not exists` is deferred
until a corpus uses a correlation a backlink cannot express.
`skill-catalog seed | list | roots | count | get <name>`, WAL-durable under
`WO_DATA`. Acceptance: `scripts/skill-catalog-accept.sh`.

View file

@ -0,0 +1,7 @@
ROOT := source_directory() / "../../.."
default: accept
build:
{{ROOT}}/compiler/_build/default/bin/woc .
@ls -la target/skill-catalog
accept:
{{ROOT}}/scripts/skill-catalog-accept.sh

View file

@ -0,0 +1,72 @@
-- skill-catalog — iteration 9g corpus proof. Each mode is a 1:1 translation
-- of one statement skillhost issues against its embedded SQLite; the point is
-- that ALL of them run on the query surface iteration 9b already shipped, with
-- NO new grammar. skillhost's SQL, translated:
--
-- INSERT INTO skills (...) VALUES (?,...) -> insert Skill { ... }
-- SELECT ... WHERE name = ? -> where s.name == n
-- SELECT ... ORDER BY name -> order by s.name
-- SELECT COUNT(*) FROM skills -> count(from ... select ...)
-- SELECT ... WHERE NOT EXISTS (child) ORDER BY name -> where len(s.children) == 0
--
-- The NOT EXISTS correlated subquery becomes a backlink emptiness check — the
-- idiomatic writeonce form, needing no subquery construct.
fn main(args: multi Text) -> Int {
if len(args) >= 1 and args[0] == "seed" { return seed(); }
if len(args) >= 1 and args[0] == "list" { return list(); }
if len(args) >= 1 and args[0] == "roots" { return roots(); }
if len(args) >= 1 and args[0] == "count" { return count_all(); }
if len(args) >= 2 and args[0] == "get" { return get(args[1]); }
print_err("usage: skill-catalog seed | list | roots | count | get <name>");
return 1;
}
-- INSERT + the app's duplicate-name handling (skillhost checks SQLITE_CONSTRAINT;
-- writeonce catches the @unique trap).
fn seed() -> Int {
let git = try insert Skill { name: "git", description: "version control",
location: ".agents/git", root: ".agents/git", parent: nil }
catch (e) nil;
if git == nil { print("SEED-DUP catalog already seeded"); return 3; }
insert Skill { name: "git-commit", description: "make a commit",
location: ".agents/git/commit", root: ".agents/git", parent: git };
insert Skill { name: "git-rebase", description: "rebase a branch",
location: ".agents/git/rebase", root: ".agents/git", parent: git };
insert Skill { name: "docker", description: "containers",
location: ".agents/docker", root: ".agents/docker", parent: nil };
print("SEEDED 4 skills");
return 0;
}
-- SELECT ... FROM skills ORDER BY name
fn list() -> Int {
for s in from x in Skill order by x.name select x {
print("${s.name}\t${s.description}");
}
return 0;
}
-- SELECT ... WHERE NOT EXISTS (SELECT 1 FROM skills c WHERE c.parent = s.name)
-- ORDER BY name -- the childless skills (skillhost's list-roots)
fn roots() -> Int {
for s in from x in Skill where len(x.children) == 0 order by x.name select x {
print(s.name);
}
return 0;
}
-- SELECT COUNT(*) FROM skills
fn count_all() -> Int {
print_int(count(from x in Skill select x));
return 0;
}
-- SELECT ... FROM skills WHERE name = ?
fn get(name: Text) -> Int {
let hits = from x in Skill where x.name == name take 1 select x;
if len(hits) == 0 { print_err("no such skill: ${name}"); return 1; }
let s = hits[0];
print("${s.name}\t${s.description}\t${s.location}\t${s.root}");
return 0;
}

View file

@ -0,0 +1,17 @@
-- Mirrors ~/projects/skillhost's `skills` table (src/catalog/catalog.cpp):
-- CREATE TABLE skills (name TEXT PRIMARY KEY, description TEXT NOT NULL,
-- location TEXT NOT NULL, root TEXT NOT NULL, parent TEXT);
-- CREATE INDEX skills_parent ON skills(parent);
-- TEXT PRIMARY KEY -> @unique name; the parent self-reference is a ?ref, and
-- the `skills_parent` index is what the `children` backlink reads. This is
-- iteration 9g's corpus #1: proving the writeonce query surface already
-- covers a real embedded-SQLite app.
@table(name: "skills", index: [name], index: [parent])
class Skill {
name: Text @unique
description: Text
location: Text
root: Text
parent: ?ref Skill -- SQLite's nullable `parent` column
children: backlink Skill.parent -- the inverse the skills_parent index serves
}

View file

@ -0,0 +1,9 @@
name = "skill-catalog"
version = "0.1.0"
description = "Iteration 9g corpus: skillhost's embedded-SQLite skill catalog, translated 1:1 to the writeonce query surface — proving it needs no new grammar"
[runtime]
wo = ">= 0.1"
[build]
runtime = "../../../runtime/wovm"

View file

@ -24,6 +24,21 @@ readiness: refine
> applied to a whole application: **an embedded-SQLite app is a grammar
> corpus; each one analysed drives a grammar increment.**
>
> **Implemented 2026-08-16 (branch `query-grammar`).** The forks resolved
> empirically, and the result is the strongest possible one for the method:
> **corpus #1 (skillhost) forced NO new query grammar.** `count(<query>)` and
> `len(<query>)` already work (fork 2), and skillhost's correlated `NOT EXISTS`
> is a backlink emptiness — `where len(x.children) == 0` — using only 9b
> machinery (fork 1). So `exists`/`not exists` was **not** built: the method is
> "add only what a corpus uses," and this corpus uses nothing new. Deliverable
> is the parity sample (`docs/examples/skill-catalog`, all five skillhost
> statements translated 1:1, `scripts/skill-catalog-accept.sh` 7/0) and this
> recorded conclusion — not speculative subquery machinery.
>
> **Still open (no corpus yet):** the general `exists`/`not exists` construct,
> for a correlation a backlink cannot express (a correlation on a non-relation
> column). It enters when a corpus forces it, exactly as the method prescribes.
> **No spec exists yet.** The forks in *Info* are genuine decisions.
## Why this iteration exists

View file

@ -116,6 +116,10 @@ mod log-watcher "docs/examples/log-watcher"
# (scan/where/select/order/take, update, delete). `just employee` runs it.
mod employee "docs/examples/employee"
# iteration 9g corpus: skillhost's embedded-SQLite catalog translated to the
# writeonce query surface (proves it needs no new grammar). `just skill-catalog`
mod skill-catalog "docs/examples/skill-catalog"
# conformance harness (plan 3): walks tests/corpus/{run,compile-fail,trap},
# exact outcome per fixture kind — see docs/plan/oop-vm/02-corpus.md.
# Fails loudly (and names the recipe to run) if woc or wovm isn't built.

34
scripts/skill-catalog-accept.sh Executable file
View file

@ -0,0 +1,34 @@
#!/usr/bin/env bash
# scripts/skill-catalog-accept.sh — iteration 9g acceptance: every skillhost
# catalog operation, translated to writeonce, produces the expected result on
# the 9b query surface (no new grammar). WAL-durable; a second seed proves the
# @unique trap persists across a process restart.
set -uo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
WOC="$ROOT/compiler/_build/default/bin/woc"
WOVM="$ROOT/runtime/wovm"
pass=0; fail=0
ok() { echo "ok $1"; pass=$((pass+1)); }
bad() { echo "FAIL $1 -- $2"; fail=$((fail+1)); }
[[ -x "$WOC" && -x "$WOVM" ]] || { echo "build woc+wovm first" >&2; exit 1; }
WORK="$(mktemp -d "${TMPDIR:-/tmp}/skillcat.XXXXXX")"; DATA="$WORK/d"; mkdir -p "$DATA"
IMG="$WORK/sc.wob"
trap '[[ -n "${SKILLCAT_KEEP:-}" ]] && echo "kept $WORK" || rm -rf "$WORK"' EXIT
if "$WOC" --emit "$ROOT/docs/examples/skill-catalog" -o "$IMG" >"$WORK/c.out" 2>&1; then
ok "compile ($(stat -c%s "$IMG") bytes)"
else bad "compile" "$(head -1 "$WORK/c.out")"; echo; echo "skill-catalog-accept: $pass/$((pass+fail))"; exit 1; fi
run() { WO_DATA="$DATA" "$WOVM" "$IMG" "$@"; }
run seed | grep -q "SEEDED 4 skills" && ok "seed (insert)" || bad "seed" "no SEEDED"
[[ "$(run count)" == "4" ]] && ok "count (COUNT(*) -> count(query))" || bad "count" "not 4"
[[ "$(run list | head -1)" == docker* ]] && ok "list (ORDER BY name)" || bad "list" "not name-ordered"
if [[ "$(run roots | tr '\n' ',')" == "docker,git-commit,git-rebase," ]]; then
ok "roots (NOT EXISTS -> backlink emptiness)"
else bad "roots" "got: $(run roots | tr '\n' ',')"; fi
run get git | grep -q "^git version control" && ok "get by name (WHERE name = ?)" || bad "get" "wrong row"
out="$(run seed 2>&1)"; rc=$?
[[ "$out" == *SEED-DUP* && $rc -eq 3 ]] && ok "unique trap persists across restart" || bad "dup" "rc=$rc"
echo
printf 'skill-catalog-accept: %d checks, %d failures\n' "$((pass+fail))" "$fail"
[[ $fail -eq 0 ]]

View file

@ -0,0 +1,3 @@
3
docker
git-commit

View file

@ -0,0 +1,17 @@
-- iteration 9g: skillhost's grammar on the 9b surface. count(query) for
-- COUNT(*), backlink-emptiness for a correlated NOT EXISTS. No new grammar.
@table(name: "skills", index: [name], index: [parent])
class Skill {
name: Text
parent: ?ref Skill
children: backlink Skill.parent
}
fn main() {
let git = insert Skill { name: "git", parent: nil }
insert Skill { name: "git-commit", parent: git }
insert Skill { name: "docker", parent: nil }
print_int(count(from x in Skill select x))
for s in from x in Skill where len(x.children) == 0 order by x.name select x {
print(s.name)
}
}