You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 20d61f7
Browse filesBrowse the repository at this point in the historyBrowse files
feat(qa-checklist): resolve provisioning.use against its own area's recipes
`docs/qa/platform-checklist/README.md` recorded option C from #7716 as
deliberately deferred (tracked at #7720, which landed only the documentation
half), "to be revisited if the recipe shape spreads to more areas". It has:
recipes now live in three area files and six items carry a
`provisioning.use`. Until now a `use` naming a key its area does not define
validated clean — measured on 112a8c6, a typo'd `qa-media-constraint` and a
cross-area `qa-contributor-bound-member` each exited 0 with the untouched OK
line. An item that reads as provisioned and is not costs the run the clauses
the recipe was meant to unblock, mid-run and on a live boot.
The resolve mirrors the existing `supersededBy` check — same `err(file, id, …)`
reporting, same "points at unknown" wording — and reuses the trap checker's
did-you-mean, since a typo of a real recipe is the drift shape review is worst
at. It carries its own inline positive-control battery (14 assertions) for the
reason the trap battery does: this gate is not CI-wired, so a check that
quietly stopped firing would restore exactly the green it replaced.
Scope is gap 1 of #10593 only. Cross-area reuse still has no spelling, so the
resolve is same-area only and the unreferenced-recipe direction is deliberately
NOT checked — redding it would settle that convention question by accident.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DdCnBGcHeufjrq7drTD3wt
out.push('"fixtures.provisioning" must carry a non-empty string "use" naming a recipe in this area\'s area-level "fixtures" block — a provisioning block that opts into nothing reads as provisioned and is not');
273
+
returnout;
274
+
}
275
+
constrecipes=newSet(recipeKeys);
276
+
if(recipes.has(p.use))returnout;
277
+
if(recipes.size===0){
278
+
out.push(
279
+
`"fixtures.provisioning.use" names "${p.use}" but this area file has no area-level "fixtures" block to resolve it against`+
280
+
' — write the recipe as a sibling of "area"/"title"/"items" (README "Area-level `fixtures`"), or drop the reference.',
281
+
);
282
+
returnout;
283
+
}
284
+
out.push(
285
+
`"fixtures.provisioning.use" names "${p.use}", which is not a recipe in this area's area-level "fixtures" block${didYouMean(p.use,recipes)}`+
286
+
` — this area offers ${[...recipes].map((k)=>`\`${k}\``).join(', ')}. References are AREA-SCOPED: a recipe another area owns cannot be opted into by key`+
287
+
' (cross-area reuse has no spelling yet, #10593) — cite that recipe by name in this item\'s "knownGaps" instead, and do not fork a second copy.',
288
+
);
289
+
returnout;
290
+
}
291
+
223
292
/**
224
293
* The positive control. Proves the extractor reads a good table AND refuses an
225
294
* empty / renamed / reshaped one, and that the item-side checker catches both
@@ -289,14 +358,64 @@ function selfTestTrapVocabulary() {
289
358
return{ checked, failures };
290
359
}
291
360
361
+
/**
362
+
* The positive control for the provisioning resolve — same shape and the same
363
+
* reason as the trap battery above. This check's entire value is that it
364
+
* FIRES, and the failure it prevents is invisible from the outside: measured
365
+
* on `main` at 112a8c6731, a typo'd `use` (`qa-media-constraint` for
366
+
* `qa-media-constraints`) and a cross-area `use` each validated clean, exit 0,
367
+
* printing the same OK line as an untouched tree. A check that quietly stopped
368
+
* firing would restore exactly that green. Zero I/O — every subject literal.
369
+
*/
370
+
functionselfTestProvisioningUse(){
371
+
constfailures=[];
372
+
letchecked=0;
373
+
constt=(what,ok)=>{
374
+
checked++;
375
+
if(!ok)failures.push(what);
376
+
};
377
+
378
+
constkeys=areaRecipeKeys({fixtures: {$comment: 'what this block is, and the replay rule','qa-scratch-authz': {},'qa-media-constraints': {}}});
379
+
constitem=(use)=>({fixtures: {app: 'showcase',provisioning: { use,why: 'which clauses it unblocks'}}});
380
+
381
+
t('U1 a `use` naming a recipe of this area passes',provisioningProblems(item('qa-scratch-authz'),keys).length===0);
382
+
t('U2 an item whose fixtures carry no provisioning is fine (optional field)',provisioningProblems({fixtures: {app: 'showcase'}},keys).length===0);
383
+
t('U3 an item with no fixtures block at all is fine',provisioningProblems({},keys).length===0);
t('U5 a TYPO of a real recipe is flagged — the drift shape review is worst at',typo.length===1);
390
+
t('U6 the typo message names the recipe that was meant',typo.length===1&&typo[0].includes('did you mean `qa-media-constraints`'));
391
+
t('U7 the message lists the recipes this area does offer',typo.length===1&&typo[0].includes('`qa-scratch-authz`'));
392
+
393
+
t('U8 a recipe key ANOTHER area owns does not resolve here (references are area-scoped)',provisioningProblems(item('qa-contributor-bound-member'),keys).length===1);
394
+
t('U9 `$comment` is an annotation, not a recipe',!keys.includes('$comment')&&provisioningProblems(item('$comment'),keys).length===1);
395
+
t('U10 a whitespace-padded spelling does not resolve',provisioningProblems(item('qa-scratch-authz '),keys).length===1);
396
+
t('U11 a provisioning block with no "use" is flagged',provisioningProblems({fixtures: {provisioning: {why: 'because'}}},keys).length===1);
397
+
t('U12 a non-string "use" is flagged',provisioningProblems({fixtures: {provisioning: {use: 42}}},keys).length===1);
t('U13 an area with NO fixtures block says so, rather than offering an empty list',noBlock.length===1&&noBlock[0].includes('no area-level "fixtures" block'));
401
+
t('U14 a fixtures block holding only annotations exposes zero recipes',areaRecipeKeys({fixtures: {$comment: 'x'}}).length===0);
console.log(`✓ check-platform-checklist --self-test: ${r.checked} assertions — the trap-table extractor reads a good table and REFUSES an empty/renamed/reshaped one.`);
`✓ check-platform-checklist --self-test: ${trap.checked+prov.checked} assertions — the trap-table extractor reads a good table and REFUSES an empty/renamed/reshaped one;`+
413
+
' `fixtures.provisioning.use` resolves against its own area and fires on a dangling one.',
console.error("check-platform-checklist: the provisioning-resolve check's own positive control FAILED — a `use` that resolves to nothing would pass, which is the exact defect this check was added to close.\n");
console.error(`check-platform-checklist: missing ${RUNNER_FILE} — the trap vocabulary lives in its "${TRAP_HEADING}" table and \`traps\` has nothing to validate against.`);
313
441
process.exit(1);
@@ -336,6 +464,8 @@ if (files.length === 0) {
336
464
337
465
constallIds=newMap();// id -> file
338
466
constallItems=[];
467
+
letrecipeTotal=0;// area-level provisioning recipes, across all areas
468
+
letrecipeRefs=0;// item references that resolved to one
339
469
340
470
for(constfileoffiles){
341
471
letdoc;
@@ -353,6 +483,13 @@ for (const file of files) {
353
483
continue;
354
484
}
355
485
486
+
// Area-scoped: the universe a `provisioning.use` resolves against is THIS
487
+
// file's recipe keys, so it is read once here rather than in the post-loop
488
+
// cross-file section where `supersededBy` (whose universe is every id in the
489
+
// ledger) has to live. Same reporting: `err(file, item.id, …)`.
0 commit comments