docs+examples: de-identify — generic hosts, no personal/model/internal names

Public repo hygiene: scrub local identifiers from the design doc and the usage
examples/tests. docs/design-nested.md loses the hostname, the maintainer name, a
model name, and internal system/agent names (the rejected shared-store router is
now described generically, not by product name); its stale wire line is corrected
to netstring(argc) while there. Usage comments and test fixtures switch real fleet
hostnames/container names to host1/host2/app/db. No behaviour change; all tests green.
This commit is contained in:
Claude (noether)
2026-07-23 13:17:06 +02:00
parent 4fcd8512ae
commit 20a6608f0e
6 changed files with 55 additions and 52 deletions
+14 -12
View File
@@ -6,33 +6,35 @@ import (
) )
// WP7: parseHostsConfig parses the minimal hosts.toml subset (no external dep — stdlib only): // WP7: parseHostsConfig parses the minimal hosts.toml subset (no external dep — stdlib only):
// # comment //
// [<host>] // # comment
// nest = ["incus", "docker"] // [<host>]
// nest = ["incus", "docker"]
//
// into map[host] -> ordered runtime types. Comments and blank lines are ignored; an empty nest // into map[host] -> ordered runtime types. Comments and blank lines are ignored; an empty nest
// list is a present host with zero hops. // list is a present host with zero hops.
func TestParseHostsConfig(t *testing.T) { func TestParseHostsConfig(t *testing.T) {
data := "# fleet nest config\n" + data := "# fleet nest config\n" +
"[boltzmann]\n" + "[host1]\n" +
"nest = [\"incus\", \"docker\"]\n" + "nest = [\"incus\", \"docker\"]\n" +
"\n" + "\n" +
"[data]\n" + "[host2]\n" +
"nest = [\"pct\"]\n" + "nest = [\"pct\"]\n" +
"\n" + "\n" +
"[bosch]\n" + "[host3]\n" +
"nest = []\n" "nest = []\n"
cfg, err := parseHostsConfig(data) cfg, err := parseHostsConfig(data)
if err != nil { if err != nil {
t.Fatalf("unexpected err: %v", err) t.Fatalf("unexpected err: %v", err)
} }
if !reflect.DeepEqual(cfg["boltzmann"], []string{"incus", "docker"}) { if !reflect.DeepEqual(cfg["host1"], []string{"incus", "docker"}) {
t.Fatalf("boltzmann nest = %#v, want [incus docker]", cfg["boltzmann"]) t.Fatalf("host1 nest = %#v, want [incus docker]", cfg["host1"])
} }
if !reflect.DeepEqual(cfg["data"], []string{"pct"}) { if !reflect.DeepEqual(cfg["host2"], []string{"pct"}) {
t.Fatalf("data nest = %#v, want [pct]", cfg["data"]) t.Fatalf("host2 nest = %#v, want [pct]", cfg["host2"])
} }
b, ok := cfg["bosch"] b, ok := cfg["host3"]
if !ok || len(b) != 0 { if !ok || len(b) != 0 {
t.Fatalf("bosch must be present with an empty nest; got %#v ok=%v", b, ok) t.Fatalf("host3 must be present with an empty nest; got %#v ok=%v", b, ok)
} }
} }
+4 -4
View File
@@ -13,15 +13,15 @@
// //
// sic host echo hi # run on the host // sic host echo hi # run on the host
// sic host touch 'a b' # ONE file // sic host touch 'a b' # ONE file
// sic dcw2/noether cat /etc/os-release # into the 'noether' guest on dcw2 // sic host1/app cat /etc/os-release # into the 'app' guest on host1
// sic data/ct110/app id # host -> pct ct110 -> docker app (two hops) // sic host2/ct/svc id # host -> pct ct -> docker svc (two hops)
// sic --sh host 'echo hi | wc -c' # shell line (space-join is intended here) // sic --sh host 'echo hi | wc -c' # shell line (space-join is intended here)
// //
// Hop runtimes come from /etc/sic/hosts.toml, one stanza per host: // Hop runtimes come from /etc/sic/hosts.toml, one stanza per host:
// //
// [dcw2] // [host1]
// nest = ["incus"] // nest = ["incus"]
// [data] // [host2]
// nest = ["pct", "docker"] // nest = ["pct", "docker"]
package main package main
+3 -3
View File
@@ -14,9 +14,9 @@ func TestParseTarget(t *testing.T) {
host string host string
hops []string hops []string
}{ }{
{"boltzmann", "boltzmann", nil}, {"host1", "host1", nil},
{"boltzmann/memory", "boltzmann", []string{"memory"}}, {"host1/app", "host1", []string{"app"}},
{"boltzmann/memory/stash-stash-1", "boltzmann", []string{"memory", "stash-stash-1"}}, {"host1/app/db", "host1", []string{"app", "db"}},
} }
for _, tc := range cases { for _, tc := range cases {
host, hops := parseTarget(tc.in) host, hops := parseTarget(tc.in)
+2 -2
View File
@@ -9,11 +9,11 @@ import (
// Too many hops for the declared nest depth is a hard error (fail-loud); an unknown runtime // Too many hops for the declared nest depth is a hard error (fail-loud); an unknown runtime
// propagates verbFor's error. Zero hops -> zero verbs (a bare host is a single v2 frame). // propagates verbFor's error. Zero hops -> zero verbs (a bare host is a single v2 frame).
func TestResolveVerbs(t *testing.T) { func TestResolveVerbs(t *testing.T) {
got, err := resolveVerbs([]string{"memory", "stash-stash-1"}, []string{"incus", "docker"}) got, err := resolveVerbs([]string{"app", "db"}, []string{"incus", "docker"})
if err != nil { if err != nil {
t.Fatalf("unexpected err: %v", err) t.Fatalf("unexpected err: %v", err)
} }
want := []string{"incus exec memory --", "docker exec stash-stash-1"} want := []string{"incus exec app --", "docker exec db"}
if !reflect.DeepEqual(got, want) { if !reflect.DeepEqual(got, want) {
t.Fatalf("resolveVerbs = %#v, want %#v", got, want) t.Fatalf("resolveVerbs = %#v, want %#v", got, want)
} }
+2 -2
View File
@@ -10,8 +10,8 @@ func TestVerbFor(t *testing.T) {
cases := []struct { cases := []struct {
runtime, name, want string runtime, name, want string
}{ }{
{"incus", "memory", "incus exec memory --"}, {"incus", "app", "incus exec app --"},
{"docker", "stash-stash-1", "docker exec stash-stash-1"}, {"docker", "db", "docker exec db"},
{"pct", "107", "pct exec 107 --"}, {"pct", "107", "pct exec 107 --"},
} }
for _, tc := range cases { for _, tc := range cases {
+30 -29
View File
@@ -1,9 +1,8 @@
# sic — nested (multi-hop) design & chain-resolution decision # sic — nested (multi-hop) design & chain-resolution decision
Status: **Accepted** — 2026-07-23, bullpen design session (room msgs 865877; Status: **Accepted** — 2026-07-23. This records a decision and its rejected alternatives
@architect / @skeptic on deepseek-v4-flash-dspark, initiated by Markus). *for the audit trail* — a future reader should see that a shared-store router was considered
This records a decision and its rejected alternatives *for the audit trail* — a future and killed for cause, not overlooked.
reader should see that mneme-as-router was considered and killed for cause, not overlooked.
## Goal ## Goal
@@ -22,8 +21,8 @@ repeats. There is **no separate `sic-run` binary** — see Decision 3. Construct
and up-front; unrolling is iterative and distributed. and up-front; unrolling is iterative and distributed.
One frame = preamble (`MAGIC 0x00`, then 4-byte big-endian `len32`) + a djb netstring whose One frame = preamble (`MAGIC 0x00`, then 4-byte big-endian `len32`) + a djb netstring whose
content is `<command> 0x00 <payload>`; the payload of a non-terminal frame is itself a full content is `netstring(argc)` + argc argv netstrings + `payload`; the payload of a non-terminal
frame for the next hop. frame is itself a full frame for the next hop.
## Decision 1 — chain resolution: **static per-client config** ## Decision 1 — chain resolution: **static per-client config**
@@ -32,40 +31,41 @@ frame for the next hop.
concrete verb + address at each layer. **Fail loud on any mismatch** (unknown host, more hops concrete verb + address at each layer. **Fail loud on any mismatch** (unknown host, more hops
than the config declares) — never guess. than the config declares) — never guess.
### Rejected: mneme as the chain router ### Rejected: a shared memory store as the chain router
Resolving chains by querying the fleet-memory store (mneme) was proposed and **rejected**. Resolving chains by querying a shared, mutable fleet-memory store was proposed and **rejected**.
Even with the bootstrap circularity removed (mneme's own address is a flat local setting, so a Even with the bootstrap circularity removed (the store's own address is a flat local setting, so
lookup is one direct call, not a nested one), it loses on four counts: a lookup is one direct call, not a nested one), it loses on four counts:
- **Trust (dispositive).** mneme is a *mutable shared* store. Any container with write access - **Trust (dispositive).** Such a store is *mutable and shared*. Any writer poisons one chain
poisons one chain entry and every subsequent `sic` call through that chain execs the entry and every subsequent `sic` call through that chain execs the attacker's verb on the
attacker's verb on the target host — a supply-chain RCE vector. Static config is target host — a supply-chain RCE vector. Static config is immutable-after-deploy and auditable
immutable-after-deploy and auditable once. once.
- **Staleness fails the wrong way.** A moved/renamed container makes the stored chain lie, and - **Staleness fails the wrong way.** A moved/renamed container makes the stored chain lie, and
the client *silently* execs into the wrong container. Static config fails loud (connection the client *silently* execs into the wrong container. Static config fails loud (connection
refused); mneme turns a hard error into quiet wrong-target execution. refused); a mutable store turns a hard error into quiet wrong-target execution.
- **Availability.** mneme lives on boltzmann, which sleeps; resolution would die with it, and a - **Availability.** The store may live on a host that sleeps; resolution would die with it, and a
local cache to cover that is just a slower, staler config file. local cache to cover that is just a slower, staler config file.
- **No net benefit.** The client needs a local mneme *address* regardless, so mneme only adds a - **No net benefit.** The client needs a local store *address* regardless, so the store only adds
fragile network hop + cache to do what a flat file read already does. a fragile network hop + cache to do what a flat file read already does.
mneme remains the fleet **memory** store — this decision is narrowly about *routing*, nothing else. A shared memory store may still be useful for *memory* — this decision is narrowly about
*routing*, nothing else.
## Decision 2 — migration: **daemon dual-read, daemon-first** ## Decision 2 — migration: **daemon dual-read, daemon-first**
The v2 daemon hard-requires the `0x00` magic; the deployed v1 client sends a digit first, so a The v2 daemon hard-requires the `0x00` magic; the deployed v1 client sends a digit first, so a
naive v2 deploy breaks every existing call on 30+ live hosts. Instead the daemon **sniffs the naive v2 deploy breaks every existing call on the many live hosts already running v1. Instead the
first byte** — `0x00` → v2 parser, digit → v1-legacy parser — a once-per-connection branch, daemon **sniffs the first byte**`0x00` → v2 parser, digit → v1-legacy parser — a
~20 lines, zero perf cost. Ship the v2 daemon everywhere first; roll clients independently, no once-per-connection branch, ~20 lines, zero perf cost. Ship the v2 daemon everywhere first; roll
flag day. clients independently, no flag day.
## Decision 3 — the chain link is `sicd` itself (no `sic-run` binary) ## Decision 3 — the chain link is `sicd` itself (no `sic-run` binary)
An earlier draft proposed a separate `sic-run` helper as the per-hop peeler. It is **not built**: An earlier draft proposed a separate `sic-run` helper as the per-hop peeler. It is **not built**:
`sicd` is already a pure v2 frame-peeler, so the inner-hop command is just `sicd` `sicd` is already a pure v2 frame-peeler, so the inner-hop command is just `sicd`
(`incus exec <c> -- sicd`), and one binary deploys instead of two (Markus' call: deployment (`incus exec <c> -- sicd`), and one binary deploys instead of two (a deployment-simplicity
simplicity). **Verified empirically, not assumed**`sicd` invoking `sicd` was run against a decision). **Verified empirically, not assumed**`sicd` invoking `sicd` was run against a
two-layer nested frame (`build sicd; frame("sicd", frame("cat", payload)) | sicd`): the payload two-layer nested frame (`build sicd; frame("sicd", frame("cat", payload)) | sicd`): the payload
reached the inner hop, exit status propagated, stderr clean. sicd calling sicd peels correctly, reached the inner hop, exit status propagated, stderr clean. sicd calling sicd peels correctly,
so nothing about a nested hop needs behaviour sicd lacks. The only prerequisite is that **sicd is so nothing about a nested hop needs behaviour sicd lacks. The only prerequisite is that **sicd is
@@ -74,15 +74,15 @@ present in every container a chain traverses** (a packaging/rollout item, not ne
## Backward compat & the two original bugs ## Backward compat & the two original bugs
- A **bare `--`** must survive into the innermost argv untouched (the payload is opaque bytes; - A **bare `--`** must survive into the innermost argv untouched (the payload is opaque bytes;
no hop interprets it) — closes marfrit/sic#1. no hop interprets it) — closes the double-dash bug (#1).
- **stdin must be forwarded** into/through the frame — closes the silent zero-byte-file bug. - **stdin must be forwarded** into/through the frame — closes the silent zero-byte-file bug.
- A **bare host** with no `/` stays a single v1-style hop (backward compatible). - A **bare host** with no `/` stays a single v1-style hop (backward compatible).
## Escape hatch (documented, not built) ## Escape hatch (documented, not built)
If redeploying clients on chain changes ever costs more than the trust of a live store, If redeploying clients on chain changes ever costs more than the trust of a live store,
distribute **signed manifests out-of-band** — deterministic like static config, updatable like distribute **signed manifests out-of-band** — deterministic like static config, updatable like a
mneme, without the mutable-store poison vector. Future work; explicitly not today's problem. live store, without the mutable-store poison vector. Future work; explicitly not today's problem.
## Consequences ## Consequences
@@ -90,4 +90,5 @@ mneme, without the mutable-store poison vector. Future work; explicitly not toda
- No network dependency and no mutable store in the routing path — routing is deterministic and - No network dependency and no mutable store in the routing path — routing is deterministic and
auditable. auditable.
- Next: implement `cmd/sic` (config read + recursive frame build + `--`/stdin fixes) and the - Next: implement `cmd/sic` (config read + recursive frame build + `--`/stdin fixes) and the
daemon dual-read; ensure sicd is deployed into target containers (no `sic-run` binary); both are Go, grindable via @godev. daemon dual-read; ensure sicd is deployed into target containers (no `sic-run` binary); both
are Go.