Files
tai/taicpp/docs/cap.md
T
retoorandClaude Opus 4.8 79ae6f27da cpp-safety: safe-C++ playbook, fix a second UB, and a sanitizer net
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>
2026-10-27 19:03:32 +01:00

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_MANIFEST replaces the one-line placeholder (CapAgentManifest::TAI_CAP_MANIFEST, --cap-manifest prints it plus the print() newline; byte-identical to tai.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 Orchestrator grant/check placeholder is gone.
  • Layer wire_* (namespace detz::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)

  • CapAnswerer runs [binary, --auto, prompt] directly; Python ran [sys.executable, tai.py, ...]. Default binary is /proc/self/exe.
  • CapVtScreen parses 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 from re.error.
  • argparse help is plain (no 3.14 color).
  • Dates in TOML come back as strings.
  • Terminal terminal/output keeps the read(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).