Skip to content

feat(media-buy): add flexible-window availability discovery - #6644

Open
bokelley wants to merge 2 commits into
mainfrom
feat/flexible-availability-windows
Open

feat(media-buy): add flexible-window availability discovery#6644
bokelley wants to merge 2 commits into
mainfrom
feat/flexible-availability-windows

Conversation

@bokelley

Copy link
Copy Markdown
Contributor

Summary

Buyers can ask "can I run these dates?" today (offer_filters.start_date/end_date), but not "which dates can I run?" — the flexible-window question that calendar-shaped inventory (homepage takeovers, sponsorships) actually raises, and the gap that pushes sellers toward inventing non-standard get_availability tools. This PR absorbs that use case into existing product discovery instead:

  • offer_filters.availability_horizon ({start_time, end_time}, half-open, RFC 3339): the buyer is open to any bookable window inside the horizon. Mutually exclusive with start_date/end_date (fixed flight). Not an eligibility filter — products remain returnable when only part of the horizon is open.
  • forecast-dimension-time: new forecast dimension variant (kind: "time") so sellers partition the horizon into non-overlapping, coalesced, half-open windows inside the existing forecast_range_unit: "availability" shape.
  • availability_status (available | unavailable) on both canonical and 3.x forecast points. A snapshot bounded by the forecast's valid_until, never a hold — proposal finalization remains the firm-avails/commitment boundary, and sellers MUST NOT disclose who holds a closed window.

Because product-discovery-criteria is shared, one field serves list_products (wholesale/simple), request_proposals (brief-driven), and re-check flows — no new task, no per-task variants.

Caching contract for list_products

Availability is volatile, so it is a per-read overlay, never versioned feed content:

  • feed_version covers the durable offer definition only; forecasts carry their own freshness (generated_at/valid_until).
  • A request whose fields includes forecast MUST NOT be answered with outcome: "unchanged" (that arm omits products and cannot carry the requested data).
  • Wholesale feed webhooks intentionally do not carry booking-state changes — broadcasting window closures to every subscriber is a demand-signal leak. Buyers read availability on demand; buyers needing a guarantee take a hold via refine_proposals finalize.

Docs

  • media-products.mdx: new "Flexible Availability Windows" section with request/response example and window semantics (partition completeness via incomplete[], snapshot-not-hold, no demand disclosure, derived values stay buyer-side).
  • list_products.mdx: "Availability forecasts are a per-read overlay" section with the caching contract.
  • proposal-negotiation.mdx: names the existing lifecycle in avails terms — draft = soft avail, finalize = firm avail (re-pricing at finalize is correct behavior, not an error).

Scope notes

  • Request-side availability_horizon is canonical-criteria only (list_products, request_proposals, and get_products via criteria); the legacy product-filters.json facade is not extended. The shared forecast-point response side means 3.x responses can still express windows.
  • Follow-up (not in this PR): structured outcome_target input for reverse forecasting ("10,000 clicks → what budget"), reusing the optimization-goal vocabulary and the existing total_budget_guidance/forecast_range_unit: "conversions" response surfaces.
  • Follow-up: conformance storyboards for exact-date vs flexible-window discovery, race-to-purchase (PRODUCT_UNAVAILABLE on a closed window), and no-demand-disclosure.

🤖 Generated with Claude Code

offer_filters.availability_horizon lets buyers ask "which dates can I
run?" instead of filtering to one exact flight. Sellers answer inside
the existing availability forecast shape via a new time forecast
dimension and an availability_status snapshot on forecast points.
Forecast data is excluded from list_products feed versioning, and
conditional reads cannot answer unchanged when forecast is requested.

Also raises the precommit server-unit with-timeout budget from 240s
to 600s: the full server unit suite takes ~363s wall, so the old
budget killed every schema-touching commit before the suite could
report. CI runs the suites directly and is unaffected.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@aao-secretariat aao-secretariat Bot added the ladon/needs-human-review Ladon has escalated this PR for human review. label Aug 18, 2026

@aao-secretariat aao-secretariat Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ladon verdict: Escalate to human review

Escalate — gated schema paths require human/CODEOWNERS review.

