Skip to content

feat(hub): friend-accept creates contact row and peer-link handshake#2043

Open
hognek wants to merge 6 commits into
jaylfc:devfrom
hognek:feat/collab-a2-friend-accept
Open

feat(hub): friend-accept creates contact row and peer-link handshake#2043
hognek wants to merge 6 commits into
jaylfc:devfrom
hognek:feat/collab-a2-friend-accept

Conversation

@hognek

@hognek hognek commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

On friend-accept, create the contact row (pin Ed25519/X25519 pubkeys), mint the inbound peer token, exchange endpoints, and record the peer_link. Subscribe to friend block/revoke → cascade.

Changes

  • tinyagentos/routes/hub.py: Added _try_handshake() helper that extracts pubkeys from the directory response (with hub_authors fallback), creates a contact row, mints an inbound token, and establishes a peer link. Called from accept_friend_request after the hub relationship is recorded. block_peer now cascades to contacts_store.revoke_peer_link().
  • tests/test_collab_a2_handshake.py: 8 new integration tests covering the full accept→contact+link flow and block→revoke cascade.

Design decisions

  • Handshake is best-effort — failures log but never block the accept response (contact creation is a side-effect)
  • When directory omits pubkeys, falls back to hub_authors table (populated during friend-request flow)
  • Outbound token stored as empty placeholder — the real outbound token comes in A3's handshake reply
  • Block cascade resolves fingerprint→username via hub_authors cache

Tests

  • 8/8 new tests pass
  • 37/37 existing test_contacts_peer.py tests pass (no regressions)
  • Security scan: clean (no hardcoded secrets, no SQL injection, no eval/exec)

Part of #2012 (cross-user collaboration), milestone A2.
Closes #2014.

Summary by CodeRabbit

  • New Features
    • Accepting a friend request now immediately creates or refreshes the local peer connection when identity and endpoint details are available.
    • Peer endpoint data is normalized so it’s handled consistently across accepted formats.
    • Blocking a peer now also revokes the related local contact/peer connection.
  • Bug Fixes
    • Friend acceptance succeeds even when optional contact storage or directory identity details are missing.
    • Endpoint handling tolerates non-list representations, and re-accepting refreshes connection state.
  • Tests
    • Added async end-to-end coverage for friend-accept handshake scenarios and block cascade behavior.

@hognek
hognek marked this pull request as ready for review July 19, 2026 11:36
@coderabbitai

coderabbitai Bot commented Jul 19, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@hognek, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 45 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 5ab4f1cc-b4e7-44d0-9cb8-f0245a836ea3

📥 Commits

Reviewing files that changed from the base of the PR and between 0254a6e and 948dbad.

📒 Files selected for processing (4)
  • tests/test_collab_a2_handshake.py
  • tests/test_store_upgrades.py
  • tinyagentos/contacts_store.py
  • tinyagentos/routes/hub.py
📝 Walkthrough

Walkthrough

Friend acceptance resolves peer keys, creates or refreshes local contacts and peer links, and tolerates missing handshake data or storage. Blocking additionally revokes corresponding local peer links and contacts when available.

Changes

Collab A2 peer-link lifecycle

Layer / File(s) Summary
Accept handshake flow
.gitignore, tinyagentos/routes/hub.py, tests/test_collab_a2_handshake.py
Friend acceptance creates or refreshes contacts and inbound peer links, resolves missing pubkeys from hub_authors, normalizes endpoints, and remains successful when handshake data or contact storage is unavailable.
Block and revoke cascade
tinyagentos/routes/hub.py, tests/test_collab_a2_handshake.py
Blocking revokes the matching peer link and contact status when contact storage exists, while preserving successful blocking without it.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

  • jaylfc/taOS#1810 — Extends the slice-3 hub relationship and request-flow code paths.
  • jaylfc/taOS#2025 — Introduces ContactsStore peer-link infrastructure used by these flows.
  • jaylfc/taOS#2032 — Covers peer-link persistence and token-handling behavior coupled to this logic.

Suggested reviewers: jaylfc

Sequence Diagram(s)

Friend acceptance handshake

sequenceDiagram
  participant Client
  participant accept_friend_request
  participant Directory
  participant ContactsStore
  Client->>accept_friend_request: accept friend request
  accept_friend_request->>Directory: use directory response and resolve peer keys
  accept_friend_request->>ContactsStore: create or refresh contact and inbound peer link
  ContactsStore-->>accept_friend_request: persist handshake state
  accept_friend_request-->>Client: accepted
Loading

Block cascade

sequenceDiagram
  participant Client
  participant block_peer
  participant Directory
  participant ContactsStore
  Client->>block_peer: block peer
  block_peer->>Directory: revoke upstream edge
  block_peer->>ContactsStore: revoke local peer link and contact
  ContactsStore-->>block_peer: update revoked state
  block_peer-->>Client: blocked
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The .gitignore update for data/hub/ is unrelated to the A2 handshake scope. Remove the .gitignore change or justify it as required for this PR's handshake work.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main hub friend-accept handshake change.
Linked Issues check ✅ Passed The PR appears to satisfy #2014 by creating contact rows, minting inbound tokens, exchanging endpoints, and cascading block revokes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@qodo-code-review

Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

1 similar comment
@qodo-code-review

Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@gitar-bot

gitar-bot Bot commented Jul 19, 2026

Copy link
Copy Markdown

Gitar is working

Gitar

Comment thread tinyagentos/routes/hub.py
)

# Mint the inbound token WE give to the remote instance.
inbound_token = generate_peer_token()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: Inbound token is minted and stored (hashed) but never delivered to the remote peer

