play: changesNotSentForReview goes only on edits.commit (validate answers 400 "Unknown name changesNotSentForReview: Cannot bind query parameter", found by a game on 220e9bb's real run): a refused commit is committed again with the parameter, a dry run's refused validate only says how the real run commits; the fake Play refuses the parameter on validate and a test asserts validate never gets it

This commit is contained in:
CRTkafa 2026-10-16 05:53:18 +08:00
parent dcb553fd16
commit 8a84ab4089
6 changed files with 22 additions and 13 deletions

View file

@ -86,15 +86,16 @@ export async function playClient(root: string, packageName: string) {
let notSent = !!o.notSentForReview;
const refused = (): void => {
notSent = true;
log('play', 'Play refuses to send these changes for review automatically (after a rejection, other pending changes or managed publishing): validating again with changesNotSentForReview=true');
log('play', 'Play refuses to send these changes for review automatically (after a rejection, other pending changes or managed publishing): committing with changesNotSentForReview=true (only edits.commit takes it; validate answers 400 "Unknown name")');
};
try {
const out = await fn(id, { unchanged: () => { same = true; } });
if (o.dry) {
try { await call('POST', `${API}/edits/${id}:validate${withNotSent(undefined, notSent)}`); } catch (err) {
if (notSent || !isReviewRefused(err)) throw err;
// validate never takes changesNotSentForReview (live: 400 "Unknown name changesNotSentForReview: Cannot bind
// query parameter"); its refusal only tells the dry run how the real run will commit
try { await call('POST', `${API}/edits/${id}:validate`); } catch (err) {
if (!isReviewRefused(err)) throw err;
refused();
await call('POST', `${API}/edits/${id}:validate${withNotSent(undefined, true)}`);
}
log('play', 'dry run: edit validated, discarding');
if (notSent) log('play', `dry run: the real run commits with changesNotSentForReview=true; Play then does not send it for review: Send changes for review in Publishing overview is the owner's button: ${publishingUrl()}`);
@ -116,7 +117,6 @@ export async function playClient(root: string, packageName: string) {
try { await commit(); } catch (err) {
if (notSent || !isReviewRefused(err)) throw err;
refused();
await call('POST', `${API}/edits/${id}:validate${withNotSent(undefined, true)}`);
await commit();
}
openEdit = null;
@ -162,7 +162,7 @@ export async function playClient(root: string, packageName: string) {
* invalidates an open edit ("A change was made to the application outside of this Edit"); then the whole edit is
* redone, up to `retries` times. Keep edits small (e.g. one per language for images) so a retry is cheap.
* Play's 400 "Changes cannot be sent for review automatically" (pitfall 72) is answered on the same edit: validate
* and commit again with changesNotSentForReview=true (a dry run validates that way and says so); the owner then
* and commit again with changesNotSentForReview=true (a dry run says so; validate never takes the parameter); the owner then
* presses Send changes for review in Publishing overview, recorded in .storebound/ for `play status`.
*/
async edit<T>(fn: (id: string, ctl: EditCtl) => Promise<T>, o: EditOpts & { retries?: number } = {}): Promise<T> {