feat(server): add private asks and offers matching - #43
Merged
Merged
Conversation
added 7 commits
September 2, 2026 04:17
tmad4000
force-pushed
the
feat/agent-network-quiet-match
branch
from
September 2, 2026 10:18
dfd0196 to
f7a4014
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Intent
Build and safely ship the first OpenChat agent-first asks/offers vertical slice in the existing OpenChat app. Users explicitly capture private asks or offers, activate each item for quiet network discovery, receive anonymous compatible match proposals, approve or decline independently, and only after double opt-in reveal identities and create or reuse one ordinary human DM with exactly one neutral context card and no automatic opener. Preserve privacy: no inferred record becomes discoverable without explicit activation; pre-approval projections must never reveal identity, private details, the other party's response, or source evidence. Support both the hosted OpenChat Assistant and provider-neutral external agents through REST/MCP, with client parity across React Native/RN-web and legacy web. This is Milestone 1 only: defer passive Noos inference, Stories, the in-chat overlay, payments, and Cortex hosting integration. Validate deterministic matching, blocked/expired/withdrawn/ineligible exclusions, idempotency and concurrency, forged-card rejection, real Neo4j behavior, docs, builds, lint, tests, pushed branch, PR #40, and CI/merge readiness.
What Changed
Risk Assessment
✅ Low: The remaining direct-conversation creators now share the atomic participant-pair claim while preserving bot metadata, direct/group behavior, response shapes, and socket notifications; no new material source defects were found.
Testing
The focused agent-network unit suite passed; the real-Neo4j suite remained intentionally skipped locally, and manual workflow inspection confirmed an ephemeral Neo4j 5 GitHub Actions service with readiness health checks and focused real-database plus unit commands. No reviewer-visible product artifact was produced because this follow-up changes CI infrastructure only; definitive real-database evidence must come from the PR #40 workflow run.
Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
🔧 **Review** - 5 issues found → auto-fixed (3) ✅
apps/server/src/services/agentNetwork.ts:535- A crash after setting the match toconnectedbut before creating the DM/card permanently strands completion: retries returnalreadyResolvedat this early exit and never resume missing side effects. Move the connected transition and durable completion state behind a transactional/outbox boundary, or resume connected matches whoseconversationId/card is absent.apps/server/src/services/agentNetwork.ts:595- Using the publicmatchIdas the context-card Message id is unsafe because clients may supply arbitrary message ids. Either participant can first send a normal message with that id;persistMessagethen MERGEs the existing message, does not set card fields, and links it into the new DM. Use a dedicated uniquely constrained match-context key or a protected server-only id namespace.apps/server/src/services/agentNetwork.ts:546- The required “blocked/expired/withdrawn/ineligible exclusions” are not enforced when responding: this transition checks onlymatch.status = 'proposed', so a proposal can still connect after either intent is withdrawn or either user blocks the other. Expiry/ineligibility are not modeled at all, while the added design document calls expiry a non-goal. The implementation or authoritative requirement must be reconciled before merge.apps/server/src/services/agentNetwork.ts:455- Concurrent scans can both pass theNOT EXISTSpredicate and CREATE different AgentMatch nodes for the same intent pair; uniqueness is only on random match ids. This can produce duplicate proposals and independent approvals. Add a deterministic sorted-pair key with a uniqueness constraint and MERGE/claim at this shared creation boundary.apps/server/src/services/directConversation.ts:90- DM reuse is a check-then-CREATE sequence without a unique participant-pair key. A normal DM request racing match completion can therefore create two human DMs, violating the required exactly-one DM and concurrency behavior. Make the exact participant pair uniquely claimable and create/reuse it atomically.🔧 Fix: Harden quiet-match completion and concurrency guarantees
2 errors still open:
apps/server/src/services/agentNetwork.ts:581- Connected-status delivery is still neither recoverable nor idempotent. A crash afterconversationIdis stored but before these calls makes retries exit at line 602 without posting either status card; concurrent retries whileconversationIdis absent both reach these calls and create duplicate status cards, duplicate webhook dispatches, and can race the check-then-create Assistant-DM helper. Persist deterministic per-match/per-user status-card identities and delivery state at the shared completion boundary, and atomically claim Assistant DMs.apps/server/src/services/agentNetwork.ts:494- Proposal delivery occurs after the unique AgentMatch is committed. If either card write fails or the process stops here, the pair remains permanently excluded by the existing-match predicate, while one or both users never receive the required proposal; retrying the scan cannot repair it, and retrying delivery would currently duplicate random-id cards. Use deterministic per-viewer proposal-card keys plus a durable/reconcilable delivery claim.🔧 Fix: Make match card delivery durable and idempotent
1 error still open:
apps/server/src/routes/assistant.ts:80- The atomic Assistant-DM fix is incomplete because two competing creation paths still bypassensureDirectConversation:POST /api/assistant/ensureperforms its own check-then-CREATEhere, and hosted AssistanttoolCreateConversationdirectly createstype:'direct'conversations atservices/assistant.ts:500. Either can race proposal/status delivery or match completion and create an unkeyed duplicate Assistant or human DM, leaving the required exactly-one/concurrency invariant reachable. Route every direct-conversation creator through the shared pair-key boundary while preserving bot metadata.🔧 Fix: Route all direct creation through atomic pair claims
✅ Re-checked - no issues remain.
apps/server/src/services/agentNetwork.ts:424- The authoritative acceptance criteria require expired intents to be excluded and validated, but AgentIntent has no expiry field or expired state and both candidate queries filter onlystatus: 'active'. The focused tests likewise cover same-kind/same-owner ineligibility but contain no expired, blocked, or withdrawn candidate regression. Add explicit expiry semantics and focused exclusion tests for expired, blocked, withdrawn, and otherwise ineligible intents.apps/server/test/agentNetwork.integration.test.ts:6- All eight real-Neo4j acceptance tests were skipped because NEO4J_TEST_URI, NEO4J_TEST_USER, and NEO4J_TEST_PASSWORD were unset and no Neo4j service was reachable. Consequently, this local phase could not demonstrate the required real-database concurrency, deduplication, recovery, decline, and exactly-one-card behavior.git diff --stat edce62823c45443a0873c969104df32fb39ba0ab..dafcef0afed194a46a74e836d52f128274ffe90aand targeted source/test inspection../../node_modules/.bin/vitest run test/agentNetwork.test.ts test/agentNetwork.route.test.ts test/chatUnread.route.test.ts test/agentNetwork.integration.test.ts --reporter=verbosefromapps/serverCheckedNEO4J_TEST_*configuration, local port 7687, and running containers; no usable Neo4j test service was availableRendered the production legacy-web and React Native/RN-webAgentNetworkCardcomponents in Chrome with proposal and neutral context-card payloadsClicked Approve independently on both client implementations and verified each changed toApproved · waiting for the other sideExercisedapps/mcp-server/src/api.tsagainst a controlled live HTTP server: oc_ Bearer authentication, intent publication, and match approval projectionInspected the matching Cypher and focused tests for blocked, expired, withdrawn, and ineligible candidate exclusionsRemoved the temporary browser harness and stopped its Vite/Chrome processes;git status --shortremained clean🔧 Fix: Exclude expired intents from quiet matching
1 warning still open:
apps/server/test/agentNetwork.integration.test.ts:6- The nine real-Neo4j acceptance tests were skipped because NEO4J_TEST_URI, NEO4J_TEST_USER, and NEO4J_TEST_PASSWORD were unset and no Neo4j service was reachable. Consequently, this local phase could not demonstrate the required real-database concurrency, deduplication, recovery, decline, exclusion, and exactly-one-card behavior../node_modules/.bin/vitest run apps/server/test/agentNetwork.test.ts apps/server/test/agentNetwork.expiry.test.ts apps/server/test/agentNetwork.route.test.ts apps/server/test/agentNetwork.integration.test.ts./node_modules/.bin/vitest run apps/server/test/chatUnread.route.test.tsRendered and visually inspected the target legacy-webAgentNetworkCard; reviewed the active-run parity screenshot covering legacy web and React Native/RN-web.Reviewed the REST/MCP publish-and-approve evidence payload for privacy-safe pre-approval projection.CheckedNEO4J_TEST_*,127.0.0.1:7687, and running containers for an available real Neo4j test service.🔧 Fix: Test agent network against ephemeral Neo4j in CI
1 warning still open:
.github/workflows/agent-network-integration.yml:1- I could not execute the requested cloud-runner Neo4j evidence in this local test phase. Verifying the new workflow requires its GitHub Actions run on PR feat(agent-network): quiet-match asks/offers loop (M1) #40; local developers intentionally remain env-gated and no shared/production Neo4j database was used.git diff --check edce62823c45443a0873c969104df32fb39ba0ab..b93b55fe0232b693855c62b833335c87a118cbfc -- .github/workflows/agent-network-integration.yml apps/server/test/agentNetwork.integration.test.ts apps/server/src/services/agentNetwork.tscd apps/server && npm test -- --runInBand test/agentNetwork.test.tsManual inspection of.github/workflows/agent-network-integration.ymlfor ephemeralneo4j:5-communityservice isolation, health checks, test credentials, PR/push triggers, and focused integration/unit commandscd apps/server && npm test -- --runInBand test/agentNetwork.integration.test.ts(confirmed intentional local env-gated skip; did not point at any developer/shared/production graph)✅ **Document** - passed
✅ No issues found.
✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.