mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
98ee4d79e5b10cb6942230e52aede4ecc84aa2bb
12
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
98ee4d79e5 |
fix(x): thread mention replies and audit every post outcome
x_reply drafts (the one case X's 2026-02-23 reply policy allows - the author summoned us) now post as real threaded replies via the carried mention id; barfly drafts stop sending in_reply_to_tweet_id entirely. Failed posts log a server-side warning and write an x_post.post_failed audit row; successes write x_post.posted - at the _post chokepoint so both approve routes are covered. |
||
|
|
401f8a2cc9 |
feat(board): Board Programs — the complete twelve-program catalog (Phases 1-3) (#699)
* feat(board): Pest Control — the first project-scoped Board Program The Product Owner hunts latent defects (what the org records but nobody reads): a weekly cycle — accelerated off-schedule when the trailing-7-day rework rate crosses pest_rework_threshold, with the cheap dedup/scope gates evaluated before the metrics queries — opens one held exploration task against the least-recently-explored opted-in project (deterministic round-robin; opted_in_projects gains a stable ORDER BY), with server- assembled evidence in the spawn prompt (rework hotspots, recurring-findings and waived-minor ledger aggregates, all capped) plus prior-cycle LEARN context. The PO calls the new PO-only propose_bug_hunt verb once: ≤5 items, evidence required per item, targets validated against pest_control participation. CEO decides per item — approve materializes a BACKLOG task (source pest_control, never auto-starts), reject records the reason; both feed the LEARN ledger by exploration task id; all-terminal completes the cycle. Telegram queue pushes carry working Approve/Reject handlers mirroring the roadmap kind. Doctrine: board.md Pest Control section + product-owner verb entry + regenerated verb tables. * feat(panel): Pest Control review queue Command Center gains the pest review queue (per-item approve/reject with reason, mirroring the roadmap queue); the Programs card and the project settings participates-in checkboxes pick the new program up registry-driven — the settings section renders for the first time now that a project-scoped program exists. * feat(board): Periscope — HoM market-research brief program Weekly org-scoped cycle: a solo HoM spawn researches the market (web research with mandatory source URLs — uncited findings are rejected) and files one structured brief via the new HoM-only propose_market_brief verb: headline, cited findings, threats/opportunities, positioning note, all soup-checked and screened through the injection guard at persist time (web-derived text later reaches prompts; flags recorded, content never dropped). A brief is a report, not a proposal: the verb completes the exploration in the same call (the x_feature asymmetry), the cycle ledger auto-closes, and the CEO gets a best-effort notification with no approve/reject surface (periscope deliberately never joins Telegram's action kinds). The latest brief is injected into the roadmap exploration prompt — Periscope feeds Printer, the first cross-role program input. * feat(panel): Market Briefs tab (read-only) Business page gains a Market Briefs tab listing Periscope briefs — headline, cited findings, threats/opportunities — read-only by design; a report has nothing to approve. * feat(board): Coroner — event-triggered Auditor postmortems The first EVENT program: no cron — three best-effort hooks open an autopsy when a task bounces to its 3rd revision (the audit chokepoint), is cancelled after work started, or is budget-blocked; all gated on arming + one-open-autopsy dedup, none can fail the underlying transition. A solo Auditor spawn reads the incident (server-assembled findings + transition context) and files one propose_postmortem: incident summary, root cause, failed stage (validated against the real status vocabulary), and ONE process change — a playbook-kind change drafts via PlaybookService directly into the normal pending-curation queue; the briefed draft_playbook manifest grant was deliberately NOT added, preserving the existing 'auditor curates but never drafts' invariant test. Complete-at-propose (report asymmetry), cycle ledger auto-closes, CEO notified link-only. Integrated as a union with Periscope across the shared program surfaces. * feat(panel): Coroner postmortems card Read-only postmortems list under Business → Programs — incident, root cause, failed stage, process change; nothing to approve, the process-change artifact (a draft playbook) rides the existing curation queue. * feat(board): Sentinel — Auditor drift-watch quality reports Weekly org-scoped cycle: a solo Auditor spawn receives a server-assembled drift context (waived-findings trend, open findings by severity, conventions-violation hotspots, top spend — all capped, pure ORM) and files one propose_quality_report: headline, 1-7 area-validated items with evidence and suggested actions, overall assessment. Report semantics — complete-at-propose, cycle auto-closes, CEO notified display-only (never on Telegram's approve/reject surface); items are structured so a later convert-to-task control is cheap. Integration adopts Sentinel's module- level dict-dispatch for board-program routing (xenon-driven), folding all prior programs in; app router mounting extracted to a helper for the same budget. * feat(panel): Quality Reports tab (read-only) Business page gains the Sentinel quality-reports tab — headline, per-area observations with evidence and suggested actions; read-only, a report has nothing to approve. * feat(board): Spackle — gap-fill audit program Biweekly project-scoped PO cycle over the half-shipped surface area: API routes without panel surfaces (and vice versa), armed flags without docs, docs promises the code doesn't keep, dead-end tabs — the inventory diffing is the PO's own read-tool work, ordered by the spawn prompt with file:line citations required; the server injects only prior-cycle LEARN and the rotation target. Rotation is now a shared module-level helper (pick_rotation_target, parameterized by source) both project-scoped engines use — pest_control delegates to it, behavior-identical, with a cross-pollution test proving the two programs' rotations stay independent. propose_gap_fill mirrors the bug-hunt verb (≤5 items, two-sided evidence required, participation gate); per-item CEO decide materializes BACKLOG source=spackle tasks; full Telegram kind incl. approve/reject handlers. All seven program routers now mount from one helper. * feat(panel): Spackle gap-fill review queue Command Center gains the gap-fill queue mirroring the pest-control one — per-item approve/reject with the two-sided gap evidence rendered. * feat(board): Scales — monthly portfolio rebalance Org-scoped PO cycle over the stale backlog: the spawn receives a capped stale-task snapshot (BACKLOG/PENDING unclaimed >30 days) plus the charter and prior-cycle LEARN, and files one propose_rebalance — 1-7 items, each a resolvable task_ref with action reprioritize (validated new priority) or cancel, rationale required. Per-item CEO decide: approve EXECUTES the action (audited priority update, or the normal cancel path) — the first program whose materializer mutates existing tasks instead of creating them; reject records the reason; LEARN by exploration task id; all-terminal completes the cycle. Full Telegram decide-kind wiring. Integrated as the eight-program union (registry, dict dispatch, routers helper, teardown enumerations). * feat(panel): Scales rebalance review queue Command Center gains the rebalance queue — per-item approve/reject with the action, target task, and rationale rendered. * feat(board): Mirror — quarterly positioning audit Project-scoped HoM cycle over messaging surfaces: README claims vs shipped reality, docs-site promises vs code, charter alignment — the audit is the HoM's own read-tool work with citations required; the server injects the charter, prior-cycle LEARN, and the shared rotation target. propose_ messaging_fixes mirrors the gap-fill verb (≤5 items, drift evidence naming claim + contradicting reality, participation gate); per-item CEO decide materializes BACKLOG source=mirror documentation tasks; full Telegram decide-kind wiring. Nine-program union across the shared surfaces. * feat(panel): Mirror messaging-fixes review queue * feat(board): Megaphone — HoM standing editorial calendar Cron cycle (3 days, org-scoped, gated on X credentials — drafting content nobody can post is pointless): the HoM receives a shipped-this-week digest plus Unreleased changelog bullets and files one propose_editorial_post (angle-validated, ≤280, brand voice) that materializes a held x_editorial draft through the SAME X-queue origination chokepoint release posts use — zero new approval surface, notifications and CEO decide for free. Complete-at-propose; cycle auto-closes. Ten-program union. * feat(panel): x_editorial source labels in the X queue surfaces * feat(board): Librarian — proactive playbook mining Biweekly org-scoped Auditor cycle: mines recurring non-private learning journals (≥2-count grouping with a recency fallback) against the existing playbook-title inventory and files one propose_playbook_drafts — 1-3 drafts, each with the repeated-pattern evidence that justifies it, duplicate titles rejected in-batch and against the live store. Drafts are created via PlaybookService directly (the Coroner precedent — the 'auditor curates but never drafts' do-verb invariant stays intact and tested) and land in the normal pending-curation queue the Auditor's own triage already surfaces; no new panel surface. Complete-at-propose; display-only CEO notification. Eleven-program union. * feat(board): War Room — release campaign planning EVENT program with a REAL originator (unlike coroner's stub): a release publish hooks a campaign brief beside the release-post seam, and the CEO's run-now originates on demand — the cron loop never fires it. The HoM designs a 2-6 post arc (teaser → launch → follow-up → spotlight; 280-cap, future strictly-ascending publish_after, stage vocabulary) and one propose_campaign call materializes each post as a held x_campaign draft through the X-queue chokepoint. V1 is manual-cadence by design: publish_ after renders as queue guidance and the CEO approves each post at its moment — nothing auto-posts, ever; the auto-schedule upgrade is a documented ceiling. Twelve-program union: full registry complete. * feat(panel): x_campaign labels + publish-after guidance in the X queue * feat(board): Barfly — adjacent-conversation replies Cron cycle (2 days, org-scoped, X-credentials gated): the engine searches X for conversations where RoboCo is relevant but unmentioned (new OAuth- signed search_recent on the client; queries + candidate cap configurable), screens every fetched tweet through the injection guard (stored unclamped — a clamp was truncating the candidate under the envelope, caught by the dev's own tests), dedupes via the existing x_seen_mentions ledger (no migration; also prevents double-drafting against the mentions poll), and opens one held HoM exploration carrying the screened candidates. propose_ conversation_replies enforces candidate-id-only replies (≤5, 280-cap); each materializes a held x_barfly draft through the X-queue chokepoint, threaded via a new in_reply_to seam on post_tweet that only x_barfly drafts use. The X redraft machinery is now dict-dispatch over per-source extractors with reply-ref carry for x_barfly. Thirteen-program registry. War Room's test fakes gained the new abstract search_recent stub. * feat(board): Dogfood — the PO walks the product The fourteenth and final registry entry, completing the catalog. EVENT program (release-publish hook beside the war-room hook + CEO run-now, both through the same real originator; the cron loop never fires it), project- scoped with shared rotation. The permission surface is the careful part: the PO's dogfood spawn — and ONLY that spawn — gets the Playwright MCP mounted, via a task-scoped fail-closed probe mirroring the video-authoring precedent (a PO spawned for roadmap/pest/scales never sees browser tools; tested both ways); the PM agent image bakes chromium unconditionally like the ux image, the mount stays task-gated in code. The walk targets the rotation target's live surfaces (panel_base_url only when the target is the org's own project, honest degradation otherwise); propose_friction_ fixes files ≤5 walked-path-evidenced items; per-item CEO decide materializes BACKLOG source=dogfood tasks; full Telegram decide kind. Also: megaphone/librarian/war_room arming keys restored to the settings validator — their panel toggles would have been rejected (dropped in earlier unions; the same silent-arming class the drill killed once already). * feat(panel): Dogfood friction review queue * chore(board): final whole-branch sweep fixes The night's closing adversarial pass over the integrated fourteen-program registry found ONE functional defect — the war-room test fakes' post_tweet predated Barfly's in_reply_to_tweet_id kwarg (LSP violation, the only red in an otherwise fully green gate) — plus doc/test drift, all fixed: the source-parity test completes to fourteen (spackle/mirror were silently absent while its neighboring comment claimed full coverage), the PO identity doc gains its missing Dogfood verb, the auditor quick-list gains propose_postmortem, three stale comments corrected (rotation docstring, panel registry header, X source enumerations), the dogfood release-hook gains the exception-swallow test its four sibling hooks already had, and the CHANGELOG's Unreleased section documents the whole Board Programs train. Full make quality: exit 0, all gates green. * docs: full documentation sweep for the Board Programs train CLAUDE.md's roadmap-engine entry superseded by the Board Program registry entry (all fourteen programs, arming, scoping, LEARN, guardrails) with the role verb tables and playwright row refreshed; docs/rag gains the agent- facing architecture doc plus full propose_* call-shape sections in the three board role docs, and corrects the strategy-engine section to shipped reality (only idle→roadmap is wired); docs/map covers the registry + all twelve engines with flags, gotchas, and drift notes. The 0.27.0 reference inventory confirmed only the release-executor's canonical set carries the version — left for the 0.28.0 cut. * feat(board): human titles + descriptions on every program surface Raw registry keys rendered as bare panel labels — an operator reading x_feature had no idea what enabling or running it does. The registry dataclass gains title/description (test-enforced non-empty for every entry, unique titles), the API passes them through, and every surface renders title-with-description-tooltip instead of the key: the Programs card (label, toggle hint, run-now toast), and the project settings participates-in/excluded-from checkboxes. --------- Co-authored-by: Renn F <rennf93@users.noreply.github.com> |
||
|
|
e77c3b7a63 |
feat(board): Board Program registry — Phase 1 (engine, LEARN ledger, per-project scoping, panel) (#689)
* feat(board): Board Program registry — generic trigger/dedup/originate/LEARN engine
One registry (foundation/policy/board_programs.py) + one BoardProgramEngine +
one orchestrator loop replace the bespoke roadmap/spotlight loops, behavior-
preserved: same sources, dispatch routing, one-open-cycle dedup (ledger rows
auto-close when their exploration task goes terminal, so x_feature's
complete-at-propose flow can't wedge), and live per-program interval
overrides with the tick capped at 1h.
program_armed() is the single arming chokepoint: the settings-store
board_program.<key>.enabled override when present, else the legacy flag —
routed through BoardProgramEngine, RoadmapEngine.run_cycle, and XEngine's
spotlight gate, so the panel toggle can never be a silent no-op against a
legacy boot flag.
LEARN: board_program_cycles (migration 087) accrues per-item CEO decisions
(exact attribution by exploration_task_id where the caller holds it) and
feeds the last closed cycles back into both exploration prompts. The
strategy engine's idle signal now opens a roadmap cycle (enabled+dedup
respected) instead of only nudging.
Per-project scoping (migration 088, projects.board_programs, dual polarity):
plain keys opt a project INTO project-scoped programs; "!key" opts it OUT
of an org-scoped program's outputs (default eligible — parity). Enforced at
propose_roadmap (names the excluded project) and defensively at materialize;
validation rejects unknown keys and meaningless polarity both directions.
API: GET /api/board-programs + POST /api/board-programs/{key}/run-now
(CEO-gated); settings keys for both migrated programs.
* feat(panel): Board Programs card + per-project program controls
Business page gains a Programs tab: per-program rows (role, trigger, scope,
open-cycle badge), enabled switch on the settings-store key, Run now
(disabled while a cycle is open). The edit-project dialog gains the
program controls next to the CI-watch/video toggles: participates-in
checkboxes for project-scoped programs, excluded-from checkboxes for
org-scoped outputs.
* test(board): full-gate hermeticity — mypy casts + shared-DB purge fixtures
make quality runs one pytest process over all suites against the shared
persistent DB: integration collects before unit, so the board-programs API
test's committed run-now state (settings-store overrides, an open cycle row,
its board_roadmap task) poisoned 13 downstream unit tests that pass in
isolation. The polluter now purges its own committed state in fixture
teardown, and the four consumer files get an autouse per-test purge
(board_program.% settings keys, ledger rows, open exploration tasks) so
they are hermetic regardless of collection order. Also the four
cast("UUID", ...) sites the tests-scope mypy run requires.
* feat(panel): re-home per-project program controls onto the settings page
Wave C deleted the edit-project dialog these controls originally landed in;
they now live on the project settings page's budget/ops card next to the
CI-watch/video toggles — participates-in switches for project-scoped
programs, excluded-from switches for org-scoped outputs, dual-polarity
tooltips, order-independent dirty tracking. Nine makeProject test fixtures
gain the required board_programs field the rebase left behind.
---------
Co-authored-by: Renn F <rennf93@users.noreply.github.com>
|
||
|
|
4585a248ce |
feat(x): redraft loop on CEO reject — feedback re-enters the draft flow (#648)
* feat(x): redraft loop on CEO reject — feedback re-enters the draft flow A rejected X draft's reason used to die with the cancel. reject() with a non-blank reason now schedules a redraft after its commit (defer_after_commit; fresh session; never blocks or fails the HTTP response): XEngine.redraft_from_rejection re-drafts the same source kind via the local model with the reason and rejected body folded in as revision guidance, originating ONE fresh held draft — mirroring the video pipeline's reauthor_from_rejection. Local-model failure or empty output originates nothing (no degraded copies); markers carry forward whole so a redrafted reply/spotlight stays fully functional downstream; bodies ride the same 280 clamp; the open-posts cap holds. Hardened per adversarial review: reject() is now idempotent on an already-CANCELLED target at both check sites (mirroring approve's already_rejected guard — a replayed reject schedules nothing), and the dedup check+originate runs under a non-blocking identity-keyed Redis lock (SET NX + compare-and-del, matching the approve/reject mutex style) so racing rejects can't stack duplicate drafts — lock held or Redis down skips the redraft, which is always safe. Tests pin the fresh-session contract by session identity, the replay no-op, the lock-skip, and clean up their own committed rows. * fix(tests): runtime UUID import + typed task-id coercion in x cleanup helper CI's quality gate runs mypy over tests/ (the local pass covered only roboco/): the _delete_tasks calls handed ORM-typed ids where uuid.UUID was expected. Coercing at the call sites then exposed that UUID was imported under TYPE_CHECKING only — a runtime NameError. Import moved to runtime; both call sites coerce explicitly. --------- Co-authored-by: Renn F <rennf93@users.noreply.github.com> |
||
|
|
17de29545a |
[6788ce7f] Silent bug sweep: concurrency, state integrity, engine edge-cases, panel data freshness (#638)
* [943d8c4d] Frontend data freshness and approval-queue reliability audit (#631)
* [233a8b0f] WebSocket reconnect message-loss audit and fix (#625)
* [233a8b0f] fix(panel): add REST catch-up to useNotificationStream on WS reconnect
connection.ts has no message buffering/replay, so a notification published
while the CEO bell's socket was down (disconnected/reconnecting) was lost
forever instead of merely delayed. Add a reconnect-triggered GET
/notifications?unread_only=true catch-up folded into the existing
notification_id dedup so a notification delivered both via catch-up and
live WS is never double-counted, and make clearMessages drop the held
catch-up batch too. use-a2a-live.ts and use-rate-limit-websocket.ts were
audited and already have working reconnect-triggered REST fallbacks
(verified via a2a/page.tsx, rate-limit-banner.tsx, usage-overview-panel.tsx
and their existing F083 tests) so no fix was needed there.
* [233a8b0f] docs(panel): add comprehensive WebSocket hooks reference and reconnect architecture guide
Add panel/docs/frontend/hooks.md with full API reference for useWebSocket, useNotificationStream (with new REST catch-up behavior), useAgentStream, useA2ALiveStream, and useConnectionStatus. Include examples, best practices, and testing guidance.
Add panel/docs/architecture/websocket-reconnect.md documenting the message-loss mitigation pattern: Strategy 1 (REST catch-up for events, used by useNotificationStream) and Strategy 2 (REST invalidation for state, used by A2A/rate-limit consumers), plus the dedup logic ensuring no notification is double-counted on reconnect.
---------
Co-authored-by: Frontend Developer 1 <fe-dev-1@roboco.tech>
Co-authored-by: Frontend Documenter <fe-doc@roboco.tech>
* [d5315683] fix(frontend): add distinct toast feedback for silently-swallowed x-post and release-proposal statuses, plus regression tests for all 4 approval queues (#626)
Co-authored-by: Frontend Developer 2 <fe-dev-2@roboco.tech>
* [cd953838] Data-hook null-guard audit and API client 429 retry-by-method fix (#630)
* [cd953838] fix(panel): gate 429 retry by HTTP method, add hook null-guard regression tests
* [cd953838] chore(conventions): waive test-fixture wrapper in hooks null-guard test
* [cd953838] docs(frontend): document API rate-limit retry behavior and null-guard audit results
Added `docs/frontend/api-rate-limiting.md` to document the 429 retry strategy: GET/PUT auto-retry, POST/PATCH/DELETE require X-Idempotency-Key header. Updated `docs/frontend/hooks.md` to confirm the data-hook null-guard audit found all hooks already have correct `enabled` guards and include a regression test suite for the board-review poll on/off behavior and enabled-guard assertions.
---------
Co-authored-by: Frontend Developer 1 <fe-dev-1@roboco.tech>
Co-authored-by: Frontend Documenter <fe-doc@roboco.tech>
---------
Co-authored-by: Frontend Developer 1 <fe-dev-1@roboco.tech>
Co-authored-by: Frontend Documenter <fe-doc@roboco.tech>
Co-authored-by: Frontend Developer 2 <fe-dev-2@roboco.tech>
* [4534c71a] Backend concurrency, state-machine, and engine audit (#634)
* [41de844a] fix(lifecycle): sync CLAIM_RULES with runtime + clear stale claimant on PM hand-off (#627)
Two confirmed state-machine gaps found while auditing lifecycle.py,
task_lifecycle.py, the _ESCALATABLE_TO_BLOCKED bypass, and every
_REVIEW_QUEUE_STATES entry point:
- lifecycle.py's CLAIM_RULES/claim-ActionSpec/StatusTransition table
did not grant CELL_PM/MAIN_PM re-claim of AWAITING_PM_REVIEW even
though task.py's runtime _ROLE_CLAIM_STATUSES already granted it
and claimed the spec agreed -- the two tables had silently drifted,
breaking i_will_plan re-claim on an awaiting_pm_review task.
- docs_complete's _maybe_advance_to_pm_review pre-assigns a specific
owning PM via assigned_to but left claimed_by/active_claimant_id
pointing at the outgoing documenter, unlike every sibling transition
into a review-queue state. A stale active_claimant_id makes
content_actions.py's _active_claim_violation wrongly reject the
newly-assigned PM's own content writes before it formally claims.
Reassign claimed_by + active_claimant_id to the owning PM alongside
assigned_to.
Adds a regression test asserting the documenter's stale claim does not
survive the docs_complete -> awaiting_pm_review hand-off.
Co-authored-by: Backend Developer 2 <be-dev-2@roboco.tech>
* [0c46f666] Engine dedup race + sequencing.py edge-case audit (#628)
* [0c46f666] fix(sequencing): dedup race audit + collision-edge fallback bug
Audited the list-open-then-originate dedup pattern across six engines:
RoadmapEngine, XEngine.run_cycle, DepUpdateEngine, and CIWatchEngine each
run inside exactly one sequential orchestrator-loop asyncio task (no other
call site invokes run_cycle), so they cannot race with themselves; their
in-cycle dedup sets/keys are correctly built before any commit. SelfHealEngine
is the same shape. VideoEngine.open_video_task is genuinely different: it is
reachable from the release-publish hook, the feature-spotlight hook, and the
on-demand POST /video/request route, so two overlapping calls for the same
occasion can both pass the "no open task yet" check before either commits.
Fixed by wrapping the check+insert in a short-lived Redis mutex (reusing
HeartbeatMutex) keyed by occasion, mirroring XPostService's existing
lock pattern, with a regression test proving only one of two concurrent
calls creates a task.
Verified ReleaseExecutor's half-landed retry path (release_commit_sha):
apply_version_bumps and write_changelog_entry both run as uncommitted
working-tree edits before commit_and_push's single `git add -A` + commit,
so a bumped-version-without-changelog state can never reach origin (and
therefore can never be observed by a fresh retry clone) - confirmed correct
with a real-git-repo regression test, no fix needed.
Fixed sequencing.py's dev_task_collision_edges: the `if edges: return edges`
short-circuit dropped the same-assignee-lane fallback entirely whenever ANY
surfaced sibling pair produced a collision edge, even for a completely
unrelated same-assignee pair with no declared surface. Now the fallback
always runs, skipping only pairs the analyzer already ordered (so the two
mechanisms can never disagree on direction for the same pair).
Verified sequencing.py rule 3 (all-shared batch generates no edges): correct
by inspection (_shared_last_edges skips every pair when both are shared) and
confirmed with a regression test - no fix needed.
* [0c46f666] docs(reference): concurrency audit summary - engine races, fixes, verified patterns
---------
Co-authored-by: Backend Developer 2 <be-dev-2@roboco.tech>
Co-authored-by: Backend Documenter <be-doc@roboco.tech>
* [8f7f167a] Redis mutex pre-lock write audit (#629)
* [8f7f167a] Redis mutex pre-lock write audit: add cross-session regression test for XPostService.approve
Audited x_post_service.py, video_post_service.py, release_proposal.py, and
heartbeat_mutex.py for the pre-lock DB-write anti-pattern (a session write
that happens before the SET NX / HeartbeatMutex acquire returns a token,
letting a losing racer's stale write clobber a winner's committed state).
XPostService.approve, VideoPostService.approve, and
ReleaseProposalService.approve/reject already implement the correct
validate-pure-pre-lock, apply-under-lock pattern (the XPostService fix
already shipped per CHANGELOG.md: "X edited_body write deferred into the
single-flight lock (M5)"). HeartbeatMutex holds no AsyncSession at all, so
the anti-pattern is structurally inapplicable there.
Adds a genuine cross-session concurrency regression test to
test_x_post_service.py (a real second DB connection, not an in-process
mock) mirroring VideoPostService's existing cross-session test, proving a
concurrently-committed post survives and the CEO's edited body never lands
on the just-posted row.
* [8f7f167a] Remove redundant inline comments flagged by QA in cross-session regression test
Both comments restated what the surrounding docstrings already say
explicitly, per QA findings F-dbadd8f0 (line 294) and F-27ac051e (line
631) — no behavior change, tests re-verified green against a sandbox
Postgres.
* [8f7f167a] Remove inline trailing comments flagged by QA (correct file this time)
QA findings F-e6f3e6a6 and F-24189858 cited tests/unit/services/
test_x_post_service.py:294 and :631 across 5 revision rounds, but that
file never contained the flagged comment text — a repo-wide grep for
the exact quoted strings shows both comments actually live in the
mirrored tests/unit/services/test_video_post_service.py file, in its
own cross-session concurrency regression tests (the caption-edit and
tiktok-skip tests). Removed both there:
- "# externally visible to the "concurrent" session below" on the
db_session.commit() call
- "# never attempted without credentials" on the tiktok_poster.calls
assertion
Both restated what the surrounding docstrings/test names already say;
no behavior change. Verified with the full make quality gate against a
sandbox Postgres/Redis: 13,717 passed, 94.41% coverage, clean except
one pre-existing unrelated failure in tests/unit/api/test_cloud_auth.py
::test_login_route_parses_oauth2_form_not_query_params, which connects
to the app's default localhost:5432 Postgres (not the db_session
sandbox fixture) and is unreachable in this sandboxed environment —
structurally unrelated to the auth subsystem this task never touches.
* [8f7f167a] Redis mutex pre-lock write audit (round 7): add cross-session regression tests for reject() lock protection
Round-7 QA findings F-7eb9fbcb, F-06f39a2e, and F-4d56e49b claim
XPostService.reject(), ReleaseProposalService.reject(), and
release_executor._await_proc() lack lock protection / a CancelledError
handler — but their cited line ranges (255-267, 429-454, 241-257)
describe a pre-fix, shorter version of these functions that predates
commit
|
||
|
|
b0dcb03356 |
fix(marketing): release and spotlight drafts carry their source project
draft_release_post / draft_release_video accept a project_id and the release-proposal approve hooks pass the proposal task's own project; the spotlight companion video forwards the spotlight draft's project into open_video_task — previously it always authored against the deployment-anchor project's motion/ tree regardless of which project the spotlight was about. Omitted project_id keeps the anchor-project fallback, so single-project deployments are unchanged. |
||
|
|
9fbec78126 |
feat: Telegram V2 — inbound commands + actionable approve/reject from chat (#551)
* feat(telegram): V2 inbound — command router, actionable approve/reject keyboards, chat-gated poll loop * fix(release,x,video,telegram): terminal-state guards on approve/reject; sender-identity check * docs(map,rag): Telegram V2 inbound surfaces and terminal-state approve/reject guards --------- Co-authored-by: Renn F <rennf93@users.noreply.github.com> |
||
|
|
18998c4a42 |
Video pipeline fixes: visibility strip, rich briefs, spotlight timing + fps (#369)
* feat(video): pipeline visibility — strip, state-aware queue, render-error capture Task 1 of the 2026-07-09 video-pipeline review. New CEO-gated GET /video/pipeline lists every in-flight video item (authoring statuses, rendering attempt n/max, terminal failures with the error — now stamped onto the video_draft marker instead of dying as a log line). source_task_id exposed on both video schemas. Panel: pipeline strip on the Social page, state-aware queue empty copy, title/script on queue rows, missing cuts disabled instead of a blank player, notifications deep-link related_task_id. MAX_VIDEO_RENDER_ATTEMPTS moved to the markers policy layer (single source of truth). * feat(video): rich authoring briefs — changelog section, brand voice, kit pointer Task 2 of the 2026-07-09 video-pipeline review. The release brief is now a structured block (full CHANGELOG section capped at 4000 chars + highlights) instead of one LLM-compressed sentence; brand_voice and a motion/kit design-bar pointer are appended centrally in open_video_task so release, spotlight, and on-demand paths all inherit them. suggested_input_props seeded on the video_draft marker; third acceptance criterion pins the design bar; propose_video docstring points at the kit. * fix(video): spotlight video drafts on CEO approval, renderer honors data-fps Task 4 of the 2026-07-09 video-pipeline review. The companion-video hook moves from propose_feature_spotlight (HoM authoring time) to XPostService approve's posted-success branch for x_feature drafts, mirroring the release-publish seam — a rejected spotlight no longer burns a ux-dev cycle; wants_video/video_script ride the x_feature_ref marker. Best-effort: a video-engine failure never breaks the post. render.js reads data-fps from the composition HTML (clamped 24-60, fallback 30) instead of hardcoding 30; parseFps covered by node --test. --------- Co-authored-by: Renn F <rennf93@users.noreply.github.com> |
||
|
|
f0b6390189 |
feat: Social page — aggregated post queues + X/video history (#345)
* feat(api): x/video post history endpoints Approved or rejected drafts vanished from both queues permanently -- the listers exclude terminal statuses and no history surface existed, so a posted tweet or video was only findable in the raw task list. GET /x/posts/history and GET /video/posts/history (CEO-gated, bounded) return acted-on drafts newest-first with the posted platform ids and reject reasons from the draft markers. Route tests assert by identity, not emptiness: approve/reject commits the whole session, so prior tests' rows legitimately persist in the shared test DB. * feat(panel): Social page aggregating post queues and history New dashboard page composing the X and video post queues with one unified history section beneath them -- both platforms interleaved newest-first, kind and outcome badges, posted X ids linking to the live tweet, reject reasons shown. The command center's two full queue cards become a compact pending-counts card linking to the page, so the queues have one home instead of duplicated surfaces. --------- Co-authored-by: Renn F <rennf93@users.noreply.github.com> |
||
|
|
3849c1737e |
feat(video): switch 0.19.0 renderer Remotion → HyperFrames (HTML-native, Apache-2.0) (#314)
* feat(video): rewrite sidecar render core to HyperFrames (in place)
* feat(video): convert motion compositions from Remotion TSX to HyperFrames HTML
* refactor(video): rename render client to video_renderer_client (renderer-agnostic)
* chore(video): rename remotion-renderer prose in test_video_pipeline docstrings
* chore(video): rename sidecar to video-renderer + add system ffmpeg for HyperFrames
* chore(video): rename stray remotion-renderer refs in sidecar + py docstrings (controller cleanup)
* chore(video): fix stale Remotion API names in Dockerfile comment (controller cleanup)
* docs(video): rewrite video-engine prose for HyperFrames + add map entry + folded prose fixes
* docs(video): add trailing newline to docs/map/video-engine.md (controller cleanup)
* chore(video): drop internal spec refs + minio/test suppressions (folded hygiene)
* fix(video): reclaim outDir on createRenderJob throw + hide empty 4th highlight
Final whole-branch review (Opus) triaged two FIX items from the SDD nits
ledger; the rest ship as-is.
- render.js: a synchronous throw from createRenderJob (post-mkdtemp, not
awaited) left an empty outDir on disk — the outer catch only reclaimed
extractDir. Reclaim outDir too when it exists, and correct the stale
comment that claimed the out dir was never created.
- {vertical,square}.html: the 4th highlights <li> lived in the DOM hidden
only by JS, so a no-JS / failed-script render would show an empty bullet.
Start it style="display:none" and reveal on populate, so an unscripted
render shows nothing instead.
Vitest smoke (release-announcement.test.js) 4/4 green; render.js syntax
checked. Python suite untouched by this fix (JS/HTML only).
* fix(video): type _override_db yield as AsyncSession | None
T7 widened _build_app's db_session param to AsyncSession | None (to drop the
4x # type: ignore[arg-type] on the DB-independent _build_app(None, ...) calls)
but left the inner _override_db fixture typed AsyncIterator[AsyncSession] —
so 'yield db_session' yielded AsyncSession | None into a declared AsyncSession,
and mypy failed at test_video_routes.py:177 ('Incompatible types in yield').
The DB-independent media tests pass db_session=None deliberately: their route
uses a monkeypatched task service and never awaits the session, so yielding
None is safe at runtime. Type the override's yield as AsyncSession | None to
match — no cast, no # type: ignore, no assert, runtime behavior unchanged.
The 3 media tests (3 passed) and the 19 db-gated tests (skipped locally) hold.
* chore(gate): skip .superpowers scratch in markdown prose gate
reflow_md.py walks the filesystem via rglob('*.md') and skips tooling dirs
(.venv, .mypy_cache, .pytest_cache, ...) but not .superpowers/ — the
superpowers SDD workflow's scratch dir (briefs, reports, progress ledger,
all gitignored). A dev running SDD locally would hit a false markdown-prose
gate failure on those transient files. Add .superpowers to SKIP_DIRS,
consistent with the existing tooling-scratch exclusions.
* fix(video): validate composition_id to close path traversal (CodeQL)
compositionId flowed unvalidated from the POST body into path.join
under extractDir/motion/compositions/, so a '../..'-style value could
escape the composition dir (CodeQL: Uncontrolled data used in path
expression). Validate at the trust boundary in server.js
(/^[A-Za-z0-9_-]+$/) and add a path.resolve + startsWith containment
check in render.js so it stays safe regardless of caller.
* fix(mcp): send X-Agent-Token + X-Agent-Team from flow/do servers
flow_server._build_headers and do_server._build_headers constructed
only X-Agent-ID/Role/Correlation-ID, omitting X-Agent-Token and
X-Agent-Team (unlike ApiClient._get_agent_headers used by the other
MCP servers). Latent since the gateway refactor — surfaced when
ROBOCO_AGENT_AUTH_REQUIRED=true was armed on the NAS, 401-ing every
flow/do verb with 'Missing X-Agent-Token header'. Add both headers
(mirroring ApiClient) so the HMAC gate passes. Tests assert the
headers are now injected.
* [video-engine] Per-project video_engine_enabled opt-in toggle
Mirrors ci_watch_enabled (migration 048): the global
ROBOCO_VIDEO_ENGINE_ENABLED flag arms the subsystem; the new
projects.video_engine_enabled column (migration 063) opts a repo into
authoring against its motion/ dir. VideoEngine._opted_in_project no-ops
open_video_task at the single chokepoint covering all three trigger
paths (on-release, on-spotlight, CEO on-demand) until the operator
flips it in the panel edit-project dialog. Existing projects stay
opted out (server_default=false).
* fix(auth): send X-Agent-Token + X-Agent-Team from all agent->API call sites
The prior fix (
|
||
|
|
da17c49f2d |
feat(marketing): HoM feature-spotlight X drafts + brand-voice charter (v0.18.0 B)
The Head of Marketing now markets features, not just releases: a default-off x_feature_spotlight loop periodically spawns the HoM to investigate what shipped (CHANGELOG, feature flags, docs/map, KB) and draft ONE held marketing post via propose_feature_spotlight, reviewed in the X post queue. - New x_feature source (distinct from x_post, fixing panel mislabeling) + a panel Feature-spotlight branch. - brand_voice column on company_goals (migration 061, single head) as the CEO-editable voice source, surfaced in Settings and injected into the HoM briefing; a VOICE GUIDE baseline in head-marketing.md. - propose_feature_spotlight verb (HoM-only), mirroring propose_roadmap. Gated by x_feature_spotlight_enabled (default off; flag-off dormancy proven). Also fixed two real bugs found mid-build: company_goals API schemas dropped brand_voice on GET/PUT; the live charter UI is goals-tab.tsx, not the unmounted company-goals-card.tsx. Full suite green (2935); migration single-head verified. |
||
|
|
3ccc723cd4 |
v0.17.0 — Wave 3: sandbox DB, DB isolation, mobile UI, cloud auth, X account, roadmap engine (#303)
* feat(sandbox): throwaway per-agent Postgres/Redis sandbox containers
Orchestrator-provisioned sibling containers per agent spawn
(SandboxProvisioner, roboco/runtime/sandbox.py). Per-project opt-in via
projects.sandbox_services (migration 057); master switch
ROBOCO_SANDBOX_DB_ENABLED, default-off, armed in the NAS compose only.
When active, ROBOCO_TEST_DB_* / ROBOCO_TEST_REDIS_* point at the sandbox
and the prod-creds gate-env injection is suppressed (sandbox replaces,
never coexists). Sandbox lifetime tracks the agent container: teardown at
every removal path, orphan janitor at startup + each reaper tick with a
grace window for mid-flight spawns. The pre-spawn stale-clear spares the
just-provisioned sandbox; provision pre-clears stale same-named
containers from a crash-missed teardown.
Panel: per-project sandbox-service switches in the edit dialog + feature
flag card entry.
* docs: CLAUDE.md entry for the sandboxed dev DB/Redis subsystem
* feat(security): isolate prod Postgres/Redis from agent containers (roboco_data network)
Second user-defined bridge roboco_data carries postgres+redis only; the
orchestrator is multi-homed (default + data). Spawned agents and their
sandbox sidecars stay on roboco_default and can no longer resolve or
reach roboco-postgres:5432 / roboco-redis:6379 (redis has no auth —
membership is its only containment). Normal bridge, so host-published
ports (15432/16379) keep working. Applied to both build composes and
the registry compose; docker-compose.yml re-synced byte-identical with
docker-compose.yaml (it had drifted by the sandbox flag block).
ROBOCO_DB_NETWORK_ISOLATED (config default false, armed alongside the
topology) suppresses the legacy _append_gate_env prod-creds injection:
under isolation those creds dead-end, and unreachable creds are worse
than none. DB-needing projects opt into sandbox_services instead. The
flag is deliberately not a panel feature flag - it must travel with the
compose networks: stanzas.
Preserved by construction: agent<->agent A2A and orchestrator->agent SDK
polls on :9000, MCP->orchestrator on :8000, ollama reachability, docker
exec/inspect (daemon socket), host port publishing.
* feat(panel): full mobile responsiveness pass
Shared primitives: useIsMobile (useSyncExternalStore, hydration-safe,
memoized matchMedia subscribe), ResponsiveTable table->card switch below
md (single subtree mounted, no duplicated interactive rows), scrollable
snap TabsList in the base primitive (justify-center-safe so the first
tab stays reachable on overflow), persistent md:hidden bottom tab bar
(Overview/Tasks/Kanban/Chat, safe-area padded).
Applied: card lists for tasks/projects/products/work-sessions/sessions
+ the three raw metrics tables; CEO approval queue / release proposal /
playbook review action rows stack on narrow; command-center reorders
approvals above the fold on mobile; task-header metadata wraps;
Communications + A2A become URL-driven single-pane drill-downs below lg
(fixes the unconstrained-height ScrollArea bug) with dvh heights;
recharts label density/radius adapts via useIsMobile; git diff viewer
gets mobile font + wrap toggle; vh->dvh sweep; chat composers get
safe-area-inset padding; dashboard main p-4 md:p-6 + pb-20 for the bar.
Verified at 375px on the built app: bottom bar, drawer, approval-first
overview, swipeable kanban tab strip. All gates green (eslint, tsc,
vitest 249, next build 24/24 routes).
* feat(auth): cloud auth via FastAPI Users (default-off, single-user cookie session)
ROBOCO_CLOUD_AUTH_ENABLED (default off) lets the panel/API be exposed
beyond localhost without changing the CEO's local no-login flow while
off — get_agent_context and the WS gate are byte-for-byte unchanged in
off-mode. On: header-trust dies for humans — any agent-role claim (ceo
or a privileged PM/board role) with no valid HMAC token or session
cookie is 401, closing the header-spoof hole on the host-published
:8000 port for every role. The agent-fleet HMAC path and the system
self-PATCH keep working unmodified in both modes.
Single seeded CEO user (migration 058 users table, UserTable), no
registration router — idempotent env-driven upsert at startup by PK.
Cookie transport (httponly/secure/samesite=lax) + a JWTStrategy bound
to a fingerprint of the current password hash (rotating the password
invalidates every prior session). Sliding 30-day session: every
authenticated request re-mints the cookie, so an active session never
expires — no unexpected logouts.
Panel: (auth)/login page + proxy.ts (Next 16 rename of middleware; probes
/auth/status over the docker-internal URL, fails open to off) gate the
dashboard; client.ts gets withCredentials + 401->/login. nginx unchanged.
Review hardening: broadened the on-mode rejection from ceo-only to every
non-CEO role without a valid token (was only closed when
ROBOCO_AGENT_AUTH_REQUIRED was also armed); Next-16 proxy.ts rename to
clear the middleware deprecation warning.
* feat(x): RoboCo X account engine — HoM drafts, per-post CEO approval (default-off)
ROBOCO_X_ENGINE_ENABLED (default off, inert without creds). Mirrors the
ReleaseManagerEngine held-artifact shape: XEngine drafts a post when a
release publishes (via a draft_release_post seam on ReleaseProposalService
.approve) and drafts replies to meaningful mentions (dedicated poll loop,
x_seen_mentions dedup ledger, per-cycle/open caps). Drafting is
local-model-only, clamped to 280 chars. Nothing auto-posts — every tweet
is a held task (source x_post/x_reply, confirmed_by_human=False,
Secretary-owned, dispatcher-skipped) the CEO edits/approves/rejects in a
panel queue.
The four OAuth 1.0a secrets live Fernet-encrypted in a singleton
x_credentials row (migration 059, all-or-nothing, API returns only
has_credentials); decryption is server-side, agents never hold creds or
egress. Hand-rolled OAuth 1.0a HMAC-SHA1 signer, no new dependency;
NullXClient makes the unconfigured path a graceful no-op.
XPostService.approve (CEO-only) is the sole caller of post_tweet.
Review hardening: closed a double-post race — the approve path now
re-reads committed task state inside the Redis lock and commits COMPLETED
before releasing, so a concurrent approve that acquires the lock after the
winner released can't re-post (SET-NX is non-waiting, and the route-level
commit landed after the lock dropped). Added a regression test.
* feat(roadmap): board roadmap engine — PO proposes themed cycles, CEO approves per-item (default-off)
ROBOCO_ROADMAP_ENGINE_ENABLED (default off). Weekly, RoadmapEngine opens
ONE held exploration task (source=board_roadmap, confirmed_by_human=False,
Product-Owner-assigned), deduped to one open cycle. A dedicated one-shot
_dispatch_roadmap_exploration spawns the PO solo (not the two-reviewer
board path, which would also spawn HoM + fire Approve-&-Start). The PO
explores read-only (git/KB/metrics/releases/charter/web) and makes one
propose_roadmap call (PO-only content verb) authoring a themed cycle —
goal + 3-7 item drafts — persisted as a roadmap_cycle marker (no table,
no migration; head stays 059).
The CEO acts per-item in the panel roadmap queue: approve materializes a
BACKLOG task (source=roadmap, no assignee — never auto-starts), reject
records a reason; all-items-terminal completes the exploration task.
RoadmapService is idempotent per item. Dispatchers skip board_roadmap.
Includes a real SQLAlchemy dirty-check fix (deep-copy the JSON marker
before mutating, or the in-place edit + reassign compares equal to its
own baseline and the UPDATE is skipped).
Review hardening: create_task_from_draft now honors a draft-declared
source only from a {prompter, roadmap} whitelist — drafts are
LLM-authored, so an unbounded source could impersonate a privileged
origin (release_manager would even wedge that engine's dedup).
* chore(release): 0.17.0
Wave 3 — six default-off subsystems: sandboxed dev DB/Redis, prod
Postgres/Redis network isolation, full mobile UI pass, cloud auth
(FastAPI Users), the RoboCo X account engine, and the board roadmap
engine. Plus the waves 1+2 work already on master since 0.16.0.
Version bumped across the canonical set (config.py, __init__.py,
pyproject.toml, panel/package.json, uv.lock); CHANGELOG [Unreleased]
cut to [0.17.0]; docs/map delta added.
Compose: every optional feature armed :-true in the NAS composes, OFF
in the user-facing registry compose. Two opt-in exceptions default off
(CLOUD_AUTH — needs email/password/secret + TLS, would otherwise fail
startup; ROUTING_STRICT — fail-closed spawning). DB_NETWORK_ISOLATED
stays on in both (coupled to the roboco_data topology).
* chore(compose): arm cloud_auth + routing_strict ON in the NAS composes
Every feature defaults ON in the NAS composes per policy — these two
were wrongly left off. Both keep the ${VAR:-true} form so the operator
controls the real runtime via .env: cloud auth needs
ROBOCO_CLOUD_AUTH_EMAIL/_PASSWORD/_SECRET + TLS set there before a boot
(else startup fails loud), and routing_strict is fail-closed. Registry
compose keeps both off.
* fix(ci): reflow board.md prose (quality gate) + document v0.17.0 env creds
The roadmap section added hard-wrapped prose that failed the markdown
prose gate; reflowed (token-invariant). Also brought .env.example
current: cloud auth (now armed — needs SECRET or startup fails), routing
strict, the X engine (panel-entered OAuth), and web research.
* fix(ci): reduce cyclomatic complexity of five wave-3 blocks (xenon gate)
The wave-3 subagents introduced C-rank functions the CI xenon gate
rejects (my per-item reviews ran ruff/mypy/pytest but not xenon):
- sandbox.janitor_sweep -> extract _list_labeled_sandboxes /
_list_live_agent_containers / _prune_grace
- x_client.fetch_mentions -> extract _parse_mention_items
- x_engine.run_cycle -> extract _process_mentions
- orchestrator._dispatch_pm_work -> extract the source-skip into a
MODULE-level _is_held_ceo_source (module, not method, so the
wholesale-mocked dispatcher unit tests exercise the real logic)
- auth/seed.ensure_seed_user -> extract _apply_seed_updates (module avg -> A)
Behavior-preserving; full suite green (11902), xenon clean.
* fix(ci): declare pyjwt + fastapi-users-db-sqlalchemy as direct deps (deptry)
The cloud-auth code imports jwt and fastapi_users_db_sqlalchemy directly
but they were only transitive deps (via fastapi-users), which deptry
(quality gate, DEP003) rejects. Declared explicitly; deptry roboco/ clean.
Missed originally because local make quality stopped at earlier gates
before reaching deptry.
* feat(x): gate mention replies behind ROBOCO_X_REPLIES_ENABLED (default off)
Per CEO decision: the X engine should only post about releases by
default. Reading mentions needs a paid X API tier, so the mention-reply
half is now a deliberate opt-in on top of release posting.
New default-off flag x_replies_enabled gates the mentions poll loop
(_x_mentions_poll_loop) and XEngine.run_cycle; release-post drafting
(the release-proposal approve hook) is unaffected and still runs when
x_engine_enabled + credentials are set. Added to FEATURE_FLAGS + the
panel card. Tests: release posting works with replies off; run_cycle +
the poll loop are no-ops with replies off.
* fix: 401 only redirects to /login when cloud auth is on; panel-token strips .env quotes
Two bugs that together dead-ended login in secure mode:
- client.ts redirected to /login on ANY 401, so a mismatched panel
token (header-trust/secure mode, cloud auth off) bounced the user to a
login page whose backend route isn't mounted -> 404. Now it probes
/auth/status (bare fetch, no interceptor re-entry) and only redirects
when cloud_auth_enabled.
- make panel-token read the .env secret with grep|cut without stripping
surrounding quotes, so a quoted ROBOCO_AGENT_AUTH_SECRET produced a
token signed with the quotes included — which never verifies against
the orchestrator (docker-compose/pydantic unquote the secret). Now
strips surrounding single/double quotes.
* fix: git-log 500 on '|' in commit message; X queue shows an empty state
- GET /api/git/log 500'd (ValueError: Invalid isoformat) when a commit
SUBJECT contained a '|' (e.g. the 'curl|sh' lockdown commit): the
fixed '|' field delimiter let the subject's pipe shift the split so
author+date collapsed into one field. Switched to \x1f (Unit
Separator), which can't appear in commit content. Regression test with
a piped subject.
- The X Post Queue returned null when empty, so there was no visible
place for the X drafts. It now renders a discoverable empty state
pointing at Settings -> X credentials.
* docs: bring docs/rag + docs/map current for v0.17.0 (waves 1-3)
Agent-facing RAG corpus and codebase map updated for every feature in
the 0.17.0 span, code-verified:
- wave 3: sandbox DB, DB network isolation, cloud auth, X engine
(+ x_replies_enabled sub-flag), board roadmap engine — new RAG
architecture pages + role/tool/config-reference updates; new symbols,
migrations 057-059, panel surfaces, and the get_agent_context
dual-path across the map slices.
- waves 1-2: A2A live view + switchboard, prompter memory
(search_past_tasks), Secretary edit access + PM-lighter scope, the
PR-gate auto-submit turn cut (ROBOCO_PR_GATE_AUTO_SUBMIT_ENABLED).
- correctness fix: api-routes-schemas.md no longer claims the A2A admin
routes are reachable by any authenticated agent — they carry a
_require_ceo gate (wave 2c).
docs/internal, _front.md deltas, and the frozen _complete_map.md
snapshot untouched.
* fix(rag): atomic upsert for indexed-doc tracking (kills e2e segfault)
The indexed-document tracking write used check-then-insert in two paths
(IndexedDocumentRepository.upsert_batch and the file-source
_upsert_doc_record). Under concurrent indexing both callers saw no row
and both inserted, so the second violated uq_indexed_doc_source and
poisoned its transaction — surfacing in CI as the intermittent
_checkin_failed SIGSEGV on the failed connection's pool checkin.
Both paths now use INSERT ... ON CONFLICT DO UPDATE against the
constraint: coalesce keeps an existing title/preview when the new value
is empty (matching the old guards) and metadata is jsonb-merged. The
batch dedupes within itself first (ON CONFLICT can't touch a row twice
in one statement). expire_all after the Core upsert keeps same-session
ORM reads consistent with the merged DB row.
---------
Co-authored-by: Renn F <rennf93@users.noreply.github.com>
|