This PR is an additive flexible-window availability discovery change: offer_filters.availability_horizon, a new forecast-dimension-time (kind: time) variant, and an availability_status enum on both canonical and 3.x forecast points. The reviewer confirmed schema↔docs coherence (media-products.mdx and list_products.mdx match the new fields), the forecast-point-dimensions oneOf stays discriminated by the kind const, mutual exclusion of availability_horizon vs start_date/end_date is enforced via not.anyOf, the minor changeset is correct for a purely additive wire change, and no dist/** released artifact is touched. No Critical/High/Medium findings.

However, this PR modifies files under static/schemas/source/**, which are under a deterministic hard approval gate (gated_paths: true), and review_decision is REVIEW_REQUIRED — not APPROVED. Per decision-table row 2, the gate is not satisfied and the outcome must be escalate until a human/CODEOWNERS approval lands. The clean diff cannot auto-approve while the gate is open.

Escalation reasons

  • Touches gated protocol-source schema files (e.g. static/schemas/source/core/forecast-point.json, product-offer-filters.json, and new forecast-dimension-time.json / availability-status.json); human/CODEOWNERS approval required before merge.

Why human review

  • Modifies gated paths under static/schemas/source/** (protocol source of truth) while review_decision is REVIEW_REQUIRED, not APPROVED — human/CODEOWNERS approval required per hard approval gate (decision-table row 2).
  • This PR touches a path under a hard, non-overridable approval gate (static/schemas/source/core/canonical-forecast-point.json (modified) matches static/schemas/source/**; static/schemas/source/core/forecast-dimension-time.json (added) matches static/schemas/source/**; static/schemas/source/core/forecast-point-dimensions.json (modified) matches static/schemas/source/**; static/schemas/source/core/forecast-point.json (modified) matches static/schemas/source/**; static/schemas/source/core/product-offer-filters.json (modified) matches static/schemas/source/**; static/schemas/source/enums/availability-status.json (added) matches static/schemas/source/**; static/schemas/source/index.json (modified) matches static/schemas/source/**) and the current GitHub review decision is 'REVIEW_REQUIRED', not APPROVED. This is a hard gate enforced in code — Ladon cannot auto-approve until a human/CODEOWNERS approval is recorded, regardless of how clean the diff is.

@damiendonnelly

Copy link
Copy Markdown

Read the PR — strongly supportive, and we’re the motivating case: the GameOn agent ships the exact non-standard get_availability extension it names. The shape maps cleanly onto what we already compute per-read, so we’ll implement availability_horizon + time-dimension forecast points and retire the extension once it lands — happy to be the conformance guinea pig for the exact-date vs flexible-window storyboards. One question from implementing the real thing: where does a product’s minimum bookable duration live relative to the windows? Ours is 3 days, so an open 2-day gap is returnable but not bookable — buyers need that constraint next to the partition or they’ll race into a PRODUCT_UNAVAILABLE they could have predicted.

@bokelley

Copy link
Copy Markdown
Contributor Author

Thanks for implementing this and raising the min-duration question before the storyboards are written — this is exactly the kind of edge case that's easier to close in spec text than in conformance tests after the fact.

Where it lives: availability_status, seller-side. A gap that can't be booked due to minimum duration is unavailable — same as a gap held by another buyer. The field's semantics are "can this row result in a successful booking as of generated_at?" which is all-in: capacity, holds, and booking eligibility rules like minimum duration. Your 2-day gap should come back unavailable, not available. Buyers that see only unavailable windows in the range they want know to widen the horizon or go elsewhere, without a round-trip to confirm.

The spec text gap: unavailable's current description ("cannot be booked") is broad enough to cover this, but it doesn't say explicitly that sellers MUST apply all product booking constraints — including min/max duration — when computing status. Without that language, some sellers will report the gap as available because no hold exists, and the race reappears. I'll add a clarifying sentence to the availability-status.json description and the window-semantics bullet in the doc:

unavailable applies to any window that cannot result in a successful booking, including windows shorter than the product's minimum bookable duration, windows longer than the maximum, or windows blocked by other scheduling constraints — not only windows with competing holds.

I'll push that as a follow-up commit on this branch.

On exposing the constraint itself: min_duration/max_duration already exist on pricing-options/time-option.json, so the schema pattern is established. Surfacing a product-level min_bookable_duration scalar (not per-window — the constraint is a commercial policy of the product, not a property of individual gaps) is a reasonable follow-up issue once this shape stabilizes, and would let buyers pre-filter before inspecting availability_status. But it's not what closes the race you described — correctly setting status does that.


Generated by Claude Code

Sellers MUST apply all booking eligibility rules (min/max duration,
day-of-week, scheduling constraints) before reporting availability_status.
A gap too short to meet minimum bookable duration is `unavailable` even
when no competing hold exists, preventing a predictable PRODUCT_UNAVAILABLE
race for buyers that respect the status.

Raised by GameOn agent (@damiendonnelly) as a conformance implementation
question on PR #6644.

@aao-secretariat aao-secretariat Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ladon verdict: Escalate to human review

Escalate — gated protocol source touched, human/CODEOWNERS approval required.

This PR modifies protocol source of truth under static/schemas/source/** (added forecast-dimension-time.json, availability-status.json; modified canonical-forecast-point.json, forecast-point-dimensions.json, forecast-point.json, product-offer-filters.json, index.json) plus corresponding docs. gated_paths: true and review_decision: REVIEW_REQUIRED (not APPROVED), so the hard, non-overridable approval gate (decision-table row 2) applies. Ladon cannot auto-approve until a human/CODEOWNERS approval is recorded, regardless of how clean the diff is.

The reviewer surfaced no Critical/High/Medium findings — schema↔docs coherence holds (the new window-semantics bullet in media-products.mdx matches the rewritten 'unavailable' enum description; enum value set unchanged). But a clean diff does not lift a gated-path/prose ratification gate that is not yet satisfied by an APPROVED review. Prior decision was also escalate for the same gate reason; the gate remains unsatisfied.

No blocking code findings. This is a governance/approval-status gate, not a code-quality block. Once a human/CODEOWNERS APPROVED review is recorded, this can fall through to a normal approve.

Escalation reasons

  • Modifies gated protocol-source files under static/schemas/source/** while review_decision is REVIEW_REQUIRED, not APPROVED — human/CODEOWNERS approval required per the hard approval gate (row 2).

Why human review

  • Modifies gated protocol-source files under static/schemas/source/** (canonical-forecast-point.json, forecast-dimension-time.json, forecast-point-dimensions.json, forecast-point.json, product-offer-filters.json, availability-status.json, index.json) while review_decision is REVIEW_REQUIRED, not APPROVED — human/CODEOWNERS review required per the hard approval gate (decision-table row 2).
  • This PR touches a path under a hard, non-overridable approval gate (static/schemas/source/core/canonical-forecast-point.json (modified) matches static/schemas/source/**; static/schemas/source/core/forecast-dimension-time.json (added) matches static/schemas/source/**; static/schemas/source/core/forecast-point-dimensions.json (modified) matches static/schemas/source/**; static/schemas/source/core/forecast-point.json (modified) matches static/schemas/source/**; static/schemas/source/core/product-offer-filters.json (modified) matches static/schemas/source/**; static/schemas/source/enums/availability-status.json (added) matches static/schemas/source/**; static/schemas/source/index.json (modified) matches static/schemas/source/**) and the current GitHub review decision is 'REVIEW_REQUIRED', not APPROVED. This is a hard gate enforced in code — Ladon cannot auto-approve until a human/CODEOWNERS approval is recorded, regardless of how clean the diff is.

@bokelley

Copy link
Copy Markdown
Contributor Author

Pushed in 385c82d. unavailable now explicitly requires sellers to apply all booking eligibility constraints — min/max bookable duration, day-of-week, and other scheduling rules — before reporting status, in both the enum description and the window-semantics doc. Your 2-day gap comes back unavailable, so buyers never race into that particular PRODUCT_UNAVAILABLE.

And noted on the guinea-pig offer — when the exact-date vs flexible-window storyboards get written (tracked as a follow-up in the PR description), we'll validate them against the GameOn agent's implementation first.


Generated by Claude Code

@damiendonnelly

Copy link
Copy Markdown

Confirmed on our side, and folding the constraint into status actually simplifies us. Our extension currently returns open gaps plus a separate minimum_flight_days field for the buyer to apply, which is exactly the “buyer has to know the rules to avoid the error” shape you’re removing. Under 3.2 that field disappears and our minimum moves into the status computation instead. Might be worth a line in the storyboards for anyone migrating a homegrown avails tool, since the naive port keeps reporting hold-state only and quietly regresses.
On sequencing, so the reference-implementation plan isn’t built on a wrong assumption: we’re holding the change until this merges rather than putting draft-spec fields on a live agent, and our adcp_version stays at 3.1 until then. The work itself is small, we already compute these windows per read, so it’s a reshape rather than a build. If you can flag when the storyboards are being drafted, we’ll have it deployed ahead of them.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ladon/needs-human-review Ladon has escalated this PR for human review.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants