Skip to content

chore(channels)!: remove 6 low-value channel adapters#5265

Merged
houko merged 3 commits into
mainfrom
chore/remove-uncommon-channels
May 19, 2026
Merged

chore(channels)!: remove 6 low-value channel adapters#5265
houko merged 3 commits into
mainfrom
chore/remove-uncommon-channels

Conversation

@houko

@houko houko commented May 19, 2026

Copy link
Copy Markdown
Contributor

Summary

BREAKING: removes 6 in-process channel adapters without sidecar replacement.

Channel Lines Reason
viber 813 Global usage declining
messenger 881 Meta bot-restrictions make it nearly unusable
nostr 643 Tiny user base
discourse 632 Forum software, doesn't match channel-adapter model
mqtt 846 IoT pub/sub, doesn't match channel-adapter model
linkedin 615 API hostile to bots

Total: 30 files, +22 / −5832 lines (net −5810 Rust + docs deletions).

Operator action required

# config.toml: remove any of these blocks
# [channels.viber]
# [channels.messenger]
# [channels.nostr]
# [channels.discourse]
# [channels.mqtt]
# [channels.linkedin]

Cascade

  • 6 src/<name>.rs files deleted
  • lib.rs mods, Cargo.toml features (channels + api), allowlist
  • <Name>Config structs, channels.<name> fields, validation hooks
  • channel_bridge.rs imports, spawn blocks, macro arms, test asserts
  • routes/channels.rs ChannelMeta + 4 match arms each + webhook allowlist
  • routes/config.rs ch!() calls
  • Kernel channel_sender macro + name list
  • CLI TUI ChannelDef
  • Docs [channels.X] blocks + tutorial sections (en + zh)
  • Optional deps k256 (nostr) and rumqttc (mqtt) dropped

mqtt is the cleanest delete — it didn't even have a MqttConfig struct or ChannelMeta entry; only the per-adapter feature + lib.rs mod existed.

Test plan

  • CI: cargo check --workspace --lib
  • CI: cargo clippy --workspace --all-targets -- -D warnings
  • CI: cargo test -p librefang-channels / -p librefang-types / -p librefang-api / -p librefang-kernel / -p xtask (channel-policy gate must remain green with these 6 delisted)

BREAKING: removes viber, messenger, nostr, discourse, mqtt, linkedin.
No sidecar replacement — these channels have either a tiny user base,
upstream-imposed bot restrictions that prevent usable operation, or
fit a protocol model (forum, IoT pub/sub) that doesn't match the
channel-adapter shape.

Full cascade removed:

- `crates/librefang-channels/src/{viber,messenger,nostr,discourse,
  mqtt,linkedin}.rs` (6 files deleted).
- `librefang-channels`: `lib.rs` mods, `Cargo.toml` features
  (incl. the `k256` / `rumqttc` optional deps that nostr / mqtt
  pulled in), `all-channels` membership, `channels-allowlist.txt`
  entries (so `cargo xtask channel-policy` permanently rejects any
  attempt to reintroduce them in-process).
- `librefang-types`: `ViberConfig`, `MessengerConfig`, `NostrConfig`,
  `DiscourseConfig`, `LinkedInConfig` structs + Default impls
  (mqtt had no `MqttConfig`); the matching `channels.<name>` fields
  in `ChannelsConfig` and its `Default`; the 5 validation hooks in
  `config/validation.rs`; the `LinkedInConfig` default-roundtrip
  test.
- `librefang-api`: `channel-*` features + their membership in
  `all-channels` / `all-channels-no-email`; `channel_bridge.rs`
  imports, spawn blocks, `find_channel_info!` and `check_channel!`
  arms, and the 5 default-empty test assertions; `routes/channels.rs`
  `ChannelMeta` entries plus the 4 match arms each (`is_some` /
  serialize / `len` / `ser`); the `webhook_route_suffix` allowlist
  shedding `messenger` and `viber`; `routes/config.rs` `ch!()` calls.
- `librefang-kernel`: `channel_sender.rs` `for_each_channel_field!`
  macro entries and the expected-name list in the registry test.
