mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
817f7f23ac2cfdbc3f85293bcabee0c57d8bfa7d
899
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
92410d47cf |
fix(prompter): MegaTask review card scrolls + confirm-batch guard releases on failure (#376)
The MegaTask review card was a static sibling of the scrollable chat list with
no height bound, so a tall batch overflowed the clipped container and stranded
the launch buttons off-screen with no scrollbar. It now owns the scroll area
(min-h-0 flex-1 overflow-y-auto), matching the pattern ChatMessages already uses.
confirm_live_batch's Redis idempotency guard (1h TTL) was acquired before the
build but only released via a success sidecar, so a build failure wedged the
session: every retry hit ServiceError("already in progress") -> HTTP 500 for an
hour. The build now releases the guard on any failure before the sidecar write,
so a retry re-attempts (and surfaces the real error) instead of being locked out.
Extracted _build_confirm_batch to keep the try/except thin.
Adds DB-backed tests for the panel the_work[].project_id shape, multi-cell
root-subtasks, dense same-repo collisions (both routes), and guard release.
Co-authored-by: Renn F <rennf93@users.noreply.github.com>
|
||
|
|
7be725cc13 |
fix(security): disposition all 104 code-scanning + dependabot alerts (#375)
Fix the 4 real CodeQL path-injection alerts (open_conventions_pr trusted the API-settable project.workspace_path with no containment) plus defense-in-depth segment validation at the get_workspace_path chokepoint. Bump next 16.1.1->16.1.7 and transitive lockfile deps to clear 24 Dependabot alerts. Close the intake subagent-ban gap: the Claude intake driver still carried the Task tool and the prompter prompt told it to fan out research subagents, contradicting the fleet-wide ban. The remaining 47 CodeQL + 29 Dependabot alerts are dismissed on GitHub with per-alert justifications (guard patterns CodeQL can't model across call hops; next 16.2.x blocked by the verified tab-hostage router regression). Co-authored-by: Renn F <rennf93@users.noreply.github.com> |
||
|
|
0450ec9e89 |
feat(x-engine): smart spotlight cadence — daily when there's news, quiet when there isn't (#374)
CEO verdict on the blind 3-day timer: 'It should default to 1 day and be more like... smart.' Now: interval defaults to 1 day; the cycle skips (with logged reasons) while a spotlight draft is still awaiting the CEO, and stretches to 3x the interval when nothing has shipped (CHANGELOG sections via the read clone) since the last spotlight activity — where activity is a materialized draft's seen_at or a completed exploration's updated_at, deliberately excluding the stale- cycle janitor's cancels. The HoM gains an explicit skip exit (propose_feature_spotlight skip=true + reason: completes the exploration, no draft, no seen-slug, still counts as activity), and its spawn prompt now carries the seen ledger WITH dates, what shipped since the last spotlight, and recently rejected drafts with the CEO's reasons — fresh-but-unspotlighted first. Fail-open on changelog read errors so a signal outage never starves the engine. Co-authored-by: Renn F <rennf93@users.noreply.github.com> |
||
|
|
3cb32e3c5f |
fix(release-recap): darkmode logo hero — the commit that missed the #333 merge (#373)
* [e0bc418f] add v019-release HyperFrames composition with vertical/square cuts, theme, props, and smoke tests * [e0bc418f] fix(v019-release): remove local artifacts, replace em dashes, add captions.json and regression tests * [e0bc418f] chore: delete .venv symlink and tighten .gitignore for symlink leakage * [e0bc418f] chore(.gitignore): remove duplicate .pnpm-store line * [e0bc418f] fix(v019-release): clean internal em dashes and add package-lock regression test * [e0bc418f] chore(v019-release): remove committed vitest cache artifact from node_modules * [e0bc418f] fix(.gitignore): restore .pnpm-store/ next to its GH001 comment and remove stray duplicate * [e0bc418f] docs(motion): document v019-release composition and captions convention * [e0bc418f] feat(release-recap): rebuild v0.19.0 video as panel-demo three-release recap CEO rejected the text-card v019-release composition and rescoped this to a recap of three releases (0.18.0-0.20.0) shipped in six days, built in the panel-demo register per the CEO's brief. Brings motion/kit/ and motion/compositions/panel-demo/ into this branch (byte-identical to master, which merged them after this branch diverged and cannot be rebased onto directly), deletes the obsolete motion/compositions/v019-release/ text-card composition, and adds motion/compositions/release-recap/: the panel frame, one intake typing reveal, three release cards (v0.18.0/v0.19.0/v0.20.0) cycling through a single shared kanban slot with their own progress-to-completed pill flip, a cursor that glides in once and click-pulses at each flip, a closing toast, and the roboco.tech outro, both at 1080x1920 and 1080x1080. Includes props.js, captions.json (X 136/280 chars, TikTok 365/2200 chars, no em dash), and a vitest smoke test (11/11 passing across all three compositions in the package). Also refreshes motion/README.md with the current design-bar/panel-demo-kit sections and a release-recap example replacing the deleted v019-release one. * [e0bc418f] fix(release-recap): polish pass — accumulate the three releases, fix facts, robust typewriter Cards now stack and accumulate instead of cycling one absolute slot (the three-release story never landed visually); duration tightened 20s to 15s; per-release copy corrected (0.18.0 fleet doctrine + design bar, 0.19.0 video engine + sandboxes, 0.20.0 root-owned coverage + the kit) along with the same fixes in captions.json; one cursor with one glide and one click landing at the pill edge instead of parking on pill text for 17s; the per-char typing reveal (which partially froze) replaced by a single width-steps mono typewriter sized to the supplied text. * [e0bc418f] feat(release-recap): identity redesign — real logo, real-UI sidebar, explainers, stat beat CEO review round 2: no logo, no similarity with the actual UI, explains nothing. Rebuilt: branded cold open (the real logo on an app-icon tile with a ring sweep, wordmark, tagline), a labeled sidebar mirroring the real panel (nav labels + active state + search + Live dot + CEO chip), a one-line explainer under each release title, a kinetic stat beat (3 releases / 6 days / 1 human — full-frame interstitial on the square cut where no side zone exists), and a logo lockup outro; 18s. The logo PNG is vendored into motion/public (offline constraint, same pattern as the fonts). * [e0bc418f] fix(release-recap): darkmode logo hero — crisp 1024px mark replaces the pixelated 64px tile The cold open now uses logos/roboco-logo-darkmode.png (vendored into motion/public), built for dark backgrounds with the wordmark included: full-size mark with a soft glow and the ring sweep, no white tile, no separate typed wordmark. The small app-icon tiles in the sidebar and outro keep the 64px icon where tiny sizes favor it. --------- Co-authored-by: UX/UI Developer 1 <ux-dev-1@roboco.tech> Co-authored-by: UX/UI Documenter <ux-doc@roboco.tech> Co-authored-by: UX/UI Developer 2 <ux-dev-2@roboco.tech> Co-authored-by: Renn F <rennf93@users.noreply.github.com> |
||
|
|
1c799ea139 |
fix(tests): grok session test pins the fleet-wide subagent ban
The last test asserting the prompter kept the Agent tool — it pinned the allowance through the disallowed-tools string, which the #372 test sweep (grepping the flag names) missed.v0.21.0 |
||
|
|
6188cce58c |
[e0bc418f] Video: v0.19.0 - our newest release (#333)
* [e0bc418f] add v019-release HyperFrames composition with vertical/square cuts, theme, props, and smoke tests * [e0bc418f] fix(v019-release): remove local artifacts, replace em dashes, add captions.json and regression tests * [e0bc418f] chore: delete .venv symlink and tighten .gitignore for symlink leakage * [e0bc418f] chore(.gitignore): remove duplicate .pnpm-store line * [e0bc418f] fix(v019-release): clean internal em dashes and add package-lock regression test * [e0bc418f] chore(v019-release): remove committed vitest cache artifact from node_modules * [e0bc418f] fix(.gitignore): restore .pnpm-store/ next to its GH001 comment and remove stray duplicate * [e0bc418f] docs(motion): document v019-release composition and captions convention * [e0bc418f] feat(release-recap): rebuild v0.19.0 video as panel-demo three-release recap CEO rejected the text-card v019-release composition and rescoped this to a recap of three releases (0.18.0-0.20.0) shipped in six days, built in the panel-demo register per the CEO's brief. Brings motion/kit/ and motion/compositions/panel-demo/ into this branch (byte-identical to master, which merged them after this branch diverged and cannot be rebased onto directly), deletes the obsolete motion/compositions/v019-release/ text-card composition, and adds motion/compositions/release-recap/: the panel frame, one intake typing reveal, three release cards (v0.18.0/v0.19.0/v0.20.0) cycling through a single shared kanban slot with their own progress-to-completed pill flip, a cursor that glides in once and click-pulses at each flip, a closing toast, and the roboco.tech outro, both at 1080x1920 and 1080x1080. Includes props.js, captions.json (X 136/280 chars, TikTok 365/2200 chars, no em dash), and a vitest smoke test (11/11 passing across all three compositions in the package). Also refreshes motion/README.md with the current design-bar/panel-demo-kit sections and a release-recap example replacing the deleted v019-release one. * [e0bc418f] fix(release-recap): polish pass — accumulate the three releases, fix facts, robust typewriter Cards now stack and accumulate instead of cycling one absolute slot (the three-release story never landed visually); duration tightened 20s to 15s; per-release copy corrected (0.18.0 fleet doctrine + design bar, 0.19.0 video engine + sandboxes, 0.20.0 root-owned coverage + the kit) along with the same fixes in captions.json; one cursor with one glide and one click landing at the pill edge instead of parking on pill text for 17s; the per-char typing reveal (which partially froze) replaced by a single width-steps mono typewriter sized to the supplied text. * [e0bc418f] feat(release-recap): identity redesign — real logo, real-UI sidebar, explainers, stat beat CEO review round 2: no logo, no similarity with the actual UI, explains nothing. Rebuilt: branded cold open (the real logo on an app-icon tile with a ring sweep, wordmark, tagline), a labeled sidebar mirroring the real panel (nav labels + active state + search + Live dot + CEO chip), a one-line explainer under each release title, a kinetic stat beat (3 releases / 6 days / 1 human — full-frame interstitial on the square cut where no side zone exists), and a logo lockup outro; 18s. The logo PNG is vendored into motion/public (offline constraint, same pattern as the fonts). * [e0bc418f] fix(release-recap): darkmode logo hero — crisp 1024px mark replaces the pixelated 64px tile The cold open now uses logos/roboco-logo-darkmode.png (vendored into motion/public), built for dark backgrounds with the wordmark included: full-size mark with a soft glow and the ring sweep, no white tile, no separate typed wordmark. The small app-icon tiles in the sidebar and outro keep the 64px icon where tiny sizes favor it. --------- Co-authored-by: UX/UI Developer 1 <ux-dev-1@roboco.tech> Co-authored-by: UX/UI Documenter <ux-doc@roboco.tech> Co-authored-by: UX/UI Developer 2 <ux-dev-2@roboco.tech> Co-authored-by: Renn F <rennf93@users.noreply.github.com> |
||
|
|
a322dc8f9f | chore(release): 0.21.0 | ||
|
|
e1a8734daa |
fix(tests): hoist ROLE_CONFIGS import in the subagent invariant test
PLC0415 red on master's quality gate — the module already imports ROLE_CONFIGS at top level; the function-level re-import from #372's invariant test is deleted. |
||
|
|
7bc42ec114 |
fix(runtime): fleet-wide subagent ban — no role fans out, not one (#372)
CEO directive 2026-07-09: every allows_subagent in role_config flips to False (was True for cell_pm, main_pm, product_owner, head_marketing, prompter, secretary); the grok path's drifted _SUBAGENT_ALLOWED_ROLES allowlist empties to match. The spawn manifest already consumes the flag, so Claude-path agents lose the Agent tool and grok-path agents get it in disallowed-tools by construction. New invariant test iterates every role config so a single role can't quietly regain it. Co-authored-by: Renn F <rennf93@users.noreply.github.com> |
||
|
|
d585260087 |
fix(panel): pin next to 16.1.1 — 16.2.x breaks searchParams soft navigation in Chrome (#371)
Every URL-driven tabbed page (kanban/business/metrics/notifications/KB) was stuck on the first tab in real Chrome 149 against a production build: clicking a tab fetched the target RSC payload, then Next's router MPA-reloaded the CURRENT url, snapping the tab back. Bisected empirically with a scripted real-Chrome sweep: 16.1.1 green, 16.2.6 red, 16.2.10 (latest stable) red — the regression spans the 16.2 line and reproduces with zero app data, no proxy, and a fresh profile. Dev builds and Playwright's bundled Chromium mask it, which is how the #354 bump (shipped in v0.20.0) passed review. Reverts the next half of that bump; the axios half stays. Co-authored-by: Renn F <rennf93@users.noreply.github.com> |
||
|
|
e1d1fdc707 | Merge pull request #370 from rennf93/fix/gateway/sync-branch-standalone-base | ||
|
|
a87b55f748 |
ci(cla): allowlist the org's own agent identities
Agent workspace clones commit as '<Display Name> <slug@roboco.tech>'; the emails map to no GitHub account, so CLA Assistant identifies those committers by display name and reds any PR carrying agent-authored commits with a signature no agent can post (live on PR #333). The fleet is the org's own machinery, not an external contributor — the roster names are allowlisted, wildcarded per team family. |
||
|
|
213b6ba830 |
refactor(gateway): fold sync_branch base refusal into one helper for the xenon budget
The inline compound condition pushed _sync_branch_preflight_rejection to cyclomatic rank C on the PR gate; the whole refusal predicate now lives in _sync_base_refused. |
||
|
|
93f63d5fe9 |
fix(gateway): allow sync_branch onto a legitimately-master base
A standalone task (video/CI-watch/dep-update — no parent) and a child of a branchless coordination parent merge into the project default branch by design, but the protected-base guard refused every master/main base, hard-wedging their rebases into block/PM/respawn churn (hit live on the v0.19.0 video task). The rebase force-pushes only the task branch (with lease) and cannot write to the base, so the guard now refuses master/main only when it is mis-resolved: a branch-bearing parent exists or the parent row is missing/corrupt. The '-'-prefixed injection guard stays unconditional. |
||
|
|
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> |
||
|
|
12b91d17a2 |
chore(lifecycle): regenerate stale verb-table artifacts
Verb-description changes from the recent gateway PRs (declare_coverage, sync_branch stash, reviewer unclaim) never regenerated the artifacts; the master gate died at reflow-check before foundation-check could flag it.v0.20.0 |
||
|
|
9e6f3a5c6c |
docs(motion): reflow hard-wrapped prose + lock 0.20.0
The kit READMEs from #365 were hard-wrapped and failed the master quality gate's reflow-check (the gate runs on master pushes, not PRs). uv.lock picks up the 0.20.0 project version from the release bump. |
||
|
|
80e54390dc | + uv.lock | ||
|
|
7b6eaa47c8 | chore(release): 0.20.0 | ||
|
|
dd71eb381e |
feat(motion): panel-demo kit + reference composition (#365)
Reusable pk-* building blocks (motion/kit/) that recreate the control panel's look for HyperFrames compositions: frame chrome, task card, status pills with swap crossfade, chips, toast, typing reveal, cursor sprite. Tokens lifted from the panel's dark theme and badge components. Reference composition motion/compositions/panel-demo/ (12s, both cuts): a task title types into intake, the card materializes, the cursor clicks, the status pill flips to completed, a toast slides in. Verified: motion vitest gate green (7/7); both cuts visually checked in-browser at native resolution. Co-authored-by: Renn F <rennf93@users.noreply.github.com> |
||
|
|
08c02e2251 |
[79d686f0] Add page-scoped refresh button to the navbar (#351)
* [870467e6] Frontend: page-scoped refresh provider, hook, and navbar button (#347) * [55376b8a] Create page-scoped refresh provider and context (#327) * [55376b8a] feat(panel): add page-scoped refresh context and provider * [55376b8a] docs(frontend): add page-refresh-provider component documentation --------- Co-authored-by: Frontend Developer 1 <fe-dev-1@roboco.tech> Co-authored-by: Frontend Documenter <fe-doc@roboco.tech> * [a0c02d0f] Add public usePageRefresh hook (#332) * [a0c02d0f] test(hooks): assert usePageRefresh is exported from hooks barrel * [a0c02d0f] feat(hooks): add public usePageRefresh hook with provider and tests * [a0c02d0f] fix(panel): move hook test wrappers to components and rename providers.tsx to unshadow barrel * [a0c02d0f] docs(panel): document usePageRefresh hook and PageRefreshProvider API --------- Co-authored-by: Frontend Developer 2 <fe-dev-2@roboco.tech> Co-authored-by: Frontend Documenter <fe-doc@roboco.tech> Co-authored-by: Renn F <rennf93@users.noreply.github.com> * [5f28dd9b] Add navbar refresh button and remove inline dashboard refresh buttons (#336) * [5f28dd9b] Align PageRefreshProvider with active hook API and remove inline dashboard refresh buttons * [5f28dd9b] Remove unused scope-keyed PageRefreshProvider, context, and associated tests * [5f28dd9b] Address QA revision: add header refresh tests, page-scoped label, remove dead provider code and .venv symlink, revert formatting-only changes * [5f28dd9b] Remove remaining inline dashboard refresh buttons and committed .venv symlink * [5f28dd9b] docs(frontend): update page-refresh provider docs and panel README for navbar refresh button * [5f28dd9b] fix(panel): remove .venv symlink, ignore root .venv entries, and thin task-detail page data fetch into useTaskDetail hook * [5f28dd9b] Extract GitBrowser data fetching into useGitBrowser hook and add tests; verify .venv cleanup and task-detail thin hook usage * [5f28dd9b] fix(panel): remove root .venv symlink, restore .gitignore anchored rule, and revert lifecycle.json formatting noise * Delete .venv --------- Co-authored-by: Frontend Developer 2 <fe-dev-2@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> Co-authored-by: Renn F <rennf93@users.noreply.github.com> * [b8e1de1b] Fix navbar refresh button disabled state when registry is empty (#356) (#358) * [b8e1de1b] fix(panel): derive navbar refresh disabled state from registry, not unused prop PageRefreshProvider now computes `disabled` from whether any refresh callback is currently registered (registry size > 0) instead of a static, never-passed `disabled` prop that left the button permanently enabled. header.tsx now destructures `disabled` from usePageRefresh() and disables the button on `disabled || loading`. Updated the tests that asserted the old always-enabled-by-default behavior and added a new header test asserting the button is disabled with zero registered callbacks. * [b8e1de1b] docs(panel): document PageRefreshProvider disabled state derived from registry Updated documentation to reflect the refactored PageRefreshProvider behavior: the `disabled` state is now derived from whether any refresh callbacks are currently registered (empty registry = disabled), rather than a static `disabled` prop. Clarified in both panel/README.md and the full component guide that the navbar refresh button disables when no callbacks are registered and when a refresh cycle is in progress. Updated API documentation to remove the now-removed `disabled` prop from PageRefreshProviderProps and updated code examples and test coverage descriptions to reflect the new callback-driven semantics. --------- Co-authored-by: Frontend Developer 1 <fe-dev-1@roboco.tech> Co-authored-by: Frontend Documenter <fe-doc@roboco.tech> * test(panel): mock usePageRefresh in tests predating the provider Merge-skew: the page-refresh feature makes CommandCenter and the agent detail page call usePageRefresh; three tests merged from master render them without the new provider. Mock the hook module, matching the files' stub-everything style. --------- 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> Co-authored-by: Renn F <rennf93@users.noreply.github.com> |
||
|
|
ec5323917e |
[d8a2999b] CI-watch: fix the CI regression on roboco-api (#364)
* [ffcc7317] fix(tests): tolerate tagless checkouts in release-readiness smoke test (#362) (#363) Co-authored-by: Backend Developer 1 <be-dev-1@roboco.tech> * docs: reflow panel/UPGRADE.md — no hard-wrapped prose --------- Co-authored-by: Backend Developer 1 <be-dev-1@roboco.tech> Co-authored-by: Renn F <rennf93@users.noreply.github.com> |
||
|
|
0888fefdac |
[3f57bfb0] Supersede external PR #344: finish + harden it ourselves (#360)
* chore(deps): bump axios from 1.13.2 to 1.16.0 in /panel Bumps [axios](https://github.com/axios/axios) from 1.13.2 to 1.16.0. - [Release notes](https://github.com/axios/axios/releases) - [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md) - [Commits](https://github.com/axios/axios/compare/v1.13.2...v1.16.0) --- updated-dependencies: - dependency-name: axios dependency-version: 1.16.0 dependency-type: direct:production ... Signed-off-by: dependabot[bot] <support@github.com> * [47c230b6] Finish + harden axios dependency bump (supersedes PR #344) (#355) * [9a0e97fb] Bump axios to ^1.16.0 and verify build/lint/typecheck/test (#349) * [9a0e97fb] chore(panel): regenerate lockfile for axios ^1.16.0 (1.18.1) * [9a0e97fb] docs(changelog): document axios ^1.16.0 bump with new transitive deps --------- Co-authored-by: Frontend Developer 1 <fe-dev-1@roboco.tech> Co-authored-by: Frontend Documenter <fe-doc@roboco.tech> * [2b0d5fe8] docs(api-client): record axios 1.16 compatibility audit finding (#352) Co-authored-by: Frontend Developer 1 <fe-dev-1@roboco.tech> * [36ae2ab0] Remove stray .venv symlink and harden .gitignore (#359) * [36ae2ab0] chore(repo): remove stray .venv symlink and harden .gitignore Removes the tracked .venv symlink at the repository root and drops the trailing slash on the .gitignore .venv entry so the pattern matches a symlink or plain file named .venv, not only a real directory. * [36ae2ab0] chore(repo): untrack the .venv symlink from the branch --------- Co-authored-by: Frontend Developer 1 <fe-dev-1@roboco.tech> --------- Co-authored-by: Frontend Developer 1 <fe-dev-1@roboco.tech> Co-authored-by: Frontend Documenter <fe-doc@roboco.tech> --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Frontend Developer 1 <fe-dev-1@roboco.tech> Co-authored-by: Frontend Documenter <fe-doc@roboco.tech> |
||
|
|
47c927c598 |
feat(gateway): root-owned acceptance criteria via declare_coverage (#357)
The coverage gates had no vocabulary for criteria only the root itself can satisfy (the supersede PR from feature/main_pm/*, closing the contributor's PR): once a Main PM declared coverage for the legitimate cell criteria, the idle gate demanded a cell for the impossible ones too, so they got pushed into a cell task and the cell PM (correctly) escalated. declare_coverage now accepts the PM's own task: self-declared criteria count as claimed for the idle gate and satisfied for the roll-up (the roll-up actor is their owner by construction), surface as claimed_by=root in the briefing, and both PM prompts say to never hand a cell a criterion it cannot satisfy inside its own cell. Co-authored-by: Renn F <rennf93@users.noreply.github.com> |
||
|
|
8327df4fd2 |
[f9ab9c9d] Supersede external PR #343: finish + harden it ourselves (#354)
* chore(deps): bump next from 16.1.1 to 16.2.6 in /panel Bumps [next](https://github.com/vercel/next.js) from 16.1.1 to 16.2.6. - [Release notes](https://github.com/vercel/next.js/releases) - [Commits](https://github.com/vercel/next.js/compare/v16.1.1...v16.2.6) --- updated-dependencies: - dependency-name: next dependency-version: 16.2.6 dependency-type: direct:production ... Signed-off-by: dependabot[bot] <support@github.com> * [bd79402c] Verify and harden panel/ on Next.js 16.2.6 (#350) (#353) * [bd79402c] chore(panel): align eslint-config-next to 16.2.6 with next * [bd79402c] docs(panel): add UPGRADE guide and dependency alignment note Documented the Next.js 16.2.6 dependency bump and version-alignment requirement for eslint-config-next. Added UPGRADE.md with procedures for future Next.js bumps and troubleshooting, and updated README.md with a dependency management section pointing to the guide. --------- Co-authored-by: Frontend Developer 1 <fe-dev-1@roboco.tech> Co-authored-by: Frontend Documenter <fe-doc@roboco.tech> --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Frontend Developer 1 <fe-dev-1@roboco.tech> Co-authored-by: Frontend Documenter <fe-doc@roboco.tech> |
||
|
|
94dd6b6d53 |
chore(prompts): decomposition minimalism — fewest subtasks the work needs (#348)
CEO doctrine after an axios bump became a planning parent plus two sequenced same-branch code children: single-concern work is ONE subtask covering change, verification, and PR; same-branch sequenced siblings are one task wearing two ids (the gateway serializes sibling code subtasks anyway, so the split buys zero parallelism); the i_will_plan sub_tasks list is a checklist, not a delegation quota. Co-authored-by: Renn F <rennf93@users.noreply.github.com> |
||
|
|
199cc5d2bc |
fix(api): run the RAG reconcile in the background, never on the bind path (#346)
The lifespan awaited the reconcile, and the journal/learning backfill made it expensive: up to 200 entries each needing an Ollama embedding behind a busy Ollama held the API bind down for 30+ minutes on the NAS (observed: ~6 embeds/min). uvicorn only binds after the lifespan completes, so the whole stack 502'd while a best-effort maintenance pass ran. The reconcile is now a background task scheduled at the end of startup (crash-logged via done-callback, cancelled at shutdown); the backfill still converges across boots under its per-boot cap. 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> |
||
|
|
5886336259 |
fix(db): latch init_db per database and time-bound the alembic runner (#342)
Bootstrap and the API lifespan both ran init_db in one process seconds apart; the second call re-entered the alembic-in-thread machinery (nested asyncio.run + NullPool engine + greenlet bridge in a reused worker thread) for zero benefit and hung two consecutive NAS boots there, blocking the API bind forever with zero SQL activity. init_db now latches per database URL (drop_db resets it; a different DB always runs fully), and the alembic worker is bounded at 300s -- a wedged thread fails startup loudly with a pinpointed error so the container restarts into a clean retry instead of hanging silently. Co-authored-by: Renn F <rennf93@users.noreply.github.com> |
||
|
|
5e7c498d00 |
fix(runtime): auto-submit is unconditional; refusals brief the fallback PM
The PR-gate turn cut (#295) already auto-submitted assembled tasks, but its refusals fell back to a PM spawn silently -- in production the PM turns the cut was meant to remove kept happening with no visible cause (live case: an AC-coverage refusal). The flag is gone (the fallback is the safety net), the umbrella/branchless exclusion uses the canonical batch predicates, and every refusal reason now rides into the spawned PM's prompt so the fallback starts informed. |
||
|
|
f48d088c08 |
fix(gateway): working exits for wedged agents + declare_coverage roll-up unblock (#341)
A live task burned 5+ hours because every exit was locked. unclaim now works from verifying and needs_revision (service guard + lifecycle edge); the circuit breaker and the i_am_done push-failure remediate name the working chain ending in unclaim(); sync_branch(stash=true) clears the DIRTY_WORKSPACE dead-end (pop-conflict preserves the stash); blocking a task QA already owns now says to idle instead of listing states; the orchestrator auto-block logs real errors and skips states where blocking is meaningless instead of force-blocking them. declare_coverage (cell/main PM) retroactively stamps parent-AC refs on a child that implements them -- closing the roll-up deadlock where the declaring child was cancelled and its re-delegated replacement completed the work uncredited. Cancelling a ref-declaring child now warns and surfaces the orphaned criteria. Co-authored-by: Renn F <rennf93@users.noreply.github.com> |
||
|
|
889d48b99b | Merge branch 'master' of https://github.com/rennf93/roboco into fix/backend/video-renderer-producer-api | ||
|
|
47d78f50ee |
feat(sandbox): on-demand provisioning via request_sandbox verb (#338)
* feat(sandbox): on-demand request_sandbox verb replaces eager provisioning Sandboxes were provisioned at every agent spawn for opted-in projects, so every role paid the sidecar spin-up and a provisioning failure refused the spawn. Provisioning now happens when an agent asks: the request_sandbox do-verb (dev + QA) reaches the orchestrator through ContentActionsDeps, ensure_sandbox provisions idempotently with an in-memory per-agent cache (evicted at teardown and janitor sweep), and creds return in the envelope payload including ready-to-export ROBOCO_TEST_* values. Spawn now only injects a marker env naming the available services plus a briefing line; sandbox failures can no longer refuse a spawn. Teardown lifecycle unchanged. * feat(sandbox): harden request_sandbox + Phase 3 wiring proof and docs Hardening from adversarial review: ensure_sandbox now provisions the project's full opted-in set on first request (a later superset can never tear down a live sandbox mid-use), serializes per-agent behind an asyncio lock (a client timeout-retry no longer races its own in-flight provision), and verifies container liveness on every cache hit (a dead sandbox evicts and re-provisions instead of serving dead creds). MCP client budget 720->1080s for the full-set cold case. Phase 3: e2e smoke wiring test (manifest grants + guard-chain envelopes over the real API), sandbox-db/tools/map docs and CLAUDE.md rewritten for on-demand. * feat(sandbox): release sandboxes when the agent's work ends CEO directive: sidecars must not dangle once the agent is done. The six work-ending verbs (i_am_done, unclaim, i_am_idle, pass_review, fail_review, i_documented) now release the caller's sandbox best-effort on their success path via release_sandbox (lock + teardown + cache evict; a no-sandbox agent costs a dict lookup). Container removal and the janitor remain the backstop; a re-request provisions fresh. * test(sandbox): monkeypatch the release hook instead of method assignment mypy method-assign rejected the direct AsyncMock assignments; the prior static gate ran before this test file landed. * test(sandbox): guard envelope evidence for mypy in verb tests * chore(prompts): regenerate verb tables for request_sandbox * chore: resolve merge with master (breadcrumbs + statement budget) --------- Co-authored-by: Renn F <rennf93@users.noreply.github.com> |
||
|
|
9e4025b822 |
fix(git): scope post-op ownership repair to what the op could change (#337)
Co-authored-by: Renn F <rennf93@users.noreply.github.com> |
||
|
|
4fb0059556 |
fix: journals/learnings never reached the RAG corpus + git-readonly slug 404s (#339)
* fix(rag): per-index chunk floors — journals and learnings were never indexed The global 200-char garbage floor (sized for code/doc chunks) discarded every templated journal note and most distilled org-memory lessons, silently: ingest returned success with zero chunks, so agent journals and learnings were never retrievable via RAG. IndexConfig now carries a per-type min_chunk_length (journals 40, learnings 80, others unchanged). * fix(mcp): git-readonly tools default project_slug from the container env Agents 404ed /api/git/status with 'Project not found: roboco' — the tools made the LLM supply the slug and six doc examples taught a slug that matches no registered project. The tools now fall back to the ROBOCO_PROJECT_SLUG the orchestrator already injects, and the stale examples are corrected. * feat(rag): startup backfill re-ingests zero-chunk journals and learnings Before the per-index chunk-floor fix, ingest() returned success with chunk_count=0 for undersized content: every historical journal entry and distilled learning below the (then-global) 200-char floor was durably recorded in journal_entries but silently never got a chunks_journals / chunks_learnings row, and no exception meant the existing dead-letter (rag_index_failures) never saw it either. Extends the startup reconcile (roboco/api/app.py _reconcile_rag_indexes) with a new pass: backfill_unindexed_journals (roboco/services/ rag_index_failures.py) queries journal_entries for rows missing from each vector table and re-ingests them through the same live code paths (_reindex_journal_entry / record_learning). Journals and learnings are backfilled independently since a LEARNING entry can clear the (lower) JOURNALS floor while still failing the (higher) LEARNINGS floor — a learning's doc_source is a content hash, not the entry id, so presence there is checked by hashing each candidate the same way LearningsIndexPlugin.record_learning does and batch-querying chunks_learnings for those exact sources. Bounded to 200 rows per pass per boot (converges over restarts on a larger backlog) and best-effort per row (one failure never aborts the pass). Rows still under the current floor are excluded by a length filter in the SELECT so they are never retried forever, and private entries are excluded from the JOURNALS pass exactly like the live indexing path. * test(rag): scope backfill assertions to their own rows --------- Co-authored-by: Renn F <rennf93@users.noreply.github.com> |
||
|
|
0e9f21de69 |
fix(api): default event loop to asyncio + cancellation-safe commit — kills the CI segfault (#340)
* fix(api): default the event loop to asyncio + cancellation-safe commit The recurring CI e2e segfault traced to uvloop: the harness's uvicorn.run() auto-selected it while production's serve() path never consulted Config.loop (stock asyncio, accidentally safe). Every launch site now resolves ROBOCO_UVICORN_LOOP (default asyncio; uvloop opt-in), and DbCommitMiddleware's commit-in-send can no longer be interrupted mid-wire: on cancellation it gets a bounded grace to finish (committed data survives the 504), else invalidate-and-reraise. * feat(runtime): expected-stop breadcrumbs attribute container deaths Two production exit-143s had no attributable source: every orchestrator kill path now records a short reason breadcrumb, and the exit monitor consumes it -- an expected stop logs its reason at info, a genuinely unexpected one logs none_recorded plus docker-inspect diagnostics (OOMKilled, timestamps) so the next mystery SIGTERM self-identifies. --------- Co-authored-by: Renn F <rennf93@users.noreply.github.com> |
||
|
|
cf96fa7f11 |
fix(video-renderer): align render.js with @hyperframes/producer 0.7.36 API
The sidecar's render.js was written against an older producer API and
never rendered on this deploy: it called createRenderJob({inputPath,
outputPath, width, height, fps}) and executeRenderJob(job, onProgress),
but 0.7.36 moved the paths to executeRenderJob(job, projectDir,
outputPath, onProgress) and takes only render params in the job config.
outputPath arrived undefined and dirname(undefined) threw on every
render (every composition), HTTP 500.
- Pass projectDir (the composition dir) + outputPath as executeRenderJob
args; select the cut via config.entryFile.
- Drop width/height — 0.7.36 reads dimensions from the composition HTML
(data-width/data-height); add the required quality tier.
- Stage motion/public into the composition dir: the producer serves the
compiled entry at the file-server root, clamping the composition's
../../public/fonts to /public/fonts, so fonts must sit under projectDir.
Verified by rendering both v0.19.0 cuts against the running sidecar's
0.7.36 libraries: styled MP4s, no asset 404s.
|
||
|
|
312ec990dd |
fix: prod triage 2026-07-08 — MCP auth residue, gateway envelopes, verb-loop cap, A2A interjection, manual spawn UX (#334)
* fix(auth): pass agent UUID to CLI-arg MCP servers (optimal/docs/search) The container token is HMAC-signed over the agent UUID (#314), but the optimal/docs/search MCP servers received the slug as their CLI arg and sent X-Agent-ID=<slug>, so every research/RAG/docs call 401ed with signature mismatch under enforced auth. Pass the already-computed agent_uuid in the three args lists instead. * fix(gateway): include remediate in gateway.rejected audit details Conventions-gate rejections carry the offending file:line listing only in the envelope's remediate field, which the audit row dropped -- ops logs showed just the violation count with no way to see what blocked. * fix(gateway): return envelope on do/commit git failure A GitError from the commit verb propagated to the generic middleware handler, so agents got a raw error blob with no remediate/next. Catch it and return an error envelope; 'no changes added to commit' with an explicit files list now names the mismatch and the omit-files fallback. * fix(agent-sdk): absolute rejection cap breaks slow-drip verb loops The verb circuit breaker only counted rejections inside a 60s sliding window, so an agent retrying i_am_done every 3-4 minutes looped for 30+ minutes without tripping it. Add a session-scoped cumulative per-(verb, task) cap at 3x the windowed limit that trips regardless of pacing. * feat(a2a): CEO chime-in interjects into the viewed conversation Previously reply_as_ceo re-homed the message into a canonical CEO<->target conversation with no panel surface, so a chime-in reported success but was invisible and only opportunistically delivered. interject_as_ceo now inserts the message into the conversation being viewed (from_agent=ceo, directed via an @target content prefix), bumps that conversation's counters with the unread ping keyed to the addressed participant, and both participants see it in transcript and read_a2a. * feat(panel): manual spawn carries task + message, surfaces refusals The agent detail page spawned with no request body (task/message impossible), the spawn button could double-fire (2.5ms double-POST seen live), and refusal reasons never reached the UI: readiness refusals were generic 500s and the already-running no-op looked like success. Detail page now uses SpawnAgentDialog, a synchronous ref guard blocks re-entry, AgentReadinessError maps to 409 with its reason shown, already_running is signalled and toasted, and a task_id builds a task-aware prompt instructing the claim (task_id alone never did), with the CEO's message appended as a note. * test(panel): align a2a page test with the interjection footer copy The chime-in rebuild changed the composer footer; the page-level test asserting the old copy was outside the rebuild's scoped vitest run. * fix(api): commit the request DB session before the response is sent FastAPI unwinds yield-dependencies after the response bytes go out, so get_db's post-yield commit raced the client's next request -- a verb could return ok while its claim/status write was still uncommitted (the e2e ok-without-effect flake family), and a failed commit was silently lost behind an already-sent 200. DbCommitMiddleware (innermost, pure ASGI) commits the session stashed by get_db_committed before forwarding http.response.start; commit failure now surfaces as a 5xx. get_db is untouched for its direct non-request callers. * fix(db): invalidate, not rollback, the session on request cancellation With the commit moved into the send path, the flow-verb timeout can cancel mid-commit; rolling back then issues another command over an asyncpg connection stranded mid-wire-protocol, and the poisoned connection segfaults uvloop/asyncpg when a later checkout recycles it (3/3 identical CI faulthandler dumps). On CancelledError discard the connection via session.invalidate() -- SQLAlchemy's documented handling for a timeout during commit -- and keep rollback for plain exceptions. --------- Co-authored-by: Renn F <rennf93@users.noreply.github.com> |
||
|
|
60f571bc02 |
fix(release): publish via GitHub REST — gh CLI is not installed in any image (#331)
ReleaseExecutor.publish_release shelled out to 'gh release create', but no
Dockerfile installs the gh CLI (verified missing in the live orchestrator
container), so an armed release manager died at publish AFTER the release
commit was pushed. Publish now POSTs /repos/{owner}/{repo}/releases with the
project's decrypted token — same auth/httpx pattern as PR creation, same
fail-closed semantics (non-201 -> structured publish_failed, CEO retries;
the 300s deadline is the httpx client timeout). Subprocess publish-timeout
test replaced with REST-path tests (201/non-201/transport-error/no-token).
Co-authored-by: Renn F <rennf93@users.noreply.github.com>
|
||
|
|
cc07c580e2 |
chore(docker): lean images — drop unused playwright, dedupe grok layer, cache-stable /app layering
- agent-dev-fe / agent-qa-fe: remove playwright chromium + its system libs (~770MB each; verified unused — panel tests are vitest, e2e harness is scripted Python; pnpm kept) - agent-grok: drop the redundant chown -R that duplicated the 149MB CLI tree into a second layer (install already runs as agent) - agent-base + orchestrator runners: split the single /app COPY into .venv-first / source-last layers so a source-only deploy re-layers ~13MB instead of ~380MB per image - agent-base: split the 813MB apt+node+claude-code RUN so a CLI bump no longer re-downloads the OS/node layer - .dockerignore: exclude gitignored docs/internal from the orchestrator's docs COPY; pin uv helper image to 0.11 - verified: all four images rebuilt + runtime-probed (claude/git/jq/node/uv/pnpm/grok, import roboco, docs/alembic/agents present); .venv layer proven CACHED across a source-only change |
||
|
|
0bf0cd69b3 |
fix(release): close the 0.19.0 scan findings — sandbox mongo tag, flow-verb timeout walls, video hardening (#329)
- mongo:8-alpine → mongo:8 (tag never existed; a mongo-opted project could spawn no agents) + a Docker Hub tag-existence e2e guard for every sandbox engine - flow-verb timeouts at both walls: shared SLOW_VERBS policy (i_am_done / submit_up / submit_root / open_pr / i_will_work_on get the 900s server budget); the MCP client now outlasts the server budget (+10s headroom, orchestrator-injected env) so agents receive the middleware's clean 504 envelope instead of dying at the old flat 30s client timeout - cancellation safety: the quality gate kills+reaps its child on CancelledError; create_pr records the PR via a shield-with-wait-out helper so the write can neither be skipped nor race get_db's rollback - video engine: renderer sidecar isolated on a render-only network, 2g/2cpu caps, 570s render watchdog with exit-on-hang, 512MB tar decompression cap, CEO notification on terminal render failure, reject under the approve mutex (fail-closed on Redis-down) - dead python-jose dependency removed (drops ecdsa and its unfixable Minerva advisory PYSEC-2026-1325); panel --font-mono now a real monospace stack Co-authored-by: Renn F <rennf93@users.noreply.github.com>v0.19.0 |
||
|
|
2a9d9e25d9 |
feat(tasks): task-content guardrails — structured plans + constraints split (#328)
* feat(tasks): task-content guardrails — structured plans + constraints split Bound task PLANNING content the way journals/notes already are, fixing the poor task quality flagged 2026-07-07 (degenerate roots, over-decomposed leaves, descriptions bloated by an auto-attached conventions dump). Phase A — plan/AC guardrails (no migration): - _pm_sub_tasks_gate: cap sub_tasks at 7; per-subtask ceilings (title <=200, description <=600) enforced at both the Pydantic boundary and the gate. Dropped the min-2-roots and no-subtasks-on-code rules: both contradict the 2026-05-08 rule (test_cell_pm_can_plan_code_typed_parent_via_i_will_plan) and break legitimate single-cell roots. Long comment in the gate explains. - IWillPlanRequest: plan <=2000, approach <=800 (floor 150 kept), typed SubTaskCreate/RiskCreate/OpenQuestionCreate replacing loose list[dict]. - DelegateRequest + task_completeness: acceptance_criteria capped at 7 items, each <=200 chars. New FieldRule.MAX_LENGTH_LIST + _post_rule_reject helper (extracted to keep the gate under xenon B). - Routes dump typed models to dicts for the existing rich_plan shaper. Phase B — conventions split (migration 068): - New nullable tasks.constraints Text column; _attach_baseline_constraints now writes the ## Constraints block there instead of appending to description, so description is the human-authored instruction only. The conventions still reach the agent independently at spawn via the ambient block, so agent correctness is unaffected. - TaskResponse / Task model / panel Task type carry constraints; panel shows a read-only Constraints card. Field is optional on the TS type (backend returns null for flag-off / pre-migration rows). Tests: 5 new gate unit tests, 7 schema tests, 3 AC policy tests, 3 e2e smoke scenarios; 4 baseline-constraints integration tests updated. ruff/mypy/xenon clean; 10026 unit+foundation+e2e green; panel typecheck clean. Refs: plan breezy-imagining-kahn * test(tasks): use typed SubTaskCreate instead of dict literals in plan tests make quality runs mypy over tests/ (1079 files), not just roboco/ — the four sites passing dict literals to the now-typed sub_tasks: list[SubTaskCreate] field failed mypy. Construct SubTaskCreate directly; the typed model raising ValidationError IS the boundary the rejection tests assert. * fix(deps): drop unused python-jose — clears PYSEC-2026-1325 (ecdsa, no fix) CI's pip-audit went red on a freshly-published advisory PYSEC-2026-1325 against ecdsa 0.19.2 (no fix published — 0.19.2 is the latest). ecdsa is a transitive dep of python-jose, which is a DIRECT dep of roboco but is NOT imported anywhere in roboco/ or tests/ (grep-verified). The actual JWT path uses PyJWT (import jwt) + fastapi_users.jwt, not python-jose. So python-jose is a dead dependency. Removing it (deletion over an --ignore-vuln waiver) drops ecdsa + rsa + pyasn1 + their type stubs from the lockfile, eliminating the CVE at the source. deptry roboco/ stays clean (no missing-dep), mypy clean, auth + schema tests pass. Master CI was green 9h before this PR's run, so the advisory published in that window would red any run including master — this fix unblocks both. * chore(prompts): regenerate verb tables for typed plan sub_tasks Phase A's IWillPlanRequest schema change (sub_tasks/risks/open_questions from loose list[dict] to typed SubTaskCreate/RiskCreate/OpenQuestionCreate) made the auto-generated verb tables stale. Regenerated via scripts/regenerate_verb_tables.py — the diff is purely the signature reflection (list[str|str] -> list[SubTaskCreate], etc.). Required by the foundation-check gate (Makefile:559). --------- Co-authored-by: Renn F <rennf93@users.noreply.github.com> |
||
|
|
92ab13bce0 |
Fix/backend/flow verb timeout row lock (#326)
* feat(sandbox): pluggable per-engine registry (postgres/redis/mongo) Replaces the hardcoded postgres+redis branches in the provisioner and the env emitter with a registry of SandboxEngine specs (image, run args, readiness probe, connection, ROBOCO_TEST_* env) in a pure low module (roboco/models/sandbox.py). VALID_SANDBOX_SERVICES is derived from the registry — single source of truth — and the provisioner + orchestrator iterate it, so adding an engine is one class + one registry line, not another branch. Adds a mongo:8-alpine engine (ROBOCO_TEST_MONGO_*) as the third service alongside postgres/redis. Also fixes the cold-pull loop that stranded v0.19.0 board agents with empty error strings: docker run pulled inline under a 20s deadline, so a NAS cold pull was killed, cancelled, and re-pulled from scratch forever. _ensure_image now inspects + pulls (300s) before run; provisioning errors log type+message so a bare TimeoutError no longer shows as "". Panel edit-project dialog: postgres/redis toggles -> a Set<string> multi-select driven by a SANDBOX_SERVICES catalog, so new engines appear in the UI by adding to the catalog. Tests: engine parity (allowlist==registry, unique slugs/images, no None leak in env, SandboxInfo aggregates every engine), mongo provision + env injection, plus the existing postgres/redis provision/env/spawn/janitor suite updated to the registry shape. 821 unit / 5 skip green; ruff + mypy (360 files) clean. * docs(sandbox): reflect pluggable engine registry + mongo across docs CHANGELOG (0.19.0): Added entry for the pluggable sandbox engine registry (postgres/redis/mongo) + Fixed entry for the cold-pull loop/empty-error strand that boarded v0.19.0 board agents. docs/map (9 files): sandbox subsystem blurbs, SandboxProvisioner rows, _maybe_provision_sandbox/_append_sandbox_env rows, feature-flag rows, the migration-057 row + v0.17.0 delta, and the models.md VALID_SANDBOX_SERVICES note — all retitled to DB/Redis/Mongo via the engine registry (roboco/models/sandbox.py), with the one-class-one-line extension story and the _ensure_image cold-pull fix. Production-network (roboco_data) lines left as postgres+redis — mongo is sandbox-only, not a prod service. docs/rag (3 files): sandbox-db.md rewritten around the registry (engine list, generic _provision_engine, image pre-pull, ROBOCO_TEST_DB_*/REDIS_*/MONGO_* incl. MONGO_AUTH_DB=admin, single emit_env); config-reference sandbox flag row + subsection retitled; db-network-isolation framing broadened to postgres/redis/mongo. preconditions-and-rejections left untouched (its hit was an unrelated gateway see-also link). * test(e2e): harden umbrella close terminal reads with bounded wait-for-state The MegaTask umbrella close test flaked once on CI (ceo-approve returned 200 but the re-fetch saw awaiting_pm_review) then passed on re-run. The production path is deterministic: complete -> main_pm_complete -> submit_pm_review -> escalate_to_ceo -> ceo_approve -> commit, all on one session, all awaited; the fire-and-forget completion hooks are isolated (own session, best-effort, never touch task.status or the request session). 20 local runs could not reproduce it. The one real surface is the read pattern: the e2e stack commits on the uvicorn thread's loop and reads via a separate loop (run_db -> asyncio.run with a fresh engine), so a terminal single point-read can race a still-draining completion hook on a contended runner. Replace the two terminal point-reads with a bounded wait_for_status poll. Strictly better than a one-shot read: absorbs the transient, and a genuine state bug still surfaces via the timeout branch asserting against the last-read state. * fix(gateway): bound hung flow-verbs with a server-side timeout A gateway intent-verb whose request transaction held the SELECT ... FOR UPDATE lock on the task row never committed: uvicorn does not cancel the endpoint coroutine on client disconnect and get_db only rolled back on Exception (not a hang/cancellation), so the row lock was held indefinitely and every later task-row write on that task wedged (2026-07-07 kimi-k2.7-code:cloud agent on task 79d686f0). Reads (evidence) and journal writes (note) stayed fast — the symptom that pointed at a task-row lock. Fix: pure-ASGI FlowVerbTimeoutMiddleware wraps each /api/v1/flow/* request in asyncio.timeout(flow_verb_timeout_seconds, default 120s). On expiry the inner app is cancelled; CancelledError now propagates through get_db (which catches it alongside Exception and rolls back), releasing the FOR UPDATE lock, and a retryable 504 gateway_timeout envelope is returned. Pure ASGI (not BaseHTTPMiddleware) so cancellation reaches the route coroutine + get_db dependency directly, with no spawned-task gap. Registered innermost so correlation + logging still wrap the 504. E2E: two fault-injection scenarios in tests/e2e_smoke/test_flow_verb_timeout.py. A hang is injected inside the verb's own transaction (claim acquires the FOR UPDATE lock, then set_plan sleeps past the timeout; only the first set_plan call runs — a retry short-circuits as idempotent re-entry). - ARMED (server timeout 1s): verb-1 returns a bounded 504 gateway_timeout, verb-2 re-acquires the row and reaches the post-claim gate (tracing_gap) — proving the lock was released by verb-1's cancellation. - DISARMED (server timeout 1000s, MCP client timeout 3s): verb-1 holds the lock past the client's HTTP timeout — the empirical reproduction of the wedge on the same branch, by turning the fix off. Full e2e suite green (32 passed). * feat(sandbox): pluggable per-engine registry (postgres/redis/mongo) (#324) (#325) * feat(sandbox): pluggable per-engine registry (postgres/redis/mongo) Replaces the hardcoded postgres+redis branches in the provisioner and the env emitter with a registry of SandboxEngine specs (image, run args, readiness probe, connection, ROBOCO_TEST_* env) in a pure low module (roboco/models/sandbox.py). VALID_SANDBOX_SERVICES is derived from the registry — single source of truth — and the provisioner + orchestrator iterate it, so adding an engine is one class + one registry line, not another branch. Adds a mongo:8-alpine engine (ROBOCO_TEST_MONGO_*) as the third service alongside postgres/redis. Also fixes the cold-pull loop that stranded v0.19.0 board agents with empty error strings: docker run pulled inline under a 20s deadline, so a NAS cold pull was killed, cancelled, and re-pulled from scratch forever. _ensure_image now inspects + pulls (300s) before run; provisioning errors log type+message so a bare TimeoutError no longer shows as "". Panel edit-project dialog: postgres/redis toggles -> a Set<string> multi-select driven by a SANDBOX_SERVICES catalog, so new engines appear in the UI by adding to the catalog. Tests: engine parity (allowlist==registry, unique slugs/images, no None leak in env, SandboxInfo aggregates every engine), mongo provision + env injection, plus the existing postgres/redis provision/env/spawn/janitor suite updated to the registry shape. 821 unit / 5 skip green; ruff + mypy (360 files) clean. * docs(sandbox): reflect pluggable engine registry + mongo across docs CHANGELOG (0.19.0): Added entry for the pluggable sandbox engine registry (postgres/redis/mongo) + Fixed entry for the cold-pull loop/empty-error strand that boarded v0.19.0 board agents. docs/map (9 files): sandbox subsystem blurbs, SandboxProvisioner rows, _maybe_provision_sandbox/_append_sandbox_env rows, feature-flag rows, the migration-057 row + v0.17.0 delta, and the models.md VALID_SANDBOX_SERVICES note — all retitled to DB/Redis/Mongo via the engine registry (roboco/models/sandbox.py), with the one-class-one-line extension story and the _ensure_image cold-pull fix. Production-network (roboco_data) lines left as postgres+redis — mongo is sandbox-only, not a prod service. docs/rag (3 files): sandbox-db.md rewritten around the registry (engine list, generic _provision_engine, image pre-pull, ROBOCO_TEST_DB_*/REDIS_*/MONGO_* incl. MONGO_AUTH_DB=admin, single emit_env); config-reference sandbox flag row + subsection retitled; db-network-isolation framing broadened to postgres/redis/mongo. preconditions-and-rejections left untouched (its hit was an unrelated gateway see-also link). * test(e2e): harden umbrella close terminal reads with bounded wait-for-state The MegaTask umbrella close test flaked once on CI (ceo-approve returned 200 but the re-fetch saw awaiting_pm_review) then passed on re-run. The production path is deterministic: complete -> main_pm_complete -> submit_pm_review -> escalate_to_ceo -> ceo_approve -> commit, all on one session, all awaited; the fire-and-forget completion hooks are isolated (own session, best-effort, never touch task.status or the request session). 20 local runs could not reproduce it. The one real surface is the read pattern: the e2e stack commits on the uvicorn thread's loop and reads via a separate loop (run_db -> asyncio.run with a fresh engine), so a terminal single point-read can race a still-draining completion hook on a contended runner. Replace the two terminal point-reads with a bounded wait_for_status poll. Strictly better than a one-shot read: absorbs the transient, and a genuine state bug still surfaces via the timeout branch asserting against the last-read state. --------- Co-authored-by: Renn F <rennf93@users.noreply.github.com> --------- Co-authored-by: Renn F <rennf93@users.noreply.github.com> |
||
|
|
8f6dde9a50 |
feat(sandbox): pluggable per-engine registry (postgres/redis/mongo) (#324)
* feat(sandbox): pluggable per-engine registry (postgres/redis/mongo) Replaces the hardcoded postgres+redis branches in the provisioner and the env emitter with a registry of SandboxEngine specs (image, run args, readiness probe, connection, ROBOCO_TEST_* env) in a pure low module (roboco/models/sandbox.py). VALID_SANDBOX_SERVICES is derived from the registry — single source of truth — and the provisioner + orchestrator iterate it, so adding an engine is one class + one registry line, not another branch. Adds a mongo:8-alpine engine (ROBOCO_TEST_MONGO_*) as the third service alongside postgres/redis. Also fixes the cold-pull loop that stranded v0.19.0 board agents with empty error strings: docker run pulled inline under a 20s deadline, so a NAS cold pull was killed, cancelled, and re-pulled from scratch forever. _ensure_image now inspects + pulls (300s) before run; provisioning errors log type+message so a bare TimeoutError no longer shows as "". Panel edit-project dialog: postgres/redis toggles -> a Set<string> multi-select driven by a SANDBOX_SERVICES catalog, so new engines appear in the UI by adding to the catalog. Tests: engine parity (allowlist==registry, unique slugs/images, no None leak in env, SandboxInfo aggregates every engine), mongo provision + env injection, plus the existing postgres/redis provision/env/spawn/janitor suite updated to the registry shape. 821 unit / 5 skip green; ruff + mypy (360 files) clean. * docs(sandbox): reflect pluggable engine registry + mongo across docs CHANGELOG (0.19.0): Added entry for the pluggable sandbox engine registry (postgres/redis/mongo) + Fixed entry for the cold-pull loop/empty-error strand that boarded v0.19.0 board agents. docs/map (9 files): sandbox subsystem blurbs, SandboxProvisioner rows, _maybe_provision_sandbox/_append_sandbox_env rows, feature-flag rows, the migration-057 row + v0.17.0 delta, and the models.md VALID_SANDBOX_SERVICES note — all retitled to DB/Redis/Mongo via the engine registry (roboco/models/sandbox.py), with the one-class-one-line extension story and the _ensure_image cold-pull fix. Production-network (roboco_data) lines left as postgres+redis — mongo is sandbox-only, not a prod service. docs/rag (3 files): sandbox-db.md rewritten around the registry (engine list, generic _provision_engine, image pre-pull, ROBOCO_TEST_DB_*/REDIS_*/MONGO_* incl. MONGO_AUTH_DB=admin, single emit_env); config-reference sandbox flag row + subsection retitled; db-network-isolation framing broadened to postgres/redis/mongo. preconditions-and-rejections left untouched (its hit was an unrelated gateway see-also link). * test(e2e): harden umbrella close terminal reads with bounded wait-for-state The MegaTask umbrella close test flaked once on CI (ceo-approve returned 200 but the re-fetch saw awaiting_pm_review) then passed on re-run. The production path is deterministic: complete -> main_pm_complete -> submit_pm_review -> escalate_to_ceo -> ceo_approve -> commit, all on one session, all awaited; the fire-and-forget completion hooks are isolated (own session, best-effort, never touch task.status or the request session). 20 local runs could not reproduce it. The one real surface is the read pattern: the e2e stack commits on the uvicorn thread's loop and reads via a separate loop (run_db -> asyncio.run with a fresh engine), so a terminal single point-read can race a still-draining completion hook on a contended runner. Replace the two terminal point-reads with a bounded wait_for_status poll. Strictly better than a one-shot read: absorbs the transient, and a genuine state bug still surfaces via the timeout branch asserting against the last-read state. --------- Co-authored-by: Renn F <rennf93@users.noreply.github.com> |
||
|
|
49bff15c78 |
fix(sandbox): pre-pull images before run + capture timeout type in spawn-refusal log
`docker run postgres:16-alpine` pulled inline under a 20s deadline; on a cold
NAS the pull exceeded it, the run was killed, the pull cancelled, and every
retry re-pulled from scratch — a persistent spawn-refusal loop that
deadlocked board reviews on opted-in projects. `postgres:16-alpine` is
referenced nowhere in compose (the main service uses pgvector/pgvector:pg16),
so it was always a cold pull.
- sandbox: `_ensure_image` pulls with a 300s deadline when `image inspect`
reports absent, before `docker run` (postgres + redis). Pull failure raises
before any run, so it self-diagnoses instead of looping.
- orchestrator: log + raise `f"{type(e).__name__}: {e}"` — `str(TimeoutError())`
is `""`, which made the failure mode invisible in the logs.
- tests: fake runner covers image/pull verbs; 3 new tests for skip/pull/fail.
|
||
|
|
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 (
|
||
|
|
cebbd73e07 |
Ponytail build-laziness doctrine (bundled with Fable, 0.19.0) (#313)
* feat(agents): vendor trimmed ponytail doctrine (full + ethos) * feat(agents): compose ponytail doctrine layer, bundled with fable * docs: document ponytail doctrine bundled with fable-mode * style: add trailing newline to ponytail doctrine files test * docs: changelog + map/rag for ponytail doctrine (0.19.0) user-facing docs skipped: Fable precedent absent from README/deployment/usage; ponytail is default-off internal. --------- Co-authored-by: Renn F <rennf93@users.noreply.github.com> |
||
|
|
4923ee3ff3 |
MinIO video storage (chunk 1: config+deps+compose) + event-loop perf fix (#308)
* feat(video): Phase A — VideoEngine origination spine + held-source gates
New default-off engine skeleton: opens a UX/UI authoring task (source=video, assigned to a ux-dev, LOW complexity to clear the dev-needs-subtasks guard) and materializes a held CEO-approval draft (source=video_post). Excludes video_post from all three held-source skip sites; adds the video_draft marker, six config flags, and the feature-flag entries. Origination + gate behavior unit-tested.
* refactor(orchestrator): fold _dispatch_dev_work skip chain into a helper
The per-source if/continue chain grew past xenon's --max-absolute B when the video_post held source joined it. Extract _is_non_dev_dispatch_source (every held-CEO source plus the two Board exploration sources) so the dev loop's skip is one flat call. Behavior-identical.
* feat(video): Phase B — propose_video do-tool (metadata-only, team-gated)
UX/UI dev records a video's composition ref + per-platform captions onto the authoring task's video_draft marker. Team-gated at runtime via _caller_team (Role.DEVELOPER can't tell a ux-dev from a be-dev). Resolves the caller's ACTIVE task via get_active_task_for_agent, not an oldest-first scan that would clobber a second open video task. Metadata only, no render. Wired through do_server + route + schema; added to _DEV_DO.
* feat(video): Phase D — render loop + RemotionRenderer client
Orchestrator-async _video_render_loop renders a completed authoring task's merged composition to MP4 (vertical + square) via the remotion-renderer sidecar and materializes the held video_post draft. RemotionRenderer tars the read-clone's motion/ source, POSTs it, and saves the returned MP4 bytes to a TASK-scoped local path (no shared volume; a composition is reused across videos so a composition-scoped path would clobber an earlier draft). Render failures bounded-retry (read-clone catch-up window, transient sidecar) up to a cap, then terminal-fail. Client tested vs a mock transport; loop vs a mock renderer + real DB.
* feat(video): Phase C — release / spotlight / on-demand video triggers
Three entry points open a UX/UI video-authoring task via VideoEngine.open_video_task: (1) a published release drafts a companion video — best-effort in ReleaseProposalService.approve, never fails the publish; script from the CHANGELOG via the local model with a template fallback. (2) propose_feature_spotlight gains optional wants_video/video_script — best-effort, gated on video_on_spotlight, default-off leaves the spotlight flow byte-for-byte unchanged. (3) POST /video/request (CEO-only) for an on-demand brief, with clean disabled/not_opened responses. All gated on video_engine_enabled.
* fix(video): savepoint-isolate video-task inserts (F042 poisoned session)
The best-effort try/except around open_video_task (release-publish + spotlight hooks) swallowed the Python exception, but a DBAPI error at the insert flush left the shared session must-rollback — so the caller's next commit (release finalize / request boundary) threw PendingRollbackError: the release stuck 'pending' after actually publishing, or the spotlight draft + HTTP response were lost. Wrap both inserts (open_video_task, _originate_video_post) in a begin_nested savepoint (the repo's established F042 pattern) so a DB error rolls back only the insert. open_video_task returns None (every caller already handles it); _originate_video_post propagates to the render loop's handler. Regression test: an insert FK error returns None with the session left usable. Dormant while the flags were off; armed on the NAS.
* feat(video): Phase G — motion/ package + remotion-renderer sidecar + compose
In-repo Remotion v4 motion/ package (ReleaseAnnouncement composition; calculateMetadata returns 1080x1920 vertical / 1080x1080 square from inputProps.orientation) + a credential-free remotion-renderer sidecar: untar the POSTed motion/ source, bundle (LRU-cached per source sha), selectComposition + renderMedia h264, stream the MP4 bytes back — matching the RemotionRenderer client contract. docker/remotion.Dockerfile on Debian (Chrome apt deps, build-time Chrome pre-warm, ffmpeg bundled in @remotion/renderer). Wired into both compose files (roboco_default only, shm_size 1gb, /health check) + the release publish matrix. Verified via a real local render of both cuts; the Debian docker build is the CEO's to run.
* chore(video): D-hardening — video_post source_task_id + render-loop docstring
Add a source_task_id back-reference to the video_post held-draft marker (traceability from a draft to its authoring task; also makes the render loop's two-key idempotency check wireable later). Fix the render-loop test's stale docstring ('never retried' -> bounded-retry). Both from the Phase D critic's non-blocking follow-ups.
* feat(video): Phase E1 — VideoPostService + heartbeat mutex (approve->post)
CEO-approve->post service: heartbeat-renewed Redis mutex (fail-closed, grace=ttl-2*heartbeat), re-read-in-lock double-post guard, per-platform durable commits (asyncio.shield-ed, settle-before-rollback on lock-loss), all writes inside the lock (captions validated pre-lock, applied in-lock — no stale whole-column clobber), idempotent, per-platform retry-skip. Poster interfaces (X/TikTok, mocked here). Reject + list-held-drafts. Survived 3 adversarial rounds; residual = a crash in the poster->commit window (CEO-gated low-freq, documented).
* fix(video): G-hardening — renderer leaks + Share Tech Mono brand font
Sidecar: give bundle() an explicit outDir tracked + deleted on LRU eviction (was leaking ~19MB remotion-webpack-bundle-* per source); res.on('close') cleanup so an aborted/retried download no longer leaks its remotion-out-* MP4 dir. Fonts: vendor Share Tech Mono (roboco-website brand font) as the display face (self-hosted woff2, 400-weight, headline fontWeight 700->400 to avoid faux-bold) + self-hosted Inter body — no gstatic fetch at render time (lsof-verified). Extras: composition_id whitelist (400) + Multer error middleware (400/413).
* feat(video): Phase E2 — X v2 + TikTok posters, tiktok_credentials, routes
LiveXVideoPoster (X v2 chunked media upload: init/append/finalize/STATUS-poll -> tweet w/ media_ids, OAuth1 signer reused). LiveTikTokPoster (OAuth2 inbox: init -> chunked PUT with asymmetric final chunk -> status-fetch; 401 -> refresh_token grant, rotated token persisted). tiktok_credentials Fernet singleton + migration 062 (single head). Routes: CEO approve/reject + list held drafts + write-only tiktok creds, wiring real posters into VideoPostService. Residual: a lock-loss right after a token-refresh flush can discard the rotated token (same rare CEO-gated class as the documented post->commit window).
* feat(video): Phase F — panel video-post queue + TikTok creds card + flags
video-post-queue.tsx: <video> MP4 preview with 9:16/1:1 cut switch, per-platform editable captions (280/2200 counters, over-limit disables approve), approve/reject, Request-a-video dialog. tiktok-credentials-card.tsx (4 write-only OAuth2 fields). feature-flags-card inlines TikTokCredentialsForm under video_engine_enabled. Mounted in command-center. tsc/eslint clean, 273 panel tests green. NOTE: needs the GET /video/posts/{id}/media route + mp4_paths on VideoPostResponse (folded into H) for the preview source.
* feat(video): Phase H — media route + e2e smoke + NAS arming + docs
GET /video/posts/{id}/media?cut= (CEO-gated FileResponse of the rendered MP4; closes the panel preview gap) + mp4_paths on VideoPostResponse. e2e smoke tests/e2e_smoke/test_video_pipeline.py (full flow, sidecar+X/TikTok mocked; asserts dispatcher skips, render-loop materialize, propose_video team-gate, approve idempotency). NAS arming: docker-compose.yml/.yaml ROBOCO_VIDEO_ENGINE_ENABLED/ON_RELEASE/ON_SPOTLIGHT default-on (.yaml resynced to .yml); registry stays off. CLAUDE.md video-engine section + CHANGELOG. Fixed 2 pre-existing route-test pollution leaks. Full suite 11763 passed.
* fix(video): auth-carrying preview, media route confinement, VideoPost type drift
Three fixes along the video preview path:
1. panel video preview auth: the <video> element was pointed straight at
GET /video/posts/{id}/media, but a native <video src> GET carries none
of axios's X-Agent-ID/X-Agent-Role headers — so in the default
header-trust deployment the request 401s. Fetch the cut via
videoApi.getMediaBlob (axios, responseType: blob) and drive <video>
off a URL.createObjectURL result instead. The object URL is revoked
on cut-change (the previous cut's URL) and on unmount, so neither
cut switches nor row teardown leak blob URLs.
2. backend media route confinement: GET /video/posts/{id}/media now
resolves mp4_path and refuses it with 404 when it falls outside
settings.video_output_dir. Defense-in-depth against any future
writer of mp4_paths serving files from arbitrary disk locations.
3. panel VideoPost type/comment drift: added mp4_paths to the
VideoPost interface (the committed VideoPostResponse already
carries it), and corrected the stale comment on videoMediaUrl
that claimed no route served the rendered bytes — the route has
existed since the media endpoint landed; the comment now describes
why getMediaBlob exists instead of a direct <video src>.
* Persist rendered videos to data in physical storage.
* ++
* docs(video): 0.18.0 CHANGELOG entry + RAG + map reference for video engine
- Move the video engine bullet from [Unreleased] into [0.18.0] and note
the ROBOCO_VIDEO_OUTPUT_DIR bind-mount persistence.
- Add docs/rag/architecture/video-engine.md (mirrors x-engine.md shape:
enable/disable, three triggers, render loop + sidecar, CEO gate, media
route confinement, credentials).
- Reference the video render loop in docs/map/orchestrator.md's engine list.
* chore(video): re-bump to 0.19.0 + sync registry compose defaults
Version was wrongly bumped to 0.18.0; 0.18.0 is an already-released
section. Restore its 2026-07-04 date and move the video-engine CHANGELOG
bullet into a new [0.19.0] - 2026-07-05 section above it. Bump
pyproject.toml, roboco/__init__.py, roboco/config.py (app_version),
panel/package.json, and the motion/README inputProps example to 0.19.0.
docker-compose.registry.yml: add ROBOCO_VIDEO_ENGINE_ENABLED /
_VIDEO_ON_RELEASE / _VIDEO_ON_SPOTLIGHT defaulted false (NAS arms them
true), and comment out the video-renders bind mount with a short note
so the public registry image ships video off by default. Structural
sync with docker-compose.yml maintained.
* fix(video): rate-limit /render + reflow motion/README
CodeQL flagged js/missing-rate-limiting on the renderer /render route.
The sidecar is container-network-only with one trusted caller (the
orchestrator, which renders cuts serially), so this limiter is a
retry-storm ceiling (30/min, well above legit render rate), not the
primary control. Also reflows motion/README.md hard-wrapped prose that
failed the markdown quality gate.
* fix(build): finish pnpm 11 migration + regen verb tables
The panel Docker image build failed on `pnpm install --frozen-lockfile`:
node:22-alpine's corepack resolved to its bundled pnpm 11, but
panel/package.json pinned packageManager to pnpm@10.25.0, and pnpm 11
refuses to run against that pin. The Dockerfiles were already written for
pnpm 11 (comments, CI=true, strictDepBuilds); the package.json pin was the
stale outlier. Finish the migration instead of working around it:
- panel/package.json: packageManager pnpm@10.25.0 -> pnpm@11.10.0; drop the
`pnpm` field (pnpm 11 ignores it — build approval lives in
panel/pnpm-workspace.yaml's allowBuilds). Lockfile unchanged (pnpm 11
accepts it as-is); frozen-lockfile verified.
- remotion-renderer/package.json: pin packageManager pnpm@11.10.0 for
determinism (was relying on corepack's implicit default); engines.node
>=22.13 (pnpm 11 requirement).
- docker/panel.Dockerfile + docker/remotion.Dockerfile: `corepack prepare
pnpm@11.10.0 --activate` so the build uses the pinned version explicitly
instead of trusting corepack's bundled default (which a future
node:22-alpine could change).
- .github/workflows/panel-ci.yml: Node 20 -> 22 (pnpm 11 requires
Node >=22.13; Node 20 fails the engines check).
Also regenerate agents/prompts/_generated/{developer,head_marketing,verbs}.md
— the video engine added propose_video and extended propose_feature_spotlight
(wants_video, video_script) but the verb tables weren't refreshed, failing
the foundation-check quality gate.
* chore(build): approve esbuild build script in remotion pnpm-workspace.yaml
pnpm 11 generated this file with a placeholder ('set this to true or false')
during install; resolve it to true so local dev of the renderer doesn't
re-prompt. esbuild's postinstall only verifies the prebuilt platform binary
(@esbuild/<platform> is installed as an optional dep), so approving it is
safe and silences the ERR_PNPM_IGNORED_BUILDS warning.
* fix(build): copy pnpm-workspace.yaml into panel + remotion images
pnpm 11 hard-errors with [ERR_PNPM_IGNORED_BUILDS] (exit 1) when a
dependency ships a postinstall script that isn't approved in
allowBuilds. Both Dockerfiles copied only package.json + pnpm-lock.yaml,
so the build-approval map in pnpm-workspace.yaml never made it into the
image — the remotion image build died on esbuild@0.28.1's postinstall.
Copy pnpm-workspace.yaml alongside the manifests in both images. In
panel, this also drops the --config.strictDepBuilds=false workaround:
with sharp and unrs-resolver now approved, their postinstalls run and
install the platform-specific binaries (previously skipped, leaving
sharp without its @img/sharp-* binary at runtime).
Verified locally: remotion + panel `pnpm install --frozen-lockfile`
exit 0 with the workspace file present; both exit 1 without it.
* fix(perf): offload conventions + release-readiness blocking I/O off the event loop
The orchestrator runs uvicorn and the orchestration background loops on a
single shared event loop, so any sync I/O anywhere — even inside a background
loop — blocks API responsiveness for its duration. Two call sites were missing
asyncio.to_thread wrappers:
- ConventionsService.get_map/health/restore called the sync _resolve
(`git rev-parse`), _read_committed_standard (file read + yaml parse), and
_derive (filesystem walk via derive_from_scan) inline. Reachable from
GET /api/projects/{id}/conventions and from the agent spawn-prepare path.
- ReleaseManagerEngine._production_assess called gather_snapshot inline —
multiple `subprocess.run` git calls + a filesystem walk, running inside the
release-manager background loop.
Wrap each blocking call in asyncio.to_thread at the async boundary. No
signature changes; helpers stay sync. Verified: targeted tests pass
(196 passed, 36 DB-skipped), ruff + format clean.
These were the only responsiveness gaps surfaced by the concurrency audit —
the rest of the heavy paths (agent spawn via `docker run -d`, video render
loop, git ops via the 16-worker ThreadPoolExecutor, workspace subprocess
calls) already offload correctly. No API/worker container split needed.
* feat(storage): add MinIO config + dep + compose (no-op, default-off)
Chunk 1 of the MinIO video-storage plan (§1, §2, §6). No behavior change:
minio_endpoint defaults to empty = disabled, the existing FileResponse serve
path is untouched (chunk 4 wires the serve path; chunk 2 adds the client).
- pyproject.toml: add `minio` (minio-py) to dependencies; regenerate uv.lock
(resolves minio v7.2.20 + pycryptodome transitive).
- roboco/config.py: add 5 settings fields after video_output_dir
(minio_endpoint/_access_key/_secret_key/_bucket/_region). Plain str Fields
matching the existing ROBOCO_ENCRYPTION_KEY style; no SecretStr, no
presign_ttl_seconds (YAGNI — we don't presign in phase 1).
- docker-compose.yml: add `minio` service (data network only, named
minio-data volume, host ports 19000/19001 for debugging, mc healthcheck)
and a one-shot `minio-init` service mirroring the ollama-init pattern
(mc alias set + mb -p, idempotent via || true). Add ROBOCO_MINIO_* env to
the orchestrator env block (endpoint, access/secret key, bucket, region).
- docker-compose.registry.yml: intentionally omit the minio/minio-init
services and leave ROBOCO_MINIO_* unset (NAS default-on, registry
default-off — the established pattern); comment added to the orchestrator
env block noting the omission.
* docs(storage): 0.19.0 CHANGELOG + RAG + map reference for MinIO chunk 1
Backfills the release-polish docs for MinIO chunk 1 (§10 of the plan):
- docker-compose.yaml synced to docker-compose.yml (the two NAS compose files
must stay byte-identical; .yml was edited in chunk 1, .yaml was stale).
- CHANGELOG [0.19.0]: Added (MinIO scaffolding) + Fixed (event-loop I/O offload).
- docs/rag/architecture/minio-storage.md: RAG doc mirroring video-engine.md.
- docs/map/deployment-tooling.md: one-line storage reference.
* MinIO chunk 2: minio_client module (singleton + unconfigured guard) (#309)
* feat(storage): minio_client module (singleton + unconfigured guard)
Chunk 2 of the MinIO plan (§3). roboco/services/minio_client.py adds:
- get_client(): singleton minio-py Minio from settings; returns None when
minio_endpoint is empty (the disabled path used by the chunk 3/4 guards).
Parses http://... endpoint into host:port + secure flag.
- put_object(bytes, key): no-ops when unconfigured; otherwise PUTs to
settings.minio_bucket with ContentType video/mp4.
- get_object_stream(key): yields object bytes for StreamingResponse; lets
S3Error propagate so the serve route (chunk 4) can fall back to disk.
Sync calls — every call site wraps in asyncio.to_thread (chunks 3/4). One
unit test covers the unconfigured guard + endpoint scheme parsing (mocks,
no real MinIO). Not yet wired into remotion_client._save or the media route.
* MinIO chunk 3: wire write path (remotion_client._save PUT) (#310)
* feat(storage): wire MinIO write path in remotion_client._save
Chunk 3 of the MinIO plan (§3). After the local mp4 write, _save PUTs the bytes
to MinIO under key = Path(mp4_path).name (already {render_key}-{orientation}.mp4),
guarded by minio_client.get_client() (None when minio_endpoint empty) and
wrapped in asyncio.to_thread. Local disk stays the source of truth for the
poster publish path (x_video_client/tiktok_client read mp4_path from disk);
the PUT is additive. _save still returns the local path str — mp4_paths,
marker, and schema unchanged. Disabled (local-only) when MinIO unconfigured.
One test: asserts put_object is called with the basename key when configured
and the local file is still written; existing test stays green via the
unconfigured-default path. Mocks only.
* fix(storage): make MinIO PUT non-fatal in remotion_client._save
A configured-but-down MinIO made put_object raise inside the worker thread,
failing the render and retry-looping a task whose local file was already
written. Local disk is the source of truth and the serve route falls back to
FileResponse on S3Error, so a failed durable-copy PUT must never fail the
render — log and continue; the next render re-attempts the PUT.
Adds test_save_swallows_minio_put_failure (PUT raises -> _save still returns
the local path and the local file is written). Extends the CHANGELOG write-
path bullet with the non-fatal guarantee.
* MinIO chunk 4: serve path (StreamingResponse + FileResponse fallback) (#311)
* feat(storage): serve MinIO via the media route (StreamingResponse + FileResponse fallback)
Chunk 4 of the MinIO plan (§4 — the crux). GET /api/video/posts/{id}/media
derives key = Path(mp4_path).name and, when minio_endpoint is set, returns a
StreamingResponse over minio_client.get_object_stream(key), keeping
_require_ceo so auth stays end-to-end (no presigned URLs). Falls back to
FileResponse on S3Error (old render not in MinIO) or when MinIO is
unconfigured — the panel's axios-blob flow is unchanged (same URL, headers,
body, just chunked). The confinement check is kept as defense-in-depth (the
key is a basename so traversal is impossible, but the check is cheap and
protects the poster path).
Two integration tests: configured serve path streams from a stubbed
get_object_stream (CEO 200, non-CEO 403); unconfigured fallback serves the
local file via FileResponse. Mocks only — no real MinIO.
* fix(storage): eager stat_object probe so the MinIO serve fallback actually fires
The chunk-4 route wrapped StreamingResponse(get_object_stream(key), ...) in a
try/except, but get_object_stream is a lazy generator — its client.get_object
call runs on the first next(), i.e. AFTER the route returned and Starlette
started streaming. An S3Error (NoSuchKey / MinIO down) there is uncatchable;
the try/except caught nothing and the FileResponse fallback never triggered.
Add minio_client.stat_object(key): an eager existence/readiness probe that
runs INSIDE the route's try/except, so a missing object or down MinIO raises
before the StreamingResponse starts and the fallback serves the local file.
stat-then-get is two round trips; a mid-stream failure after a successful stat
is a rare race the CEO can retry (documented ceiling).
Tests: the configured test now stubs stat_object; a new test asserts the
S3Error fallback serves the local file via FileResponse and that
get_object_stream is never called. RAG doc updated to record the eager-probe
correctness detail + the non-fatal PUT.
* docs(rag): mark MinIO deployment note landed (chunk 5) (#312)
Co-authored-by: Renn F <rennf93@users.noreply.github.com>
---------
Co-authored-by: Renn F <rennf93@users.noreply.github.com>
---------
Co-authored-by: Renn F <rennf93@users.noreply.github.com>
---------
Co-authored-by: Renn F <rennf93@users.noreply.github.com>
* fix(video): offload minio stat_object off the event loop
stat_object was called inline in the async media route, blocking the
shared event loop for one sync urllib3 round-trip per preview request —
contradicting minio_client's own 'every call site wraps in to_thread'
docstring and this PR's perf-fix theme. Wrap in asyncio.to_thread; the
try/except still catches S3Error (to_thread re-raises) so the
FileResponse fallback is unchanged. Also add the trailing newline to
the minio-storage RAG doc.
* Fix red CI
* Make CI green
---------
Co-authored-by: Renn F <rennf93@users.noreply.github.com>
|
||
|
|
e9d0e0bd48 |
feat(video): 0.19.0 video engine (Remotion) + preview auth + render persistence (#307)
* feat(video): Phase A — VideoEngine origination spine + held-source gates
New default-off engine skeleton: opens a UX/UI authoring task (source=video, assigned to a ux-dev, LOW complexity to clear the dev-needs-subtasks guard) and materializes a held CEO-approval draft (source=video_post). Excludes video_post from all three held-source skip sites; adds the video_draft marker, six config flags, and the feature-flag entries. Origination + gate behavior unit-tested.
* refactor(orchestrator): fold _dispatch_dev_work skip chain into a helper
The per-source if/continue chain grew past xenon's --max-absolute B when the video_post held source joined it. Extract _is_non_dev_dispatch_source (every held-CEO source plus the two Board exploration sources) so the dev loop's skip is one flat call. Behavior-identical.
* feat(video): Phase B — propose_video do-tool (metadata-only, team-gated)
UX/UI dev records a video's composition ref + per-platform captions onto the authoring task's video_draft marker. Team-gated at runtime via _caller_team (Role.DEVELOPER can't tell a ux-dev from a be-dev). Resolves the caller's ACTIVE task via get_active_task_for_agent, not an oldest-first scan that would clobber a second open video task. Metadata only, no render. Wired through do_server + route + schema; added to _DEV_DO.
* feat(video): Phase D — render loop + RemotionRenderer client
Orchestrator-async _video_render_loop renders a completed authoring task's merged composition to MP4 (vertical + square) via the remotion-renderer sidecar and materializes the held video_post draft. RemotionRenderer tars the read-clone's motion/ source, POSTs it, and saves the returned MP4 bytes to a TASK-scoped local path (no shared volume; a composition is reused across videos so a composition-scoped path would clobber an earlier draft). Render failures bounded-retry (read-clone catch-up window, transient sidecar) up to a cap, then terminal-fail. Client tested vs a mock transport; loop vs a mock renderer + real DB.
* feat(video): Phase C — release / spotlight / on-demand video triggers
Three entry points open a UX/UI video-authoring task via VideoEngine.open_video_task: (1) a published release drafts a companion video — best-effort in ReleaseProposalService.approve, never fails the publish; script from the CHANGELOG via the local model with a template fallback. (2) propose_feature_spotlight gains optional wants_video/video_script — best-effort, gated on video_on_spotlight, default-off leaves the spotlight flow byte-for-byte unchanged. (3) POST /video/request (CEO-only) for an on-demand brief, with clean disabled/not_opened responses. All gated on video_engine_enabled.
* fix(video): savepoint-isolate video-task inserts (F042 poisoned session)
The best-effort try/except around open_video_task (release-publish + spotlight hooks) swallowed the Python exception, but a DBAPI error at the insert flush left the shared session must-rollback — so the caller's next commit (release finalize / request boundary) threw PendingRollbackError: the release stuck 'pending' after actually publishing, or the spotlight draft + HTTP response were lost. Wrap both inserts (open_video_task, _originate_video_post) in a begin_nested savepoint (the repo's established F042 pattern) so a DB error rolls back only the insert. open_video_task returns None (every caller already handles it); _originate_video_post propagates to the render loop's handler. Regression test: an insert FK error returns None with the session left usable. Dormant while the flags were off; armed on the NAS.
* feat(video): Phase G — motion/ package + remotion-renderer sidecar + compose
In-repo Remotion v4 motion/ package (ReleaseAnnouncement composition; calculateMetadata returns 1080x1920 vertical / 1080x1080 square from inputProps.orientation) + a credential-free remotion-renderer sidecar: untar the POSTed motion/ source, bundle (LRU-cached per source sha), selectComposition + renderMedia h264, stream the MP4 bytes back — matching the RemotionRenderer client contract. docker/remotion.Dockerfile on Debian (Chrome apt deps, build-time Chrome pre-warm, ffmpeg bundled in @remotion/renderer). Wired into both compose files (roboco_default only, shm_size 1gb, /health check) + the release publish matrix. Verified via a real local render of both cuts; the Debian docker build is the CEO's to run.
* chore(video): D-hardening — video_post source_task_id + render-loop docstring
Add a source_task_id back-reference to the video_post held-draft marker (traceability from a draft to its authoring task; also makes the render loop's two-key idempotency check wireable later). Fix the render-loop test's stale docstring ('never retried' -> bounded-retry). Both from the Phase D critic's non-blocking follow-ups.
* feat(video): Phase E1 — VideoPostService + heartbeat mutex (approve->post)
CEO-approve->post service: heartbeat-renewed Redis mutex (fail-closed, grace=ttl-2*heartbeat), re-read-in-lock double-post guard, per-platform durable commits (asyncio.shield-ed, settle-before-rollback on lock-loss), all writes inside the lock (captions validated pre-lock, applied in-lock — no stale whole-column clobber), idempotent, per-platform retry-skip. Poster interfaces (X/TikTok, mocked here). Reject + list-held-drafts. Survived 3 adversarial rounds; residual = a crash in the poster->commit window (CEO-gated low-freq, documented).
* fix(video): G-hardening — renderer leaks + Share Tech Mono brand font
Sidecar: give bundle() an explicit outDir tracked + deleted on LRU eviction (was leaking ~19MB remotion-webpack-bundle-* per source); res.on('close') cleanup so an aborted/retried download no longer leaks its remotion-out-* MP4 dir. Fonts: vendor Share Tech Mono (roboco-website brand font) as the display face (self-hosted woff2, 400-weight, headline fontWeight 700->400 to avoid faux-bold) + self-hosted Inter body — no gstatic fetch at render time (lsof-verified). Extras: composition_id whitelist (400) + Multer error middleware (400/413).
* feat(video): Phase E2 — X v2 + TikTok posters, tiktok_credentials, routes
LiveXVideoPoster (X v2 chunked media upload: init/append/finalize/STATUS-poll -> tweet w/ media_ids, OAuth1 signer reused). LiveTikTokPoster (OAuth2 inbox: init -> chunked PUT with asymmetric final chunk -> status-fetch; 401 -> refresh_token grant, rotated token persisted). tiktok_credentials Fernet singleton + migration 062 (single head). Routes: CEO approve/reject + list held drafts + write-only tiktok creds, wiring real posters into VideoPostService. Residual: a lock-loss right after a token-refresh flush can discard the rotated token (same rare CEO-gated class as the documented post->commit window).
* feat(video): Phase F — panel video-post queue + TikTok creds card + flags
video-post-queue.tsx: <video> MP4 preview with 9:16/1:1 cut switch, per-platform editable captions (280/2200 counters, over-limit disables approve), approve/reject, Request-a-video dialog. tiktok-credentials-card.tsx (4 write-only OAuth2 fields). feature-flags-card inlines TikTokCredentialsForm under video_engine_enabled. Mounted in command-center. tsc/eslint clean, 273 panel tests green. NOTE: needs the GET /video/posts/{id}/media route + mp4_paths on VideoPostResponse (folded into H) for the preview source.
* feat(video): Phase H — media route + e2e smoke + NAS arming + docs
GET /video/posts/{id}/media?cut= (CEO-gated FileResponse of the rendered MP4; closes the panel preview gap) + mp4_paths on VideoPostResponse. e2e smoke tests/e2e_smoke/test_video_pipeline.py (full flow, sidecar+X/TikTok mocked; asserts dispatcher skips, render-loop materialize, propose_video team-gate, approve idempotency). NAS arming: docker-compose.yml/.yaml ROBOCO_VIDEO_ENGINE_ENABLED/ON_RELEASE/ON_SPOTLIGHT default-on (.yaml resynced to .yml); registry stays off. CLAUDE.md video-engine section + CHANGELOG. Fixed 2 pre-existing route-test pollution leaks. Full suite 11763 passed.
* fix(video): auth-carrying preview, media route confinement, VideoPost type drift
Three fixes along the video preview path:
1. panel video preview auth: the <video> element was pointed straight at
GET /video/posts/{id}/media, but a native <video src> GET carries none
of axios's X-Agent-ID/X-Agent-Role headers — so in the default
header-trust deployment the request 401s. Fetch the cut via
videoApi.getMediaBlob (axios, responseType: blob) and drive <video>
off a URL.createObjectURL result instead. The object URL is revoked
on cut-change (the previous cut's URL) and on unmount, so neither
cut switches nor row teardown leak blob URLs.
2. backend media route confinement: GET /video/posts/{id}/media now
resolves mp4_path and refuses it with 404 when it falls outside
settings.video_output_dir. Defense-in-depth against any future
writer of mp4_paths serving files from arbitrary disk locations.
3. panel VideoPost type/comment drift: added mp4_paths to the
VideoPost interface (the committed VideoPostResponse already
carries it), and corrected the stale comment on videoMediaUrl
that claimed no route served the rendered bytes — the route has
existed since the media endpoint landed; the comment now describes
why getMediaBlob exists instead of a direct <video src>.
* Persist rendered videos to data in physical storage.
* ++
* docs(video): 0.18.0 CHANGELOG entry + RAG + map reference for video engine
- Move the video engine bullet from [Unreleased] into [0.18.0] and note
the ROBOCO_VIDEO_OUTPUT_DIR bind-mount persistence.
- Add docs/rag/architecture/video-engine.md (mirrors x-engine.md shape:
enable/disable, three triggers, render loop + sidecar, CEO gate, media
route confinement, credentials).
- Reference the video render loop in docs/map/orchestrator.md's engine list.
* chore(video): re-bump to 0.19.0 + sync registry compose defaults
Version was wrongly bumped to 0.18.0; 0.18.0 is an already-released
section. Restore its 2026-07-04 date and move the video-engine CHANGELOG
bullet into a new [0.19.0] - 2026-07-05 section above it. Bump
pyproject.toml, roboco/__init__.py, roboco/config.py (app_version),
panel/package.json, and the motion/README inputProps example to 0.19.0.
docker-compose.registry.yml: add ROBOCO_VIDEO_ENGINE_ENABLED /
_VIDEO_ON_RELEASE / _VIDEO_ON_SPOTLIGHT defaulted false (NAS arms them
true), and comment out the video-renders bind mount with a short note
so the public registry image ships video off by default. Structural
sync with docker-compose.yml maintained.
* fix(video): rate-limit /render + reflow motion/README
CodeQL flagged js/missing-rate-limiting on the renderer /render route.
The sidecar is container-network-only with one trusted caller (the
orchestrator, which renders cuts serially), so this limiter is a
retry-storm ceiling (30/min, well above legit render rate), not the
primary control. Also reflows motion/README.md hard-wrapped prose that
failed the markdown quality gate.
* fix(build): finish pnpm 11 migration + regen verb tables
The panel Docker image build failed on `pnpm install --frozen-lockfile`:
node:22-alpine's corepack resolved to its bundled pnpm 11, but
panel/package.json pinned packageManager to pnpm@10.25.0, and pnpm 11
refuses to run against that pin. The Dockerfiles were already written for
pnpm 11 (comments, CI=true, strictDepBuilds); the package.json pin was the
stale outlier. Finish the migration instead of working around it:
- panel/package.json: packageManager pnpm@10.25.0 -> pnpm@11.10.0; drop the
`pnpm` field (pnpm 11 ignores it — build approval lives in
panel/pnpm-workspace.yaml's allowBuilds). Lockfile unchanged (pnpm 11
accepts it as-is); frozen-lockfile verified.
- remotion-renderer/package.json: pin packageManager pnpm@11.10.0 for
determinism (was relying on corepack's implicit default); engines.node
>=22.13 (pnpm 11 requirement).
- docker/panel.Dockerfile + docker/remotion.Dockerfile: `corepack prepare
pnpm@11.10.0 --activate` so the build uses the pinned version explicitly
instead of trusting corepack's bundled default (which a future
node:22-alpine could change).
- .github/workflows/panel-ci.yml: Node 20 -> 22 (pnpm 11 requires
Node >=22.13; Node 20 fails the engines check).
Also regenerate agents/prompts/_generated/{developer,head_marketing,verbs}.md
— the video engine added propose_video and extended propose_feature_spotlight
(wants_video, video_script) but the verb tables weren't refreshed, failing
the foundation-check quality gate.
* chore(build): approve esbuild build script in remotion pnpm-workspace.yaml
pnpm 11 generated this file with a placeholder ('set this to true or false')
during install; resolve it to true so local dev of the renderer doesn't
re-prompt. esbuild's postinstall only verifies the prebuilt platform binary
(@esbuild/<platform> is installed as an optional dep), so approving it is
safe and silences the ERR_PNPM_IGNORED_BUILDS warning.
* fix(build): copy pnpm-workspace.yaml into panel + remotion images
pnpm 11 hard-errors with [ERR_PNPM_IGNORED_BUILDS] (exit 1) when a
dependency ships a postinstall script that isn't approved in
allowBuilds. Both Dockerfiles copied only package.json + pnpm-lock.yaml,
so the build-approval map in pnpm-workspace.yaml never made it into the
image — the remotion image build died on esbuild@0.28.1's postinstall.
Copy pnpm-workspace.yaml alongside the manifests in both images. In
panel, this also drops the --config.strictDepBuilds=false workaround:
with sharp and unrs-resolver now approved, their postinstalls run and
install the platform-specific binaries (previously skipped, leaving
sharp without its @img/sharp-* binary at runtime).
Verified locally: remotion + panel `pnpm install --frozen-lockfile`
exit 0 with the workspace file present; both exit 1 without it.
---------
Co-authored-by: Renn F <rennf93@users.noreply.github.com>
|
||
|
|
4b62b6278f | Update CHANGELOG.md v0.18.0 |