instar 1.3.212 → 1.3.214
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/dist/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +2 -0
- package/dist/commands/server.js.map +1 -1
- package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
- package/dist/core/PostUpdateMigrator.js +15 -0
- package/dist/core/PostUpdateMigrator.js.map +1 -1
- package/dist/core/SessionManager.d.ts +24 -0
- package/dist/core/SessionManager.d.ts.map +1 -1
- package/dist/core/SessionManager.js +79 -0
- package/dist/core/SessionManager.js.map +1 -1
- package/dist/monitoring/StaleSessionBackstop.d.ts +15 -0
- package/dist/monitoring/StaleSessionBackstop.d.ts.map +1 -1
- package/dist/monitoring/StaleSessionBackstop.js +16 -4
- package/dist/monitoring/StaleSessionBackstop.js.map +1 -1
- package/dist/scaffold/templates.d.ts.map +1 -1
- package/dist/scaffold/templates.js +1 -0
- package/dist/scaffold/templates.js.map +1 -1
- package/dist/server/SecretDrop.d.ts +19 -6
- package/dist/server/SecretDrop.d.ts.map +1 -1
- package/dist/server/SecretDrop.js +79 -30
- package/dist/server/SecretDrop.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +21 -21
- package/src/scaffold/templates.ts +1 -0
- package/src/templates/scripts/secret-drop-retrieve.mjs +65 -2
- package/upgrades/1.3.213.md +50 -0
- package/upgrades/1.3.214.md +65 -0
- package/upgrades/side-effects/codex-wedged-job-cpu-detection.md +118 -0
- package/upgrades/side-effects/secret-drop-sliding-window.md +83 -0
|
@@ -25,6 +25,16 @@
|
|
|
25
25
|
* → opt into one-shot semantics (the submission is removed after read).
|
|
26
26
|
* Default is peek (non-destructive) per the 2026-05-20 hardening.
|
|
27
27
|
*
|
|
28
|
+
* node .instar/scripts/secret-drop-retrieve.mjs <token> <field> --run -- <cmd...>
|
|
29
|
+
* → ATOMIC use-and-consume. Retrieves the field value, runs <cmd> with the
|
|
30
|
+
* value piped to its stdin, and consumes the submission ONLY if <cmd> exits
|
|
31
|
+
* 0. On any non-zero exit the secret is left intact for retry. This makes it
|
|
32
|
+
* structurally impossible to drop a secret on a failed handoff — the
|
|
33
|
+
* 2026-06-02 failure mode where a standalone `--consume` was fired after a
|
|
34
|
+
* step that had not actually succeeded. Prefer this over a separate
|
|
35
|
+
* retrieve-then-consume whenever the value feeds a single command.
|
|
36
|
+
* Example: `... <token> github_token --run -- gh auth login --with-token`
|
|
37
|
+
*
|
|
28
38
|
* Config:
|
|
29
39
|
* Reads `authToken` and `port` from `.instar/config.json` relative to cwd.
|
|
30
40
|
* `INSTAR_PORT` env var overrides the config port.
|
|
@@ -36,14 +46,31 @@
|
|
|
36
46
|
*/
|
|
37
47
|
|
|
38
48
|
import * as fs from 'node:fs';
|
|
49
|
+
import { spawnSync } from 'node:child_process';
|
|
39
50
|
|
|
40
51
|
const args = process.argv.slice(2);
|
|
41
52
|
const token = args[0];
|
|
42
53
|
const mode = args[1];
|
|
43
54
|
const consume = args.includes('--consume');
|
|
44
55
|
|
|
45
|
-
|
|
46
|
-
|
|
56
|
+
// Atomic use-and-consume: `<token> <field> --run -- <cmd...>`. Everything after
|
|
57
|
+
// the first `--` (which must follow `--run`) is the command to run with the
|
|
58
|
+
// field value piped to its stdin. The submission is consumed only if the
|
|
59
|
+
// command exits 0, so a failed handoff never destroys the secret.
|
|
60
|
+
const runIdx = args.indexOf('--run');
|
|
61
|
+
const runMode = runIdx !== -1;
|
|
62
|
+
let runCmd = [];
|
|
63
|
+
if (runMode) {
|
|
64
|
+
const sep = args.indexOf('--', runIdx + 1);
|
|
65
|
+
runCmd = sep !== -1 ? args.slice(sep + 1) : [];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (!token || !mode || mode === '--consume' || mode === '--run') {
|
|
69
|
+
process.stderr.write('usage: secret-drop-retrieve.mjs <token> <field-name|--names> [--consume | --run -- <cmd...>]\n');
|
|
70
|
+
process.exit(2);
|
|
71
|
+
}
|
|
72
|
+
if (runMode && runCmd.length === 0) {
|
|
73
|
+
process.stderr.write('usage: --run requires a command after `--`, e.g. --run -- gh auth login --with-token\n');
|
|
47
74
|
process.exit(2);
|
|
48
75
|
}
|
|
49
76
|
|
|
@@ -127,6 +154,42 @@ if (typeof v !== 'string') {
|
|
|
127
154
|
process.exit(1);
|
|
128
155
|
}
|
|
129
156
|
|
|
157
|
+
// Helper: consume the submission (idempotent best-effort). Used only after a
|
|
158
|
+
// verified-successful handoff in --run mode.
|
|
159
|
+
async function consumeSubmission() {
|
|
160
|
+
try {
|
|
161
|
+
await fetch(`http://localhost:${resolvedPort}/secrets/retrieve/${token}?consume=true`, {
|
|
162
|
+
method: 'POST',
|
|
163
|
+
headers: { 'Authorization': `Bearer ${authToken}` },
|
|
164
|
+
});
|
|
165
|
+
} catch {
|
|
166
|
+
// Non-fatal — the sliding/absolute-cap cleanup will reclaim it regardless.
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (runMode) {
|
|
171
|
+
// Pipe the value into the command's stdin; never let it touch argv (process
|
|
172
|
+
// listings / shell history) or stdout (transcript). Inherit stdout/stderr so
|
|
173
|
+
// the operator sees the command's own (non-secret) output.
|
|
174
|
+
const child = spawnSync(runCmd[0], runCmd.slice(1), {
|
|
175
|
+
input: v,
|
|
176
|
+
stdio: ['pipe', 'inherit', 'inherit'],
|
|
177
|
+
encoding: 'utf-8',
|
|
178
|
+
});
|
|
179
|
+
if (child.error) {
|
|
180
|
+
process.stderr.write(`run: failed to launch '${runCmd[0]}': ${child.error.message} — secret NOT consumed (safe to retry)\n`);
|
|
181
|
+
process.exit(1);
|
|
182
|
+
}
|
|
183
|
+
const code = child.status ?? 1;
|
|
184
|
+
if (code === 0) {
|
|
185
|
+
await consumeSubmission();
|
|
186
|
+
process.stderr.write('run: command succeeded — secret consumed.\n');
|
|
187
|
+
} else {
|
|
188
|
+
process.stderr.write(`run: command exited ${code} — secret NOT consumed (safe to retry).\n`);
|
|
189
|
+
}
|
|
190
|
+
process.exit(code);
|
|
191
|
+
}
|
|
192
|
+
|
|
130
193
|
// Stream the value to stdout, no trailing newline so it pipes cleanly into
|
|
131
194
|
// `gh secret set` or `bw unlock --raw`. process.stdout.write (not console.log)
|
|
132
195
|
// — no extra newline, no trailing whitespace to confuse downstream consumers.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Upgrade Guide — vNEXT
|
|
2
|
+
|
|
3
|
+
<!-- assembled-by: assemble-next-md -->
|
|
4
|
+
<!-- bump: patch -->
|
|
5
|
+
|
|
6
|
+
## What Changed
|
|
7
|
+
|
|
8
|
+
A scheduled codex job (`codex exec --json`) can wedge: the process stays alive but
|
|
9
|
+
makes no progress — 0% CPU, no output. One such job sat wedged ~12h undetected. The
|
|
10
|
+
StaleSessionBackstop (which raises a "stale but unkillable" attention item, never
|
|
11
|
+
kills) missed it because its forward-progress check used process **existence**:
|
|
12
|
+
`hasActiveProcesses` walks descendant processes and returns true if any non-baseline
|
|
13
|
+
child is alive — which a wedged-but-alive job is. So the backstop read it as
|
|
14
|
+
"active" and never escalated. (Conversational claude sessions are saved by transcript
|
|
15
|
+
growth, so only jobs — whose codex transcript doesn't resolve — were exposed.)
|
|
16
|
+
|
|
17
|
+
The fix gives the backstop a real CPU signal: `SessionManager.descendantCpuSeconds`
|
|
18
|
+
reads each non-baseline descendant's accumulated CPU time, and the backstop compares
|
|
19
|
+
the delta across its two snapshots — real CPU *used* in the interval, not mere
|
|
20
|
+
existence. This is scoped to **job sessions** (which have no legitimate-idle state):
|
|
21
|
+
a job that uses no CPU between snapshots reads as no-progress and escalates.
|
|
22
|
+
Conversational sessions keep the existing existence-based check, so a legitimately-idle
|
|
23
|
+
session with a background process is *not* falsely flagged.
|
|
24
|
+
|
|
25
|
+
## What to Tell Your User
|
|
26
|
+
|
|
27
|
+
If one of my scheduled background jobs ever freezes — alive but doing nothing — I'll
|
|
28
|
+
now actually notice it and flag it for attention, instead of mistaking "the process
|
|
29
|
+
is still running" for "it's making progress." Before, a job could sit stuck for hours
|
|
30
|
+
unseen; now a job that stops using any CPU gets surfaced. This only adds an
|
|
31
|
+
attention notice — it never kills anything — and it doesn't change how I treat your
|
|
32
|
+
normal interactive sessions.
|
|
33
|
+
|
|
34
|
+
## Summary of New Capabilities
|
|
35
|
+
|
|
36
|
+
- The stale-session backstop now detects a wedged-but-alive **job** (0% CPU, no
|
|
37
|
+
output) and raises its existing attention notice, where it previously read process
|
|
38
|
+
existence as "progress" and stayed silent.
|
|
39
|
+
- New `SessionManager.descendantCpuSeconds` (accumulated CPU-seconds of a session's
|
|
40
|
+
real children) + `parseProcTimeToSeconds` — a CPU-delta progress signal.
|
|
41
|
+
- Scoped to job sessions only; conversational sessions are unchanged (no new
|
|
42
|
+
false-positives on idle-with-a-background-process). Never kills — attention-only.
|
|
43
|
+
|
|
44
|
+
## Evidence
|
|
45
|
+
|
|
46
|
+
- 19 tests: `stale-session-backstop` (13 — incl. wedged-job→escalate, busy-job→no,
|
|
47
|
+
conversational-idle-with-bg→no-regression) + `parseProcTimeToSeconds` (6, covering
|
|
48
|
+
`MM:SS` / `MM:SS.ss` / `HH:MM:SS` / `DD-HH:MM:SS`). `tsc --noEmit` + `pnpm build` clean.
|
|
49
|
+
- The escalation path is unchanged (still attention-only, never a kill); only the
|
|
50
|
+
forward-progress test gains a CPU-delta requirement for job sessions.
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Upgrade Guide — vNEXT
|
|
2
|
+
|
|
3
|
+
<!-- assembled-by: assemble-next-md -->
|
|
4
|
+
<!-- bump: patch -->
|
|
5
|
+
|
|
6
|
+
## What Changed
|
|
7
|
+
|
|
8
|
+
Secret Drop's stored-submission lifetime was reworked so a submitted secret can no
|
|
9
|
+
longer be deleted out from under an active consumer.
|
|
10
|
+
|
|
11
|
+
- **Server (`SecretDrop.ts`)** — the post-submission store now uses a **sliding idle
|
|
12
|
+
window** instead of a fixed 5-minute timer. The cleanup timer re-arms on every
|
|
13
|
+
`peekReceived`, so a secret that is actively being retrieved never expires. The idle
|
|
14
|
+
window is 15 minutes (was 5) and a new per-token `receivedDeadline` enforces a hard
|
|
15
|
+
**30-minute absolute cap** (`armReceivedCleanup` clamps each re-arm with
|
|
16
|
+
`Math.min(idle, deadline − now)`), so even a continuously-polled secret is purged.
|
|
17
|
+
`consumeReceived`/`shutdown` clear the new deadline map; the stuck-consumer event's
|
|
18
|
+
`minutesUntilCleanup` is derived from the deadline.
|
|
19
|
+
- **Consumer (`secret-drop-retrieve.mjs`)** — new `--run -- <cmd>` mode performs an
|
|
20
|
+
**atomic use-and-consume**: it pipes the field value to the command's stdin and
|
|
21
|
+
consumes the submission only if the command exits 0. A failed or launch-error
|
|
22
|
+
handoff never destroys the secret, making consume-on-failure structurally impossible.
|
|
23
|
+
- **Awareness/migration** — the CLAUDE.md scaffold documents `--run` for new agents;
|
|
24
|
+
an idempotent `PostUpdateMigrator` block adds the guidance to existing agents that
|
|
25
|
+
already have the hardened helper. The helper script itself ships via the existing
|
|
26
|
+
always-overwrite relay-script refresh.
|
|
27
|
+
|
|
28
|
+
## What to Tell Your User
|
|
29
|
+
|
|
30
|
+
- **Secret Drop is now sturdier**: "If you hand me a password or key through a secure
|
|
31
|
+
link, it won't quietly expire while I'm in the middle of using it anymore — so you
|
|
32
|
+
shouldn't have to send the same thing twice. And if a step fails, I keep what you
|
|
33
|
+
sent so we can retry, instead of throwing it away." (Note: a secret still lives only
|
|
34
|
+
in memory, so a server restart still clears anything not yet used — that's the
|
|
35
|
+
security trade-off, unchanged.)
|
|
36
|
+
|
|
37
|
+
## Summary of New Capabilities
|
|
38
|
+
|
|
39
|
+
| Capability | How to Use |
|
|
40
|
+
|-----------|-----------|
|
|
41
|
+
| Sliding retrieval window + 30-min cap | automatic |
|
|
42
|
+
| Atomic use-and-consume | `secret-drop-retrieve.mjs TOKEN field --run -- <cmd>` |
|
|
43
|
+
|
|
44
|
+
## Evidence
|
|
45
|
+
|
|
46
|
+
**Reproduction (observed live, 2026-06-02):** during a multi-step credential handoff
|
|
47
|
+
(Bitwarden login, then a GitHub token) the operator had to resubmit the same secret
|
|
48
|
+
roughly six times. Each time, the submission sat in the store while a multi-step
|
|
49
|
+
retrieval ran; the fixed 5-minute timer deleted it mid-flow regardless of the active
|
|
50
|
+
retrievals, and a standalone consume fired after a step that had not yet succeeded —
|
|
51
|
+
destroying the secret with no recovery but resubmission.
|
|
52
|
+
|
|
53
|
+
**Before/after (deterministic, fake-timer unit tests in
|
|
54
|
+
`tests/unit/SecretDrop.test.ts`):**
|
|
55
|
+
- Before this change, a submission was unconditionally deleted 5 minutes after
|
|
56
|
+
submit. After it: a submission peeked at 14 min and again at 28 min is still present
|
|
57
|
+
(the window slid on each read); an untouched submission is still cleaned up after the
|
|
58
|
+
idle window; and a submission polled every 10 min is nonetheless purged once past the
|
|
59
|
+
30-min absolute cap.
|
|
60
|
+
- `--run` mode: consume is invoked only on child exit 0; on non-zero exit or a launch
|
|
61
|
+
error the submission is left intact (verified by inspecting the spawnSync branch and
|
|
62
|
+
the documented `gh auth login --with-token` example).
|
|
63
|
+
|
|
64
|
+
Suites: SecretDrop 37/37, migration 15/15 green; `tsc --noEmit` clean. An independent
|
|
65
|
+
second-pass reviewer concurred with the side-effects review.
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# Side-Effects Review — Codex wedged-job CPU-stall detection
|
|
2
|
+
|
|
3
|
+
**Version / slug:** `codex-wedged-job-cpu-detection`
|
|
4
|
+
**Date:** `2026-06-03`
|
|
5
|
+
**Author:** `Echo`
|
|
6
|
+
**Second-pass reviewer:** `Echo (self) — Tier-1; the regression analysis below is the load-bearing part`
|
|
7
|
+
|
|
8
|
+
## Summary of the change
|
|
9
|
+
|
|
10
|
+
StaleSessionBackstop's `hasForwardProgress` gains a CPU-delta progress signal for
|
|
11
|
+
**job sessions**, replacing the existence-based `mainProcessActive` check for them.
|
|
12
|
+
A wedged-but-alive codex job (0% CPU) now reads as no-progress and triggers the
|
|
13
|
+
existing attention escalation (never a kill). Files: `SessionManager.ts`
|
|
14
|
+
(`descendantCpuSeconds` + `parseProcTimeToSeconds`), `StaleSessionBackstop.ts`
|
|
15
|
+
(ProgressSnapshot fields `descendantCpuSeconds`/`isJobSession`, the job-scoped
|
|
16
|
+
`hasForwardProgress` branch, `cpuFloorSeconds` option), `server.ts` (snapshot
|
|
17
|
+
populates the two fields). The only decision point: *is this session making forward
|
|
18
|
+
progress* — and the change narrows what counts as progress for jobs.
|
|
19
|
+
|
|
20
|
+
## Decision-point inventory
|
|
21
|
+
|
|
22
|
+
- `StaleSessionBackstop.hasForwardProgress` (ii) — modify — for job sessions, require
|
|
23
|
+
cpu-seconds delta > floor instead of process existence.
|
|
24
|
+
- No message block/allow surface. No kill/terminate surface (the backstop has none —
|
|
25
|
+
asserted by an existing structural test).
|
|
26
|
+
|
|
27
|
+
## 1. Over-block
|
|
28
|
+
|
|
29
|
+
No block/allow surface. The analogue ("does it raise a false attention notice?") is
|
|
30
|
+
THE risk and is handled by scoping: the stricter cpu-delta test applies ONLY to job
|
|
31
|
+
sessions (`!!session.jobSlug`). A conversational session that's legitimately idle
|
|
32
|
+
with a background process keeps the existence-based check → not flagged. Verified by
|
|
33
|
+
the `conversational-idle-with-bg → no` test.
|
|
34
|
+
|
|
35
|
+
## 2. Under-block
|
|
36
|
+
|
|
37
|
+
A job that uses *some* CPU between two 120s snapshots (above `cpuFloorSeconds=1`) is
|
|
38
|
+
treated as progressing — so a job that's busy-looping (high CPU, no real output)
|
|
39
|
+
would not be flagged by this signal. That's acceptable: this targets the 0%-CPU
|
|
40
|
+
freeze; a CPU-burning runaway is a different detector's concern (and would show on
|
|
41
|
+
load). I/O-bound jobs at 0% CPU with no output would be flagged — but a job has no
|
|
42
|
+
legitimate long idle-wait state (it runs to completion), so flagging it for attention
|
|
43
|
+
(not killing) is correct.
|
|
44
|
+
|
|
45
|
+
## 3. Level-of-abstraction fit
|
|
46
|
+
|
|
47
|
+
Correct layer. This is a cheap, deterministic process-metric read feeding an existing
|
|
48
|
+
attention-raising backstop. It reuses `hasActiveProcesses`'s exact descendant
|
|
49
|
+
tree-walk + baseline filter, adding only the CPU-time accumulation. No LLM needed —
|
|
50
|
+
the signal is objective (CPU seconds), and the escalation it feeds is attention-only.
|
|
51
|
+
|
|
52
|
+
## 4. Signal vs authority compliance
|
|
53
|
+
|
|
54
|
+
**Required reference:** [docs/signal-vs-authority.md](../../docs/signal-vs-authority.md)
|
|
55
|
+
|
|
56
|
+
- [x] No — this change has no message block/allow surface.
|
|
57
|
+
|
|
58
|
+
It refines a SIGNAL (forward-progress) consumed by an existing smart-ish gate
|
|
59
|
+
(the backstop's episode state machine), which only ever raises an attention item and
|
|
60
|
+
never kills. No new authority.
|
|
61
|
+
|
|
62
|
+
## 5. Interactions
|
|
63
|
+
|
|
64
|
+
- **Shadowing:** The change is inside `hasForwardProgress`, one branch (ii). Branches
|
|
65
|
+
(i) transcript-growth and (iii) idle-token-change are unchanged and still short-
|
|
66
|
+
circuit to "progress" first, so a job that IS producing output/transcript is still
|
|
67
|
+
exempt before the CPU test is consulted.
|
|
68
|
+
- **Double-fire:** None — same single escalation path, same per-episode dedup
|
|
69
|
+
(`episodeActive`), so a persistently-wedged job still raises exactly one item.
|
|
70
|
+
- **Races:** `descendantCpuSeconds` is a synchronous `ps` read per snapshot, same
|
|
71
|
+
cadence/shape as the existing `hasActiveProcesses` call; no shared mutable state.
|
|
72
|
+
- **Feedback loops:** None — the output is an attention notice, which does not feed
|
|
73
|
+
back into the session.
|
|
74
|
+
|
|
75
|
+
## 6. External surfaces
|
|
76
|
+
|
|
77
|
+
- **Persistent state:** none new. ProgressSnapshot lives in memory per tick.
|
|
78
|
+
- **External systems:** none (the attention item uses the existing poster).
|
|
79
|
+
- **Other agents/users:** none — per-agent backstop.
|
|
80
|
+
- **`ps` cost:** one extra `ps -eo pid,ppid,time,command` per session per 120s tick —
|
|
81
|
+
negligible, same class as the existing `hasActiveProcesses` `ps`.
|
|
82
|
+
- **Cross-platform:** `parseProcTimeToSeconds` handles the `ps -o time` formats
|
|
83
|
+
(`MM:SS`, `MM:SS.ss`, `HH:MM:SS`, `DD-HH:MM:SS`); a bad parse reads as 0 (no growth),
|
|
84
|
+
which is safe (the delta just shows no progress; attention, never kill).
|
|
85
|
+
|
|
86
|
+
## 7. Rollback cost
|
|
87
|
+
|
|
88
|
+
Pure code change; no persistent state, no migration. Back-out is `git revert` + ship
|
|
89
|
+
the next patch — the backstop reverts to existence-based progress (the prior, looser
|
|
90
|
+
behavior). No user-visible regression during rollback (it only changes whether a
|
|
91
|
+
*job* gets a stale-attention notice). Worst case if the floor is mis-tuned: a busy
|
|
92
|
+
job flagged spuriously (attention only) or a wedged job missed — both are
|
|
93
|
+
attention-level, never destructive.
|
|
94
|
+
|
|
95
|
+
## Conclusion
|
|
96
|
+
|
|
97
|
+
The review's central risk — falsely flagging legitimately-idle sessions — is closed
|
|
98
|
+
by scoping the cpu-delta requirement to job sessions (no legitimate-idle state) and
|
|
99
|
+
leaving conversational sessions on the existing existence check. That scoping is the
|
|
100
|
+
load-bearing design decision and is directly tested. The escalation remains
|
|
101
|
+
attention-only (never a kill), and the change reuses the existing process-walk. Clear
|
|
102
|
+
to ship.
|
|
103
|
+
|
|
104
|
+
## Second-pass review (if required)
|
|
105
|
+
|
|
106
|
+
**Reviewer:** Echo (self) — Tier-1.
|
|
107
|
+
**Independent read of the artifact: concur**
|
|
108
|
+
|
|
109
|
+
The job-scoping is the right way to avoid the idle-with-bg false-positive; the three
|
|
110
|
+
behavior tests pin both the fix and the no-regression case; rollback is a revert.
|
|
111
|
+
No concerns.
|
|
112
|
+
|
|
113
|
+
## Evidence pointers
|
|
114
|
+
|
|
115
|
+
- `tests/unit/stale-session-backstop.test.ts` (13: incl. the 3 new behavior cases),
|
|
116
|
+
`tests/unit/parseProcTimeToSeconds.test.ts` (6). `tsc` + `pnpm build` clean.
|
|
117
|
+
- Root-cause + design trail: `.instar/autonomous/13435.local.md` item 2 (the iterative
|
|
118
|
+
diagnosis that caught the idle-with-bg / extended-think regression traps).
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# Side-effects review — Secret Drop Sliding Window + Atomic Use-and-Consume
|
|
2
|
+
|
|
3
|
+
**Spec:** `docs/specs/secret-drop-sliding-window.md`
|
|
4
|
+
**Files:** `src/server/SecretDrop.ts`, `src/templates/scripts/secret-drop-retrieve.mjs`, `tests/unit/SecretDrop.test.ts`, `tests/unit/PostUpdateMigrator-secretDropHardenedRetrieve.test.ts`, `src/core/PostUpdateMigrator.ts`, `src/scaffold/templates.ts`
|
|
5
|
+
|
|
6
|
+
## 1. Over-block — what legitimate inputs does this reject that it shouldn't?
|
|
7
|
+
|
|
8
|
+
Nothing new is rejected. Submission validation (CSRF, required fields, R1a signature)
|
|
9
|
+
is unchanged. The sliding window only affects how long an accepted submission is
|
|
10
|
+
*retained*; it never rejects a submission or a retrieve. `--run` adds a code path but
|
|
11
|
+
the default retrieve/`--names`/`--consume` behaviors are byte-for-byte unchanged.
|
|
12
|
+
|
|
13
|
+
## 2. Under-block — what failure modes does this still miss?
|
|
14
|
+
|
|
15
|
+
- **Server restart loses an unconsumed secret.** Explicitly out of scope (in-memory by
|
|
16
|
+
design); contract documented in the spec; encrypted-at-rest tracked as follow-up
|
|
17
|
+
(`fb-391a4a30-de9`).
|
|
18
|
+
- **An agent that uses the destructive `consumeReceived`/`--consume` mid-flow can still
|
|
19
|
+
drop a secret.** The `--run` mode + CLAUDE.md guidance steer away from this, but the
|
|
20
|
+
standalone `--consume` is retained for back-compat and is still misusable. Mitigated,
|
|
21
|
+
not eliminated. Accepted: removing `--consume` outright would be a breaking change to
|
|
22
|
+
any caller already using it.
|
|
23
|
+
|
|
24
|
+
## 3. Level-of-abstraction fit
|
|
25
|
+
|
|
26
|
+
Correct layer. Lifetime management belongs in `SecretDrop` (it owns the store and the
|
|
27
|
+
timers). The atomic use-and-consume belongs in the consumer helper (it owns the
|
|
28
|
+
hand-off to the command). No higher/lower layer is a better owner, and no smarter gate
|
|
29
|
+
exists that this should feed instead.
|
|
30
|
+
|
|
31
|
+
## 4. Signal vs authority compliance
|
|
32
|
+
|
|
33
|
+
Compliant. This adds no decision point that gates information flow or blocks actions.
|
|
34
|
+
The `--run` "consume only on exit 0" is a success gate on a local subprocess, not an
|
|
35
|
+
authority over agent behavior. No brittle-check-with-blocking-authority is introduced.
|
|
36
|
+
(Ref: `docs/signal-vs-authority.md`.)
|
|
37
|
+
|
|
38
|
+
## 5. Interactions
|
|
39
|
+
|
|
40
|
+
- **Stuck-consumer timer:** still fires at 60s; `minutesUntilCleanup` now derives from
|
|
41
|
+
`receivedDeadline` (true purge time) — more accurate, not less. The stuck timer is
|
|
42
|
+
NOT re-armed on peek (deliberate: it's a "nobody has consumed this yet" nudge, and
|
|
43
|
+
the absolute deadline it reports is fixed at submit). No double-fire, no race.
|
|
44
|
+
- **Cleanup timer:** centralized in `armReceivedCleanup`; each re-arm clears the prior
|
|
45
|
+
timer first (no timer leak). `consumeReceived`/`shutdown` clear the timer, the stuck
|
|
46
|
+
timer, AND the new `receivedDeadline` map (no map leak).
|
|
47
|
+
- **`--consume` flag and `--run`:** mutually independent; `--run` consumes via the same
|
|
48
|
+
`?consume=true` endpoint after success. No shadowing.
|
|
49
|
+
|
|
50
|
+
## 6. External surfaces
|
|
51
|
+
|
|
52
|
+
- **Other agents:** the helper template change reaches every agent via the
|
|
53
|
+
always-overwrite relay-script refresh; the server change ships via npm. CLAUDE.md
|
|
54
|
+
awareness reaches new agents (scaffold) and existing agents (idempotent migrator
|
|
55
|
+
block, tested). No external API shape change — `/secrets/retrieve/:token` and
|
|
56
|
+
`?consume=true` are unchanged.
|
|
57
|
+
- **Timing/runtime dependence:** the window is wall-clock based (setTimeout); fake-timer
|
|
58
|
+
tests cover the boundaries deterministically. No dependence on conversation state.
|
|
59
|
+
|
|
60
|
+
## 7. Rollback cost
|
|
61
|
+
|
|
62
|
+
Pure code revert — no data migration, no agent-state repair. Reverting restores the
|
|
63
|
+
fixed 5-min window and removes `--run`; the standalone `--consume` path is untouched
|
|
64
|
+
throughout, so nothing depends on the new behavior to function. The migrator block is
|
|
65
|
+
idempotent and additive (a revert simply stops adding the bullet; already-migrated
|
|
66
|
+
CLAUDE.md files keep a harmless accurate bullet).
|
|
67
|
+
|
|
68
|
+
## Tier
|
|
69
|
+
|
|
70
|
+
**Tier 2.** Risk floor 2 (safety-invariant proximity: SecretDrop / never-on-disk).
|
|
71
|
+
Declared at the floor — not below it. Full ceremony: converged + approved spec, ELI16,
|
|
72
|
+
this artifact, second-pass review, trace.
|
|
73
|
+
|
|
74
|
+
## Second-pass review (independent)
|
|
75
|
+
|
|
76
|
+
An independent reviewer audited this artifact against the code and verified each
|
|
77
|
+
claim: (1) no timer/`receivedDeadline` map leak — every add has a matching clear on
|
|
78
|
+
consume/shutdown/re-arm; (2) the sliding window cannot exceed the 30-min cap (the
|
|
79
|
+
`Math.min(IDLE, deadline−now)` clamp enforces it); (3) `--run` consumes only on exit
|
|
80
|
+
0, never on launch error or non-zero exit, and the value reaches the subprocess via
|
|
81
|
+
stdin only — never stdout/argv; (4) signal-vs-authority compliant (no brittle gate
|
|
82
|
+
with blocking authority); (5) the stuck-consumer timer is correctly NOT re-armed on
|
|
83
|
+
peek. **Verdict: "Concur with the review."**
|