humanish 0.16.0 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -71,6 +71,13 @@ sanitized transcripts, traces, and verdict events are available. This gives a
71
71
  served Observer a truthful active state to poll while noninteractive local
72
72
  actors are still running.
73
73
 
74
+ Watch is deliberately distinct from `humanish serve`. Watch serves ONE
75
+ attached run, and the process that created it may inject runtime stream URLs
76
+ (live hosted-desktop viewers) into the observer data it serves. Serve is the
77
+ LIBRARY surface — every run under `.humanish/runs/` — and never serves runtime
78
+ stream URLs in any mode; remote viewers see persisted evidence only. See
79
+ [Serve: the run library surface](serve.md).
80
+
74
81
  ## UI Shape
75
82
 
76
83
  The Observer shell has:
@@ -0,0 +1,140 @@
1
+ # Serve: the run library surface
2
+
3
+ Date: 2026-08-02
4
+
5
+ Status: shipped in v1 — loopback, capability-link, and share-safe-open modes
6
+ (`src/observer-serve.ts`, `src/observer-auth.ts`, `src/observer-library.ts`,
7
+ `src/serve-tunnel.ts`; CLI wiring in `src/program.ts`). The `/_humanish/api/*`
8
+ control-plane namespace is reserved and answers `501`; no mutating route ships.
9
+
10
+ ## What serve is
11
+
12
+ `humanish serve` is the third observer surface, and the first whose subject is
13
+ the LIBRARY rather than a run:
14
+
15
+ - `humanish watch` — one ATTACHED run: the process that created the run serves
16
+ it and may inject runtime stream URLs for live following;
17
+ - `humanish observe` — one FINISHED run, re-served read-only;
18
+ - `humanish serve` — the whole local library under `.humanish/runs/`: a
19
+ library index, per-run Observer pages, `/_humanish/history.json` polling, and
20
+ optional exposure beyond the machine.
21
+
22
+ The server binds `127.0.0.1` unconditionally (`serveObserverLibrary`); exposure
23
+ only ever happens through a tunnel or proxy forwarding to the loopback port.
24
+ `--expose` gates every request behind a capability link: a per-process secret
25
+ whose SHA-256 digest is compared timing-safe (`verifyTokenDigest`), minting an
26
+ HttpOnly viewer-session cookie with a bounded TTL. Ctrl-C revokes the link and
27
+ all sessions (`revokeAll` on close); restarting mints a new link.
28
+
29
+ ## Threat model
30
+
31
+ **The tunnel-agent-connects-from-loopback trap.** The classic mistake on a
32
+ tunneled local server is "peer address is 127.0.0.1, so this is the local
33
+ operator — skip auth." The tunnel agent connects from loopback, so that
34
+ shortcut disables auth in exactly the deployment it exists to protect. Serve
35
+ auth therefore ignores source address entirely: every route below the auth
36
+ mint requires a valid session, loopback peer or not.
37
+
38
+ **Referer and unfurler leakage.** The capability URL embeds the secret in the
39
+ path. Every response carries `referrer-policy: no-referrer` (plus `no-store`
40
+ and `x-robots-tag: noindex`), so a link inside a served page can never leak the
41
+ origin or token onward. The residual channel is the operator pasting the link
42
+ into a chat app whose unfurler prefetches it server-side — which is why the
43
+ link is deliberately NOT single-use (a preview fetch must not burn it before
44
+ the phone taps it), and why the README steers sharing toward AirDrop or manual
45
+ entry.
46
+
47
+ **Host-only cookie scope on shared tunnel domains.** The session cookie sets
48
+ no `Domain` attribute (`buildSessionCookie`), so it is host-only. On a shared
49
+ tunnel apex (many customers under one provider domain), a `Domain` cookie
50
+ would be presented to sibling subdomains an attacker can rent; a host-only
51
+ cookie can never leave the exact public origin the operator declared.
52
+
53
+ **DNS rebinding and the strict Host allowlist.** A malicious page can point an
54
+ attacker-controlled DNS name at 127.0.0.1 and read a permissive local server
55
+ from the victim's browser. Serve keeps a strict Host allowlist in ALL modes —
56
+ loopback names plus the declared tunnel/public origin only — and answers
57
+ `421 Misdirected Request` otherwise. Even the unauthenticated loopback default
58
+ never trusts an arbitrary Host header.
59
+
60
+ **Token in phone history (residual).** After the tap, the capability URL
61
+ remains in the phone's browser history and share sheet. TTL-bounded sessions,
62
+ per-process minting, and Ctrl-C revocation bound the damage — a link recovered
63
+ from history after the process exits or restarts is dead — but while the same
64
+ process runs, a leaked history entry is a live credential. This risk is
65
+ accepted and documented, not solved.
66
+
67
+ ## Mode-to-boundary mapping
68
+
69
+ | Mode | Invocation | Boundary class |
70
+ | --- | --- | --- |
71
+ | `loopback` | `humanish serve` | Capture-side trust: readable only by whoever can already read gitignored `.humanish/` on this machine; no new boundary is crossed. |
72
+ | `capability-link` | `--expose` (auth defaults to `link`) | Declared-friction exposure: publishing-adjacent, not publishing. Anyone holding the secret link reads everything it grants until Ctrl-C — the printed warning names the run count, including non-`share_ready` runs unless `--safe` composes in. |
73
+ | `share-safe-open` | `--expose --safe --auth none` | Genuine publishing, behind the feedback-grade `share_ready` gate: only runs that pass verify are served — admission is re-checked when a bundle changes and re-verified within a bounded window (default 30s), so it is fresh, not perfectly live; everything else is absent, 404ing byte-identically to a nonexistent run (no existence oracle). |
74
+
75
+ ## Stream-URL doctrine
76
+
77
+ Live desktop stream URLs (auth-bearing hosted-VNC links) are never served by
78
+ this surface, in any mode, and the guarantee is layered:
79
+
80
+ - **structural** — runtime stream URLs live in a `WeakMap` keyed by the watch
81
+ process's in-memory `ObserverResult` (`src/observer.ts`) and are never
82
+ persisted into any bundle artifact; serve is a separate process reading disk,
83
+ so there is nothing for it to find;
84
+ - **defensive** — the serve handler passes an explicit empty array at its
85
+ `serveRunPath` call site, so a future refactor that makes injection ambient
86
+ would still serve zero stream URLs here;
87
+ - **tested** — the serve suite pins a no-injection test: observer data served
88
+ through the library surface carries no runtime stream URLs.
89
+
90
+ A `--live-streams` flag on serve was considered and deliberately refused: the
91
+ persisted bundle carries no stream URLs, so the flag would be a claim without a
92
+ mechanism (invariant 6). Remote live following belongs to a designed
93
+ `watch --expose` (see Future work), where the attached process actually holds
94
+ the URLs.
95
+
96
+ ## The share_ready doctrine
97
+
98
+ `share_ready` was designed as the bar for FEEDBACK payloads: evidence eligible
99
+ to leave the machine inside a public issue draft. Serve's open mode extends
100
+ that same gate to arbitrary-audience BROWSING, which is a broader exposure of
101
+ the same artifacts. The honest caveat carries over unchanged: `humanish
102
+ verify` does not yet detect free-form PII/PHI (names, emails, medical
103
+ identifiers — see the README's public-safety boundary and issue #108), so
104
+ `share_ready` means the automated secret/path scan passed, not that a human
105
+ would publish every pixel. Maintainers should treat open mode accordingly:
106
+ synthetic data upstream, review before exposing, and treat `--safe --auth
107
+ none` as publishing because it is.
108
+
109
+ ## v2 control-plane seam contract
110
+
111
+ The reserved `/_humanish/api/*` namespace answers `501` with
112
+ `HUMANISH_SERVE_CONTROL_PLANE_DISABLED` in v1 to any request that clears the
113
+ auth gate — so under `--expose --auth link` a session-less request gets the
114
+ uniform `401` first, and the `501` is what an authenticated (or loopback) caller
115
+ sees. No run artifact can ever shadow the namespace. The seam is already typed:
116
+ `createServeRequestHandler` accepts an optional `ServeControlPlane`, and v1
117
+ always passes `undefined`. Before any mutating route ships, the contract is:
118
+
119
+ - sessions grow a real scope split — today every session is scope `viewer`;
120
+ operators get a DISTINCT operator token, never an upgraded viewer cookie;
121
+ - mutating routes require double-submit CSRF on top of the operator session,
122
+ because a cookie alone is exactly what a cross-site request forges;
123
+ - the spend rule is invariant 3 applied to remote hands: a phone-initiated
124
+ LIVE run needs its own affirmative declaration at serve startup (an explicit
125
+ opt-in naming the lab and budget), never a default the viewer UI can reach.
126
+
127
+ ## Future work
128
+
129
+ - **Persistent capability links.** A link that survives restart is a stored
130
+ secret and needs a real secret-storage design (keychain/agent integration,
131
+ rotation, revocation records) — not a `--token <value>` CLI string, which
132
+ would land in shell history and process listings.
133
+ - **Google Fonts inlining for per-run observer pages.** The library index is
134
+ self-contained; the per-run observer HTML still references remote Google
135
+ Fonts, which degrade gracefully offline but should be inlined (or dropped)
136
+ so a served run page makes no third-party requests from a viewer's browser.
137
+ - **`watch --expose --live-streams`.** Remote LIVE following is a separate
138
+ design: the attached watch process genuinely holds runtime stream URLs, so
139
+ it can expose them deliberately — with the same capability-link gate and its
140
+ own doctrine for auth-bearing hosted-desktop URLs.
@@ -3,7 +3,7 @@
3
3
  Date: 2026-06-02 (current-state note updated 2026-07-14)
4
4
 
5
5
  Status: reference map for the major contracts shipped through source version
6
- `0.16.0`; it is not an exhaustive inventory of command/result envelopes. Exported types,
6
+ `0.17.0`; it is not an exhaustive inventory of command/result envelopes. Exported types,
7
7
  schema constants, parsers, and validators in `src/` are authoritative. Rows
8
8
  marked "reserved" name layering intent only — no code emits or validates them
9
9
  yet. Do not emit a reserved schema.
@@ -46,6 +46,8 @@ workflow without leaking private upstream truth into core.
46
46
  | Adapter score | `humanish.adapter-score.v1` (`RunBundle.adapterScore`; namespaced; route-specific acceptance semantics) | see Product-Adapter Extension Seam below |
47
47
  | Adapter artifact | `humanish.adapter-artifact.v1` (`RunBundle.adapterArtifacts[]`; namespaced; local relative proof references) | see Product-Adapter Extension Seam below |
48
48
  | Shared-world evidence | `humanish.shared-world.v1` (additive `RunBundle.sharedWorld` + `RunBundle.attributionClass`; `topologyMode: sequential \| concurrent`) | see Shared-World Evidence below |
49
+ | Serve result | `humanish.serve-result.v1` (`src/observer-serve.ts` is authoritative) | none (command result envelope; see Serve Result below) |
50
+ | Serve control plane | reserved (`/_humanish/api/*` answers `501` `HUMANISH_SERVE_CONTROL_PLANE_DISABLED` in v1) | none |
49
51
 
50
52
  ## Lab Manifest
51
53
 
@@ -646,6 +648,26 @@ never shipped — no code emits or validates it. Substrate truth today lives
646
648
  inside run bundles (per-stream transport and status) and lab execution config
647
649
  (`execution.target: local | e2b-desktop`). Do not emit this schema.
648
650
 
651
+ ## Serve Result And Reserved Control-Plane Namespace
652
+
653
+ `humanish serve` reports `humanish.serve-result.v1`. The exported `ServeResult`
654
+ type and `SERVE_SCHEMA` constant in `src/observer-serve.ts` are authoritative:
655
+ mode (`loopback | capability-link | share-safe-open`), the loopback host/port,
656
+ capability/public URLs, runs listed, computed warnings, and the
657
+ `ServeErrorCode` union. Capability URLs embed a live secret; they belong on the
658
+ operator's terminal and never in a persisted or committed artifact.
659
+
660
+ Reserved: `/_humanish/api/*` is the serve control-plane namespace. In v1 any
661
+ request under it that clears the auth gate answers `501` with error code
662
+ `HUMANISH_SERVE_CONTROL_PLANE_DISABLED`; under `--expose --auth link` a
663
+ session-less request answers the uniform `401` first. The typed `ServeControlPlane`
664
+ parameter exists in the handler options and is always `undefined` in v1; no
665
+ code dispatches into it yet. Do not build against the namespace; the
666
+ reservation guarantees only that no run artifact or observer asset will ever be
667
+ served under it. See
668
+ [`docs/architecture/serve.md`](../architecture/serve.md) for the v2 seam
669
+ contract.
670
+
649
671
  ## Terminal Cost Ledger And No-Spend Proof
650
672
 
651
673
  The terminal-product lane (`src/e2b-terminal-lab.ts`) passes a real provider key
@@ -16,7 +16,7 @@ Humanish should be the open-source CLI that lets a maintainer ask:
16
16
  The answer should be observable, verifiable, public-safe, and easy to turn into
17
17
  actionable feedback.
18
18
 
19
- ## Current Program Truth (source `0.16.0`)
19
+ ## Current Program Truth (source `0.17.0`)
20
20
 
21
21
  The package source and repository implementation in this tree agree on these
22
22
  points:
@@ -32,6 +32,7 @@ The immutable 2026-06-10 proof-roadmap packet is paired with a
32
32
  | Subject sources/routes | Six declared sources: `this-repo`, `clone`, `app-url`, `local-app`, `terminal-product`, and `local-tree`; support is route-specific and `this-repo` remains dry-run-only | One centralized run/resource lifecycle boundary across all routes |
33
33
  | Public proof | Version-pinned synthetic Observer sample shipped in the npm payload | A legible Observer hero from one verified real public-application run |
34
34
  | OSS meta-lab | Dry-run contract and separate disposable smoke harness | Live meta-lab execution; disabled until repository instructions and actor credentials have an isolated boundary |
35
+ | Observer serving | `watch`/`observe` loopback servers plus `serve` — the run-library surface with loopback default, capability-link exposure, `share_ready`-gated open mode, and optional operator-run tunnel; streams never served remotely | A remote live-stream (`--live-streams`) design; a persistent capability-link store; a control plane that can start runs |
35
36
 
36
37
  Capability proof and adopter replacement are different gates. A deterministic
37
38
  test or kept live receipt proves that a Humanish mechanism works. The depth-axis
@@ -74,6 +74,7 @@ silently drifting from one is not.
74
74
  | Single lane | Cost + evidence simplicity | Declared fan-out where the backend supports it — `actors[0].count: N` (homogeneous), explicit `actors[0].lanes[]` (differentiated persona/device/instruction), or compact `actors[0].roster[]` groups that normalize into lanes on the computer-use E2B route (per-lane worlds, cap 16; `execution.concurrency` bounds concurrent paid lanes) |
75
75
  | Stock `desktop` template | The stock E2B desktop image is right for most subjects; absent `execution.desktop.template` keeps `Sandbox.create(opts)` byte-stable | `execution.desktop.template` names a custom E2B desktop image (any name/id, no allowlist) for a subject needing baked-in runtimes the stock image lacks (e.g. node/bun/a local Postgres) — threaded to `Sandbox.create(template, opts)` on every desktop-creating route and recorded in the bundle as `desktopTemplate` (public-safe) |
76
76
  | Desktop default URL opener | Preserve the route/image's historical browser/default opener behavior when unset | `execution.desktop.browser: chrome | chromium | firefox` makes hosted CUA/shared-world browser choice explicit, fail-closed, and recorded as `desktopBrowser` in the run bundle |
77
+ | Loopback-only observer serving | Run bundles are local by default (public-safety plank 3); the serve surface binds `127.0.0.1` unconditionally, and exposing the library is publishing-adjacent, so it carries declared friction | `humanish serve --expose` (secret capability link + a declared public origin via `--tunnel`/`--public-url`) or `--expose --safe --auth none` (open mode, gated on verify-`share_ready` runs only); both print computed warnings naming exactly what became reachable |
77
78
 
78
79
  ## The placement rule (worked example)
79
80
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  Status: public-safe contributor and agent ramp.
4
4
 
5
- Package/source version in this tree: `0.16.0` (2026-08-01). The containment boundary introduced in
5
+ Package/source version in this tree: `0.17.0` (2026-08-02). The containment boundary introduced in
6
6
  `0.15.1` remains in force: managed run and output paths bind to validated
7
7
  physical filesystem identities, and stored provider IDs are evidence, not
8
8
  cleanup authority. The bundled OSS meta-lab is dry-run only until
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "humanish",
3
- "version": "0.16.0",
3
+ "version": "0.17.0",
4
4
  "description": "Open-source-safe CLI for persona simulation, observer review, and public-safe feedback drafts.",
5
5
  "author": "Daniel G Wilson <daniel@danielgwilson.com>",
6
6
  "keywords": [