- `librefang-cli`: TUI `ChannelDef` entries.
- `librefang-channels/src/http_client.rs`: docstring updated to drop
  the now-dead `messenger`/`viber` caller list.
- `docs/`: `[channels.X]` blocks removed from
  `configuration/page.mdx` and `configuration/channels/page.mdx`
  (en + zh); the matching `## X` tutorial sections removed from
  `integrations/channels/social/page.mdx` and
  `integrations/channels/integrations/page.mdx` (en + zh); the
  top-level channel-list page (en + zh) updated.
- `CHANGELOG.md`: `[Unreleased] ### Removed` entry describing the
  cascade and the operator action required.

Operator action required:

  # config.toml: remove any of these blocks if you have them
  # [channels.viber]
  # [channels.messenger]
  # [channels.nostr]
  # [channels.discourse]
  # [channels.mqtt]
  # [channels.linkedin]

`cargo check / clippy / test` deferred to CI — the host's cargo is a
Docker wrapper with `rust:latest` not cached locally, and
workspace-wide `cargo test` is forbidden by repo policy. The deletion
is mechanical (no logic added), so the surface is mostly compile-time
gates and trivial match-arm pruning — CI will catch any missed site.
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

houko added 2 commits May 19, 2026 17:00
…rketing entries

Self-review on the original 6-channel removal turned up four
straggler sites the first pass missed.

- Workspace `Cargo.toml`: drop `k256` and `rumqttc` from
  `[workspace.dependencies]`. Both were declared optional and only
  consumed by `nostr` / `mqtt`, which are gone. Cargo.lock still
  carries the stale package entries; the next `cargo check` regens
  the lock (CI runs that doesn't pass `--locked` will fix it
  in-place).
- `librefang-cli/Cargo.toml` doc comment: shed the now-dead
  `rumqttc` / `nostr-sdk` mentions from the "heavy / unmaintained"
  list.
- `librefang-api/Cargo.toml` doc comment: same — drop the mqtt
  mention from the analogous comment.
- `web/public/registry.json`: drop the 6 channel marketing entries
  (`discourse`, `linkedin` channel — NOT the `linkedin` Hand which
  is a separate entry, `messenger`, `mqtt`, `nostr`, `viber`).
  Unlike gotify (which stays in the registry because it still ships
  as a sidecar), these have no replacement and the marketing site
  would otherwise advertise a channel that no longer exists.
  Validated as JSON after the edits.

Also audited and confirmed *no* residual:

- middleware route allowlist
- integration / unit test fixtures in `librefang-api/tests/`
- examples/ directory
- `librefang.toml.example`
- `pairing.rs` (still uses `push_provider = "ntfy" | "gotify"` —
  unrelated to channels, intentionally untouched)
- `kernel_config_schema.golden.json` still carries the 5 stale
  config-struct definitions, but its test is `#[ignore]`'d on main
  and the fixture has drifted across earlier migrations — regen is
  a separate follow-up (`cargo test -p librefang-api --test
  config_schema_golden -- --ignored regenerate_golden`).
- `kernel-router` "linkedin" string in HAND auto-routing is for the
  LinkedIn hand (agent kind), not the channel — kept.
- `openclaw` migrator's `"messaging" | "chat" | "messenger"` arm
  maps an incoming category alias to the canonical `"messaging"`
  category — no `[channels.messenger]` is emitted, so the alias
  staying is harmless and migration-input-tolerant.
…ignores, dashboard mqtt icon, stale doc/AGENT lists

Third pass turned up several places the first two reviews didn't
catch.

Stale RUSTSEC ignores tied to rumqttc:

- `.github/workflows/ci.yml`: drop `--ignore RUSTSEC-2026-0049 /
  -0098 / -0099 / -0104` and the explanatory comment. All four were
  rustls-webpki 0.102.x advisories that only entered the lockfile
  via rumqttc, which is gone.
- `deny.toml`: drop the same four IDs and the comment block.

Stale dashboard channel-icon map:

- `dashboard/src/pages/CommsPage.tsx` and `ChannelsPage.tsx`: drop
  the `mqtt: <Wifi …>` entry from the channel-icon dictionary; the
  `Wifi` import is no longer used so drop it too (would otherwise
  fail dashboard typecheck on unused-imports).

