docs: employee-list sample -- program B's manifest pair (9c/9d target)

- docs/examples/employee-list: attaches to the running employee
  program; modes list / report (byte-identical to A's own) /
  staff <dept> / probe-write (registered read-only, insert must trap
  access-denied, exit 4, A unchanged)
- the manifests ARE the design, written as a pair: A's [share] gains
  listen = unix socket beside WO_DATA plus [[share.clients]] naming
  B's public-key fingerprint with rights = "read"; B's
  [connect.employee] carries A's ipc string, A's PINNED fingerprint,
  and project = ../employee for compile-time shapes -- the connect
  section's name is the code's namespace (employee.Employee)
- fingerprints are PASTE-HERE placeholders by design: keys generate
  into WO_DATA at first boot (9d), tomls carry fingerprints only,
  printed by --identity
- sample-first: compiles after 9/9b/9c/9d; README maps each mode to
  the acceptance line it exists for; 9c/9d stories now name this
  sample as their workload

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
shoney.arickathil 2026-08-15 12:34:19 +02:00
parent fe56ba0944
commit 2e1041daed
6 changed files with 171 additions and 8 deletions

View file

@ -0,0 +1,42 @@
# employee-list — program B: attach, authenticate, read
> **Status: target workload — does not compile on today's toolchain.**
> Written ahead of iterations
> [9c (cross-program tables)](../../stories/language-runtime-database/09c-cross-program-tables.md)
> and [9d (keypair attach auth)](../../stories/language-runtime-database/09d-keypair-attach-auth.md),
> the way every acceptance sample here precedes its features. It also leans
> on 9/9b (the [employee sample](../employee/) it attaches to must run
> first).
Two programs, one database, one writer:
```
employee (A) employee-list (B)
owns WO_DATA + WAL no database of its own
[share] listen = unix:...sock [connect.employee] ipc = unix:...sock
[[share.clients]] public_key = <A's fingerprint, pinned>
public_key = <B's fingerprint> project = ../employee (shapes)
rights = "read"
▲ │
└── every statement executes here ◄───┘ (typed, over the wire)
```
The manifests are the design: **A grants, B pins.** A's `[share]` names B's
public-key fingerprint with rights (`read` here); B's `[connect.employee]`
names A's IPC string AND A's fingerprint, so neither side talks to an
impostor. Fingerprints are printed by each program's `--identity` after
first boot (keys are generated into `WO_DATA`, never written into a toml)
and pasted — the `PASTE-…-HERE` placeholders mark exactly where. The
connect-section name is the code's namespace: `[connect.employee]` is why
the source says `employee.Employee`.
| Mode | What it proves |
| --- | --- |
| `employee-list list` | typed reads over the wire, `e.dept.name` ref navigation executing inside A |
| `employee-list report` | **byte-identical output to A's own `report`** — attach + GroupBy compose, the wire changes nothing |
| `employee-list staff <dept>` | unique-name index probe + `staff` backlink scan, both in A |
| `employee-list probe-write` | the rights matrix: registered read-only, so the insert traps with access-denied (caught, `DENIED …`, exit 4) and A's row count is unchanged |
The 9d acceptance drives the rest from the outside: wrong key, no key,
same-uid-wrong-key, impostor socket, handshake replay, key rotation — see
the iteration's criteria; this sample is the workload they run against.

View file

@ -0,0 +1,73 @@
-- employee-list — program B of the cross-program story (iterations 9c/9d).
-- Attaches to the RUNNING employee program (A) named by [connect.employee]
-- in wo.toml: keypair handshake first (9d), then typed statements over the
-- wire (9c). A registered this program read-only, so every mode here reads —
-- except probe-write, which exists to prove the rights matrix refuses.
--
-- The `employee.` prefix is the manifest's connect-section name: these are
-- A's tables, checked against A's shapes at compile time and re-verified by
-- the schema handshake at attach. A stays the single writer; every statement
-- below executes inside A.
fn main(args: multi Text) -> Int {
if len(args) >= 1 and args[0] == "list" { return list(); }
if len(args) >= 1 and args[0] == "report" { return report(); }
if len(args) >= 2 and args[0] == "staff" { return staff(args[1]); }
if len(args) >= 1 and args[0] == "probe-write" { return probe_write(); }
print_err("usage:");
print_err(" employee-list list every employee, with department");
print_err(" employee-list report aggregates by department (A's own report, over the wire)");
print_err(" employee-list staff <dept> one department's staff");
print_err(" employee-list probe-write prove read-only: the insert must be DENIED");
return 1;
}
-- Every employee with forward ref navigation — each `e.dept.name` is a
-- point read executing inside A.
fn list() -> Int {
for e in from x in employee.Employee order by x.name select x {
print("EMP ${e.name} ${e.salary} ${e.dept.name}");
}
return 0;
}
-- Byte-identical output to A's own `employee report` — the 9c acceptance
-- line: attach + query compose, and the wire changes nothing.
fn report() -> Int {
let rows = from e in employee.Employee
group e by e.dept into g
order by avg(g.salary) desc
select { dept: g.key.name, headcount: count(g),
avg_salary: avg(g.salary), min_salary: min(g.salary),
max_salary: max(g.salary) };
for r in rows {
print("DEPT ${r.dept} headcount=${r.headcount} avg=${r.avg_salary} min=${r.min_salary} max=${r.max_salary}");
}
let payroll = sum(from e in employee.Employee select e.salary);
print("PAYROLL ${payroll}");
return 0;
}
-- Unique-name index probe + backlink scan, both executing in A.
fn staff(name: Text) -> Int {
let ds = from d in employee.Department where d.name == name take 1 select d;
if len(ds) == 0 { print_err("no such department: ${name}"); return 1; }
for e in from s in ds[0].staff order by s.salary desc select s {
print("STAFF ${e.name} ${e.salary}");
}
return 0;
}
-- The rights matrix, exercised: this program is registered READ-ONLY, so
-- the insert must trap with the access-denied code — caught here, printed,
-- and nothing was applied or WAL-logged in A (the acceptance asserts A's
-- row count is unchanged).
fn probe_write() -> Int {
let id = try insert employee.Department { name: "Intruders" } catch (e) nil;
if id == nil {
print("DENIED write to employee.Department (registered read-only)");
return 4;
}
print_err("UNEXPECTED: write succeeded with id ${id} — rights not enforced");
return 1;
}

View file

@ -0,0 +1,29 @@
name = "employee-list"
version = "0.1.0"
description = "Program B of the cross-program story: attaches read-only to the running employee program (A) over its IPC channel after keypair authentication, and lists/aggregates A's tables over the wire"
[runtime]
wo = ">= 0.1"
[build]
runtime = "../../../runtime/wovm"
# Iteration 9c/9d surface (target — compiles once those land).
# The section NAME is the namespace this program's code uses: [connect.employee]
# makes A's tables reachable as `employee.Department` / `employee.Employee`.
[connect.employee]
# A's IPC string (9c fork 1: unix socket, listening beside A's WO_DATA;
# A declares the same path in its [share] section).
ipc = "unix:../employee/target/data/employee.sock"
# A's PINNED public-key fingerprint (9d): B refuses to speak to anything on
# that socket path that cannot sign A's challenge with this key — the
# impostor-socket acceptance line. Printed by `employee --identity` after
# A's first boot; paste it here. Never a private key, never a secret.
public_key = "ed25519:PASTE-EMPLOYEE-FINGERPRINT-HERE"
# Where B's compiler reads A's table shapes at compile time (9c fork 2's
# milestone lean: project-directory reference). The runtime handshake
# re-verifies the shapes against A's live class table at attach — a stale
# checkout refuses the attachment with both shapes named.
project = "../employee"

View file

@ -10,3 +10,19 @@ wo = ">= 0.1"
# toward, sample-first like log-watcher was.
[build]
runtime = "../../../runtime/wovm"
# Iteration 9c/9d surface (target): this program OWNS its database and
# shares it. The runtime listens on `listen` beside WO_DATA; every client
# below is a grant — no registration, no attach, same uid included.
[share]
listen = "unix:target/data/employee.sock"
# Program B (docs/examples/employee-list), granted read-only. Identity is
# B's public-key fingerprint (9d): printed by `employee-list --identity`
# after B's first boot; paste it here. Rights: "read" or "rw" — B's
# probe-write mode exists to prove "read" refuses. Rotation and revocation
# are edits to this table plus a restart, never an API.
[[share.clients]]
name = "employee-list"
public_key = "ed25519:PASTE-EMPLOYEE-LIST-FINGERPRINT-HERE"
rights = "read"

View file

@ -161,10 +161,10 @@ SIGTERM'd B parked on a channel read exits cleanly).
doc's wire protocol, profiled for unix sockets, values in the WAL's
encoding.
- **The acceptance workload extends the employee sample**: A = the employee
program with `[share]`; B = a new thin `docs/examples/employee-report`
client attaching read-only for the GroupBy report, plus a read+write
audit-log writer path exercising the rights matrix and the refusal
traps. The sample stays the test.
program with `[share]`; B = `docs/examples/employee-list` (pre-authored
2026-08-15, sample-first — both manifests designed as a pair), attaching
read-only for the list/report/staff modes and proving the rights matrix
with its `probe-write` mode. The sample stays the test.
- Depends on iterations 9 (engine, WAL — done through Task 3 as of
2026-08-15) and 9b (typed statements and queries worth sharing); wants
iteration 8's event loop for A's serving side but can prototype on a

View file

@ -132,10 +132,13 @@ authorization input.
9c without 9d ships a placeholder identity and 9d without 9c has nothing
to authenticate. The 9c milestone may still land first with the uid
bootstrap, flagged loudly as pre-9d.
- **Acceptance extends the 9c workload**: the employee-A / report-B pair
gains the key exchange in both manifests; the acceptance script adds the
wrong-key, no-key, same-uid-wrong-key, replay, impostor-socket, and
rotation checks above, each asserting the exact trap/refusal.
- **Acceptance extends the 9c workload**: the employee-A /
employee-list-B pair (`docs/examples/employee-list`, pre-authored
2026-08-15) carries the key exchange in both manifests — A's
`[[share.clients]]` names B's fingerprint, B's `[connect.employee]` pins
A's; the acceptance script adds the wrong-key, no-key,
same-uid-wrong-key, replay, impostor-socket, and rotation checks above,
each asserting the exact trap/refusal.
- Expected shape: handshake module beside the channel code (both ends),
`[share]`/`[connect]` manifest keys for fingerprints, first-boot keygen
in the runtime's data-directory setup, vendored signature primitive with