Skip to content

fix(bridges): the bot congratulated "them" on the owner's own store - #230

Merged
dshakes merged 2 commits into
masterfrom
fix/public-facts-and-language-scorer
Sep 4, 2026
Merged

dshakes merged 2 commits into
masterfrom
fix/public-facts-and-language-scorer

Conversation

@dshakes

@dshakes dshakes commented Sep 4, 2026

Copy link
Copy Markdown
Owner

A friend forwarded the owner's own announcement back and said "congratulations". The bot replied "congrats to them, that's exciting" — then asked the friend whether a store was opening near him.

Not a reasoning failure. The fact was unrepresentable.

1. No bucket for a public fact

## Facts is typed (spouse, kids, key dates) and private-by-default — contacts get "never confirm, never deny". The 12 lines that did mention Brambleton live in ## Private, sealed from contacts by design.

So the bot had nowhere to hold "I am opening a store" — and had it been in Facts, the model would have been told not to confirm it.

Added ## Public (aliases: ## Announced, ## What I'm up to, ## Shareable). Parsed into publicFacts, kept out of prose like Facts, injected into the persona for both audiences with the opposite framing: the one kind of fact the model may openly confirm. A congratulations about it is about the owner — thank them as the person it happened to, never as an outsider, never ask if it's happening near them.

2. "Brambleton, VA" was French

The scorer accepted any single token hit. The French list contains va (also ma, ce, tu). "Brambleton, VA" → French 0.95; "see you in MA next week" → Bengali. A foreign-language reply mode engaged on the owner's English announcement.

Single hit now requires ≥3 chars. Not 4 — "ela unnav" is a real greeting whose only hit is the 3-letter ela; a stricter first cut regressed it and the test caught it.

3. The owner's own Telugu wasn't in the detector

CLAUDE.md documents the owner's short forms — vasta, cheptha, matladtham. None were in the lexicon, so the owner's own dialect scored as English on the original code. Added, with inflections. Pre-existing; found while verifying (2).

Observed, not fixed

The contact had no relationship entry → confidence fired with zero signals (MEDIUM ()). The bot discussed the owner's biggest news with someone it knew nothing about.

Verification

  • 1506/1506 bridge-core · 57/57 imessage-bridge · both bridges typecheck clean
  • Verified against the real profile: fact parses, doesn't leak into prose/Facts/vault, persona rule lands
  • First typecheck pass hid a TS2322 (double comma → undefined in the array) behind a pipe; caught on the re-run with real exit codes

A friend forwarded the owner's OWN announcement ("we are opening a Crispy
Cones store in Brambleton, VA") back to him and said "congratulations". The
bot replied "congrats to them, that's exciting" — then asked the friend
whether a store was opening near HIM. In front of a contact, about the single
biggest thing in the owner's life.

Not a reasoning failure. The fact was UNREPRESENTABLE.