Stale narrative docs (heavy-deps lists, channel rollcalls, env-var
tables, webhook-contract upgrade notes — all in both English and
Chinese):

- `crates/librefang-channels/README.md` and `AGENTS.md`: drop
  `MQTT, Nostr` from the heavy-deps example list; drop
  `Messenger, Viber` from the HMAC-mandatory list (LINE / Teams /
  DingTalk remain) and the matching `Messenger:
  MESSENGER_APP_SECRET` / `LINE / Viber / DingTalk` lines.
- `docs/src/app/configuration/security/page.mdx` (en + zh):
  drop the env-var rows for `VIBER_AUTH_TOKEN`, `MESSENGER_*`,
  `NOSTR_PRIVATE_KEY`, `DISCOURSE_API_KEY`, `LINKEDIN_ACCESS_TOKEN`.
- `docs/src/app/configuration/page.mdx` (en + zh): same six rows
  in the **second** env-var table near the validation section, plus
  the validator-checked-env-var summary table at the bottom (six
  rows).
- `docs/src/app/configuration/channels/page.mdx` (en): drop
  `X-Viber-Content-Signature` from the required-header table; drop
  the Messenger-specific fallback parenthetical; drop the
  "Operating Messenger?" and "Operating LINE / Viber / DingTalk?"
  upgrade-notes line (folded into "Operating LINE / DingTalk?").
- `docs/src/app/operations/troubleshooting/page.mdx` (en + zh) and
  `operations/faq/page.mdx` (en + zh): drop the
  `Viber` / `Facebook Messenger` mentions from "Webhook-based
  channels" and the social-channel list.
- `docs/src/app/architecture/page.mdx` (en + zh): rewrite the
  `librefang-channels` rollcall (the "44 adapters" paragraph) to
  drop the 6 removed channels and to mention Telegram / ntfy /
  Gotify / Mastodon as sidecars; rewrite the Wave 2/3/4 table
  similarly. Drop the misleading "44 adapters" total — the count
  is now dynamic.
- `docs/src/app/integrations/api/communication/page.mdx` (en + zh):
  drop `Viber, Messenger` from the channel listing in the
  `GET /api/channels` description.

Audit-issue bookkeeping:

- `docs/issues/mqtt-task-not-tracked.md`: drop the MQTT sub-finding
  (the adapter no longer exists), keep the two unrelated
  sub-findings (process-manager readers, `held_locks` `RefCell`).
- `docs/issues/INDEX.md`: drop the `rumqttc-rustls-webpki-cves`
  entry (file deleted in the round-2 followup) and renumber the
  rollup total from 120 to 119; update the
  `mqtt-task-not-tracked` description to reflect the trimmed
  scope.
- `docs/issues/comms-send-impersonation.md`: swap the
  `Viber`/`Messenger` HMAC-validation examples for `LINE` /
  `Threema` (which still exist).

Confirmed clean (no edit needed):

- `Cargo.lock`: still has `k256` and `rumqttc` package entries; CI
  will regenerate on the first `cargo check`. Same disposition as
  the round-2 followup noted.
- `kernel_config_schema.golden.json`: 5 stale config-struct
  definitions remain, matching the `#[ignore]`'d test status on
  main.
- `articles/release-0.6.5.md`: historical release note mentioning a
  rumqttc dep update — intentional historical record, kept.
- `crates/librefang-kernel-router/src/lib.rs`: "linkedin" string
  remains; that's the LinkedIn HAND auto-routing keyword, not the
  channel.
- `crates/librefang-migrate/src/openclaw.rs`: `"messaging" |
  "chat" | "messenger"` category alias remains — maps a legacy
  category name to `"messaging"`, does not write a
  `[channels.messenger]` block.
