instar 1.3.504 → 1.3.506
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 +46 -0
- package/dist/commands/server.js.map +1 -1
- package/dist/core/AutonomousSessions.d.ts +19 -0
- package/dist/core/AutonomousSessions.d.ts.map +1 -1
- package/dist/core/AutonomousSessions.js +71 -0
- package/dist/core/AutonomousSessions.js.map +1 -1
- package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
- package/dist/core/PostUpdateMigrator.js +34 -1
- package/dist/core/PostUpdateMigrator.js.map +1 -1
- package/dist/core/TransferByNickname.d.ts +11 -0
- package/dist/core/TransferByNickname.d.ts.map +1 -1
- package/dist/core/TransferByNickname.js +29 -6
- package/dist/core/TransferByNickname.js.map +1 -1
- package/dist/core/devGatedFeatures.d.ts.map +1 -1
- package/dist/core/devGatedFeatures.js +6 -0
- package/dist/core/devGatedFeatures.js.map +1 -1
- package/dist/core/time-claim.d.ts +57 -0
- package/dist/core/time-claim.d.ts.map +1 -0
- package/dist/core/time-claim.js +167 -0
- package/dist/core/time-claim.js.map +1 -0
- package/dist/messaging/OutboundAdvisory.d.ts +20 -2
- package/dist/messaging/OutboundAdvisory.d.ts.map +1 -1
- package/dist/messaging/OutboundAdvisory.js +37 -1
- package/dist/messaging/OutboundAdvisory.js.map +1 -1
- package/dist/monitoring/SessionReaper.d.ts +27 -0
- package/dist/monitoring/SessionReaper.d.ts.map +1 -1
- package/dist/monitoring/SessionReaper.js +41 -2
- package/dist/monitoring/SessionReaper.js.map +1 -1
- package/dist/scaffold/templates.d.ts.map +1 -1
- package/dist/scaffold/templates.js +4 -2
- package/dist/scaffold/templates.js.map +1 -1
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +96 -9
- package/dist/server/routes.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +64 -64
- package/src/scaffold/templates.ts +4 -2
- package/src/templates/scripts/telegram-reply.sh +28 -8
- package/upgrades/1.3.505.md +57 -0
- package/upgrades/1.3.506.md +63 -0
- package/upgrades/side-effects/multi-machine-seamlessness-ws12-ws14.md +197 -0
- package/upgrades/side-effects/time-claim-advisory.md +177 -0
|
@@ -38,8 +38,18 @@
|
|
|
38
38
|
# and every error path (server down, timeout, bad JSON) proceeds straight
|
|
39
39
|
# to the send as if the preflight returned nothing. Script-class senders
|
|
40
40
|
# (INSTAR_SENDER_CLASS=script) skip the preflight — there is no agent to
|
|
41
|
-
# inform.
|
|
42
|
-
#
|
|
41
|
+
# inform.
|
|
42
|
+
#
|
|
43
|
+
# Every OTHER sender (including a conversational session with no stamps —
|
|
44
|
+
# typically an interactive session running an autonomous job) also runs the
|
|
45
|
+
# preflight, with its real message kind defaulting to "reply". The server
|
|
46
|
+
# applies ONLY the TIME_CLAIM check to non-automated kinds — an
|
|
47
|
+
# elapsed/remaining claim contradicting the topic's live session clock
|
|
48
|
+
# (operator mandate 2026-06-12: accurate time reporting is structural, not
|
|
49
|
+
# willpower). Jargon/path/link detectors never run for conversational
|
|
50
|
+
# sends, and when the topic has no active time-boxed session the preflight
|
|
51
|
+
# returns nothing — conversational sends remain effectively unaffected.
|
|
52
|
+
# Same fail-open contract end-to-end.
|
|
43
53
|
#
|
|
44
54
|
# Port resolution (in order):
|
|
45
55
|
# 1. INSTAR_PORT environment variable (explicit operator override).
|
|
@@ -180,7 +190,13 @@ esac
|
|
|
180
190
|
JOB_SLUG=$(printf '%s' "${INSTAR_JOB_SLUG:-}" | tr -c 'A-Za-z0-9._-' '_' | head -c 128)
|
|
181
191
|
|
|
182
192
|
ADVISORY_CODES_CSV=""
|
|
183
|
-
|
|
193
|
+
# Preflight gate: every sender EXCEPT script-class (no agent to inform).
|
|
194
|
+
# automated+llm-session → full detector set server-side (unchanged);
|
|
195
|
+
# anything else (incl. unstamped conversational sessions) → the server
|
|
196
|
+
# applies only the TIME_CLAIM clock check, kind defaulting to "reply".
|
|
197
|
+
# An older server returns no advisories for non-automated kinds — the new
|
|
198
|
+
# gate is a no-op against it (version-skew safe both directions).
|
|
199
|
+
if [ "$SENDER_CLASS" != "script" ]; then
|
|
184
200
|
# Timeout: config messaging.outboundAdvisory.timeoutMs (default 2000ms),
|
|
185
201
|
# converted ms→SECONDS for curl --max-time with ceil division, clamped to
|
|
186
202
|
# [1, 10]. A raw `--max-time 2000` would be a ~33-minute fail-HANG and
|
|
@@ -194,11 +210,11 @@ if [ "$MESSAGE_KIND" = "automated" ] && [ "$SENDER_CLASS" = "llm-session" ]; the
|
|
|
194
210
|
|
|
195
211
|
# Preflight body via python3 (already a hard dependency of this script). If
|
|
196
212
|
# python3 is unavailable the preflight is skipped entirely — fail-open.
|
|
197
|
-
PREFLIGHT_BODY=$(PF_TOPIC="$TOPIC_ID" PF_SLUG="$JOB_SLUG" PF_KIND="$MESSAGE_KIND" python3 -c '
|
|
213
|
+
PREFLIGHT_BODY=$(PF_TOPIC="$TOPIC_ID" PF_SLUG="$JOB_SLUG" PF_KIND="${MESSAGE_KIND:-reply}" python3 -c '
|
|
198
214
|
import sys, json, os
|
|
199
215
|
print(json.dumps({
|
|
200
216
|
"text": sys.stdin.read(),
|
|
201
|
-
"messageKind": os.environ.get("PF_KIND"
|
|
217
|
+
"messageKind": os.environ.get("PF_KIND") or "reply",
|
|
202
218
|
"topicId": int(os.environ.get("PF_TOPIC") or 0),
|
|
203
219
|
"jobSlug": os.environ.get("PF_SLUG", ""),
|
|
204
220
|
}))
|
|
@@ -252,7 +268,7 @@ for a in advisories:
|
|
|
252
268
|
# was deliberately not yet sent and the next move belongs to the agent.
|
|
253
269
|
echo "NOT SENT — advisory (fix and re-run, or re-run with --ack-advisory to send unchanged)"
|
|
254
270
|
echo ""
|
|
255
|
-
echo "The outbound advisory flagged this
|
|
271
|
+
echo "The outbound advisory flagged this outbound message BEFORE delivery:"
|
|
256
272
|
printf '%s\n' "$ADVISORY_DETAIL"
|
|
257
273
|
echo ""
|
|
258
274
|
echo "Next move (yours — the advisory layer never blocks):"
|
|
@@ -269,7 +285,7 @@ fi
|
|
|
269
285
|
# component is enum-validated or charset-clamped above, so this fragment is
|
|
270
286
|
# safe to interpolate into JSON and (parameterized) SQL contexts.
|
|
271
287
|
METADATA_JSON=""
|
|
272
|
-
if [ -n "$MESSAGE_KIND" ] || [ -n "$SENDER_CLASS" ] || [ -n "$JOB_SLUG" ]; then
|
|
288
|
+
if [ -n "$MESSAGE_KIND" ] || [ -n "$SENDER_CLASS" ] || [ -n "$JOB_SLUG" ] || { [ "$ACK_ADVISORY" = "1" ] && [ "$SENDER_CLASS" != "script" ]; }; then
|
|
273
289
|
META_PARTS=""
|
|
274
290
|
[ -n "$MESSAGE_KIND" ] && META_PARTS="\"messageKind\":\"${MESSAGE_KIND}\""
|
|
275
291
|
if [ -n "$SENDER_CLASS" ]; then
|
|
@@ -280,7 +296,11 @@ if [ -n "$MESSAGE_KIND" ] || [ -n "$SENDER_CLASS" ] || [ -n "$JOB_SLUG" ]; then
|
|
|
280
296
|
[ -n "$META_PARTS" ] && META_PARTS="${META_PARTS},"
|
|
281
297
|
META_PARTS="${META_PARTS}\"jobSlug\":\"${JOB_SLUG}\""
|
|
282
298
|
fi
|
|
283
|
-
|
|
299
|
+
# Ack annotation rides for EVERY non-script sender (not just automated):
|
|
300
|
+
# an unstamped interactive session's --ack-advisory must record 'acked'
|
|
301
|
+
# server-side, or its advised episodes never resolve and the escalation
|
|
302
|
+
# false-fires on messages that actually delivered (second-pass concern 2).
|
|
303
|
+
if [ "$ACK_ADVISORY" = "1" ] && [ "$SENDER_CLASS" != "script" ]; then
|
|
284
304
|
# REQUIRED annotation (§2.4(4)) — how the server audits "acked" as the
|
|
285
305
|
# single writer. Carries the overridden codes, including [] for a
|
|
286
306
|
# preemptive ack on a clean message (itself a signal).
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Upgrade Guide — vNEXT
|
|
2
|
+
|
|
3
|
+
<!-- assembled-by: assemble-next-md -->
|
|
4
|
+
<!-- bump: patch -->
|
|
5
|
+
|
|
6
|
+
## What Changed
|
|
7
|
+
|
|
8
|
+
- **Post-transfer closeout P19 breaker (SessionReaper):** the closeout's veto-retry
|
|
9
|
+
loop is now bounded — after `monitoring.sessionReaper.topicMovedVetoBreakerAttempts`
|
|
10
|
+
(default 5) consecutive vetoed close attempts on the same session, retries stop for
|
|
11
|
+
the episode, a `closeout-breaker-open` row lands in sentinel-events.jsonl, and ONE
|
|
12
|
+
deduped attention item surfaces. The session still closes via the normal idle path
|
|
13
|
+
when its work finishes. Episode resets on success / topic-home / pin-conflict hold.
|
|
14
|
+
- **Autonomous-run transfer consent gate (WS1.4):** `POST /pool/transfer` and the
|
|
15
|
+
natural-language "move this to <nickname>" now answer `needsConfirmation` when the
|
|
16
|
+
topic has a LIVE autonomous run on this machine — evaluated first among the consent
|
|
17
|
+
gates. A confirmed move (`"confirm":true`) suspends the run at its next turn
|
|
18
|
+
boundary via the new `suspendAutonomousTopicForMove`: `active: false` +
|
|
19
|
+
`moved_to`/`move_suspended_at` markers, ATOMIC rewrite (temp + fsync + rename), the
|
|
20
|
+
state file SURVIVES to ride the working-set carrier, and the journal `stopped` emit
|
|
21
|
+
re-fires the receiver's pull. Response reports `autonomousRunSuspended`.
|
|
22
|
+
- CLAUDE.md template + PostUpdateMigrator migration so new AND deployed agents learn
|
|
23
|
+
the consent gate (Agent Awareness + Migration Parity).
|
|
24
|
+
|
|
25
|
+
## What to Tell Your User
|
|
26
|
+
|
|
27
|
+
Two multi-machine rough edges are gone. First: when a conversation moves between
|
|
28
|
+
your machines, the old machine's cleanup can no longer get stuck hammering a session
|
|
29
|
+
that's still finishing real work — it now backs off after a few refused attempts and
|
|
30
|
+
sends you one clear notice instead of retrying silently for hours. Second: moving a
|
|
31
|
+
topic that has an autonomous job running now asks you first ("there's a run in
|
|
32
|
+
flight, ~N minutes remaining — move anyway?"), and if you say yes, the job is paused
|
|
33
|
+
at a clean stopping point and its progress travels with the conversation to the new
|
|
34
|
+
machine — nothing is lost mid-thought.
|
|
35
|
+
|
|
36
|
+
## Summary of New Capabilities
|
|
37
|
+
|
|
38
|
+
- The post-transfer closeout is bounded by a circuit breaker with one honest
|
|
39
|
+
operator notice per episode (tunable via
|
|
40
|
+
`monitoring.sessionReaper.topicMovedVetoBreakerAttempts`).
|
|
41
|
+
- Topic transfers are autonomous-run-aware: consent before interrupting a run, clean
|
|
42
|
+
turn-boundary suspend on confirmation, and the run's state rides the working-set
|
|
43
|
+
carrier with atomic-write + hash-verified transport.
|
|
44
|
+
|
|
45
|
+
## Evidence
|
|
46
|
+
|
|
47
|
+
- `tests/unit/session-reaper-topic-moved.test.ts` +5 (14 total): the spec's required
|
|
48
|
+
sustained-failure test (permanently-vetoing session → exactly N attempts + ONE
|
|
49
|
+
escalation with stable dedupe id), episode reset on topic-home, success-clears-
|
|
50
|
+
counter, pin-conflict clears counter, absent-dep audit-only.
|
|
51
|
+
- `tests/unit/TransferByNickname.test.ts` +4 (17 total): veto prompt content,
|
|
52
|
+
precedence over offline/mid-reply, idempotency-noop stays ahead, backward compat.
|
|
53
|
+
- `tests/unit/AutonomousSessions.test.ts` +4 (17 total): file survives with markers,
|
|
54
|
+
idempotent re-suspend, no-run no-op, journal stopped emit with artifact path.
|
|
55
|
+
- PostUpdateMigrator suite 416 green; `tsc --noEmit` clean; build green.
|
|
56
|
+
- Independent second-pass review (reaper kill loop + transfer dispatch surface) —
|
|
57
|
+
verdict appended to `upgrades/side-effects/multi-machine-seamlessness-ws12-ws14.md`.
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Upgrade Guide — vNEXT
|
|
2
|
+
|
|
3
|
+
<!-- assembled-by: assemble-next-md -->
|
|
4
|
+
<!-- bump: patch -->
|
|
5
|
+
|
|
6
|
+
## What Changed
|
|
7
|
+
|
|
8
|
+
- **New `TIME_CLAIM` advisory code in the outbound preflight** (`POST /messaging/preflight`):
|
|
9
|
+
when the sending topic has an ACTIVE time-boxed (autonomous) session, anchored
|
|
10
|
+
elapsed/remaining/percent claims in the candidate text ("~7h elapsed", "2h 40m left",
|
|
11
|
+
"8% through") are verified against the live session clock (`readSessionClocks`).
|
|
12
|
+
A claim contradicting the clock beyond tolerance (max(15 min, 20%) for durations,
|
|
13
|
+
15 points for percent) returns a TIME_CLAIM advisory → the relay script's standard
|
|
14
|
+
NOT-SENT loop (fix and re-run, or `--ack-advisory`). Pure deterministic detector at
|
|
15
|
+
`src/core/time-claim.ts`; quoted claims (a correction citing the wrong number) and
|
|
16
|
+
unanchored durations never match.
|
|
17
|
+
- **The relay preflight now covers unstamped senders** (`telegram-reply.sh` template):
|
|
18
|
+
every non-script sender runs the preflight — an interactive session running an
|
|
19
|
+
autonomous run (exactly the founding mis-report path) defaults to kind `reply`,
|
|
20
|
+
where the server applies ONLY the clock check (jargon/path/link detectors still
|
|
21
|
+
never run for conversational sends; no active clock → no advisories). Fail-open
|
|
22
|
+
end-to-end, version-skew safe in both directions. Prior shipped template SHA added
|
|
23
|
+
to `TELEGRAM_REPLY_PRIOR_SHIPPED_SHAS` so deployed stock scripts auto-upgrade.
|
|
24
|
+
- **Ships dark behind the development-agent gate** at
|
|
25
|
+
`messaging.outboundAdvisory.timeClaim.enabled` (registered in `DEV_GATED_FEATURES`);
|
|
26
|
+
master advisory off-switch (`messaging.outboundAdvisory.enabled`) still wins.
|
|
27
|
+
- CLAUDE.md template + PostUpdateMigrator bullet-insert migration so new AND deployed
|
|
28
|
+
agents learn the TIME_CLAIM rule (Agent Awareness + Migration Parity).
|
|
29
|
+
|
|
30
|
+
## What to Tell Your User
|
|
31
|
+
|
|
32
|
+
When I'm running a long autonomous job for you, my progress reports state how long
|
|
33
|
+
I've been running and how much time is left. Previously I could (and once did) guess
|
|
34
|
+
those numbers wrong — saying "7 hours in" when the run was 1.5 hours old. Now there's
|
|
35
|
+
a structural check: before a report reaches you, any time claim in it is compared
|
|
36
|
+
against the run's real clock, and a wrong number stops the message until I correct it.
|
|
37
|
+
You'll simply notice that the times in my reports are right.
|
|
38
|
+
|
|
39
|
+
## Summary of New Capabilities
|
|
40
|
+
|
|
41
|
+
- Time claims in outbound messages are verified against the live session clock
|
|
42
|
+
before delivery (TIME_CLAIM advisory; dark by default, dev-agents live, fleet flip
|
|
43
|
+
via `messaging.outboundAdvisory.timeClaim.enabled: true`).
|
|
44
|
+
- The outbound advisory preflight now also covers interactive sessions running
|
|
45
|
+
autonomous jobs — the clock check only; conversational text gets no new friction.
|
|
46
|
+
|
|
47
|
+
## Evidence
|
|
48
|
+
|
|
49
|
+
- `tests/unit/time-claim.test.ts` (15 new): extraction decision table (anchored,
|
|
50
|
+
composites, minutes-only, percent), both sides of every tolerance boundary, the
|
|
51
|
+
founding incident shape fires, quoted-claim and future-tense "in 2 hours" never
|
|
52
|
+
match, unbounded-run skip, lenient multi-clock.
|
|
53
|
+
- `tests/unit/outbound-advisory-routes.test.ts` +8: automated + reply kinds fire,
|
|
54
|
+
reply kind runs ONLY the clock check, accurate report passes, inactive run never
|
|
55
|
+
fires, dev-gate dark-on-fleet / explicit-enable / force-dark, audit row keyed
|
|
56
|
+
`interactive-session`.
|
|
57
|
+
- `tests/unit/telegram-reply-advisory-script.test.ts` +1/±2: unstamped sender NOT-SENT
|
|
58
|
+
loop, preflight kind degrades to `reply` under an injection-shaped kind env,
|
|
59
|
+
script-class senders still skip.
|
|
60
|
+
- `tests/e2e/outbound-advisory-alive.test.ts` +1: TIME_CLAIM alive on the production
|
|
61
|
+
init path (explicit-enable fires; dark by default on a non-dev config).
|
|
62
|
+
- `tests/unit/devGatedFeatures-wiring.test.ts` auto-covers the new registry entry
|
|
63
|
+
(live-on-dev / dark-on-fleet, both sides). `tsc --noEmit` clean.
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
# Side-Effects Review — WS1.2 closeout P19 breaker + WS1.4 autonomous-run transfer guard
|
|
2
|
+
|
|
3
|
+
**Spec:** docs/specs/MULTI-MACHINE-SEAMLESSNESS-SPEC.md §WS1.2 (closeout coupling + P19
|
|
4
|
+
foundation patch) + §WS1.4 (autonomous runs survive or veto topic moves)
|
|
5
|
+
**Build order position:** step 5's first slice. This change ships the two pieces that
|
|
6
|
+
are complete WITHOUT the drain mesh verb; the drain verb + barrier + terminal
|
|
7
|
+
semantics are the next item in this same workstream's build order (spec §WS1.2 lines
|
|
8
|
+
141–172) and extend — never rework — what ships here.
|
|
9
|
+
|
|
10
|
+
What ships:
|
|
11
|
+
|
|
12
|
+
1. **Closeout P19 breaker (SessionReaper):** the post-transfer closeout's veto-retry
|
|
13
|
+
loop was unbounded — the 2026-06-12 incident was this exact loop attacking a
|
|
14
|
+
working session every 2 minutes for hours. Now: after
|
|
15
|
+
`topicMovedVetoBreakerAttempts` (default 5) CONSECUTIVE vetoed terminate attempts
|
|
16
|
+
on the same session, retries STOP for the episode, one `closeout-breaker-open`
|
|
17
|
+
audit row lands in sentinel-events.jsonl, and ONE deduped attention item surfaces
|
|
18
|
+
("topic moved to X but the old session won't close — held by Y"). The session is
|
|
19
|
+
NOT stranded: the idle pipeline still evaluates it every tick and reaps it when
|
|
20
|
+
its work actually finishes. Episode resets on success / topic-home /
|
|
21
|
+
pin-conflict hold / session end.
|
|
22
|
+
2. **WS1.4 transfer-time veto (planTransferByNickname):** a LIVE local autonomous run
|
|
23
|
+
on the topic → `confirm-required` (`detail: 'autonomous-run-in-flight'`),
|
|
24
|
+
evaluated FIRST among the consent gates (spec precedence: the veto fires at
|
|
25
|
+
transfer-request time, before any move mechanics). Wired into BOTH callers:
|
|
26
|
+
POST /pool/transfer (confirm:true consents) and the NL "move this to <nick>" arm
|
|
27
|
+
(prompts conversationally; the confirmed move arrives via the API).
|
|
28
|
+
3. **WS1.4 confirmed move → turn-boundary suspend (suspendAutonomousTopicForMove):**
|
|
29
|
+
distinct from stop in exactly one way — the state file SURVIVES to ride the
|
|
30
|
+
working-set carrier. `active: false` releases the stop hook at the run's next
|
|
31
|
+
turn boundary; `moved_to` + `move_suspended_at` markers are the honest
|
|
32
|
+
breadcrumb; the rewrite is ATOMIC (same-dir temp + fsync + rename) so the carrier
|
|
33
|
+
can never ship a half-rewritten file; the journal `stopped` emit is what re-fires
|
|
34
|
+
the receiving machine's working-set pull (WorkingSetManifest §3.4). Stated
|
|
35
|
+
outright: NOTHING auto-resumes the run on the receiving machine — the carried
|
|
36
|
+
file lands `active: false` with its markers, and resuming is a deliberate act
|
|
37
|
+
(the operator's or a future workstream's). The spec's receiving-side schema
|
|
38
|
+
gate applies when auto-resume exists; transport integrity (hash-verified
|
|
39
|
+
assembly, torn-chunk refusal, never-clobber) is already enforced by the
|
|
40
|
+
carrier today.
|
|
41
|
+
|
|
42
|
+
**Second-pass fixes folded in (2026-06-13, before merge):** the independent review
|
|
43
|
+
below caught two real defects in the WS1.4 half as first built — (1) a stacked-gate
|
|
44
|
+
consent collapse: `confirm:true` after the autonomous-run prompt would silently
|
|
45
|
+
consent to an offline target the user was never shown (the veto's higher precedence
|
|
46
|
+
suppressed the offline prompt). Fixed at the planner: ALL live consent conditions
|
|
47
|
+
now stack into ONE prompt with a '+'-joined `detail`, the route no longer suppresses
|
|
48
|
+
the gate on confirm (the full chain re-evaluates), and the 409 echoes `detail`.
|
|
49
|
+
(2) a silent false-success: the suspend's `active: true` flip regex was stricter
|
|
50
|
+
than the reader's quote-tolerant parse, so a quoted-active file would be reported
|
|
51
|
+
`suspended: true` while the run stayed live. Fixed: reader-aligned tolerant flip +
|
|
52
|
+
a post-flip verification gate that returns `suspended: false` (and emits nothing)
|
|
53
|
+
when the flip did not land; an inactive file with no move markers is an honest
|
|
54
|
+
no-op. Both fixes carry dedicated tests.
|
|
55
|
+
|
|
56
|
+
## 1. Over-block
|
|
57
|
+
The veto requires confirmation for EVERY move off a topic with a live run — including
|
|
58
|
+
a move the operator urgently wants. Bounded: one extra round-trip (`confirm:true`),
|
|
59
|
+
and the prompt carries goal + remaining time so the consent is informed
|
|
60
|
+
(context-before-consent, P8). The breaker stops closeout retries while a session
|
|
61
|
+
still SHOULD close — bounded by the idle pipeline still owning the session (it reaps
|
|
62
|
+
on actual completion) and by the attention item handing the operator the manual
|
|
63
|
+
lever. The idempotency no-op stays AHEAD of the veto, so "already there" never asks.
|
|
64
|
+
|
|
65
|
+
## 2. Under-block
|
|
66
|
+
The veto and the suspend read THIS machine's run registry (the state-file directory)
|
|
67
|
+
— a run living on a REMOTE owner (holder ≠ owner under the active-active pool) is
|
|
68
|
+
not seen, because the holder can neither see nor suspend a remote machine's run
|
|
69
|
+
without a drain signal; that is precisely the WS1.2 drain mesh verb's job (next item
|
|
70
|
+
in this workstream, spec §WS1.2 "drain authorization"), and today's production
|
|
71
|
+
topology (one-awake-machine; the run lives where the transfer executes) is fully
|
|
72
|
+
covered. The breaker counts only REAL vetoed terminate calls — budget-exhausted
|
|
73
|
+
ticks don't increment, so a busy reap hour cannot open the breaker spuriously.
|
|
74
|
+
|
|
75
|
+
## 3. Level-of-abstraction fit
|
|
76
|
+
The breaker lives inside the loop it bounds (P19's "in-component" rule). The veto is
|
|
77
|
+
a planner consent gate exactly beside the two existing ones (offline / mid-reply) —
|
|
78
|
+
no parallel mechanism. The suspend is a sibling of stopAutonomousTopic in the run
|
|
79
|
+
registry's own module, not route-inline logic. The carrier integrity story reuses
|
|
80
|
+
the EXISTING working-set machinery (manifest sha256, served-bytes assembly hash,
|
|
81
|
+
fstat tear detection, never-clobber landing, liveSource re-fire on `stopped`) —
|
|
82
|
+
nothing re-implemented.
|
|
83
|
+
|
|
84
|
+
## 4. Signal vs authority compliance
|
|
85
|
+
The breaker REMOVES authority (stops retrying a kill) on a deterministic counter and
|
|
86
|
+
escalates to the operator — the safe direction. The veto converts an implicit move
|
|
87
|
+
into explicit operator consent; it never blocks (confirm proceeds). The suspend acts
|
|
88
|
+
only on the operator's confirmed instruction, and the run's own stop hook remains
|
|
89
|
+
the enforcement authority (`active: false` is the hook's existing contract, not a
|
|
90
|
+
new kill path). No message content is inspected anywhere.
|
|
91
|
+
|
|
92
|
+
## 5. Interactions
|
|
93
|
+
- Breaker × pin-conflict hold (WS1.3): the hold withdraws the closeout intent and
|
|
94
|
+
CLEARS the veto counter — a post-reconcile genuine move starts a fresh episode.
|
|
95
|
+
- Breaker × idle pipeline: a breaker-open session falls through to the idle
|
|
96
|
+
evaluation the same tick (verified: only a successful terminate `continue`s).
|
|
97
|
+
- Breaker × attention queue: keyed `closeout-breaker:<session>` — the store dedupes
|
|
98
|
+
on id (P17), which makes the dedupe per SESSION LIFETIME, not per episode: a
|
|
99
|
+
second breaker episode for the same still-running session re-raises the same id
|
|
100
|
+
and is swallowed even if the first item was resolved. The per-episode audit row
|
|
101
|
+
still lands; acceptable, named honestly (reviewer correction).
|
|
102
|
+
- Veto × confirm flow: a consented call cannot loop on its own prompt — the route
|
|
103
|
+
proceeds past `confirm-required` when `confirm:true` — and because the planner
|
|
104
|
+
stacks every live condition into the one prompt the caller saw, a confirm can
|
|
105
|
+
never consent to an unseen condition (second-pass fix).
|
|
106
|
+
- Suspend × journal scanner: emitStopped runs BEFORE the rewrite so the runId keys
|
|
107
|
+
on the live startedAt — op-key dedupe with the scanner's observed-stopped holds.
|
|
108
|
+
- Suspend × stop-all/emergency-stop: untouched — those still DELETE state (a stop is
|
|
109
|
+
not a move).
|
|
110
|
+
- Suspend × pin/release ordering: suspend runs BEFORE pin+release so the re-fired
|
|
111
|
+
carrier pull reads the final, consistent file.
|
|
112
|
+
|
|
113
|
+
## 6. External surfaces
|
|
114
|
+
POST /pool/transfer gains a response field (`autonomousRunSuspended`) and a new
|
|
115
|
+
confirm prompt — additive. The NL arm gains one conversational prompt. CLAUDE.md
|
|
116
|
+
template + PostUpdateMigrator migration ship in this change (Agent Awareness +
|
|
117
|
+
Migration Parity): new installs and deployed agents both learn the consent gate.
|
|
118
|
+
No new mesh verbs, no protocol bump (that arrives with the drain verb).
|
|
119
|
+
|
|
120
|
+
## 7. Multi-machine posture (Cross-Machine Coherence)
|
|
121
|
+
The breaker is per-machine local state over local terminate attempts — no
|
|
122
|
+
replication, no cross-machine reads on the reaper tick (spec hot-path rule). The
|
|
123
|
+
veto/suspend read only the local run registry; the cross-machine arm rides the
|
|
124
|
+
already-replicated coherence journal (`stopped` emit) and the existing carrier.
|
|
125
|
+
Phase C: nothing here assumes 2 machines, a LAN, or interactivity — the breaker
|
|
126
|
+
scales per-session O(1); the veto's registry read is one directory listing
|
|
127
|
+
regardless of pool size; headless VMs run the identical path.
|
|
128
|
+
|
|
129
|
+
## 8. Rollback cost
|
|
130
|
+
Breaker: `topicMovedVetoBreakerAttempts` is plain config; setting it very high
|
|
131
|
+
restores the old retry-forever behavior (and `topicMovedCloseout:false` disables the
|
|
132
|
+
rule wholesale, as before). Veto/suspend: the planner dep is optional — reverting
|
|
133
|
+
the two wiring sites restores pre-WS1.4 behavior byte-for-byte; a suspended state
|
|
134
|
+
file is manually resumable (`active: true`, delete the markers). No data migration,
|
|
135
|
+
no state repair. Revert-and-release covers all of it.
|
|
136
|
+
|
|
137
|
+
## Second-pass review
|
|
138
|
+
REQUIRED (session lifecycle: reaper kill loop + transfer dispatch). Independent
|
|
139
|
+
reviewer response appended below.
|
|
140
|
+
|
|
141
|
+
<!-- second-pass reviewer response appended below by the independent reviewer -->
|
|
142
|
+
|
|
143
|
+
### Independent second-pass review — round 1 (2026-06-12)
|
|
144
|
+
|
|
145
|
+
**Concern raised: confirm:true on the autonomous-run prompt silently consents to the
|
|
146
|
+
offline-target gate the user never saw, and `suspendAutonomousTopicForMove` can
|
|
147
|
+
report `suspended: true` while having suspended nothing.**
|
|
148
|
+
|
|
149
|
+
Summary of the round-1 verdict (full notes in the review record): the WS1.2 breaker
|
|
150
|
+
half was confirmed correct and well-tested without reservation (only-real-vetoes
|
|
151
|
+
counting, exactly-once open/escalation, no-strand fall-through, all four episode
|
|
152
|
+
resets, throw-proof attention wiring). The WS1.4 half had two real defects — (1) the
|
|
153
|
+
stacked-gate consent collapse: the autonomous veto's early return suppressed the
|
|
154
|
+
offline prompt, and the route's confirm:true suppression let a confirm consent to a
|
|
155
|
+
condition the caller was never shown; (2) a latent silent false-success: the
|
|
156
|
+
suspend's `active: true` flip regex was stricter than the quote-tolerant
|
|
157
|
+
`readField`, so a quoted-active file would be reported suspended while the run
|
|
158
|
+
stayed live. Plus two wording corrections: the breaker attention dedupe is per
|
|
159
|
+
session lifetime (not per episode), and the artifact should state outright that
|
|
160
|
+
nothing auto-resumes a carried run on the receiving machine. Both defects were
|
|
161
|
+
fixed and both corrections folded into the artifact before commit (see "Second-pass
|
|
162
|
+
fixes folded in" above).
|
|
163
|
+
|
|
164
|
+
### Independent second-pass re-verification — round 2 (2026-06-12)
|
|
165
|
+
|
|
166
|
+
**Concur — both fixes verified.**
|
|
167
|
+
|
|
168
|
+
1. **Consent collapse FIXED.** All three consent conditions evaluate unconditionally
|
|
169
|
+
into one `consents` array with no early return between them
|
|
170
|
+
(TransferByNickname.ts:112-131); `detail` is the '+'-joined list and the prompt
|
|
171
|
+
joins every condition's sentence (:135-136); the triple-stack is test-pinned
|
|
172
|
+
(TransferByNickname.test.ts:139-151). The route passes `autonomousRunActive`
|
|
173
|
+
unconditionally (routes.ts:10609-10627), the only 409 branch requires
|
|
174
|
+
`confirm !== true` (:10638) so confirm:true falls through to execute (no loop),
|
|
175
|
+
the 409 echoes `detail` (:10641), and the holder-proxy forwards `confirm`
|
|
176
|
+
(:10564). The NL arm relays the stacked prompt for free (server.ts:14373-14375)
|
|
177
|
+
and never executes a confirm itself. Residual (non-blocking): the generic
|
|
178
|
+
prompt→confirm TOCTOU (a condition going live between the 409 and the retry) —
|
|
179
|
+
the `detail` echo is the natural hook for an `expectedDetail` handshake if that
|
|
180
|
+
window ever matters.
|
|
181
|
+
2. **Suspend false-success FIXED — structurally, not regex-patched.** The post-flip
|
|
182
|
+
verification gate re-reads with the same `readField` the veto uses and returns
|
|
183
|
+
`{suspended:false}` with no emit and no write when the flip did not land
|
|
184
|
+
(AutonomousSessions.ts:213-217). Adversarial counterexample search (quoted,
|
|
185
|
+
unspaced, trailing-whitespace, CRLF, malformed-quote forms): every shape
|
|
186
|
+
`readField` accepts as 'true' is either flipped or caught by the gate; the only
|
|
187
|
+
constructed escape (duplicate active keys) is one where all readers already
|
|
188
|
+
treat the run as inactive — reader and suspend agree, no system-visible false
|
|
189
|
+
success. Idempotent re-suspend refreshes markers without emitting (:206-228,
|
|
190
|
+
:232); honest no-op and missing-file paths verified byte-identical/false; the
|
|
191
|
+
atomic same-dir temp + fsync + rename path is unchanged (:234-242).
|
|
192
|
+
3. **Suite green, artifact accurate.** 51/51 across the three test files;
|
|
193
|
+
`tsc --noEmit` clean. The artifact records both fixes, the no-auto-resume
|
|
194
|
+
property, and the per-session-lifetime dedupe wording. The dedicated tests added
|
|
195
|
+
for both fixes would each have failed against the originally-reviewed code.
|
|
196
|
+
|
|
197
|
+
No regressions found.
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# Side-Effects Review — TIME_CLAIM outbound advisory (accurate time reporting)
|
|
2
|
+
|
|
3
|
+
**Version / slug:** `time-claim-advisory`
|
|
4
|
+
**Date:** `2026-06-12`
|
|
5
|
+
**Author:** `Instar Agent (echo)`
|
|
6
|
+
**Second-pass reviewer:** `independent reviewer subagent (verdict appended below)`
|
|
7
|
+
|
|
8
|
+
## Summary of the change
|
|
9
|
+
|
|
10
|
+
Adds a `TIME_CLAIM` advisory code to the inform-only outbound preflight: when the
|
|
11
|
+
sending topic has an ACTIVE time-boxed (autonomous) session, anchored
|
|
12
|
+
elapsed/remaining/percent claims in the candidate text are verified against the
|
|
13
|
+
live session clock; a gross contradiction returns an advisory and the relay
|
|
14
|
+
script's existing NOT-SENT loop holds the message until the sender fixes the
|
|
15
|
+
number or acks. Files: `src/core/time-claim.ts` (new pure detector),
|
|
16
|
+
`src/messaging/OutboundAdvisory.ts` (code + guidance + `composeTimeClaimAdvisories`),
|
|
17
|
+
`src/server/routes.ts` (preflight route resolves active clocks; reply-kind sends get
|
|
18
|
+
the clock check ONLY), `src/templates/scripts/telegram-reply.sh` (preflight gate
|
|
19
|
+
widened from automated+llm-session to every non-script sender),
|
|
20
|
+
`src/core/devGatedFeatures.ts` (dev-gate registry), `src/core/PostUpdateMigrator.ts`
|
|
21
|
+
(prior-shipped template SHA + CLAUDE.md bullet migration), `src/scaffold/templates.ts`
|
|
22
|
+
(Agent Awareness). Operator mandate: Justin, 2026-06-12 17:55 PDT, topic 13481.
|
|
23
|
+
|
|
24
|
+
## Decision-point inventory
|
|
25
|
+
|
|
26
|
+
- `POST /messaging/preflight` advisory composition — **modify** — adds the TIME_CLAIM
|
|
27
|
+
detector for automated kinds and makes it the ONLY detector for non-automated kinds.
|
|
28
|
+
Inform-only: the route returns advisories; it never blocks a send.
|
|
29
|
+
- `telegram-reply.sh` preflight gate — **modify** — widens which senders consult the
|
|
30
|
+
preflight (every non-script sender). The script's existing NOT-SENT/ack loop is
|
|
31
|
+
unchanged; sender keeps final authority.
|
|
32
|
+
- `recordPreflight` audit keying — **modify (pass-through shape)** — reply-kind rows
|
|
33
|
+
with no job slug are keyed `interactive-session` instead of empty string.
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## 1. Over-block
|
|
38
|
+
|
|
39
|
+
**What legitimate inputs does this reject that it shouldn't?**
|
|
40
|
+
|
|
41
|
+
The advisory never rejects — worst case is a held message the sender must re-send or
|
|
42
|
+
ack. The realistic false-hold shapes, and their mitigations:
|
|
43
|
+
|
|
44
|
+
- A message quoting a WRONG time to correct it ("my '~7h elapsed' line was wrong"):
|
|
45
|
+
quoted-claim skip (chars `"'""''` + backtick immediately before the claim) — unit-tested.
|
|
46
|
+
- Honest rounding ("about 2h" at 1h54m): tolerance is max(15 min, 20%) — unit-tested
|
|
47
|
+
on both sides.
|
|
48
|
+
- Talk about OTHER durations ("the regression took 3h", "in 2 hours I'll check"):
|
|
49
|
+
extraction requires an explicit elapsed/remaining/percent anchor; future-tense
|
|
50
|
+
"in X hours" and "Xh in CI" shapes are excluded by the boundary lookahead — unit-tested.
|
|
51
|
+
- A topic with NO active timed run: detector is a structural no-op (no clocks → no
|
|
52
|
+
advisory) — conversational sends remain effectively friction-free.
|
|
53
|
+
|
|
54
|
+
Residual: a sender quoting a wrong time WITHOUT quote marks in a correction message
|
|
55
|
+
could be held once; the advisory text tells it the real numbers, and `--ack-advisory`
|
|
56
|
+
delivers unchanged. Accepted — this is the exact message class the mandate targets.
|
|
57
|
+
|
|
58
|
+
## 2. Under-block
|
|
59
|
+
|
|
60
|
+
**What failure modes does this still miss?**
|
|
61
|
+
|
|
62
|
+
- Prose-only claims with no anchored number ("we're most of the way through") — out
|
|
63
|
+
of scope for a deterministic detector; accepted.
|
|
64
|
+
- Wrong ABSOLUTE times ("started at 4pm", "ends at 16:07 tomorrow") — not parsed in
|
|
65
|
+
v1; the elapsed/remaining/percent family covers the founding incident class.
|
|
66
|
+
Tracked for a follow-up detector revision if dogfooding shows absolute-time
|
|
67
|
+
guesses recur. <!-- tracked: 13481 -->
|
|
68
|
+
- Slack/WhatsApp/iMessage relay scripts have no preflight at all today (pre-existing);
|
|
69
|
+
the Telegram template is where autonomous reports go. Same tracking item.
|
|
70
|
+
<!-- tracked: 13481 -->
|
|
71
|
+
- Time claims in CONVERSATIONAL sessions on topics with no active run are unchecked
|
|
72
|
+
by design (no clock = no truth to compare against).
|
|
73
|
+
|
|
74
|
+
## 3. Level-of-abstraction fit
|
|
75
|
+
|
|
76
|
+
Right layer. The deterministic detector lives beside the other text detectors
|
|
77
|
+
(`JargonDetector`, `raw-file-path`, `localhost-link`) in `src/core/`; the clock READ
|
|
78
|
+
stays in the route (the module remains pure); the hold-and-fix loop reuses the
|
|
79
|
+
existing advisory mechanism rather than inventing a parallel one. The smarter
|
|
80
|
+
outbound authority (MessagingToneGate) is not the right owner: this is an objective
|
|
81
|
+
numeric contradiction, not a tone/judgment call, and the mandate asks for a
|
|
82
|
+
deterministic guarantee.
|
|
83
|
+
|
|
84
|
+
## 4. Signal vs authority compliance
|
|
85
|
+
|
|
86
|
+
Compliant (docs/signal-vs-authority.md). The detector is a brittle/cheap
|
|
87
|
+
signal-producer feeding the existing inform-only advisory surface. It holds NO
|
|
88
|
+
blocking authority: the server route only returns advisories; the script-side loop
|
|
89
|
+
gives the sender the fix-or-ack decision; every error path (clock read failure,
|
|
90
|
+
detector throw, server down, timeout, malformed JSON) fails OPEN to delivery.
|
|
91
|
+
|
|
92
|
+
## 5. Interactions
|
|
93
|
+
|
|
94
|
+
- The localhost-link server guard, tone gate, dedup, and delivery pipeline are
|
|
95
|
+
untouched — the TIME_CLAIM advisory composes alongside the other codes for
|
|
96
|
+
automated sends and rides the same audit/escalation machinery (signature includes
|
|
97
|
+
the code set, so per-signature escalation just works).
|
|
98
|
+
- Double-fire: for automated sends the detector runs once inside
|
|
99
|
+
`composeAdvisories`; the reply path calls `composeTimeClaimAdvisories` directly —
|
|
100
|
+
one detector run per preflight, never two.
|
|
101
|
+
- The reply-kind preflight writes audit rows that previously only automated sends
|
|
102
|
+
wrote; rows are keyed `interactive-session`, so per-slug aggregate escalation for
|
|
103
|
+
real jobs is not polluted.
|
|
104
|
+
- Race with a run finishing between preflight and send: harmless — worst case a
|
|
105
|
+
passing claim was checked against a clock that then expired; the advisory is
|
|
106
|
+
point-in-time by design.
|
|
107
|
+
|
|
108
|
+
## 6. External surfaces
|
|
109
|
+
|
|
110
|
+
- `POST /messaging/preflight` response may now include a `TIME_CLAIM` advisory; for
|
|
111
|
+
non-automated kinds the response can be non-empty for the first time. OLD scripts
|
|
112
|
+
never call the preflight for those kinds → no deployed-behavior change until the
|
|
113
|
+
script template migrates (SHA-gated, stock copies only; customized copies get a
|
|
114
|
+
`.new` candidate + degradation event — the established mechanism).
|
|
115
|
+
- NEW script + OLD server: server returns `[]` for non-automated kinds → no-op.
|
|
116
|
+
Version-skew safe both directions (tested at the route level for the kinds matrix).
|
|
117
|
+
- Timing dependence: yes, inherently — the detector compares against the live clock
|
|
118
|
+
at preflight time; tolerances absorb request latency (seconds vs a 15-minute floor).
|
|
119
|
+
|
|
120
|
+
## 7. Multi-machine posture (Cross-Machine Coherence)
|
|
121
|
+
|
|
122
|
+
**machine-local BY DESIGN.** The session clock reads `<stateDir>/autonomous/*.local.md`
|
|
123
|
+
— per-machine state for runs executing ON this machine — and the preflight runs on
|
|
124
|
+
the same machine as the sending session, so the clock consulted is the right one by
|
|
125
|
+
construction. A topic transferred mid-run carries its autonomous state file via the
|
|
126
|
+
working-set carrier (WS1.4, PR #1092); after a transfer the new machine's preflight
|
|
127
|
+
reads the carried file once the run resumes there. No replication needed; no URL or
|
|
128
|
+
notice surface is generated; one-voice gating is unaffected (this layer holds a
|
|
129
|
+
message BEFORE send, it never speaks).
|
|
130
|
+
|
|
131
|
+
## 8. Rollback cost
|
|
132
|
+
|
|
133
|
+
Cheap, three independent levers, no data migration:
|
|
134
|
+
- Live-config: `messaging.outboundAdvisory.timeClaim.enabled: false` (or the master
|
|
135
|
+
`messaging.outboundAdvisory.enabled: false`) — read live, no restart.
|
|
136
|
+
- The feature ships DARK on the fleet (dev-agent gate); never enabling it is the
|
|
137
|
+
default state.
|
|
138
|
+
- Full revert: the detector is additive; reverting the PR restores the prior
|
|
139
|
+
template SHA path (the SHA set keeps old entries valid as migration sources).
|
|
140
|
+
State written: only advisory audit JSONL rows (observe-only).
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## Second-pass review
|
|
145
|
+
|
|
146
|
+
**Round 1 (independent reviewer): CONCERN RAISED — two genuine defects; all safety
|
|
147
|
+
claims (fail-open at every layer, dev-gate resolution, version-skew no-op both
|
|
148
|
+
directions, ReDoS-bounded regexes ≤1.3ms on adversarial 64KB inputs, migration
|
|
149
|
+
idempotency, template-SHA correctness) verified clean.**
|
|
150
|
+
|
|
151
|
+
- **Concern 1 — percent anchors captured TASK progress.** `done`/`complete`/bare-`in`
|
|
152
|
+
("the migration is 90% done", "passes 100% in CI") parsed as TIME claims; task
|
|
153
|
+
progress diverges from wall-clock percent in the NORMAL state, so honest reports
|
|
154
|
+
would be held routinely — and the guidance would have made them WRONG.
|
|
155
|
+
**Fixed:** percent anchors now require an explicit time noun
|
|
156
|
+
(`elapsed | (through|of|into) the (run|session|clock)`); 5 new negative tests on
|
|
157
|
+
the reviewer's exact counterexamples.
|
|
158
|
+
- **Concern 2 — a non-automated `--ack-advisory` never recorded `acked`.** The
|
|
159
|
+
script's ack annotation was gated to automated+llm-session, and the route's ack
|
|
160
|
+
writer used the raw (empty) jobSlug — so an interactive session's advised
|
|
161
|
+
episodes could never resolve, and the ignore-escalation would false-page on
|
|
162
|
+
messages that actually DELIVERED (compounded by every interactive session
|
|
163
|
+
sharing the `interactive-session` signature). **Fixed:** the script annotates
|
|
164
|
+
the ack for every non-script sender, and the route's ack writer applies the same
|
|
165
|
+
`interactive-session` slug fallback as the preflight writer; covered by a new
|
|
166
|
+
script test (unstamped ack carries advisoryAck + codes) and a new route test
|
|
167
|
+
(kindless ack lands `acked` under the SAME signature key).
|
|
168
|
+
|
|
169
|
+
**Round 2: CONCUR.** The reviewer executed the literal percent regex against all
|
|
170
|
+
round-1 counterexamples (zero matches) and the three time-noun shapes (all match);
|
|
171
|
+
traced the script's ack-first METADATA_JSON assembly for the all-env-empty case
|
|
172
|
+
(valid JSON, no leading-comma defect, confirmed empirically by the unstamped-ack
|
|
173
|
+
test JSON-parsing the delivered body); and verified the preflight writer and the
|
|
174
|
+
ack writer resolve to the IDENTICAL signature key for every kind, so a reply-kind
|
|
175
|
+
advised episode is genuinely resolved by its acked row (recordAck clears
|
|
176
|
+
`unresolved` + `escalatedIgnore` on the `slug|topic|codes` signature). 55/55 tests
|
|
177
|
+
green in the three touched files.
|