1. There was no bucket for a public fact.

   `## Facts` is typed (marital status, spouse, kids, key dates) and is
   private-by-default: contacts get "never confirm, never deny". The 12 lines
   that DID mention Brambleton live in `## Private`, sealed from contacts by
   design. So the bot had nowhere to hold "I am opening a store", and even if
   it had been in Facts the model would have been TOLD not to confirm it.

   Added `## Public` (also `## Announced` / `## What I'm up to` /
   `## Shareable`): free-form bullets, parsed into `publicFacts`, kept OUT of
   prose like Facts, rendered by `publicBlock()`, injected into the persona
   for BOTH audiences with the OPPOSITE framing — this is the one kind of
   fact the model may openly confirm, and a congratulations about it is about
   the OWNER: thank them as the person it happened to, never as an outsider,
   never ask whether it is happening near them.

   Wired into both bridges. The fact itself is written into the owner's
   profile (the owner stated it directly: "we are opening crispy cones
   franchise on Sep 10").

2. "Brambleton, VA" was French.

   The romanized-lexicon scorer accepted ANY single token hit. The French
   list has "va" (also "ma", "ce", "tu"); "Brambleton, VA" scored French at
   0.95, "see you in MA next week" scored Bengali, and the language-modality
   hint engaged on the owner's own English announcement. A single hit now
   only counts when the token is >= 3 chars — 2-letter function words cannot
   classify a message alone. 3 rather than 4 because "ela unnav" is a real
   greeting whose only lexicon hit is the 3-letter "ela" (a stricter first
   cut regressed it; caught by the test, not by luck).

3. The owner's own Telugu was not in the detector.

   CLAUDE.md documents the owner's short forms — "vasta", "cheptha",
   "matladtham". None were in the Telugu lexicon, so the owner's own dialect
   scored as ENGLISH on the original code too. Added them plus common
   inflections. Pre-existing, found while verifying (2).

Also observed, not fixed here: the contact had no relationship entry, so the
confidence tier fired with zero signals ("MEDIUM ()"). The bot replied about
the owner's biggest news to someone it knew nothing about.

Tests pin the invariants: `## Public` parses and does not leak into prose,
Facts or the vault; the persona carries the ownership rule; a US state code
cannot classify a message; real romanized one-word messages still do.

1506/1506 bridge-core, 57/57 imessage-bridge, both bridges typecheck clean
(real exit codes — the first pass hid a TS2322 behind a pipe).
@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

🔎 Codex cross-audit (agent:audit)

Blocking

  • packages/bridge-core/src/owner-profile.ts now treats ## News / ## Public / ## What I'm up to as typed public facts and removes them from prose(), but only the contact reply call sites pass publicBlock() into prompts. Owner self-chat/Jarvis paths still only inject prose() plus facts/vault, e.g. services/whatsapp-bridge/src/session.ts and services/imessage-bridge/src/session.ts. So existing profile sections named ## News stop being visible to owner-facing queries, and new ## Public facts are not available when the owner asks their own assistant about them. Either pass ownerProfileStore.publicBlock() into all owner-self prompts too, or don’t remove broad headings like ## News from prose unless every prompt path gets the typed block.

Verification

  • Tried npm test in packages/bridge-core; it could not run because local dependency tsx is not installed (ERR_MODULE_NOT_FOUND).

@compass-sdlc-bot compass-sdlc-bot Bot added the agent:reviewed-clean Reviewer found no Blocking issues this round label Sep 4, 2026
@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

🔷 Gemini cross-audit (agent:audit-gemini)

Audit Report: Unified PR Patch Analysis

Verdict: APPROVED (Green) — Zero blocking correctness, security, or regression issues. The diff is extremely high-quality, elegant, and robust.


Key Strengths & Solved Issues

  1. Robust Language Classification (packages/bridge-core/src/language.ts)

    • The Problem: Single 2-letter tokens like "VA" (Brambleton, VA) or "MA" (see you in MA) matched French ("va") or Bengali ("ma"), causing English announcements to engage foreign-language reply modes.
    • The Fix: Successfully introduces bestHitLen. Single-token matches must now be $\ge 3$ characters (e.g., "ela", "vasta", "bonjour"). Multiple matching tokens ($\ge 2$) bypass this length restriction, preserving reliable multi-word detection while eliminating single-token US state/postal code false positives.
    • Telangana Dialect Parity: Adding colloquial short forms ("vasta", "cheptha", "matladtham", etc.) directly into LEX.telugu resolves a major dialect mismatch where the owner's own messages were erroneously classified as English.
  2. Factual Polarity Correction (packages/bridge-core/src/owner-profile.ts & natural.ts)

    • The Problem: The existing system kept all biographical facts in ## Facts (private-by-default, triggering deflect/non-disclosure rules). The agent had no way to know an announcement was the owner's own news, leading to the highly uncanny "congrats to them" response to friends.
    • The Fix: Introduces ## Public (with alias support for Announced, What I'm up to, and Shareable). This separates private facts from proud public announcements. The prompt instructions in agentPersonaPrompt are explicitly crafted to handle congratulations, thank senders warmly as the subject of the news, and strictly avoid outsider deflection.
    • Leakage Prevention: Public facts are correctly parsed into a dedicated publicFactLines array and kept completely out of the free-form prose block, eliminating duplicate injection risks.
  3. High-Fidelity Regression Test Coverage (owner-public-facts.test.ts)

    • Incorporates a solid test suite with comprehensive assertions validating parsing logic, header alias matching, contact-facing persona prompt construction, single 2-letter postal code exclusions, and colloquial Telugu detection.

Non-Blocking Edge-Case Analysis

  • Token Collision Order: If a message contains multiple single-token hits across different languages (e.g., one 2-letter token in Bengali, one 2-letter token in French), the tie-breaker behaves deterministically by favoring the first language defined in LEX_SET order. This maintains parity with the original codebase behavior.
  • Nested Indented Bullets: Bullet sub-lists inside the ## Public section will be split into individual strings, which is perfectly acceptable as they are compiled with semicolons in publicBlock().

@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

🛡️ Vuln scan — ❌ vulnerable dependency found

cd services/control-plane && govulncheck ./...
=== Symbol Results ===

No vulnerabilities found.

Your code is affected by 0 vulnerabilities.
This scan also found 1 vulnerability in packages you import and 18
vulnerabilities in modules you require, but your code doesn't appear to call
these vulnerabilities.
Use '-show verbose' for more details.
cd services/gateway && cargo audit
    Fetching advisory database from `https://github.com/RustSec/advisory-db.git`
      Loaded 1239 security advisories (from /home/runner/.cargo/advisory-db)
    Updating crates.io index
    Scanning Cargo.lock for vulnerabilities (272 crate dependencies)
cd services/model-router && cargo audit
    Fetching advisory database from `https://github.com/RustSec/advisory-db.git`
      Loaded 1239 security advisories (from /home/runner/.cargo/advisory-db)
    Updating crates.io index
    Scanning Cargo.lock for vulnerabilities (251 crate dependencies)
cd services/runtime-manager && cargo audit
    Fetching advisory database from `https://github.com/RustSec/advisory-db.git`
      Loaded 1239 security advisories (from /home/runner/.cargo/advisory-db)
    Updating crates.io index
    Scanning Cargo.lock for vulnerabilities (480 crate dependencies)
Crate:     wasmtime
Version:   46.0.2
Title:     Guest controlled-size host heap allocation through WASIp3 streams
Date:      2026-08-20
ID:        RUSTSEC-2026-0268
URL:       https://rustsec.org/advisories/RUSTSEC-2026-0268
Severity:  6.9 (medium)
Solution:  Upgrade to >=46.0.3, <47.0.0 OR >=47.0.4

Crate:     wasmtime
Version:   46.0.2
Title:     Filesystem sandbox escape when paths or symlinks contain trailing slashes
Date:      2026-08-20
ID:        RUSTSEC-2026-0269
URL:       https://rustsec.org/advisories/RUSTSEC-2026-0269
Severity:  8.8 (high)
Solution:  Upgrade to >=24.0.13, <25.0.0 OR >=36.0.14, <37.0.0 OR >=46.0.3, <47.0.0 OR >=47.0.4

Crate:     chacha20
Version:   0.10.0
Warning:   yanked

error: 2 vulnerabilities found!
warning: 1 allowed warning found
make: *** [Makefile:270: audit] Error 1

…anked chacha20

Three new findings on the `vuln` gate, none in this PR's diff, all in the
runtime-manager's Wasmtime stack — the layer invariant #5 relies on to
isolate untrusted code, so not something to merge past because the check is
"not required".

  RUSTSEC-2026-0269  wasmtime 46.0.2  filesystem sandbox escape via
                     trailing slashes in paths/symlinks
  RUSTSEC-2026-0268  wasmtime 46.0.2  guest-controlled host heap allocation
                     through WASIp3 streams
  chacha20 0.10.0    yanked (via wasmtime-wasi -> rand); cargo audit treats
                     a yank as a failure

`cargo update -p wasmtime` alone was a no-op: the wasmtime family pins itself
with `=`, so wasmtime-environ stayed at 46.0.2 and the resolver refused
46.0.3. Updating `wasmtime` and `wasmtime-wasi` together moves the whole
family (and cranelift 0.133.2 -> 0.133.3, pulley 46.0.3, cap-std 3.4.6).
Stays on the 46.x line — no API change, `cargo check` clean in 31s.

Lockfile only. `make audit` (govulncheck + cargo audit x3) exits 0.
@dshakes
dshakes merged commit 53069f0 into master Sep 4, 2026
12 checks passed
@dshakes
dshakes deleted the fix/public-facts-and-language-scorer branch September 4, 2026 19:49
dshakes added a commit that referenced this pull request Sep 4, 2026
…sured reply loop (#231)

The plan behind PRs #224–#230, written down so it outlives the session that
produced it. Fourteen root-caused production failures from 2026-08-23 to
2026-09-04 are the Context; five workstreams derived from them are the
Decision.

The three findings the plan rests on:
  - none of the fourteen threw an error — every one was silent
  - the owner told the assistant the facts and they never reached the bot
  - static classifiers pre-empt the model at every gate a contact can see

And the three things the 2026 literature says NOT to do, so nobody relitigates
them: no more exemplars, no fine-tuning, no multi-agent fan-out on the reply
path. Sequenced so the deploy step comes first (three services ran stale code
for weeks) and the measurement loop comes before any voice change it would
have to judge.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agent:reviewed-clean Reviewer found no Blocking issues this round

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant