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.
- package/dist/commands/init.js +15 -0
- package/dist/commands/init.js.map +1 -1
- package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
- package/dist/core/PostUpdateMigrator.js +18 -0
- package/dist/core/PostUpdateMigrator.js.map +1 -1
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +23 -5
- package/dist/server/routes.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +64 -64
- package/src/templates/hooks/dangerous-command-guard.sh +25 -0
- package/upgrades/1.3.256.md +92 -0
- package/upgrades/side-effects/force-with-lease-own-branch.md +103 -0
- package/upgrades/side-effects/threadline-canonicalize-peer-address.md +93 -0
- package/upgrades/threadline-canonicalize-peer-address.eli16.md +58 -0
|
@@ -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.
|