|
| 1 | +# Deployment Readiness Assessment — 2026-04-14 |
| 2 | + |
| 3 | +Adjutant v2.0.0 monorepo. Assessed against the on-disk codebase in this repository. |
| 4 | + |
| 5 | +Generated using the evaluation prompt in `docs/reference/deployment-readiness.md`. |
| 6 | + |
| 7 | +--- |
| 8 | + |
| 9 | +## 1. Deployment Readiness Verdict |
| 10 | + |
| 11 | +**Verdict: Not Ready** |
| 12 | + |
| 13 | +The single biggest blocker is that the public release contract is inconsistent at |
| 14 | +the user boundary: installation and command documentation materially disagree with |
| 15 | +shipped behavior, shell-based scheduled command execution still needs hardening, |
| 16 | +and this assessment does not yet have a recorded clean full-suite release gate. |
| 17 | + |
| 18 | +**Deployment** here means: a public GitHub Release that ships the Adjutant tarball |
| 19 | +and checksum, supports the documented installation flow, and gets a new user to a |
| 20 | +working local install via the published setup or installer path. |
| 21 | + |
| 22 | +--- |
| 23 | + |
| 24 | +## 2. Completeness Audit (MECE — 7 branches) |
| 25 | + |
| 26 | +### A. Core runtime is implemented, but release confidence is reduced by drift |
| 27 | + |
| 28 | +- **Status:** Partial |
| 29 | +- **Evidence:** CLI and runtime entrypoints exist in `src/adjutant/cli.py`. |
| 30 | + Dispatcher/auth/rate-limit/feature gating are centralized in |
| 31 | + `src/adjutant/messaging/dispatch.py`. Lifecycle control exists in |
| 32 | + `src/adjutant/lifecycle/control.py` and `src/adjutant/lifecycle/cron.py`. |
| 33 | + Lockfile state is handled in `src/adjutant/core/lockfiles.py`. Process |
| 34 | + management exists in `src/adjutant/core/process.py`. |
| 35 | +- **Evidence:** The security-critical dispatch path is explicit and centralized: |
| 36 | + auth check in `dispatch.py`, rate limiting in `_check_rate_limit()`, and |
| 37 | + feature-gate rejection in the `_FEATURE_GATES` block. |
| 38 | +- **Gap:** The runtime surface is real, but release confidence is reduced by |
| 39 | + command/docs drift and the absence of a clearly demonstrated clean release gate. |
| 40 | + |
| 41 | +### B. Setup and onboarding exist, but installer distribution is only partially integrated |
| 42 | + |
| 43 | +- **Status:** Partial |
| 44 | +- **Evidence:** Interactive setup exists in `src/adjutant/setup/wizard.py`. |
| 45 | + Repair and uninstall flows exist in `src/adjutant/setup/repair.py` and |
| 46 | + `src/adjutant/setup/uninstall.py`. Service installation support exists in |
| 47 | + `src/adjutant/setup/steps/service.py`. A curl-style installer exists in |
| 48 | + `src/adjutant/setup/install.py`. |
| 49 | +- **Evidence:** Public release packaging exists in `.github/workflows/release.yml`. |
| 50 | +- **Gap:** The installer exists as a standalone Python entrypoint, but the public |
| 51 | + release flow still tells users to clone or unpack the tarball, create a venv, |
| 52 | + `pip install -e .`, and run `adjutant setup`. |
| 53 | + `.github/workflows/release.yml:92-111` does not present the installer as the |
| 54 | + canonical path, so the distribution story is only partially integrated. |
| 55 | + |
| 56 | +### C. Security posture is strong in design, but subprocess shell usage is a release risk |
| 57 | + |
| 58 | +- **Status:** Partial |
| 59 | +- **Evidence:** Single-operator authentication is enforced in |
| 60 | + `src/adjutant/messaging/dispatch.py` by comparing `from_id` to `chat_id`. |
| 61 | + Feature-gated commands fail closed on config parse failure in the same module. |
| 62 | + `.env` parsing is hand-rolled and non-exec-based in `src/adjutant/core/env.py`. |
| 63 | + KB sandboxing is documented in `docs/guides/knowledge-bases.md` and scaffolded |
| 64 | + in the KB management path. |
| 65 | +- **Evidence:** Source-wide subprocess counts gathered during this audit: |
| 66 | + `subprocess.run(...)` = **44**, `subprocess.Popen(...)` = **6**, |
| 67 | + `shell=True` = **3**, `os.system(...)` = **0**. |
| 68 | +- **Evidence:** The `shell=True` call sites are: |
| 69 | + - `src/adjutant/cli.py:1097` |
| 70 | + - `src/adjutant/capabilities/schedule/notify_wrap.py:78-84` |
| 71 | + - `src/adjutant/capabilities/schedule/install.py:237-240` |
| 72 | +- **Gap:** Scheduled-command execution still relies on shell-string execution in |
| 73 | + public-release code paths. Even if these inputs are operator-authored, they |
| 74 | + should be tightened before public release. |
| 75 | + |
| 76 | +### D. Test coverage is large, but release cleanliness is not yet demonstrated |
| 77 | + |
| 78 | +- **Status:** Partial |
| 79 | +- **Evidence:** Test tree count gathered during this audit found |
| 80 | + **61 unit test files / 1422 unit test functions** and |
| 81 | + **2 integration test files / 19 integration test functions**, for a total of |
| 82 | + **1441** discovered test functions. |
| 83 | +- **Evidence:** Security-relevant coverage exists in |
| 84 | + `tests/unit/test_messaging_dispatch.py`, `tests/integration/test_feature_gating.py`, |
| 85 | + `tests/unit/test_env.py`, `tests/unit/test_kb_manage.py`, |
| 86 | + `tests/unit/test_schedule_install.py`, and related backend/config tests. |
| 87 | +- **Gap:** Release readiness requires a clearly recorded clean full-suite pass. |
| 88 | + At the time of this assessment, that release gate was not documented here as a |
| 89 | + final clean result. Repo docs and builder guidance also still contain stale, |
| 90 | + lower approximate test-count figures. |
| 91 | + |
| 92 | +### E. Public distribution infrastructure exists, but is not yet coherent end-to-end |
| 93 | + |
| 94 | +- **Status:** Partial |
| 95 | +- **Evidence:** `.github/workflows/release.yml` verifies tag vs `VERSION`, builds |
| 96 | + a tarball, generates a `.sha256`, and publishes a GitHub Release. Versioning is |
| 97 | + driven from `VERSION` through packaging metadata. Self-update logic exists in |
| 98 | + `src/adjutant/lifecycle/update.py`. |
| 99 | +- **Gap:** Artifact production is implemented, but the install story is split |
| 100 | + between release assets, manual editable installation, and a separate installer. |
| 101 | + Public distribution exists mechanically, not yet as one coherent user flow. |
| 102 | + |
| 103 | +### F. Documentation is extensive, but materially out of sync with shipped behavior |
| 104 | + |
| 105 | +- **Status:** Partial |
| 106 | +- **Evidence:** Public docs exist across `README.md`, `docs/guides/`, |
| 107 | + `docs/architecture/`, and `site/`. |
| 108 | +- **Evidence:** `README.md:71-79` advertises Telegram commands `/models`, |
| 109 | + `/memory`, and `/news`, but targeted search found no corresponding command |
| 110 | + handlers in `src/adjutant/messaging/telegram/*.py`. |
| 111 | +- **Evidence:** `README.md:122` links to `docs/getting-started/installation.md`, |
| 112 | + while the current tracked guide structure includes `docs/guides/getting-started.md`. |
| 113 | +- **Evidence:** `docs/guides/commands.md` still references backend-native web |
| 114 | + server concepts such as `cloudcli`, while current backend capability objects in |
| 115 | + `src/adjutant/core/backend_opencode.py` and |
| 116 | + `src/adjutant/core/backend_claude_cli.py` explicitly set `web_server=False`. |
| 117 | +- **Gap:** A public release cannot rely on docs that advertise commands, links, |
| 118 | + or backend behavior the shipped code does not provide. |
| 119 | + |
| 120 | +### G. Cross-platform support is implemented for macOS and Linux, with operational caveats |
| 121 | + |
| 122 | +- **Status:** Implemented |
| 123 | +- **Evidence:** Platform/service support exists in the setup and platform layers, |
| 124 | + including macOS launchd, Linux systemd-user, and cron-backed scheduled jobs. |
| 125 | + These paths are represented in `src/adjutant/setup/steps/service.py`, |
| 126 | + `src/adjutant/core/platform.py`, and schedule modules under |
| 127 | + `src/adjutant/capabilities/schedule/`. |
| 128 | +- **Gap:** The remaining issue is not missing platform support. It is the safety |
| 129 | + and robustness of shell-based scheduled-command execution across those supported |
| 130 | + platforms. |
| 131 | + |
| 132 | +--- |
| 133 | + |
| 134 | +## 3. Code Quality Deep Dive |
| 135 | + |
| 136 | +### Unused or weakly integrated code still exists |
| 137 | + |
| 138 | +- **Declared-but-unused dependency:** `rich` appears to be unused as a runtime |
| 139 | + import. Search in `src/` found no actual `rich` import usage; only incidental |
| 140 | + text matches. |
| 141 | +- **Standalone-but-unwired installer:** `src/adjutant/setup/install.py` is a real |
| 142 | + installer, but the public release workflow still routes users through manual |
| 143 | + clone or tarball extraction plus editable install. |
| 144 | +- **Decorative or stale docs abstractions:** docs still describe retired |
| 145 | + backend-native web-server behavior while backend capability objects explicitly |
| 146 | + declare `web_server=False`. |
| 147 | + |
| 148 | +### Broad exception handling is frequent |
| 149 | + |
| 150 | +- **Count:** `except Exception` in `src/` = **100** |
| 151 | +- **Approximate categories gathered during this audit:** |
| 152 | + - `catch_and_log`: **41** |
| 153 | + - `fallback_default`: **28** |
| 154 | + - `silent_swallow`: **20** |
| 155 | + - `user_visible_fallback`: **3** |
| 156 | + - `reraises_or_raises`: **5** |
| 157 | + - `other`: **3** |
| 158 | +- **Observation:** `src/adjutant/core/config.py` returns defaults on parse/load |
| 159 | + errors. That is acceptable in some branches, but security-sensitive paths must |
| 160 | + explicitly fail closed. `src/adjutant/messaging/dispatch.py` does this correctly |
| 161 | + for feature-gated commands. |
| 162 | + |
| 163 | +### Subprocess safety is the sharpest code-quality and security intersection |
| 164 | + |
| 165 | +- **Counts:** `subprocess.run` = **44**, `subprocess.Popen` = **6**, |
| 166 | + `shell=True` = **3**, `os.system` = **0**. |
| 167 | +- **High-risk or high-scrutiny call sites:** |
| 168 | + - `src/adjutant/cli.py:1095-1097` — scheduled job execution via shell |
| 169 | + - `src/adjutant/capabilities/schedule/notify_wrap.py:78-84` — wrapper runs a |
| 170 | + command string via shell |
| 171 | + - `src/adjutant/capabilities/schedule/install.py:237-240` — KB-backed |
| 172 | + scheduled jobs via shell |
| 173 | +- **Assessment:** These do not appear to be directly fed from arbitrary Telegram |
| 174 | + input, but they are still the main hardening target before a public installer |
| 175 | + release. |
| 176 | + |
| 177 | +### Naming and boundary violations are small but real |
| 178 | + |
| 179 | +- **Private helper imports discovered during this audit:** |
| 180 | + - `src/adjutant/capabilities/schedule/install.py` imports `_resolve_command` |
| 181 | + - `src/adjutant/capabilities/kb/query.py` imports `_get_kb` |
| 182 | +- **Assessment:** This weakens module boundaries and violates the project's own |
| 183 | + naming conventions for underscore-prefixed internals. |
| 184 | + |
| 185 | +### Dependency hygiene is imperfect |
| 186 | + |
| 187 | +- Runtime dependencies are small and mostly justified. |
| 188 | +- `rich` appears unused. |
| 189 | +- Public docs still describe old backend or web-server concepts that are no |
| 190 | + longer reflected by code. |
| 191 | +- This is not a release blocker on its own, but it is visible polish debt. |
| 192 | + |
| 193 | +--- |
| 194 | + |
| 195 | +## 4. Critical Path: P0 / P1 / P2 |
| 196 | + |
| 197 | +### P0 — blocks release |
| 198 | + |
| 199 | +1. **Public docs advertise commands the Telegram dispatcher does not implement** |
| 200 | + - **Files:** `README.md`, `docs/guides/commands.md`, |
| 201 | + `src/adjutant/messaging/dispatch.py`, |
| 202 | + `src/adjutant/messaging/telegram/commands.py` |
| 203 | + - **Action:** Align public command docs to actual shipped handlers, or |
| 204 | + implement the missing commands before release. |
| 205 | + |
| 206 | +2. **Release and install story is not coherent end-to-end** |
| 207 | + - **Files:** `.github/workflows/release.yml`, `src/adjutant/setup/install.py`, |
| 208 | + `README.md` |
| 209 | + - **Action:** Pick one supported public install path and document it |
| 210 | + consistently. If the Python installer is the intended story, wire and |
| 211 | + publish it as such. If not, stop presenting it as part of release readiness. |
| 212 | + |
| 213 | +3. **Shell-based scheduled command execution needs hardening before public ship** |
| 214 | + - **Files:** `src/adjutant/cli.py`, |
| 215 | + `src/adjutant/capabilities/schedule/notify_wrap.py`, |
| 216 | + `src/adjutant/capabilities/schedule/install.py` |
| 217 | + - **Action:** Replace shell-string execution with argument-list execution where |
| 218 | + possible, or constrain and validate command generation so no user- or |
| 219 | + config-derived string reaches `shell=True` unsafely. |
| 220 | + |
| 221 | +4. **Release gate requires a clearly recorded clean full test run** |
| 222 | + - **Files:** `tests/`, release/testing docs, backend and schedule test paths |
| 223 | + - **Action:** Run the full suite, resolve any failures, and make a clean full |
| 224 | + pass the explicit release gate before tagging. |
| 225 | + |
| 226 | +### P1 — degrades quality |
| 227 | + |
| 228 | +1. **Documentation still references retired backend-native web server behavior** |
| 229 | + - **Files:** `docs/guides/commands.md`, `docs/guides/backends.md`, |
| 230 | + `docs/guides/configuration.md`, `README.md` |
| 231 | + - **Action:** Remove CloudCLI or opencode-web-era references and align docs to |
| 232 | + the current `web/` architecture. |
| 233 | + |
| 234 | +2. **Unused runtime dependency likely remains** |
| 235 | + - **Files:** `pyproject.toml` |
| 236 | + - **Action:** Remove `rich` if it is truly unused, or add the missing usage |
| 237 | + intentionally. |
| 238 | + |
| 239 | +3. **Private helper imports weaken code boundaries** |
| 240 | + - **Files:** `src/adjutant/capabilities/schedule/install.py`, |
| 241 | + `src/adjutant/capabilities/kb/query.py` |
| 242 | + - **Action:** Promote these helpers to public APIs or stop importing |
| 243 | + underscore-prefixed functions across modules. |
| 244 | + |
| 245 | +4. **Exception swallowing remains high** |
| 246 | + - **Files:** distributed across `src/` |
| 247 | + - **Action:** Review silent-swallow and fallback-default cases, especially |
| 248 | + around config, filesystem, and subprocess branches. |
| 249 | + |
| 250 | +### P2 — acceptable to defer |
| 251 | + |
| 252 | +- Fuzz or property-style testing for hand-rolled parsers (`.env`, registry, |
| 253 | + NDJSON, Claude JSON) |
| 254 | +- Further reduction of broad exception usage in non-critical paths |
| 255 | +- Cleanup of stale historical doc references and outdated test-count statements |
| 256 | + |
| 257 | +--- |
| 258 | + |
| 259 | +## 5. Structural Strengths Worth Protecting |
| 260 | + |
| 261 | +1. **Centralized dispatch security envelope** |
| 262 | + - `src/adjutant/messaging/dispatch.py` keeps auth, rate limiting, feature |
| 263 | + gating, and routing in one place. |
| 264 | + - This is worth protecting because fail-closed behavior remains reviewable and |
| 265 | + testable. |
| 266 | + |
| 267 | +2. **Backend abstraction with explicit capability flags** |
| 268 | + - `src/adjutant/core/backend.py` plus backend-specific capability objects form |
| 269 | + a good boundary. |
| 270 | + - This is worth protecting because optional features can degrade via declared |
| 271 | + capabilities instead of backend-specific conditionals scattered everywhere. |
| 272 | + |
| 273 | +3. **Lockfile-based lifecycle model** |
| 274 | + - `KILLED` and `PAUSED` precedence is simple and operationally legible. |
| 275 | + - This is worth protecting because it reduces hidden state and keeps recovery |
| 276 | + logic understandable. |
| 277 | + |
| 278 | +4. **Hand-rolled, non-exec parsing for environment and config-adjacent inputs** |
| 279 | + - The `.env` parser and related small parsers avoid dangerous shell sourcing |
| 280 | + behavior. |
| 281 | + - This is worth protecting because it removes an entire class of injection and |
| 282 | + config-evaluation surprises. |
| 283 | + |
| 284 | +5. **KB sandbox model** |
| 285 | + - KB queries and KB-local operations are scoped through controlled boundaries. |
| 286 | + - This is worth protecting because it prevents the main runtime from casually |
| 287 | + traversing external project trees. |
| 288 | + |
| 289 | +--- |
| 290 | + |
| 291 | +## 6. Implementation Roadmap (Immediate / Short-term / Long-term) |
| 292 | + |
| 293 | +### Immediate (before any public release) |
| 294 | + |
| 295 | +- **Make the full test suite green and record the result** — **Small** |
| 296 | +- **Align README and docs with actual Telegram and CLI commands** — **Medium** |
| 297 | +- **Decide and unify the public installation path** — **Medium** |
| 298 | +- **Harden all `shell=True` schedule execution paths** — **Medium** |
| 299 | +- **Remove retired backend web-server references from docs** — **Small** |
| 300 | + |
| 301 | +**Sequencing dependencies:** |
| 302 | + |
| 303 | +- Docs alignment should follow the installation-path decision. |
| 304 | +- Release should wait on shell hardening and a confirmed clean full-suite pass. |
| 305 | + |
| 306 | +### Short-term (next minor) |
| 307 | + |
| 308 | +- **Remove unused dependency or dependency drift (`rich`)** — **Small** |
| 309 | +- **Clean private-helper cross-imports** — **Small** |
| 310 | +- **Audit and reduce silent `except Exception` cases in non-critical paths** — |
| 311 | + **Medium** |
| 312 | +- **Refresh stale test-count and architecture statements in docs** — **Small** |
| 313 | + |
| 314 | +### Long-term (next major) |
| 315 | + |
| 316 | +- **Add stronger parser fuzz or property tests** — **Medium** |
| 317 | +- **Refactor schedule command resolution toward structured argv throughout** — |
| 318 | + **Medium** |
| 319 | +- **Continue tightening backend/docs/feature-contract discipline** — **Medium** |
0 commit comments