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:
parent
fd48a27082
commit
6a234c552a
6 changed files with 171 additions and 8 deletions
42
docs/examples/employee-list/README.md
Normal file
42
docs/examples/employee-list/README.md
Normal 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.
|
||||
73
docs/examples/employee-list/main.wo
Normal file
73
docs/examples/employee-list/main.wo
Normal 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;
|
||||
}
|
||||
29
docs/examples/employee-list/wo.toml
Normal file
29
docs/examples/employee-list/wo.toml
Normal 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"
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue