Skip to content

feat(server): add private asks and offers matching - #43

Merged
tmad4000 merged 8 commits into
mainfrom
feat/agent-network-quiet-match
Sep 2, 2026
Merged

tmad4000 merged 8 commits into
mainfrom
feat/agent-network-quiet-match

Conversation

@tmad4000

@tmad4000 tmad4000 commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

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

  • Add private ask/offer publishing, withdrawal, expiration, deterministic quiet matching, and privacy-safe approval APIs for hosted and external agents via REST/MCP.
  • Render anonymous match proposals across mobile, RN-web, and legacy web, creating or reusing one human DM with an idempotent neutral context card only after double opt-in.
  • Harden matching and card delivery against blocked or ineligible intents, retries, concurrency, and forged cards, with focused tests, Neo4j CI coverage, and updated integration docs.

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.

  • Outcome: ⚠️ 1 warning across 3 runs (17m1s)

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 to connected but before creating the DM/card permanently strands completion: retries return alreadyResolved at 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 whose conversationId/card is absent.
  • 🚨 apps/server/src/services/agentNetwork.ts:595 - Using the public matchId as 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; persistMessage then 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 only match.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 the NOT EXISTS predicate 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 after conversationId is stored but before these calls makes retries exit at line 602 without posting either status card; concurrent retries while conversationId is 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 bypass ensureDirectConversation: POST /api/assistant/ensure performs its own check-then-CREATE here, and hosted Assistant toolCreateConversation directly creates type:'direct' conversations at services/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.

⚠️ **Test** - 1 warning
  • 🚨 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 only status: '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..dafcef0afed194a46a74e836d52f128274ffe90a and 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=verbose from apps/server
  • Checked NEO4J_TEST_* configuration, local port 7687, and running containers; no usable Neo4j test service was available
  • Rendered the production legacy-web and React Native/RN-web AgentNetworkCard components in Chrome with proposal and neutral context-card payloads
  • Clicked Approve independently on both client implementations and verified each changed to Approved · waiting for the other side
  • Exercised apps/mcp-server/src/api.ts against a controlled live HTTP server: oc_ Bearer authentication, intent publication, and match approval projection
  • Inspected the matching Cypher and focused tests for blocked, expired, withdrawn, and ineligible candidate exclusions
  • Removed the temporary browser harness and stopped its Vite/Chrome processes; git status --short remained 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.ts
  • Rendered and visually inspected the target legacy-web AgentNetworkCard; 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.
  • Checked NEO4J_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.ts
  • cd apps/server && npm test -- --runInBand test/agentNetwork.test.ts
  • Manual inspection of .github/workflows/agent-network-integration.yml for ephemeral neo4j:5-community service isolation, health checks, test credentials, PR/push triggers, and focused integration/unit commands
  • cd 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.

@tmad4000
tmad4000 force-pushed the feat/agent-network-quiet-match branch from dfd0196 to f7a4014 Compare September 2, 2026 10:18
@tmad4000
tmad4000 merged commit bfe6747 into main Sep 2, 2026
2 checks passed
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.

1 participant