Repository navigation
feat(media-buy): choose installments through refine_proposals without opening commercial_terms (#8967) - #8974
Conversation
…docs #8967's PR is #8974: installment_changes (include or omit) on refine_proposals, unsatisfied_installment_changes with a reason, the installment_changes value in proposal_refinement.supported_dimensions, and a seller answer that buys a product whose installments are the chosen set. Correct the RFC-based placeholder, link the new anchors, and point its markers at #8974. Drop the sentence in the 4.0 page's What stays in 3.3, which #8974 rewrites itself. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Ladon verdict: Escalate to human review
Escalate — gated paths touched without required approval.
This PR modifies the published protocol surface under static/schemas/source/** (7 gated files: installment-change-set.json, installment-change.json, proposal-refinement-reason.json, index.json, proposal-refinement.json, refine-proposals-response.json, get-adcp-capabilities-response.json). Those paths are under a hard, deterministic approval gate (gated_paths: true), and review_decision is REVIEW_REQUIRED — not APPROVED. Per decision-table row 2, a gated-path PR cannot auto-approve until a human/CODEOWNERS approval lands.
Escalation reasons
- Gated schema source files changed; human/CODEOWNERS review required before merge. Files: static/schemas/source/core/installment-change-set.json, static/schemas/source/core/installment-change.json, static/schemas/source/enums/proposal-refinement-reason.json, static/schemas/source/index.json, static/schemas/source/media-buy/proposal-refinement.json, static/schemas/source/media-buy/refine-proposals-response.json, static/schemas/source/protocol/get-adcp-capabilities-response.json.
The reviewer found no Critical/High/Medium findings: the change is additive (new feature id media_buy.installment_refinement), every new node is x-status: experimental / x-added-in: 3.3.0, carries a correct minor changeset, preserves schema↔docs coherence across all 7 doc pages, introduces no undiscriminated oneOf, breaks no released-artifact immutability, and leaves signed terms untouched. It is not breaking-class. Once a human approval satisfies the gate, this falls through to approve on the clean diff.
Why human review
- Gated paths touched (static/schemas/source/**) and review_decision is REVIEW_REQUIRED, not APPROVED — human/CODEOWNERS approval required before merge. Files: installment-change-set.json, installment-change.json, proposal-refinement-reason.json, index.json, proposal-refinement.json, refine-proposals-response.json, get-adcp-capabilities-response.json.
- This PR touches a path under a hard, non-overridable approval gate (static/schemas/source/core/installment-change-set.json (added) matches
static/schemas/source/**; static/schemas/source/core/installment-change.json (added) matchesstatic/schemas/source/**; static/schemas/source/enums/proposal-refinement-reason.json (modified) matchesstatic/schemas/source/**; static/schemas/source/index.json (modified) matchesstatic/schemas/source/**; static/schemas/source/media-buy/proposal-refinement.json (modified) matchesstatic/schemas/source/**; static/schemas/source/media-buy/refine-proposals-response.json (modified) matchesstatic/schemas/source/**; static/schemas/source/protocol/get-adcp-capabilities-response.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.
…docs #8967's PR is #8974: installment_changes (include or omit) on refine_proposals, unsatisfied_installment_changes with a reason, the installment_changes value in proposal_refinement.supported_dimensions, and a seller answer that buys a product whose installments are the chosen set. Correct the RFC-based placeholder, link the new anchors, and point its markers at #8974. Drop the sentence in the 4.0 page's What stays in 3.3, which #8974 rewrites itself. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… opening commercial_terms (#8967) A buyer can ask for specific installments of a product during negotiation (experimental, media_buy.installment_refinement). installment_changes returns on proposal-refinement.json with the #8246 shape: a list of {product_id, installment_ref, action}, unique by (product_id, installment_ref), with installment_ref the closed core/canonical-installment-ref.json. The seller answers with a draft that buys, for each named product, one product whose installments are exactly the selected set (the product's installments with omits removed and includes added), often one it derives for the selection. Buying that product buys its complete installment set, so terms_digest binds the choice through the product_id and nothing in commercial_terms changes. unsatisfied_installment_changes on the refine_proposals result row repeats each unmet entry with a reason, on a partial or unable result with constraint_unsatisfiable. installment_changes joins proposal_refinement.supported_dimensions, and listing it requires media_buy.installment_refinement in experimental_features. The rules are normative in the schemas, refine_proposals.mdx and collections-and-installments.mdx: an offered product's installments never change, the buyer rejects a mismatched answer, and the seller checks product membership before it resolves any reference. New core/installment-change.json and core/installment-change-set.json, every new field experimental and added in 3.3.0. The MCP analysis pins (714 instances, 195 unique) and the capabilities worst case (99,101,140 bytes, on top of #8458's 99,101,090) are re-measured. The 4.0 page, registry entry, known limitations and experimental status say what 3.3 offers before 4.0 binds the slots in the signed terms. Training-agent support is out of scope. Refs #8967, #8501 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
d912d2d to
6657af7
Compare
There was a problem hiding this comment.
Ladon verdict: Escalate to human review
Escalate — gated schema paths require human/CODEOWNERS approval, and this PR introduces a new experimental protocol surface.
No blocking findings. Reviewer reports a clean pass: schema↔docs coherence verified across refine_proposals.mdx, collections-and-installments.mdx, experimental-status.mdx, v4-compatibility.mdx, and the capabilities doc; the minor changeset is protocol-scoped and correct (additive experimental fields); the response-shape not.anyOf/then.anyOf forbidding extensions match the new contract test; no new undiscriminated oneOf; withdrawn media_buy.installment_selection stays withdrawn. No Critical/High/Medium findings.
Why escalate (not approve): This PR touches files under the hard, deterministic approval gate (static/schemas/source/**) and review_decision is REVIEW_REQUIRED, not APPROVED. Decision-table row 2 forces escalate until a human/CODEOWNERS approval is recorded — a clean diff cannot lift this gate. Additionally, re-introducing the experimental media_buy.installment_refinement protocol surface is a governance/ratification matter that is a human act, reinforcing escalation.
Escalation reasons:
- Gated paths touched (
static/schemas/source/**) withreview_decision: REVIEW_REQUIRED— human/CODEOWNERS approval required before merge. Files: installment-change-set.json, installment-change.json, proposal-refinement-reason.json, index.json, proposal-refinement.json, refine-proposals-response.json, get-adcp-capabilities-response.json. - New experimental protocol surface (installment_refinement) added — ratification is a human act.
This continues the prior escalate decision: the gate condition still holds (review_decision unchanged), so the escalation stands pending human sign-off. No code changes are requested.
Why human review
- Gated paths touched (static/schemas/source/**) and review_decision is REVIEW_REQUIRED, not APPROVED — human/CODEOWNERS approval required before merge. Files: installment-change-set.json, installment-change.json, proposal-refinement-reason.json, index.json, proposal-refinement.json, refine-proposals-response.json, get-adcp-capabilities-response.json.
- Re-introduces the experimental media_buy.installment_refinement protocol surface — ratification of a new protocol addition is a human act, not auto-approvable.
- This PR touches a path under a hard, non-overridable approval gate (static/schemas/source/core/installment-change-set.json (added) matches
static/schemas/source/**; static/schemas/source/core/installment-change.json (added) matchesstatic/schemas/source/**; static/schemas/source/enums/proposal-refinement-reason.json (modified) matchesstatic/schemas/source/**; static/schemas/source/index.json (modified) matchesstatic/schemas/source/**; static/schemas/source/media-buy/proposal-refinement.json (modified) matchesstatic/schemas/source/**; static/schemas/source/media-buy/refine-proposals-response.json (modified) matchesstatic/schemas/source/**; static/schemas/source/protocol/get-adcp-capabilities-response.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.
…docs refine_proposals, unsatisfied_installment_changes with a reason, the installment_changes value in proposal_refinement.supported_dimensions, and a seller answer that buys a product whose installments are the chosen set. Correct the RFC-based placeholder, link the new anchors, and point its markers at #8974. Drop the sentence in the 4.0 page's What stays in 3.3, which #8974 rewrites itself. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With #8967 (#8974) on main, the installments a package bought are the selected set when the buyer chose installments through refine_proposals. Link the new section from the creatives-per-installment paragraph. Committed with HUSKY=0: a one-sentence docs change; staging docs/ runs the full server unit suite, which exceeds its budget at current host load. Refs #8968 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#8974 merged as a7af517. Drop its markers, and narrow the shared installment markers to #8972. The migration guide no longer says core/installment-change-set.json and the installment_changes dimension are gone: they leave after beta.1 and return in rc.0 under media_buy.installment_refinement. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…on 1.2, in the rc.0 docs #8983 (#8971) adds Reliable Reporting report-definition contract_version 1.2: a definition whose dimensions include media_buy_id must include package_id, account-level definitions are exempt, and callers pinned below 3.3 get 1.0 or 1.1 offerings. Add it to the release notes (checkpoint row, feature index, behavior change, experimental table), What's new, and the migration guide (checklist row 55 and a section), with markers until it merges. The checkpoint row also gains #8974 and #8972, which it was missing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… (experimental) (#8972) * feat(schema): assign a creative to specific installments of a package (#8968) Add experimental installment_refs (media_buy.installment_creative_assignment) to the sync_creatives assign and replace operations: an array of core/canonical-installment-ref.json, unique, 1 to 500 (a canonical product's installments cap). Present, the assignment applies only to those installments of the package; absent, to every installment, as before. - Within one installment, weight, rotation and placement routing work unchanged; placement routing and installment scope intersect, and an installment no assignment applies to gets no other installment's creative. - Each ref names an installment in the purchased product's installments, else VALIDATION_ERROR. A change to an installment past its change_cutoff is refused with CREATIVE_DEADLINE_EXCEEDED, never accepted silently. No new error codes. - A supplied list replaces the scope; omission on an assign upsert keeps it. replace transfers the scope unless it supplies one, merge moves it, and unassign and merge reject installment_refs by name. - Readback on get_media_buys packages[].creative_approvals[] and list_creatives assignments.assigned_packages[], omitted for an assignment on every installment. - Buyers send it only to sellers listing the feature (versioning rule 4); others reject it with UNSUPPORTED_FEATURE. New experimental-status row. Not on core/creative-assignment.json, the deprecated create_media_buy and update_media_buy rows, or the deprecated sync_creatives assignments[] row: 3.3 adds nothing to deprecated tasks (#8501, #8767). Docs: sync_creatives section with a two-post creator example, a creator-brief section in collections-and-installments, readback rows. Tests: tests/installment-creative-assignment.test.cjs in test:schemas. Committed with HUSKY=0: precommit:server-unit hit its 1500s budget at host load ~110-140. Its timed-out files pass alone; the other hook steps were run by hand and pass. No server file is touched. Refs #8968 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(sync_creatives): validate the installment example in its test, not via $schema (#8968) check-schema-links rejects /schemas/latest/ for a schema already released in v3, and sync-creatives-request.json is released in 3.2.10, which lacks installment_refs. Follow the assignment-trackers example: drop $schema from the creator example and have tests/installment-creative-assignment.test.cjs extract the block from the doc and validate it against the source schema. Committed with HUSKY=0 for the same reason as the previous commit; this commit touches one doc example and one root test file, both run locally. Refs #8968 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(collections): tie installment_refs to chosen installments (#8968) With #8967 (#8974) on main, the installments a package bought are the selected set when the buyer chose installments through refine_proposals. Link the new section from the creatives-per-installment paragraph. Committed with HUSKY=0: a one-sentence docs change; staging docs/ runs the full server unit suite, which exceeds its budget at current host load. Refs #8968 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Brings the branch to a7af517 (#8974). package.json test:schemas keeps both tests/installment-refinement.test.cjs and tests/reporting-report-definition-vectors.test.cjs. Pins re-measured: controller parity 1_330_906 bytes (bound 1_388_000); capabilities and model-context bounds pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…docs refine_proposals, unsatisfied_installment_changes with a reason, the installment_changes value in proposal_refinement.supported_dimensions, and a seller answer that buys a product whose installments are the chosen set. Correct the RFC-based placeholder, link the new anchors, and point its markers at #8974. Drop the sentence in the 4.0 page's What stays in 3.3, which #8974 rewrites itself. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#8974 merged as a7af517. Drop its markers, and narrow the shared installment markers to #8972. The migration guide no longer says core/installment-change-set.json and the installment_changes dimension are gone: they leave after beta.1 and return in rc.0 under media_buy.installment_refinement. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…on 1.2, in the rc.0 docs #8983 (#8971) adds Reliable Reporting report-definition contract_version 1.2: a definition whose dimensions include media_buy_id must include package_id, account-level definitions are exempt, and callers pinned below 3.3 get 1.0 or 1.1 offerings. Add it to the release notes (checkpoint row, feature index, behavior change, experimental table), What's new, and the migration guide (checklist row 55 and a section), with markers until it merges. The checkpoint row also gains #8974 and #8972, which it was missing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…docs refine_proposals, unsatisfied_installment_changes with a reason, the installment_changes value in proposal_refinement.supported_dimensions, and a seller answer that buys a product whose installments are the chosen set. Correct the RFC-based placeholder, link the new anchors, and point its markers at #8974. Drop the sentence in the 4.0 page's What stays in 3.3, which #8974 rewrites itself. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#8974 merged as a7af517. Drop its markers, and narrow the shared installment markers to #8972. The migration guide no longer says core/installment-change-set.json and the installment_changes dimension are gone: they leave after beta.1 and return in rc.0 under media_buy.installment_refinement. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…on 1.2, in the rc.0 docs #8983 (#8971) adds Reliable Reporting report-definition contract_version 1.2: a definition whose dimensions include media_buy_id must include package_id, account-level definitions are exempt, and callers pinned below 3.3 get 1.0 or 1.1 offerings. Add it to the release notes (checkpoint row, feature index, behavior change, experimental table), What's new, and the migration guide (checklist row 55 and a section), with markers until it merges. The checkpoint row also gains #8974 and #8972, which it was missing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…docs refine_proposals, unsatisfied_installment_changes with a reason, the installment_changes value in proposal_refinement.supported_dimensions, and a seller answer that buys a product whose installments are the chosen set. Correct the RFC-based placeholder, link the new anchors, and point its markers at #8974. Drop the sentence in the 4.0 page's What stays in 3.3, which #8974 rewrites itself. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#8974 merged as a7af517. Drop its markers, and narrow the shared installment markers to #8972. The migration guide no longer says core/installment-change-set.json and the installment_changes dimension are gone: they leave after beta.1 and return in rc.0 under media_buy.installment_refinement. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…on 1.2, in the rc.0 docs #8983 (#8971) adds Reliable Reporting report-definition contract_version 1.2: a definition whose dimensions include media_buy_id must include package_id, account-level definitions are exempt, and callers pinned below 3.3 get 1.0 or 1.1 offerings. Add it to the release notes (checkpoint row, feature index, behavior change, experimental table), What's new, and the migration guide (checklist row 55 and a section), with markers until it merges. The checkpoint row also gains #8974 and #8972, which it was missing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…docs refine_proposals, unsatisfied_installment_changes with a reason, the installment_changes value in proposal_refinement.supported_dimensions, and a seller answer that buys a product whose installments are the chosen set. Correct the RFC-based placeholder, link the new anchors, and point its markers at #8974. Drop the sentence in the 4.0 page's What stays in 3.3, which #8974 rewrites itself. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#8974 merged as a7af517. Drop its markers, and narrow the shared installment markers to #8972. The migration guide no longer says core/installment-change-set.json and the installment_changes dimension are gone: they leave after beta.1 and return in rc.0 under media_buy.installment_refinement. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…on 1.2, in the rc.0 docs #8983 (#8971) adds Reliable Reporting report-definition contract_version 1.2: a definition whose dimensions include media_buy_id must include package_id, account-level definitions are exempt, and callers pinned below 3.3 get 1.0 or 1.1 offerings. Add it to the release notes (checkpoint row, feature index, behavior change, experimental table), What's new, and the migration guide (checklist row 55 and a section), with markers until it merges. The checkpoint row also gains #8974 and #8972, which it was missing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Implements the RFC in #8967. Refs #8501, #8230, #8246, #8537.
For the 3.3 RC (Mon 2026-11-16).
static/schemas/source/**is Ladon-gated, so a human merges this.Summary
A buyer can now choose specific installments of a product during negotiation, without adding anything to
commercial_terms. It's experimental, under the new feature idmedia_buy.installment_refinement. The adopter is Availnet, a creator marketplace, through Apostra.installment_changesreturns onmedia-buy/proposal-refinement.json, which fix(schemas): open the 3.3 schemas that were meant to be open #8866 opened. Each entry is{ product_id, installment_ref, action }, and the entries are unique by(product_id, installment_ref).installment_refis the closedcore/canonical-installment-ref.json, andactionisincludeoromit. It counts as a revise dimension, andfinalizeandextend_holdforbid it. The new files arecore/installment-change.json(one entry) andcore/installment-change-set.json(the list, with the uniqueness rule).installments[]with everyomitremoved and everyincludeadded. A satisfying draft buys one product whoseinstallments[]is exactly the selected set: the product itself, or one the seller offers for the selection in its place, such ascreator_juniper_vale__2026-12-03. Under today's rule, buying a product buys its complete installment set, soterms_digestbinds the choice through theproduct_id.unsatisfied_installment_changeson therefine_proposalsresult row repeats each unmet request entry and adds a requiredreason, on apartialorunableresult withreason_code: "constraint_unsatisfiable". Thepartialinvariant covers it: every draft satisfies every entry the list leaves out.installment_changesjoinsmedia_buy.proposal_refinement.supported_dimensions. The capabilities schema requiresmedia_buy.installment_refinementinexperimental_featureswhenever a seller lists that dimension. Buyers sendinstallment_changesonly to such a seller, and any other seller rejects the omitted dimension withUNSUPPORTED_FEATUREunder the existingsupported_dimensionsrule.media_buy.installment_selectionisn't reused, because that id meant the signed field.Normative rules
These are in the schema descriptions, in
refine_proposals.mdx"Choose installments (experimental)" and incollections-and-installments.mdx"Choosing installments (experimental)".product_id. The digest binds theproduct_id, not the slots, so this rule keeps an accepted selection fixed.installments[]and rejects a mismatch. After finalize,holds[]reads back the slots the hold covers.product_idis bought by the source proposal, or added byproduct_changesin the same request, before it resolves any reference, as feat(media-buy): add installments to the canonical product and let a proposal buy named slots #8246 required. The ordered checks are feat(media-buy): add installments to the canonical product and let a proposal buy named slots #8246's:UNSUPPORTED_FEATUREfor an undeclared dimension;INVALID_REQUESTfor a duplicate or contradictory entry, or a product outside the proposal or omitted byproduct_changes;REFERENCE_NOT_FOUNDfor an unresolvable reference;constraint_unsatisfiablewith the entry listed.Nothing in
commercial_terms.product-purchase.jsonstill carries noinstallments. Every new field isx-status: experimentalandx-added-in: 3.3.0. 4.0 keeps the signed design (installment_selection_required_in_4_0), and the 4.0 page and registry now say what 3.3 offers in the meantime.For reviewers
installments[]equalling the selected set, so there's no new field on the canonical product or inside the signed terms. feat(media-buy): add installments to the canonical product and let a proposal buy named slots #8246's rule that anincludenames one of the product's own declared installments is widened to "an installment of a collection the product'scollectionsname". Without that, a buyer couldn't add a slot back to a derived product that dropped it.reasonis free text. The result row'sreason_codealready saysconstraint_unsatisfiable. A per-entry code can be added later on the open item if adopters want one.tests/mcp-schema-analysis.test.cjsgo from 711 to 714 definition instances and from 192 to 195 unique definitions: the two new files pluscanonical-installment-ref, now reached from therefine_proposalsinput.mainafter feat(schema): add media_buy.supported_delivery_types to capabilities #8458 (99,101,090) it measures 99,101,140, which is pinned in the test and insecurity.mdx.tools/listbudget (162 KiB) and the projection bounds hold unchanged.TYPED_DIMENSIONSdoesn't listinstallment_changes, so it never advertises the dimension, and no storyboard sends it.installments[]is the selection. The only shared file iscollections-and-installments.mdx, where the two PRs edit adjacent sections.Testing
Rebased onto
main38a83d1d44(after #8793 and #8458) as6657af7001, with the worst case re-measured at 99,101,140 / 190 open positions. On the rebased tree these pass:lint:additive-minor,capabilities-structural-bounds,mcp-schema-analysis(714/195),test:mcp-schema-projection,product-discovery-schema-parity, the installment, withdrawn and canonical-installments tests,build:compliance -- --check, the hook guards,typecheckand the image check.The full run below was on
maina2a7d20fffplus this commit, run sequentially:npm run build:schemas: pass.npm run test:schemas: 1197/1197, plus premium-display 10/10 and geo-region 6/6. The newtests/installment-refinement.test.cjshas 9 tests:installment_changesalone is a valid revise, including a swap. Rejected are a missingaction,action: "swap", an extra member inside the closedinstallment_ref, an empty list, an exact duplicate, andinstallment_changesonfinalizeorextend_hold. The change set declaresunique_by.partialresult whose only unsatisfied list isunsatisfied_installment_changessatisfiesconstraint_unsatisfiable. An entry withoutreasonis rejected, and so is the list on arevisedorfinalizedresult.installment_changesdimension withoutmedia_buy.installment_refinementis rejected, and with it, accepted.experimentaland3.3.0.product-purchase.jsonhas noinstallments, and only the refinement id has an experimental-status row.withdrawn-to-4-0.test.cjskeeps the signedpurchases[].installmentswithdrawn and its 4.0 check, and stops asserting that the request-side pieces are absent.canonical-installments.test.cjsnow checks thatmedia_buy.installment_selectionhas no registry row, and allows the new row's prose to name it.npm run test:mcp-schema-projection(pins re-measured as above),npm run test:examplesandnpm run test:contracts506/506 pass.capabilities-structural-boundspasses, andproduct-discovery-schema-paritypasses 4/4.npm run lint:additive-minor(2 recorded, 0 new),test:additive-minor, the 4.0 registry lint and test,audit-oneof --check, andbuild:compliance -- --check(1889 references, after regenerating the links for the newrefine_proposalssection). JSON-schema validation of the 7 changed docs pages and the changeset scope check pass too.ADCP_SKIP_STORYBOARD_MATRIX=1for host load, as the playbook allows; this PR's only compliance-path change is registry text inv4-compatibility.yaml. CI'straining-agent-storyboardsis the gate.Local hook
Committed with
HUSKY=0under the release coordinator's standing approval for #8501-line PRs that touch no server file. The hook's server-unit stage times out at this host's load, so it was skipped. CI is the gate. I ran the other hook steps by hand:npm run typecheckand the image check pass.npm run test:unitpassed 1281/1284. The 3 misses were 10s load timeouts inaddie/compliance-validate-input-gatesandbilling/stripe-client, which this PR doesn't touch, and those files pass 69/69 on their own.🤖 Generated with Claude Code