goav is pre-v1. Use this roadmap to answer a human question: what can I rely on today, what is still allowed to move, and what would be dishonest to imply as done? It states the design contract, separates stable, experimental, deliberately deferred, planned, and non-goal work, then names the checks that must hold before a v1 tag. Every claim cites the test, benchmark, or document that backs it, or is marked roadmap.
The nearby docs split the work by reader need: docs/V1_SCOPE.md names the
release scope, docs/REPOSITORY_TRUST.md records the GitHub metadata and
release posture, and docs/GSTREAMER_ALTERNATIVE.md explains how goav
relates to GStreamer. Earlier planning artifacts live in docs/history/.
docs/NORTH_STAR.md was folded into this document (2026-07-13); its numbered
evidence map continues below unchanged.
goav has one user-facing model, and it must hold for initial builds, runtime attach, diagnostics, control, and examples:
From(inputs...) -> stream selection -> operations -> taps -> branches -> destinations -> task
Normal users should not need graph handles, string routing, or a separate
workflow API for recording, transcoding, preview, diagnostics, or late
attachment. Internally every recipe lowers the same way —
BranchSpec -> operationSpec list -> WorkPlan / WorkPatch -> executable task
— and each fluent operation appends exactly one internal operation record
(Decode → OpDecode, Copy → OpCopy, Shape/Auto/Require/Prefer →
shape, Resize/Resample → transform, Do → stage, Encode → encode,
Tap → tap).
- A direct stream is an implicit
Branch("main"). - A branch is an ordered operation list plus destinations, source/tap anchor, media kind, buffer policy, and detach policy.
- A flow is reusable operations; it owns no source, destination, runtime state, or lifecycle.
Mux(name, destination)is the first-class way to group branches into one mux or sink group; reusing one ungrouped destination value is rejected.- Shape validation is central. Inputs, operations, taps, flows, branches, and destinations all participate in the same compatibility check.
- Build and Attach share the same lowering model:
WorkPlanfor a full task,WorkPatchfor a runtime branch update. - Runtime observation is composition:
Branch + Do + Sink, plusWatch(...).Events()subscriptions,Snapshot,Stats,Explain, and graph rendering. - Runtime controls lower into
task.Control,Mutable.Attach,Attachment.Rebranch,Mutable.Detach,Watch,Snapshot,Stats, orClose; the control-plane binder never calls arbitrary methods.
This contract is broader than the v1 front door: where it names runtime mutation, control, observation, dynamic streams, or joins, it documents the shared architecture for governed advanced features, not an automatic v1 promise.
The working rule: new functionality belongs in the grammar only when it can
answer, without special cases — what operation record it appends; what shape
facts it consumes and produces; how it appears in Explain, Describe, and
Snapshot; how the same branch attaches at runtime; how it fails before
resources open; and how a test source or external adapter exercises it end to
end. If those answers require a separate workflow path, keep the feature out
of the front-door API until the shared planner can express it.
The acceptance tests keep the design honest. The numbers below are the stable
labels used in test failure messages (NORTH_STAR #n); use them as a map when
reviewing whether a new feature strengthens the grammar or bypasses it.
| Area | Current evidence |
|---|---|
| Grammar | #1 README and docs guards keep the adoption vocabulary on Input, Stream, Branch, Destination, and Task; deeper docs own Tap and Flow. #2 direct chains lower like Branch("main"). #3 flows expose no destinations. #4 destination grouping is explicit with Mux(name, destination); ungrouped handle reuse is rejected. |
| Planner | #5 Build and Attach share canonical operation lowering. #6 Attach emits WorkPatch downstream of taps. #7 Explain reads from WorkPlan. #8 Snapshot reflects plan plus patches. #9 legacy workflow packages are gone. #10 workflow-kind dispatch is gone from normal recipes. |
| Shape | #11 Resize requires video frames. #12 Resample requires audio frames. #13 frames cannot go to byte destinations without Encode. #14 packet Copy to File succeeds. #15 decoded frames can end in Sink. #16 errors include operation, actual/expected shape, and fix. #17 conversions are inserted only under an explicit policy. |
| Branches | #18 branches after Decode share one decoder. #19 dropping preview branches do not stall archive branches. #20 Blocking backpressures. #21 branch drop counters are visible. #22 mutable branch output cannot corrupt siblings. |
| Runtime mutation | #23 Attach opens destinations before mutation. #24 attach failure rolls back. #25/#26 drain and abort are pinned for Rebranch and standalone Mutable.Detach. #27 Rebranch starts replacement before old detach. #28 failed Rebranch keeps the old branch. #29 Pause/Resume affects one branch. |
| Events and control | #30 Watch filters and stream/attach/backpressure events are pinned; EventBranchAttached/EventBranchDetached report runtime branch lifecycle, and destination commit/abort/error events report finalization. #31 Snapshot reports typed task, branch, destination, tap, and drop state. #32 Keyframe reaches adapters or fails clearly. #33 SetBitrate reaches encoders or fails clearly. |
| Sources | #34 custom packet source Copy to File. #35 custom frame source Encode to File. #36 source.Push reports Accepted/Dropped. #37 source EOS commits destinations. |
| Dynamic streams | #38 late streams attach branches. #39 ambiguous stream selection lists candidates and fixes. #40 removal drains rule-created branches. |
| Multi-input and joins | #41 multiple inputs can share one destination. #42 codec/format/timebase mux compatibility is checked. #43 Mix joins audio branches. #44 join shape mismatch is solved or refused before mutation. |
The shortest version: recipes are the product surface; plans, graphs, and
runtimes are implementation layers. The declarative grammar is the only normal
composer:
input -> stream -> operations -> tap -> branch -> destination lowers into
WorkPlan -> pipeline.Graph -> Task.
Live mutation follows the same promise: runtime attachment is a patch of the
same plan model. Mutable.Attach compiles the same branch grammar into
WorkPatch, validates before graph mutation, and rolls back fully on failure
(TestTaskAttachRuntimeBranchGroupRollsBackOnLaterFailure).
The destination model is also settled: Target collapsed into Destination,
and Write, URI, Writer, Sink, and Custom return stable goav-owned
destination handles. Mux(name, destination) is the preferred way to declare
one shared mux/sink group when branches build matching destinations
independently. Reusing one ungrouped handle is rejected; grouping is explicit
(TestMuxPreferredOverHandleIdentity, TestMuxSurvivesWithAndCopy,
TestSameHandleGroupingRequiresMux).
The time domain is settled too (landed 2026-07-13): one shared timeline per
task with task-wide control.Rate, synchronized sink playout, task
Pause/Resume with readiness, seek with flush, QoS feedback, and declared
per-path latency budgets in Explain. Contract prose lives in
docs/FLOW_CONTROL.md; the slice-by-slice record is
docs/history/TIME_DOMAIN_PLAN.md.
Governed here means "changes are deliberate, tested, and recorded", not "this
whole surface is the v1 promise." The governed surface is the tier inventory
in docs/API_SURFACE.md, reviewed on every change: the machine-enforced
approved-identifier pins were deliberately removed on 2026-06-27, CI still
requires a doc comment on every public package, and the doc-citation pins
(docs_citation_contract_test.go) keep the governance docs from citing
enforcement that no longer exists. The inventory:
- Tier A inventory: the grammar and task capabilities.
From/stream selection/operations (Decode/Copy/Resize/Resample/Do/Encode)/Shape/Auto/Require/Prefer/Tap/Branches/To/OnStream;Mix/Composite/Select;Flow;Tasklifecycle (Run/Close) plus structural built-taskCloseContext(ctx); opt-in task capability interfaces forExplain, inspection (Describe/Taps/Snapshot/Stats), mutation (Attach/DetachwithDrainBranch/AbortBranch,Rebranch), controls (Control), and observation (Watch; unfilteredWatch()observes every task event);New/UseRuntimeand thebundleruntime helpers; structuredBuildErrorwith stable families, detailed codes,Detail(key),DetailLines(), andFixLines(); theplan,snapshot,lifecycle,shape,flow, andavvocabulary packages. Runtime mutation/control/advanced observation and joined-stream breadth are governed pre-v1 behavior, not normal v1 promises unless the release decision explicitly retains them. - Tier B: extension points.
provider.SourceandSource(fn)push sources;provider.Destination/Writer/Sinkdestinations;EventFunc/FrameFunc/PacketFunc/SinkFunchooks; codec/format/filter factory interfaces with per-runtimeWith*registration;goavtest. Every extension point has an external toy implementation run end to end (adapterproof/adapter_compat_test.go, guidedocs/ADAPTER_AUTHORING.md). - Tier C: expert.
expert.Graph(runtime)handles,pipelinegraph machinery, prebuilt codec/format/filter stages. These are off the grammar, governed as escape hatches, and advanced/non-v1 unless explicitly retained.
The error contract (error_acceptance_test.go,
TestErrorCatalogDocMatchesErrcodeCatalog, docs/ERRORS.md) and runtime
invariants are pinned current contracts: close
idempotency, close during run, race-safe snapshots under attach/detach,
commit-failure propagation (task_invariants_test.go), watcher isolation
(watch_test.go), and drop observability
(TestFrontDoorDropReasonsReadableWithoutPipeline). They prove the current
surface is controlled; they do not by themselves promote every advanced
capability into v1.
These pieces exist and are tested, but their numbers or exact semantics may
still move. docs/PERFORMANCE.md "Experimental" is the performance side of
this list:
- Buffered mutable frame/owned-payload fanout: borrowed packet fanout now
shares one graph-owned refcounted copy (
pipelineTestBufferedFanoutRefcountAllocs,BenchmarkBufferedFanout/refcount); owned packets, frame planes, and defensiveCopyAlwaysmode still copy per target to preserve branch-local mutation isolation. - Attach-under-load cost:
BenchmarkAttachDetachUnderLoadmeasures a cold-path control operation dominated by planning; not a data-plane figure. - OnStream rule breadth: identity matches (
source.MatchMedia/source.MatchCodec/source.MatchStreamID/source.MatchStream(fn)) plus statefulsource.MatchFirst(n),source.MatchAfter(d), andsource.MatchWithin(d)conditions ship. Rule-created branches drain when the matched stream disappears. - Join nesting depth: nested joins are proven at the tested depths
(
join_nested_test.go,TestJoinDescribeEqualsBuildNestedMix); deeper nesting compiles through the same recursion but has no dedicated proof or cost model. - PGO profile workflow:
default.pgois a checked-in workflow artifact generated byscripts/bench/pgo.shand freshness-checked in CI byscripts/bench/check-pgo.sh. It is not a performance claim; measured deltas still need same-machine before/after evidence. - Benchmark allocation/byte ratchet:
bench-results/reference/{root,pipeline,soak}.jsonis checked in and enforced byscripts/bench/baseline.sh checkin CI. It gatesallocs/opandB/oponly; timing, RSS, and latency claims still need release artifacts indexed by ascripts/bench/perf-lab.shmanifest. - Runtime media pools:
runconfig.WithMediaPools(true)enables per-runtime scratch pooling for runtime-owned metadata maps and join frame-plane slices. The allocation path is pinned, but the option stays explicit while attach/detach churn workloads gather real release evidence. - Custom shape solver deltas: external adapters can register per-runtime
.Auto(...)delta contributors withrunconfig.WithShapeDeltaand require callers to opt in withshape.AllowCustom("name"). The external proof isadapterproof.TestExternalShapeDeltaContributorAppearsInExplain; this is governed pre-v1 API design, not a v1 promise yet.
- H264 recipe encode: deliberately descriptor-only in the default bundle.
Registry descriptors are discoverable while factory lookup returns
codec.ErrUnavailable(docs/ARCHITECTURE.md"Codec backends"). Decode and receive verticals are active; encode is not advertised as runnable because the repo does not ship a vetted pure-Go H.264 encoder backend with caller-owned output buffers and allocation pins. Applications that need H.264 encode register their chosen backend explicitly withrunconfig.WithEncoder(or a codec adapter) instead of getting an unproven bundled path. - Pull scheduling: the one time-domain gap left open, deferred with a
reason — consumer backpressure already bounds producers
(
flow.Blocking,TestBufferedFanoutDropBlockBackpressuresSource), so a pull scheduler would duplicate flow control the graph already has. The rest of the time domain is landed and cited in "The settled model" above. - Internal-package layering: measured on the cross-file reference graph
and still not ready for a package split. The data-transfer boundary has
started with
internal/recipeir; normal recipe work-plan handoffs, branch-composition planning, media input selection, OnStream branch facts, and runtime attach/rebranch branch, operation, and destination facts now cross explicit DTOs, but root-only attachments remain before planner internals can move behind enforced package boundaries (docs/ARCHITECTURE.md"Package layering"). - Destination lifecycle events: task and runtime-branch destinations now
publish commit/abort/error events. Standalone
Mutable.Detachhas explicit drain/abort outcomes, branch attach/detach events are watchable, and stream-rule removals drain rule-created branches. streamIntentnormalization fold: Explain stream rows and adapter requirements, plus mux compatibility, now consume codec facts fromWorkPlanoperations. Remaining validation/planning readers still consume the intent layer; fold them as they are touched.
- SRT/NDI providers through the
provider.Sourceextension point: by design zero core changes. The third-provider claim is now proven byplayoutav, a nested scheduled-playout module with an external compat test; SRT/NDI remain roadmap. - Extension closure: done for the current grammar. Everything the
grammar accepts is implementable externally: adapters
(
adapterproof/adapter_compat_test.go), custom joins (goav.Join, symmetry proof inadapterproof/join_proof_test.go), input decoration (goav.WrapSource), custom controls (Deliver+AtTap), and custom.Auto(...)solver deltas (shape.AllowCustom+runconfig.WithShapeDelta). New grammar verbs reopen the question and must ship with their extension point. - Published performance baselines: the allocation/byte CI ratchet now
ships, and the perf-lab harness reports p50/p95/p99, heap/RSS, pressure,
soak drift, live-room attach/detach churn, fanout, container corpus,
real-Opus smoke, and a release manifest that records host/toolchain/git
provenance plus separate soak benchtimes. The churn soak summary carries
max_rss_Bwhen the host exposes RSS, and release-quality soaks run through wall-clock test harnesses with a recorded paced churn interval. The long-run artifact path has been locally proven; fresh latency/RSS/throughput artifacts still need to be attached per release candidate (docs/PERFORMANCE.md). Roadmap. - Additional
lifecycle.SwitchAtboundaries beyondNextFrame/NextKeyframe/AtMediaTime(rebranch_policy.go). Roadmap.
- GStreamer plugin parity. goav is not a general multimedia framework;
matching element-for-element would reproduce the surface the grammar
exists to avoid (
docs/GSTREAMER_ALTERNATIVE.md). - Hardware codec backends in core. Core stays pure Go — every CI build
and test runs
CGO_ENABLED=0, so a cgo import cannot land; acceleration belongs in external adapters behind thecodecextension points, where cgo is the adapter's choice. - cgo in core. Same gate; single-binary
CGO_ENABLED=0deployment is a headline property (.github/workflows/ci.ymlbuilds with it). - Global registries. Registries are per-runtime; two runtimes in one
process must never see each other's adapters (
docs/ARCHITECTURE.md:goav.Newis the composition root). - JIT / runtime code generation. The planner emits a static graph; per-message dispatch stays direct calls. The win would belong to codecs (external anyway) and the cost is un-debuggable, un-pinnable hot paths.
These checks prove the current surface is governed. They do not, by
themselves, mean the current surface is the v1 target. The release candidate
must also satisfy docs/history/SIMPLIFICATION_TARGET.md, or explicitly document every
retained exception.
The checklist below gates the tag. Each item names its current evidence.
- Approved API surface: the tier inventory in
docs/API_SURFACE.md, updated in the same change as any new export and enforced in review. The mechanical both-direction pin was removed 2026-06-27; the doc-citation pins (docs_citation_contract_test.go) keep the inventory's evidence honest. - Compile-tested examples: root
Example*functions run undergo test(example_test.go); theexamples/webrtc-runtime-laddermodule builds and tests in CI. - Operation reference:
docs/OPERATIONS.mdcovers the front-door chain operations by input shape, output shape, domain, inserted conversions, primary refusals, and runtime attach behavior. Its former section pin was removed with the doc source pins (2026-06-27); accuracy is review-owned. - Structured errors enforced:
error_acceptance_test.gobuilds the bad recipes and asserts rendered refusals, andTestErrorCatalogDocMatchesErrcodeCatalogkeepsdocs/ERROR_CATALOG.mdin lockstep witherrcode/errcode.go; every current errcode names a bad recipe, coverage assertion, a fix, and the test that owns it. - Benchmarks present: 16 measured workloads (
bench_test.go) + pipeline/container suites plus perf-lab latency/RSS/pressure/control/fanout/ container/real-Opus smoke; bench artifacts run in CI; methodology indocs/PERFORMANCE.md. - Adapter guide:
docs/ADAPTER_AUTHORING.mdwith the executable extension-point proof (adapterproof/adapter_compat_test.go) and copyable external modules underexamples/custom-*,examples/transactional-writer, andexamples/control-plane-host. - Extension cookbook:
docs/EXTENSION_COOKBOOK.mdmaps source, destination, transactional writer, filter, codec, join, and control-plane host seams to copyable code shapes and example modules. - Composability laws:
docs/COMPOSABILITY_LAWS.mdmaps direct stream equivalence, flow restraint, Build/Attach parity, Describe/Build equality, Explain diagnostics, destination handles, branch isolation, rollback, and external parity to executable tests. - Race tests pass: CI runs
go test -race ./...across the whole root module plus each nested module (.github/workflows/ci.yml). - Container fuzz/corpus:
FuzzDemuxerMalformedInputswith seed corpora (container/matroska,container/webmfuzz_test.go+testdata/fuzz) plus external field-corpus tests (TestExternalMatroskaFieldCorpus,TestExternalWebMFieldCorpus). - No global state: registries live on the runtime
(
docs/ARCHITECTURE.md); audited 2026-06: remaining package-level vars are error sentinels, immutable profile tables, and atomic ID counters. No pin test exists for this; the audit is repeated at review. - Documented public packages: CI's package-documentation smoke fails
any public package without a package doc; per-symbol doc comments are
review-enforced since the per-symbol scanner was removed 2026-06-27
(
adapters/*andcontainer/*sit behind the codec/format extension points and are excluded by the decision recorded indocs/API_SURFACE.md). - Dependency purity: importing the root package does not pull bundled
adapter packages into its dependency graph. The root module still carries
bundled backend requirements because
goav/bundleis not a nested module; revisit a nested-module split only if SBOM or scanner pressure justifies it (TestRootModuleDependencyPurity). The Pion ecosystem lives in the nestedrtpavandwebrtcavmodules;playoutavis nested for the same provider-seam isolation without Pion. Nested modules tag independently with prefixed tags (rtpav/vX.Y.Z,webrtcav/vX.Y.Z,playoutav/vX.Y.Z), so a root v1 does not freeze the transport modules and vice versa; webrtcav requires rtpav requires the root, so tag the root first, then rtpav, then webrtcav/playoutav as needed. - Release automation:
.github/workflows/release.ymlvalidates existing signed tags, runs checks in the tagged module directory, builds root CLI archives, and creates GitHub release notes; root CLI releases include checksums, Go module SBOM, per-binary buildinfo, and provenance metadata.docs/RELEASING.mddocuments signed-tag ownership and tag order. - Release decision: confirm the
go 1.26directive ingo.modis the intended minimum supported Go, close or explicitly waive every item indocs/history/SIMPLIFICATION_TARGET.md, fill the compatibility note template indocs/COMPATIBILITY.md, and cut v1. Not done; this is a maintainer product decision plus any remaining simplification work.