@@ -285,14 +285,26 @@ export default class Compile extends Command {
285285 // through it; this covers the ones that skip it (a plain object
286286 // default-export, `strict: false`) and would otherwise emit an
287287 // artifact with the key quietly gone. Advisory, never fatal.
288- const unknownKeyFindings = [
288+ //
289+ // [#11643] FORMATTED HERE, once, and consumed by BOTH faces — the
290+ // text block just below and the `--json` payload at the end of this
291+ // command. The findings used to be formatted inside that print
292+ // block, i.e. under `!flags.json`, which made them structurally
293+ // unreachable for the payload: computed, then discarded, for the one
294+ // audience `--json` exists to serve. `os validate --json` had this
295+ // exact defect and fixed it this exact way —
296+ // `.map(formatUnknownAuthoringKey)` at the computation site, beside
297+ // its own `normalized` — so hoisting the formatting rather than
298+ // restating it at the payload is what keeps the two faces from
299+ // reporting different sets. One list cannot drift from itself.
300+ const unknownKeyWarnings = [
289301 ...lintUnknownStackKeys ( normalized as Record < string , unknown > , ObjectStackDefinitionSchema ) ,
290302 ...lintUnknownAuthoringKeys ( normalized as Record < string , unknown > , ObjectStackDefinitionSchema ) ,
291- ] ;
292- if ( unknownKeyFindings . length > 0 && ! flags . json ) {
293- printWarning ( `Undeclared authoring keys (${ unknownKeyFindings . length } ) — dropped at load (#3786)` ) ;
294- for ( const f of unknownKeyFindings . slice ( 0 , 50 ) ) {
295- console . log ( ` • ${ formatUnknownAuthoringKey ( f ) } ` ) ;
303+ ] . map ( formatUnknownAuthoringKey ) ;
304+ if ( unknownKeyWarnings . length > 0 && ! flags . json ) {
305+ printWarning ( `Undeclared authoring keys (${ unknownKeyWarnings . length } ) — dropped at load (#3786)` ) ;
306+ for ( const w of unknownKeyWarnings . slice ( 0 , 50 ) ) {
307+ console . log ( ` • ${ w } ` ) ;
296308 }
297309 }
298310
@@ -464,15 +476,39 @@ export default class Compile extends Command {
464476 // The whole registry's advisory set, in the shape `os validate --json`
465477 // reports. This key used to carry the widget rule's warnings alone —
466478 // one gate out of the twenty-odd that raise them.
467- warnings : ruleAdvisories ,
479+ //
480+ // [#11643] …and then, still, only the RULE advisories: the #3786
481+ // undeclared-authoring-key findings were computed above and dropped,
482+ // so a CI consumer reading `warnings` off this command saw a strictly
483+ // smaller set than the same consumer reading it off
484+ // `os validate --json` on the same tree — missing exactly the "your
485+ // key was dropped at load" members. Two costs, both real: the machine
486+ // faces of two commands disagreed about one class of warning, and
487+ // #11529's truncation notice points the reader at `--json` "for the
488+ // full list", which was true of one advisory list and false of the
489+ // other.
490+ //
491+ // MIXED BY DESIGN, because that is what parity means here. This list
492+ // now carries the rule advisories as RECORDS and the undeclared-key
493+ // findings as formatted STRINGS — byte-for-byte the shape
494+ // `os validate --json` has shipped since it fixed this on its own
495+ // face (`warnings: [...ruleAdvisories, ...docWarnings,
496+ // ...unknownKeyWarnings, …]`, likewise a heterogeneous list). The
497+ // homogeneity this key used to have was not a contract; it was the
498+ // symptom of the omission.
499+ warnings : [ ...ruleAdvisories , ...unknownKeyWarnings ] ,
468500 // [#10678] Body-extraction failures that made a callable fall back to
469- // the legacy .mjs bundle. A SEPARATE key on purpose: `warnings` above
470- // is author-time RULE advisories (`{where,message,rule,path,hint}`)
471- // and is the shape `os validate --json` shares — folding a different
472- // record shape (`{origin,reason}`) into it would break that contract
473- // for every consumer that reads one shape from either command. Empty
474- // array when every callable lowered cleanly, so a CI consumer can read
475- // the key unconditionally.
501+ // the legacy .mjs bundle. A SEPARATE key on purpose, and the reason is
502+ // parity too — the opposite way round from `unknownKeyWarnings` just
503+ // above. `{origin,reason}` extraction records have NO counterpart in
504+ // `os validate --json`: that command lowers no handlers, so there is
505+ // no cross-command list for these to join, and folding a shape only
506+ // ONE command can ever emit into the shared key would teach consumers
507+ // a shape the other command never ships. The undeclared-key findings
508+ // are the mirror case — validate already carries them, in `warnings`,
509+ // so that is where build has to carry them too. Empty array when every
510+ // callable lowered cleanly, so a CI consumer can read the key
511+ // unconditionally.
476512 bodyExtractionWarnings : lowering . bodyExtractionWarnings ,
477513 // Same key `os validate --json` uses, so a CI consumer reads one shape
478514 // from either command rather than learning two.
0 commit comments