@houko
houko merged commit 766d89d into main May 19, 2026
23 checks passed
@houko
houko deleted the chore/remove-uncommon-channels branch May 19, 2026 08:17
@github-actions github-actions Bot added area/docs Documentation and guides area/channels Messaging channel adapters area/kernel Core kernel (scheduling, RBAC, workflows) area/ci CI/CD and build tooling labels May 19, 2026
@github-actions github-actions Bot added the size/XL 1000+ lines changed label May 19, 2026
houko added a commit that referenced this pull request May 19, 2026
`Quality / Check formatting` CI lane was broken on `main` after the
6-channel-removal PR (#5265) landed: the deletions in that PR
shortened several match arms and function signatures enough that
rustfmt now wants the re-flowed form, but the PR merged before its
own followup fmt commit could land.

This commit is the exact rustfmt diff from the failed CI job
applied verbatim to main:

- `crates/librefang-api/src/routes/channels.rs`:
  alphabetical sort of `use super::sidecar_describe` vs
  `use super::skills`; `webhook_route_suffix` match arms collapsed
  to fewer lines; `secrets_env_keys` read re-indented to the
  single-call form; `upsert_secret(...).map_err(|e| {…})?`
  collapsed.
- `crates/librefang-api/src/routes/secrets_env.rs`:
  `parent.join(format!(...))` fits on one line.
- `crates/librefang-api/src/routes/sidecar_describe.rs`:
  `describe_sidecar(command, args)` signature on one line.
- `crates/librefang-api/tests/channels_routes_test.rs`:
  `vec![SidecarSchemaField {...}]` re-formatted to the multi-line
  `vec![\n    Struct { ... },\n]` form rustfmt prefers for single
  big literals.
- `crates/librefang-api/tests/sidecar_describe_test.rs`:
  `describe_sidecar("python3", &[...]).await` collapsed.
- `crates/librefang-channels/src/sidecar.rs`:
  `build_spawn_env(home, ctx)` and `SidecarTask::new(config, home)`
  signatures collapsed; the `assert_eq!` chain in the test re-flowed
  because the new `got.get(...).map(|s| s.as_str())` chain doesn't
  fit the single-line form inside `assert_eq!(...)`.
- `crates/librefang-types/tests/config_default_roundtrip.rs`:
  re-flow the import list now that `LinkedInConfig` is gone; drop
  the double blank line after `voice_config_default_roundtrips_*`.

No logic changes — pure rustfmt diff.
houko added a commit that referenced this pull request May 19, 2026
)