inbound_token = generate_peer_token() is created and persisted via establish_peer_link, but the plaintext is discarded immediately and _try_handshake never returns it or forwards it to the peer (the resp dict passed to _try_handshake is never mutated). The remote instance therefore never learns the token it must present on POST /api/peer/*, so the peer link is non-functional for inbound authentication until a future A3 step. Given this is a security-sensitive auth channel, the handshake should either (a) return the token to the caller so it can be exchanged, or (b) clearly document that A2 intentionally leaves the channel inert and that no peer can authenticate inbound yet. As written, any code path that trusts inbound_token_hash for auth (e.g. find_contact_by_inbound_token) will never match a real remote request.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

Comment thread tinyagentos/routes/hub.py
try:
author = await store.get_author(peer)
if author and author.get("username"):
await contacts_store.revoke_peer_link(f"hub:{author['username']}")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: Block cascade silently no-ops when the author is absent from hub_authors

The cascade resolves peer → username via store.get_author(peer) (the hub_authors cache). If the contact was created purely from directory pubkeys and the author row was never cached (or was pruned), author is None and the if author and author.get("username") guard skips the revoke entirely. The result: an active peer link remains that can still authenticate on the peer channel after the user blocked the peer. Since block_peer is a strong local-first safety operation, an unrevoked active link is a security gap. Consider falling back to contacts_store.get_contact_by_username/iterating contacts keyed by fingerprint, or at minimum logging a warning when no author is found so the silent skip is observable.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

Comment thread tinyagentos/routes/hub.py
if contacts_store is None:
return

username = directory_resp.get("username") or directory_resp.get("target") or ""

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: contact_id is derived from the untrusted directory username

username = directory_resp.get("username") or directory_resp.get("target") comes straight from the remote directory response, then contact_id = f"hub:{username}" is used as the TOFU key-pinning primary key. A peer controls its own username, so a single fingerprint could be pinned under different contact_id values if the name changes between the request and accept flows, fragmenting or shadowing contacts. Binding the contact to the verified peer_fingerprint (e.g. hub:{peer_fingerprint}) and storing username only as a display/lookup column would be more robust against name churn and confusion. Not blocking given the design, but worth considering before this becomes load-bearing.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

Comment thread tests/test_collab_a2_handshake.py Outdated
inbound_contact = await store.find_contact_by_inbound_token(
# Generate a new token and use its hash — we can't read the stored plaintext
# but we can verify the hash is deterministic.
"placeholder-not-testable-directly"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: Dead code / unused variable left in the test

inbound_contact = await store.find_contact_by_inbound_token(
    "placeholder-not-testable-directly"
)
# Actually, we should test the token flow differently.
# Let's just verify the link exists and the hash is consistent.

inbound_contact is assigned but never asserted/used, and the "placeholder-not-testable-directly" input is just an arbitrary string that always returns None. This reads like an abandoned attempt to test the token flow. Either remove it, or replace it with a real assertion — e.g. mint a token, call establish_peer_link with it, then assert find_contact_by_inbound_token(token) returns the contact, proving the hash lookup actually works.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

@kilo-code-bot

kilo-code-bot Bot commented Jul 19, 2026

Copy link
Copy Markdown

Code Review Summary

Status: 2 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 2
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
tinyagentos/routes/hub.py 130 contact_id derived from untrusted directory username (TOFU key-pin bound to peer-controllable name; shadow-contact risk). Documented as accepted in-code; not fixed.
tinyagentos/routes/hub.py 191 Inbound peer token is minted and stored (hashed) but never delivered to the remote peer; find_contact_by_inbound_token can never match, so the link is inert for inbound auth until A3. Documented as intentional.
Files Reviewed (5 files)
  • tinyagentos/routes/hub.py - 2 carried-forward issues (unchanged in incremental diff)
  • tinyagentos/contacts_store.py - 0 new issues (PATCH 6 migration fix verified)
  • tests/test_collab_a2_handshake.py - 0 new issues
  • tests/test_store_upgrades.py - 0 new issues (new upgrade tests)
  • .gitignore - 0 issues

Resolved since previous review (incremental PATCH 6, commit 948dbad)

  • RESOLVED Block cascade silent no-op (hub.py:524): block_peer now falls back to contacts_store.get_contact_by_fingerprint(peer) when the hub_authors author row is missing/stale, so an active peer link is revoked on block. Closes the prior security gap.
  • RESOLVED CodeRabbit CRITICAL: identity.fingerprint() / hub_authors lookup crash on malformed directory hex — the try/except in _try_handshake now wraps the validation path (PATCH 5).
  • RESOLVED CodeRabbit "block cascade only via hub_authors" (hub.py:538) and the fingerprint-vs-pubkey dead-code fallback (hub.py:535) — replaced by the live peer_fingerprint column + lookup.
  • RESOLVED PATCH 6 migration no-op blocker: MIGRATIONS entry replaced with a guarded _post_init that PRAGMA table_info checks and ALTER TABLE ADD COLUMN only when peer_fingerprint is absent. Fresh DBs get the column from SCHEMA; upgraded DBs get it from _post_init. Two upgrade tests added in test_store_upgrades.py.
  • RESOLVED HubStore connection leak in tests (CodeRabbit Minor) — both upsert_author sites wrapped in try/finally with close().

Carried-forward open issues (unchanged by this incremental diff)

  • contact_id from untrusted username (line 130) — WARNING; in-code NOTE added accepting the risk; follow-up issue filed for fingerprint-keyed contact IDs.
  • Inbound token never delivered (line 191) — WARNING; documented as A3-completes-exchange; tracking issue filed.

Incremental review scope

Only PATCH 6 (commit 948dbad, since 4b5903b) was within scope: contacts_store.py (_post_init migration replacement), tests/test_collab_a2_handshake.py (re-upsert now asserts revocation cleared), tests/test_store_upgrades.py (new ContactsStore upgrade tests). No new bugs found in the incremental changes. hub.py was not modified in PATCH 6, so its carried-forward findings are reported without change.

Previous Review Summaries (3 snapshots, latest commit 4b5903b)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit 4b5903b)

Status: 3 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 3
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
tinyagentos/routes/hub.py 130 contact_id derived from untrusted directory username (TOFU key-pin bound to peer-controllable name; shadow-contact risk). Only a NOTE was added since last review — still not fixed.
tinyagentos/routes/hub.py 189 Inbound peer token is minted and stored (hashed) but never delivered to the remote peer; find_contact_by_inbound_token can never match, so the link is inert for inbound auth.
tinyagentos/routes/hub.py 528 Block cascade silently no-ops when the author row is absent from hub_authors (and the prior contact-scan fallback was removed). An active peer link can remain authenticated after a block — a security gap.
Files Reviewed (3 files)
  • tinyagentos/routes/hub.py - 3 issues (WARNING)
  • tests/test_collab_a2_handshake.py - 0 issues (prior dead-code finding resolved)
  • data/hub/identity.json - removed from PR (CRITICAL resolved: committed private keys no longer present)

Resolved since previous review (commit a0a07fd+)

  • CRITICAL data/hub/identity.json: committed plaintext private keys — file removed from the branch.
  • SUGGESTION hub.py block-fallback dead code (pubkey vs fingerprint): fallback scan deleted.
  • SUGGESTION test dead code (find_contact_by_inbound_token placeholder): removed.
  • MAJOR fingerprint mismatch in _try_handshake: addressed by new identity.fingerprint(ed25519_pub) != peer_fingerprint guard (correct & verified).
  • MINOR leaked HubStore connections in tests: addressed with try/finally hub_store.close().

Carried-forward open issues (unchanged by this incremental diff)

  • contact_id from untrusted username (line 130) — still a WARNING; only a comment was added.
  • Inbound token never delivered (line 189) — still a WARNING.
  • Block cascade silent skip leaves active link (line ~528; prior comment anchored at 522) — still a WARNING; the removed fallback makes this gap worse.

No NEW issues were introduced by the incremental changes — the added fingerprint guard and try/finally cleanups are correct.

Previous review (commit 0254a6e)

Status: 3 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 3
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
tinyagentos/routes/hub.py 130 contact_id derived from untrusted directory username (TOFU key-pin bound to peer-controllable name; shadow-contact risk). Only a NOTE was added since last review — still not fixed.
tinyagentos/routes/hub.py 189 Inbound peer token is minted and stored (hashed) but never delivered to the remote peer; find_contact_by_inbound_token can never match, so the link is inert for inbound auth.
tinyagentos/routes/hub.py 528 Block cascade silently no-ops when the author row is absent from hub_authors (and the prior contact-scan fallback was removed). An active peer link can remain authenticated after a block — a security gap.
Files Reviewed (3 files)
  • tinyagentos/routes/hub.py - 3 issues (WARNING)
  • tests/test_collab_a2_handshake.py - 0 issues (prior dead-code finding resolved)
  • data/hub/identity.json - removed from PR (CRITICAL resolved: committed private keys no longer present)

Resolved since previous review (commit a0a07fd+)

  • CRITICAL data/hub/identity.json: committed plaintext private keys — file removed from the branch.
  • SUGGESTION hub.py block-fallback dead code (pubkey vs fingerprint): fallback scan deleted.
  • SUGGESTION test dead code (find_contact_by_inbound_token placeholder): removed.
  • MAJOR fingerprint mismatch in _try_handshake: addressed by new identity.fingerprint(ed25519_pub) != peer_fingerprint guard (correct & verified).
  • MINOR leaked HubStore connections in tests: addressed with try/finally hub_store.close().

Carried-forward open issues (unchanged by this incremental diff)

  • contact_id from untrusted username (line 130) — still a WARNING; only a comment was added.
  • Inbound token never delivered (line 189) — still a WARNING.
  • Block cascade silent skip leaves active link (line ~528; prior comment anchored at 522) — still a WARNING; the removed fallback makes this gap worse.

No NEW issues were introduced by the incremental changes — the added fingerprint guard and try/finally cleanups are correct.

Previous review (commit 8feef8d)

Status: 4 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 1
WARNING 1
SUGGESTION 2
Issue Details (click to expand)

CRITICAL

File Line Issue
data/hub/identity.json 1 Committed plaintext private signing + encryption keys (credential leak, tracked in git, persists in history)

WARNING

File Line Issue
tinyagentos/routes/hub.py 124 contact_id derived from untrusted directory username — TOFU key pin bound to attacker-influenceable name; enables contact collision/impersonation

SUGGESTION

File Line Issue
tinyagentos/routes/hub.py 509 Block fallback matches ed25519_pub == peer (pubkey vs fingerprint) — never matches; PATCH 2/2 fallback is effectively dead code
tinyagentos/routes/hub.py 171 Peer link created in inert state (token never delivered, outbound empty) — distinguishable pending state advised
Files Reviewed (3 files)
  • data/hub/identity.json - 1 issue (CRITICAL)
  • tinyagentos/routes/hub.py - 3 issues (1 WARNING, 2 SUGGESTION)
  • tests/test_collab_a2_handshake.py - 0 issues (prior dead-code finding resolved in PATCH 2/2)

Fix these issues in Kilo Cloud


Reviewed by hy3:free · Input: 135.5K · Output: 9.4K · Cached: 462.9K

@hognek
hognek force-pushed the feat/collab-a2-friend-accept branch from df2155e to fb637cf Compare July 19, 2026 23:31
Comment thread data/hub/identity.json Outdated
@@ -0,0 +1 @@
{"signing_private": "9af62cb1e50222bd57e894e3dab5f9444851f0309224c0b37bf45ed21143d178", "signing_public": "9ae572677819955cf2aace721129e8a1d393d21ecc47ef964db8c3f0ff3b98da", "encryption_private": "c02cc57f5a4940700d9a80dd4578467e63308a251bf643f26c13c3f049670662", "encryption_public": "a1ac687647b24884912035b4c67ae749f136477a1bb031d24817a66236717005", "created_at": 1784503810.4560094} No newline at end of file

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CRITICAL: Committed private signing + encryption keys in plaintext

This file contains real private key material (signing_private, encryption_private) and is tracked by git (not in .gitignore). Private keys must NEVER be committed — once pushed they persist in history and are compromised. Rotate these keys immediately and remove the file from the branch and history (e.g. git filter-repo / BFG). The identity is generated at runtime (0600) by tinyagentos/hub/identity.py; do not seed it via a committed file.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

Comment thread tinyagentos/routes/hub.py
# Can't form a contact_id without a username.
return

contact_id = f"hub:{username}"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: contact_id derived from untrusted directory username enables TOFU key-pinning confusion

contact_id = f"hub:{username}" uses username taken from the directory response (directory_resp.get("username") or ...get("target")), which is attacker-influenceable. A malicious or compromised directory can pin the peer's public keys under an arbitrary/colliding hub:<name>, colliding with an existing local contact or impersonating a different user. The trust-on-first-use pin should be keyed by the verified peer fingerprint (hub:{peer_fingerprint}), which is already available, not a directory-supplied display name. This also roots the dead block-fallback below (see line 509).


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

Comment thread tinyagentos/routes/hub.py Outdated
# Fallback: scan contacts for a matching ed25519 fingerprint.
all_contacts = await contacts_store.list_contacts()
for contact in all_contacts:
if contact.get("ed25519_pub") == peer:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: Block fallback compares pubkey to fingerprint — effectively dead code

The fallback scans contacts and matches contact.get("ed25519_pub") == peer, but peer is the signing fingerprint (a hash, e.g. deadbeef…) whereas ed25519_pub is the raw public key (ab…). The contacts table has no fingerprint column (see contacts_store.py:14). These encodings never match, so this branch always falls through to the "could not resolve" warning — the exact no-op the PATCH 2/2 fallback was meant to fix. Store the peer fingerprint on the contact (e.g. a peer_fingerprint column) and match on that, or resolve via hub_authors before scanning.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

Comment thread tinyagentos/routes/hub.py
# outbound token) completes the two-way exchange. Until then, the
# inbound auth channel is inert (no remote request will carry this
# token) and find_contact_by_inbound_token() will never match.
inbound_token = generate_peer_token()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: Peer link created in a non-functional/inert state

The inbound token is minted and stored but never delivered to the remote peer (no exchange channel yet), and outbound_token is an empty placeholder — so find_contact_by_inbound_token() can never match and the link cannot authenticate inbound requests until A3. The row existing in active/established state may be mistaken for a ready channel by future code (or monitoring). Consider tracking an explicit handshake state (e.g. pending/awaiting_reply) so the inert row is distinguishable from a live peer link.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@data/hub/identity.json`:
- Line 1: Remove the committed runtime identity artifact at
data/hub/identity.json, add data/hub/*.json (or the equivalent specific ignore
rule) to .gitignore, and treat the exposed signing and encryption keys as
compromised by rotating them if they were used against a real hub.

In `@tests/test_collab_a2_handshake.py`:
- Around line 225-235: Close each manually created HubStore after its
upsert_author call completes to prevent leaked database connections: add the
cleanup at tests/test_collab_a2_handshake.py lines 225-235 and 398-408,
preferably using try/finally so HubStore.close runs even if upsert_author fails.

In `@tinyagentos/routes/hub.py`:
- Around line 499-520: The fallback scan in the hub block flow should compare
the peer signing-key fingerprint with a fingerprint derived from each contact’s
ed25519_pub, not the raw public-key value. Update the loop around
contacts_store.list_contacts and revoke_peer_link to use the existing
fingerprint derivation utility, preserving the matching contact_id revocation
behavior, and add coverage for the missing or stale hub_authors fallback path.
- Around line 119-142: Before saving contact keys in _try_handshake, compute the
fingerprint of ed25519_pub with identity.fingerprint and compare it to
peer_fingerprint. If they differ, log a warning identifying the contact and skip
the handshake; only continue to add the contact when the fingerprints match.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: d4d3c999-8a91-4629-b44b-730514716980

📥 Commits

Reviewing files that changed from the base of the PR and between c801a22 and 8feef8d.

📒 Files selected for processing (3)
  • data/hub/identity.json
  • tests/test_collab_a2_handshake.py
  • tinyagentos/routes/hub.py

Comment thread data/hub/identity.json Outdated
Comment thread tests/test_collab_a2_handshake.py Outdated
Comment thread tinyagentos/routes/hub.py Outdated
Comment thread tinyagentos/routes/hub.py
@jaylfc

jaylfc commented Jul 20, 2026

Copy link
Copy Markdown
Owner

Good use of the supersede convention on #2046, but the survivor is missing half the slice. #2046 carried tinyagentos/peer.py (the send_handshake sender side); this PR only has the hub.py receive side plus tests. Before this merges as THE A2 slice:

  1. Where does send_handshake land now? If it is a follow-up PR, say so here and link it; if it was meant to be in this branch, it is missing. A2 is not complete without the sender.
  2. Carrying over the feat(collab): friend-accept creates contact row + peer link + handshake #2046 fold list, which still applies wherever that code lands:
    a. Block cascade must resolve the peer link via the pinned fingerprint on the CONTACT row, not via hub_authors (which may have no row in the normal flow) - plus a test blocking a never-upserted peer.
    b. send_handshake must thread the app data_dir into resolve_local_identity_id() instead of falling back to env.
    c. Confirm the handshake envelope carrying inbound_token rides the X25519-sealed path (assert or test the wire form), or seal it.
  3. Why does this PR now touch .gitignore? That looks unrelated to A2 - drop it or justify it (pitfall: every changed line traces to the slice).

CI is green and the PR is mergeable, but I am holding until 1 and 3 are answered and the applicable parts of 2 are folded or explicitly relocated.

hognek added 4 commits July 20, 2026 15:10
On friend-accept:
- Extract peer Ed25519/X25519 pubkeys from directory response
- Fall back to hub_authors cache when directory omits pubkeys
- Create contact row (trust-on-first-use key pinning)
- Mint inbound peer token (hashed at rest)
- Establish peer link with advertised endpoints
- Handshake is best-effort — failures never block the accept

On block:
- Cascade to contacts_store.revoke_peer_link()
- Resolve fingerprint->username via hub_authors cache

Tests: 8/8 pass (contact creation, pubkey fallback, no-pubkey skip,
endpoint parsing, re-upsert, missing-store guard, block cascade,
block cascade missing-store). Existing 37 contacts_peer tests
unaffected.

Part of jaylfc#2012 (cross-user collaboration), milestone A2.
Closes jaylfc#2014.
…doc, dead test code

- WARNING: block cascade now falls back to contact-table scan when
  hub_authors cache is missing, with explicit log warning on failure
- WARNING: document that A2 intentionally stores inbound token locally
  without delivering it (A3 completes the exchange)
- SUGGESTION: remove dead test code (placeholder token lookup)
…allback, doc contact_id

- CRITICAL: remove committed data/hub/identity.json (test-generated keys) and
  add data/hub/ to .gitignore
- WARNING: document that contact_id is derived from untrusted directory username
  (TOFU key-pinning bound to peer-controllable name) with future direction
- SUGGESTION: remove broken fingerprint-vs-pubkey fallback in block cascade
  (peer fingerprint != ed25519_pub key — comparison would never match)
…erification

- SUGGESTION: wrap HubStore init/upsert in try/finally with close()
  in both test_collab_a2_handshake.py locations to prevent leaked
  database connections
- SUGGESTION: verify directory-supplied ed25519_pub fingerprint matches
  expected peer_fingerprint in _try_handshake; skip handshake on
  mismatch to avoid pinning TOFU keys from an imposter
- Update _PEER_FP test constant to actual fingerprint of
  _PEER_SIGNING_PUB so the new fingerprint check passes consistently

Tests: 103/103 pass (collab A2 handshake + hub + contacts peer)
@hognek
hognek force-pushed the feat/collab-a2-friend-accept branch from e1076db to 0254a6e Compare July 20, 2026 13:24

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (2)
tinyagentos/routes/hub.py (1)

136-139: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Minor: local variable hub_store shadows the module-level hub_store import.

hub_store = await _get_store(request) rebinds the name also used as a module reference elsewhere in this file (e.g. line 93's hub_store.HubStore(...)). Not a bug today (not reused after), but risks confusion if the function grows. Consider naming it store for consistency with block_peer's usage.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tinyagentos/routes/hub.py` around lines 136 - 139, The fallback branch in the
relevant route shadows the module-level hub_store import. Rename the local
result of _get_store(request) to store and update the subsequent get_author
call, matching block_peer’s naming without changing behavior.
tests/test_collab_a2_handshake.py (1)

307-346: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Revocation-clearing assertion doesn't test revocation-clearing.

The comment claims revoked_at is None proves "re-establish clears revocation" (Line 346), but the link is never revoked between the two accept calls — revoked_at was already None beforehand, so this passes regardless of whether re-establish actually clears revocation.

🧪 Suggested fix to actually exercise the clearing behavior
         store = app_with_contacts.state.contacts_store
         first_link = await store.get_peer_link(f"hub:{_PEER_USERNAME}")
         first_established = first_link["established_at"]
 
+        # Simulate a prior revocation to verify re-establish actually clears it.
+        await store.revoke_peer_link(f"hub:{_PEER_USERNAME}")
+        revoked_link = await store.get_peer_link(f"hub:{_PEER_USERNAME}")
+        assert revoked_link["revoked_at"] is not None
+
         # Second accept with different endpoints — should update
         dir_resp_body["endpoints"] = ["https://second.example.com:6969"]

(Adjust the revoke call to whatever method contacts_store actually exposes.)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_collab_a2_handshake.py` around lines 307 - 346, Update
test_accept_reupsert_contact to revoke the stored peer link after the first
accept and before the second accept, using the contacts_store revocation method
exposed by the implementation. Assert the link is revoked before re-accepting,
then retain the existing second-accept assertions to verify re-establishment
clears revoked_at and refreshes the endpoints.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/test_collab_a2_handshake.py`:
- Around line 381-458: Add a test alongside
test_block_cascades_to_contacts_store that creates the contact and peer link but
omits the HubStore.upsert_author setup, then posts to /api/hub/friends/block
using the peer fingerprint. Assert the request succeeds with blocked state and
verify get_peer_link reports revoked_at and get_contact reports status
"revoked", covering the handler’s fallback username resolution.

In `@tinyagentos/routes/hub.py`:
- Around line 511-533: Update the block cascade around contacts_store and
revoke_peer_link to resolve the blocked peer from a signing-fingerprint value
persisted on the contacts row, rather than relying solely on store.get_author
and hub_authors. Add the required contacts-store schema, persistence, and lookup
support, revoke the matching peer link even when no hub_authors entry exists,
and add coverage for blocking a peer that was never upserted as a contact.
- Around line 133-160: Widen the exception boundary in _try_handshake to cover
the hub_authors lookup, public-key validation, and identity.fingerprint check,
not only the handshake side effect. Ensure malformed directory keys or database
lookup failures are caught and logged, then return without propagating an
exception so accept_friend_request preserves its successful-response guarantee.

---

Nitpick comments:
In `@tests/test_collab_a2_handshake.py`:
- Around line 307-346: Update test_accept_reupsert_contact to revoke the stored
peer link after the first accept and before the second accept, using the
contacts_store revocation method exposed by the implementation. Assert the link
is revoked before re-accepting, then retain the existing second-accept
assertions to verify re-establishment clears revoked_at and refreshes the
endpoints.

In `@tinyagentos/routes/hub.py`:
- Around line 136-139: The fallback branch in the relevant route shadows the
module-level hub_store import. Rename the local result of _get_store(request) to
store and update the subsequent get_author call, matching block_peer’s naming
without changing behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 0d2de826-ccbe-4cfd-8abd-365ad5ef0d0c

📥 Commits

Reviewing files that changed from the base of the PR and between e1076db and 0254a6e.

📒 Files selected for processing (3)
  • .gitignore
  • tests/test_collab_a2_handshake.py
  • tinyagentos/routes/hub.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • .gitignore

Comment thread tests/test_collab_a2_handshake.py
Comment thread tinyagentos/routes/hub.py Outdated
Comment thread tinyagentos/routes/hub.py
…ade fingerprint fallback

- Widen try/except in _try_handshake to cover hub_authors lookup,
  fingerprint validation, and endpoint processing — prevents
  ValueError from bytes.fromhex() on malformed directory pubkeys
  from crashing the accept endpoint (CodeRabbit CRITICAL).

- Add peer_fingerprint column to contacts table with migration,
  store it at friend-accept for stable fingerprint→contact lookup.

- Implement fingerprint-based fallback in block_peer's contact
  cascade: when hub_authors is missing or stale, resolve via
  get_contact_by_fingerprint() instead of silently skipping.

- Rename hub_store→store in _try_handshake to avoid shadowing
  the module-level import (CodeRabbit nit).

- Add test_block_cascade_fingerprint_fallback: verifies block
  revokes peer link via fingerprint when hub_authors is empty.
@jaylfc

jaylfc commented Jul 20, 2026

Copy link
Copy Markdown
Owner

Deep review at head 4b5903b. Good movement: the fingerprint-fallback block cascade, widened exception boundary, endpoint normalization, and the 9 new tests are all solid, and most earlier bot findings are genuinely fixed at head. But this is a HOLD on one reproduced upgrade-breaking defect plus two unanswered folds:

  1. BLOCKER (reproduced): the peer_fingerprint retrofit migration is a no-op on every pre-existing DB. contacts_store.py:77-81 adds the column via MIGRATIONS = [(1, ALTER TABLE ...)], but the runner's baseline-at-latest semantics (documented as FOOTGUN Fresh install test — clean Orange Pi 5 Plus #2 in db_migrations.py's own docstring) stamp any DB that predates the migration at v1 WITHOUT executing it. Reproduced against head: on a dev-schema DB the column is absent after init(), schema_migrations reads ('ContactsStore', 1), add_contact throws OperationalError: table contacts has no column named peer_fingerprint, and get_contact_by_fingerprint fails the same way. Since contacts_store already shipped to dev in feat(contacts): contacts_store + peer_links + signed-envelope peer channel #2025, every UPGRADED install silently loses the entire A2 feature - the broad except in _try_handshake (hub.py:208) swallows it, and the block-cascade security fix throws and is swallowed too (hub.py:536). Fix with the codebase's own guarded pattern: _post_init with PRAGMA table_info + ALTER when absent (see knowledge_store._migration_v1_add_user_id), and add an existing-DB upgrade test - repo policy is to test upgrades over a pre-change DB, and fresh-DB tests (all 9 here) are blind to exactly this.
  2. Fold 1 from my earlier list is still unanswered: peer.py has no send_handshake at head, and the spec (cross-user-collaboration.md, Day 0) says accept mints the peer a scoped token on BOTH sides. Deferring delivery to A3 may be a fine slice boundary, but it is a spec deviation and needs an explicit statement in the PR body + a tracking issue, not a silent relocation via code comment (hub.py:184-196).
  3. Fold 3 (.gitignore question) unanswered in-thread. The data/hub/ ignore line is justified (this branch's own history committed identity.json with plaintext keys at 2b28043 before 500da60 removed it), but answer the question so the thread closes.
  4. Key hygiene: those private keys remain in this branch's public commit history. Merge will be SQUASH so they never enter dev history, and confirm they were throwaway test keys never used against real endpoints - rotate if not. Note feat(community): add collaborator community view (stats + leaderboard + kanban) #2042 currently re-commits the same file; coordinate the data/hub/ ignore landing on dev.
  5. Procedural: CodeRabbit's head status is a rate-limit fake pass (it reviewed 0254a6e, not 4b5903b). Re-trigger @coderabbitai review after pushing the fix for item 1.
  6. Track-don't-block: Kilo W1 (contact_id bound to peer-controllable username) - either accept the documented NOTE or file a follow-up for fingerprint-keyed contact IDs; say which.

Item 1 is the gate. Everything else can land in the same push.

@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '== PR metadata and changed files =='
gh pr view 2043 --repo jaylfc/taOS --json number,headRefOid,body,commits,files \
  --jq '{number, headRefOid, body, commits: [.commits[] | {oid: .oid, messageHeadline: .messageHeadline}], files: [.files[].path]}'

echo
echo '== Relevant file locations =='
fd -a -t f '^(contacts_store|db_migrations|knowledge_store|peer|hub)\.py$|^cross-user-collaboration\.md$|^\.gitignore$|^test.*collab.*|^test.*contact.*' . \
  | sed -n '1,160p'

echo
echo '== Structural outlines =='
for f in $(fd -a -t f '^(contacts_store|db_migrations|knowledge_store|peer|hub)\.py$' .); do
  echo "--- $f"
  ast-grep outline "$f" --items all --view compact || true
done

echo
echo '== Migration, handshake, and contact references =='
rg -n -C 4 \
  -e 'MIGRATIONS' \
  -e '_post_init' \
  -e 'migration_v1_add_user_id' \
  -e 'peer_fingerprint' \
  -e 'get_contact_by_fingerprint' \
  -e 'send_handshake' \
  -e 'resolve_local_identity_id' \
  -e 'inbound_token' \
  -e 'data/hub' \
  --glob '*.py' --glob '*.md' --glob '.gitignore' . \
  | sed -n '1,500p'

echo
echo '== Relevant commit file lists =='
for sha in 2b28043a 500da607 4b5903b3; do
  echo "--- $sha"
  git show --no-ext-diff --format='%H %s' --name-status "$sha" 2>&1 | sed -n '1,100p' || true
done

Length of output: 42728


🏁 Script executed:

#!/bin/bash
set -euo pipefail

repo="jaylfc/taOS"

echo '== ContactsStore schema, migration, and lifecycle =='
contacts=$(fd -a -t f '^contacts_store\.py$' . | head -n1)
dbm=$(fd -a -t f '^db_migrations\.py$' . | head -n1)
ks=$(fd -a -t f '^knowledge_store\.py$' . | head -n1)
tests=$(fd -a -t f '^test_collab_a2_handshake\.py$' . | head -n1)
peer=$(fd -a -t f '^peer\.py$' . | rg '/tinyagentos/peer\.py$' | head -n1)

sed -n '1,260p' "$contacts"
echo '--- db_migrations ---'
sed -n '1,240p' "$dbm"
echo '--- analogous guarded migration ---'
sed -n '90,145p' "$ks"

echo '== A2 tests relevant to legacy upgrades and block cascade =='
rg -n -C 5 \
  -e 'upgrade|existing|legacy|peer_fingerprint|never.*upsert|block_cascade' \
  "$tests" tests \
  | sed -n '1,360p'

echo '== Sender implementation definitions/call sites =='
rg -n -P -C 3 '\b(send_handshake|build_envelope|resolve_local_identity_id)\s*\(' \
  "$peer" tinyagentos tests \
  | sed -n '1,360p'

echo '== Design specification: A2/A3 handshake statements =='
design=$(fd -a -t f '^cross-user-collaboration\.md$' . | head -n1)
rg -n -i -C 4 'day 0|A2|A3|handshake|token' "$design" | sed -n '1,360p'

echo '== Existing GitHub issues/PRs mentioning sender-side handshake or fingerprint-keyed contacts =='
gh search issues \
  --repo "$repo" --state open \
  --match title,body \
  --limit 100 \
  --json number,title,url,state \
  'send_handshake OR "fingerprint-keyed" OR "contact_id" OR "peer handshake"' \
  --jq '.[] | {number,title,url,state}' || true

Length of output: 76029


@jaylfc Confirmed. The upgrade blocker is real:

  • ContactsStore.MIGRATIONS uses the migration runner’s explicitly documented baseline-at-latest path, so an existing contacts table is stamped at v1 without executing ALTER TABLE ... ADD COLUMN peer_fingerprint.
  • ContactsStore has no guarded _post_init() repair, and the A2 tests only initialize fresh schemas. An upgraded install will therefore fail both the contact upsert and fingerprint lookup until repaired.

Required follow-up before re-review

  • Replace the retrofit MIGRATIONS entry with an idempotent ContactsStore._post_init() upgrade: inspect PRAGMA table_info(contacts), add peer_fingerprint only when absent, and commit the change.
  • Add an upgrade regression test that creates the pre-peer_fingerprint contacts schema first, then initializes ContactsStore and verifies:
    • the column exists;
    • add_contact(..., peer_fingerprint=...) succeeds; and
    • get_contact_by_fingerprint() finds the stored contact.
  • Update the PR description to describe the actual fingerprint-pinned-contact fallback, rather than the obsolete hub_authors-only lookup.

The remaining holds also remain valid:

  1. Sender side: tinyagentos/peer.py contains build_envelope() and resolve_local_identity_id(), but no send_handshake(). The repository search found no open tracking issue for it. The PR body should explicitly declare this as a spec deviation/deferred follow-up and link the issue that will deliver sender-side token exchange. The design describes A3 as sealed-envelope relay endpoints, so calling it “A3’s handshake reply” does not itself account for the missing sender.
  2. Sealing: there is currently no handshake sender or wire-form test to establish that an inbound_token envelope is X25519-sealed. This needs to land with the sender-side work, with a test that asserts the sealed transport representation rather than only the signed envelope.
  3. .gitignore: data/hub/ is justified: commit 2b28043a added data/hub/identity.json, and 500da607 removed it while adding the ignore rule. Please state that rationale in-thread and coordinate the rule with #2042.
  4. Key hygiene: whether the exposed key was throwaway and never used against real endpoints cannot be determined from this branch. The author needs to confirm that explicitly; otherwise rotate it. Squashing prevents entry into dev history but does not erase the already-public branch history.
  5. Kilo W1: choose and state one outcome: accept the documented username-derived contact_id limitation for this slice, or open/link a follow-up for fingerprint-keyed contact IDs.

After the migration fix and regression test are pushed, re-trigger @coderabbitai review so the new head—not 0254a6e—is evaluated.

🐇🔍


✏️ Learnings added
Learnt from: jaylfc
URL: https://github.com/jaylfc/taOS/pull/2043

Timestamp: 2026-07-20T16:35:42.468Z
Learning: For taOS SQLite stores using `tinyagentos/db_migrations.py`, retrofit schema changes must use a guarded `_post_init()` migration (`PRAGMA table_info`, conditional `ALTER TABLE`, and commit) rather than `MIGRATIONS`: pre-existing databases are intentionally baseline-stamped at the latest version without executing migration SQL. Upgrade coverage must create a pre-change database schema and initialize the store before exercising the new column.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


  • 📌 Commit the changes to this branch

…gerprint

jaylfc deep review at 4b5903b — fold all six findings:

1. BLOCKER: peer_fingerprint retrofit migration was a no-op on every
   pre-existing DB.  BaseStore's migration runner uses baseline-at-latest
   semantics — existing DBs get stamped at version 1 without executing
   the ALTER, so the column was absent after init().  The broad except in
   _try_handshake swallowed the resulting OperationalError, and the
   block-cascade security fix was similarly swallowed.

   Replaced the MIGRATIONS list with a guarded _post_init that checks
   PRAGMA table_info('contacts') and ALTER TABLE ADD COLUMN only when
   peer_fingerprint is absent.  Same pattern as agent_registry_store's
   _migration_v1_add_status.  Fresh databases still get the column from
   SCHEMA; upgraded databases get it from _post_init.

   Added two ContactsStore upgrade tests in test_store_upgrades.py
   following the existing pattern — column-presence check and
   add_contact-after-upgrade.

2. Fold 1 (send_handshake): A2 intentionally stores the inbound token
   locally without delivering it — the token exchange channel doesn't
   exist yet.  A3 completes the two-way exchange.  The send_handshake
   envelope builder from jaylfc#2046 is deferred to a follow-up PR linked from
   the tracking issue.  This is a spec deviation from
   cross-user-collaboration.md Day 0 (mint token on BOTH sides), filed
   as a tracking issue.

3. Fold 3 (.gitignore): the data/hub/ ignore line is justified — this
   branch's own history committed identity.json with throwaway test keys
   at 2b28043 (removed at 500da60).  The .gitignore prevents future
   accidental commits.  Squash merge will keep dev history clean.

4. Key hygiene: the keys in 2b28043 were throwaway test keys never used
   against real endpoints.  Squash merge removes them from dev history.
   jaylfc#2042 re-commits the same file; coordination note added in-thread.

5. Re-trigger: @coderabbitai review after push.

6. Track-don't-block (Kilo W1): accepted the documented NOTE about
   contact_id bound to peer-controllable username.  Follow-up issue filed
   for fingerprint-keyed contact IDs in a future slice.

BONUS: Fixed CodeRabbit nit from head review — test_accept_reupsert_contact
now actually revokes between accepts to verify re-establishment clears
revoked_at (was a no-op assertion before).
@hognek

hognek commented Jul 20, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the thorough deep review. All six findings folded at 948dbad:

1. BLOCKER — fixed. Replaced the MIGRATIONS list with a guarded _post_init (PRAGMA table_info check + ALTER TABLE ADD COLUMN only when absent). Same pattern as agent_registry_store._migration_v1_add_status. Fresh DBs still get peer_fingerprint from SCHEMA; upgraded DBs get it from _post_init. Two upgrade tests added in test_store_upgrades.py: column-presence check and add_contact-after-upgrade.

2. Fold 1 (send_handshake) — deferred. A2 intentionally stores the inbound token locally without delivering it (the token exchange channel doesn't exist yet). The send_handshake envelope builder from #2046 is deferred to A3. This is a spec deviation from cross-user-collaboration.md Day 0 (mint token on BOTH sides) — tracking issue filed.

3. Fold 3 (.gitignore) — answered. The data/hub/ ignore line is justified: this branch's own history committed identity.json with throwaway test keys at 2b28043 (removed at 500da60). The .gitignore prevents future accidental commits. Squash merge keeps dev history clean.

4. Key hygiene — confirmed. The keys at 2b28043 were throwaway test keys never used against real endpoints. Squash merge removes them from dev history. Re #2042: coordination note added — when #2042 lands on dev, the data/hub/ ignore line will already be present from this PR.

5. CodeRabbit — re-triggered. @coderabbitai review

6. Track-don't-block (Kilo W1) — accepted. The documented NOTE about contact_id bound to peer-controllable username stays. Follow-up issue filed for fingerprint-keyed contact IDs in a future slice.

BONUS: re-upsert test now actually revokes between accepts so the revoked_at is None assertion tests real revocation-clearing (was a no-op before).

@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown

@hognek Thanks for the thorough follow-up. The guarded upgrade path and legacy-schema coverage address the release-blocking migration defect. I’ve also noted the explicit A3 deferral, .gitignore rationale, throwaway-key confirmation, and accepted Kilo W1 limitation.

I’ll review the current head to verify the implementation and regression coverage.

🐇✅


✏️ Learnings added
Learnt from: hognek
URL: https://github.com/jaylfc/taOS/pull/2043

Timestamp: 2026-07-20T16:50:44.513Z
Learning: For taOS PR `#2043`’s A2 collaboration slice, `tinyagentos/peer.py` sender-side `send_handshake` and sealed delivery of the inbound peer token are intentionally deferred to the A3 token-exchange channel; A2 only persists the locally minted inbound token and peer-link state.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


If you found this review helpful, would you consider giving us a shout-out on X?

Thank you for using CodeRabbit!

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants