@logicseek/hello-cc 0.1.8 → 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.
Files changed (108) hide show
  1. package/CHANGELOG.md +117 -0
  2. package/README.md +26 -4
  3. package/README.zh-CN.md +22 -3
  4. package/bin/hcc.mjs +3 -5622
  5. package/docs/README.md +8 -0
  6. package/docs/README.zh-CN.md +6 -0
  7. package/docs/architecture.md +29 -18
  8. package/docs/architecture.zh-CN.md +20 -10
  9. package/docs/commands.md +16 -5
  10. package/docs/commands.zh-CN.md +16 -5
  11. package/docs/current-state-review-2026-08.md +341 -0
  12. package/docs/defect-review-2026-07.md +382 -0
  13. package/docs/guide.md +53 -5
  14. package/docs/guide.zh-CN.md +46 -3
  15. package/docs/superpowers/plans/2026-08-03-runtime-safety-plan.md +294 -0
  16. package/docs/superpowers/plans/2026-08-03-v1-data-identity-plan.md +400 -0
  17. package/docs/superpowers/plans/2026-08-03-v1-release-verification-plan.md +284 -0
  18. package/docs/superpowers/plans/2026-08-03-web-api-hardening-plan.md +303 -0
  19. package/docs/superpowers/plans/2026-08-04-runtime-buffer-gc-two-phase.md +71 -0
  20. package/docs/superpowers/specs/2026-08-03-runtime-safety-design.md +117 -0
  21. package/docs/superpowers/specs/2026-08-03-v1-data-identity-design.md +120 -0
  22. package/docs/superpowers/specs/2026-08-03-v1-release-verification-design.md +117 -0
  23. package/docs/superpowers/specs/2026-08-03-web-api-hardening-design.md +104 -0
  24. package/lib/cli/app.mjs +642 -0
  25. package/lib/cli/commands/coordination.mjs +247 -0
  26. package/lib/cli/commands/doctor.mjs +81 -0
  27. package/lib/cli/commands/gc.mjs +852 -0
  28. package/lib/cli/commands/hook.mjs +205 -0
  29. package/lib/cli/commands/install.mjs +294 -0
  30. package/lib/cli/commands/lifecycle.mjs +101 -0
  31. package/lib/cli/commands/lock.mjs +197 -0
  32. package/lib/cli/commands/misc.mjs +61 -0
  33. package/lib/cli/commands/msg.mjs +152 -0
  34. package/lib/cli/commands/peer.mjs +233 -0
  35. package/lib/cli/commands/query.mjs +96 -0
  36. package/lib/cli/commands/run.mjs +463 -0
  37. package/lib/cli/commands/task.mjs +405 -0
  38. package/lib/cli/commands/team.mjs +131 -0
  39. package/lib/cli/commands/tmux.mjs +838 -0
  40. package/lib/cli/commands/up.mjs +135 -0
  41. package/lib/cli-args.mjs +16 -2
  42. package/lib/coordination-state.mjs +112 -51
  43. package/lib/core/coordination/automation.mjs +5 -8
  44. package/lib/core/coordination/clock-safety.mjs +323 -0
  45. package/lib/core/coordination/event-retention.mjs +17 -0
  46. package/lib/core/coordination/gc-plan.mjs +614 -0
  47. package/lib/core/coordination/lease-renewal.mjs +102 -0
  48. package/lib/core/coordination/lock-evidence.mjs +77 -0
  49. package/lib/core/coordination/optimistic-evidence.mjs +26 -0
  50. package/lib/core/coordination/tasks.mjs +156 -36
  51. package/lib/core/peers/evidence-runtime.mjs +312 -0
  52. package/lib/core/peers/evidence.mjs +103 -0
  53. package/lib/core/peers/hook-owner.mjs +32 -0
  54. package/lib/core/peers/liveness.mjs +44 -4
  55. package/lib/core/peers/peer-helpers.mjs +121 -0
  56. package/lib/core/peers/session.mjs +8 -2
  57. package/lib/core/peers/tmux-safety.mjs +290 -0
  58. package/lib/core/sessions/launch.mjs +15 -1
  59. package/lib/db/connection.mjs +104 -0
  60. package/lib/db/events.mjs +34 -0
  61. package/lib/db/migration-backup.mjs +225 -0
  62. package/lib/db/schema.mjs +102 -5
  63. package/lib/db/stores/peers.mjs +34 -0
  64. package/lib/integrations/peers/identity.mjs +62 -16
  65. package/lib/integrations/shims/script.mjs +139 -15
  66. package/lib/process/identity.mjs +192 -0
  67. package/lib/process/pty-lifecycle.mjs +149 -0
  68. package/lib/runtime/buffer-directory-lease.mjs +47 -0
  69. package/lib/runtime/buffer-evidence.mjs +336 -0
  70. package/lib/runtime/buffer-gc-protocol.mjs +167 -0
  71. package/lib/runtime/buffer-gc.mjs +270 -0
  72. package/lib/runtime/client.mjs +61 -8
  73. package/lib/runtime/fatal-shutdown.mjs +54 -0
  74. package/lib/runtime/project-path.mjs +139 -0
  75. package/lib/runtime/projects.mjs +320 -40
  76. package/lib/runtime/state.mjs +232 -29
  77. package/lib/shared/cleanup-error.mjs +54 -0
  78. package/lib/shared/clock-grace.mjs +106 -0
  79. package/lib/shared/errors.mjs +9 -0
  80. package/lib/shared/file-lock.mjs +370 -0
  81. package/lib/shared/json-file.mjs +6 -2
  82. package/lib/shared/redact.mjs +60 -0
  83. package/lib/shared/socket-lock-worker.mjs +201 -0
  84. package/lib/terminal/tmux.mjs +78 -4
  85. package/lib/tmux.mjs +4 -0
  86. package/lib/ui/help.mjs +53 -15
  87. package/lib/web/api-version.mjs +42 -0
  88. package/lib/web/auto-attach.mjs +185 -0
  89. package/lib/web/buffer-gc-runtime.mjs +175 -0
  90. package/lib/web/cookie-auth.mjs +119 -0
  91. package/lib/web/external-sessions.mjs +254 -0
  92. package/lib/web/http-routes.mjs +542 -0
  93. package/lib/web/http.mjs +143 -7
  94. package/lib/web/liveness-reaper.mjs +145 -0
  95. package/lib/web/peer-actions.mjs +132 -23
  96. package/lib/web/project-contexts.mjs +178 -0
  97. package/lib/web/pty-sessions.mjs +203 -0
  98. package/lib/web/runtime-main.mjs +714 -0
  99. package/lib/web/runtime.mjs +91 -36
  100. package/lib/web/session-serialize.mjs +0 -0
  101. package/lib/web/startup.mjs +333 -0
  102. package/lib/web/tls.mjs +365 -0
  103. package/lib/web/tmux-sessions.mjs +1361 -0
  104. package/lib/web/tmux-stream.mjs +166 -0
  105. package/lib/web/ui-template.mjs +152 -20
  106. package/package.json +5 -2
  107. package/scripts/check-free-identifiers.mjs +290 -0
  108. package/scripts/regression.mjs +5646 -262
