carrick 0.3.123 → 0.3.125

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -115,6 +115,12 @@ export const MACHINERY_MEMBER_INDICATORS = new Set([
115
115
  'clone',
116
116
  'json',
117
117
  'statusText',
118
+ // FROZEN (old path, 2026-10-21): the reply-object half below only (the
119
+ // fetch half above and the threshold stay). Deletions and safety fixes
120
+ // only. Fixes for wrong or missing types go in labelled reply objects
121
+ // (carrick#2472 C4) and the language library rows (carrick#2445). Delete
122
+ // when a delete-and-replay on the four repos moves no row wrongly, after
123
+ // carrick-cloud#2379 and carrick#2445 (carrick#2472, C6).
118
124
  // Node http ServerResponse / reply-object surface
119
125
  'statusCode',
120
126
  'statusMessage',
@@ -1628,6 +1634,8 @@ export class TypeInferrer {
1628
1634
  branches = [
1629
1635
  ...returnedBranches,
1630
1636
  ...statementSends
1637
+ // FROZEN (old path, 2026-10-21): the `statementEndsFunctionPath` test
1638
+ // here; see that method.
1631
1639
  .filter((s) => s.send === locatedBranch || this.statementEndsFunctionPath(s.statement, func))
1632
1640
  .map((s) => s.send),
1633
1641
  ];
@@ -1702,6 +1710,10 @@ export class TypeInferrer {
1702
1710
  }
1703
1711
  return sends;
1704
1712
  }
1713
+ // FROZEN (old path, 2026-10-21): this method and its one use. Deletions and
1714
+ // safety fixes only. Fixes for wrong or missing types go in send versus
1715
+ // setter, read from the framework label (carrick#2472 C4). Delete when C4 is
1716
+ // released (carrick#2472, C6).
1705
1717
  /**
1706
1718
  * True when nothing in `func` runs after `statement`: a bare `return;`
1707
1719
  * follows it, or it is the last statement of its block and the statement
@@ -1748,6 +1760,11 @@ export class TypeInferrer {
1748
1760
  return false;
1749
1761
  }
1750
1762
  }
1763
+ // FROZEN (old path, 2026-10-21): this method's structural test (the whole
1764
+ // method is the upper bound). Deletions and safety fixes only. Fixes for
1765
+ // wrong or missing types go in labelled sends (carrick#2472 C4). Delete when
1766
+ // a delete-and-replay on the four repos moves no row wrongly, after
1767
+ // carrick-cloud#2379 and carrick#2445 (carrick#2472, C6).
1751
1768
  /**
1752
1769
  * True when `candidate` is a response send: a call or `new` whose result is
1753
1770
  * transport machinery (by the extraction config's verified rule or the
@@ -1979,6 +1996,27 @@ export class TypeInferrer {
1979
1996
  });
1980
1997
  }
1981
1998
  typeString = this.unwrapPromise(typeString, returnType);
1999
+ // carrick#2594: the call's value is untyped (`Promise<any>`,
2000
+ // `Promise<unknown>`), and the function it is made in states its type:
2001
+ // it returns the value from a function with a declared return type, holds
2002
+ // it in an annotated name, validates it with a schema, narrows it with a
2003
+ // function that returns it unchanged, casts it whole, or hands it to a
2004
+ // typed parameter. A function that converts the value into something new
2005
+ // states nothing about it, and neither does a use on a failure path.
2006
+ const untypedAnswer = typeString.trim() === 'any' || typeString.trim() === 'unknown';
2007
+ if (untypedAnswer && func && !explicitType && !unwrapResult.wasUnwrapped && !carrierPayload) {
2008
+ const stated = this.statedInFunction(callExpr, func, {
2009
+ returned: true,
2010
+ parameters: true,
2011
+ skipFailurePaths: true,
2012
+ });
2013
+ if (stated) {
2014
+ this.log(`Call result at ${where} is untyped, and its function states its type ` +
2015
+ `(${stated.how}); publishing that type`);
2016
+ typeString = stated.text;
2017
+ isExplicit = true;
2018
+ }
2019
+ }
1982
2020
  // #336: consumer analogue of the #306 producer anchor. Anchor on the
1983
2021
  // CALL's own payload (the rule-extracted Type, else the awaited call
1984
2022
  // type), peeling array levels to the element symbol + depth. Without the
@@ -2642,18 +2680,22 @@ export class TypeInferrer {
2642
2680
  isExplicit = true;
2643
2681
  }
2644
2682
  // carrick#1166: the read itself is untyped (`await c.req.json()` is `any`
2645
- // or `unknown`), and the handler validates it by handing it to a schema:
2646
- // `Schema.safeParse(body)`, `Schema.parse(await c.req.json())`. The schema
2647
- // declares what a caller may send, so its INPUT is the request contract,
2648
- // read the way every other request anchor reads a schema (carrick#1101).
2683
+ // or `unknown`), and the handler states its type further on: it validates
2684
+ // it with a schema (`Schema.safeParse(body ?? {})`, whose INPUT is the
2685
+ // request contract, carrick#1101), assigns it to an annotated name
2686
+ // (`let body: T; body = await c.req.json()`), narrows it with a function
2687
+ // that returns it unchanged, or casts the whole value (carrick#2594).
2649
2688
  const readType = this.unwrapPromiseType(payloadType);
2650
2689
  if (!explicitType &&
2651
2690
  !unwrapResult.wasUnwrapped &&
2652
2691
  (readType.isAny() || readType.isUnknown())) {
2653
- const parsedWith = this.schemaConsumingRead(node);
2692
+ const func = this.findContainingFunctionForNode(node);
2693
+ const parsedWith = func
2694
+ ? this.statedInFunction(node, func, { returned: false, parameters: false, skipFailurePaths: false })
2695
+ : null;
2654
2696
  if (parsedWith) {
2655
2697
  this.log(`Request locator at ${request.file_path}:${request.line_number} is an untyped read ` +
2656
- "the handler validates with a schema; publishing the schema's input");
2698
+ `its handler states a type for (${parsedWith.how}); publishing that type`);
2657
2699
  return this.declaredRequestInferredType(request, parsedWith, this.getNodeLocation(node));
2658
2700
  }
2659
2701
  }
@@ -5288,6 +5330,10 @@ export class TypeInferrer {
5288
5330
  }
5289
5331
  return flattened;
5290
5332
  }
5333
+ // FROZEN (old path, 2026-10-21): this method only. Deletions and safety fixes
5334
+ // only. Fixes for wrong or missing types go in labelled sends (carrick#2472
5335
+ // C4). Delete when a delete-and-replay on the four repos moves no row
5336
+ // wrongly, after carrick-cloud#2379 and carrick#2445 (carrick#2472, C6).
5291
5337
  /**
5292
5338
  * The callees these returned expressions PROVE are response serialisers.
5293
5339
  *
@@ -6585,8 +6631,10 @@ export class TypeInferrer {
6585
6631
  * - a cast on it: `(await request.json()) as NewWidget`;
6586
6632
  * - the annotation of the binding it initialises:
6587
6633
  * `const input: NewWidget = await request.json()`;
6588
- * - a schema the read, or the binding that holds it, is handed to
6589
- * (`schemaConsumingRead`): the schema's input is what a caller may send.
6634
+ * - a type the handler states for the read further on
6635
+ * (`statedInFunction`): a schema it is handed to, the annotation of a
6636
+ * name it is assigned to, a pass-through function, a cast of the whole
6637
+ * value.
6590
6638
  *
6591
6639
  * The first two are `typeNodeStatedAtRead`, the reading a consumer's
6592
6640
  * response read already gets. An annotation further out is not one of them:
@@ -6602,10 +6650,13 @@ export class TypeInferrer {
6602
6650
  'reads off the platform request, as the source types that read');
6603
6651
  return this.createInferredType(request, statedText, true, this.getNodeLocation(read));
6604
6652
  }
6605
- const validated = this.schemaConsumingRead(read);
6653
+ const func = this.findContainingFunctionForNode(read);
6654
+ const validated = func
6655
+ ? this.statedInFunction(read, func, { returned: false, parameters: false, skipFailurePaths: false })
6656
+ : null;
6606
6657
  if (validated) {
6607
6658
  this.log(`Request at ${request.file_path}:${request.line_number} is the body its handler ` +
6608
- "reads off the platform request and validates with a schema; publishing the schema's input");
6659
+ `reads off the platform request and states a type for (${validated.how}); publishing that type`);
6609
6660
  return this.declaredRequestInferredType(request, validated, this.getNodeLocation(read));
6610
6661
  }
6611
6662
  return null;
@@ -7264,70 +7315,280 @@ export class TypeInferrer {
7264
7315
  return values;
7265
7316
  }
7266
7317
  /**
7267
- * The contract of the schema a handler validates an untyped body read with
7268
- * (carrick#1166), or null.
7269
- *
7270
- * `read` is the located expression: the read call, or the binding that holds
7271
- * it. Every call in the enclosing function that takes that value as an
7272
- * argument is a candidate, and the schema is the call's receiver
7273
- * (`Schema.safeParse(body)`) or one of its other arguments
7274
- * (`parse(Schema, body)`). A candidate counts only when it declares schema
7275
- * types (`schemaOutputType` / `schemaInputType`), so a logger or a service
7276
- * call the body is also handed to is never read as a contract. No method or
7277
- * library name is matched.
7278
- */
7279
- schemaConsumingRead(read) {
7280
- const func = this.findContainingFunctionForNode(read);
7281
- if (!func)
7282
- return null;
7283
- const bindings = new Set();
7284
- const addBinding = (identifier) => {
7285
- const symbol = identifier?.getSymbol()?.compilerSymbol;
7286
- if (symbol)
7287
- bindings.add(symbol);
7318
+ * The type the function a value is made in states for it, where the
7319
+ * compiler types the value itself `any` or `unknown` (carrick#2594), or
7320
+ * null.
7321
+ *
7322
+ * `value` is the located expression: a body read, or a call whose result is
7323
+ * untyped. The value is followed through the names that hold it: a
7324
+ * declaration it initialises, and a name it is assigned to later
7325
+ * (`let body: unknown; body = await read()`). At each place the value or
7326
+ * one of those names is used whole, the source can state a type:
7327
+ *
7328
+ * 0. the annotation of the name that holds it (`let body: Upload`), or the
7329
+ * declared return type of the function that returns it unchanged (only
7330
+ * with `reads.returned`);
7331
+ * 1. a schema it is handed to (`Schema.safeParse(body ?? {})`,
7332
+ * `parse(Schema, body)`): the schema's input is what a sender may send
7333
+ * (carrick#1166). A candidate counts only when it declares schema types,
7334
+ * so no method or library name is matched;
7335
+ * 2. a function of the project it is handed to that returns that argument
7336
+ * unchanged (through casts) or an empty default: its declared return
7337
+ * type. A function that builds a new value from its argument is a
7338
+ * converter, and its return type describes what it built, never the
7339
+ * value it was handed, so it states nothing here;
7340
+ * 3. a cast of the whole value (`(body as { phone: string }).phone`);
7341
+ * 4. the parameter of a call it is handed to (only with
7342
+ * `reads.parameters`), where that parameter's type is a body shape once
7343
+ * any callable member is dropped (a function never crosses the wire).
7344
+ *
7345
+ * The first rank with an answer decides. A placeholder (`unknown`,
7346
+ * `Record<string, unknown>`, any type node with `any` or `unknown` written
7347
+ * in it) states nothing, and two different statements in one rank decide
7348
+ * nothing: the answer is null.
7349
+ */
7350
+ statedInFunction(value, func, reads) {
7351
+ const holders = new Set();
7352
+ const positions = [value];
7353
+ const holderUse = (node) => {
7354
+ if (!Node.isIdentifier(node))
7355
+ return false;
7356
+ const symbol = node.getSymbol()?.compilerSymbol;
7357
+ return !!symbol && holders.has(symbol);
7288
7358
  };
7289
- if (Node.isIdentifier(read)) {
7290
- addBinding(read);
7291
- }
7292
- else {
7293
- const declaration = read.getFirstAncestorByKind(SyntaxKind.VariableDeclaration);
7294
- const initializer = declaration?.getInitializer();
7295
- if (declaration &&
7296
- initializer &&
7297
- this.unwrapExpressionNode(initializer) === read &&
7298
- Node.isIdentifier(declaration.getNameNode())) {
7299
- addBinding(declaration.getNameNode());
7300
- }
7301
- }
7302
- const carriesRead = (argument) => argument === read ||
7303
- (Node.isIdentifier(argument) &&
7304
- bindings.has(argument.getSymbol()?.compilerSymbol));
7305
- for (const call of func.getDescendantsOfKind(SyntaxKind.CallExpression)) {
7306
- const args = call.getArguments().map((arg) => this.unwrapExpressionNode(arg));
7307
- const carrier = args.findIndex(carriesRead);
7308
- if (carrier < 0)
7359
+ const hold = (name) => {
7360
+ if (!Node.isIdentifier(name))
7361
+ return;
7362
+ const symbol = name.getSymbol()?.compilerSymbol;
7363
+ if (!symbol || holders.has(symbol))
7364
+ return;
7365
+ holders.add(symbol);
7366
+ for (const id of func.getDescendantsOfKind(SyntaxKind.Identifier)) {
7367
+ if (id === name || id.getSymbol()?.compilerSymbol !== symbol)
7368
+ continue;
7369
+ const parent = id.getParent();
7370
+ if (Node.isVariableDeclaration(parent) && parent.getNameNode() === id)
7371
+ continue;
7372
+ if (Node.isBinaryExpression(parent) &&
7373
+ parent.getLeft() === id &&
7374
+ parent.getOperatorToken().getKind() === SyntaxKind.EqualsToken) {
7375
+ continue;
7376
+ }
7377
+ positions.push(id);
7378
+ }
7379
+ };
7380
+ hold(value);
7381
+ const found = [];
7382
+ const stateNode = (rank, typeNode, at, how) => {
7383
+ if (!typeNode || this.leavesAPositionOpen(typeNode))
7384
+ return;
7385
+ const text = this.structuralTextFromTypeNode(typeNode);
7386
+ if (text)
7387
+ found.push({ rank, text, at, how });
7388
+ };
7389
+ for (let i = 0; i < positions.length; i++) {
7390
+ const position = positions[i];
7391
+ if (reads.skipFailurePaths &&
7392
+ position !== value &&
7393
+ reachedOnlyOnFailure(position, func, holderUse)) {
7309
7394
  continue;
7310
- const candidates = [];
7311
- const callee = call.getExpression();
7312
- if (Node.isPropertyAccessExpression(callee)) {
7313
- candidates.push(callee.getExpression());
7314
7395
  }
7315
- args.forEach((arg, index) => {
7316
- if (index !== carrier)
7317
- candidates.push(arg);
7318
- });
7319
- for (const candidate of candidates) {
7320
- const candidateType = candidate.getType();
7321
- if (!this.schemaOutputType(candidateType, candidate) &&
7322
- !this.schemaInputType(candidateType, candidate)) {
7396
+ // Climb the wrappers that leave the value what it was: parentheses,
7397
+ // `await`, `!`, `satisfies`, a placeholder cast, and an empty default
7398
+ // (`body ?? {}`).
7399
+ let outer = position;
7400
+ for (;;) {
7401
+ const parent = outer.getParent();
7402
+ if (!parent)
7403
+ break;
7404
+ if ((Node.isParenthesizedExpression(parent) ||
7405
+ Node.isAwaitExpression(parent) ||
7406
+ Node.isNonNullExpression(parent) ||
7407
+ Node.isSatisfiesExpression(parent)) &&
7408
+ parent.getExpression() === outer) {
7409
+ outer = parent;
7410
+ continue;
7411
+ }
7412
+ if ((Node.isAsExpression(parent) || Node.isTypeAssertion(parent)) &&
7413
+ parent.getExpression() === outer) {
7414
+ const typeNode = parent.getTypeNode();
7415
+ if (typeNode && !this.leavesAPositionOpen(typeNode)) {
7416
+ stateNode(3, typeNode, parent, 'a cast of the whole value');
7417
+ break;
7418
+ }
7419
+ outer = parent;
7323
7420
  continue;
7324
7421
  }
7325
- const contract = this.schemaContract(candidate, 'input');
7326
- if (contract)
7327
- return contract;
7422
+ if (Node.isBinaryExpression(parent) &&
7423
+ parent.getLeft() === outer &&
7424
+ [SyntaxKind.QuestionQuestionToken, SyntaxKind.BarBarToken].includes(parent.getOperatorToken().getKind()) &&
7425
+ this.isEmptyDefault(parent.getRight())) {
7426
+ outer = parent;
7427
+ continue;
7428
+ }
7429
+ break;
7430
+ }
7431
+ const parent = outer.getParent();
7432
+ if (!parent)
7433
+ continue;
7434
+ if (Node.isVariableDeclaration(parent) && parent.getInitializer() === outer) {
7435
+ stateNode(0, parent.getTypeNode(), parent, 'the annotation of the name that holds it');
7436
+ hold(parent.getNameNode());
7437
+ continue;
7438
+ }
7439
+ if (Node.isBinaryExpression(parent) &&
7440
+ parent.getRight() === outer &&
7441
+ parent.getOperatorToken().getKind() === SyntaxKind.EqualsToken &&
7442
+ Node.isIdentifier(parent.getLeft())) {
7443
+ const declaration = parent.getLeft().getSymbol()?.getValueDeclaration();
7444
+ if (declaration && Node.isVariableDeclaration(declaration)) {
7445
+ stateNode(0, declaration.getTypeNode(), declaration, 'the annotation of the name it is assigned to');
7446
+ }
7447
+ hold(parent.getLeft());
7448
+ continue;
7449
+ }
7450
+ if (reads.returned) {
7451
+ const returnedHere = (Node.isReturnStatement(parent) &&
7452
+ parent.getExpression() === outer &&
7453
+ this.findContainingFunctionForNode(parent) === func) ||
7454
+ (Node.isArrowFunction(parent) && parent === func && parent.getBody() === outer);
7455
+ if (returnedHere) {
7456
+ stateNode(0, func.getReturnTypeNode(), func, 'the declared return type of the function that returns it');
7457
+ continue;
7458
+ }
7459
+ }
7460
+ if (Node.isCallExpression(parent) && parent.getArguments().includes(outer)) {
7461
+ this.stateAtReceivingCall(parent, parent.getArguments().indexOf(outer), reads, found, stateNode);
7328
7462
  }
7329
7463
  }
7330
- return null;
7464
+ if (found.length === 0)
7465
+ return null;
7466
+ const rank = Math.min(...found.map((entry) => entry.rank));
7467
+ const decided = found.filter((entry) => entry.rank === rank);
7468
+ if (new Set(decided.map((entry) => entry.text)).size !== 1) {
7469
+ this.log(`A value at ${this.getNodeLocation(value).file_path}:${this.getNodeLocation(value).start_line} ` +
7470
+ `is stated as ${decided.length} different types in its function; stating none`);
7471
+ return null;
7472
+ }
7473
+ const [first] = decided;
7474
+ return { text: first.text, provenance: first.provenance, at: first.at, how: first.how };
7475
+ }
7476
+ /**
7477
+ * What a call that takes the value at argument `index` states for it:
7478
+ * a schema among its receiver and other arguments (rank 1), a callee of the
7479
+ * project that returns that argument unchanged (rank 2), or, with
7480
+ * `reads.parameters`, the parameter's own type (rank 4).
7481
+ */
7482
+ stateAtReceivingCall(call, index, reads, found, stateNode) {
7483
+ const schemaCandidates = [];
7484
+ const callee = call.getExpression();
7485
+ if (Node.isPropertyAccessExpression(callee))
7486
+ schemaCandidates.push(callee.getExpression());
7487
+ call.getArguments().forEach((argument, at) => {
7488
+ if (at !== index)
7489
+ schemaCandidates.push(this.unwrapExpressionNode(argument));
7490
+ });
7491
+ for (const candidate of schemaCandidates) {
7492
+ const candidateType = candidate.getType();
7493
+ if (!this.schemaOutputType(candidateType, candidate) &&
7494
+ !this.schemaInputType(candidateType, candidate)) {
7495
+ continue;
7496
+ }
7497
+ const contract = this.schemaContract(candidate, 'input');
7498
+ if (contract) {
7499
+ found.push({ rank: 1, text: contract.text, provenance: contract.provenance, at: call, how: 'a schema it is handed to' });
7500
+ return;
7501
+ }
7502
+ }
7503
+ const passThrough = this.passThroughFunction(call, index);
7504
+ if (passThrough) {
7505
+ stateNode(2, passThrough.getReturnTypeNode(), call, 'a function that returns it unchanged');
7506
+ return;
7507
+ }
7508
+ if (!reads.parameters)
7509
+ return;
7510
+ const signature = this.project.getTypeChecker().getResolvedSignature(call);
7511
+ const parameter = signature?.getParameters()[index];
7512
+ const declaration = parameter?.getValueDeclaration();
7513
+ if (!parameter || (declaration && Node.isParameterDeclaration(declaration) && declaration.isRestParameter())) {
7514
+ return;
7515
+ }
7516
+ let parameterType = parameter.getTypeAtLocation(call);
7517
+ if (parameterType.isUnion()) {
7518
+ const bodies = parameterType.getUnionTypes().filter((part) => !this.isCallableType(part));
7519
+ if (bodies.length !== 1)
7520
+ return;
7521
+ parameterType = bodies[0];
7522
+ }
7523
+ if (!this.isObjectShape(parameterType))
7524
+ return;
7525
+ const text = this.structuralTextFromType(parameterType, call);
7526
+ if (!text || /\b(any|unknown)\b/.test(text))
7527
+ return;
7528
+ found.push({ rank: 4, text, at: call, how: 'the parameter of a call it is handed to' });
7529
+ }
7530
+ /**
7531
+ * The project function `call` invokes, when every value it returns is its
7532
+ * argument at `index` as it was handed in (through parentheses, casts,
7533
+ * `satisfies` and `!`) or an empty default (`{}`, `[]`, `null`,
7534
+ * `undefined`), and at least one return is that argument. Such a function
7535
+ * narrows its argument, so its declared return type describes the value it
7536
+ * was handed. Any other function builds something new from it, and is not
7537
+ * this.
7538
+ */
7539
+ passThroughFunction(call, index) {
7540
+ let symbol = call.getExpression().getSymbol();
7541
+ if (symbol?.isAlias())
7542
+ symbol = symbol.getAliasedSymbol() ?? symbol;
7543
+ for (const declaration of symbol?.getDeclarations() ?? []) {
7544
+ const fn = Node.isFunctionDeclaration(declaration) || Node.isMethodDeclaration(declaration)
7545
+ ? declaration
7546
+ : Node.isVariableDeclaration(declaration)
7547
+ ? this.unwrapExpressionNode(declaration.getInitializer())
7548
+ : undefined;
7549
+ if (!fn ||
7550
+ !(Node.isFunctionDeclaration(fn) ||
7551
+ Node.isMethodDeclaration(fn) ||
7552
+ Node.isArrowFunction(fn) ||
7553
+ Node.isFunctionExpression(fn)) ||
7554
+ !fn.getBody() ||
7555
+ fn.getSourceFile().isDeclarationFile() ||
7556
+ fn.getSourceFile().isInNodeModules()) {
7557
+ continue;
7558
+ }
7559
+ const parameter = fn.getParameters()[index];
7560
+ const parameterSymbol = parameter?.getNameNode().getSymbol()?.compilerSymbol;
7561
+ if (!parameter || parameter.isRestParameter() || !parameterSymbol)
7562
+ continue;
7563
+ const returned = this.returnedExpressions(fn);
7564
+ let returnsArgument = false;
7565
+ const everyReturnPasses = returned.every((expression) => {
7566
+ let node = expression;
7567
+ while (Node.isParenthesizedExpression(node) ||
7568
+ Node.isAsExpression(node) ||
7569
+ Node.isTypeAssertion(node) ||
7570
+ Node.isSatisfiesExpression(node) ||
7571
+ Node.isNonNullExpression(node)) {
7572
+ node = node.getExpression();
7573
+ }
7574
+ if (Node.isIdentifier(node) && node.getSymbol()?.compilerSymbol === parameterSymbol) {
7575
+ returnsArgument = true;
7576
+ return true;
7577
+ }
7578
+ return this.isEmptyDefault(node);
7579
+ });
7580
+ if (everyReturnPasses && returnsArgument)
7581
+ return fn;
7582
+ }
7583
+ return undefined;
7584
+ }
7585
+ /** `{}`, `[]`, `null` or `undefined`: a default that states no member. */
7586
+ isEmptyDefault(node) {
7587
+ const bare = this.unwrapExpressionNode(node);
7588
+ return ((Node.isObjectLiteralExpression(bare) && bare.getProperties().length === 0) ||
7589
+ (Node.isArrayLiteralExpression(bare) && bare.getElements().length === 0) ||
7590
+ Node.isNullLiteral(bare) ||
7591
+ (Node.isIdentifier(bare) && bare.getText() === 'undefined'));
7331
7592
  }
7332
7593
  /**
7333
7594
  * The part a validated read names when that part is not a body, or
@@ -411,6 +411,18 @@ export interface Yields {
411
411
  export: string;
412
412
  side: string;
413
413
  }
414
+ /**
415
+ * Where a route handler's request holds its path and method (#2474 R0): the
416
+ * handler's parameter `param` is the request, and each of the others is a
417
+ * member path read off it, relative to that parameter (`['path']`,
418
+ * `['raw', 'url']`). At least one of `path` or `url`.
419
+ */
420
+ export interface RequestAt {
421
+ param: number;
422
+ path?: string[];
423
+ url?: string[];
424
+ method?: string[];
425
+ }
414
426
  /** The closed role list; the role alone picks the checks a claim needs. */
415
427
  export type LibraryRole = 'http_client' | 'graphql_client' | 'broker' | 'in_process_bus' | 'socket' | 'server_framework' | 'none';
416
428
  /** What an `op` claim does on the wire. */
@@ -467,6 +479,15 @@ export type LibraryClaim = {
467
479
  key_labels?: Record<string, KeyLabel>;
468
480
  name_scope?: NameScope;
469
481
  picker?: string;
482
+ /**
483
+ * Route fields (#2474 R0): the member runs the function here and hands
484
+ * it, as `yields` says, an instance bound under `name`. Comes with
485
+ * `yields`.
486
+ */
487
+ runs?: ClaimSlot;
488
+ yields?: Yields;
489
+ /** Route fields (#2474 R0): the instance handed here is served under `name`. */
490
+ mounts?: ClaimSlot;
470
491
  } | {
471
492
  kind: 'op';
472
493
  op: LibraryOp;
@@ -502,6 +523,8 @@ export type LibraryClaim = {
502
523
  runs?: RunsSlot;
503
524
  /** Label v2, `receive` only: what the handler is handed. */
504
525
  yields?: Yields;
526
+ /** Route fields (#2474 R0), `receive` only: where the handler's request holds its path and method. */
527
+ request?: RequestAt;
505
528
  } | {
506
529
  /** A name the library emits itself, spelled in one of `member`'s parameters. */
507
530
  kind: 'reserved';
@@ -538,6 +561,8 @@ export interface LibraryCheck {
538
561
  export interface VerifyLibraryClaimsRequest extends BaseRequest {
539
562
  action: 'verify_library_claims';
540
563
  from_dir: string;
564
+ /** The scanned repo's root, as on `init`: a verdict's `declarations` are named from it, as position rows are. */
565
+ scan_root?: string;
541
566
  checks: LibraryCheck[];
542
567
  /** Checks not reached in time come back `unchecked` with reason `budget`; null or absent is the default. */
543
568
  budget_ms?: number | null;
@@ -825,6 +850,11 @@ export interface RetypeCheckResponse extends BaseResponse {
825
850
  outcomes?: RetypeOutcome[];
826
851
  errors?: string[];
827
852
  }
853
+ /** Where a verified op's member is declared on its receiver, as a position row's `decl_file` and `decl_line`. */
854
+ export interface SemanticsDeclaration {
855
+ decl_file: string;
856
+ decl_line: number;
857
+ }
828
858
  /**
829
859
  * The verdict on one `(claim_id, receiver)` pair.
830
860
  *
@@ -838,6 +868,8 @@ export interface SemanticsResult {
838
868
  verdict: 'verified' | 'failed' | 'unchecked';
839
869
  /** Absent exactly when verified. */
840
870
  reason?: string;
871
+ /** Verified op verdicts only: the declarations its member resolves to on the receiver. */
872
+ declarations?: SemanticsDeclaration[];
841
873
  }
842
874
  /** How one package of the request resolved, for logs. */
843
875
  export interface SemanticsModule {