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
+11 -9
View File
@@ -6,33 +6,35 @@ import (
)
// WP7: parseHostsConfig parses the minimal hosts.toml subset (no external dep — stdlib only):
//
// # comment
// [<host>]
// nest = ["incus", "docker"]
//
// into map[host] -> ordered runtime types. Comments and blank lines are ignored; an empty nest
// list is a present host with zero hops.
func TestParseHostsConfig(t *testing.T) {
data := "# fleet nest config\n" +
"[boltzmann]\n" +
"[host1]\n" +
"nest = [\"incus\", \"docker\"]\n" +
"\n" +
"[data]\n" +
"[host2]\n" +
"nest = [\"pct\"]\n" +
"\n" +
"[bosch]\n" +
"[host3]\n" +
"nest = []\n"
cfg, err := parseHostsConfig(data)
if err != nil {
t.Fatalf("unexpected err: %v", err)
}
if !reflect.DeepEqual(cfg["boltzmann"], []string{"incus", "docker"}) {
t.Fatalf("boltzmann nest = %#v, want [incus docker]", cfg["boltzmann"])
if !reflect.DeepEqual(cfg["host1"], []string{"incus", "docker"}) {
t.Fatalf("host1 nest = %#v, want [incus docker]", cfg["host1"])
}
if !reflect.DeepEqual(cfg["data"], []string{"pct"}) {
t.Fatalf("data nest = %#v, want [pct]", cfg["data"])
if !reflect.DeepEqual(cfg["host2"], []string{"pct"}) {
t.Fatalf("host2 nest = %#v, want [pct]", cfg["host2"])
}
b, ok := cfg["bosch"]
b, ok := cfg["host3"]
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 touch 'a b' # ONE file
// sic dcw2/noether cat /etc/os-release # into the 'noether' guest on dcw2
// sic data/ct110/app id # host -> pct ct110 -> docker app (two hops)
// sic host1/app cat /etc/os-release # into the 'app' guest on host1
// 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)
//
// Hop runtimes come from /etc/sic/hosts.toml, one stanza per host:
//
// [dcw2]
// [host1]
// nest = ["incus"]
// [data]
// [host2]
// nest = ["pct", "docker"]
package main
+3 -3
View File
@@ -14,9 +14,9 @@ func TestParseTarget(t *testing.T) {
host string
hops []string
}{
{"boltzmann", "boltzmann", nil},
{"boltzmann/memory", "boltzmann", []string{"memory"}},
{"boltzmann/memory/stash-stash-1", "boltzmann", []string{"memory", "stash-stash-1"}},
{"host1", "host1", nil},
{"host1/app", "host1", []string{"app"}},
{"host1/app/db", "host1", []string{"app", "db"}},
}
for _, tc := range cases {
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
// propagates verbFor's error. Zero hops -> zero verbs (a bare host is a single v2 frame).
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 {
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) {
t.Fatalf("resolveVerbs = %#v, want %#v", got, want)
}
+2 -2
View File
@@ -10,8 +10,8 @@ func TestVerbFor(t *testing.T) {
cases := []struct {
runtime, name, want string
}{
{"incus", "memory", "incus exec memory --"},
{"docker", "stash-stash-1", "docker exec stash-stash-1"},
{"incus", "app", "incus exec app --"},
{"docker", "db", "docker exec db"},
{"pct", "107", "pct exec 107 --"},
}
for _, tc := range cases {
+30 -29
View File
@@ -1,9 +1,8 @@
# sic — nested (multi-hop) design & chain-resolution decision
Status: **Accepted** — 2026-07-23, bullpen design session (room msgs 865877;
@architect / @skeptic on deepseek-v4-flash-dspark, initiated by Markus).
This records a decision and its rejected alternatives *for the audit trail* — a future
reader should see that mneme-as-router was considered and killed for cause, not overlooked.
Status: **Accepted** — 2026-07-23. This records a decision and its rejected alternatives
*for the audit trail* — a future reader should see that a shared-store router was considered
and killed for cause, not overlooked.
## 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.
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
frame for the next hop.
content is `netstring(argc)` + argc argv netstrings + `payload`; the payload of a non-terminal
frame is itself a full frame for the next hop.
## 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
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**.
Even with the bootstrap circularity removed (mneme's own address is a flat local setting, so a
lookup is one direct call, not a nested one), it loses on four counts:
Resolving chains by querying a shared, mutable fleet-memory store was proposed and **rejected**.
Even with the bootstrap circularity removed (the store's own address is a flat local setting, so
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
poisons one chain entry and every subsequent `sic` call through that chain execs the
attacker's verb on the target host — a supply-chain RCE vector. Static config is
immutable-after-deploy and auditable once.
- **Trust (dispositive).** Such a store is *mutable and shared*. Any writer poisons one chain
entry and every subsequent `sic` call through that chain execs the attacker's verb on the
target host — a supply-chain RCE vector. Static config is immutable-after-deploy and auditable
once.
- **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
refused); mneme turns a hard error into quiet wrong-target execution.
- **Availability.** mneme lives on boltzmann, which sleeps; resolution would die with it, and a
refused); a mutable store turns a hard error into quiet wrong-target execution.
- **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.
- **No net benefit.** The client needs a local mneme *address* regardless, so mneme only adds a
fragile network hop + cache to do what a flat file read already does.
- **No net benefit.** The client needs a local store *address* regardless, so the store only adds
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**
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
first byte** — `0x00` → v2 parser, digit → v1-legacy parser — a once-per-connection branch,
~20 lines, zero perf cost. Ship the v2 daemon everywhere first; roll clients independently, no
flag day.
naive v2 deploy breaks every existing call on the many live hosts already running v1. Instead the
daemon **sniffs the first byte**`0x00` → v2 parser, digit → v1-legacy parser — a
once-per-connection branch, ~20 lines, zero perf cost. Ship the v2 daemon everywhere first; roll
clients independently, no flag day.
## 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**:
`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
simplicity). **Verified empirically, not assumed**`sicd` invoking `sicd` was run against a
(`incus exec <c> -- sicd`), and one binary deploys instead of two (a deployment-simplicity
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
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
@@ -74,15 +74,15 @@ present in every container a chain traverses** (a packaging/rollout item, not ne
## Backward compat & the two original bugs
- 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.
- A **bare host** with no `/` stays a single v1-style hop (backward compatible).
## Escape hatch (documented, not built)
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
mneme, without the mutable-store poison vector. Future work; explicitly not today's problem.
distribute **signed manifests out-of-band** — deterministic like static config, updatable like a
live store, without the mutable-store poison vector. Future work; explicitly not today's problem.
## 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
auditable.
- 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.