`Quality / Check formatting` CI lane was broken on `main` after the
6-channel-removal PR (#5265) landed: the deletions in that PR
shortened several match arms and function signatures enough that
rustfmt now wants the re-flowed form, but the PR merged before its
own followup fmt commit could land.

This commit is the exact rustfmt diff from the failed CI job
applied verbatim to main:

- `crates/librefang-api/src/routes/channels.rs`:
  alphabetical sort of `use super::sidecar_describe` vs
  `use super::skills`; `webhook_route_suffix` match arms collapsed
  to fewer lines; `secrets_env_keys` read re-indented to the
  single-call form; `upsert_secret(...).map_err(|e| {…})?`
  collapsed.
- `crates/librefang-api/src/routes/secrets_env.rs`:
  `parent.join(format!(...))` fits on one line.
- `crates/librefang-api/src/routes/sidecar_describe.rs`:
  `describe_sidecar(command, args)` signature on one line.
- `crates/librefang-api/tests/channels_routes_test.rs`:
  `vec![SidecarSchemaField {...}]` re-formatted to the multi-line
  `vec![\n    Struct { ... },\n]` form rustfmt prefers for single
  big literals.
- `crates/librefang-api/tests/sidecar_describe_test.rs`:
  `describe_sidecar("python3", &[...]).await` collapsed.
- `crates/librefang-channels/src/sidecar.rs`:
  `build_spawn_env(home, ctx)` and `SidecarTask::new(config, home)`
  signatures collapsed; the `assert_eq!` chain in the test re-flowed
  because the new `got.get(...).map(|s| s.as_str())` chain doesn't
  fit the single-line form inside `assert_eq!(...)`.
- `crates/librefang-types/tests/config_default_roundtrip.rs`:
  re-flow the import list now that `LinkedInConfig` is gone; drop
  the double blank line after `voice_config_default_roundtrips_*`.

No logic changes — pure rustfmt diff.
@AIHunter83

Copy link
Copy Markdown
Contributor

@houko
I think it would have been better to stay on LinkedIn and Messenger because they are popular and in demand in other regions and countries.
What do you think

GQAdonis added a commit to GQAdonis/librefang that referenced this pull request May 19, 2026
Merges 10 further upstream librefang/librefang commits onto the
2026-05-17 merge (channel-adapter pruning: drops 18 in-process adapters
across librefang#5265/librefang#5267, migrates mastodon/gotify to sidecars, dep bumps).

Conflict resolution (7 files):
- librefang-api / librefang-cli Cargo.toml: kept surreal-backend in the
  default feature set, merged upstream's updated channel-set comment.
- docs architecture + integrations pages (en + zh): took upstream's
  channel-adapter content (in-process set + sidecar note, IRC/Mumble
  doc sections removed) but kept BossFang surface strings ("BossFang
  Protocol", not "LibreFang Protocol").
- Cargo.lock: regenerated; surreal-memory 95a83b6 / UAR 0f9385d pins
  preserved.

Post-merge fixes (build breakage surfaced under default `surreal-backend`):
- Cargo.toml: reverted upstream's opentelemetry-otlp 0.31 -> 0.32 bump.
  The in-repo comment forbids 0.32 until tracing-opentelemetry depends
  on opentelemetry 0.32 (0.32.1 still pins 0.31); 0.32 leaves two
  opentelemetry trees and breaks the SpanExporter trait bound in
  telemetry.rs. Same revert dependabot librefang#4947 already had.
- context_engine/scriptable/mod.rs: restored BossFang's TraceBackend
  abstraction the 2026-05-17 merge dropped — `trace_store` field
  re-typed to `Arc<dyn TraceBackend>`, added `with_trace_backend`
  builder (the context_engine.rs caller already expects it), moved
  circuit-breaker restore into it (backend is injected after
  with_plugin_name). Disambiguated a `Memory::remember` call.
- kernel-metering/lib.rs: `MeteringEngine::new` was missing the
  `exhaustion: None` field; dropped an unused `UsageStore` import.
- routes/agents.rs: `CloneAgentRequest` reverted pub(crate) -> pub —
  the `clone_agent` handler is registered by the librefang-testing
  router builder, so pub(crate) leaked a private type cross-crate.

SurrealDB schema parity: no new .surql migration required (no upstream
SQLite schema changes in this batch).

Verification:
- cargo check --workspace --lib: PASS
- cargo check -p librefang-llm-drivers --features uar-driver
  -p librefang-storage -p librefang-uar-spec -p librefang-memory: PASS
- python3 scripts/enforce-branding.py --check: PASS (exit 0)
- Tauri audit: productName BossFang, ai.bossfang.* identifiers,
  GQAdonis updater endpoint, minisign key E329A6B2863F1707 — intact.
houko added a commit that referenced this pull request May 20, 2026
…5322)

Delete the audit doc and update INDEX.md per the convention #5265
established: decrement narrative count (119→118), rollup table
Critical (7→6) + Total (120→119), remove the bullet under Critical,
and renumber the Pass 1 / Pass 2 triage-order lists so the numbering
stays contiguous after the item is dropped from slot #1.
houko added a commit that referenced this pull request May 20, 2026
Delete the audit doc + INDEX update per the #5265 convention:
narrative 119→118, Critical 7→6, Total 120→119, drop the bullet
under Critical, and drop the audit-export-401 leg from Pass 1
priority #4 (the entry was a combined 'list-sessions + audit-export'
line; list-sessions is addressed by a separate PR so the
audit-export-401 half is removed in isolation here).
houko added a commit that referenced this pull request May 20, 2026
Delete the audit doc + INDEX update per the #5265 convention:
narrative 119→118, Critical 7→6, Total 120→119, drop the bullet
under Critical, and drop the list-sessions-decode-on-poll leg
from Pass 1 priority #4 (paired with audit-export-401; the other
leg is addressed by a separate PR so this PR only removes its own
half).
houko added a commit that referenced this pull request May 20, 2026
Delete the audit doc + INDEX update per the #5265 convention:
narrative 119→118, High 37→36, Total 120→119, drop the bullet
under '### Data integrity (Pass 2)', and drop the entry from the
Pass 2 priority list (renumbering subsequent entries 9→8, 10→9,
…, 14→13 so the list stays contiguous).
houko added a commit that referenced this pull request May 20, 2026
…5328)

