@@ -248,12 +248,17 @@ const SELF_TEST_BATTERIES = Object.freeze({
248248 'ts.transpileModule: the quietest of the three' : 4 ,
249249 'the refusal is not swallowable on the new doors either' : 1 ,
250250 'the census names which door each source came through' : 1 ,
251+ 'the fourth door: a synthesis that does not parse is a VERDICT, not a takedown' : 5 ,
252+ 'acceptance: a genuinely unparseable input is still REFUSED at every door' : 4 ,
253+ 'the fourth door is UNREACHABLE for a source the gate could not read' : 7 ,
254+ 'the fourth door parses under the SAME fixed knobs as the three above' : 1 ,
255+ 'the census counts a derived parse as its own door, and a rejection as NOT a refusal' : 1 ,
251256 'the diagnostics reader itself, in-process' : 4 ,
252257} ) ;
253258
254259// DELETING an entry silences that battery's floor exactly as effectively as
255260// zeroing it, so the roster's own size is pinned too.
256- const SELF_TEST_BATTERY_FLOOR = 12 ;
261+ const SELF_TEST_BATTERY_FLOOR = 17 ;
257262
258263// The key an assertion is filed under when no battery is open. It is not a
259264// declared battery, so it reds by the same set difference rather than silently
@@ -1002,7 +1007,8 @@ export function selfTest() {
10021007 ) ;
10031008 t ( 'the census counts parses and distinct file names' ,
10041009 counted . status === 0
1005- && counted . out === '{"parses":3,"programs":0,"transpiles":0,"files":2,"refusals":0}' ,
1010+ && counted . out === '{"parses":3,"programs":0,"transpiles":0,"derived":0,"files":2,'
1011+ + '"refusals":0,"rejections":0}' ,
10061012 JSON . stringify ( counted ) ) ;
10071013
10081014 // -- and the report is armed by the first PARSE, not by the IMPORT. Both
@@ -1125,9 +1131,181 @@ export function selfTest() {
11251131 ) ;
11261132 t ( 'the census counts programs and transpiles as their own doors' ,
11271133 countedAll . status === 0
1128- && countedAll . out === '{"parses":3,"programs":1,"transpiles":1,"files":3,"refusals":0}' ,
1134+ && countedAll . out === '{"parses":3,"programs":1,"transpiles":1,"derived":0,"files":3,'
1135+ + '"refusals":0,"rejections":0}' ,
11291136 JSON . stringify ( countedAll ) ) ;
11301137
1138+ // ⭐⭐ THE FOURTH DOOR. The fixtures are the census's own round trip, spelled
1139+ // the way the census spells it: a receiver type authored across lines, put
1140+ // through `.replace(/\s+/g, ' ')` -- the collapse at
1141+ // `tenant-audit-census.mjs:459` -- and wrapped in the synthetic alias its
1142+ // `:723` builds. Measured 2026-09-18 on TypeScript 6.0.3: the AUTHORED
1143+ // spelling is legal TypeScript (0 diagnostics), the COLLAPSED one is not
1144+ // (1: "';' expected"), and the SEMICOLON spelling is the card's lit control.
1145+ // All three are held here, because only the set of them says the defect is
1146+ // the collapse and not the shape.
1147+ const AUTHORED_RECEIVER = '{\n'
1148+ + ' insert(object: string, data: unknown): Promise<void>\n'
1149+ + ' find(object: string, query: unknown): Promise<void>\n'
1150+ + '}' ;
1151+ const COLLAPSED_RECEIVER = AUTHORED_RECEIVER . replace ( / \s + / g, ' ' ) ;
1152+ const SEMICOLON_RECEIVER = '{ insert(object: string, data: unknown): Promise<void>;'
1153+ + ' find(object: string, query: unknown): Promise<void>; }' ;
1154+ const alias = ( typeText ) => `type CensusReceiver = ${ typeText } ;\n` ;
1155+
1156+ const IMPORT_DERIVED =
1157+ `import { parseDerivedText } from ${ JSON . stringify ( pathToFileURL ( SELF ) . href ) } ;\n` ;
1158+
1159+ // One certified origin, then the derived parse. The origin is a real parse
1160+ // through the real door, so the case exercises the vouch rather than a
1161+ // model of it.
1162+ const derived = ( text , fileName = 'census-receiver-type.ts' ) =>
1163+ run (
1164+ IMPORT_DERIVED
1165+ + `const origin = parseSourceFile('corpus/receiver.ts', 'export const x = 1;\\n');\n`
1166+ + `const r = parseDerivedText(origin, ${ JSON . stringify ( fileName ) } , ${ JSON . stringify ( text ) } );\n`
1167+ + `console.log(JSON.stringify({ tree: r.sourceFile === null ? null : r.sourceFile.statements.length,`
1168+ + ` failure: r.failure === null ? null : { line: r.failure.line, column: r.failure.column,`
1169+ + ` count: r.failure.count, names: r.failure.report.includes('corpus/receiver.ts') } }));\n` ,
1170+ ) ;
1171+
1172+ battery ( 'the fourth door: a synthesis that does not parse is a VERDICT, not a takedown' ) ;
1173+ // The defect, at the door it came in by: the collapsed text still ends the
1174+ // process when it arrives as a TREE to be read. ⛔ This must not change.
1175+ const collapsedThroughRefusal = parse ( alias ( COLLAPSED_RECEIVER ) , 'census-receiver-type.ts' ) ;
1176+ t ( '⛔ the collapsed receiver STILL exits through parseSourceFile — the refusal is untouched' ,
1177+ collapsedThroughRefusal . status === EXIT_UNPARSEABLE && collapsedThroughRefusal . out === '' ,
1178+ JSON . stringify ( { status : collapsedThroughRefusal . status , out : collapsedThroughRefusal . out } ) ) ;
1179+ // …and the same text through the fourth door: the run SURVIVES and the
1180+ // verdict is data the caller can attribute to one site.
1181+ const collapsedDerived = derived ( alias ( COLLAPSED_RECEIVER ) ) ;
1182+ t ( '⭐ the same collapsed receiver through parseDerivedText returns a verdict and the run continues' ,
1183+ collapsedDerived . status === 0
1184+ && JSON . parse ( collapsedDerived . out ) . tree === null
1185+ && JSON . parse ( collapsedDerived . out ) . failure . count === 1
1186+ && JSON . parse ( collapsedDerived . out ) . failure . line === 1 ,
1187+ JSON . stringify ( collapsedDerived ) ) ;
1188+ t ( '…and that verdict names the SOURCE the text was derived from, not only the synthetic name' ,
1189+ collapsedDerived . status === 0 && JSON . parse ( collapsedDerived . out ) . failure . names === true ,
1190+ JSON . stringify ( collapsedDerived ) ) ;
1191+ // LIT CONTROL, twice. The door must be capable of SUCCEEDING on this
1192+ // fixture family, or the case above is indistinguishable from a door that
1193+ // rejects everything.
1194+ const semicolonDerived = derived ( alias ( SEMICOLON_RECEIVER ) ) ;
1195+ t ( '⭐ LIT CONTROL: the semicolon spelling of the SAME literal parses through the same door' ,
1196+ semicolonDerived . status === 0 && JSON . parse ( semicolonDerived . out ) . tree === 1
1197+ && JSON . parse ( semicolonDerived . out ) . failure === null ,
1198+ JSON . stringify ( semicolonDerived ) ) ;
1199+ const authoredDerived = derived ( alias ( AUTHORED_RECEIVER ) ) ;
1200+ t ( '⭐ LIT CONTROL: so does the AUTHORED spelling — the collapse is the defect, not the shape' ,
1201+ authoredDerived . status === 0 && JSON . parse ( authoredDerived . out ) . tree === 1
1202+ && JSON . parse ( authoredDerived . out ) . failure === null ,
1203+ JSON . stringify ( authoredDerived ) ) ;
1204+
1205+ battery ( 'acceptance: a genuinely unparseable input is still REFUSED at every door' ) ;
1206+ // Without this leg, "the round trip is fixed" and "the parse check was
1207+ // switched off" are the same green.
1208+ for ( const [ name , text ] of [
1209+ [ 'merge-conflict markers' , CONFLICTED ] ,
1210+ [ 'a truncated body' , TRUNCATED ] ,
1211+ ] ) {
1212+ const viaRefusal = parse ( text , 'packages/foo/src/bar.ts' ) ;
1213+ t ( `⛔ ${ name } : parseSourceFile still ends the run` ,
1214+ viaRefusal . status === EXIT_UNPARSEABLE && viaRefusal . out === '' ,
1215+ JSON . stringify ( { status : viaRefusal . status , out : viaRefusal . out } ) ) ;
1216+ // At the fourth door the refusal is not an exit -- so what has to hold is
1217+ // that NO TREE comes back. A caller cannot walk recovered wreckage and
1218+ // cannot read the result as "nothing to report".
1219+ const viaDerived = derived ( text , 'synthesised.ts' ) ;
1220+ t ( `⛔ ${ name } : parseDerivedText hands back NO TREE, so there is nothing to score clean` ,
1221+ viaDerived . status === 0 && JSON . parse ( viaDerived . out ) . tree === null
1222+ && JSON . parse ( viaDerived . out ) . failure . count >= 1 ,
1223+ JSON . stringify ( viaDerived ) ) ;
1224+ }
1225+
1226+ battery ( 'the fourth door is UNREACHABLE for a source the gate could not read' ) ;
1227+ // The floor, by construction: the only way to hold a certified origin is a
1228+ // door that exits on an unparseable source. An uncertified one is refused.
1229+ const misuse = ( originExpr ) =>
1230+ run (
1231+ IMPORT_DERIVED
1232+ + `const r = parseDerivedText(${ originExpr } , 'derived.ts', 'const a = 1;\\n');\n`
1233+ + `console.log('RETURNED ' + JSON.stringify(r.failure));\n` ,
1234+ ) ;
1235+ const rawOrigin = misuse (
1236+ `ts.createSourceFile('raw.ts', 'export const a = 1;\\n', ts.ScriptTarget.Latest, true)` ,
1237+ ) ;
1238+ t ( '⛔ a tree this module never certified is REFUSED, however well it parses' ,
1239+ rawOrigin . status === EXIT_DERIVED_MISUSE && rawOrigin . out === '' ,
1240+ JSON . stringify ( { status : rawOrigin . status , out : rawOrigin . out } ) ) ;
1241+ t ( '…and that refusal says which door to use instead' ,
1242+ rawOrigin . err . includes ( 'parseSourceFile' ) , rawOrigin . err . slice ( 0 , 400 ) ) ;
1243+ for ( const [ name , expr ] of [ [ 'null' , 'null' ] , [ 'undefined' , 'undefined' ] , [ 'a string' , `'not a tree'` ] ] ) {
1244+ const bad = misuse ( expr ) ;
1245+ t ( `⛔ an origin that is ${ name } is refused rather than throwing a TypeError` ,
1246+ bad . status === EXIT_DERIVED_MISUSE , JSON . stringify ( { status : bad . status , err : bad . err . slice ( 0 , 120 ) } ) ) ;
1247+ }
1248+ const swallowedMisuse = run (
1249+ IMPORT_DERIVED
1250+ + `let caught = false;\n`
1251+ + `try { parseDerivedText(null, 'derived.ts', 'const a = 1;\\n'); } catch { caught = true; }\n`
1252+ + `console.log(caught ? 'SWALLOWED' : 'NOT REACHED');\n` ,
1253+ ) ;
1254+ t ( '⛔ and a caller’s try/catch cannot downgrade the misuse refusal either' ,
1255+ swallowedMisuse . status === EXIT_DERIVED_MISUSE && ! swallowedMisuse . out . includes ( 'SWALLOWED' ) ,
1256+ JSON . stringify ( swallowedMisuse ) ) ;
1257+ // The other certifying door. A gate that reads its corpus through a Program
1258+ // can synthesise from it on the same terms.
1259+ const programOrigin = run (
1260+ IMPORT_DERIVED
1261+ + `import { createProgramChecked } from ${ JSON . stringify ( pathToFileURL ( SELF ) . href ) } ;\n`
1262+ + `const p = createProgramChecked(${ JSON . stringify ( [ okEntry ] ) } , ${ PROGRAM_OPTIONS } );\n`
1263+ + `const origin = p.getSourceFiles().find((f) => f.fileName === ${ JSON . stringify ( okEntry ) } );\n`
1264+ + `const r = parseDerivedText(origin, 'from-program.ts', 'const a = 1;\\n');\n`
1265+ + `console.log('DERIVED ' + (r.failure === null));\n` ,
1266+ ) ;
1267+ t ( '⭐ a source file a checked Program was built over IS certified' ,
1268+ programOrigin . status === 0 && programOrigin . out === 'DERIVED true' ,
1269+ JSON . stringify ( programOrigin ) ) ;
1270+
1271+ battery ( 'the fourth door parses under the SAME fixed knobs as the three above' ) ;
1272+ // Two doors, one source, the knobs compared rather than asserted in prose:
1273+ // a drift in either would leave the header's "the knobs that are NOT knobs"
1274+ // section describing one door only.
1275+ const knobs = run (
1276+ IMPORT_DERIVED
1277+ + `const origin = parseSourceFile('corpus/a.ts', 'export const x = 1;\\n');\n`
1278+ + `const direct = parseSourceFile('b.ts', 'export const y = 2;\\n');\n`
1279+ + `const { sourceFile: fromDerived } = parseDerivedText(origin, 'c.ts', 'export const z = 3;\\n');\n`
1280+ + `console.log(JSON.stringify({\n`
1281+ + ` sameTarget: direct.languageVersion === fromDerived.languageVersion,\n`
1282+ + ` latest: fromDerived.languageVersion === ts.ScriptTarget.Latest,\n`
1283+ + ` parentsDirect: direct.statements[0].parent !== undefined,\n`
1284+ + ` parentsDerived: fromDerived.statements[0].parent !== undefined,\n`
1285+ + `}));\n` ,
1286+ ) ;
1287+ t ( 'the derived tree carries the same ScriptTarget and has its parents set, exactly as a direct parse' ,
1288+ knobs . status === 0
1289+ && knobs . out === '{"sameTarget":true,"latest":true,"parentsDirect":true,"parentsDerived":true}' ,
1290+ JSON . stringify ( knobs ) ) ;
1291+
1292+ battery ( 'the census counts a derived parse as its own door, and a rejection as NOT a refusal' ) ;
1293+ // The numerator claim: a rejection left the run going, so conflating it
1294+ // with a refusal would make the one report that says how much was measured
1295+ // say it wrong.
1296+ const countedDerived = run (
1297+ IMPORT_DERIVED
1298+ + `const origin = parseSourceFile('corpus/a.ts', 'export const x = 1;\\n');\n`
1299+ + `parseDerivedText(origin, 'ok.ts', 'const a = 1;\\n');\n`
1300+ + `parseDerivedText(origin, 'bad.ts', ${ JSON . stringify ( alias ( COLLAPSED_RECEIVER ) ) } );\n`
1301+ + `console.log(JSON.stringify(parseCensus()));\n` ,
1302+ ) ;
1303+ t ( 'two derived parses, one rejected: counted as derived, and the run’s refusals stay 0' ,
1304+ countedDerived . status === 0
1305+ && countedDerived . out === '{"parses":3,"programs":0,"transpiles":0,"derived":2,"files":3,'
1306+ + '"refusals":0,"rejections":1}' ,
1307+ JSON . stringify ( countedDerived ) ) ;
1308+
11311309 // -- the diagnostics reader itself, in-process ---------------------------
11321310 battery ( 'the diagnostics reader itself, in-process' ) ;
11331311 t ( 'describeDiagnostics answers [] for a tree that parsed' ,
@@ -1155,7 +1333,10 @@ export function selfTest() {
11551333 console . log (
11561334 `✓ ts-parse self-test: ${ cases . length } cases pass (every measured wreck refuses and names its file, `
11571335 + `across all three parser entry points, both ScriptKind directions, a Program's transitive import `
1158- + `included, and a caller’s try/catch cannot swallow any of it).` ,
1336+ + `included, and a caller’s try/catch cannot swallow any of it -- plus the fourth door, where text `
1337+ + `THIS process synthesised returns its verdict instead of ending the run, reachable only from an `
1338+ + `origin one of those three certified, handing back no tree when it fails, and with the census `
1339+ + `counting a rejection as a run that continued rather than as a refusal).` ,
11591340 ) ;
11601341 selfTestReachedVerdict = true ;
11611342 return 0 ;
0 commit comments