docs(release): prepare 0.27.0 — curated changelog, rag/map sweep, concat rebuild script (#662)

This commit is contained in:
Renzo F
2026-07-23 11:33:02 +02:00
committed by GitHub
parent d8841279a8
commit dd4c3c3ed0
26 changed files with 501 additions and 81 deletions
@@ -42,3 +42,9 @@ The three custom validators above are not covered by that exclusion — they sca
## Scanner Auto-Ban
A separate layer targets automated scanners (not agents — agents run on Docker-internal IPs). Repeated probes to scanner fingerprints on `/api` paths (`recon`/`sensitive_file`/`cms_probing` categories) trip a per-IP auto-ban in active mode, and nginx drops the classic root scanner paths (`/.env`, `/wp-login.php`, `/.git/config`, …) at the edge with `444`. This does not affect legitimate agent traffic to the gateway verbs.
## Internal Agent Mesh Exemption
Agents reach the orchestrator DIRECTLY on the docker bridge (no nginx hop), HMAC-authenticated — the guard's WAF/IP-ban/rate-limit is meant for the EXTERNAL attack surface arriving through nginx, not for that already-authenticated internal traffic. A `whitelist` of loopback (`127.0.0.1`/`::1`) plus docker's default bridge address-pool range (`172.16.0.0/12`) skips WAF/ban/rate-limit checks entirely for requests from those addresses — without it, an ordinary journal/note body tripping a WAF signature would IP-ban the whole agent container, wedging every subsequent verb call (`dm`, `i_am_idle`, ...) behind it.
This whitelist is deliberately narrow — NOT the full RFC1918 range. `10.0.0.0/8` and `192.168.0.0/16` are excluded on purpose: those also cover any real LAN client hitting nginx, not just the docker mesh, and with `trusted_proxy_depth=1` a genuine LAN browser's real IP survives the one XFF hop, so including them would let real external traffic skip the WAF right alongside agent traffic. A known ceiling remains: this can't distinguish a real docker-bridge peer from host-loopback/NAT'd traffic landing on the same address family, so a host-proxied chain (e.g. Tailscale Serve terminating on the host before nginx) can still resolve into this range and ride the exemption — see `ROBOCO_GUARD_TRUSTED_CHAIN_PEERS` above for the separate mechanism that scopes that specific shape.
+2 -2
View File
@@ -22,7 +22,7 @@ Both are bounded by `ROBOCO_X_MAX_OPEN_POSTS` (rolling cap on concurrently-open
## Drafting
Draft bodies are written by a **local-model** chat call (`_chat`, hitting `ROBOCO_LOCAL_LLM_BASE_URL` — never a cloud LLM in the hot path), in a fixed Head-of-Marketing voice prompt, then hard-clamped to 280 characters (`_clamp_tweet`). A local-model failure falls back to a plain template body rather than failing the draft outright. Drafting is **not** an agent spawn — no agent (including Head of Marketing) is spawned to write these; see `docs/rag/roles/head-marketing.md` for why the HoM's tool surface doesn't change.
Draft bodies are written by a **local-model** chat call (`_chat`, hitting `ROBOCO_LOCAL_LLM_BASE_URL` — never a cloud LLM in the hot path), in a full Head-of-Marketing voice prompt (the reasoning-backed VOICE GUIDE plus a banned-word/AI-slop list and style exemplars — no em dashes, no "game-changer"/"seamless"/etc., no exclamation pileups, aimed well under 240 characters so the 280 clamp never truncates mid-sentence), then hard-clamped to 280 characters (`_clamp_tweet`). A release-post local-model failure still falls back to a plain template body (a release announcement always has something real to say); a failed reply draft instead skips origination entirely rather than shipping a generic "Thanks for the mention!". Drafting is **not** an agent spawn — no agent (including Head of Marketing) is spawned to write these; see `docs/rag/roles/head-marketing.md` for why the HoM's tool surface doesn't change.
## Ownership and the CEO gate
@@ -34,7 +34,7 @@ The CEO acts through panel-only REST, CEO-role-gated (`require_ceo_role`), never
|----------|--------|
| `GET /api/x/posts` | List every held draft (both sources) awaiting decision. |
| `POST /api/x/posts/{task_id}/approve` | Post to X (optionally with an edited body, still 280-char clamped). Idempotent — approving an already-posted draft returns `already_posted` without a second API call. |
| `POST /api/x/posts/{task_id}/reject` | Cancel the draft with a reason. Terminal — a rejected draft is never posted: `approve` refuses a CANCELLED draft outright (returns `already_rejected` without calling the X API), so a stale approve can't resurrect it; a fresh one originates on the next cycle/release if still relevant. |
| `POST /api/x/posts/{task_id}/reject` | Cancel the draft with a reason. The cancelled draft itself is never posted `approve` refuses a CANCELLED draft outright (returns `already_rejected` without calling the X API), so a stale approve can't resurrect it. A non-blank reason also schedules a redraft: the same source (release/reply/spotlight) is revised by the local model with the CEO's feedback folded in as guidance, and one fresh held draft appears in the queue — the feedback loop closes immediately rather than waiting on the next cycle/release. A local-model failure or empty revision originates nothing (no degraded copy). |
| `GET /api/x/credentials` | Whether all four OAuth secrets are stored (`has_credentials` boolean — never the secrets). |
| `POST /api/x/credentials` | Set (or, passing all four empty, clear) the four secrets. All-or-nothing — a partial set raises a validation error. |
+2
View File
@@ -46,6 +46,8 @@ commit(message="Add rate limiting endpoint", files=["roboco/api/routes/rate.py"]
There is no separate `push` step and no separate `create_pr` step. Both are side-effects of the lifecycle transitions the verbs already drive.
On a project that checks in generated artifacts, the push step also regenerates them: right before your branch is pushed, the project's codegen command runs in your worktree, and any drift it produces is committed into the SAME push automatically — you don't need to run it yourself, and a broken/timing-out codegen command never blocks your push (it just logs and lets you proceed; a real drift then surfaces on CI's own drift gate instead).
### PMs → `complete` (roboco-flow)
```python
+6
View File
@@ -108,6 +108,12 @@ Don't checkout by hand — there is no `roboco_git_checkout` tool.
Some low-level git error messages still say "GitHub API" in their text regardless of which forge the project actually uses (a known wording gap, not a routing bug) — the underlying failure is real even when the vendor name in the message is wrong. Diagnose from the actual symptom (missing token, PR-not-found, merge conflict, etc.), not from the vendor name in the message.
## Rebase/sync_branch refused on a protected branch
**Cause:** `sync_branch`/rebase refuses to force-push a branch whose name matches the project's operator-declared `protected_branches` list, its environment-ladder rungs, or the hardcoded `master`/`main` floor. You should never legitimately be working on one of these directly — your own feature branch is never a protected name.
**Fix:** If you see this, your task's recorded branch is misconfigured. `unclaim(task_id)` and re-claim to rebuild a proper branch; don't try to force through it.
## FORCE_PUSH_FORBIDDEN
**Cause:** Force-push is CEO-only. Anyone else attempting it (typically because their branch diverged) is denied.
+8
View File
@@ -111,6 +111,14 @@ escalate_to_ceo(task_id=parent_id, reason="...")
**Solution**: Verify with the repo-wide check (not a scoped one, since scoping is a no-op) and record the zero-diff finding in a `decision` journal entry. If the task's acceptance criteria are already met with nothing left to change in the named files, do not keep re-running the same check — escalate once with the concrete verification so a PM can either stamp the task as satisfied-by-upstream/cancel it, or direct a small verification commit outside the target file(s) to satisfy the commit gate.
## Task Auto-Blocked for Budget
**Symptom**: Your in-progress task flips to `blocked` with a budget marker, and you weren't the one who blocked it.
**Cause** (only when task budgets are armed): a periodic sweep prices the task's own spend against its `budget_usd` (or the `TaskType` default when unset) and blocks it before you get a chance to finish gracefully, so the dispatcher won't respawn onto an already-over-budget task.
**Fix**: This is a PM/CEO decision, not yours to route around — `unblock` re-checks live spend and refuses again while still over the cap. Escalate (`i_am_blocked`/`escalate_up`) naming the budget breach; the CEO either raises the task's/project's cap or the PM redirects the remaining work.
## Invalid Task Status for Operation
**Error**: "Task is in [status], expected [expected_status]"
+3 -1
View File
@@ -27,7 +27,9 @@ claim_doc_task(task_id) # Documenter
The claim verb both claims and starts the task — there is no separate `start` call. For developers, `i_will_work_on` also creates the `feature/{team}/{task-hierarchy}` branch and **adds a dedicated per-task worktree** at `{clone_root}/.worktrees/{task-id-first-8}/`, checking out the branch there. Your container is started with that worktree as its cwd, and the clone root's HEAD is never moved by the claim — so a second claim (or a coordinator PM's many parallel roots) never overwrites your first task's uncommitted work. See `docs/rag/architecture/workspaces.md` for the worktree model.
Exactly one active WorkSession exists per task at a time (enforced in the service layer and by a DB unique index). A re-claim — pool release, reaper unclaim, escalation redirect — supersedes any prior agent's stale active session for that task and re-points the worktree at the new claim.
Exactly one active WorkSession exists per task at a time (enforced in the service layer and by a DB unique index). A re-claim — pool release, reaper unclaim, escalation redirect, or your own `i_will_work_on`/`i_will_plan` re-entry — supersedes any prior agent's stale active session for that task and re-points the worktree at the new claim.
If the branch you're reclaiming was cut a while ago, the base it branched from may have advanced since (a sibling merged into the parent, or a cell PR landed on root). The claim automatically folds that advance into your existing branch: an already-ancestor base is a no-op, a clean merge just happens silently, and a real conflict leaves your branch exactly where it was and appends a note plus a `dev_notes` line telling you to run `sync_branch(task_id)` to finish it by hand — the claim itself never fails over this. This only runs on developer/cell_pm/main_pm claims of a `pending`/`needs_revision` task; a QA/documenter claim or a PM gate claim reviews the branch exactly as pushed and never touches it.
If your task has `dependency_ids` in the same repo, the fresh branch cut also backfills each dependency's already-landed work when it sits outside your branch's own ancestor chain (a same-parent sibling or a same-project wave usually doesn't need this — the shared base already has it; a cross-subtree or cross-cell dependency edge can). This is a content assist, not a gate: a clean merge is silent, and a real conflict aborts the merge (your branch is left exactly at its cut point) and appends a note to the task naming the conflicting branch and files — resolve it by hand (merge the named branch into yours) before assembling your PR. A cross-repo dependency has no shared git history and is skipped entirely.