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 fd48a27082
commit 6a234c552a
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. # toward, sample-first like log-watcher was.
[build] [build]
runtime = "../../../runtime/wovm" 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 doc's wire protocol, profiled for unix sockets, values in the WAL's
encoding. encoding.
- **The acceptance workload extends the employee sample**: A = the employee - **The acceptance workload extends the employee sample**: A = the employee
program with `[share]`; B = a new thin `docs/examples/employee-report` program with `[share]`; B = `docs/examples/employee-list` (pre-authored
client attaching read-only for the GroupBy report, plus a read+write 2026-08-15, sample-first — both manifests designed as a pair), attaching
audit-log writer path exercising the rights matrix and the refusal read-only for the list/report/staff modes and proving the rights matrix
traps. The sample stays the test. with its `probe-write` mode. The sample stays the test.
- Depends on iterations 9 (engine, WAL — done through Task 3 as of - Depends on iterations 9 (engine, WAL — done through Task 3 as of
2026-08-15) and 9b (typed statements and queries worth sharing); wants 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 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 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 to authenticate. The 9c milestone may still land first with the uid
bootstrap, flagged loudly as pre-9d. bootstrap, flagged loudly as pre-9d.
- **Acceptance extends the 9c workload**: the employee-A / report-B pair - **Acceptance extends the 9c workload**: the employee-A /
gains the key exchange in both manifests; the acceptance script adds the employee-list-B pair (`docs/examples/employee-list`, pre-authored
wrong-key, no-key, same-uid-wrong-key, replay, impostor-socket, and 2026-08-15) carries the key exchange in both manifests — A's
rotation checks above, each asserting the exact trap/refusal. `[[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), - Expected shape: handshake module beside the channel code (both ends),
`[share]`/`[connect]` manifest keys for fingerprints, first-boot keygen `[share]`/`[connect]` manifest keys for fingerprints, first-boot keygen
in the runtime's data-directory setup, vendored signature primitive with in the runtime's data-directory setup, vendored signature primitive with