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:
parent
e159b5a754
commit
5941a092f7
11 changed files with 209 additions and 0 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -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
|
||||
|
|
|
|||
30
docs/examples/skill-catalog/README.md
Normal file
30
docs/examples/skill-catalog/README.md
Normal 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`.
|
||||
7
docs/examples/skill-catalog/justfile
Normal file
7
docs/examples/skill-catalog/justfile
Normal 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
|
||||
72
docs/examples/skill-catalog/main.wo
Normal file
72
docs/examples/skill-catalog/main.wo
Normal 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;
|
||||
}
|
||||
17
docs/examples/skill-catalog/types.wo
Normal file
17
docs/examples/skill-catalog/types.wo
Normal 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
|
||||
}
|
||||
9
docs/examples/skill-catalog/wo.toml
Normal file
9
docs/examples/skill-catalog/wo.toml
Normal 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"
|
||||
|
|
@ -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
|
||||
|
|
|
|||
4
justfile
4
justfile
|
|
@ -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
34
scripts/skill-catalog-accept.sh
Executable 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 ]]
|
||||
3
tests/corpus/run/db-query-corpus/fixture.out
Normal file
3
tests/corpus/run/db-query-corpus/fixture.out
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
3
|
||||
docker
|
||||
git-commit
|
||||
17
tests/corpus/run/db-query-corpus/fixture.wo
Normal file
17
tests/corpus/run/db-query-corpus/fixture.wo
Normal 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)
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue