Repository navigation
feat(media): personalised video as a rendition of an approved draft - #28
Merged
Merged
Conversation
Adds @outreachgraph/media: a personalised outreach clip built from a draft that has already passed the §14.2 grounding gates and been approved by a human. The design point is that a video is never an independent generation. Video is a medium where a reviewer cannot skim the claims the way they can in text, so nothing gets spoken that a person did not already read and approve: - The script is assembled deterministically from the approved draft's own sentences. No model runs here. It is then re-checked with the same findUnsupportedClaims gate as a second, independent pass, on both the hook and the whole body. - Only the hook may reference the prospect's activity, so the grounding requirement lands on a bounded, checkable span. - Human approval is mandatory and is read from the approvals table. There is no trusted_automation path: the capability is additionally gated by an explicit opt-in flag, and a missing flag means disabled — the inverse of the network flags, because this one costs money and puts a synthetic likeness in front of someone who did not ask for it. - Policy is re-evaluated at render time. The snapshot on the recommendation is never trusted. - A 45s ceiling, estimated at a deliberately slow speaking rate so the cap bites early. Renderers sit behind an interface, so vendor objects do not leak: FixtureVideoRenderer keeps the pipeline runnable with no API keys, and GenMediaVideoRenderer adapts a generative video library structurally (satisfied by @profullstack/transcoder's genmedia layer) without taking a dependency on it. One video per draft, enforced by a unique index — rendering is billable and a retried worker run must not produce a second clip. A row is written before the render starts so a crash leaves a trace rather than nothing. 36 new tests. Full suite: 537 pass, 0 fail. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Personalised video is the highest-lift-per-send format in cold outreach, and OutreachGraph already holds the thing that makes a clip worth watching: a specific, evidence-backed reason to be in touch. This adds the format without loosening any of the non-negotiables.
The design point
A video is never an independent generation. It is a rendition of a draft that already passed the §14.2 grounding gates and was approved by a human.
That framing is what makes it safe. Video is a medium where a reviewer cannot skim the claims the way they can in text, so the rule here is that nothing gets spoken that a person did not already read and approve.
How each non-negotiable is held
findUnsupportedClaimsgate as an independent second pass, on both the hook and the whole body. Only thehooksegment may reference the prospect, so grounding lands on a bounded, checkable span.approvalstable. There is notrusted_automationpath to a render.trusted_automationis additionally gated per capabilitycapability.personalized_videomust be explicitlytrue. A missing flag means disabled — deliberately the inverse of the network flags, because this one costs money and puts a synthetic likeness in front of someone who did not ask for it.evaluatePolicyruns at render time against current flags and rules. The snapshot on the recommendation is never read.VideoRenderer.GenMediaVideoRendererdeclares what it needs structurally, so the package takes no dependency on any specific library (@profullstack/transcoder's genmedia layer satisfies it).FixtureVideoRendererproduces a deterministic descriptor. Same script → same URL, so fixture-backed tests can assert on it.Other decisions worth flagging
rendering/failedrow rather than no trace at all.grounded_signal_idsis stored on the asset, not joined from the draft. The audit question is "what evidence backed the thing we actually sent", and a later edit to the draft must not rewrite that answer.Contents
packages/media— script builder, renderer boundary, render orchestrationpackages/domain/src/video.ts— segment/script/asset types, duration estimationmigrations/0007_video_assets.sqldocs/prd-implementation-map.md— §14.3 row addedTesting
36 new tests.
bun run check(format, typecheck, tests) is clean: 537 pass, 0 fail across the monorepo.Refusal paths are covered explicitly: unapproved draft, rejected draft, missing flag, flag off, policy denial at render time, ungrounded hook, unsupported claim outside the hook, over-length script, and a failing renderer.
🤖 Generated with Claude Code