@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.
- package/CHANGELOG.md +117 -0
- package/README.md +26 -4
- package/README.zh-CN.md +22 -3
- package/bin/hcc.mjs +3 -5622
- 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 +16 -5
- package/docs/commands.zh-CN.md +16 -5
- package/docs/current-state-review-2026-08.md +341 -0
- package/docs/defect-review-2026-07.md +382 -0
- package/docs/guide.md +53 -5
- package/docs/guide.zh-CN.md +46 -3
- 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 +15 -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 +139 -15
- 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 +61 -8
- 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 +232 -29
- 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 +143 -7
- 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 +5646 -262
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
# hello-cc v1 Data and Identity 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:** Introduce schema v7, safe pre-migration backups, process fingerprints, and the breaking v1 provider identity contract.
|
|
6
|
+
|
|
7
|
+
**Architecture:** Put process inspection and migration backup logic in focused modules. Persist opaque process fingerprints on peers and external/tmux session metadata, then route all liveness consumers through one three-state resolver.
|
|
8
|
+
|
|
9
|
+
**Tech Stack:** Node.js 24 ESM, `node:test`, `node:sqlite`, macOS `ps`, Linux `/proc`, tmux.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## File Map
|
|
14
|
+
|
|
15
|
+
- Create `test/process-identity.test.mjs`: real-process and parser behavior.
|
|
16
|
+
- Create `test/schema-v7.test.mjs`: real SQLite backup and migration behavior.
|
|
17
|
+
- Create `lib/process/identity.mjs`: inspect and compare process fingerprints.
|
|
18
|
+
- Create `lib/db/migration-backup.mjs`: consistent `VACUUM INTO` backup.
|
|
19
|
+
- Create `lib/core/peers/evidence.mjs`: `live`/`dead`/`unknown` resolver.
|
|
20
|
+
- Modify `package.json`: unit-test scripts and v1 version.
|
|
21
|
+
- Modify `lib/db/schema.mjs`: schema v7 process columns and backup hook.
|
|
22
|
+
- Modify `bin/hcc.mjs`: migration hook, peer identity persistence, liveness wiring.
|
|
23
|
+
- Modify `lib/core/peers/liveness.mjs`: consume evidence rather than age alone.
|
|
24
|
+
- Modify `lib/core/coordination/tasks.mjs`: use evidence for takeover.
|
|
25
|
+
- Modify `lib/core/peers/session.mjs`: finalize the breaking v1 ID algorithm.
|
|
26
|
+
- Modify `lib/integrations/shims/script.mjs`: keep shim hashing byte-identical.
|
|
27
|
+
- Modify `scripts/regression.mjs`: real CLI migration and session evidence coverage.
|
|
28
|
+
|
|
29
|
+
### Task 1: Add the focused unit-test entry point
|
|
30
|
+
|
|
31
|
+
**Files:**
|
|
32
|
+
- Modify: `package.json:24-32`
|
|
33
|
+
- Create: `test/process-identity.test.mjs`
|
|
34
|
+
|
|
35
|
+
- [ ] **Step 1: Write the failing test**
|
|
36
|
+
|
|
37
|
+
Create the test with the intended module API:
|
|
38
|
+
|
|
39
|
+
```js
|
|
40
|
+
import test from 'node:test';
|
|
41
|
+
import assert from 'node:assert/strict';
|
|
42
|
+
import {
|
|
43
|
+
compareProcessIdentity,
|
|
44
|
+
inspectProcessIdentity,
|
|
45
|
+
parseLinuxStatStartTicks,
|
|
46
|
+
parsePsStartIdentity
|
|
47
|
+
} from '../lib/process/identity.mjs';
|
|
48
|
+
|
|
49
|
+
test('reads a stable identity for the current process', () => {
|
|
50
|
+
const first = inspectProcessIdentity(process.pid);
|
|
51
|
+
const second = inspectProcessIdentity(process.pid);
|
|
52
|
+
assert.equal(first.state, 'live');
|
|
53
|
+
assert.deepEqual(second.identity, first.identity);
|
|
54
|
+
assert.equal(compareProcessIdentity(first.identity, second.identity), 'live');
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test('parses Linux stat when command contains spaces and parentheses', () => {
|
|
58
|
+
const fields = Array.from({ length: 30 }, (_, i) => String(i + 1));
|
|
59
|
+
fields[19] = '987654';
|
|
60
|
+
assert.equal(parseLinuxStatStartTicks(`42 (worker (one)) ${fields.join(' ')}`), '987654');
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test('parses macOS ps start and command identity', () => {
|
|
64
|
+
assert.deepEqual(parsePsStartIdentity('Mon Aug 3 06:10:11 2026\t/usr/bin/node app.mjs\n'), {
|
|
65
|
+
startToken: 'Mon Aug 3 06:10:11 2026',
|
|
66
|
+
command: '/usr/bin/node app.mjs'
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test('rejects a reused PID fingerprint', () => {
|
|
71
|
+
const stored = { pid: 42, startToken: 'boot-a:100', commandHash: 'aaa' };
|
|
72
|
+
const current = { pid: 42, startToken: 'boot-a:200', commandHash: 'aaa' };
|
|
73
|
+
assert.equal(compareProcessIdentity(stored, current), 'dead');
|
|
74
|
+
assert.equal(compareProcessIdentity(null, current), 'unknown');
|
|
75
|
+
});
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
- [ ] **Step 2: Run the test to verify RED**
|
|
79
|
+
|
|
80
|
+
Run: `node --test test/process-identity.test.mjs`
|
|
81
|
+
|
|
82
|
+
Expected: FAIL with `ERR_MODULE_NOT_FOUND` for `lib/process/identity.mjs`.
|
|
83
|
+
|
|
84
|
+
- [ ] **Step 3: Add the unit-test scripts**
|
|
85
|
+
|
|
86
|
+
Set the scripts to:
|
|
87
|
+
|
|
88
|
+
```json
|
|
89
|
+
"pretest": "npm run test:unit",
|
|
90
|
+
"test": "npm run test:regression",
|
|
91
|
+
"test:unit": "node --test test/*.test.mjs",
|
|
92
|
+
"test:regression": "node ./scripts/regression.mjs"
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Do not change the production version in this task.
|
|
96
|
+
|
|
97
|
+
- [ ] **Step 4: Verify the intended failure through npm**
|
|
98
|
+
|
|
99
|
+
Run: `npm run test:unit`
|
|
100
|
+
|
|
101
|
+
Expected: the same missing-module failure, proving the new test entry point runs.
|
|
102
|
+
|
|
103
|
+
- [ ] **Step 5: Commit**
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
git add package.json test/process-identity.test.mjs
|
|
107
|
+
git commit -m "test: add v1 focused test entry point"
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Task 2: Implement cross-platform process fingerprints
|
|
111
|
+
|
|
112
|
+
**Files:**
|
|
113
|
+
- Create: `lib/process/identity.mjs`
|
|
114
|
+
- Test: `test/process-identity.test.mjs`
|
|
115
|
+
|
|
116
|
+
- [ ] **Step 1: Complete the failing boundary tests before implementation**
|
|
117
|
+
|
|
118
|
+
Append tests that inspect PID `2147483647` as dead, treat a partial stored
|
|
119
|
+
fingerprint as unknown, reject a changed command hash, and parse malformed Linux
|
|
120
|
+
and macOS fixture rows as unknown/null.
|
|
121
|
+
|
|
122
|
+
- [ ] **Step 2: Run the complete test to verify RED**
|
|
123
|
+
|
|
124
|
+
Run: `node --test test/process-identity.test.mjs`
|
|
125
|
+
|
|
126
|
+
Expected: the module is still missing and every final identity expectation is
|
|
127
|
+
already present before production code exists.
|
|
128
|
+
|
|
129
|
+
- [ ] **Step 3: Implement the minimal identity module**
|
|
130
|
+
|
|
131
|
+
```js
|
|
132
|
+
import fs from 'node:fs';
|
|
133
|
+
import { createHash } from 'node:crypto';
|
|
134
|
+
import { spawnSync } from 'node:child_process';
|
|
135
|
+
|
|
136
|
+
function commandHash(command) {
|
|
137
|
+
return createHash('sha256').update(String(command || '').trim()).digest('hex');
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export function parseLinuxStatStartTicks(text) {
|
|
141
|
+
const close = String(text).lastIndexOf(')');
|
|
142
|
+
if (close < 0) return null;
|
|
143
|
+
const rest = String(text).slice(close + 2).trim().split(/\s+/);
|
|
144
|
+
return rest[19] || null;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function parsePsStartIdentity(text) {
|
|
148
|
+
const line = String(text).trimEnd();
|
|
149
|
+
const tab = line.indexOf('\t');
|
|
150
|
+
if (tab < 0) return null;
|
|
151
|
+
const startToken = line.slice(0, tab).trim();
|
|
152
|
+
const command = line.slice(tab + 1).trim();
|
|
153
|
+
return startToken && command ? { startToken, command } : null;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function linuxIdentity(pid) {
|
|
157
|
+
const stat = fs.readFileSync(`/proc/${pid}/stat`, 'utf8');
|
|
158
|
+
const startTicks = parseLinuxStatStartTicks(stat);
|
|
159
|
+
const bootId = fs.readFileSync('/proc/sys/kernel/random/boot_id', 'utf8').trim();
|
|
160
|
+
const command = fs.readFileSync(`/proc/${pid}/cmdline`, 'utf8').split('\0').filter(Boolean).join(' ');
|
|
161
|
+
if (!startTicks || !bootId) throw new Error('incomplete Linux identity');
|
|
162
|
+
return { pid, startToken: `${bootId}:${startTicks}`, commandHash: commandHash(command) };
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function psIdentity(pid) {
|
|
166
|
+
const boot = spawnSync('sysctl', ['-n', 'kern.boottime'], { encoding: 'utf8' });
|
|
167
|
+
const started = spawnSync('ps', ['-p', String(pid), '-o', 'lstart='], { encoding: 'utf8' });
|
|
168
|
+
const command = spawnSync('ps', ['-p', String(pid), '-o', 'command='], { encoding: 'utf8' });
|
|
169
|
+
if (started.status !== 0 || command.status !== 0 || boot.status !== 0) return null;
|
|
170
|
+
const parsed = parsePsStartIdentity(`${started.stdout.trim()}\t${command.stdout.trim()}\n`);
|
|
171
|
+
if (!parsed) throw new Error('incomplete ps identity');
|
|
172
|
+
return {
|
|
173
|
+
pid,
|
|
174
|
+
startToken: `${String(boot.stdout || '').trim()}:${parsed.startToken}`,
|
|
175
|
+
commandHash: commandHash(parsed.command)
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export function inspectProcessIdentity(value) {
|
|
180
|
+
const pid = Number(value);
|
|
181
|
+
if (!Number.isInteger(pid) || pid <= 0) return { state: 'unknown', identity: null };
|
|
182
|
+
try {
|
|
183
|
+
process.kill(pid, 0);
|
|
184
|
+
} catch (err) {
|
|
185
|
+
if (err?.code === 'ESRCH') return { state: 'dead', identity: null };
|
|
186
|
+
if (err?.code !== 'EPERM') return { state: 'unknown', identity: null };
|
|
187
|
+
}
|
|
188
|
+
try {
|
|
189
|
+
const identity = process.platform === 'linux' ? linuxIdentity(pid) : psIdentity(pid);
|
|
190
|
+
return identity ? { state: 'live', identity } : { state: 'dead', identity: null };
|
|
191
|
+
} catch {
|
|
192
|
+
return { state: 'unknown', identity: null };
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function isCompleteIdentity(value) {
|
|
197
|
+
return Number.isInteger(Number(value?.pid)) && Number(value.pid) > 0 &&
|
|
198
|
+
typeof value?.startToken === 'string' && value.startToken.length > 0 &&
|
|
199
|
+
typeof value?.commandHash === 'string' && /^[a-f0-9]{64}$/.test(value.commandHash);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export function compareProcessIdentity(stored, current) {
|
|
203
|
+
if (!isCompleteIdentity(stored) || !isCompleteIdentity(current)) return 'unknown';
|
|
204
|
+
return Number(stored.pid) === Number(current.pid) &&
|
|
205
|
+
stored.startToken === current.startToken &&
|
|
206
|
+
stored.commandHash === current.commandHash ? 'live' : 'dead';
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
- [ ] **Step 4: Run focused tests to verify GREEN**
|
|
211
|
+
|
|
212
|
+
Run: `node --test test/process-identity.test.mjs`
|
|
213
|
+
|
|
214
|
+
Expected: all boundary tests PASS on macOS and Linux. If the macOS parser fixture exposes a spacing mismatch, fix the parser, not the assertions.
|
|
215
|
+
|
|
216
|
+
- [ ] **Step 5: Commit**
|
|
217
|
+
|
|
218
|
+
```bash
|
|
219
|
+
git add lib/process/identity.mjs test/process-identity.test.mjs
|
|
220
|
+
git commit -m "feat: add process identity fingerprints"
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### Task 3: Add schema v7 and consistent migration backups
|
|
224
|
+
|
|
225
|
+
**Files:**
|
|
226
|
+
- Create: `lib/db/migration-backup.mjs`
|
|
227
|
+
- Create: `test/schema-v7.test.mjs`
|
|
228
|
+
- Modify: `lib/db/schema.mjs:3,76-88,169-179,318-355`
|
|
229
|
+
- Modify: `bin/hcc.mjs:413-466`
|
|
230
|
+
|
|
231
|
+
- [ ] **Step 1: Write real SQLite failing tests**
|
|
232
|
+
|
|
233
|
+
Create temporary v5 and v6 databases using `DatabaseSync`. Assert that `ensureMigrationBackup(db, dbPath, 5, 7)` returns a new path, the backup passes `quick_check`, contains the original row, and a second call returns a different non-overwriting path. Add a failure case with an unwritable destination and assert the source `schema_version` remains unchanged.
|
|
234
|
+
|
|
235
|
+
Use this call shape:
|
|
236
|
+
|
|
237
|
+
```js
|
|
238
|
+
const backup = ensureMigrationBackup(db, dbPath, 5, 7, { timestamp: () => '20260803T120000000Z' });
|
|
239
|
+
assert.match(backup, /mesh\.db\.pre-v5-to-v7\.20260803T120000000Z\.[a-f0-9]{8}\.bak$/);
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
- [ ] **Step 2: Run to verify RED**
|
|
243
|
+
|
|
244
|
+
Run: `node --test test/schema-v7.test.mjs`
|
|
245
|
+
|
|
246
|
+
Expected: FAIL because `lib/db/migration-backup.mjs` does not exist.
|
|
247
|
+
|
|
248
|
+
- [ ] **Step 3: Implement backup and migration hook**
|
|
249
|
+
|
|
250
|
+
Implement `ensureMigrationBackup` with a SQL-string quoting helper, `VACUUM INTO`, an exclusive new filename, read-only `quick_check`, and cleanup of an invalid backup. Export `initSchema(db, { beforeMigration, ... })`; call `beforeMigration({ fromVersion, toVersion })` before the migration transaction when `fromVersion < 7`.
|
|
251
|
+
|
|
252
|
+
Set:
|
|
253
|
+
|
|
254
|
+
```js
|
|
255
|
+
export const DB_SCHEMA_VERSION = 7;
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
Add to `peers` and migration 7:
|
|
259
|
+
|
|
260
|
+
```sql
|
|
261
|
+
pid_start_token TEXT,
|
|
262
|
+
pid_command_hash TEXT
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
In every production connection path, pass:
|
|
266
|
+
|
|
267
|
+
```js
|
|
268
|
+
beforeMigration: ({ fromVersion, toVersion }) =>
|
|
269
|
+
ensureMigrationBackup(db, dbPath, fromVersion, toVersion)
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
The sibling-project migration catch remains isolated and logs the backup failure.
|
|
273
|
+
|
|
274
|
+
- [ ] **Step 4: Verify GREEN and migration integration**
|
|
275
|
+
|
|
276
|
+
Run: `node --test test/schema-v7.test.mjs`
|
|
277
|
+
|
|
278
|
+
Then add v7 expectations to `createLegacySchemaDb`, `assertLegacySchemaMigration`, `assertRegisteredProjectDbMigration`, and the future-schema rejection workflow in `scripts/regression.mjs`.
|
|
279
|
+
|
|
280
|
+
Run: `npm run test:regression`
|
|
281
|
+
|
|
282
|
+
Expected: `FULL_REGRESSION_OK` with v5 and v6 fixtures backed up before v7.
|
|
283
|
+
|
|
284
|
+
- [ ] **Step 5: Commit**
|
|
285
|
+
|
|
286
|
+
```bash
|
|
287
|
+
git add lib/db/migration-backup.mjs lib/db/schema.mjs bin/hcc.mjs test/schema-v7.test.mjs scripts/regression.mjs
|
|
288
|
+
git commit -m "feat: back up databases before schema v7"
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
### Task 4: Persist identities and unify liveness evidence
|
|
292
|
+
|
|
293
|
+
**Files:**
|
|
294
|
+
- Create: `lib/core/peers/evidence.mjs`
|
|
295
|
+
- Create: `test/peer-evidence.test.mjs`
|
|
296
|
+
- Modify: `bin/hcc.mjs:524-575,2610-2720,4369-4515,5620-5735`
|
|
297
|
+
- Modify: `lib/core/peers/liveness.mjs:1-65`
|
|
298
|
+
- Modify: `lib/core/coordination/tasks.mjs:65-90`
|
|
299
|
+
- Modify: `scripts/regression.mjs:2616-2820,6834-6918`
|
|
300
|
+
|
|
301
|
+
- [ ] **Step 1: Write failing evidence tests**
|
|
302
|
+
|
|
303
|
+
Use complete peer/session fixtures and assert:
|
|
304
|
+
|
|
305
|
+
```js
|
|
306
|
+
assert.equal(resolvePeerEvidence({ peer: { status: 'exited' } }).state, 'dead');
|
|
307
|
+
assert.equal(resolvePeerEvidence({ peer: detachedPeer, process: matchingProcess, tmuxManaged: false }).state, 'live');
|
|
308
|
+
assert.equal(resolvePeerEvidence({ peer: detachedPeer, tmux: matchingPane, tmuxManaged: true }).state, 'live');
|
|
309
|
+
assert.equal(resolvePeerEvidence({ peer: workingPeer, process: reusedPid, tmuxManaged: false }).state, 'dead');
|
|
310
|
+
assert.equal(resolvePeerEvidence({ peer: legacyPeer, process: { state: 'live', identity: null }, tmuxManaged: false }).state, 'unknown');
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
Run: `node --test test/peer-evidence.test.mjs`
|
|
314
|
+
|
|
315
|
+
Expected: FAIL because the resolver module is missing.
|
|
316
|
+
|
|
317
|
+
- [ ] **Step 2: Implement the resolver**
|
|
318
|
+
|
|
319
|
+
```js
|
|
320
|
+
export function resolvePeerEvidence({ peer, process: processEvidence, tmux = null, tmuxManaged = false } = {}) {
|
|
321
|
+
if (peer?.status === 'exited') return { state: 'dead', reason: 'explicit-exit' };
|
|
322
|
+
if (tmux?.state === 'live') return { state: 'live', reason: 'tmux-identity' };
|
|
323
|
+
if (processEvidence?.state === 'live' && processEvidence.identityMatched) {
|
|
324
|
+
return { state: 'live', reason: 'process-identity' };
|
|
325
|
+
}
|
|
326
|
+
const processDead = processEvidence?.state === 'dead' ||
|
|
327
|
+
(processEvidence?.state === 'live' && processEvidence.identityMatched === false);
|
|
328
|
+
if (tmux?.state === 'unknown' || processEvidence?.state === 'unknown') {
|
|
329
|
+
return { state: 'unknown', reason: 'incomplete-evidence' };
|
|
330
|
+
}
|
|
331
|
+
if (tmuxManaged && tmux?.state === 'dead' && processDead) {
|
|
332
|
+
return { state: 'dead', reason: 'process-and-tmux-dead' };
|
|
333
|
+
}
|
|
334
|
+
if (!tmuxManaged && processDead) return { state: 'dead', reason: 'process-dead' };
|
|
335
|
+
return { state: 'unknown', reason: 'insufficient-evidence' };
|
|
336
|
+
}
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
- [ ] **Step 3: Persist and consume fingerprints**
|
|
340
|
+
|
|
341
|
+
When registering/upserting a peer, inspect its PID and persist `pid_start_token` and `pid_command_hash` only for verified identity. Store full wrapper/child identity objects in external `.meta`. Store pane identity when adopting or starting tmux.
|
|
342
|
+
|
|
343
|
+
Replace age-only decisions in task annotations, takeover, reaper, lock recovery, and GC peer selection with the shared evidence result. `detached` must no longer be included in a hard-coded dead-status set.
|
|
344
|
+
|
|
345
|
+
- [ ] **Step 4: Verify real behavior**
|
|
346
|
+
|
|
347
|
+
Run: `node --test test/peer-evidence.test.mjs`
|
|
348
|
+
|
|
349
|
+
Add integration fixtures for a detached live tmux pane, live non-tmux child, reused PID fingerprint, and legacy unknown metadata. Run: `npm run test:regression`.
|
|
350
|
+
|
|
351
|
+
Expected: live evidence blocks takeover; reused/dead evidence permits it; legacy evidence is unknown.
|
|
352
|
+
|
|
353
|
+
- [ ] **Step 5: Commit**
|
|
354
|
+
|
|
355
|
+
```bash
|
|
356
|
+
git add lib/core/peers/evidence.mjs test/peer-evidence.test.mjs lib/core/peers/liveness.mjs lib/core/coordination/tasks.mjs bin/hcc.mjs scripts/regression.mjs
|
|
357
|
+
git commit -m "fix: resolve ownership from process evidence"
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
### Task 5: Prove and freeze the v1 provider identity break
|
|
361
|
+
|
|
362
|
+
**Files:**
|
|
363
|
+
- Modify: `lib/core/peers/session.mjs:1-12`
|
|
364
|
+
- Modify: `lib/integrations/shims/script.mjs:60-149,335-415`
|
|
365
|
+
- Modify: `scripts/regression.mjs:1385-1584,5660-5745`
|
|
366
|
+
|
|
367
|
+
- [ ] **Step 1: Add failing upgrade-contract assertions**
|
|
368
|
+
|
|
369
|
+
Assert exact new IDs for a UUID and two same-prefix names, assert they differ from the remote v0.1.9 prefix algorithm, and assert every generated shim returns the JavaScript ID. Add an invalid hash-command fixture and assert transparent provider passthrough.
|
|
370
|
+
|
|
371
|
+
- [ ] **Step 2: Run the identity contract**
|
|
372
|
+
|
|
373
|
+
Run: `npm run test:regression`
|
|
374
|
+
|
|
375
|
+
Expected: if the current worktree already satisfies every final identity
|
|
376
|
+
assertion, record this item as `no_change` and do not modify production code. If
|
|
377
|
+
any assertion fails, the failure becomes the RED proof for Step 3.
|
|
378
|
+
|
|
379
|
+
- [ ] **Step 3: Correct only a demonstrated algorithm mismatch**
|
|
380
|
+
|
|
381
|
+
When Step 2 is RED, keep `providerSessionPeerId(kind, providerId)` as full-input
|
|
382
|
+
SHA-1 first-eight hex and make the failing shim path byte-identical. Keep strict
|
|
383
|
+
40-hex validation and passthrough on failure. When Step 2 is already green, skip
|
|
384
|
+
this production step.
|
|
385
|
+
|
|
386
|
+
- [ ] **Step 4: Verify GREEN**
|
|
387
|
+
|
|
388
|
+
Run: `npm run test:unit`.
|
|
389
|
+
|
|
390
|
+
Run: `npm run test:regression`.
|
|
391
|
+
|
|
392
|
+
Expected: all identity fixtures PASS and no old-name scan regression.
|
|
393
|
+
|
|
394
|
+
- [ ] **Step 5: Commit**
|
|
395
|
+
|
|
396
|
+
```bash
|
|
397
|
+
git add scripts/regression.mjs
|
|
398
|
+
# Add the two production files only when Step 2 proved a mismatch.
|
|
399
|
+
git commit -m "feat: finalize v1 provider identities"
|
|
400
|
+
```
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
# hello-cc v1 Release and Verification 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:** Convert the completed hardening into a coherent 1.0.0 release with controlled fatal shutdown, complete packaging, accurate documentation, stable tests, and fresh cross-platform evidence.
|
|
6
|
+
|
|
7
|
+
**Architecture:** A focused fatal-shutdown helper owns process-level failure behavior. Release metadata and documentation declare breaking changes and accepted risks. Verification audits the entire local delta against `origin/master`, not only the last patch.
|
|
8
|
+
|
|
9
|
+
**Tech Stack:** Node.js 24 ESM, npm packaging, Docker/BuildKit, tmux, SQLite, HTTP/HTTPS/WebSocket regression suite.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## File Map
|
|
14
|
+
|
|
15
|
+
- Create `lib/runtime/fatal-shutdown.mjs` and `test/fatal-shutdown.test.mjs`.
|
|
16
|
+
- Modify `bin/hcc.mjs` fatal handlers and shutdown orchestration.
|
|
17
|
+
- Modify `package.json`, `package-lock.json`, `CHANGELOG.md`.
|
|
18
|
+
- Modify root and `docs/` English/Chinese README, commands, and guide files.
|
|
19
|
+
- Modify `docs/defect-review-2026-07.md` status evidence.
|
|
20
|
+
- Track/modify `.dockerignore`, `Dockerfile`, `lib/shared/clock-grace.mjs`, `lib/web/tls.mjs`.
|
|
21
|
+
- Modify `scripts/regression.mjs` for deterministic TTL evidence and package audits.
|
|
22
|
+
|
|
23
|
+
### Task 1: Replace unknown-error survival with idempotent fatal shutdown
|
|
24
|
+
|
|
25
|
+
**Files:**
|
|
26
|
+
- Create: `lib/runtime/fatal-shutdown.mjs`
|
|
27
|
+
- Create: `test/fatal-shutdown.test.mjs`
|
|
28
|
+
- Modify: `bin/hcc.mjs:5190-5335`
|
|
29
|
+
|
|
30
|
+
- [ ] **Step 1: Write failing fatal-state tests**
|
|
31
|
+
|
|
32
|
+
Use a real `EventEmitter` as the process signal source and real promises for asynchronous cleanup. Do not mock cleanup behavior; provide complete callbacks that append observable events.
|
|
33
|
+
|
|
34
|
+
```js
|
|
35
|
+
const events = [];
|
|
36
|
+
const controller = createFatalShutdownController({
|
|
37
|
+
cleanup: async (reason) => { events.push(`cleanup:${reason}`); },
|
|
38
|
+
exit: (code) => { events.push(`exit:${code}`); },
|
|
39
|
+
forceExit: (code) => { events.push(`force:${code}`); },
|
|
40
|
+
log: (value) => { events.push(`log:${value.code}`); }
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
await controller.fatal(new Error('first'));
|
|
44
|
+
await controller.fatal(new Error('second'));
|
|
45
|
+
assert.deepEqual(events, ['log:FATAL_RUNTIME_ERROR', 'cleanup:first', 'exit:1', 'force:1']);
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Also assert cleanup failure still exits nonzero and its log is redacted.
|
|
49
|
+
|
|
50
|
+
- [ ] **Step 2: Run to verify RED**
|
|
51
|
+
|
|
52
|
+
Run: `node --test test/fatal-shutdown.test.mjs`.
|
|
53
|
+
|
|
54
|
+
Expected: missing-module failure.
|
|
55
|
+
|
|
56
|
+
- [ ] **Step 3: Implement the controller and wire Web shutdown**
|
|
57
|
+
|
|
58
|
+
```js
|
|
59
|
+
export function createFatalShutdownController({ cleanup, exit, forceExit, log }) {
|
|
60
|
+
let state = 'idle';
|
|
61
|
+
return {
|
|
62
|
+
async fatal(error) {
|
|
63
|
+
if (state !== 'idle') {
|
|
64
|
+
forceExit(1);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
state = 'stopping';
|
|
68
|
+
log({ code: 'FATAL_RUNTIME_ERROR', error });
|
|
69
|
+
try { await cleanup(error?.message || 'fatal runtime error'); }
|
|
70
|
+
catch (cleanupError) { log({ code: 'FATAL_CLEANUP_ERROR', error: cleanupError }); }
|
|
71
|
+
state = 'stopped';
|
|
72
|
+
exit(1);
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
In `cmdWeb`, create one controller after all owned resources exist. `uncaughtException` and `unhandledRejection` call it. Cleanup clears timers/watchers, closes WebSockets/server, detaches or exits sessions by existing semantics, closes DB resources, and clears only runtime pointers matching the current process identity. Remove the log-and-continue handlers.
|
|
79
|
+
|
|
80
|
+
- [ ] **Step 4: Verify GREEN at unit and child-process boundaries**
|
|
81
|
+
|
|
82
|
+
Run unit tests. Add a child-process fixture that imports the helper, opens a temporary server/resource, triggers an unhandled rejection, and asserts cleanup marker, closed listener, and exit status 1.
|
|
83
|
+
|
|
84
|
+
Run: `npm run test:unit`.
|
|
85
|
+
|
|
86
|
+
Run: `npm run test:regression`.
|
|
87
|
+
|
|
88
|
+
- [ ] **Step 5: Commit**
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
git add lib/runtime/fatal-shutdown.mjs test/fatal-shutdown.test.mjs bin/hcc.mjs scripts/regression.mjs
|
|
92
|
+
git commit -m "fix: terminate cleanly after fatal runtime errors"
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Task 2: Publish the breaking 1.0.0 contract in metadata and docs
|
|
96
|
+
|
|
97
|
+
**Files:**
|
|
98
|
+
- Modify: `package.json:3,24-32`
|
|
99
|
+
- Modify: `package-lock.json`
|
|
100
|
+
- Modify: `CHANGELOG.md`
|
|
101
|
+
- Modify: `README.md`, `README.zh-CN.md`
|
|
102
|
+
- Modify: `docs/README.md`, `docs/README.zh-CN.md`
|
|
103
|
+
- Modify: `docs/commands.md`, `docs/commands.zh-CN.md`
|
|
104
|
+
- Modify: `docs/guide.md`, `docs/guide.zh-CN.md`
|
|
105
|
+
- Modify: `docs/defect-review-2026-07.md`
|
|
106
|
+
- Modify: `lib/ui/help.mjs`
|
|
107
|
+
|
|
108
|
+
- [ ] **Step 1: Add failing release-document assertions**
|
|
109
|
+
|
|
110
|
+
Extend `syntaxAndHelp` and release checks to require all of these concepts in
|
|
111
|
+
`README.md`, `README.zh-CN.md`, `docs/commands.md`,
|
|
112
|
+
`docs/commands.zh-CN.md`, `docs/guide.md`, `docs/guide.zh-CN.md`, the changelog,
|
|
113
|
+
and CLI help:
|
|
114
|
+
|
|
115
|
+
```text
|
|
116
|
+
1.0.0
|
|
117
|
+
schema v7 / no downgrade
|
|
118
|
+
pre-migration backup
|
|
119
|
+
Runtime API v2
|
|
120
|
+
provider peer IDs changed
|
|
121
|
+
gc --history
|
|
122
|
+
process evidence / unknown-only grace
|
|
123
|
+
--tls and --trust-proxy
|
|
124
|
+
plaintext LAN accepted risk
|
|
125
|
+
arbitrary authenticated project root accepted risk
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Assert the defect report uses the actual current branch dynamically or avoids naming a branch, and that fixed/partial/deferred counts add up to the listed findings.
|
|
129
|
+
|
|
130
|
+
- [ ] **Step 2: Run to verify RED**
|
|
131
|
+
|
|
132
|
+
Run: `npm run test:regression`.
|
|
133
|
+
|
|
134
|
+
Run: `npm run release:check`.
|
|
135
|
+
|
|
136
|
+
Expected: FAIL on version and missing release/help documentation.
|
|
137
|
+
|
|
138
|
+
- [ ] **Step 3: Update release metadata and documentation**
|
|
139
|
+
|
|
140
|
+
Set `package.json` version to `1.0.0`, then run:
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
npm install --package-lock-only --ignore-scripts --no-audit --no-fund
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Add one coherent 1.0.0 changelog section with Breaking, Security, Reliability, GC, and Upgrade headings. Document exact backup naming and recovery inspection, no downgrade, old/new peer ID examples, API v2 negotiation, safe GC flags, supported transport modes, and the two accepted risks.
|
|
147
|
+
|
|
148
|
+
Update help text without claiming plaintext confidentiality or project isolation. Correct defect statuses from current tests and code evidence.
|
|
149
|
+
|
|
150
|
+
- [ ] **Step 4: Verify GREEN**
|
|
151
|
+
|
|
152
|
+
Run: `npm run test:regression`.
|
|
153
|
+
|
|
154
|
+
Run: `npm run release:check`.
|
|
155
|
+
|
|
156
|
+
Run: `node ./bin/hcc.mjs --help`.
|
|
157
|
+
|
|
158
|
+
Expected: all release assertions PASS and version/help output is 1.0.0.
|
|
159
|
+
|
|
160
|
+
- [ ] **Step 5: Commit**
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
git add package.json package-lock.json CHANGELOG.md README.md README.zh-CN.md docs/README.md docs/README.zh-CN.md docs/commands.md docs/commands.zh-CN.md docs/guide.md docs/guide.zh-CN.md docs/defect-review-2026-07.md lib/ui/help.mjs scripts/regression.mjs
|
|
164
|
+
git commit -m "docs: publish hello-cc 1.0.0 contract"
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### Task 3: Make packaging complete and TTL regression deterministic
|
|
168
|
+
|
|
169
|
+
**Files:**
|
|
170
|
+
- Track/modify: `.dockerignore`
|
|
171
|
+
- Track/modify: `Dockerfile`
|
|
172
|
+
- Track: `lib/shared/clock-grace.mjs`
|
|
173
|
+
- Track: `lib/web/tls.mjs`
|
|
174
|
+
- Modify: `scripts/regression.mjs:164-185,2730-2820,7072-7115`
|
|
175
|
+
- Modify: `package.json:10-22`
|
|
176
|
+
|
|
177
|
+
- [ ] **Step 1: Add failing package-completeness and TTL tests**
|
|
178
|
+
|
|
179
|
+
Add a package audit that runs `npm pack --dry-run --json`, parses the file list, resolves every relative import reachable from `bin/hcc.mjs`, and asserts every production dependency is tracked and included in the package.
|
|
180
|
+
|
|
181
|
+
Change hook renewal event payload assertions to require a `renewed_at` timestamp. Seed a fixture where parent `before` is one second greater than child `renewed_at` and assert expiration equals `renewed_at + renewalSec`, not a cross-process before/after window.
|
|
182
|
+
|
|
183
|
+
- [ ] **Step 2: Run to verify RED**
|
|
184
|
+
|
|
185
|
+
Run: `npm run test:regression`.
|
|
186
|
+
|
|
187
|
+
Expected: FAIL because renewal events lack `renewed_at` and current package audit sees untracked production modules.
|
|
188
|
+
|
|
189
|
+
- [ ] **Step 3: Record operation time and complete packaging**
|
|
190
|
+
|
|
191
|
+
Include `renewed_at: hookNow` in `lock.renewed_by_hook` payload and assert exact TTL from that value. Keep created time and `ttl_sec` invariants.
|
|
192
|
+
|
|
193
|
+
Track all imported production modules. Ensure package `files` includes every `lib/` module. Change Docker dependency install to:
|
|
194
|
+
|
|
195
|
+
```dockerfile
|
|
196
|
+
RUN npm ci --no-audit --no-fund
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
At test startup print `node --version` and `tmux -V`. Keep `.git`, runtime state, tokens, DBs, WAL files, logs, and local buffers out of the build context; do not exclude production source.
|
|
200
|
+
|
|
201
|
+
- [ ] **Step 4: Verify GREEN and repeatability**
|
|
202
|
+
|
|
203
|
+
Run: `npm run test:regression` twice locally.
|
|
204
|
+
|
|
205
|
+
Run: `npm pack --dry-run --json` and inspect the parsed audit result.
|
|
206
|
+
|
|
207
|
+
Build and run the container twice at this task boundary. Both runs must reach `FULL_REGRESSION_OK`.
|
|
208
|
+
|
|
209
|
+
- [ ] **Step 5: Commit**
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
git add .dockerignore Dockerfile lib/shared/clock-grace.mjs lib/web/tls.mjs scripts/regression.mjs package.json
|
|
213
|
+
git commit -m "test: make v1 package and regression deterministic"
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### Task 4: Execute the final ordered verification gates
|
|
217
|
+
|
|
218
|
+
**Files:**
|
|
219
|
+
- Review: every path in `git diff --name-only origin/master`
|
|
220
|
+
- Modify only when a failed gate identifies a defect, using a new RED-GREEN cycle.
|
|
221
|
+
|
|
222
|
+
- [ ] **Step 1: Audit final scope and production imports**
|
|
223
|
+
|
|
224
|
+
Run:
|
|
225
|
+
|
|
226
|
+
```bash
|
|
227
|
+
git status --short
|
|
228
|
+
git diff --check origin/master
|
|
229
|
+
git diff --stat origin/master
|
|
230
|
+
git diff --name-status origin/master
|
|
231
|
+
npm pack --dry-run --json
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
Expected: no untracked production dependency; no whitespace errors; package audit lists every reachable module.
|
|
235
|
+
|
|
236
|
+
- [ ] **Step 2: Run syntax, unit, and focused security checks**
|
|
237
|
+
|
|
238
|
+
Run:
|
|
239
|
+
|
|
240
|
+
```bash
|
|
241
|
+
node --check bin/hcc.mjs
|
|
242
|
+
node --check scripts/regression.mjs
|
|
243
|
+
npm run test:unit
|
|
244
|
+
npm run smoke
|
|
245
|
+
npm run release:check
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
Expected: exit 0 for every command with zero test failures.
|
|
249
|
+
|
|
250
|
+
- [ ] **Step 3: Re-run original vulnerable paths and bypass controls**
|
|
251
|
+
|
|
252
|
+
Run focused selectors or fixtures for negative GC, live-buffer manual GC, CLI-only clock gap, concurrent registry writers, PID reuse, API v1/missing version, cross-session tokens, symlink escape, secret log scanning, and fatal shutdown.
|
|
253
|
+
|
|
254
|
+
Expected: original unsafe behavior no longer reproduces; legitimate live/dead/admin/arbitrary-root/plaintext-LAN controls remain intact.
|
|
255
|
+
|
|
256
|
+
- [ ] **Step 4: Run complete local regression**
|
|
257
|
+
|
|
258
|
+
Run: `npm test`.
|
|
259
|
+
|
|
260
|
+
Expected: unit tests PASS and regression ends with `FULL_REGRESSION_OK`.
|
|
261
|
+
|
|
262
|
+
- [ ] **Step 5: Build the exact current Node 24/Linux image**
|
|
263
|
+
|
|
264
|
+
Run:
|
|
265
|
+
|
|
266
|
+
```bash
|
|
267
|
+
docker build --no-cache -t hello-cc-v1-verify:node24 .
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
Expected: successful build using `npm ci`, with all current source copied.
|
|
271
|
+
|
|
272
|
+
- [ ] **Step 6: Run three consecutive container regressions**
|
|
273
|
+
|
|
274
|
+
Run `docker run --rm hello-cc-v1-verify:node24` three separate times. Do not combine them in a shell loop; preserve each output independently.
|
|
275
|
+
|
|
276
|
+
Expected for each run: `FULL_REGRESSION_OK`, no TTL clock-window failure, no leaked tmux session, exit 0.
|
|
277
|
+
|
|
278
|
+
- [ ] **Step 7: Review the complete delta and requirements**
|
|
279
|
+
|
|
280
|
+
Read all four design specs and map every acceptance criterion to a passing command or test. Review `git diff origin/master` for alternate unsafe branches, duplicated policy logic, accidental user-change loss, secrets, stale documentation, and unrelated churn.
|
|
281
|
+
|
|
282
|
+
- [ ] **Step 8: Commit only evidence-required final corrections**
|
|
283
|
+
|
|
284
|
+
If every gate passed without a correction, do not create an empty commit. If a gate required a fix, use a focused commit containing its failing test and minimal production correction.
|