instar 1.3.255 → 1.3.256

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.
@@ -0,0 +1,92 @@
1
+ # Upgrade Guide — vNEXT
2
+
3
+ <!-- assembled-by: assemble-next-md -->
4
+ <!-- bump: patch -->
5
+
6
+ ## What Changed
7
+
8
+ The `dangerous-command-guard.sh` PreToolUse hook blocks risky git commands,
9
+ including `git push --force` and `git push -f`. Because the match is a
10
+ case-insensitive substring, the SAFE variant `git push --force-with-lease` also
11
+ matched `git push --force` and got blocked — even though force-with-lease to one's
12
+ OWN feature/PR branch is the normal, expected way to update an amended/rebased
13
+ branch (it refuses to overwrite work the local clone hasn't seen, so it can't
14
+ silently clobber a collaborator).
15
+
16
+ This adds a narrow carve-out: `git push --force-with-lease` is allowed ONLY when it
17
+ does not explicitly target a protected branch (`main`/`master`/`develop`/`release*`).
18
+ Everything else stays exactly as strict — plain `--force`/`-f` (no lease), any
19
+ force-push to a protected branch, and all other risky patterns (`git reset --hard`,
20
+ `git clean -fd`, DB-destruction) remain blocked. The fix is applied to both
21
+ canonical deploy sources (`PostUpdateMigrator.getDangerousCommandGuard()` for
22
+ existing agents, always-overwritten on migration; and the `src/commands/init.ts`
23
+ inline copy for fresh init).
24
+
25
+ Fixed a Threadline continuity bug where a known peer's reply could be
26
+ false-isolated to a fresh cold-spawn thread, breaking agent-to-agent
27
+ conversation memory.
28
+
29
+ When a caller addressed a peer with the `name:fpPrefix` disambiguation syntax
30
+ (e.g. `Dawn-Workstation:8c7928aa`), `/threadline/relay-send` resolved the peer's
31
+ full fingerprint for routing but `captureOrigin` stored the raw composite string
32
+ as the conversation's `remoteAgent`. The inbound anti-hijack guard
33
+ (`ThreadlineRouter`) compares a reply's bare `senderFingerprint` (often with an
34
+ empty `senderName`) against the stored owner; a composite never equals a bare
35
+ fingerprint, so the guard treated the legitimate reply as a possible hijack and
36
+ shunted it into a new empty thread.
37
+
38
+ The fix stores the **resolved full fingerprint** as `remoteAgent` (and keeps the
39
+ typed label only as `remoteAgentDisplayName`), so the reply matches and resumes.
40
+ This mirrors `telegramBridge.mirrorOutbound`, which already stored the resolved
41
+ fingerprint as the owner. The anti-hijack guard is unchanged and not weakened: we
42
+ only store the full fingerprint the server itself resolved for routing — never a
43
+ guessed value and never a spoofable short prefix — so an impostor with a
44
+ different fingerprint is still isolated.
45
+
46
+ ## What to Tell Your User
47
+
48
+ Nothing proactive required — this is an internal developer-experience fix to the
49
+ safety guard. If a dev session previously got blocked trying to safely update its
50
+ own pull-request branch, that false alarm is gone; force-pushing to shared
51
+ branches like main is still blocked.
52
+
53
+ Agent-to-agent conversations that were addressed using the name-plus-shortcode
54
+ syntax now stay coherent across replies instead of occasionally restarting from
55
+ scratch as if the other agent had become a stranger. Nothing to turn on, and no
56
+ conversation history is lost.
57
+
58
+ ## Summary of New Capabilities
59
+
60
+ None user-facing — a safety-hook false-positive fix. Agents resolving their own PRs
61
+ (rebase/amend → safe force-with-lease push to a feature branch) are no longer
62
+ incorrectly blocked, with no loss of protection against genuinely-risky force-pushes.
63
+
64
+ | Capability | How to Use |
65
+ |-----------|-----------|
66
+ | Coherent replies for name:shortcode-addressed peers | Automatic. When you message a peer by the name-plus-fingerprint-prefix form, the conversation owner is now recorded as the peer's full resolved fingerprint, so their reply resumes the same thread instead of cold-spawning. |
67
+
68
+ ## Evidence
69
+
70
+ - **Reproduction:** Codey, mid-cycle on its own PR branch, ran a
71
+ `--force-with-lease` push to update an amended commit and was blocked by the
72
+ guard (framework-issue `52fa8663`, "force-push-guard-blocks-own-pr-amend").
73
+ Reproduced again live this session: a Telegram status message that merely
74
+ *quoted* the command string was itself blocked by the deployed hook.
75
+ - **Before:** `--force-with-lease` to a feature branch → BLOCKED (exit 2), matching
76
+ the `git push --force` substring pattern.
77
+ - **After:** `--force-with-lease` to a non-protected branch → ALLOWED; plain
78
+ `--force`/`-f`, force-to-`main`, and all other risky patterns → still BLOCKED.
79
+ - **Tests:** `tests/unit/dangerous-command-guard-force-with-lease.test.ts` — 10
80
+ tests, both content (carve-out present in both writers) and behavioral (runs the
81
+ actually-rendered migrator hook: ALLOW own/feature branch + no-branch + protected-
82
+ word-substring; BLOCK plain force, `-f`, force-to-main/master, and an unrelated
83
+ `git reset --hard`). All pass. The existing
84
+ `dangerous-command-guard-gh-pr-merge-gate.test.ts` suite re-run green (no
85
+ regression). `tsc --noEmit` clean.
86
+
87
+ Reproduced live as the 2026-06-04 Dawn cold-spawn incident (a reply isolated with
88
+ `unverified sender 8c7928aa9f04… owned by Dawn-Workstation; isolating`). New unit
89
+ test reproduces that exact log line for the composite-owner case and proves the
90
+ full-fingerprint-owner case resumes; new integration test asserts
91
+ `/threadline/relay-send` stores the resolved fingerprint. tsc clean; 56 related
92
+ tests green.
@@ -0,0 +1,103 @@
1
+ # Side-Effects Review — dangerous-command-guard allows force-with-lease to own branch
2
+
3
+ **Version / slug:** `force-with-lease-own-branch`
4
+ **Date:** `2026-06-04`
5
+ **Author:** `echo`
6
+ **Second-pass reviewer:** `required (safety-hook relaxation)`
7
+
8
+ ## Summary of the change
9
+
10
+ The `dangerous-command-guard.sh` PreToolUse hook blocks risky git commands. Its
11
+ risky-pattern list includes `"git push --force"` and `"git push -f"`. Because the
12
+ match is a case-insensitive substring, `git push --force-with-lease` ALSO matches
13
+ `git push --force` and gets blocked — even though `--force-with-lease` is the
14
+ *safe* force-push (it refuses to overwrite work the local clone hasn't seen).
15
+
16
+ This is a recurring friction: a dev session resolving its OWN PR (rebase/amend →
17
+ push) needs `--force-with-lease` to its feature/PR branch, and the guard blocks
18
+ it. Codey hit it mid-cycle (framework-issue `52fa8663`).
19
+
20
+ The fix adds a narrow carve-out BEFORE the risky-pattern loop:
21
+
22
+ ```
23
+ FORCE_WITH_LEASE_OWN_BRANCH=0
24
+ if <command contains "git push ... --force-with-lease">; then
25
+ if <command explicitly names a protected branch: main|master|develop|release*>; then
26
+ FORCE_WITH_LEASE_OWN_BRANCH=0 # keep blocking
27
+ else
28
+ FORCE_WITH_LEASE_OWN_BRANCH=1 # allow the safe case
29
+ fi
30
+ fi
31
+ ```
32
+
33
+ Inside the loop, when `FORCE_WITH_LEASE_OWN_BRANCH=1` and the matched pattern is a
34
+ force-push pattern, the loop `continue`s (skips the block). All OTHER risky
35
+ patterns (`git reset --hard`, `git clean -fd`, DB-destruction, plain `--force`)
36
+ are untouched.
37
+
38
+ The change is applied identically to BOTH canonical deploy sources —
39
+ `PostUpdateMigrator.getDangerousCommandGuard()` (existing agents, always-overwrite
40
+ on every migration) and the inline copy in `src/commands/init.ts` (fresh init) —
41
+ plus the vestigial reference copy at `src/templates/hooks/dangerous-command-guard.sh`
42
+ (not on any runtime deploy path, kept consistent to avoid a future stale-source trap).
43
+
44
+ ## Decision-point inventory
45
+
46
+ One decision point, two conditions:
47
+ 1. **Is it force-with-lease?** `git +push[^|;&]*--force-with-lease` (the safe variant).
48
+ If not present → no carve-out, normal blocking applies.
49
+ 2. **Does it explicitly target a protected branch?** A standalone `main`/`master`/
50
+ `develop`/`release*` token (word-boundary anchored: preceded by space/`:`/`/`/start,
51
+ followed by space/`:`/end). If yes → keep blocking. If no → allow.
52
+
53
+ ## 1. Over-block (what does this still block that it should?)
54
+
55
+ - **Plain `git push --force` / `git push -f`** (no `--force-with-lease`): still
56
+ blocked — `FORCE_WITH_LEASE_OWN_BRANCH` stays 0, the force pattern matches, no
57
+ carve-out. ✅ (test: "BLOCKS plain git push --force", "BLOCKS git push -f")
58
+ - **`--force-with-lease` to an explicit protected branch** (`... origin main`,
59
+ `... master`): still blocked — the protected-token regex sets the flag back to 0.
60
+ ✅ (tests: "BLOCKS force-with-lease that explicitly targets main/master")
61
+ - **Every non-force risky pattern** (`git reset --hard`, `git clean -fd`, DROP/
62
+ TRUNCATE/DELETE, `rm -rf .`): untouched — the carve-out only `continue`s for
63
+ patterns matching `git push (--force|-f)`. ✅ (test: "still BLOCKS git reset --hard")
64
+
65
+ ## 2. Under-block (what does it now allow that it didn't?)
66
+
67
+ ONLY `git push --force-with-lease` to a NON-protected branch (the agent's own
68
+ feature/PR branch). This is genuinely safe:
69
+ - `--force-with-lease` will not clobber commits the local ref hasn't observed (it
70
+ fails loudly if the remote moved underneath you), so it cannot silently destroy
71
+ a collaborator's work the way bare `--force` can.
72
+ - A feature/PR branch is not shared history; force-updating it after a rebase/amend
73
+ is the normal, expected workflow.
74
+
75
+ **Residual edge — `git push --force-with-lease` while checked out ON main with no
76
+ branch named** (the command string contains no protected token, so the flag is 1).
77
+ This is double-protected and not a real risk:
78
+ 1. Instar agents work in feature-branch worktrees per the Worktree Convention —
79
+ they are never checked out on main.
80
+ 2. `main` carries remote branch protection on the canonical repo, which rejects
81
+ any force-push regardless of what the local guard allows.
82
+ The local guard is defense-in-depth, not the only line; this edge requires
83
+ defeating BOTH the convention and remote branch protection.
84
+
85
+ ## 3. Blast radius
86
+
87
+ - `src/core/PostUpdateMigrator.ts` (`getDangerousCommandGuard()`) — redeploys the
88
+ hook to every agent on next migration (always-overwrite path, line ~1832). No
89
+ new migration method required: the existing always-overwrite is the delivery.
90
+ - `src/commands/init.ts` — fresh-init copy.
91
+ - `src/templates/hooks/dangerous-command-guard.sh` — vestigial reference, kept in sync.
92
+ - `tests/unit/dangerous-command-guard-force-with-lease.test.ts` — new (10 tests).
93
+ - No API, schema, config, or behavior change beyond the one guard decision.
94
+ - The pre-existing gh-pr-merge gate and coherence-gate blocks in the same hook are
95
+ untouched (regression test `dangerous-command-guard-gh-pr-merge-gate.test.ts`
96
+ re-run green).
97
+
98
+ ## 4. Reversibility
99
+
100
+ Fully reversible: revert the carve-out block in the three sources; the next
101
+ migration redeploys the prior hook. No state, no persisted format, no migration
102
+ flag. Verified: `tsc --noEmit` clean; 10/10 new tests pass; the existing guard
103
+ test suite stays green.
@@ -0,0 +1,93 @@
1
+ # Side-Effects Review — Canonicalize peer address to full fingerprint on send
2
+
3
+ **Version / slug:** `threadline-canonicalize-peer-address`
4
+ **Date:** `2026-06-04`
5
+ **Author:** `Echo (instar dev agent)`
6
+ **Tier:** `1 (targeted bug fix to existing route + tests; no new surface, no schema change)`
7
+ **Second-pass reviewer:** `recommended — SHARED Threadline security code (Dawn runs it too). PR is opened for Dawn's review before merge; NOT self-merged.`
8
+
9
+ ## Summary of the change
10
+
11
+ `/threadline/relay-send` resolves a peer's **full fingerprint** for routing
12
+ (`resolvedId` on the relay path; `localTarget.fingerprint` on the local path),
13
+ but `captureOrigin` stored the raw caller-supplied target (which may be a
14
+ `name:fpPrefix` composite like `Dawn-Workstation:8c7928aa`) as the conversation's
15
+ `remoteAgent`. The inbound anti-hijack guard (`ThreadlineRouter`) compares a
16
+ reply's `senderFingerprint` (the bare full fingerprint, often with empty
17
+ `senderName`) against the stored `remoteAgent`; a composite/display string never
18
+ equals a bare fingerprint, so a known peer's reply was false-isolated to a fresh
19
+ cold-spawn thread — breaking A2A continuity (the "one coherent individual"
20
+ property). Observed live as the 2026-06-04 Dawn cold-spawn incident.
21
+
22
+ Fix: `captureOrigin(effThreadId, canonicalRemoteAgent, displayName?)` now stores
23
+ the **resolved full fingerprint** as `remoteAgent` and keeps the raw target only
24
+ as `remoteAgentDisplayName`. The two call-sites pass the already-resolved
25
+ fingerprint (relay: `resolvedId`; local: `localTarget.fingerprint ||
26
+ publicKey.slice(0,32) || name`). Mirrors `telegramBridge.mirrorOutbound`, which
27
+ already stored `{ remoteAgent: resolvedId, remoteAgentName: targetAgent }`.
28
+
29
+ Files: `src/server/routes.ts` (captureOrigin signature + 2 call-sites),
30
+ `tests/unit/ThreadlineRouter-anti-hijack.test.ts` (+2 guard tests),
31
+ `tests/integration/threadline/relay-send-canonical-remoteagent.test.ts` (new).
32
+
33
+ ## 1. Over-block / weakening
34
+
35
+ Does it weaken the guard? No. We only ever store the **full** fingerprint the
36
+ server itself resolved for routing — never a guessed value and never an
37
+ 8-char prefix (prefix-matching would be a ~32-bit, grind-able hijack vector and
38
+ is explicitly NOT used). An impostor presenting a different fingerprint still
39
+ fails `peer === inboundFp` and is still isolated (proved by the retained
40
+ "isolates an unverified sender" test + the new composite-isolation test). The
41
+ change strictly *adds* correct matches for a genuinely-resolved peer; it removes
42
+ no isolation.
43
+
44
+ ## 2. Under-block
45
+
46
+ Does it miss a case it should catch? No new bypass: the stored owner is now a
47
+ stronger identity (full fingerprint) than before (a spoofable display string).
48
+ Existing threads already persisted with a composite owner are NOT retroactively
49
+ rewritten — this is a forward fix at the write path; the one historically-broken
50
+ thread is a one-off. The deeper R2 question (relay inbound `trust.kind` is
51
+ hardcoded `plaintext-tofu`, so a verified peer is never crypto-exempted) is
52
+ intentionally OUT OF SCOPE here and left for security-design review.
53
+
54
+ ## 3. Level-of-abstraction fit
55
+
56
+ The canonicalization lives at the send/record boundary (`captureOrigin`), the
57
+ same place that already owns the resolved-fingerprint vs display-name split for
58
+ the Telegram mirror. The guard is untouched (no change to ThreadlineRouter).
59
+
60
+ ## 4. Signal vs authority compliance
61
+
62
+ No gate/authority change. `remoteAgentDisplayName` preserves the human-facing
63
+ label for all display strings (`captureOriginOnSend` already falls back to
64
+ `remoteAgentDisplayName ?? remoteAgent`).
65
+
66
+ ## 5. Interactions
67
+
68
+ - The anti-hijack guard now matches a known peer's bare-fingerprint reply
69
+ (`peer === inboundFp`) and resumes instead of cold-spawning.
70
+ - `appendCanonicalOutboxEntry` and `mirrorOutbound` already used `resolvedId`;
71
+ this aligns `captureOrigin` with them (consistency, not a new direction).
72
+ - `captureOrigin` only fires when `originTopicId` is set (origin capture);
73
+ topic-less sends are unaffected (their owner is written by the inbound handler
74
+ as `message.from.agent = senderFingerprint`, already a full fingerprint).
75
+
76
+ ## 6. External surfaces
77
+
78
+ No new HTTP route, no config, no template/CLAUDE.md change, no persisted-format
79
+ change (the `remoteAgent` field already existed; only the VALUE stored is now the
80
+ canonical fingerprint). Nothing to migrate.
81
+
82
+ ## 7. Rollback cost
83
+
84
+ Low — revert one function signature + two call-sites + two test files. No
85
+ data-format change; existing records are untouched.
86
+
87
+ ## Conclusion
88
+
89
+ Low-risk, strictly non-weakening, additive correctness fix at the send/record
90
+ boundary that closes the composite-address false-isolation (the Dawn cold-spawn
91
+ incident). Guard unchanged; impostors still isolated; only genuinely-resolved
92
+ peers now resume. Shipped behind a PR for Dawn's review (shared security code);
93
+ the deeper R2 trust-propagation question is logged separately, not touched here.
@@ -0,0 +1,58 @@
1
+ # ELI16 — Store a peer's real fingerprint as the conversation owner
2
+
3
+ ## The problem, in plain English
4
+
5
+ When one agent messages another over Threadline, you can address the other
6
+ agent by a friendly "name plus a short code" label — for example
7
+ `Dawn-Workstation:8c7928aa`. The `8c7928aa` part is just the first 8 characters
8
+ of the other agent's long cryptographic fingerprint, used to tell apart two
9
+ agents that happen to share a name.
10
+
11
+ Here's the bug. When I sent the first message, my server looked up the *full*
12
+ fingerprint (`8c7928aa9f04fbda947172a2f9b2d81a`) so it could actually route and
13
+ encrypt the message to the right agent — but then, when it wrote down "who owns
14
+ this conversation," it saved the raw label I typed (`Dawn-Workstation:8c7928aa`)
15
+ instead of that resolved full fingerprint.
16
+
17
+ That mattered because of a security guard. Threadline has an anti-hijack check:
18
+ when a reply comes in claiming to belong to an existing conversation, the guard
19
+ asks "is the sender actually the agent who owns this thread?" A genuine reply
20
+ arrives carrying the sender's *full* fingerprint (and often no display name at
21
+ all). The guard compared the stored owner (`Dawn-Workstation:8c7928aa`, a label)
22
+ against the reply's bare full fingerprint (`8c7928aa9f04…`), saw they were not
23
+ equal, and concluded the reply might be an impostor. So it shunted my friend's
24
+ perfectly legitimate reply into a brand-new, empty conversation — and the thread
25
+ lost all its memory. It felt like the other agent had become a stranger
26
+ mid-conversation. This is the "fragmented identity" incoherence: a known peer's
27
+ own replies being treated as someone else's.
28
+
29
+ ## The fix
30
+
31
+ The server *already* resolves the peer's full fingerprint when it sends the
32
+ message (it has to, in order to route and encrypt). The fix is simply: store
33
+ *that* resolved full fingerprint as the conversation's owner, and keep the label
34
+ I typed only as a separate, human-friendly display name. Now when the reply
35
+ comes back carrying the full fingerprint, it matches the stored owner exactly,
36
+ the guard says "yes, this is the same agent," and the conversation continues
37
+ normally instead of cold-starting.
38
+
39
+ This follows a pattern the code already uses elsewhere: the Telegram mirror for
40
+ outbound messages already stored the resolved fingerprint as the owner and the
41
+ typed name as the display name. Only the "capture origin" write had been using
42
+ the raw label. We brought it in line.
43
+
44
+ ## Why it's safe
45
+
46
+ The guard is not weakened at all. We never guess, and we never trust a short
47
+ 8-character prefix as proof of identity (that would be too easy to fake). We
48
+ only store the *full* fingerprint that the server itself resolved for routing.
49
+ An impostor presenting a *different* fingerprint still fails the match and still
50
+ gets isolated — exactly as before. The only thing that changes is that a real,
51
+ known peer's reply now correctly resumes its own conversation instead of being
52
+ mistaken for a stranger.
53
+
54
+ ## What you'd notice
55
+
56
+ Almost nothing — that's the point. Agent-to-agent conversations that were
57
+ addressed with the `name:shortcode` syntax will simply stay coherent across
58
+ replies instead of occasionally restarting as if from scratch.