@@ -0,0 +1,303 @@
1
+ # Web and Runtime API v2 Hardening Implementation Plan
2
+
3
+ > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
4
+
5
+ **Goal:** Ship an explicit Runtime API v2 with bounded browser sessions, session-scoped terminal input, CSP, secret redaction, and symlink-safe project databases.
6
+
7
+ **Architecture:** Central helpers own API negotiation, redaction, token comparison, and canonical path validation. The HTTP and WebSocket entry points enforce these helpers before routing while preserving the accepted plaintext-LAN and arbitrary-root behaviors.
8
+
9
+ **Tech Stack:** Node.js 24 HTTP/HTTPS, `ws`, browser WebSocket/fetch, `node:test`, real filesystem integration tests.
10
+
11
+ ---
12
+
13
+ ## File Map
14
+
15
+ - Create `lib/web/api-version.mjs` and `test/web-api-version.test.mjs`.
16
+ - Create `lib/shared/redact.mjs` and `test/redact.test.mjs`.
17
+ - Create `lib/runtime/project-path.mjs` and `test/project-path.test.mjs`.
18
+ - Modify `lib/web/http.mjs`, `lib/web/runtime.mjs`, `lib/runtime/client.mjs`.
19
+ - Modify `bin/hcc.mjs` HTTP/WS routing, cookie lifecycle, logging, and TLS use.
20
+ - Modify `lib/web/ui-template.mjs` for API headers, WS version query, and CSP nonce.
21
+ - Modify `lib/web/tls.mjs` only where focused tests show a contract gap.
22
+ - Modify `scripts/regression.mjs` for real HTTP/HTTPS/WS tests.
23
+
24
+ ### Task 1: Add Runtime API v2 negotiation
25
+
26
+ **Files:**
27
+ - Create: `lib/web/api-version.mjs`
28
+ - Create: `test/web-api-version.test.mjs`
29
+ - Modify: `lib/runtime/client.mjs:7-35`
30
+ - Modify: `lib/web/runtime.mjs:11-185`
31
+ - Modify: `bin/hcc.mjs:4675-5190`
32
+ - Modify: `lib/web/ui-template.mjs:1-2100`
33
+
34
+ - [ ] **Step 1: Write failing negotiation tests**
35
+
36
+ ```js
37
+ import test from 'node:test';
38
+ import assert from 'node:assert/strict';
39
+ import { API_VERSION, readHttpApiVersion, readWebSocketApiVersion } from '../lib/web/api-version.mjs';
40
+
41
+ test('reads API v2 from the HTTP header', () => {
42
+ assert.equal(API_VERSION, 2);
43
+ assert.deepEqual(readHttpApiVersion({ headers: { 'x-hcc-api-version': '2' } }), { ok: true, version: 2 });
44
+ });
45
+
46
+ test('rejects missing and old protected API versions', () => {
47
+ assert.deepEqual(readHttpApiVersion({ headers: {} }), { ok: false, version: null });
48
+ assert.deepEqual(readHttpApiVersion({ headers: { 'x-hcc-api-version': '1' } }), { ok: false, version: 1 });
49
+ });
50
+
51
+ test('reads browser WebSocket version from query', () => {
52
+ assert.deepEqual(readWebSocketApiVersion(new URL('http://localhost/ws?api_version=2')), { ok: true, version: 2 });
53
+ });
54
+ ```
55
+
56
+ - [ ] **Step 2: Run to verify RED**
57
+
58
+ Run: `node --test test/web-api-version.test.mjs`.
59
+
60
+ Expected: missing-module failure.
61
+
62
+ - [ ] **Step 3: Implement and enforce v2**
63
+
64
+ ```js
65
+ export const API_VERSION = 2;
66
+
67
+ function result(value) {
68
+ const version = Number.parseInt(String(value ?? ''), 10);
69
+ return { ok: version === API_VERSION, version: Number.isFinite(version) ? version : null };
70
+ }
71
+
72
+ export function readHttpApiVersion(req) {
73
+ return result(req?.headers?.['x-hcc-api-version']);
74
+ }
75
+
76
+ export function readWebSocketApiVersion(url) {
77
+ return result(url?.searchParams?.get('api_version'));
78
+ }
79
+ ```
80
+
81
+ Before every protected `/api/*` route, return status 426 and:
82
+
83
+ ```json
84
+ {"ok":false,"error":{"code":"API_VERSION_UNSUPPORTED","message":"Runtime API version 2 is required","supported_version":2}}
85
+ ```
86
+
87
+ Expose `api_version: 2` from runtime metadata. Add `X-HCC-API-Version: 2` in `runtimeRequest` and every browser fetch wrapper. Add `api_version=2` to terminal WebSocket URLs and reject old/missing WS versions before authentication/session lookup.
88
+
89
+ - [ ] **Step 4: Verify GREEN and real compatibility rejection**
90
+
91
+ Run unit tests. Add regression requests with missing/1/2 headers plus WS query 1/2. Public `/`, `/login`, and static assets must remain exempt.
92
+
93
+ Run: `npm run test:regression`.
94
+
95
+ - [ ] **Step 5: Commit**
96
+
97
+ ```bash
98
+ git add lib/web/api-version.mjs test/web-api-version.test.mjs lib/runtime/client.mjs lib/web/runtime.mjs lib/web/ui-template.mjs bin/hcc.mjs scripts/regression.mjs
99
+ git commit -m "feat: require Runtime API v2"
100
+ ```
101
+
102
+ ### Task 2: Enforce administrator and session-action token semantics
103
+
104
+ **Files:**
105
+ - Modify: `lib/web/http.mjs:62-122`
106
+ - Modify: `bin/hcc.mjs:2382-2465,2475-2580,4675-4955,5100-5185`
107
+ - Modify: `lib/web/ui-template.mjs:1570-1760,2050-2110`
108
+ - Modify: `scripts/regression.mjs:1193-1355,1974-2115,2866-3300`
109
+
110
+ - [ ] **Step 1: Add failing privilege-boundary tests**
111
+
112
+ Through real HTTP and WebSocket connections assert:
113
+
114
+ ```text
115
+ admin bearer -> create/stop/inject allowed
116
+ admin cookie + same Origin -> create/stop/inject allowed
117
+ admin cookie + foreign Origin -> mutation denied
118
+ session action token absent from GET /api/sessions
119
+ wrong/missing session action token -> WS input denied
120
+ correct token for another session -> WS input denied
121
+ logout/expiry/eviction -> existing cookie WS closed and future input denied
122
+ ```
123
+
124
+ Also assert tokenless non-loopback startup fails while tokenless loopback still works.
125
+ Because constant-time behavior is not safely distinguishable with a timing test,
126
+ add a static security assertion that extracts `resolveWebActionSession` from
127
+ `bin/hcc.mjs`, requires `tokenMatches(provided, expected)`, and rejects direct
128
+ `provided !== expected` comparison. This is the repeatable validation artifact
129
+ for the timing boundary; the integration cases remain the behavior proof.
130
+
131
+ - [ ] **Step 2: Run to verify RED**
132
+
133
+ Run: `npm run test:regression`.
134
+
135
+ Expected: FAIL on the static constant-time assertion while the current code uses
136
+ direct inequality. Any additional integration failure is fixed through the same
137
+ boundary, but is not required to establish RED.
138
+
139
+ - [ ] **Step 3: Apply one shared token comparator and explicit roles**
140
+
141
+ Use `tokenMatches(provided, expected)` for administrator and session action tokens. Keep `webAuthMode` results `token` and `cookie`, document both as administrator roles, and keep same-origin enforcement on every cookie mutation. Track each cookie-authenticated WS in its server session record and validate the record before inbound and outbound frames.
142
+
143
+ Do not put action tokens in serialized session lists. Deliver them only in the corresponding terminal snapshot frame. Preserve CLI injection through administrator bearer authentication without requiring the session token.
144
+
145
+ - [ ] **Step 4: Verify GREEN and alternate bypasses**
146
+
147
+ Run the regression suite, then repeat token tests with same-length wrong tokens, URL-encoded peer IDs, revoked cookies, and a token from a sibling project session.
148
+
149
+ Expected: legitimate admin and matching-session behavior remains; all bypasses fail.
150
+
151
+ - [ ] **Step 5: Commit**
152
+
153
+ ```bash
154
+ git add lib/web/http.mjs bin/hcc.mjs lib/web/ui-template.mjs scripts/regression.mjs
155
+ git commit -m "fix: enforce Web administrator and session tokens"
156
+ ```
157
+
158
+ ### Task 3: Add structured secret redaction and CSP
159
+
160
+ **Files:**
161
+ - Create: `lib/shared/redact.mjs`
162
+ - Create: `test/redact.test.mjs`
163
+ - Modify: `lib/web/http.mjs:33-59`
164
+ - Modify: `lib/web/ui-template.mjs:1-2150`
165
+ - Modify: `bin/hcc.mjs:2180-2250,2368-2470,5290-5300`
166
+ - Modify: `scripts/regression.mjs:1841-2115,3894-4300`
167
+
168
+ - [ ] **Step 1: Write failing redaction tests**
169
+
170
+ ```js
171
+ import test from 'node:test';
172
+ import assert from 'node:assert/strict';
173
+ import { redactSecrets } from '../lib/shared/redact.mjs';
174
+
175
+ test('redacts CLI, URL, headers, cookies, and structured tokens', () => {
176
+ const secret = 's3cr3t-value';
177
+ const value = {
178
+ command: `hcc web --token ${secret}`,
179
+ url: `http://host/?token=${secret}&root=/tmp/x`,
180
+ headers: { authorization: `Bearer ${secret}`, cookie: `hcc_sid=${secret}` },
181
+ action_token: secret
182
+ };
183
+ const redacted = JSON.stringify(redactSecrets(value));
184
+ assert.equal(redacted.includes(secret), false);
185
+ assert.equal(redacted.includes('[REDACTED]'), true);
186
+ });
187
+ ```
188
+
189
+ Add an HTML test that extracts the CSP nonce, verifies the script tag uses it, and asserts a second un-nonced inline script would violate the generated `script-src` policy.
190
+
191
+ - [ ] **Step 2: Run to verify RED**
192
+
193
+ Run: `node --test test/redact.test.mjs`.
194
+
195
+ Expected: missing-module failure.
196
+
197
+ - [ ] **Step 3: Implement redaction and nonce responses**
198
+
199
+ `redactSecrets(value)` recursively clones arrays/objects, replaces secret-key values, redacts bearer/cookie fragments, replaces `--token` arguments, and deletes URL `token` parameter values. It must handle cycles with a `WeakSet` and replace them with `[Circular]`.
200
+
201
+ Pass all startup/error logging through this helper before stringification. Never log raw request headers or URLs.
202
+
203
+ Generate a random nonce per HTML response and call `webIndexHtml({ nonce })` or `webLoginPage({ nonce })`. Add the nonce to every inline script. Emit CSP:
204
+
205
+ ```text
206
+ default-src 'self'; script-src 'self' 'nonce-<value>'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self' ws: wss:; object-src 'none'; base-uri 'none'; frame-ancestors 'none'
207
+ ```
208
+
209
+ - [ ] **Step 4: Verify GREEN and leak controls**
210
+
211
+ Run unit tests. Start Web with unique test secrets, exercise login/WS/errors, stop it, and scan all generated logs/runtime output. Assert no supplied secret appears. Verify browser HTML contains a matching nonce and no un-nonced script.
212
+
213
+ Run: `npm run test:regression`.
214
+
215
+ - [ ] **Step 5: Commit**
216
+
217
+ ```bash
218
+ git add lib/shared/redact.mjs test/redact.test.mjs lib/web/http.mjs lib/web/ui-template.mjs bin/hcc.mjs scripts/regression.mjs
219
+ git commit -m "fix: redact Web secrets and enforce CSP"
220
+ ```
221
+
222
+ ### Task 4: Canonicalize arbitrary project roots without symlink escape
223
+
224
+ **Files:**
225
+ - Create: `lib/runtime/project-path.mjs`
226
+ - Create: `test/project-path.test.mjs`
227
+ - Modify: `bin/hcc.mjs:2479-2525`
228
+ - Modify: `scripts/regression.mjs:2866-3300,6300-6700`
229
+
230
+ - [ ] **Step 1: Write failing real-filesystem tests**
231
+
232
+ Create temporary roots for: normal project, missing `.hello-cc`, `.hello-cc` symlink to an outside directory, nested DB symlink escape, `/proc` pseudo-file target on Linux, and a normal custom DB below `.hello-cc`.
233
+
234
+ Use the intended API:
235
+
236
+ ```js
237
+ const safe = resolveProjectDatabase({ root, db: path.join(root, '.hello-cc', 'mesh.db'), createStateDir: true });
238
+ assert.equal(safe.db.startsWith(safe.stateDir + path.sep), true);
239
+ assert.throws(() => resolveProjectDatabase({ root: escapedRoot, db: escapedDb, createStateDir: true }), /PROJECT_PATH_FORBIDDEN/);
240
+ ```
241
+
242
+ - [ ] **Step 2: Run to verify RED**
243
+
244
+ Run: `node --test test/project-path.test.mjs`.
245
+
246
+ Expected: missing-module failure.
247
+
248
+ - [ ] **Step 3: Implement canonical containment**
249
+
250
+ Resolve an existing root with `realpathSync`. Create `.hello-cc` with mode `0700` only when requested and absent. Reject it when `lstat` reports a symlink or when its real path is outside root. Resolve the DB parent, reject symlink components escaping the state directory, and require an existing DB target to be a regular file.
251
+
252
+ Replace `assertDbUnderRoot`/lexical-only checks with `resolveProjectDatabase`. Continue allowing every existing canonical root after administrator authentication.
253
+
254
+ - [ ] **Step 4: Verify GREEN and HTTP boundary**
255
+
256
+ Run unit tests, then issue real authenticated Web requests for normal arbitrary roots and each escape fixture. Require normal requests to work and every escape to return `PROJECT_PATH_FORBIDDEN` without creating/opening the outside DB.
257
+
258
+ Run: `npm run test:regression`.
259
+
260
+ - [ ] **Step 5: Commit**
261
+
262
+ ```bash
263
+ git add lib/runtime/project-path.mjs test/project-path.test.mjs bin/hcc.mjs scripts/regression.mjs
264
+ git commit -m "fix: prevent project database symlink escape"
265
+ ```
266
+
267
+ ### Task 5: Finish TLS and trusted-proxy mode coverage
268
+
269
+ **Files:**
270
+ - Track/modify: `lib/web/tls.mjs`
271
+ - Modify: `lib/web/runtime.mjs:80-190`
272
+ - Modify: `lib/runtime/state.mjs:91-123`
273
+ - Modify: `lib/web/http.mjs:76-112`
274
+ - Modify: `scripts/regression.mjs:6440-6600`
275
+
276
+ - [ ] **Step 1: Add failing mode-matrix tests**
277
+
278
+ Cover direct self-signed TLS, expired certificate rejection, SAN mismatch regeneration, key mismatch regeneration, loopback trusted proxy, forwarded headers from non-loopback, and accepted plaintext LAN. Assert `Secure` cookie only for direct TLS or trusted loopback proxy reporting HTTPS.
279
+
280
+ - [ ] **Step 2: Run to verify RED**
281
+
282
+ Run: `npm run test:regression`.
283
+
284
+ Expected: any uncovered certificate generation race or proxy-origin discrepancy fails with the specific mode fixture.
285
+
286
+ - [ ] **Step 3: Make only evidence-required production changes**
287
+
288
+ Keep generation directories, `.creating`/`.published`, atomic `current.json`, SAN/date/key validation, and CA-scoped runtime requests. Ensure proxy host/proto is consumed only when `--trust-proxy` is enabled and socket remote address is loopback. Do not make TLS mandatory or change the accepted default listener.
289
+
290
+ - [ ] **Step 4: Verify GREEN**
291
+
292
+ Run: `npm run test:unit`.
293
+
294
+ Run: `npm run test:regression`.
295
+
296
+ Expected: all three supported modes pass and every spoofed forwarded-header control fails closed.
297
+
298
+ - [ ] **Step 5: Commit**
299
+
300
+ ```bash
301
+ git add lib/web/tls.mjs lib/web/runtime.mjs lib/runtime/state.mjs lib/web/http.mjs scripts/regression.mjs
302
+ git commit -m "test: close Web transport mode coverage"
303
+ ```
@@ -0,0 +1,71 @@
1
+ # Runtime Buffer GC Two-Phase Implementation Plan
2
+
3
+ > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
4
+
5
+ **Goal:** Make manual GC use one server-derived timing snapshot, one unified clock decision, a one-shot runtime file apply, and only then an exact database apply.
6
+
7
+ **Architecture:** The runtime prepares an immutable canonical file plan from a canonical retention period and stores it behind a short-lived root/DB-bound token. The CLI uses the runtime-derived cutoff to capture an exact database plan and merges both plans' would-delete boundaries into one clock observation. Runtime file batches each recheck clock grace under `BEGIN IMMEDIATE`; database rows are finalized only after the runtime reports that every prepared file candidate was processed.
8
+
9
+ **Tech Stack:** Node.js ESM, `node:sqlite`, synchronous filesystem APIs, built-in test runner, HTTP runtime API.
10
+
11
+ ---
12
+
13
+ ### Task 1: Read-only clock prediction
14
+
15
+ **Files:**
16
+ - Modify: `lib/core/coordination/clock-safety.mjs`
17
+ - Test: `test/clock-safety.test.mjs`
18
+
19
+ - [ ] Write a failing test that seeds a watermark, pending gap, and grace row, calls `previewClockSafety`, verifies the same grace decision expected from apply, and compares the complete `meta` table before/after.
20
+ - [ ] Run `node --test test/clock-safety.test.mjs` and verify the missing export fails.
21
+ - [ ] Extract the shared pending-gap decision calculation and implement `previewClockSafety(db, options)` without a transaction or write.
22
+ - [ ] Re-run the focused test and verify preview and observer decisions match while preview leaves `meta` byte-for-byte unchanged.
23
+
24
+ ### Task 2: One-shot runtime plan tokens and bounded apply barriers
25
+
26
+ **Files:**
27
+ - Create: `lib/runtime/buffer-gc-protocol.mjs`
28
+ - Modify: `lib/runtime/buffer-gc.mjs`
29
+ - Create: `test/buffer-gc-protocol.test.mjs`
30
+
31
+ - [ ] Write failing tests for token entropy shape, root/DB binding, replay, expiry, mismatch cleanup, and dry-run plans leaving no token.
32
+ - [ ] Write a failing real SQLite/filesystem test with more than 64 frozen candidates; extend grace from a second connection after the first committed batch and assert exactly the first batch is deleted and the remainder deferred.
33
+ - [ ] Implement `createBufferGcPlanStore` with a 15-second TTL and destructive take semantics.
34
+ - [ ] Implement `applyClockSafeBufferPlan` using batches of 64; each batch opens `BEGIN IMMEDIATE`, runs `observeClockSafetyInTransaction`, checks current grace, and performs only that batch's bounded identity rechecks/unlinks before commit.
35
+ - [ ] Re-run the focused protocol tests.
36
+
37
+ ### Task 3: Authenticated prepare/apply API
38
+
39
+ **Files:**
40
+ - Modify: `bin/hcc.mjs`
41
+ - Modify: `scripts/regression.mjs`
42
+
43
+ - [ ] Add failing authenticated endpoint regressions: legacy or forged `cutoffMs`/`observedAt` bodies return `BAD_REQUEST`; prepare accepts only canonical safe `retentionSec`; apply rejects replay, wrong root/DB, and expired tokens.
44
+ - [ ] Replace the single-call endpoint with `phase: prepare|apply`. Prepare derives `observedAt` and `cutoffMs` from server time, freezes the full actual-directory plan, and returns counts/cutoffs plus a token only for apply. Apply destructively takes the token and runs the bounded barrier.
45
+ - [ ] Add periodic opportunistic expiry cleanup and verify dry-run preparation stores no token.
46
+ - [ ] Run the endpoint regression through the multi-project runtime workflow.
47
+
48
+ ### Task 4: Manual CLI unified decision and apply ordering
49
+
50
+ **Files:**
51
+ - Modify: `bin/hcc.mjs`
52
+ - Modify: `lib/core/coordination/gc-plan.mjs`
53
+ - Modify: `scripts/regression.mjs`
54
+ - Test: `test/gc-plan.test.mjs`
55
+
56
+ - [ ] Add a failing regression with an exact DB history/lock/peer plan plus an actual-directory buffer candidate crossing one pending gap; assert dry-run predicts all would-delete categories deferred without changing `meta`, and apply deletes neither resource.
57
+ - [ ] Add a failing regression where grace is extended after prepare and before apply; assert runtime deletes zero and database finalization never starts.
58
+ - [ ] Add async snapshot cleanup support so the history temp table is always dropped across runtime awaits while preserving primary/cleanup errors.
59
+ - [ ] Split manual GC into prepare, unified preview/observe, runtime/local file apply, and exact DB finalization. Treat runtime unavailability, token failure, incomplete batched apply, subject drift, or clock failure as a barrier that prevents all database deletion.
60
+ - [ ] Ensure result counters report only committed deletes, retain verified-live buffer counts as protected, and report all unprocessed would-delete rows/files as deferred.
61
+ - [ ] Run `node --test test/gc-plan.test.mjs` and the focused manual/runtime regression.
62
+
63
+ ### Task 5: Full verification and amended commit
64
+
65
+ **Files:**
66
+ - Verify all Task 4 files only.
67
+
68
+ - [ ] Run `npm run test:unit` and require zero failures.
69
+ - [ ] Run `npm run test:regression` and require `FULL_REGRESSION_OK`.
70
+ - [ ] Rebuild the Node 24 image and run its full regression.
71
+ - [ ] Run `git diff --cached --check`, selectively stage Task 4 files, and amend `f075976` without staging unrelated worktree changes.
@@ -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.