diff --git a/deploy/compose/docker-compose.override.yml b/deploy/compose/docker-compose.override.yml index 420501c..9288bc6 100644 --- a/deploy/compose/docker-compose.override.yml +++ b/deploy/compose/docker-compose.override.yml @@ -133,6 +133,10 @@ services: # TypeSafe Jev — shadow skill triage (cm-api skill_triage.rs). Optional: # unset, the triage records nothing. Server-side only; never a mission's. TYPESAFE_API_KEY: ${TYPESAFE_API_KEY:-} + # Where this deployment is reachable from OUTSIDE. The podcast feed + # builds every episode URL from it, so unset means a feed whose items + # all point at localhost — valid XML that no subscriber can play. + CLAWMATES_PUBLIC_URL: ${CLAWMATES_PUBLIC_URL:-http://localhost:8080} # ElevenLabs GenFM, for rendering the Continuous Research episode. # # SERVER-SIDE ONLY, deliberately. It is NOT in diff --git a/docs/NEXT-SESSION.md b/docs/NEXT-SESSION.md index 518e441..c7e6caf 100644 --- a/docs/NEXT-SESSION.md +++ b/docs/NEXT-SESSION.md @@ -560,3 +560,63 @@ accidents and obvious cases, which is what its own header has always said. **Also worth keeping:** measure efficacy and utility as a pair. Every gate scenario already asserts the mission still delivered; that is the utility half, and it should stay mandatory for any future rule. + +## Addendum 7 — 2026-09-22, continuous research end to end + +`docs/TEMPLATE-MATURITY.md` graded `continuous_research` "runs live, +unguarded" — the recipe with the most moving parts and no standing check. +Working on it found four defects, only one of which the plan predicted. + +**The digest never reached the vault.** Paper notes auto-merge (`library.rs` +calls `auto_merge::try_merge`); the digest did not, because nothing on the +mission path ever called it. `ContinuousResearch/` on `main` ended at +**2026-08-18** while every run since produced `analysis.md`, `script.md` and +`episode.json` onto a branch nobody merged. `mission_delivery` now accrues +such a branch behind three limits — `AdditiveOnly` (re-reads the diff against +the remote base, refuses any M/D/R), the phase's own judge verdict, and a pure +`accrues_automatically()` naming exactly one recipe. **Proven:** the scenario's +`analysis.md is on main (6717 bytes)`, where the same path returned 404 before. + +**The script parsed to zero turns, forever.** The baseline run the plan put +first found this instead of the race it was written for: the worker was +healthy, found the script in the checkout, and `parse_script` returned nothing +— so it skipped, every two minutes, with no episode and no tombstone. The +skill asks for `HOST:`; the writer, producing markdown, wrote `**HOST:**`, and +`split_once(':')` yields `**HOST` which fails the all-uppercase test. The +parser now accepts emphasis around the label and leaves emphasis *inside* +speech alone. Had the baseline been skipped, the vault fallback would have +shipped as a "fix" for a race that was not happening. + +**The renderer raced a reaper.** It read the script from a checkout deleted 30 +minutes after completion; `record_unrenderable`'s own message pointed at the +vault as manual recovery. It now takes the vault — default branch, then the +delivery branch. **Proven live:** `checkout is gone; rendering from the vault +(clawmates/mission-01a0c9c4-cf8ba78d:…/script.md)`. + +**A tombstone was permanent.** `NOT EXISTS (podcast_episodes)` meant a day that +gave up could never be reconsidered: mission `01a0c9c4` was tombstoned at +16:17:39, four minutes before the build that could render it started at +16:21:58, and would have stayed silent on a script sitting on a branch. +Tombstones now expire after a backoff each attempt refreshes — recovers the +same day a cause is fixed, without the every-two-minutes churn. + +**Result — the first complete passes this pipeline has made.** Two episodes, +`419s`/`6,708,231 bytes` and `401s`/`6,422,347 bytes`, rendered by +`elevenlabs/eleven_flash_v2_5`, served at `HTTP 200 audio/mpeg`, in a feed that +now advertises `https://clawmates.work` — `CLAWMATES_PUBLIC_URL` was unset, so +every enclosure URL pointed at `localhost:8080`: valid XML no subscriber could +play. + +**The guard** (`verify-mission-delivery.sh continuous-research`): digest on +`main`, manifest triage fields, evidence spread above `SATURATED_BELOW` (live: +**0.89**), analysis naming every harvested paper, `episode.json` obeying the +recipe's own 10–70 char rule, and a rendered episode. Its first run failed the +episode assertion while the pipeline was fine — it checked the instant the +mission completed, asserting the worker is *fast* rather than that it works. It +now waits for a verdict and says how long it waited. + +**Process note.** One commit was pushed off a `cargo test … | grep …` whose +exit code came from grep, so two failures passed into a `&&` chain that +committed and pushed. Nothing bad shipped — the failures were `PoolTimedOut` +from Docker Desktop being down locally, and CI on a real Postgres was green — +but the gate did not gate. Use `set -o pipefail`, or run cargo bare.