feat(media-buy): add flexible-window availability discovery - #6644
feat(media-buy): add flexible-window availability discovery#6644bokelley wants to merge 2 commits into
Conversation
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>
There was a problem hiding this comment.
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 newforecast-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) matchesstatic/schemas/source/**; static/schemas/source/core/forecast-point-dimensions.json (modified) matchesstatic/schemas/source/**; static/schemas/source/core/forecast-point.json (modified) matchesstatic/schemas/source/**; static/schemas/source/core/product-offer-filters.json (modified) matchesstatic/schemas/source/**; static/schemas/source/enums/availability-status.json (added) matchesstatic/schemas/source/**; static/schemas/source/index.json (modified) matchesstatic/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.
|
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. |
|
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: The spec text gap:
I'll push that as a follow-up commit on this branch. On exposing the constraint itself: 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.
There was a problem hiding this comment.
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/**whilereview_decisionisREVIEW_REQUIRED, notAPPROVED— 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) matchesstatic/schemas/source/**; static/schemas/source/core/forecast-point-dimensions.json (modified) matchesstatic/schemas/source/**; static/schemas/source/core/forecast-point.json (modified) matchesstatic/schemas/source/**; static/schemas/source/core/product-offer-filters.json (modified) matchesstatic/schemas/source/**; static/schemas/source/enums/availability-status.json (added) matchesstatic/schemas/source/**; static/schemas/source/index.json (modified) matchesstatic/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.
|
Pushed in 385c82d. 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 |
|
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. |
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-standardget_availabilitytools. 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 withstart_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 existingforecast_range_unit: "availability"shape.availability_status(available|unavailable) on both canonical and 3.x forecast points. A snapshot bounded by the forecast'svalid_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-criteriais shared, one field serveslist_products(wholesale/simple),request_proposals(brief-driven), and re-check flows — no new task, no per-task variants.Caching contract for
list_productsAvailability is volatile, so it is a per-read overlay, never versioned feed content:
feed_versioncovers the durable offer definition only; forecasts carry their own freshness (generated_at/valid_until).fieldsincludesforecastMUST NOT be answered withoutcome: "unchanged"(that arm omitsproductsand cannot carry the requested data).refine_proposalsfinalize.Docs
media-products.mdx: new "Flexible Availability Windows" section with request/response example and window semantics (partition completeness viaincomplete[], 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
availability_horizonis canonical-criteria only (list_products,request_proposals, andget_productsviacriteria); the legacyproduct-filters.jsonfacade is not extended. The sharedforecast-pointresponse side means 3.x responses can still express windows.outcome_targetinput for reverse forecasting ("10,000 clicks → what budget"), reusing theoptimization-goalvocabulary and the existingtotal_budget_guidance/forecast_range_unit: "conversions"response surfaces.PRODUCT_UNAVAILABLEon a closed window), and no-demand-disclosure.🤖 Generated with Claude Code