Delete the audit doc + INDEX update per the #5265 convention:
narrative 119→118, High 37→36, Total 120→119, drop the bullet
under '### Data integrity (Pass 2)', and drop the entry from
Pass 2 priority list (renumbering subsequent entries 10→9, 11→10,
…, 14→13 so the list stays contiguous).
houko added a commit that referenced this pull request May 20, 2026
…5322)

Delete the audit doc and update INDEX.md per the convention #5265
established: decrement narrative count (119→118), rollup table
Critical (7→6) + Total (120→119), remove the bullet under Critical,
and renumber the Pass 1 / Pass 2 triage-order lists so the numbering
stays contiguous after the item is dropped from slot #1.
houko added a commit that referenced this pull request May 20, 2026
Delete the audit doc + INDEX update per the #5265 convention:
narrative 119→118, Critical 7→6, Total 120→119, drop the bullet
under Critical, and drop the audit-export-401 leg from Pass 1
priority #4 (the entry was a combined 'list-sessions + audit-export'
line; list-sessions is addressed by a separate PR so the
audit-export-401 half is removed in isolation here).
houko added a commit that referenced this pull request May 20, 2026
Delete the audit doc + INDEX update per the #5265 convention:
narrative 119→118, Critical 7→6, Total 120→119, drop the bullet
under Critical, and drop the list-sessions-decode-on-poll leg
from Pass 1 priority #4 (paired with audit-export-401; the other
leg is addressed by a separate PR so this PR only removes its own
half).
houko added a commit that referenced this pull request May 20, 2026
Delete the audit doc + INDEX update per the #5265 convention:
narrative 119→118, High 37→36, Total 120→119, drop the bullet
under '### Data integrity (Pass 2)', and drop the entry from the
Pass 2 priority list (renumbering subsequent entries 9→8, 10→9,
…, 14→13 so the list stays contiguous).
houko added a commit that referenced this pull request May 20, 2026
…5328)

