Response to incident WA-2026-10-27 (the std::vector max_size() crash). Deep research archived under research/safe-cpp-practices (8 Tier 1, 37 Tier 2, 2 conclusions) on production-safe C++ that keeps development speed: OpenSSF compiler hardening, the C++ Core Guidelines bounds profile, integer-overflow practice, libFuzzer/sanitizer CI, the sanitizer overhead ladder, clang-tidy ratchets. The research prescribed an ASan+UBSan+_GLIBCXX_ASSERTIONS build of the parsers. Standing it up immediately caught a second, independent undefined-behavior bug that two manual reviews had missed: a protobuf varint loop that shifted by up to 70 bits (shift >= 64 is UB) on malformed input. Fixed and guarded by a deterministic test and the sanitizer net. - src/whatsapp/wa_proto.cpp: guard the wire-0 varint shift (the UB). - tests/whatsapp_test.cpp: add an overlong-varint hostile case. - tools/san-whatsapp.sh: the standing sanitizer net for detz::wa parsers. - WaClient frame capture: a frame that fails to decode/dispatch has its exact bytes recorded to <home>/whatsapp/last-error.log (bounded), so the still unreproduced live <iq> failure becomes a one-paste deterministic fix. Wired from the wizard and the whatsapp_send tool. - docs/cpp-safety.md: the project-wide playbook and design decisions (four layers: overflow-safe checked-reader parsing, cheap release hardening, the sanitizer test net, capture). docs/whatsapp.md section 13.6 records the follow-up and the honest open item. AGENTS.md makes it a standing rule; cap.md and mcp.md inherit it for their wire parsers. Verified: build, whatsapp_test (incl. hostile+fuzz), make lint, and tools/san-whatsapp.sh all green; research audit PASS. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
6.2 KiB
cap (taicpp)
Port of the CAP section of ../tai.py (CAP_VERSION ... TAI_CAP_MANIFEST),
normative text in ../cap.md. Namespace detz::cap, sources in src/cap/,
one class per .h/.cpp pair. The C++ names are the Python names in
camelCase; behavior is verified by running both programs (see Verification).
Wire parsing is untrusted input: every byte here comes off a socket, so the parsers follow cpp-safety.md (overflow-safe size checks, unsigned range-checked lengths, guarded shifts, no exception escaping the read loop, hostile-input tests under sanitizers). We do not accept regressions in that.
Spec delta (this milestone)
- Real
TAI_CAP_MANIFESTreplaces the one-line placeholder (CapAgentManifest::TAI_CAP_MANIFEST,--cap-manifestprints it plus theprint()newline; byte-identical totai.py --cap-manifest). - All Cap classes ported: events, errors, bindings, manifest, registry,
session config, stream-json, speak session, the four drivers, answerer,
session, fleet, CLI. The old
Orchestratorgrant/check placeholder is gone. - Layer
wire_*(namespacedetz::wire) supplies what Python's stdlib gave tai.py:Json(json),WireToml(tomllib),WireRegex(re),WireProc/WirePty(subprocess, PtyProc),WireHttp(urllib, http.client, http.server),WireArgParse(argparse),WireShlex,WireDifflib,WireHash,WireText,WireOs.
Parity table (Python -> C++)
| Python | C++ |
|---|---|
CAP_VERSION CAP_MANIFEST_SCHEMA CAP_PROFILE_CODING CAP_MANIFEST_REVISION CAP_PROBE_TIMEOUT CAP_ERROR_CODES CAP_ERROR_STRINGS CAP_SESSION_CONFIG_ENV CAP_SPEAK_MAX_TURNS CAP_SPEAK_DIFF_MAX CAP_PTY_SETTLE_MS CAP_PTY_IDLE_MS CAP_PTY_SUBMIT_GAP_MS CAP_PTY_RESUBMITS_MAX CAP_PTY_SCROLLBACK CAP_PTY_INIT_TIMEOUT CAP_STREAM_LINE_MAX CAP_STREAM_STDERR_KEEP CAP_SESSION_POLL CAP_TURN_TIMEOUT CAP_WORKTREE_DIR CAP_ROUTE_QUEUE_MAX CAP_FLEET_DIFF_MAX CAP_MENTION_RE CAP_ACP_VERSION CAP_ACP_TERMINALS_MAX CAP_ACP_TERM_BUF CAP_ACP_TERM_WAIT_MAX CAP_ACP_FS_MAX CAP_A2A_TIMEOUT CAP_ANSWER_TIMEOUT CAP_ANSWER_PARALLEL CAP_ANSWER_MARKER CAP_ANSWER_RISKS |
cap_constants.h (same names) |
CAP_ERROR_SYMBOLS |
capErrorNumeric() in cap_error.h |
TAI_CAP_MANIFEST |
CapAgentManifest::TAI_CAP_MANIFEST() |
CapError |
cap_error.h |
CapEvent |
CapEvent (KINDS, DIRECTIONS, REQUIRED, ENUMS, make, validate, direction) |
CapBinding |
CapBinding (ORDER, UNSUPPORTED, declared, select) |
CapManifest |
CapManifest |
CapRegistry |
CapRegistry |
CapSessionConfig |
CapSessionConfig |
CapStreamJson |
CapStreamJson |
CapSpeakSession, cap_interrupt, cap_speak_main |
CapSpeakSession, CapInterrupt, CapSpeakMain |
CapDriver |
CapDriver (CapDriver::create in cap_driver_factory.cpp) |
CapPtyDriver |
CapPtyDriver over WirePty and CapVtScreen (port of TuiVtScreen, CapScreen seam) |
CapPipeReader, cap_proc_kill, cap_proc_reap, cap_stderr_drain |
CapPipeReader, CapProcUtil, CapStderrDrain |
CapStreamDriver CapAcpDriver CapA2aDriver |
same names |
CapAnswerer |
CapAnswerer |
CapTurnResult CapSession CapFleet |
same names |
cap_cli_main cap_cli_help cap_resolve_main cap_chat_main cap_run_main cap_fleet_plan cap_print_result cap_build_answerer cap_answer_flags |
CapCli static methods |
Event kinds
cap.session.ready, cap.text_chunk, cap.tool_call.start,
cap.tool_call.delta, cap.tool_call.end, cap.plan, cap.thought,
cap.ask_user, cap.permission.request, cap.usage, cap.error,
cap.artifact, cap.fs.change, cap.pty.raw_bytes,
cap.user_input.inject, cap.cancel, cap.session.config,
cap.ask_user.answer, cap.permission.response.
Errors
Symbols: cap_manifest_missing, cap_session_config_missing,
cap_unsupported_capability, cap_budget_exceeded, cap_invalid_answer,
cap_unknown_permission, cap_queued_input_unsupported,
cap_cancel_unsupported, cap_binding_unavailable,
cap_profile_unsupported, cap_scope_violation, cap_path_not_found,
cap_io_error, cap_terminal_unavailable, cap_terminal_killed.
String codes: rate_limit, auth_error, tool_timeout, context_overflow,
model_unavailable, pty_died, parse_failed, git_dirty, test_failed,
merge_conflict, language_server_down.
Seams (host-provided)
CapAgentPort / CapSpeakHost (the agent for --cap-speak), WireLog
(debug log sink), WireApp::setVersion (VERSION on the wire),
CapPtyDriver::setScreenFactory (swap the VT screen). Details and the exact
hooks: docs/wiring/capmcp.md.
Differences from Python (all declared)
CapAnswererruns[binary, --auto, prompt]directly; Python ran[sys.executable, tai.py, ...]. Default binary is/proc/self/exe.CapVtScreenparses SGR but does not store colors/attributes (text-only consumer) and keeps no scrollback history.- Regex: ECMAScript engine with Python syntax translation. Python-only syntax
beyond named groups,
\A,\Z,$, bare{/}, leading(?i)is not translated; compile error texts differ fromre.error. - argparse help is plain (no 3.14 color).
- Dates in TOML come back as strings.
- Terminal
terminal/outputkeeps theread(65536)blocking-chunk behavior. - Signals: SIGINT/SIGTERM set a flag polled by waits (
CapInterrupt).
Agents, models and @agent mentions
taicpp lists the installed CAP agents, discovers their models (manifest [models] list, optional
--cap-models probe), and lets a message start with @agent, @agent:model or @model, matched fuzzily and
run in a supervised panel on the multiplexer screen. Everything about it is in
cap-agents.md; the matching in fuzzy.md; the panels, supervision and result
collection in orchestration.md. CAP itself (events, bindings, manifest, fleet) is
unchanged by this and stays specified on this page.
Verification
tests/capmcp_diff.py (JSON, TOML, str(), float repr, regex vs CPython),
tests/capmcp_cli_diff.py (--cap-manifest, --cap resolve|chat|run incl.
stream-json, PTY, ACP and A2A fake agents, argparse errors at four widths vs
tai.py), tests/capmcp_unit.cpp (tables and drift check).