@logicseek/hello-cc 0.1.9 → 1.0.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.
- package/CHANGELOG.md +59 -0
- package/README.md +18 -3
- package/README.zh-CN.md +17 -3
- package/bin/hcc.mjs +3 -5656
- package/docs/README.md +8 -0
- package/docs/README.zh-CN.md +6 -0
- package/docs/architecture.md +29 -18
- package/docs/architecture.zh-CN.md +20 -10
- package/docs/commands.md +13 -4
- package/docs/commands.zh-CN.md +13 -4
- package/docs/current-state-review-2026-08.md +341 -0
- package/docs/defect-review-2026-07.md +382 -0
- package/docs/guide.md +39 -3
- package/docs/guide.zh-CN.md +34 -2
- package/docs/superpowers/plans/2026-08-03-runtime-safety-plan.md +294 -0
- package/docs/superpowers/plans/2026-08-03-v1-data-identity-plan.md +400 -0
- package/docs/superpowers/plans/2026-08-03-v1-release-verification-plan.md +284 -0
- package/docs/superpowers/plans/2026-08-03-web-api-hardening-plan.md +303 -0
- package/docs/superpowers/plans/2026-08-04-runtime-buffer-gc-two-phase.md +71 -0
- package/docs/superpowers/specs/2026-08-03-runtime-safety-design.md +117 -0
- package/docs/superpowers/specs/2026-08-03-v1-data-identity-design.md +120 -0
- package/docs/superpowers/specs/2026-08-03-v1-release-verification-design.md +117 -0
- package/docs/superpowers/specs/2026-08-03-web-api-hardening-design.md +104 -0
- package/lib/cli/app.mjs +642 -0
- package/lib/cli/commands/coordination.mjs +247 -0
- package/lib/cli/commands/doctor.mjs +81 -0
- package/lib/cli/commands/gc.mjs +852 -0
- package/lib/cli/commands/hook.mjs +205 -0
- package/lib/cli/commands/install.mjs +294 -0
- package/lib/cli/commands/lifecycle.mjs +101 -0
- package/lib/cli/commands/lock.mjs +197 -0
- package/lib/cli/commands/misc.mjs +61 -0
- package/lib/cli/commands/msg.mjs +152 -0
- package/lib/cli/commands/peer.mjs +233 -0
- package/lib/cli/commands/query.mjs +96 -0
- package/lib/cli/commands/run.mjs +463 -0
- package/lib/cli/commands/task.mjs +405 -0
- package/lib/cli/commands/team.mjs +131 -0
- package/lib/cli/commands/tmux.mjs +838 -0
- package/lib/cli/commands/up.mjs +135 -0
- package/lib/cli-args.mjs +16 -2
- package/lib/coordination-state.mjs +112 -51
- package/lib/core/coordination/automation.mjs +5 -8
- package/lib/core/coordination/clock-safety.mjs +323 -0
- package/lib/core/coordination/event-retention.mjs +17 -0
- package/lib/core/coordination/gc-plan.mjs +614 -0
- package/lib/core/coordination/lease-renewal.mjs +102 -0
- package/lib/core/coordination/lock-evidence.mjs +77 -0
- package/lib/core/coordination/optimistic-evidence.mjs +26 -0
- package/lib/core/coordination/tasks.mjs +156 -36
- package/lib/core/peers/evidence-runtime.mjs +312 -0
- package/lib/core/peers/evidence.mjs +103 -0
- package/lib/core/peers/hook-owner.mjs +32 -0
- package/lib/core/peers/liveness.mjs +44 -4
- package/lib/core/peers/peer-helpers.mjs +121 -0
- package/lib/core/peers/session.mjs +8 -2
- package/lib/core/peers/tmux-safety.mjs +290 -0
- package/lib/core/sessions/launch.mjs +13 -1
- package/lib/db/connection.mjs +104 -0
- package/lib/db/events.mjs +34 -0
- package/lib/db/migration-backup.mjs +225 -0
- package/lib/db/schema.mjs +102 -5
- package/lib/db/stores/peers.mjs +34 -0
- package/lib/integrations/peers/identity.mjs +62 -16
- package/lib/integrations/shims/script.mjs +111 -9
- package/lib/process/identity.mjs +192 -0
- package/lib/process/pty-lifecycle.mjs +149 -0
- package/lib/runtime/buffer-directory-lease.mjs +47 -0
- package/lib/runtime/buffer-evidence.mjs +336 -0
- package/lib/runtime/buffer-gc-protocol.mjs +167 -0
- package/lib/runtime/buffer-gc.mjs +270 -0
- package/lib/runtime/client.mjs +60 -7
- package/lib/runtime/fatal-shutdown.mjs +54 -0
- package/lib/runtime/project-path.mjs +139 -0
- package/lib/runtime/projects.mjs +320 -40
- package/lib/runtime/state.mjs +227 -27
- package/lib/shared/cleanup-error.mjs +54 -0
- package/lib/shared/clock-grace.mjs +106 -0
- package/lib/shared/errors.mjs +9 -0
- package/lib/shared/file-lock.mjs +370 -0
- package/lib/shared/json-file.mjs +6 -2
- package/lib/shared/redact.mjs +60 -0
- package/lib/shared/socket-lock-worker.mjs +201 -0
- package/lib/terminal/tmux.mjs +78 -4
- package/lib/tmux.mjs +4 -0
- package/lib/ui/help.mjs +53 -15
- package/lib/web/api-version.mjs +42 -0
- package/lib/web/auto-attach.mjs +185 -0
- package/lib/web/buffer-gc-runtime.mjs +175 -0
- package/lib/web/cookie-auth.mjs +119 -0
- package/lib/web/external-sessions.mjs +254 -0
- package/lib/web/http-routes.mjs +542 -0
- package/lib/web/http.mjs +135 -6
- package/lib/web/liveness-reaper.mjs +145 -0
- package/lib/web/peer-actions.mjs +132 -23
- package/lib/web/project-contexts.mjs +178 -0
- package/lib/web/pty-sessions.mjs +203 -0
- package/lib/web/runtime-main.mjs +714 -0
- package/lib/web/runtime.mjs +91 -36
- package/lib/web/session-serialize.mjs +0 -0
- package/lib/web/startup.mjs +333 -0
- package/lib/web/tls.mjs +365 -0
- package/lib/web/tmux-sessions.mjs +1361 -0
- package/lib/web/tmux-stream.mjs +166 -0
- package/lib/web/ui-template.mjs +152 -20
- package/package.json +5 -2
- package/scripts/check-free-identifiers.mjs +290 -0
- package/scripts/regression.mjs +5397 -263
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# Runtime Safety, Clock, GC, and Registry Design
|
|
2
|
+
|
|
3
|
+
Status: approved in conversation on 2026-08-03.
|
|
4
|
+
|
|
5
|
+
## Purpose
|
|
6
|
+
|
|
7
|
+
Close destructive-operation and ownership races without forcing every project
|
|
8
|
+
to wait after normal idle time. Verified process evidence is authoritative;
|
|
9
|
+
clock grace is reserved for unknown evidence.
|
|
10
|
+
|
|
11
|
+
## Clock Observation
|
|
12
|
+
|
|
13
|
+
The meta table stores a last-observed wall-clock watermark and the active grace
|
|
14
|
+
deadline. Observation runs before ownership-changing or destructive operations,
|
|
15
|
+
including lock acquisition, takeover, hook/heartbeat recovery, reaping, and GC.
|
|
16
|
+
Pure read-only commands do not update the watermark, preserving read-only DB
|
|
17
|
+
behavior.
|
|
18
|
+
|
|
19
|
+
A clock observation transaction:
|
|
20
|
+
|
|
21
|
+
1. reads the previous watermark and current grace deadline;
|
|
22
|
+
2. evaluates backward movement and long forward gaps;
|
|
23
|
+
3. for ownership operations, checks whether the gap crossed a peer or lock
|
|
24
|
+
expiry boundary whose owner is currently `unknown`;
|
|
25
|
+
4. for GC, checks whether suspicious backward/forward movement or a long gap
|
|
26
|
+
crossed the requested age cutoff for any deletion category, independent of
|
|
27
|
+
owner evidence;
|
|
28
|
+
5. atomically extends grace to `max(existing, now + 120 seconds)` when needed;
|
|
29
|
+
6. writes the new watermark only after the safety decision succeeds.
|
|
30
|
+
|
|
31
|
+
A verified live owner is renewed immediately from persisted `ttl_sec`. A
|
|
32
|
+
verified dead owner may expire immediately. Only unknown evidence is delayed by
|
|
33
|
+
grace. Failure to persist a required grace blocks the current destructive or
|
|
34
|
+
ownership-changing operation and returns an observable error.
|
|
35
|
+
|
|
36
|
+
The Web runtime also compares wall-clock movement with its monotonic probe and
|
|
37
|
+
uses the same database transaction. Concurrent probes can extend but never
|
|
38
|
+
shorten grace.
|
|
39
|
+
|
|
40
|
+
## Grace Semantics
|
|
41
|
+
|
|
42
|
+
While grace is active, every age-based deletion is deferred:
|
|
43
|
+
|
|
44
|
+
- expired or apparently expired locks;
|
|
45
|
+
- stale peers and peer bindings;
|
|
46
|
+
- events, completed tasks, messages, and handoffs;
|
|
47
|
+
- buffer files selected only by mtime.
|
|
48
|
+
|
|
49
|
+
Explicit `exited` peers and artifacts proven dead by identity may still be
|
|
50
|
+
cleaned. Results expose separate `deleted`, `protected`, and `deferred` counts.
|
|
51
|
+
|
|
52
|
+
## Buffer GC Arbitration
|
|
53
|
+
|
|
54
|
+
The buffer pruning policy is a shared helper that receives a fixed cutoff,
|
|
55
|
+
dry-run flag, project context, and protected absolute paths. It never recomputes
|
|
56
|
+
the cutoff during a run.
|
|
57
|
+
|
|
58
|
+
When a healthy compatible runtime exists, manual GC calls an authenticated
|
|
59
|
+
internal API. The runtime computes protection from the actual files referenced
|
|
60
|
+
by its session map and performs synchronous pruning in its event loop. Session
|
|
61
|
+
creation and replacement cannot interleave with that synchronous decision. New
|
|
62
|
+
files created after the fixed cutoff are not eligible.
|
|
63
|
+
|
|
64
|
+
If a runtime pointer exists but the runtime is unreachable or API-incompatible,
|
|
65
|
+
buffer deletion fails closed and reports deferred files. Safe database cleanup
|
|
66
|
+
may continue.
|
|
67
|
+
|
|
68
|
+
Without a runtime, local GC protects:
|
|
69
|
+
|
|
70
|
+
- external file groups whose wrapper or child identity is live or unknown;
|
|
71
|
+
- tmux pipes belonging to verified live or unknown managed panes;
|
|
72
|
+
- any legacy artifact whose ownership cannot be proved dead.
|
|
73
|
+
|
|
74
|
+
Only old artifacts with definitive dead or orphan evidence are deleted.
|
|
75
|
+
|
|
76
|
+
## GC Command Contract
|
|
77
|
+
|
|
78
|
+
`--older-than` must be zero or greater. Invalid values fail before opening a
|
|
79
|
+
write connection or deleting any file.
|
|
80
|
+
|
|
81
|
+
`hcc gc` remains dry-run by default. `--yes` removes technical state only.
|
|
82
|
+
Business history removal requires both `--history` and `--yes`. Open-task
|
|
83
|
+
handoffs, live-owner records, and unknown session artifacts are never removed
|
|
84
|
+
only because of age. Dry-run and apply execute the same planner.
|
|
85
|
+
|
|
86
|
+
A WAL checkpoint runs only after successful applied database cleanup. A busy
|
|
87
|
+
checkpoint is reported without converting successful safe cleanup into failure.
|
|
88
|
+
|
|
89
|
+
## Project Registry Lock
|
|
90
|
+
|
|
91
|
+
Registry read-modify-write is guarded by an atomic lock directory adjacent to
|
|
92
|
+
`projects.json`. Lock owner metadata includes process identity and creation
|
|
93
|
+
time. Acquisition uses bounded retry. A lock is reclaimed only when its owner is
|
|
94
|
+
definitively dead; unknown ownership times out with an error rather than being
|
|
95
|
+
stolen.
|
|
96
|
+
|
|
97
|
+
After acquiring the lock, the writer re-reads the registry, merges by canonical
|
|
98
|
+
real root, and writes with the existing atomic temp-and-rename helper. A changed
|
|
99
|
+
DB path updates immediately. The 60-second throttle applies only when root and
|
|
100
|
+
DB are both unchanged.
|
|
101
|
+
|
|
102
|
+
Authenticated Web users may continue registering arbitrary existing server
|
|
103
|
+
directories. This is an explicitly accepted product risk, not an isolation
|
|
104
|
+
guarantee.
|
|
105
|
+
|
|
106
|
+
## Acceptance Criteria
|
|
107
|
+
|
|
108
|
+
- CLI-only wake with a live process renews ownership without a blanket delay.
|
|
109
|
+
- CLI-only wake with unknown evidence enters grace before takeover or GC.
|
|
110
|
+
- A GC after a suspicious clock gap defers age-based history deletion even when
|
|
111
|
+
no peer or lock candidate exists.
|
|
112
|
+
- Grace never shrinks under concurrent writers.
|
|
113
|
+
- Manual GC preserves live external and tmux artifacts with old mtimes.
|
|
114
|
+
- Unreachable runtime state defers buffer deletion.
|
|
115
|
+
- Negative retention is rejected without mutation.
|
|
116
|
+
- Grace defers every wall-clock-based history deletion.
|
|
117
|
+
- Concurrent registry writers retain both projects and update changed DB paths.
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# hello-cc v1 Data and Identity Design
|
|
2
|
+
|
|
3
|
+
Status: approved in conversation on 2026-08-03.
|
|
4
|
+
|
|
5
|
+
## Purpose
|
|
6
|
+
|
|
7
|
+
Define the breaking v1 data contract, schema migration, process identity model,
|
|
8
|
+
and provider peer identity semantics. This specification intentionally does not
|
|
9
|
+
provide downgrade compatibility or map pre-v1 provider peer IDs to v1 IDs.
|
|
10
|
+
|
|
11
|
+
## Product Decisions
|
|
12
|
+
|
|
13
|
+
- Release the result as package version `1.0.0`.
|
|
14
|
+
- Use database schema version 7.
|
|
15
|
+
- A schema v7 database is not supported by pre-v1 binaries.
|
|
16
|
+
- Provider peer IDs use the v1 full-value hash algorithm for every provider ID,
|
|
17
|
+
including UUIDs and user-defined session names.
|
|
18
|
+
- Pre-v1 peer IDs, tasks, locks, messages, and bindings are not merged into the
|
|
19
|
+
new v1 identity automatically.
|
|
20
|
+
- Migration must create a consistent database backup before the first v7 write.
|
|
21
|
+
|
|
22
|
+
## Migration Contract
|
|
23
|
+
|
|
24
|
+
Before migrating a v5 or v6 database, hello-cc must create a SQLite-consistent
|
|
25
|
+
snapshot next to the source database. The backup name includes the source
|
|
26
|
+
schema version and a collision-resistant timestamp suffix. It must never
|
|
27
|
+
overwrite an existing file.
|
|
28
|
+
|
|
29
|
+
The backup is produced through the open SQLite connection with `VACUUM INTO`,
|
|
30
|
+
not by copying `mesh.db` while WAL frames may still be pending. Migration stops
|
|
31
|
+
without changing the source database if backup creation or backup integrity
|
|
32
|
+
validation fails. A successful backup is opened read-only and must pass
|
|
33
|
+
`PRAGMA quick_check` before schema migration begins.
|
|
34
|
+
|
|
35
|
+
Registered sibling projects follow the same contract independently. A failed
|
|
36
|
+
sibling backup or migration is reported and isolated; it must not partially
|
|
37
|
+
migrate that sibling or fail the active project's command.
|
|
38
|
+
|
|
39
|
+
## Schema v7
|
|
40
|
+
|
|
41
|
+
Schema v7 adds process identity fields to `peers`:
|
|
42
|
+
|
|
43
|
+
- `pid_start_token TEXT`: opaque platform-derived process start identity.
|
|
44
|
+
- `pid_command_hash TEXT`: hash of the normalized executable/command identity.
|
|
45
|
+
|
|
46
|
+
Both fields are nullable. Missing or unreadable identity evidence means
|
|
47
|
+
`unknown`; it never means live or dead.
|
|
48
|
+
|
|
49
|
+
Existing v6 `locks.ttl_sec` remains required. Invalid or absent historical TTL
|
|
50
|
+
values are repaired to the documented default, capped at one hour when derived
|
|
51
|
+
from historical timestamps.
|
|
52
|
+
|
|
53
|
+
The schema migration history records v7 exactly once. `doctor` reports the
|
|
54
|
+
current schema, supported schema, backup location when known, and the explicit
|
|
55
|
+
no-downgrade contract.
|
|
56
|
+
|
|
57
|
+
## Process Identity
|
|
58
|
+
|
|
59
|
+
A shared process identity helper returns one of:
|
|
60
|
+
|
|
61
|
+
- a verified identity `{ pid, startToken, commandHash }`;
|
|
62
|
+
- `null` when the process does not exist;
|
|
63
|
+
- `unknown` when the process cannot be inspected safely.
|
|
64
|
+
|
|
65
|
+
Linux identity combines boot ID, `/proc/<pid>/stat` start ticks, and normalized
|
|
66
|
+
executable identity. macOS identity combines boot time, `ps` process start time,
|
|
67
|
+
PID, and normalized command identity. Inspection failures, permission errors,
|
|
68
|
+
or malformed output return `unknown`.
|
|
69
|
+
|
|
70
|
+
A stored identity is live only when the current identity matches every stored
|
|
71
|
+
component. A missing PID is dead only when the OS definitively reports that the
|
|
72
|
+
PID does not exist. A reused PID with a different start token or command hash is
|
|
73
|
+
dead for the stored peer.
|
|
74
|
+
|
|
75
|
+
External session metadata stores separate wrapper and child identities. New
|
|
76
|
+
metadata is written atomically. Legacy metadata without fingerprints is treated
|
|
77
|
+
as unknown unless both referenced PIDs are definitively absent.
|
|
78
|
+
|
|
79
|
+
tmux identity requires all of the following:
|
|
80
|
+
|
|
81
|
+
- the managed session name matches the hello-cc naming contract;
|
|
82
|
+
- the pane still exists;
|
|
83
|
+
- `HCC_ROOT` resolves to the expected project root;
|
|
84
|
+
- the pane process identity matches the stored identity when one is available.
|
|
85
|
+
|
|
86
|
+
## Liveness Precedence
|
|
87
|
+
|
|
88
|
+
All ownership, lock, reaper, and GC decisions consume one shared resolver with
|
|
89
|
+
the result `live`, `dead`, or `unknown`.
|
|
90
|
+
|
|
91
|
+
1. Explicit administrative `exited` status is dead.
|
|
92
|
+
2. Verified tmux or process identity is live.
|
|
93
|
+
3. `detached` is not dead by itself; a detached tmux session may still be live.
|
|
94
|
+
4. Definitive process absence plus definitive tmux absence is dead.
|
|
95
|
+
5. Conflicting, missing, legacy, or unreadable evidence is unknown.
|
|
96
|
+
|
|
97
|
+
Wall-clock age never overrides verified live or dead evidence.
|
|
98
|
+
|
|
99
|
+
## Provider Peer IDs
|
|
100
|
+
|
|
101
|
+
JavaScript and generated shims derive the same first eight lowercase SHA-1 hex
|
|
102
|
+
characters from the complete provider ID. Hash output is accepted only when it
|
|
103
|
+
is exactly 40 lowercase hexadecimal characters. When no supported hash command
|
|
104
|
+
can produce a valid digest, a shim starts the real provider without hello-cc
|
|
105
|
+
coordination rather than creating an ambiguous ID.
|
|
106
|
+
|
|
107
|
+
The v1 release notes must show examples of old and new IDs and explain that old
|
|
108
|
+
coordination history remains queryable in the backup but is not attached to the
|
|
109
|
+
new peer automatically.
|
|
110
|
+
|
|
111
|
+
## Acceptance Criteria
|
|
112
|
+
|
|
113
|
+
- A v5 and a development v6 fixture each produce a validated backup and migrate
|
|
114
|
+
once to v7.
|
|
115
|
+
- A forced backup failure leaves the source schema and data unchanged.
|
|
116
|
+
- A pre-v1 binary rejects v7 with the documented error.
|
|
117
|
+
- PID reuse is classified as dead for the stored identity.
|
|
118
|
+
- A live tmux pane remains live across sleep-sized wall-clock gaps.
|
|
119
|
+
- A detached but live tmux session is not made takeover-ready.
|
|
120
|
+
- JavaScript and every generated shim produce identical v1 peer IDs.
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# hello-cc v1 Release and Verification Design
|
|
2
|
+
|
|
3
|
+
Status: approved in conversation on 2026-08-03.
|
|
4
|
+
|
|
5
|
+
## Purpose
|
|
6
|
+
|
|
7
|
+
Define code ownership boundaries, fatal error behavior, packaging, documentation,
|
|
8
|
+
and evidence required before the remaining hardening work can be called complete.
|
|
9
|
+
|
|
10
|
+
## Module Boundaries
|
|
11
|
+
|
|
12
|
+
New focused modules own these policies:
|
|
13
|
+
|
|
14
|
+
- process identity inspection and comparison;
|
|
15
|
+
- clock observation and liveness resolution;
|
|
16
|
+
- project registry locking;
|
|
17
|
+
- buffer GC planning and arbitration;
|
|
18
|
+
- Runtime API version negotiation;
|
|
19
|
+
- secret redaction.
|
|
20
|
+
|
|
21
|
+
`bin/hcc.mjs` remains the command and runtime orchestrator. It calls these
|
|
22
|
+
modules but does not duplicate their decision logic. Existing repository
|
|
23
|
+
patterns are retained; unrelated restructuring is out of scope.
|
|
24
|
+
|
|
25
|
+
Every imported production module must be tracked and included by the package
|
|
26
|
+
`files` contract. Docker test files are tracked but remain outside the npm
|
|
27
|
+
package.
|
|
28
|
+
|
|
29
|
+
## Fatal Error Behavior
|
|
30
|
+
|
|
31
|
+
Expected poller, watcher, sibling-project, and transient runtime failures are
|
|
32
|
+
caught at their ownership boundary, logged with redaction, and isolated.
|
|
33
|
+
|
|
34
|
+
An uncaught exception or unhandled rejection initiates one idempotent fatal
|
|
35
|
+
shutdown. Shutdown stops timers and watchers, closes sessions and sockets using
|
|
36
|
+
the documented detach/exit semantics, clears only runtime pointers owned by the
|
|
37
|
+
current process identity, closes the server and database resources, then exits
|
|
38
|
+
nonzero. The runtime must not continue after an unknown exception.
|
|
39
|
+
|
|
40
|
+
A second fatal signal during shutdown forces termination without attempting a
|
|
41
|
+
second cleanup pass.
|
|
42
|
+
|
|
43
|
+
## Release Contract
|
|
44
|
+
|
|
45
|
+
The package version becomes `1.0.0`. Changelog, README files, bilingual command
|
|
46
|
+
documentation, help output, and the defect review describe:
|
|
47
|
+
|
|
48
|
+
- schema v7 and no downgrade support;
|
|
49
|
+
- the pre-migration backup;
|
|
50
|
+
- provider peer ID changes;
|
|
51
|
+
- Runtime API v2 and session token delivery changes;
|
|
52
|
+
- safe GC defaults and `--history`;
|
|
53
|
+
- process-evidence-first liveness and unknown-only grace;
|
|
54
|
+
- optional TLS and trusted proxy usage;
|
|
55
|
+
- accepted plaintext LAN and arbitrary-root risks.
|
|
56
|
+
|
|
57
|
+
The defect report uses evidence-backed status counts and the actual branch. It
|
|
58
|
+
must not label accepted risks or partial controls as completely fixed.
|
|
59
|
+
|
|
60
|
+
The Dockerfile uses `npm ci`, records Node and tmux versions in test output, and
|
|
61
|
+
builds from the complete current source tree. `.dockerignore` excludes host
|
|
62
|
+
runtime state and secrets without excluding production source.
|
|
63
|
+
|
|
64
|
+
## Test Architecture
|
|
65
|
+
|
|
66
|
+
Pure policies use Node's built-in test runner in focused test files. Real CLI,
|
|
67
|
+
SQLite, tmux, HTTP, HTTPS, and WebSocket boundaries remain integration tests.
|
|
68
|
+
Tests do not add production-only backdoors or test-only public APIs.
|
|
69
|
+
|
|
70
|
+
Each behavior change follows red-green-refactor:
|
|
71
|
+
|
|
72
|
+
1. add the smallest regression that demonstrates the missing invariant;
|
|
73
|
+
2. run it and record the expected failure;
|
|
74
|
+
3. implement the minimum production change;
|
|
75
|
+
4. rerun the focused test and relevant neighboring tests;
|
|
76
|
+
5. refactor only while green.
|
|
77
|
+
|
|
78
|
+
Required regressions cover:
|
|
79
|
+
|
|
80
|
+
- v5/v6 backup and migration failure atomicity;
|
|
81
|
+
- PID reuse and platform process inspection;
|
|
82
|
+
- live, dead, and unknown tmux/external peers;
|
|
83
|
+
- CLI-only sleep-sized gaps and concurrent grace writers;
|
|
84
|
+
- manual/automatic GC, negative retention, history opt-in, and runtime failure;
|
|
85
|
+
- concurrent registry writers and same-root DB changes;
|
|
86
|
+
- API v2, cookie lifecycle, action-token scope, CSP, and redaction;
|
|
87
|
+
- realpath and symlink containment;
|
|
88
|
+
- fatal shutdown cleanup;
|
|
89
|
+
- package content and tracked-file completeness.
|
|
90
|
+
|
|
91
|
+
TTL renewal tests use the renewal operation's recorded timestamp rather than
|
|
92
|
+
assuming wall-clock readings in separate processes are monotonic. This retains
|
|
93
|
+
strict TTL inflation checks without the observed one-second container flake.
|
|
94
|
+
|
|
95
|
+
## Verification Gates
|
|
96
|
+
|
|
97
|
+
Completion requires fresh evidence, in this order:
|
|
98
|
+
|
|
99
|
+
1. final diff and tracked production import audit;
|
|
100
|
+
2. syntax/import/package-content checks;
|
|
101
|
+
3. focused unit and security regression tests;
|
|
102
|
+
4. original vulnerable-path reproductions and alternate bypass inputs;
|
|
103
|
+
5. preserved legitimate behavior tests;
|
|
104
|
+
6. complete local regression;
|
|
105
|
+
7. a freshly built Node 24/Linux image;
|
|
106
|
+
8. three consecutive complete container regressions;
|
|
107
|
+
9. final `origin/master..worktree` review and documentation consistency check.
|
|
108
|
+
|
|
109
|
+
A failed gate blocks a completion claim. Accepted risks are reported separately
|
|
110
|
+
and are not represented as test gaps or fixed findings.
|
|
111
|
+
|
|
112
|
+
## Definition of Done
|
|
113
|
+
|
|
114
|
+
The work is complete only when every approved non-accepted-risk requirement in
|
|
115
|
+
the four v1 design specifications has a passing test or a documented reason the
|
|
116
|
+
check is inapplicable, all required verification gates pass, and the final
|
|
117
|
+
working tree contains no untracked production dependency.
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# Web and Runtime API v2 Hardening Design
|
|
2
|
+
|
|
3
|
+
Status: approved in conversation on 2026-08-03.
|
|
4
|
+
|
|
5
|
+
## Purpose
|
|
6
|
+
|
|
7
|
+
Define the v1 Web authentication, Runtime API v2, browser policy, path safety,
|
|
8
|
+
and log-redaction contract while preserving two explicitly accepted risks.
|
|
9
|
+
|
|
10
|
+
## Accepted Risks
|
|
11
|
+
|
|
12
|
+
- The default Web listener remains `0.0.0.0` over HTTP for LAN access.
|
|
13
|
+
- An authenticated administrator may select any existing server project root.
|
|
14
|
+
|
|
15
|
+
These choices permit passive LAN observation of HTTP traffic and broad access
|
|
16
|
+
to server project directories by anyone holding the administrator credential.
|
|
17
|
+
Documentation must state these risks and must not mark transport confidentiality
|
|
18
|
+
or project scoping as fully remediated.
|
|
19
|
+
|
|
20
|
+
## Authentication Roles
|
|
21
|
+
|
|
22
|
+
The stable runtime token is the administrator credential. An HttpOnly browser
|
|
23
|
+
cookie issued after successful token login represents the same administrator
|
|
24
|
+
role. Both may create, stop, attach, and inject managed sessions.
|
|
25
|
+
|
|
26
|
+
A session action token is narrower. It authorizes terminal WebSocket input for
|
|
27
|
+
one managed session and is delivered only in that session's terminal snapshot.
|
|
28
|
+
It is never returned by the session list. Session action token comparisons use
|
|
29
|
+
the shared constant-time token helper.
|
|
30
|
+
|
|
31
|
+
Cookie sessions have a server-side expiry, a bounded count, explicit logout,
|
|
32
|
+
and tracked WebSocket membership. Expiry, eviction, or logout closes associated
|
|
33
|
+
WebSockets immediately. Cookie-authenticated mutations require a same-origin
|
|
34
|
+
request.
|
|
35
|
+
|
|
36
|
+
Non-loopback listeners require a non-empty administrator token. Tokenless mode
|
|
37
|
+
is accepted only from a loopback socket.
|
|
38
|
+
|
|
39
|
+
## Runtime API v2
|
|
40
|
+
|
|
41
|
+
Runtime metadata advertises API version 2. CLI and browser HTTP requests send
|
|
42
|
+
`X-HCC-API-Version: 2`. Browser WebSocket handshakes send `api_version=2` in the
|
|
43
|
+
query because the WebSocket browser API cannot set an arbitrary request header.
|
|
44
|
+
Protected API requests with a missing or unsupported version receive HTTP 426
|
|
45
|
+
with `API_VERSION_UNSUPPORTED` and the supported version.
|
|
46
|
+
|
|
47
|
+
Public login and static bootstrap routes remain reachable without the API
|
|
48
|
+
version header. The v1 release notes identify removal of `action_token` from
|
|
49
|
+
`GET /api/sessions` as a breaking API change.
|
|
50
|
+
|
|
51
|
+
## Browser Policy
|
|
52
|
+
|
|
53
|
+
HTML responses use a per-response nonce. The Content Security Policy permits
|
|
54
|
+
scripts only from self and the response nonce, denies objects and framing,
|
|
55
|
+
restricts base URIs, and permits only the same-origin HTTP/WebSocket connections
|
|
56
|
+
needed by the console. Styles allow the current xterm and inline-style contract;
|
|
57
|
+
script policy must not require `unsafe-inline`.
|
|
58
|
+
|
|
59
|
+
Responses retain no-store, no-referrer, and nosniff headers. Secure requests may
|
|
60
|
+
add transport-only headers without applying HSTS to accepted plaintext mode.
|
|
61
|
+
|
|
62
|
+
## Path Safety
|
|
63
|
+
|
|
64
|
+
Arbitrary existing roots remain supported, but lexical containment is not
|
|
65
|
+
enough. The selected root is canonicalized with `realpath`. The `.hello-cc`
|
|
66
|
+
directory is created with owner-only permissions when absent and canonicalized
|
|
67
|
+
when present. A symlinked `.hello-cc` or DB parent that resolves outside the
|
|
68
|
+
canonical state directory is rejected.
|
|
69
|
+
|
|
70
|
+
The selected database must resolve to `mesh.db` or another path below the real
|
|
71
|
+
`.hello-cc` directory. Pseudo-files, non-regular database targets, and symlink
|
|
72
|
+
escapes are rejected before SQLite opens the target.
|
|
73
|
+
|
|
74
|
+
## TLS and Proxy Modes
|
|
75
|
+
|
|
76
|
+
Direct `--tls` mode validates certificate dates, SAN coverage, key pairing, file
|
|
77
|
+
permissions, and generation publication before listening. CLI requests trust
|
|
78
|
+
only the runtime's stored self-signed certificate.
|
|
79
|
+
|
|
80
|
+
`--trust-proxy --proxy-origin https://host[:port]` accepts forwarded host/proto
|
|
81
|
+
only from a loopback peer and only when both exactly match the pinned public
|
|
82
|
+
origin. Either option without the other is rejected. Forwarded headers from any
|
|
83
|
+
non-loopback peer are ignored. Cookie `Secure` state and origin checks consume
|
|
84
|
+
the same trusted-proxy decision.
|
|
85
|
+
|
|
86
|
+
## Secret Redaction
|
|
87
|
+
|
|
88
|
+
All startup and error logging passes through one redactor. It removes or replaces
|
|
89
|
+
values for `--token`, Authorization, cookies, session action tokens, runtime
|
|
90
|
+
tokens, and URL `token` query parameters. The redactor handles both structured
|
|
91
|
+
objects and command-line strings. Runtime pointer files remain owner-readable
|
|
92
|
+
only because local CLI operation requires the administrator token.
|
|
93
|
+
|
|
94
|
+
## Acceptance Criteria
|
|
95
|
+
|
|
96
|
+
- Old or missing API versions receive 426 on protected API routes.
|
|
97
|
+
- Admin token and admin cookie retain documented management behavior.
|
|
98
|
+
- Session action tokens are absent from lists and required for WS input.
|
|
99
|
+
- Logout and expiry close existing cookie-authenticated WebSockets.
|
|
100
|
+
- CSP blocks an injected inline script without the nonce.
|
|
101
|
+
- Logs contain no supplied administrator, cookie, or session token value.
|
|
102
|
+
- Symlink DB escape is rejected while a normal arbitrary project root works.
|
|
103
|
+
- Direct TLS, loopback trusted proxy, and accepted plaintext LAN modes pass
|
|
104
|
+
independent integration tests.
|