Delete the audit doc + INDEX update per the #5265 convention:
narrative 119→118, High 37→36, Total 120→119, drop the bullet
under '### Data integrity (Pass 2)', and drop the entry from
Pass 2 priority list (renumbering subsequent entries 10→9, 11→10,
…, 14→13 so the list stays contiguous).
houko added a commit that referenced this pull request May 22, 2026
…ad (audit: audit-export-401) (#5324)

* fix(dashboard/audit): use canonical getStoredApiKey for export download (audit: audit-export-401)

downloadExport in AuditPage.tsx:110 read the bearer token via
safeStorageGet('librefang-api-key'), which only checks localStorage.
But the credential layer was migrated in #3620 to prefer
sessionStorage and explicitly WIPE localStorage on save — so the
inline accessor always returned '', sent Authorization: Bearer
(empty), and every Export click 401'd for every signed-in user
since that release.

Drop the inline accessor and route through getStoredApiKey() from
api.ts (the same one authHeader() and every other token consumer
goes through — it checks sessionStorage first and falls back to
localStorage for legacy tokens).

Other safeStorageGet sites in pages/ were audited:
- TerminalPage.tsx (terminal.fontSize + hint keys) — different key
- ChatPage.tsx (librefang.chat.show_hand_agents) — different key
None target librefang-api-key, so no further changes needed.

Verified: pnpm typecheck + pnpm build clean.

* docs(issues): retire audit-export-401 (closed by #5324)

Delete the audit doc + INDEX update per the #5265 convention:
narrative 119→118, Critical 7→6, Total 120→119, drop the bullet
under Critical, and drop the audit-export-401 leg from Pass 1
priority #4 (the entry was a combined 'list-sessions + audit-export'
line; list-sessions is addressed by a separate PR so the
audit-export-401 half is removed in isolation here).

* docs(changelog): fix stale api.ts line numbers (3341-3342 → 3595-3596)

* chore(codegen): auto-regenerate openapi.json + sdk + schema baselines [skip ci]

* chore(codegen): auto-regenerate openapi.json + sdk + schema baselines [skip ci]

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
houko added a commit that referenced this pull request May 22, 2026
…ist-sessions-decode-on-poll) (#5326)

* perf(api/config): use count_sessions() instead of list_sessions().len() on status + snapshot (audit: list-sessions-decode-on-poll)

Two routes — /api/status and /api/dashboard/snapshot — computed
session_count via substrate.list_sessions().map(|s| s.len()).
list_sessions() returns Vec<serde_json::Value> with each row's
full rmp-encoded message history decoded, so calling .len() on
the vec required decoding every blob just to count rows.

The dashboard polls /api/dashboard/snapshot every 5s with
refetchIntervalInBackground=false, so every foreground operator
hits it continuously. At 100 sessions × 200 KB each, the daemon
burnt ~20 MB/s of needless decode + rmp alloc on the hot path —
for what is morphologically a SELECT COUNT(*).

MemorySubstrate::count_sessions() (indexed SELECT COUNT(*)) already
exists in librefang-memory/src/substrate.rs:391; both call sites
now use it.

* docs(issues): retire list-sessions-decode-on-poll (closed by #5326)

Delete the audit doc + INDEX update per the #5265 convention:
narrative 119→118, Critical 7→6, Total 120→119, drop the bullet
under Critical, and drop the list-sessions-decode-on-poll leg
from Pass 1 priority #4 (paired with audit-export-401; the other
leg is addressed by a separate PR so this PR only removes its own
half).

* test(api): pin /api/status + /api/dashboard/snapshot session_count

Two #[tokio::test] cases against TestServer that seed N sessions via
the memory substrate and assert the wire field reflects N — catches
the .unwrap_or(0) silent-pin regression class that the dashboard's
existing smoke test (only checks status='running') would miss.
Required by CLAUDE.md route-test policy and the audit doc.

Also: clarify the comment + CHANGELOG figure to '~20 MB per poll
(≈ 4 MB/s)' — the original ~20 MB/s number was off by the 5 s poll
period.

* chore(codegen): auto-regenerate openapi.json + sdk + schema baselines [skip ci]

* chore(codegen): auto-regenerate openapi.json + sdk + schema baselines [skip ci]

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
houko added a commit that referenced this pull request May 22, 2026
…-walking guard (audit: agent-cascade-delete-missing-tables) (#5328)

* fix(memory): purge pending_approvals on agent cascade-delete + schema-walking guard (audit: agent-cascade-delete-missing-tables)

execute_structured_agent_deletes enumerated 14 tables but missed
pending_approvals (v26, #3611). Authored approvals are scoped by
agent_id so a deleted agent left stale approval rows behind that
could fail-open on restart recovery.

Fix: add DELETE FROM pending_approvals WHERE agent_id = ?1 to the
cascade.

The other three tables the audit doc listed are not actually
agent-scoped at this layer:
- paired_devices — no agent_id column; devices are operator-scoped,
  not per-agent, and authenticate against the operator's API key.
  The 'bearer-token replay against deleted agent' framing maps to
  paired-device lifecycle, not agent lifecycle, and is out of scope
  here.
- idempotency_keys — no agent_id column; keys are scoped by request
  Idempotency-Key value.
- workflow_runs — workflow_id column exists, but workflows have no
  agent_id mapping in librefang-memory (definitions live in the
  kernel + YAML on disk). Per-agent scoping needs the kernel layer.

These exclusions are documented in the new test's doc-comment so
future maintainers don't reintroduce the audit's misreading.

New regression test agent_cascade_purges_every_agent_keyed_table:
walks sqlite_master for every user table, runs PRAGMA table_info
on each to discover the agent-keyed subset (any agent_id or
source_agent column), seeds rows for a target + control agent,
runs the cascade transaction (same execute_session_agent_deletes
+ execute_structured_agent_deletes pair that fires in
substrate.rs:1446-1447), and asserts the target's rows are gone.

A future migration that adds a new agent-keyed table without a
matching DELETE in the cascade will fail this test with the
offending table name printed.

246 librefang-memory lib tests green; clippy clean.

* docs(issues): retire agent-cascade-delete-missing-tables (closed by #5328)

Delete the audit doc + INDEX update per the #5265 convention:
narrative 119→118, High 37→36, Total 120→119, drop the bullet
under '### Data integrity (Pass 2)', and drop the entry from
Pass 2 priority list (renumbering subsequent entries 10→9, 11→10,
…, 14→13 so the list stays contiguous).

* test(memory): make agent-cascade guard actually seed rows

The schema-walking test seeded each agent-keyed table with
INSERT OR IGNORE (agent_id) only, so every table with another NOT NULL
column silently seeded zero rows — the post-cascade assert_eq!(.., 0)
passed trivially against rows that never existed (incl. pending_approvals,
the table this PR fixes).

Walk PRAGMA table_info and seed a valid, complete row per table (distinct
values via a monotonic counter so UNIQUE constraints never collide),
assert each target row actually landed before the cascade, and assert the
control agent's row survives. group_roster (nullable agent_id, chat-scoped)
is documented in a not_cascaded allowlist so the guard still flags any new
agent-keyed table added without a matching cascade DELETE.

* chore(codegen): auto-regenerate openapi.json + sdk + schema baselines [skip ci]

* docs(memory): correct group_roster comment in cascade-delete test

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
houko added a commit that referenced this pull request May 22, 2026
…error-generic-missing-fluent-key) (#5322)

* fix(i18n): define api-error-generic across all 6 locales (audit: api-error-generic-missing-fluent-key)

41+ HTTP 500 handlers in routes/{agents,tools_sessions}.rs build
their response body as:

  t_args("api-error-generic", &[("error", &e.to_string())])

But api-error-generic was never defined in any locale's errors.ftl.
The fallback at i18n.rs:163-164 returns the bare key on lookup
miss, so the $error interpolation never ran and every 5xx surfaced
the literal 'api-error-generic' to the client with diagnostic
context silently dropped — affecting agent-not-found-returns-500,
rusqlite-errors-leak, and the agent_clone 500-on-AgentAlreadyExists
case folded into the same audit item.

Stopgap per docs/issues/api-error-generic-missing-fluent-key.md:
define the key in en/ja/zh-CN/de/fr/es. en uses bare { $error };
the five other locales prefix with localized Error/エラー/错误/
Fehler/Erreur/Error so the rendered string differs per locale (the
per-locale 'must differ from en' assertion in
all_languages_have_same_keys requires this).

Long-term remediation (typed MemoryRouteError helper per
routes/memory.rs:198-215) is tracked separately.

Regression guards:
- api-error-generic added to all_languages_have_same_keys
- new api_error_generic_interpolates_underlying_error exercises
  every locale with a sample $error and asserts the rendered body
  is not the bare key AND contains the error string verbatim

22 i18n::tests green. cargo test -p librefang-types --lib i18n.

* docs(issues): retire api-error-generic-missing-fluent-key (closed by #5322)

Delete the audit doc and update INDEX.md per the convention #5265
established: decrement narrative count (119→118), rollup table
Critical (7→6) + Total (120→119), remove the bullet under Critical,
and renumber the Pass 1 / Pass 2 triage-order lists so the numbering
stays contiguous after the item is dropped from slot #1.

* fix(i18n/fr): apply French typography — space before colon in api-error-generic

Other entries in fr/errors.ftl (e.g. `Requete invalide :`, `Echec de l'analyse de
la configuration :`) all use the space-before-colon French typographic convention.
The new `api-error-generic` entry rendered `Erreur:` without the space — minor
visual drift from the surrounding file. One-character fix; `api_error_generic_interpolates_underlying_error` still green.

* chore(codegen): auto-regenerate openapi.json + sdk + schema baselines [skip ci]

* chore(codegen): auto-regenerate openapi.json + sdk + schema baselines [skip ci]

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/channels Messaging channel adapters area/ci CI/CD and build tooling area/docs Documentation and guides area/kernel Core kernel (scheduling, RBAC, workflows) size/XL 1000+ lines changed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants