@ttt-productions/repo-guards 0.1.8 → 0.1.9

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.
@@ -59,6 +59,35 @@ export declare function chainedFailureAudits(file: FileView, names: ReadonlySet<
59
59
  name?: string;
60
60
  }>;
61
61
  export declare const failureAuditGuard: GenericGuardDefinition<FailureAuditFacts>;
62
+ export interface RefusalAuditFacts {
63
+ /** The audit writer's plain entry point, which throws when its write fails (`writeAuditEventAs`). */
64
+ plainWriter: string;
65
+ /** The audit writer's best-effort failure entry points, which never throw. */
66
+ failureEntryPoints: readonly string[];
67
+ /** The calls that build a system actor (`systemActor`). */
68
+ systemActorBuilders: readonly string[];
69
+ /** The field and value that mark an actor literal as the system (`mode: 'system'`). */
70
+ systemActorMode: {
71
+ field: string;
72
+ value: string;
73
+ };
74
+ /** The calls that define a callable (`onCall`). */
75
+ callableFactories: readonly string[];
76
+ }
77
+ /**
78
+ * In a module that defines a callable: each plain-writer failure event written outside a
79
+ * transaction, and each failure event or failure entry point handed a system actor. Calls inside a
80
+ * failure entry point's own body are the owner's and are not read. `called` names every declared
81
+ * plain writer, builder, and factory the file calls.
82
+ */
83
+ export declare function refusalAuditShapes(file: FileView, facts: RefusalAuditFacts): {
84
+ hits: Array<{
85
+ node: ts.Node;
86
+ detail: string;
87
+ }>;
88
+ called: Set<string>;
89
+ };
90
+ export declare const refusalAuditGuard: GenericGuardDefinition<RefusalAuditFacts>;
62
91
  export interface FireAndForgetFacts {
63
92
  /** The audit writer's success entry points, whose failure is never swallowed by a `.catch`. */
64
93
  auditWrites: readonly string[];
@@ -1 +1 @@
1
- {"version":3,"file":"audit.d.ts","sourceRoot":"","sources":["../../../src/modules/backend/audit.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"audit.d.ts","sourceRoot":"","sources":["../../../src/modules/backend/audit.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,6 @@
1
1
  // The audit guards: one writer of the audit collection, one builder of person-actors, no
2
- // member-written text in an audit payload, and no handler wrapped around an audit write.
2
+ // member-written text in an audit payload, no handler wrapped around an audit write, and a
3
+ // refusal recorded through the failure entry points as the caller.
3
4
  import ts from 'typescript';
4
5
  import { ownerOnly, reviewedAllowlist } from '../../shapes/shapes.js';
5
6
  import { calleeName, forEachNode, isPlainString, lineOfNode, unwrap } from '../../support/ast.js';
@@ -308,6 +309,154 @@ export const failureAuditGuard = {
308
309
  ];
309
310
  },
310
311
  };
312
+ /** The name a function declaration or a `const f = …` function carries, if any. */
313
+ function functionName(node) {
314
+ if (ts.isFunctionDeclaration(node))
315
+ return node.name?.text;
316
+ if ((ts.isArrowFunction(node) || ts.isFunctionExpression(node)) &&
317
+ ts.isVariableDeclaration(node.parent) &&
318
+ ts.isIdentifier(node.parent.name))
319
+ return node.parent.name.text;
320
+ return undefined;
321
+ }
322
+ /**
323
+ * In a module that defines a callable: each plain-writer failure event written outside a
324
+ * transaction, and each failure event or failure entry point handed a system actor. Calls inside a
325
+ * failure entry point's own body are the owner's and are not read. `called` names every declared
326
+ * plain writer, builder, and factory the file calls.
327
+ */
328
+ export function refusalAuditShapes(file, facts) {
329
+ const sf = file.sourceFile;
330
+ const failureNames = new Set(facts.failureEntryPoints);
331
+ const builders = new Set(facts.systemActorBuilders);
332
+ const factories = new Set(facts.callableFactories);
333
+ const keyOf = (prop) => prop.name?.getText(sf).replace(/^['"`]|['"`]$/g, '') ?? '';
334
+ const called = new Set();
335
+ const systemLocals = new Set();
336
+ const isSystemActor = (expression) => {
337
+ if (!expression)
338
+ return false;
339
+ const e = unwrap(expression);
340
+ if (ts.isCallExpression(e))
341
+ return builders.has(calleeName(e) ?? '');
342
+ if (ts.isIdentifier(e))
343
+ return systemLocals.has(e.text);
344
+ if (!ts.isObjectLiteralExpression(e))
345
+ return false;
346
+ return e.properties.some((prop) => {
347
+ if (!ts.isPropertyAssignment(prop) || keyOf(prop) !== facts.systemActorMode.field)
348
+ return false;
349
+ const value = unwrap(prop.initializer);
350
+ return ts.isStringLiteralLike(value) && value.text === facts.systemActorMode.value;
351
+ });
352
+ };
353
+ let definesCallable = false;
354
+ forEachNode(sf, (node) => {
355
+ if (ts.isCallExpression(node)) {
356
+ const name = calleeName(node) ?? '';
357
+ if (factories.has(name))
358
+ definesCallable = true;
359
+ if (name === facts.plainWriter || builders.has(name) || factories.has(name))
360
+ called.add(name);
361
+ }
362
+ if (ts.isVariableDeclaration(node) &&
363
+ ts.isIdentifier(node.name) &&
364
+ node.initializer &&
365
+ isSystemActor(node.initializer))
366
+ systemLocals.add(node.name.text);
367
+ });
368
+ const hits = [];
369
+ if (!definesCallable)
370
+ return { hits, called };
371
+ const failureEvent = (call) => {
372
+ for (const arg of call.arguments) {
373
+ const details = unwrap(arg);
374
+ if (!ts.isObjectLiteralExpression(details))
375
+ continue;
376
+ const result = details.properties.find((prop) => ts.isPropertyAssignment(prop) && keyOf(prop) === 'result');
377
+ const value = result && unwrap(result.initializer);
378
+ if (value && ts.isStringLiteralLike(value) && value.text === 'failure')
379
+ return { inTransaction: details.properties.some((prop) => keyOf(prop) === 'transaction') };
380
+ }
381
+ return undefined;
382
+ };
383
+ const walk = (node) => {
384
+ if (failureNames.has(functionName(node) ?? ''))
385
+ return;
386
+ if (ts.isCallExpression(node)) {
387
+ const name = calleeName(node) ?? '';
388
+ if (name === facts.plainWriter) {
389
+ const event = failureEvent(node);
390
+ if (event && !event.inTransaction)
391
+ hits.push({
392
+ node,
393
+ detail: `${name} writes a failure event outside a transaction`,
394
+ });
395
+ if (event && isSystemActor(node.arguments[0]))
396
+ hits.push({ node, detail: `${name} records a failure as the system` });
397
+ }
398
+ if (failureNames.has(name) && isSystemActor(node.arguments[0]))
399
+ hits.push({ node, detail: `${name} records a refusal as the system` });
400
+ }
401
+ ts.forEachChild(node, walk);
402
+ };
403
+ walk(sf);
404
+ return { hits, called };
405
+ }
406
+ export const refusalAuditGuard = {
407
+ id: 'refusalAudit',
408
+ absentWhen: 'the app records no audit events',
409
+ factProblems(facts) {
410
+ const problems = [];
411
+ textProblem(facts?.plainWriter, 'plainWriter', problems);
412
+ namesProblem(facts?.failureEntryPoints, 'failureEntryPoints', problems);
413
+ namesProblem(facts?.systemActorBuilders, 'systemActorBuilders', problems);
414
+ textProblem(facts?.systemActorMode?.field, 'systemActorMode.field', problems);
415
+ textProblem(facts?.systemActorMode?.value, 'systemActorMode.value', problems);
416
+ namesProblem(facts?.callableFactories, 'callableFactories', problems);
417
+ if (facts?.failureEntryPoints?.includes(facts?.plainWriter))
418
+ problems.push('the plain writer is listed as a failure entry point');
419
+ return problems;
420
+ },
421
+ build(facts) {
422
+ return [
423
+ {
424
+ title: "a refusal's failure event goes through the failure entry points, as the caller",
425
+ description: `In a module that defines a callable (${facts.callableFactories.join(', ')}), a failure event the plain writer ` +
426
+ `${facts.plainWriter} writes outside a transaction is a refusal's record, and the plain writer throws when that ` +
427
+ `write fails, so a failed record masks the refusal itself. Record it through a failure entry point ` +
428
+ `(${facts.failureEntryPoints.join(', ')}), which never throws; only a failure event committed with the state it ` +
429
+ 'explains, in that transaction, uses the plain writer. A refusal is attributed to the person who was refused, ' +
430
+ `with the request's origin, never to the system (${facts.systemActorBuilders.join(', ')}, or an actor whose ` +
431
+ `${facts.systemActorMode.field} is '${facts.systemActorMode.value}'): use the operator's actor once they are ` +
432
+ 'authorized and the request-bound entry point before. Every declared writer, builder, and factory must still ' +
433
+ 'be called somewhere: one renamed away would pass unchecked.',
434
+ run(context) {
435
+ const findings = [];
436
+ const called = new Set();
437
+ for (const file of context.files(FUNCTIONS_CODE)) {
438
+ const shapes = refusalAuditShapes(file, facts);
439
+ shapes.called.forEach((name) => called.add(name));
440
+ for (const hit of shapes.hits)
441
+ findings.push({
442
+ file: file.path,
443
+ line: lineOfNode(file, hit.node),
444
+ detail: hit.detail,
445
+ });
446
+ }
447
+ for (const name of [
448
+ facts.plainWriter,
449
+ ...facts.systemActorBuilders,
450
+ ...facts.callableFactories,
451
+ ])
452
+ if (!called.has(name))
453
+ findings.push({ file: '(refusalAudit)', detail: `${name} is called nowhere` });
454
+ return findings;
455
+ },
456
+ },
457
+ ];
458
+ },
459
+ };
311
460
  /** Every `void`-ed call, every `.catch` chained onto an audit write, and every audit write call. */
312
461
  export function fireAndForgetShapes(file, auditWrites) {
313
462
  const sf = file.sourceFile;
@@ -1 +1 @@
1
- {"version":3,"file":"audit.js","sourceRoot":"","sources":["../../../src/modules/backend/audit.ts"],"names":[],"mappings":"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","sourcesContent":["// The audit guards: one writer of the audit collection, one builder of person-actors, no\n// member-written text in an audit payload, and no handler wrapped around an audit write.\n\nimport ts from 'typescript';\nimport type { GenericGuardDefinition } from '../../node/suite/config.js';\nimport type { Finding, Guard, GuardContext } from '../../guard.js';\nimport type { Scope } from '../../node/reader.js';\nimport type { FileView } from '../../reader/view.js';\nimport type { Reviewed } from '../../node/layout.js';\nimport { ownerOnly, reviewedAllowlist, type Hit, type Matcher } from '../../shapes/shapes.js';\nimport { calleeName, forEachNode, isPlainString, lineOfNode, unwrap } from '../../support/ast.js';\nimport { namesProblem, reviewedProblems, textProblem } from '../../support/facts.js';\nimport { MODULE_LOADS, moduleImports, namesModule } from '../../support/imports.js';\nimport {\n serverZoneContext,\n serverZoneProblems,\n staleServerFolders,\n type ServerZone,\n} from './shared.js';\n\nconst FUNCTIONS_CODE: Scope = { roots: ['functions'], kinds: ['code'], tests: false };\n\n// ---------------------------------------------------------------------------------------------\n// One audit writer.\n\nexport interface AuditWriterFacts {\n /** The one module that writes audit documents. */\n writer: string;\n /** The audit collection's name as stored (`auditEvents`). */\n collectionName: string;\n /** The core's handles on the collection, as `OWNER.member` (`COLLECTIONS.AUDIT_EVENTS`). */\n collectionHandles: readonly string[];\n /** Packages that write audit documents themselves; only the writer imports them. */\n writerPackages: readonly string[];\n /** Modules that name the collection to READ it, never to write; a stale entry fails. */\n readers: readonly Reviewed[];\n /** The one module that builds a person-actor, the field that says an actor's mode, and the person modes. */\n personActor: { constructors: string; modeField: string; modes: readonly string[] };\n /** Server code in the app root, held to the same rules as the Functions tree. */\n appServerZone: ServerZone;\n}\n\nconst AUDIT_WRITER_SCOPE: Scope = {\n roots: ['functions', 'scripts', 'app'],\n optionalRoots: ['scripts'],\n kinds: ['code'],\n tests: false,\n};\n\n/** Every place a file names the audit collection or imports a package that writes it. */\nexport function auditCollectionMatcher(\n facts: Pick<AuditWriterFacts, 'collectionName' | 'collectionHandles' | 'writerPackages'>,\n): Matcher {\n const handles = new Set(facts.collectionHandles);\n const namesCollection = (text: string) =>\n text === facts.collectionName || text.startsWith(`${facts.collectionName}/`);\n return (file) => {\n const hits: Hit[] = [];\n const sf = file.sourceFile;\n const loads = new Map(moduleImports(sf, MODULE_LOADS).map((site) => [site.node, site.spec]));\n forEachNode(sf, (node) => {\n if (\n ts.isPropertyAccessExpression(node) &&\n handles.has(`${node.expression.getText(sf)}.${node.name.text}`)\n ) {\n hits.push({ index: node.getStart(sf), detail: node.getText(sf) });\n }\n const specifier = loads.get(node);\n if (specifier !== undefined && facts.writerPackages.some((p) => namesModule(specifier, p))) {\n hits.push({ index: node.getStart(sf), detail: `imports ${specifier}` });\n return;\n }\n const isSpecifier =\n !!node.parent &&\n (ts.isImportDeclaration(node.parent) || ts.isExportDeclaration(node.parent));\n if (isPlainString(node) && !isSpecifier && namesCollection(node.text)) {\n hits.push({ index: node.getStart(sf), detail: `'${node.text}'` });\n }\n if (ts.isTemplateExpression(node) && namesCollection(node.head.text)) {\n hits.push({ index: node.getStart(sf), detail: `${facts.collectionName} template path` });\n }\n });\n return hits;\n };\n}\n\n/** Every object literal that builds a person-actor by hand: its mode field set to a person mode. */\nexport function personActorMatcher(facts: AuditWriterFacts['personActor']): Matcher {\n const modes = new Set(facts.modes);\n return (file) => {\n const hits: Hit[] = [];\n const sf = file.sourceFile;\n forEachNode(sf, (node) => {\n if (!ts.isObjectLiteralExpression(node)) return;\n for (const prop of node.properties) {\n if (\n !ts.isPropertyAssignment(prop) ||\n prop.name.getText(sf).replace(/^['\"]|['\"]$/g, '') !== facts.modeField\n )\n continue;\n const init = unwrap(prop.initializer);\n if (ts.isStringLiteralLike(init) && modes.has(init.text)) {\n hits.push({ index: prop.getStart(sf), detail: `${facts.modeField}: '${init.text}'` });\n }\n }\n });\n return hits;\n };\n}\n\nexport const auditWriterGuard: GenericGuardDefinition<AuditWriterFacts> = {\n id: 'auditWriter',\n absentWhen: 'the app records no audit events',\n factProblems(facts) {\n const problems: string[] = [];\n textProblem(facts?.writer, 'writer', problems);\n textProblem(facts?.collectionName, 'collectionName', problems);\n namesProblem(facts?.collectionHandles, 'collectionHandles', problems);\n for (const handle of facts?.collectionHandles ?? []) {\n if (typeof handle === 'string' && !/^[\\w$]+\\.[\\w$]+$/.test(handle))\n problems.push(`collection handle ${handle} is not OWNER.member`);\n }\n namesProblem(facts?.writerPackages, 'writerPackages', problems, { nonEmpty: false });\n reviewedProblems(facts?.readers, 'readers', problems);\n if (facts?.readers?.some((r) => r?.path === facts.writer))\n problems.push('the writer is listed as a reader');\n textProblem(facts?.personActor?.constructors, 'personActor.constructors', problems);\n textProblem(facts?.personActor?.modeField, 'personActor.modeField', problems);\n namesProblem(facts?.personActor?.modes, 'personActor.modes', problems);\n serverZoneProblems(facts?.appServerZone, 'appServerZone', problems);\n return problems;\n },\n build(facts) {\n const collection = reviewedAllowlist({\n title: 'audit documents are written by the one audit writer',\n description:\n `Only ${facts.writer} names the audit collection (${facts.collectionName}, ${facts.collectionHandles.join(', ')}) ` +\n 'or imports a package that writes it; a second writer would skip the attribution, the request origin, and the ' +\n 'append-only rules the writer owns. Write audit events through the writer. A module that only READS the ' +\n 'collection is a reviewed reader with its why; a reader that stops naming the collection is stale.',\n scope: AUDIT_WRITER_SCOPE,\n match: auditCollectionMatcher(facts),\n allowlist: [{ path: facts.writer, why: 'the one audit writer' }, ...facts.readers],\n samples: {\n flags: [\n `db.collection('${facts.collectionName}').add(event);`,\n `db.doc(\\`${facts.collectionName}/\\${id}\\`).set(event);`,\n `db.collection(${facts.collectionHandles[0]}).doc(id).set(event);`,\n ],\n passes: [\n `// writes ${facts.collectionName} through ${facts.collectionHandles[0]}\\nconst label = '${facts.collectionName}Count';`,\n ],\n },\n });\n const actors = ownerOnly({\n title: 'person-actors are built only by the actor constructors',\n description:\n `Only ${facts.personActor.constructors} writes an object whose ${facts.personActor.modeField} is a person mode ` +\n `(${facts.personActor.modes.join(', ')}). A person-actor carries the origin of the request it acted through; ` +\n 'built by hand it can carry none, or a forged one. Build it with the actor constructors.',\n scope: AUDIT_WRITER_SCOPE,\n match: personActorMatcher(facts.personActor),\n owners: [facts.personActor.constructors],\n samples: {\n flags: [\n `const actor = { ${facts.personActor.modeField}: '${facts.personActor.modes[0]}' as const, uid };`,\n ],\n passes: [\n `type A = { ${facts.personActor.modeField}: '${facts.personActor.modes[0]}' };\\nconst b = { other${facts.personActor.modeField}: '${facts.personActor.modes[0]}' };`,\n ],\n },\n });\n const inZone = (guard: Guard): Guard => ({\n ...guard,\n async run(context: GuardContext) {\n return [\n ...staleServerFolders(context, facts.appServerZone),\n ...(await guard.run(serverZoneContext(context, facts.appServerZone))),\n ];\n },\n });\n return [inZone(collection), inZone(actors)];\n },\n};\n\n// ---------------------------------------------------------------------------------------------\n// No member-written text in an audit payload.\n\nexport interface AuditPayloadFacts {\n /** The audit writer's entry points (`writeAuditEventAs`, the failure entry points). */\n entryPoints: readonly string[];\n /** The key under which an entry point takes its payload (`payload`). */\n payloadKey: string;\n /** Keys elsewhere whose object literal is handed to an entry point as its payload (`auditPayload`). */\n forwardedPayloadKeys: readonly string[];\n /** Prose-named keys an operator (never a member) writes, as `file#key`, each with its why. */\n operatorCopy: ReadonlyArray<{ site: string; why: string }>;\n}\n\n/** A key named like prose: a title, a name, a body of text. */\nconst PROSE_KEY =\n /(title|name|description|subject|body|text|message|content|caption|narrative|comment|bio|words)$/i;\n/** A map whose keys NAME fields rather than carry their text (`changedFieldLengths`). */\nconst NAMES_NOT_TEXT = /(Lengths|Fields)$/;\n\n/** Every prose-named key, at any depth, of the payload literals a file hands to an audit entry point. */\nexport function proseKeysInAuditPayloads(\n file: FileView,\n facts: AuditPayloadFacts,\n): Array<{ key: string; node: ts.Node }> {\n const sf = file.sourceFile;\n const entryPoints = new Set(facts.entryPoints);\n const forwarded = new Set(facts.forwardedPayloadKeys);\n const hits: Array<{ key: string; node: ts.Node }> = [];\n const keyOf = (prop: ts.ObjectLiteralElementLike) =>\n prop.name?.getText(sf).replace(/^['\"`]|['\"`]$/g, '') ?? '';\n const walkPayload = (obj: ts.ObjectLiteralExpression) => {\n for (const prop of obj.properties) {\n if (!ts.isPropertyAssignment(prop) && !ts.isShorthandPropertyAssignment(prop)) continue;\n const key = keyOf(prop);\n if (PROSE_KEY.test(key)) hits.push({ key, node: prop });\n if (ts.isPropertyAssignment(prop) && !NAMES_NOT_TEXT.test(key)) {\n const value = unwrap(prop.initializer);\n if (ts.isObjectLiteralExpression(value)) walkPayload(value);\n }\n }\n };\n const payloadOf = (details: ts.ObjectLiteralExpression, keys: ReadonlySet<string>) => {\n for (const prop of details.properties) {\n if (!ts.isPropertyAssignment(prop) || !keys.has(keyOf(prop))) continue;\n const value = unwrap(prop.initializer);\n if (ts.isObjectLiteralExpression(value)) walkPayload(value);\n }\n };\n const payloadKeys = new Set([facts.payloadKey]);\n forEachNode(sf, (node) => {\n if (ts.isCallExpression(node) && entryPoints.has(calleeName(node) ?? '')) {\n for (const arg of node.arguments) {\n const value = unwrap(arg);\n if (ts.isObjectLiteralExpression(value)) payloadOf(value, payloadKeys);\n }\n }\n if (ts.isObjectLiteralExpression(node)) payloadOf(node, forwarded);\n });\n return hits;\n}\n\nexport const auditPayloadGuard: GenericGuardDefinition<AuditPayloadFacts> = {\n id: 'auditPayload',\n absentWhen: 'the app records no audit events',\n factProblems(facts) {\n const problems: string[] = [];\n namesProblem(facts?.entryPoints, 'entryPoints', problems);\n textProblem(facts?.payloadKey, 'payloadKey', problems);\n namesProblem(facts?.forwardedPayloadKeys, 'forwardedPayloadKeys', problems, {\n nonEmpty: false,\n });\n reviewedProblems(facts?.operatorCopy, 'operatorCopy', problems, { key: 'site' });\n for (const entry of facts?.operatorCopy ?? []) {\n if (typeof entry?.site === 'string' && !/^[^#]+#[^#]+$/.test(entry.site))\n problems.push(`operatorCopy site ${entry.site} is not file#key`);\n }\n return problems;\n },\n build(facts) {\n return [\n {\n title: 'no member-written text is copied into an audit payload',\n description:\n 'An audit payload records identifiers, field names, lengths, and decisions — never member-written text. An audit ' +\n 'event outlives every edit and every erasure, so a title, name, description, subject, body, or message copied ' +\n \"into one survives the member's own change and their account's deletion. Record the changed field names and \" +\n 'their lengths instead. A key under a `…Lengths` or `…Fields` map names fields and is exempt; text an operator ' +\n 'writes is a reviewed `file#key` entry with its why, and an entry that no longer occurs is stale.',\n run(context) {\n const findings: Finding[] = [];\n const reviewed = new Set(facts.operatorCopy.map((e) => e.site));\n const seen = new Set<string>();\n for (const file of context.files(FUNCTIONS_CODE)) {\n for (const { key, node } of proseKeysInAuditPayloads(file, facts)) {\n const site = `${file.path}#${key}`;\n seen.add(site);\n if (!reviewed.has(site))\n findings.push({\n file: file.path,\n line: lineOfNode(file, node),\n detail: `prose-named payload key ${key}`,\n });\n }\n }\n for (const site of reviewed)\n if (!seen.has(site))\n findings.push({\n file: site,\n detail: 'stale operator-copy entry: the key no longer occurs',\n });\n return findings;\n },\n },\n ];\n },\n};\n\n// ---------------------------------------------------------------------------------------------\n// No handler chained onto a failure-audit entry point.\n\nexport interface FailureAuditFacts {\n /** The audit writer's best-effort failure entry points, which log and capture and never throw. */\n failureEntryPoints: readonly string[];\n}\n\n/** Each aliased import of a failure entry point, and each call with a handler chained onto it. */\nexport function chainedFailureAudits(\n file: FileView,\n names: ReadonlySet<string>,\n): Array<{ node: ts.Node; detail: string; name?: string }> {\n const sf = file.sourceFile;\n const hits: Array<{ node: ts.Node; detail: string; name?: string }> = [];\n forEachNode(sf, (node) => {\n if (\n ts.isImportSpecifier(node) &&\n node.propertyName &&\n names.has(node.propertyName.text) &&\n node.propertyName.text !== node.name.text\n ) {\n hits.push({ node, detail: `imports ${node.propertyName.text} under another name` });\n }\n const name = ts.isCallExpression(node) ? calleeName(node) : undefined;\n if (ts.isCallExpression(node) && name && names.has(name)) {\n const parent = node.parent;\n const chained =\n ts.isPropertyAccessExpression(parent) &&\n parent.expression === node &&\n ['catch', 'then', 'finally'].includes(parent.name.text);\n hits.push({\n node,\n name,\n detail: chained\n ? `chains .${(parent as ts.PropertyAccessExpression).name.text} onto ${name}`\n : '',\n });\n }\n });\n return hits;\n}\n\nexport const failureAuditGuard: GenericGuardDefinition<FailureAuditFacts> = {\n id: 'failureAuditChain',\n absentWhen: 'the app records no audit events',\n factProblems(facts) {\n const problems: string[] = [];\n namesProblem(facts?.failureEntryPoints, 'failureEntryPoints', problems);\n return problems;\n },\n build(facts) {\n return [\n {\n title: 'no handler is chained onto a failure-audit entry point',\n description:\n `The failure-audit entry points (${facts.failureEntryPoints.join(', ')}) never throw — each logs and captures a ` +\n 'write that failed — so a `.catch`, `.then`, or `.finally` chained onto one is dead code that hides the contract, ' +\n 'and an aliased import hides the call from this check. Import each under its own name and call it bare. Every ' +\n 'declared entry point must still be called somewhere: one renamed away would pass unchecked.',\n run(context) {\n const names = new Set(facts.failureEntryPoints);\n const called = new Set<string>();\n const findings: Finding[] = [];\n for (const file of context.files(FUNCTIONS_CODE)) {\n for (const hit of chainedFailureAudits(file, names)) {\n if (hit.name) called.add(hit.name);\n if (hit.detail)\n findings.push({\n file: file.path,\n line: lineOfNode(file, hit.node),\n detail: hit.detail,\n });\n }\n }\n for (const name of names)\n if (!called.has(name))\n findings.push({ file: '(failureEntryPoints)', detail: `${name} is called nowhere` });\n return findings;\n },\n },\n ];\n },\n};\n\n// ---------------------------------------------------------------------------------------------\n// No fire-and-forget work and no swallowed audit write.\n\nexport interface FireAndForgetFacts {\n /** The audit writer's success entry points, whose failure is never swallowed by a `.catch`. */\n auditWrites: readonly string[];\n /** Files whose `void`-ed calls are reviewed, each with its why; a file that no longer has one is stale. */\n voidSites: readonly Reviewed[];\n}\n\n/** Every `void`-ed call, every `.catch` chained onto an audit write, and every audit write call. */\nexport function fireAndForgetShapes(file: FileView, auditWrites: ReadonlySet<string>) {\n const sf = file.sourceFile;\n const voided: ts.Node[] = [];\n const swallowed: Array<{ node: ts.Node; name: string }> = [];\n const writes: string[] = [];\n forEachNode(sf, (node) => {\n if (ts.isVoidExpression(node) && ts.isCallExpression(unwrap(node.expression)))\n voided.push(node);\n if (!ts.isCallExpression(node)) return;\n const name = calleeName(node);\n if (name && auditWrites.has(name)) writes.push(name);\n if (\n name === 'catch' &&\n ts.isPropertyAccessExpression(node.expression) &&\n ts.isCallExpression(node.expression.expression)\n ) {\n const inner = calleeName(node.expression.expression);\n if (inner && auditWrites.has(inner)) swallowed.push({ node, name: inner });\n }\n });\n return { voided, swallowed, writes };\n}\n\nexport const fireAndForgetGuard: GenericGuardDefinition<FireAndForgetFacts> = {\n id: 'fireAndForget',\n absentWhen: false,\n factProblems(facts) {\n const problems: string[] = [];\n namesProblem(facts?.auditWrites, 'auditWrites', problems, { nonEmpty: false });\n reviewedProblems(facts?.voidSites, 'voidSites', problems);\n return problems;\n },\n build(facts) {\n return [\n {\n title: 'no backend work is fired and forgotten, and no audit write is swallowed',\n description:\n 'A `void`-ed call in a callable, trigger, or worker is never awaited and never has its failure captured, and the ' +\n 'runtime throttles the instance once it has answered, so the work may never run. Await it inside its own ' +\n 'try/catch with a capture, commit it in the primary transaction, or enqueue it to a durable outbox — a write ' +\n \"composed into a caller's transaction is awaited like any other. A `.catch` chained onto an audit write is a \" +\n 'second, silent owner of a failure the best-effort failure-audit entry points already own. A reviewed `void` ' +\n 'site is a file with its why; one with no `void`-ed call left is stale, and each declared audit write must still ' +\n 'be called somewhere.',\n run(context) {\n const auditWrites = new Set(facts.auditWrites);\n const reviewed = new Set(facts.voidSites.map((s) => s.path));\n const voidFiles = new Set<string>();\n const called = new Set<string>();\n const findings: Finding[] = [];\n for (const file of context.files(FUNCTIONS_CODE)) {\n const { voided, swallowed, writes } = fireAndForgetShapes(file, auditWrites);\n writes.forEach((w) => called.add(w));\n if (voided.length) voidFiles.add(file.path);\n if (!reviewed.has(file.path)) {\n for (const node of voided)\n findings.push({\n file: file.path,\n line: lineOfNode(file, node),\n detail: `fire-and-forget: ${node.getText(file.sourceFile).slice(0, 80)}`,\n });\n }\n for (const { node, name } of swallowed)\n findings.push({\n file: file.path,\n line: lineOfNode(file, node),\n detail: `${name}(…).catch(…) swallows an audit write`,\n });\n }\n for (const path of reviewed)\n if (!voidFiles.has(path))\n findings.push({\n file: path,\n detail: 'stale void site: the file has no void-ed call',\n });\n for (const name of auditWrites)\n if (!called.has(name))\n findings.push({ file: '(auditWrites)', detail: `${name} is called nowhere` });\n return findings;\n },\n },\n ];\n },\n};\n"]}
1
+ {"version":3,"file":"audit.js","sourceRoot":"","sources":["../../../src/modules/backend/audit.ts"],"names":[],"mappings":"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","sourcesContent":["// The audit guards: one writer of the audit collection, one builder of person-actors, no\n// member-written text in an audit payload, no handler wrapped around an audit write, and a\n// refusal recorded through the failure entry points as the caller.\n\nimport ts from 'typescript';\nimport type { GenericGuardDefinition } from '../../node/suite/config.js';\nimport type { Finding, Guard, GuardContext } from '../../guard.js';\nimport type { Scope } from '../../node/reader.js';\nimport type { FileView } from '../../reader/view.js';\nimport type { Reviewed } from '../../node/layout.js';\nimport { ownerOnly, reviewedAllowlist, type Hit, type Matcher } from '../../shapes/shapes.js';\nimport { calleeName, forEachNode, isPlainString, lineOfNode, unwrap } from '../../support/ast.js';\nimport { namesProblem, reviewedProblems, textProblem } from '../../support/facts.js';\nimport { MODULE_LOADS, moduleImports, namesModule } from '../../support/imports.js';\nimport {\n serverZoneContext,\n serverZoneProblems,\n staleServerFolders,\n type ServerZone,\n} from './shared.js';\n\nconst FUNCTIONS_CODE: Scope = { roots: ['functions'], kinds: ['code'], tests: false };\n\n// ---------------------------------------------------------------------------------------------\n// One audit writer.\n\nexport interface AuditWriterFacts {\n /** The one module that writes audit documents. */\n writer: string;\n /** The audit collection's name as stored (`auditEvents`). */\n collectionName: string;\n /** The core's handles on the collection, as `OWNER.member` (`COLLECTIONS.AUDIT_EVENTS`). */\n collectionHandles: readonly string[];\n /** Packages that write audit documents themselves; only the writer imports them. */\n writerPackages: readonly string[];\n /** Modules that name the collection to READ it, never to write; a stale entry fails. */\n readers: readonly Reviewed[];\n /** The one module that builds a person-actor, the field that says an actor's mode, and the person modes. */\n personActor: { constructors: string; modeField: string; modes: readonly string[] };\n /** Server code in the app root, held to the same rules as the Functions tree. */\n appServerZone: ServerZone;\n}\n\nconst AUDIT_WRITER_SCOPE: Scope = {\n roots: ['functions', 'scripts', 'app'],\n optionalRoots: ['scripts'],\n kinds: ['code'],\n tests: false,\n};\n\n/** Every place a file names the audit collection or imports a package that writes it. */\nexport function auditCollectionMatcher(\n facts: Pick<AuditWriterFacts, 'collectionName' | 'collectionHandles' | 'writerPackages'>,\n): Matcher {\n const handles = new Set(facts.collectionHandles);\n const namesCollection = (text: string) =>\n text === facts.collectionName || text.startsWith(`${facts.collectionName}/`);\n return (file) => {\n const hits: Hit[] = [];\n const sf = file.sourceFile;\n const loads = new Map(moduleImports(sf, MODULE_LOADS).map((site) => [site.node, site.spec]));\n forEachNode(sf, (node) => {\n if (\n ts.isPropertyAccessExpression(node) &&\n handles.has(`${node.expression.getText(sf)}.${node.name.text}`)\n ) {\n hits.push({ index: node.getStart(sf), detail: node.getText(sf) });\n }\n const specifier = loads.get(node);\n if (specifier !== undefined && facts.writerPackages.some((p) => namesModule(specifier, p))) {\n hits.push({ index: node.getStart(sf), detail: `imports ${specifier}` });\n return;\n }\n const isSpecifier =\n !!node.parent &&\n (ts.isImportDeclaration(node.parent) || ts.isExportDeclaration(node.parent));\n if (isPlainString(node) && !isSpecifier && namesCollection(node.text)) {\n hits.push({ index: node.getStart(sf), detail: `'${node.text}'` });\n }\n if (ts.isTemplateExpression(node) && namesCollection(node.head.text)) {\n hits.push({ index: node.getStart(sf), detail: `${facts.collectionName} template path` });\n }\n });\n return hits;\n };\n}\n\n/** Every object literal that builds a person-actor by hand: its mode field set to a person mode. */\nexport function personActorMatcher(facts: AuditWriterFacts['personActor']): Matcher {\n const modes = new Set(facts.modes);\n return (file) => {\n const hits: Hit[] = [];\n const sf = file.sourceFile;\n forEachNode(sf, (node) => {\n if (!ts.isObjectLiteralExpression(node)) return;\n for (const prop of node.properties) {\n if (\n !ts.isPropertyAssignment(prop) ||\n prop.name.getText(sf).replace(/^['\"]|['\"]$/g, '') !== facts.modeField\n )\n continue;\n const init = unwrap(prop.initializer);\n if (ts.isStringLiteralLike(init) && modes.has(init.text)) {\n hits.push({ index: prop.getStart(sf), detail: `${facts.modeField}: '${init.text}'` });\n }\n }\n });\n return hits;\n };\n}\n\nexport const auditWriterGuard: GenericGuardDefinition<AuditWriterFacts> = {\n id: 'auditWriter',\n absentWhen: 'the app records no audit events',\n factProblems(facts) {\n const problems: string[] = [];\n textProblem(facts?.writer, 'writer', problems);\n textProblem(facts?.collectionName, 'collectionName', problems);\n namesProblem(facts?.collectionHandles, 'collectionHandles', problems);\n for (const handle of facts?.collectionHandles ?? []) {\n if (typeof handle === 'string' && !/^[\\w$]+\\.[\\w$]+$/.test(handle))\n problems.push(`collection handle ${handle} is not OWNER.member`);\n }\n namesProblem(facts?.writerPackages, 'writerPackages', problems, { nonEmpty: false });\n reviewedProblems(facts?.readers, 'readers', problems);\n if (facts?.readers?.some((r) => r?.path === facts.writer))\n problems.push('the writer is listed as a reader');\n textProblem(facts?.personActor?.constructors, 'personActor.constructors', problems);\n textProblem(facts?.personActor?.modeField, 'personActor.modeField', problems);\n namesProblem(facts?.personActor?.modes, 'personActor.modes', problems);\n serverZoneProblems(facts?.appServerZone, 'appServerZone', problems);\n return problems;\n },\n build(facts) {\n const collection = reviewedAllowlist({\n title: 'audit documents are written by the one audit writer',\n description:\n `Only ${facts.writer} names the audit collection (${facts.collectionName}, ${facts.collectionHandles.join(', ')}) ` +\n 'or imports a package that writes it; a second writer would skip the attribution, the request origin, and the ' +\n 'append-only rules the writer owns. Write audit events through the writer. A module that only READS the ' +\n 'collection is a reviewed reader with its why; a reader that stops naming the collection is stale.',\n scope: AUDIT_WRITER_SCOPE,\n match: auditCollectionMatcher(facts),\n allowlist: [{ path: facts.writer, why: 'the one audit writer' }, ...facts.readers],\n samples: {\n flags: [\n `db.collection('${facts.collectionName}').add(event);`,\n `db.doc(\\`${facts.collectionName}/\\${id}\\`).set(event);`,\n `db.collection(${facts.collectionHandles[0]}).doc(id).set(event);`,\n ],\n passes: [\n `// writes ${facts.collectionName} through ${facts.collectionHandles[0]}\\nconst label = '${facts.collectionName}Count';`,\n ],\n },\n });\n const actors = ownerOnly({\n title: 'person-actors are built only by the actor constructors',\n description:\n `Only ${facts.personActor.constructors} writes an object whose ${facts.personActor.modeField} is a person mode ` +\n `(${facts.personActor.modes.join(', ')}). A person-actor carries the origin of the request it acted through; ` +\n 'built by hand it can carry none, or a forged one. Build it with the actor constructors.',\n scope: AUDIT_WRITER_SCOPE,\n match: personActorMatcher(facts.personActor),\n owners: [facts.personActor.constructors],\n samples: {\n flags: [\n `const actor = { ${facts.personActor.modeField}: '${facts.personActor.modes[0]}' as const, uid };`,\n ],\n passes: [\n `type A = { ${facts.personActor.modeField}: '${facts.personActor.modes[0]}' };\\nconst b = { other${facts.personActor.modeField}: '${facts.personActor.modes[0]}' };`,\n ],\n },\n });\n const inZone = (guard: Guard): Guard => ({\n ...guard,\n async run(context: GuardContext) {\n return [\n ...staleServerFolders(context, facts.appServerZone),\n ...(await guard.run(serverZoneContext(context, facts.appServerZone))),\n ];\n },\n });\n return [inZone(collection), inZone(actors)];\n },\n};\n\n// ---------------------------------------------------------------------------------------------\n// No member-written text in an audit payload.\n\nexport interface AuditPayloadFacts {\n /** The audit writer's entry points (`writeAuditEventAs`, the failure entry points). */\n entryPoints: readonly string[];\n /** The key under which an entry point takes its payload (`payload`). */\n payloadKey: string;\n /** Keys elsewhere whose object literal is handed to an entry point as its payload (`auditPayload`). */\n forwardedPayloadKeys: readonly string[];\n /** Prose-named keys an operator (never a member) writes, as `file#key`, each with its why. */\n operatorCopy: ReadonlyArray<{ site: string; why: string }>;\n}\n\n/** A key named like prose: a title, a name, a body of text. */\nconst PROSE_KEY =\n /(title|name|description|subject|body|text|message|content|caption|narrative|comment|bio|words)$/i;\n/** A map whose keys NAME fields rather than carry their text (`changedFieldLengths`). */\nconst NAMES_NOT_TEXT = /(Lengths|Fields)$/;\n\n/** Every prose-named key, at any depth, of the payload literals a file hands to an audit entry point. */\nexport function proseKeysInAuditPayloads(\n file: FileView,\n facts: AuditPayloadFacts,\n): Array<{ key: string; node: ts.Node }> {\n const sf = file.sourceFile;\n const entryPoints = new Set(facts.entryPoints);\n const forwarded = new Set(facts.forwardedPayloadKeys);\n const hits: Array<{ key: string; node: ts.Node }> = [];\n const keyOf = (prop: ts.ObjectLiteralElementLike) =>\n prop.name?.getText(sf).replace(/^['\"`]|['\"`]$/g, '') ?? '';\n const walkPayload = (obj: ts.ObjectLiteralExpression) => {\n for (const prop of obj.properties) {\n if (!ts.isPropertyAssignment(prop) && !ts.isShorthandPropertyAssignment(prop)) continue;\n const key = keyOf(prop);\n if (PROSE_KEY.test(key)) hits.push({ key, node: prop });\n if (ts.isPropertyAssignment(prop) && !NAMES_NOT_TEXT.test(key)) {\n const value = unwrap(prop.initializer);\n if (ts.isObjectLiteralExpression(value)) walkPayload(value);\n }\n }\n };\n const payloadOf = (details: ts.ObjectLiteralExpression, keys: ReadonlySet<string>) => {\n for (const prop of details.properties) {\n if (!ts.isPropertyAssignment(prop) || !keys.has(keyOf(prop))) continue;\n const value = unwrap(prop.initializer);\n if (ts.isObjectLiteralExpression(value)) walkPayload(value);\n }\n };\n const payloadKeys = new Set([facts.payloadKey]);\n forEachNode(sf, (node) => {\n if (ts.isCallExpression(node) && entryPoints.has(calleeName(node) ?? '')) {\n for (const arg of node.arguments) {\n const value = unwrap(arg);\n if (ts.isObjectLiteralExpression(value)) payloadOf(value, payloadKeys);\n }\n }\n if (ts.isObjectLiteralExpression(node)) payloadOf(node, forwarded);\n });\n return hits;\n}\n\nexport const auditPayloadGuard: GenericGuardDefinition<AuditPayloadFacts> = {\n id: 'auditPayload',\n absentWhen: 'the app records no audit events',\n factProblems(facts) {\n const problems: string[] = [];\n namesProblem(facts?.entryPoints, 'entryPoints', problems);\n textProblem(facts?.payloadKey, 'payloadKey', problems);\n namesProblem(facts?.forwardedPayloadKeys, 'forwardedPayloadKeys', problems, {\n nonEmpty: false,\n });\n reviewedProblems(facts?.operatorCopy, 'operatorCopy', problems, { key: 'site' });\n for (const entry of facts?.operatorCopy ?? []) {\n if (typeof entry?.site === 'string' && !/^[^#]+#[^#]+$/.test(entry.site))\n problems.push(`operatorCopy site ${entry.site} is not file#key`);\n }\n return problems;\n },\n build(facts) {\n return [\n {\n title: 'no member-written text is copied into an audit payload',\n description:\n 'An audit payload records identifiers, field names, lengths, and decisions — never member-written text. An audit ' +\n 'event outlives every edit and every erasure, so a title, name, description, subject, body, or message copied ' +\n \"into one survives the member's own change and their account's deletion. Record the changed field names and \" +\n 'their lengths instead. A key under a `…Lengths` or `…Fields` map names fields and is exempt; text an operator ' +\n 'writes is a reviewed `file#key` entry with its why, and an entry that no longer occurs is stale.',\n run(context) {\n const findings: Finding[] = [];\n const reviewed = new Set(facts.operatorCopy.map((e) => e.site));\n const seen = new Set<string>();\n for (const file of context.files(FUNCTIONS_CODE)) {\n for (const { key, node } of proseKeysInAuditPayloads(file, facts)) {\n const site = `${file.path}#${key}`;\n seen.add(site);\n if (!reviewed.has(site))\n findings.push({\n file: file.path,\n line: lineOfNode(file, node),\n detail: `prose-named payload key ${key}`,\n });\n }\n }\n for (const site of reviewed)\n if (!seen.has(site))\n findings.push({\n file: site,\n detail: 'stale operator-copy entry: the key no longer occurs',\n });\n return findings;\n },\n },\n ];\n },\n};\n\n// ---------------------------------------------------------------------------------------------\n// No handler chained onto a failure-audit entry point.\n\nexport interface FailureAuditFacts {\n /** The audit writer's best-effort failure entry points, which log and capture and never throw. */\n failureEntryPoints: readonly string[];\n}\n\n/** Each aliased import of a failure entry point, and each call with a handler chained onto it. */\nexport function chainedFailureAudits(\n file: FileView,\n names: ReadonlySet<string>,\n): Array<{ node: ts.Node; detail: string; name?: string }> {\n const sf = file.sourceFile;\n const hits: Array<{ node: ts.Node; detail: string; name?: string }> = [];\n forEachNode(sf, (node) => {\n if (\n ts.isImportSpecifier(node) &&\n node.propertyName &&\n names.has(node.propertyName.text) &&\n node.propertyName.text !== node.name.text\n ) {\n hits.push({ node, detail: `imports ${node.propertyName.text} under another name` });\n }\n const name = ts.isCallExpression(node) ? calleeName(node) : undefined;\n if (ts.isCallExpression(node) && name && names.has(name)) {\n const parent = node.parent;\n const chained =\n ts.isPropertyAccessExpression(parent) &&\n parent.expression === node &&\n ['catch', 'then', 'finally'].includes(parent.name.text);\n hits.push({\n node,\n name,\n detail: chained\n ? `chains .${(parent as ts.PropertyAccessExpression).name.text} onto ${name}`\n : '',\n });\n }\n });\n return hits;\n}\n\nexport const failureAuditGuard: GenericGuardDefinition<FailureAuditFacts> = {\n id: 'failureAuditChain',\n absentWhen: 'the app records no audit events',\n factProblems(facts) {\n const problems: string[] = [];\n namesProblem(facts?.failureEntryPoints, 'failureEntryPoints', problems);\n return problems;\n },\n build(facts) {\n return [\n {\n title: 'no handler is chained onto a failure-audit entry point',\n description:\n `The failure-audit entry points (${facts.failureEntryPoints.join(', ')}) never throw — each logs and captures a ` +\n 'write that failed — so a `.catch`, `.then`, or `.finally` chained onto one is dead code that hides the contract, ' +\n 'and an aliased import hides the call from this check. Import each under its own name and call it bare. Every ' +\n 'declared entry point must still be called somewhere: one renamed away would pass unchecked.',\n run(context) {\n const names = new Set(facts.failureEntryPoints);\n const called = new Set<string>();\n const findings: Finding[] = [];\n for (const file of context.files(FUNCTIONS_CODE)) {\n for (const hit of chainedFailureAudits(file, names)) {\n if (hit.name) called.add(hit.name);\n if (hit.detail)\n findings.push({\n file: file.path,\n line: lineOfNode(file, hit.node),\n detail: hit.detail,\n });\n }\n }\n for (const name of names)\n if (!called.has(name))\n findings.push({ file: '(failureEntryPoints)', detail: `${name} is called nowhere` });\n return findings;\n },\n },\n ];\n },\n};\n\n// ---------------------------------------------------------------------------------------------\n// A refusal's failure event goes through the failure entry points, attributed to the caller.\n\nexport interface RefusalAuditFacts {\n /** The audit writer's plain entry point, which throws when its write fails (`writeAuditEventAs`). */\n plainWriter: string;\n /** The audit writer's best-effort failure entry points, which never throw. */\n failureEntryPoints: readonly string[];\n /** The calls that build a system actor (`systemActor`). */\n systemActorBuilders: readonly string[];\n /** The field and value that mark an actor literal as the system (`mode: 'system'`). */\n systemActorMode: { field: string; value: string };\n /** The calls that define a callable (`onCall`). */\n callableFactories: readonly string[];\n}\n\n/** The name a function declaration or a `const f = …` function carries, if any. */\nfunction functionName(node: ts.Node): string | undefined {\n if (ts.isFunctionDeclaration(node)) return node.name?.text;\n if (\n (ts.isArrowFunction(node) || ts.isFunctionExpression(node)) &&\n ts.isVariableDeclaration(node.parent) &&\n ts.isIdentifier(node.parent.name)\n )\n return node.parent.name.text;\n return undefined;\n}\n\n/**\n * In a module that defines a callable: each plain-writer failure event written outside a\n * transaction, and each failure event or failure entry point handed a system actor. Calls inside a\n * failure entry point's own body are the owner's and are not read. `called` names every declared\n * plain writer, builder, and factory the file calls.\n */\nexport function refusalAuditShapes(\n file: FileView,\n facts: RefusalAuditFacts,\n): { hits: Array<{ node: ts.Node; detail: string }>; called: Set<string> } {\n const sf = file.sourceFile;\n const failureNames = new Set(facts.failureEntryPoints);\n const builders = new Set(facts.systemActorBuilders);\n const factories = new Set(facts.callableFactories);\n const keyOf = (prop: ts.ObjectLiteralElementLike) =>\n prop.name?.getText(sf).replace(/^['\"`]|['\"`]$/g, '') ?? '';\n const called = new Set<string>();\n const systemLocals = new Set<string>();\n const isSystemActor = (expression: ts.Expression | undefined): boolean => {\n if (!expression) return false;\n const e = unwrap(expression);\n if (ts.isCallExpression(e)) return builders.has(calleeName(e) ?? '');\n if (ts.isIdentifier(e)) return systemLocals.has(e.text);\n if (!ts.isObjectLiteralExpression(e)) return false;\n return e.properties.some((prop) => {\n if (!ts.isPropertyAssignment(prop) || keyOf(prop) !== facts.systemActorMode.field)\n return false;\n const value = unwrap(prop.initializer);\n return ts.isStringLiteralLike(value) && value.text === facts.systemActorMode.value;\n });\n };\n let definesCallable = false;\n forEachNode(sf, (node) => {\n if (ts.isCallExpression(node)) {\n const name = calleeName(node) ?? '';\n if (factories.has(name)) definesCallable = true;\n if (name === facts.plainWriter || builders.has(name) || factories.has(name)) called.add(name);\n }\n if (\n ts.isVariableDeclaration(node) &&\n ts.isIdentifier(node.name) &&\n node.initializer &&\n isSystemActor(node.initializer)\n )\n systemLocals.add(node.name.text);\n });\n const hits: Array<{ node: ts.Node; detail: string }> = [];\n if (!definesCallable) return { hits, called };\n const failureEvent = (call: ts.CallExpression) => {\n for (const arg of call.arguments) {\n const details = unwrap(arg);\n if (!ts.isObjectLiteralExpression(details)) continue;\n const result = details.properties.find(\n (prop): prop is ts.PropertyAssignment =>\n ts.isPropertyAssignment(prop) && keyOf(prop) === 'result',\n );\n const value = result && unwrap(result.initializer);\n if (value && ts.isStringLiteralLike(value) && value.text === 'failure')\n return { inTransaction: details.properties.some((prop) => keyOf(prop) === 'transaction') };\n }\n return undefined;\n };\n const walk = (node: ts.Node): void => {\n if (failureNames.has(functionName(node) ?? '')) return;\n if (ts.isCallExpression(node)) {\n const name = calleeName(node) ?? '';\n if (name === facts.plainWriter) {\n const event = failureEvent(node);\n if (event && !event.inTransaction)\n hits.push({\n node,\n detail: `${name} writes a failure event outside a transaction`,\n });\n if (event && isSystemActor(node.arguments[0]))\n hits.push({ node, detail: `${name} records a failure as the system` });\n }\n if (failureNames.has(name) && isSystemActor(node.arguments[0]))\n hits.push({ node, detail: `${name} records a refusal as the system` });\n }\n ts.forEachChild(node, walk);\n };\n walk(sf);\n return { hits, called };\n}\n\nexport const refusalAuditGuard: GenericGuardDefinition<RefusalAuditFacts> = {\n id: 'refusalAudit',\n absentWhen: 'the app records no audit events',\n factProblems(facts) {\n const problems: string[] = [];\n textProblem(facts?.plainWriter, 'plainWriter', problems);\n namesProblem(facts?.failureEntryPoints, 'failureEntryPoints', problems);\n namesProblem(facts?.systemActorBuilders, 'systemActorBuilders', problems);\n textProblem(facts?.systemActorMode?.field, 'systemActorMode.field', problems);\n textProblem(facts?.systemActorMode?.value, 'systemActorMode.value', problems);\n namesProblem(facts?.callableFactories, 'callableFactories', problems);\n if (facts?.failureEntryPoints?.includes(facts?.plainWriter))\n problems.push('the plain writer is listed as a failure entry point');\n return problems;\n },\n build(facts) {\n return [\n {\n title: \"a refusal's failure event goes through the failure entry points, as the caller\",\n description:\n `In a module that defines a callable (${facts.callableFactories.join(', ')}), a failure event the plain writer ` +\n `${facts.plainWriter} writes outside a transaction is a refusal's record, and the plain writer throws when that ` +\n `write fails, so a failed record masks the refusal itself. Record it through a failure entry point ` +\n `(${facts.failureEntryPoints.join(', ')}), which never throws; only a failure event committed with the state it ` +\n 'explains, in that transaction, uses the plain writer. A refusal is attributed to the person who was refused, ' +\n `with the request's origin, never to the system (${facts.systemActorBuilders.join(', ')}, or an actor whose ` +\n `${facts.systemActorMode.field} is '${facts.systemActorMode.value}'): use the operator's actor once they are ` +\n 'authorized and the request-bound entry point before. Every declared writer, builder, and factory must still ' +\n 'be called somewhere: one renamed away would pass unchecked.',\n run(context) {\n const findings: Finding[] = [];\n const called = new Set<string>();\n for (const file of context.files(FUNCTIONS_CODE)) {\n const shapes = refusalAuditShapes(file, facts);\n shapes.called.forEach((name) => called.add(name));\n for (const hit of shapes.hits)\n findings.push({\n file: file.path,\n line: lineOfNode(file, hit.node),\n detail: hit.detail,\n });\n }\n for (const name of [\n facts.plainWriter,\n ...facts.systemActorBuilders,\n ...facts.callableFactories,\n ])\n if (!called.has(name))\n findings.push({ file: '(refusalAudit)', detail: `${name} is called nowhere` });\n return findings;\n },\n },\n ];\n },\n};\n\n// ---------------------------------------------------------------------------------------------\n// No fire-and-forget work and no swallowed audit write.\n\nexport interface FireAndForgetFacts {\n /** The audit writer's success entry points, whose failure is never swallowed by a `.catch`. */\n auditWrites: readonly string[];\n /** Files whose `void`-ed calls are reviewed, each with its why; a file that no longer has one is stale. */\n voidSites: readonly Reviewed[];\n}\n\n/** Every `void`-ed call, every `.catch` chained onto an audit write, and every audit write call. */\nexport function fireAndForgetShapes(file: FileView, auditWrites: ReadonlySet<string>) {\n const sf = file.sourceFile;\n const voided: ts.Node[] = [];\n const swallowed: Array<{ node: ts.Node; name: string }> = [];\n const writes: string[] = [];\n forEachNode(sf, (node) => {\n if (ts.isVoidExpression(node) && ts.isCallExpression(unwrap(node.expression)))\n voided.push(node);\n if (!ts.isCallExpression(node)) return;\n const name = calleeName(node);\n if (name && auditWrites.has(name)) writes.push(name);\n if (\n name === 'catch' &&\n ts.isPropertyAccessExpression(node.expression) &&\n ts.isCallExpression(node.expression.expression)\n ) {\n const inner = calleeName(node.expression.expression);\n if (inner && auditWrites.has(inner)) swallowed.push({ node, name: inner });\n }\n });\n return { voided, swallowed, writes };\n}\n\nexport const fireAndForgetGuard: GenericGuardDefinition<FireAndForgetFacts> = {\n id: 'fireAndForget',\n absentWhen: false,\n factProblems(facts) {\n const problems: string[] = [];\n namesProblem(facts?.auditWrites, 'auditWrites', problems, { nonEmpty: false });\n reviewedProblems(facts?.voidSites, 'voidSites', problems);\n return problems;\n },\n build(facts) {\n return [\n {\n title: 'no backend work is fired and forgotten, and no audit write is swallowed',\n description:\n 'A `void`-ed call in a callable, trigger, or worker is never awaited and never has its failure captured, and the ' +\n 'runtime throttles the instance once it has answered, so the work may never run. Await it inside its own ' +\n 'try/catch with a capture, commit it in the primary transaction, or enqueue it to a durable outbox — a write ' +\n \"composed into a caller's transaction is awaited like any other. A `.catch` chained onto an audit write is a \" +\n 'second, silent owner of a failure the best-effort failure-audit entry points already own. A reviewed `void` ' +\n 'site is a file with its why; one with no `void`-ed call left is stale, and each declared audit write must still ' +\n 'be called somewhere.',\n run(context) {\n const auditWrites = new Set(facts.auditWrites);\n const reviewed = new Set(facts.voidSites.map((s) => s.path));\n const voidFiles = new Set<string>();\n const called = new Set<string>();\n const findings: Finding[] = [];\n for (const file of context.files(FUNCTIONS_CODE)) {\n const { voided, swallowed, writes } = fireAndForgetShapes(file, auditWrites);\n writes.forEach((w) => called.add(w));\n if (voided.length) voidFiles.add(file.path);\n if (!reviewed.has(file.path)) {\n for (const node of voided)\n findings.push({\n file: file.path,\n line: lineOfNode(file, node),\n detail: `fire-and-forget: ${node.getText(file.sourceFile).slice(0, 80)}`,\n });\n }\n for (const { node, name } of swallowed)\n findings.push({\n file: file.path,\n line: lineOfNode(file, node),\n detail: `${name}(…).catch(…) swallows an audit write`,\n });\n }\n for (const path of reviewed)\n if (!voidFiles.has(path))\n findings.push({\n file: path,\n detail: 'stale void site: the file has no void-ed call',\n });\n for (const name of auditWrites)\n if (!called.has(name))\n findings.push({ file: '(auditWrites)', detail: `${name} is called nowhere` });\n return findings;\n },\n },\n ];\n },\n};\n"]}
@@ -1,5 +1,5 @@
1
1
  import type { Absent, GenericGuardDefinition } from '../../node/suite/config.js';
2
- import { type AuditPayloadFacts, type AuditWriterFacts, type FailureAuditFacts, type FireAndForgetFacts } from './audit.js';
2
+ import { type AuditPayloadFacts, type AuditWriterFacts, type FailureAuditFacts, type FireAndForgetFacts, type RefusalAuditFacts } from './audit.js';
3
3
  import { type AdminClaimFacts, type AppCheckFacts, type NoBlockingFunctionFacts, type RequestOriginFacts } from './auth.js';
4
4
  import { type PostCommitCaptureFacts, type ScheduledPassFacts, type SwallowedReadWriteFacts } from './failure-handling.js';
5
5
  import { type CodebaseManifestFacts, type LazyLoaderFacts, type LightFunctionFacts, type RateLimitFacts } from './functions-build.js';
@@ -9,6 +9,7 @@ export interface BackendGuardFacts {
9
9
  auditWriter: AuditWriterFacts | Absent;
10
10
  auditPayload: AuditPayloadFacts | Absent;
11
11
  failureAuditChain: FailureAuditFacts | Absent;
12
+ refusalAudit: RefusalAuditFacts | Absent;
12
13
  fireAndForget: FireAndForgetFacts;
13
14
  swallowedReadWrite: SwallowedReadWriteFacts;
14
15
  postCommitCapture: PostCommitCaptureFacts | Absent;
@@ -1 +1 @@
1
- {"version":3,"file":"guards.d.ts","sourceRoot":"","sources":["../../../src/modules/backend/guards.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AACjF,OAAO,EAKL,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,EACxB,MAAM,YAAY,CAAC;AACpB,OAAO,EAKL,KAAK,eAAe,EACpB,KAAK,aAAa,EAClB,KAAK,uBAAuB,EAC5B,KAAK,kBAAkB,EACxB,MAAM,WAAW,CAAC;AACnB,OAAO,EAIL,KAAK,sBAAsB,EAC3B,KAAK,kBAAkB,EACvB,KAAK,uBAAuB,EAC7B,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAKL,KAAK,qBAAqB,EAC1B,KAAK,eAAe,EACpB,KAAK,kBAAkB,EACvB,KAAK,cAAc,EACpB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAIL,KAAK,eAAe,EACpB,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,EACxB,MAAM,iBAAiB,CAAC;AAEzB,0DAA0D;AAC1D,MAAM,WAAW,iBAAiB;IAChC,WAAW,EAAE,gBAAgB,GAAG,MAAM,CAAC;IACvC,YAAY,EAAE,iBAAiB,GAAG,MAAM,CAAC;IACzC,iBAAiB,EAAE,iBAAiB,GAAG,MAAM,CAAC;IAC9C,aAAa,EAAE,kBAAkB,CAAC;IAClC,kBAAkB,EAAE,uBAAuB,CAAC;IAC5C,iBAAiB,EAAE,sBAAsB,GAAG,MAAM,CAAC;IACnD,sBAAsB,EAAE,kBAAkB,GAAG,MAAM,CAAC;IACpD,UAAU,EAAE,eAAe,CAAC;IAC5B,gBAAgB,EAAE,iBAAiB,GAAG,MAAM,CAAC;IAC7C,aAAa,EAAE,kBAAkB,GAAG,MAAM,CAAC;IAC3C,cAAc,EAAE,eAAe,GAAG,MAAM,CAAC;IACzC,aAAa,EAAE,kBAAkB,CAAC;IAClC,sBAAsB,EAAE,uBAAuB,CAAC;IAChD,QAAQ,EAAE,aAAa,GAAG,MAAM,CAAC;IACjC,iBAAiB,EAAE,cAAc,GAAG,MAAM,CAAC;IAC3C,UAAU,EAAE,eAAe,CAAC;IAC5B,cAAc,EAAE,kBAAkB,GAAG,MAAM,CAAC;IAC5C,gBAAgB,EAAE,qBAAqB,GAAG,MAAM,CAAC;CAClD;AAED,eAAO,MAAM,cAAc,EAAE,aAAa,CAAC,sBAAsB,CAAC,KAAK,CAAC,CAmBtB,CAAC"}
1
+ {"version":3,"file":"guards.d.ts","sourceRoot":"","sources":["../../../src/modules/backend/guards.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AACjF,OAAO,EAML,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,EACvB,MAAM,YAAY,CAAC;AACpB,OAAO,EAKL,KAAK,eAAe,EACpB,KAAK,aAAa,EAClB,KAAK,uBAAuB,EAC5B,KAAK,kBAAkB,EACxB,MAAM,WAAW,CAAC;AACnB,OAAO,EAIL,KAAK,sBAAsB,EAC3B,KAAK,kBAAkB,EACvB,KAAK,uBAAuB,EAC7B,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAKL,KAAK,qBAAqB,EAC1B,KAAK,eAAe,EACpB,KAAK,kBAAkB,EACvB,KAAK,cAAc,EACpB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAIL,KAAK,eAAe,EACpB,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,EACxB,MAAM,iBAAiB,CAAC;AAEzB,0DAA0D;AAC1D,MAAM,WAAW,iBAAiB;IAChC,WAAW,EAAE,gBAAgB,GAAG,MAAM,CAAC;IACvC,YAAY,EAAE,iBAAiB,GAAG,MAAM,CAAC;IACzC,iBAAiB,EAAE,iBAAiB,GAAG,MAAM,CAAC;IAC9C,YAAY,EAAE,iBAAiB,GAAG,MAAM,CAAC;IACzC,aAAa,EAAE,kBAAkB,CAAC;IAClC,kBAAkB,EAAE,uBAAuB,CAAC;IAC5C,iBAAiB,EAAE,sBAAsB,GAAG,MAAM,CAAC;IACnD,sBAAsB,EAAE,kBAAkB,GAAG,MAAM,CAAC;IACpD,UAAU,EAAE,eAAe,CAAC;IAC5B,gBAAgB,EAAE,iBAAiB,GAAG,MAAM,CAAC;IAC7C,aAAa,EAAE,kBAAkB,GAAG,MAAM,CAAC;IAC3C,cAAc,EAAE,eAAe,GAAG,MAAM,CAAC;IACzC,aAAa,EAAE,kBAAkB,CAAC;IAClC,sBAAsB,EAAE,uBAAuB,CAAC;IAChD,QAAQ,EAAE,aAAa,GAAG,MAAM,CAAC;IACjC,iBAAiB,EAAE,cAAc,GAAG,MAAM,CAAC;IAC3C,UAAU,EAAE,eAAe,CAAC;IAC5B,cAAc,EAAE,kBAAkB,GAAG,MAAM,CAAC;IAC5C,gBAAgB,EAAE,qBAAqB,GAAG,MAAM,CAAC;CAClD;AAED,eAAO,MAAM,cAAc,EAAE,aAAa,CAAC,sBAAsB,CAAC,KAAK,CAAC,CAoBtB,CAAC"}
@@ -1,6 +1,6 @@
1
1
  // The generic guards over server code: Cloud Functions and other backend trees. The registry runs every guard listed here; an app supplies
2
2
  // each one's facts under its id.
3
- import { auditPayloadGuard, auditWriterGuard, failureAuditGuard, fireAndForgetGuard, } from './audit.js';
3
+ import { auditPayloadGuard, auditWriterGuard, failureAuditGuard, fireAndForgetGuard, refusalAuditGuard, } from './audit.js';
4
4
  import { adminClaimGuard, appCheckGuard, noBlockingFunctionGuard, requestOriginGuard, } from './auth.js';
5
5
  import { postCommitCaptureGuard, scheduledPassGuard, swallowedReadWriteGuard, } from './failure-handling.js';
6
6
  import { codebaseManifestGuard, lazyLoaderGuard, lightFunctionGuard, rateLimitGuard, } from './functions-build.js';
@@ -9,6 +9,7 @@ export const BACKEND_GUARDS = Object.freeze([
9
9
  auditWriterGuard,
10
10
  auditPayloadGuard,
11
11
  failureAuditGuard,
12
+ refusalAuditGuard,
12
13
  fireAndForgetGuard,
13
14
  swallowedReadWriteGuard,
14
15
  postCommitCaptureGuard,
@@ -1 +1 @@
1
- {"version":3,"file":"guards.js","sourceRoot":"","sources":["../../../src/modules/backend/guards.ts"],"names":[],"mappings":"AAAA,2IAA2I;AAC3I,iCAAiC;AAGjC,OAAO,EACL,iBAAiB,EACjB,gBAAgB,EAChB,iBAAiB,EACjB,kBAAkB,GAKnB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,eAAe,EACf,aAAa,EACb,uBAAuB,EACvB,kBAAkB,GAKnB,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,sBAAsB,EACtB,kBAAkB,EAClB,uBAAuB,GAIxB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,qBAAqB,EACrB,eAAe,EACf,kBAAkB,EAClB,cAAc,GAKf,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACL,eAAe,EACf,iBAAiB,EACjB,kBAAkB,GAInB,MAAM,iBAAiB,CAAC;AAwBzB,MAAM,CAAC,MAAM,cAAc,GAAiD,MAAM,CAAC,MAAM,CAAC;IACxF,gBAAgB;IAChB,iBAAiB;IACjB,iBAAiB;IACjB,kBAAkB;IAClB,uBAAuB;IACvB,sBAAsB;IACtB,kBAAkB;IAClB,eAAe;IACf,iBAAiB;IACjB,kBAAkB;IAClB,eAAe;IACf,kBAAkB;IAClB,uBAAuB;IACvB,aAAa;IACb,cAAc;IACd,eAAe;IACf,kBAAkB;IAClB,qBAAqB;CAC0B,CAAC,CAAC","sourcesContent":["// The generic guards over server code: Cloud Functions and other backend trees. The registry runs every guard listed here; an app supplies\n// each one's facts under its id.\n\nimport type { Absent, GenericGuardDefinition } from '../../node/suite/config.js';\nimport {\n auditPayloadGuard,\n auditWriterGuard,\n failureAuditGuard,\n fireAndForgetGuard,\n type AuditPayloadFacts,\n type AuditWriterFacts,\n type FailureAuditFacts,\n type FireAndForgetFacts,\n} from './audit.js';\nimport {\n adminClaimGuard,\n appCheckGuard,\n noBlockingFunctionGuard,\n requestOriginGuard,\n type AdminClaimFacts,\n type AppCheckFacts,\n type NoBlockingFunctionFacts,\n type RequestOriginFacts,\n} from './auth.js';\nimport {\n postCommitCaptureGuard,\n scheduledPassGuard,\n swallowedReadWriteGuard,\n type PostCommitCaptureFacts,\n type ScheduledPassFacts,\n type SwallowedReadWriteFacts,\n} from './failure-handling.js';\nimport {\n codebaseManifestGuard,\n lazyLoaderGuard,\n lightFunctionGuard,\n rateLimitGuard,\n type CodebaseManifestFacts,\n type LazyLoaderFacts,\n type LightFunctionFacts,\n type RateLimitFacts,\n} from './functions-build.js';\nimport {\n monitoringGuard,\n telemetryTagGuard,\n unusedServiceGuard,\n type MonitoringFacts,\n type TelemetryTagFacts,\n type UnusedServiceFacts,\n} from './monitoring.js';\n\n/** The facts each guard in this area needs, by its id. */\nexport interface BackendGuardFacts {\n auditWriter: AuditWriterFacts | Absent;\n auditPayload: AuditPayloadFacts | Absent;\n failureAuditChain: FailureAuditFacts | Absent;\n fireAndForget: FireAndForgetFacts;\n swallowedReadWrite: SwallowedReadWriteFacts;\n postCommitCapture: PostCommitCaptureFacts | Absent;\n scheduledPassIsolation: ScheduledPassFacts | Absent;\n monitoring: MonitoringFacts;\n telemetryTagKeys: TelemetryTagFacts | Absent;\n unusedService: UnusedServiceFacts | Absent;\n adminClaimRead: AdminClaimFacts | Absent;\n requestOrigin: RequestOriginFacts;\n noAuthBlockingFunction: NoBlockingFunctionFacts;\n appCheck: AppCheckFacts | Absent;\n rateLimitSurfaces: RateLimitFacts | Absent;\n lazyLoader: LazyLoaderFacts;\n lightFunctions: LightFunctionFacts | Absent;\n codebaseManifest: CodebaseManifestFacts | Absent;\n}\n\nexport const BACKEND_GUARDS: ReadonlyArray<GenericGuardDefinition<never>> = Object.freeze([\n auditWriterGuard,\n auditPayloadGuard,\n failureAuditGuard,\n fireAndForgetGuard,\n swallowedReadWriteGuard,\n postCommitCaptureGuard,\n scheduledPassGuard,\n monitoringGuard,\n telemetryTagGuard,\n unusedServiceGuard,\n adminClaimGuard,\n requestOriginGuard,\n noBlockingFunctionGuard,\n appCheckGuard,\n rateLimitGuard,\n lazyLoaderGuard,\n lightFunctionGuard,\n codebaseManifestGuard,\n] as ReadonlyArray<GenericGuardDefinition<never>>);\n"]}
1
+ {"version":3,"file":"guards.js","sourceRoot":"","sources":["../../../src/modules/backend/guards.ts"],"names":[],"mappings":"AAAA,2IAA2I;AAC3I,iCAAiC;AAGjC,OAAO,EACL,iBAAiB,EACjB,gBAAgB,EAChB,iBAAiB,EACjB,kBAAkB,EAClB,iBAAiB,GAMlB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,eAAe,EACf,aAAa,EACb,uBAAuB,EACvB,kBAAkB,GAKnB,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,sBAAsB,EACtB,kBAAkB,EAClB,uBAAuB,GAIxB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,qBAAqB,EACrB,eAAe,EACf,kBAAkB,EAClB,cAAc,GAKf,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACL,eAAe,EACf,iBAAiB,EACjB,kBAAkB,GAInB,MAAM,iBAAiB,CAAC;AAyBzB,MAAM,CAAC,MAAM,cAAc,GAAiD,MAAM,CAAC,MAAM,CAAC;IACxF,gBAAgB;IAChB,iBAAiB;IACjB,iBAAiB;IACjB,iBAAiB;IACjB,kBAAkB;IAClB,uBAAuB;IACvB,sBAAsB;IACtB,kBAAkB;IAClB,eAAe;IACf,iBAAiB;IACjB,kBAAkB;IAClB,eAAe;IACf,kBAAkB;IAClB,uBAAuB;IACvB,aAAa;IACb,cAAc;IACd,eAAe;IACf,kBAAkB;IAClB,qBAAqB;CAC0B,CAAC,CAAC","sourcesContent":["// The generic guards over server code: Cloud Functions and other backend trees. The registry runs every guard listed here; an app supplies\n// each one's facts under its id.\n\nimport type { Absent, GenericGuardDefinition } from '../../node/suite/config.js';\nimport {\n auditPayloadGuard,\n auditWriterGuard,\n failureAuditGuard,\n fireAndForgetGuard,\n refusalAuditGuard,\n type AuditPayloadFacts,\n type AuditWriterFacts,\n type FailureAuditFacts,\n type FireAndForgetFacts,\n type RefusalAuditFacts,\n} from './audit.js';\nimport {\n adminClaimGuard,\n appCheckGuard,\n noBlockingFunctionGuard,\n requestOriginGuard,\n type AdminClaimFacts,\n type AppCheckFacts,\n type NoBlockingFunctionFacts,\n type RequestOriginFacts,\n} from './auth.js';\nimport {\n postCommitCaptureGuard,\n scheduledPassGuard,\n swallowedReadWriteGuard,\n type PostCommitCaptureFacts,\n type ScheduledPassFacts,\n type SwallowedReadWriteFacts,\n} from './failure-handling.js';\nimport {\n codebaseManifestGuard,\n lazyLoaderGuard,\n lightFunctionGuard,\n rateLimitGuard,\n type CodebaseManifestFacts,\n type LazyLoaderFacts,\n type LightFunctionFacts,\n type RateLimitFacts,\n} from './functions-build.js';\nimport {\n monitoringGuard,\n telemetryTagGuard,\n unusedServiceGuard,\n type MonitoringFacts,\n type TelemetryTagFacts,\n type UnusedServiceFacts,\n} from './monitoring.js';\n\n/** The facts each guard in this area needs, by its id. */\nexport interface BackendGuardFacts {\n auditWriter: AuditWriterFacts | Absent;\n auditPayload: AuditPayloadFacts | Absent;\n failureAuditChain: FailureAuditFacts | Absent;\n refusalAudit: RefusalAuditFacts | Absent;\n fireAndForget: FireAndForgetFacts;\n swallowedReadWrite: SwallowedReadWriteFacts;\n postCommitCapture: PostCommitCaptureFacts | Absent;\n scheduledPassIsolation: ScheduledPassFacts | Absent;\n monitoring: MonitoringFacts;\n telemetryTagKeys: TelemetryTagFacts | Absent;\n unusedService: UnusedServiceFacts | Absent;\n adminClaimRead: AdminClaimFacts | Absent;\n requestOrigin: RequestOriginFacts;\n noAuthBlockingFunction: NoBlockingFunctionFacts;\n appCheck: AppCheckFacts | Absent;\n rateLimitSurfaces: RateLimitFacts | Absent;\n lazyLoader: LazyLoaderFacts;\n lightFunctions: LightFunctionFacts | Absent;\n codebaseManifest: CodebaseManifestFacts | Absent;\n}\n\nexport const BACKEND_GUARDS: ReadonlyArray<GenericGuardDefinition<never>> = Object.freeze([\n auditWriterGuard,\n auditPayloadGuard,\n failureAuditGuard,\n refusalAuditGuard,\n fireAndForgetGuard,\n swallowedReadWriteGuard,\n postCommitCaptureGuard,\n scheduledPassGuard,\n monitoringGuard,\n telemetryTagGuard,\n unusedServiceGuard,\n adminClaimGuard,\n requestOriginGuard,\n noBlockingFunctionGuard,\n appCheckGuard,\n rateLimitGuard,\n lazyLoaderGuard,\n lightFunctionGuard,\n codebaseManifestGuard,\n] as ReadonlyArray<GenericGuardDefinition<never>>);\n"]}
@@ -0,0 +1,31 @@
1
+ import type { GenericGuardDefinition } from '../../node/suite/config.js';
2
+ import type { FileView } from '../../reader/view.js';
3
+ import { type AllowlistEntry, type Hit } from '../../shapes/shapes.js';
4
+ /** Every icon-only button with no aria-label, aria-labelledby, or sr-only text child. */
5
+ export declare function unnamedIconButtons(file: FileView, component: string, iconSizes: readonly string[]): Hit[];
6
+ /** Every button inside a same-file `<form>` with no `type`, unless an `asChild` trigger types it. */
7
+ export declare function untypedFormButtons(file: FileView, buttonComponents: readonly string[]): Hit[];
8
+ /** Every display-only element given a handler, handed to an `asChild` parent, or passed as a `trigger`. */
9
+ export declare function interactiveDisplayElements(file: FileView, displayComponents: readonly string[]): Hit[];
10
+ export interface AccessibleControlFacts {
11
+ /** The trees that hold the app's render surfaces (components and routes). */
12
+ surfaceTrees: readonly string[];
13
+ /** The app's button component, and the `size` values that make it icon-only. */
14
+ button: {
15
+ component: string;
16
+ iconSizes: readonly string[];
17
+ };
18
+ /** Components that render a non-interactive element (a badge that renders a div). */
19
+ displayComponents: readonly string[];
20
+ }
21
+ export declare const accessibleControlsGuard: GenericGuardDefinition<AccessibleControlFacts>;
22
+ /** Every call to `parseInt` / `parseFloat`, bare or on `Number`. */
23
+ export declare function prefixNumberParses(file: FileView): Hit[];
24
+ export interface WholeInputNumberFacts {
25
+ /** The trees that hold the app's render surfaces (components and routes). */
26
+ surfaceTrees: readonly string[];
27
+ /** Files that may prefix-parse, each with why its input is already a whole number. */
28
+ allowlist: readonly AllowlistEntry[];
29
+ }
30
+ export declare const wholeInputNumbersGuard: GenericGuardDefinition<WholeInputNumberFacts>;
31
+ //# sourceMappingURL=accessibility.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"accessibility.d.ts","sourceRoot":"","sources":["../../../src/modules/ui/accessibility.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AACzE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,GAAG,EAET,MAAM,wBAAwB,CAAC;AAsChC,yFAAyF;AACzF,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,QAAQ,EACd,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,SAAS,MAAM,EAAE,GAC3B,GAAG,EAAE,CA4BP;AAMD,qGAAqG;AACrG,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,QAAQ,EAAE,gBAAgB,EAAE,SAAS,MAAM,EAAE,GAAG,GAAG,EAAE,CAwB7F;AAQD,2GAA2G;AAC3G,wBAAgB,0BAA0B,CACxC,IAAI,EAAE,QAAQ,EACd,iBAAiB,EAAE,SAAS,MAAM,EAAE,GACnC,GAAG,EAAE,CA8BP;AAED,MAAM,WAAW,sBAAsB;IACrC,6EAA6E;IAC7E,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IAChC,gFAAgF;IAChF,MAAM,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAA;KAAE,CAAC;IAC5D,qFAAqF;IACrF,iBAAiB,EAAE,SAAS,MAAM,EAAE,CAAC;CACtC;AAED,eAAO,MAAM,uBAAuB,EAAE,sBAAsB,CAAC,sBAAsB,CA8GlF,CAAC;AAQF,oEAAoE;AACpE,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,QAAQ,GAAG,GAAG,EAAE,CAexD;AAED,MAAM,WAAW,qBAAqB;IACpC,6EAA6E;IAC7E,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IAChC,sFAAsF;IACtF,SAAS,EAAE,SAAS,cAAc,EAAE,CAAC;CACtC;AAED,eAAO,MAAM,sBAAsB,EAAE,sBAAsB,CAAC,qBAAqB,CAqChF,CAAC"}
@@ -0,0 +1,272 @@
1
+ // The UI guards over what a control says to assistive technology and to the form it sits in: an
2
+ // icon-only button has a name, a button in a form states its type, a display-only component is never
3
+ // a trigger or a control, and a typed number is read from the whole input.
4
+ import ts from 'typescript';
5
+ import { reviewedAllowlist, } from '../../shapes/shapes.js';
6
+ import { forEachNode } from '../../support/ast.js';
7
+ import { allowlistProblems, namesProblem, objectProblem } from '../../support/facts.js';
8
+ import { isUnder } from '../../support/text.js';
9
+ import { identifierListProblem, pathListProblem } from '../frontend-reads/shared.js';
10
+ import { APP_CODE } from './styling.js';
11
+ const openingOf = (node) => ts.isJsxElement(node) ? node.openingElement : ts.isJsxSelfClosingElement(node) ? node : undefined;
12
+ const attr = (open, name) => open.attributes.properties.find((p) => ts.isJsxAttribute(p) && p.name.getText() === name);
13
+ const hasSpread = (open) => open.attributes.properties.some((p) => ts.isJsxSpreadAttribute(p));
14
+ /** The opening element of the JSX element directly around `node`, if any. */
15
+ const parentOpening = (node) => node.parent && ts.isJsxElement(node.parent) ? node.parent.openingElement : undefined;
16
+ /** A matcher that reads only the files under the app's render-surface trees. */
17
+ const inSurfaces = (surfaceTrees, match) => (file) => surfaceTrees.some((tree) => isUnder(file.path, tree)) ? match(file) : [];
18
+ /** A string attribute's value as written, quotes included (`"icon"`), or undefined. */
19
+ const writtenValue = (open, name, sf) => attr(open, name)?.initializer?.getText(sf);
20
+ // =============================================================================================
21
+ // An icon-only button has an accessible name.
22
+ // =============================================================================================
23
+ /** Every icon-only button with no aria-label, aria-labelledby, or sr-only text child. */
24
+ export function unnamedIconButtons(file, component, iconSizes) {
25
+ const sf = file.sourceFile;
26
+ const sizes = new Set(iconSizes.map((size) => `"${size}"`));
27
+ const hits = [];
28
+ forEachNode(sf, (node) => {
29
+ const open = openingOf(node);
30
+ if (!open || open.tagName.getText(sf) !== component)
31
+ return;
32
+ const size = writtenValue(open, 'size', sf);
33
+ if (size === undefined || !sizes.has(size))
34
+ return;
35
+ if (attr(open, 'aria-label') ||
36
+ attr(open, 'aria-labelledby') ||
37
+ attr(open, 'asChild') ||
38
+ hasSpread(open))
39
+ return;
40
+ let srOnlyChild = false;
41
+ if (ts.isJsxElement(node)) {
42
+ forEachNode(node, (inner) => {
43
+ const innerOpen = openingOf(inner);
44
+ const cls = innerOpen && writtenValue(innerOpen, 'className', sf);
45
+ if (cls && /\bsr-only\b/.test(cls))
46
+ srOnlyChild = true;
47
+ });
48
+ }
49
+ if (!srOnlyChild)
50
+ hits.push({ index: node.getStart(sf), detail: `an unnamed size=${size} ${component}` });
51
+ });
52
+ return hits;
53
+ }
54
+ // =============================================================================================
55
+ // A button inside a form states its type.
56
+ // =============================================================================================
57
+ /** Every button inside a same-file `<form>` with no `type`, unless an `asChild` trigger types it. */
58
+ export function untypedFormButtons(file, buttonComponents) {
59
+ const sf = file.sourceFile;
60
+ const buttons = new Set(['button', ...buttonComponents]);
61
+ const hits = [];
62
+ const visit = (node, inForm) => {
63
+ const open = openingOf(node);
64
+ let nowInForm = inForm;
65
+ if (open) {
66
+ const tag = open.tagName.getText(sf);
67
+ if (tag === 'form')
68
+ nowInForm = true;
69
+ if (inForm && buttons.has(tag)) {
70
+ const parent = parentOpening(node);
71
+ const typedByTrigger = parent !== undefined &&
72
+ /Trigger$/.test(parent.tagName.getText(sf)) &&
73
+ attr(parent, 'asChild') !== undefined;
74
+ if (!attr(open, 'type') && !attr(open, 'asChild') && !hasSpread(open) && !typedByTrigger)
75
+ hits.push({ index: node.getStart(sf), detail: `an untyped <${tag}> inside a <form>` });
76
+ }
77
+ }
78
+ ts.forEachChild(node, (child) => visit(child, nowInForm));
79
+ };
80
+ visit(sf, false);
81
+ return hits;
82
+ }
83
+ // =============================================================================================
84
+ // A display-only component is never a trigger or a control.
85
+ // =============================================================================================
86
+ const CONTROL_HANDLERS = ['onClick', 'onKeyDown', 'onKeyUp', 'onPointerDown'];
87
+ /** Every display-only element given a handler, handed to an `asChild` parent, or passed as a `trigger`. */
88
+ export function interactiveDisplayElements(file, displayComponents) {
89
+ const sf = file.sourceFile;
90
+ const display = new Set(displayComponents);
91
+ const hits = [];
92
+ forEachNode(sf, (node) => {
93
+ const open = openingOf(node);
94
+ if (!open)
95
+ return;
96
+ const tag = open.tagName.getText(sf);
97
+ if (!display.has(tag))
98
+ return;
99
+ const handler = CONTROL_HANDLERS.find((name) => attr(open, name));
100
+ const parent = parentOpening(node);
101
+ const asChildChild = parent !== undefined && attr(parent, 'asChild') !== undefined;
102
+ let triggerValue = false;
103
+ for (let up = node.parent; up && !ts.isSourceFile(up); up = up.parent) {
104
+ if (ts.isJsxAttribute(up)) {
105
+ triggerValue = up.name.getText(sf) === 'trigger';
106
+ break;
107
+ }
108
+ if (ts.isJsxElement(up) || ts.isJsxSelfClosingElement(up))
109
+ break;
110
+ }
111
+ const why = handler
112
+ ? `given ${handler}`
113
+ : asChildChild
114
+ ? 'handed to an asChild parent'
115
+ : triggerValue
116
+ ? 'passed as a trigger'
117
+ : undefined;
118
+ if (why)
119
+ hits.push({ index: node.getStart(sf), detail: `a ${tag} ${why}` });
120
+ });
121
+ return hits;
122
+ }
123
+ export const accessibleControlsGuard = {
124
+ id: 'accessibleControls',
125
+ absentWhen: false,
126
+ factProblems(facts) {
127
+ const problems = [];
128
+ if (!objectProblem(facts, 'facts', problems))
129
+ return problems;
130
+ pathListProblem(facts.surfaceTrees, 'surfaceTrees', problems);
131
+ if (objectProblem(facts.button, 'button', problems)) {
132
+ identifierListProblem([facts.button.component], 'button.component', problems, {
133
+ nonEmpty: true,
134
+ });
135
+ namesProblem(facts.button.iconSizes, 'button.iconSizes', problems);
136
+ }
137
+ identifierListProblem(facts.displayComponents, 'displayComponents', problems, {
138
+ nonEmpty: true,
139
+ });
140
+ return problems;
141
+ },
142
+ build(facts) {
143
+ const { component, iconSizes } = facts.button;
144
+ const surface = `${facts.surfaceTrees[0]}/sample.tsx`;
145
+ const sample = (text) => ({ path: surface, text });
146
+ const icon = iconSizes[0];
147
+ const display = facts.displayComponents[0];
148
+ return [
149
+ reviewedAllowlist({
150
+ title: 'an icon-only button carries an accessible name',
151
+ description: `An icon-only ${component} (size ${iconSizes.map((s) => `"${s}"`).join(' or ')}) is named by an aria-label, an ` +
152
+ 'aria-labelledby, or an sr-only text child. A title alone is not reliably announced, and a row of icon buttons that all ' +
153
+ 'read "button" cannot be told apart.',
154
+ scope: APP_CODE,
155
+ match: inSurfaces(facts.surfaceTrees, (file) => unnamedIconButtons(file, component, iconSizes)),
156
+ allowlist: [],
157
+ samples: {
158
+ flags: [
159
+ sample(`const a = <${component} size="${icon}"><X /></${component}>;`),
160
+ sample(`const a = <${component} size="${icon}" title="Reset"><X /></${component}>;`),
161
+ ],
162
+ passes: [
163
+ sample(`const a = <${component} size="${icon}" aria-label="Reset color"><X /></${component}>;`),
164
+ sample(`const a = <${component} size="${icon}"><X /><span className="sr-only">Reset</span></${component}>;`),
165
+ sample(`const a = <${component} size="${icon}" asChild><a href="/x" aria-label="Open" /></${component}>;`),
166
+ ],
167
+ },
168
+ }),
169
+ reviewedAllowlist({
170
+ title: 'a button inside a form states its type',
171
+ description: `A button (<button>, or ${component}) inside a <form> declares type="submit" for the one submit control and ` +
172
+ 'type="button" for every other. An untyped button in a form is a submit button: pressing it, or Enter in a field, ' +
173
+ 'submits the form. A child of an asChild trigger is typed by the trigger.',
174
+ scope: APP_CODE,
175
+ match: inSurfaces(facts.surfaceTrees, (file) => untypedFormButtons(file, [component])),
176
+ allowlist: [],
177
+ samples: {
178
+ flags: [
179
+ sample(`const f = <form><${component} onClick={clear}>Clear</${component}></form>;`),
180
+ sample('const f = <form><div><button onClick={pick}>Pick</button></div></form>;'),
181
+ ],
182
+ passes: [
183
+ sample(`const f = <form><${component} type="button" onClick={clear}>Clear</${component}></form>;`),
184
+ sample(`const f = <form><PopoverTrigger asChild><${component}>Pick a date</${component}></PopoverTrigger></form>;`),
185
+ sample(`const f = <div><${component} onClick={go}>Go</${component}></div>;`),
186
+ ],
187
+ },
188
+ }),
189
+ reviewedAllowlist({
190
+ title: 'a display-only component is never a trigger or a control',
191
+ description: `${facts.displayComponents.join(', ')} render${facts.displayComponents.length === 1 ? 's' : ''} a non-interactive ` +
192
+ 'element, so one given a click or key handler, handed to an asChild trigger, or passed as a trigger is a control Tab ' +
193
+ 'never reaches and a screen reader never names. Use a real button styled to look the same.',
194
+ scope: APP_CODE,
195
+ match: inSurfaces(facts.surfaceTrees, (file) => interactiveDisplayElements(file, facts.displayComponents)),
196
+ allowlist: [],
197
+ samples: {
198
+ flags: [
199
+ sample(`const a = <TooltipTrigger asChild><${display}>3 online</${display}></TooltipTrigger>;`),
200
+ sample(`const a = <Dialog trigger={<${display}>Drama</${display}>} title="t" />;`),
201
+ sample(`const a = <${display} onClick={remove}>Drama</${display}>;`),
202
+ ],
203
+ passes: [
204
+ sample(`const a = <div><${display}>Drama</${display}><button aria-label="Remove">x</button></div>;`),
205
+ sample('const a = <TooltipTrigger asChild><button type="button">3</button></TooltipTrigger>;'),
206
+ ],
207
+ },
208
+ }),
209
+ ];
210
+ },
211
+ };
212
+ // =============================================================================================
213
+ // A typed number is read from the whole input.
214
+ // =============================================================================================
215
+ const PREFIX_PARSERS = new Set(['parseInt', 'parseFloat']);
216
+ /** Every call to `parseInt` / `parseFloat`, bare or on `Number`. */
217
+ export function prefixNumberParses(file) {
218
+ const sf = file.sourceFile;
219
+ const hits = [];
220
+ forEachNode(sf, (node) => {
221
+ if (!ts.isCallExpression(node))
222
+ return;
223
+ const callee = node.expression;
224
+ const name = ts.isIdentifier(callee)
225
+ ? callee.text
226
+ : ts.isPropertyAccessExpression(callee) && callee.expression.getText(sf) === 'Number'
227
+ ? callee.name.text
228
+ : '';
229
+ if (PREFIX_PARSERS.has(name))
230
+ hits.push({ index: node.getStart(sf), detail: `${callee.getText(sf)}()` });
231
+ });
232
+ return hits;
233
+ }
234
+ export const wholeInputNumbersGuard = {
235
+ id: 'wholeInputNumbers',
236
+ absentWhen: false,
237
+ factProblems(facts) {
238
+ const problems = [];
239
+ if (!objectProblem(facts, 'facts', problems))
240
+ return problems;
241
+ pathListProblem(facts.surfaceTrees, 'surfaceTrees', problems);
242
+ allowlistProblems(facts.allowlist, 'allowlist', problems);
243
+ return problems;
244
+ },
245
+ build(facts) {
246
+ const sample = (text) => ({ path: `${facts.surfaceTrees[0]}/sample.tsx`, text });
247
+ return [
248
+ reviewedAllowlist({
249
+ title: 'a render surface reads a typed number from the whole input',
250
+ description: 'parseInt and parseFloat read a numeric prefix ("1.9" becomes 1, "12abc" becomes 12), so a form that parses its text ' +
251
+ 'with them submits a value nobody typed. Read the whole input: Number(...) with a finiteness or integer check, or the ' +
252
+ "app's one parser for the value.",
253
+ scope: APP_CODE,
254
+ match: inSurfaces(facts.surfaceTrees, prefixNumberParses),
255
+ allowlist: facts.allowlist,
256
+ samples: {
257
+ flags: [
258
+ sample('const n = parseInt(text, 10);'),
259
+ sample('const n = Number.parseInt(text, 10);'),
260
+ sample('const n = parseFloat(amount);'),
261
+ sample('const n = Number.parseFloat(amount);'),
262
+ ],
263
+ passes: [
264
+ sample('const n = Number(text.trim());'),
265
+ sample('const n = parseShareCount(text);'),
266
+ ],
267
+ },
268
+ }),
269
+ ];
270
+ },
271
+ };
272
+ //# sourceMappingURL=accessibility.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"accessibility.js","sourceRoot":"","sources":["../../../src/modules/ui/accessibility.ts"],"names":[],"mappings":"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","sourcesContent":["// The UI guards over what a control says to assistive technology and to the form it sits in: an\n// icon-only button has a name, a button in a form states its type, a display-only component is never\n// a trigger or a control, and a typed number is read from the whole input.\n\nimport ts from 'typescript';\nimport type { GenericGuardDefinition } from '../../node/suite/config.js';\nimport type { FileView } from '../../reader/view.js';\nimport {\n reviewedAllowlist,\n type AllowlistEntry,\n type Hit,\n type Matcher,\n} from '../../shapes/shapes.js';\nimport { forEachNode } from '../../support/ast.js';\nimport { allowlistProblems, namesProblem, objectProblem } from '../../support/facts.js';\nimport { isUnder } from '../../support/text.js';\nimport { identifierListProblem, pathListProblem } from '../frontend-reads/shared.js';\nimport { APP_CODE } from './styling.js';\n\ntype Opening = ts.JsxOpeningElement | ts.JsxSelfClosingElement;\n\nconst openingOf = (node: ts.Node): Opening | undefined =>\n ts.isJsxElement(node) ? node.openingElement : ts.isJsxSelfClosingElement(node) ? node : undefined;\n\nconst attr = (open: Opening, name: string): ts.JsxAttribute | undefined =>\n open.attributes.properties.find(\n (p): p is ts.JsxAttribute => ts.isJsxAttribute(p) && p.name.getText() === name,\n );\n\nconst hasSpread = (open: Opening): boolean =>\n open.attributes.properties.some((p) => ts.isJsxSpreadAttribute(p));\n\n/** The opening element of the JSX element directly around `node`, if any. */\nconst parentOpening = (node: ts.Node): ts.JsxOpeningElement | undefined =>\n node.parent && ts.isJsxElement(node.parent) ? node.parent.openingElement : undefined;\n\n/** A matcher that reads only the files under the app's render-surface trees. */\nconst inSurfaces =\n (surfaceTrees: readonly string[], match: Matcher): Matcher =>\n (file) =>\n surfaceTrees.some((tree) => isUnder(file.path, tree)) ? match(file) : [];\n\n/** A string attribute's value as written, quotes included (`\"icon\"`), or undefined. */\nconst writtenValue = (open: Opening, name: string, sf: ts.SourceFile): string | undefined =>\n attr(open, name)?.initializer?.getText(sf);\n\n// =============================================================================================\n// An icon-only button has an accessible name.\n// =============================================================================================\n\n/** Every icon-only button with no aria-label, aria-labelledby, or sr-only text child. */\nexport function unnamedIconButtons(\n file: FileView,\n component: string,\n iconSizes: readonly string[],\n): Hit[] {\n const sf = file.sourceFile;\n const sizes = new Set(iconSizes.map((size) => `\"${size}\"`));\n const hits: Hit[] = [];\n forEachNode(sf, (node) => {\n const open = openingOf(node);\n if (!open || open.tagName.getText(sf) !== component) return;\n const size = writtenValue(open, 'size', sf);\n if (size === undefined || !sizes.has(size)) return;\n if (\n attr(open, 'aria-label') ||\n attr(open, 'aria-labelledby') ||\n attr(open, 'asChild') ||\n hasSpread(open)\n )\n return;\n let srOnlyChild = false;\n if (ts.isJsxElement(node)) {\n forEachNode(node, (inner) => {\n const innerOpen = openingOf(inner);\n const cls = innerOpen && writtenValue(innerOpen, 'className', sf);\n if (cls && /\\bsr-only\\b/.test(cls)) srOnlyChild = true;\n });\n }\n if (!srOnlyChild)\n hits.push({ index: node.getStart(sf), detail: `an unnamed size=${size} ${component}` });\n });\n return hits;\n}\n\n// =============================================================================================\n// A button inside a form states its type.\n// =============================================================================================\n\n/** Every button inside a same-file `<form>` with no `type`, unless an `asChild` trigger types it. */\nexport function untypedFormButtons(file: FileView, buttonComponents: readonly string[]): Hit[] {\n const sf = file.sourceFile;\n const buttons = new Set(['button', ...buttonComponents]);\n const hits: Hit[] = [];\n const visit = (node: ts.Node, inForm: boolean): void => {\n const open = openingOf(node);\n let nowInForm = inForm;\n if (open) {\n const tag = open.tagName.getText(sf);\n if (tag === 'form') nowInForm = true;\n if (inForm && buttons.has(tag)) {\n const parent = parentOpening(node);\n const typedByTrigger =\n parent !== undefined &&\n /Trigger$/.test(parent.tagName.getText(sf)) &&\n attr(parent, 'asChild') !== undefined;\n if (!attr(open, 'type') && !attr(open, 'asChild') && !hasSpread(open) && !typedByTrigger)\n hits.push({ index: node.getStart(sf), detail: `an untyped <${tag}> inside a <form>` });\n }\n }\n ts.forEachChild(node, (child) => visit(child, nowInForm));\n };\n visit(sf, false);\n return hits;\n}\n\n// =============================================================================================\n// A display-only component is never a trigger or a control.\n// =============================================================================================\n\nconst CONTROL_HANDLERS = ['onClick', 'onKeyDown', 'onKeyUp', 'onPointerDown'];\n\n/** Every display-only element given a handler, handed to an `asChild` parent, or passed as a `trigger`. */\nexport function interactiveDisplayElements(\n file: FileView,\n displayComponents: readonly string[],\n): Hit[] {\n const sf = file.sourceFile;\n const display = new Set(displayComponents);\n const hits: Hit[] = [];\n forEachNode(sf, (node) => {\n const open = openingOf(node);\n if (!open) return;\n const tag = open.tagName.getText(sf);\n if (!display.has(tag)) return;\n const handler = CONTROL_HANDLERS.find((name) => attr(open, name));\n const parent = parentOpening(node);\n const asChildChild = parent !== undefined && attr(parent, 'asChild') !== undefined;\n let triggerValue = false;\n for (let up: ts.Node | undefined = node.parent; up && !ts.isSourceFile(up); up = up.parent) {\n if (ts.isJsxAttribute(up)) {\n triggerValue = up.name.getText(sf) === 'trigger';\n break;\n }\n if (ts.isJsxElement(up) || ts.isJsxSelfClosingElement(up)) break;\n }\n const why = handler\n ? `given ${handler}`\n : asChildChild\n ? 'handed to an asChild parent'\n : triggerValue\n ? 'passed as a trigger'\n : undefined;\n if (why) hits.push({ index: node.getStart(sf), detail: `a ${tag} ${why}` });\n });\n return hits;\n}\n\nexport interface AccessibleControlFacts {\n /** The trees that hold the app's render surfaces (components and routes). */\n surfaceTrees: readonly string[];\n /** The app's button component, and the `size` values that make it icon-only. */\n button: { component: string; iconSizes: readonly string[] };\n /** Components that render a non-interactive element (a badge that renders a div). */\n displayComponents: readonly string[];\n}\n\nexport const accessibleControlsGuard: GenericGuardDefinition<AccessibleControlFacts> = {\n id: 'accessibleControls',\n absentWhen: false,\n factProblems(facts) {\n const problems: string[] = [];\n if (!objectProblem(facts, 'facts', problems)) return problems;\n pathListProblem(facts.surfaceTrees, 'surfaceTrees', problems);\n if (objectProblem(facts.button, 'button', problems)) {\n identifierListProblem([facts.button.component], 'button.component', problems, {\n nonEmpty: true,\n });\n namesProblem(facts.button.iconSizes, 'button.iconSizes', problems);\n }\n identifierListProblem(facts.displayComponents, 'displayComponents', problems, {\n nonEmpty: true,\n });\n return problems;\n },\n build(facts) {\n const { component, iconSizes } = facts.button;\n const surface = `${facts.surfaceTrees[0]}/sample.tsx`;\n const sample = (text: string) => ({ path: surface, text });\n const icon = iconSizes[0];\n const display = facts.displayComponents[0];\n return [\n reviewedAllowlist({\n title: 'an icon-only button carries an accessible name',\n description:\n `An icon-only ${component} (size ${iconSizes.map((s) => `\"${s}\"`).join(' or ')}) is named by an aria-label, an ` +\n 'aria-labelledby, or an sr-only text child. A title alone is not reliably announced, and a row of icon buttons that all ' +\n 'read \"button\" cannot be told apart.',\n scope: APP_CODE,\n match: inSurfaces(facts.surfaceTrees, (file) =>\n unnamedIconButtons(file, component, iconSizes),\n ),\n allowlist: [],\n samples: {\n flags: [\n sample(`const a = <${component} size=\"${icon}\"><X /></${component}>;`),\n sample(`const a = <${component} size=\"${icon}\" title=\"Reset\"><X /></${component}>;`),\n ],\n passes: [\n sample(\n `const a = <${component} size=\"${icon}\" aria-label=\"Reset color\"><X /></${component}>;`,\n ),\n sample(\n `const a = <${component} size=\"${icon}\"><X /><span className=\"sr-only\">Reset</span></${component}>;`,\n ),\n sample(\n `const a = <${component} size=\"${icon}\" asChild><a href=\"/x\" aria-label=\"Open\" /></${component}>;`,\n ),\n ],\n },\n }),\n reviewedAllowlist({\n title: 'a button inside a form states its type',\n description:\n `A button (<button>, or ${component}) inside a <form> declares type=\"submit\" for the one submit control and ` +\n 'type=\"button\" for every other. An untyped button in a form is a submit button: pressing it, or Enter in a field, ' +\n 'submits the form. A child of an asChild trigger is typed by the trigger.',\n scope: APP_CODE,\n match: inSurfaces(facts.surfaceTrees, (file) => untypedFormButtons(file, [component])),\n allowlist: [],\n samples: {\n flags: [\n sample(`const f = <form><${component} onClick={clear}>Clear</${component}></form>;`),\n sample('const f = <form><div><button onClick={pick}>Pick</button></div></form>;'),\n ],\n passes: [\n sample(\n `const f = <form><${component} type=\"button\" onClick={clear}>Clear</${component}></form>;`,\n ),\n sample(\n `const f = <form><PopoverTrigger asChild><${component}>Pick a date</${component}></PopoverTrigger></form>;`,\n ),\n sample(`const f = <div><${component} onClick={go}>Go</${component}></div>;`),\n ],\n },\n }),\n reviewedAllowlist({\n title: 'a display-only component is never a trigger or a control',\n description:\n `${facts.displayComponents.join(', ')} render${facts.displayComponents.length === 1 ? 's' : ''} a non-interactive ` +\n 'element, so one given a click or key handler, handed to an asChild trigger, or passed as a trigger is a control Tab ' +\n 'never reaches and a screen reader never names. Use a real button styled to look the same.',\n scope: APP_CODE,\n match: inSurfaces(facts.surfaceTrees, (file) =>\n interactiveDisplayElements(file, facts.displayComponents),\n ),\n allowlist: [],\n samples: {\n flags: [\n sample(\n `const a = <TooltipTrigger asChild><${display}>3 online</${display}></TooltipTrigger>;`,\n ),\n sample(`const a = <Dialog trigger={<${display}>Drama</${display}>} title=\"t\" />;`),\n sample(`const a = <${display} onClick={remove}>Drama</${display}>;`),\n ],\n passes: [\n sample(\n `const a = <div><${display}>Drama</${display}><button aria-label=\"Remove\">x</button></div>;`,\n ),\n sample(\n 'const a = <TooltipTrigger asChild><button type=\"button\">3</button></TooltipTrigger>;',\n ),\n ],\n },\n }),\n ];\n },\n};\n\n// =============================================================================================\n// A typed number is read from the whole input.\n// =============================================================================================\n\nconst PREFIX_PARSERS = new Set(['parseInt', 'parseFloat']);\n\n/** Every call to `parseInt` / `parseFloat`, bare or on `Number`. */\nexport function prefixNumberParses(file: FileView): Hit[] {\n const sf = file.sourceFile;\n const hits: Hit[] = [];\n forEachNode(sf, (node) => {\n if (!ts.isCallExpression(node)) return;\n const callee = node.expression;\n const name = ts.isIdentifier(callee)\n ? callee.text\n : ts.isPropertyAccessExpression(callee) && callee.expression.getText(sf) === 'Number'\n ? callee.name.text\n : '';\n if (PREFIX_PARSERS.has(name))\n hits.push({ index: node.getStart(sf), detail: `${callee.getText(sf)}()` });\n });\n return hits;\n}\n\nexport interface WholeInputNumberFacts {\n /** The trees that hold the app's render surfaces (components and routes). */\n surfaceTrees: readonly string[];\n /** Files that may prefix-parse, each with why its input is already a whole number. */\n allowlist: readonly AllowlistEntry[];\n}\n\nexport const wholeInputNumbersGuard: GenericGuardDefinition<WholeInputNumberFacts> = {\n id: 'wholeInputNumbers',\n absentWhen: false,\n factProblems(facts) {\n const problems: string[] = [];\n if (!objectProblem(facts, 'facts', problems)) return problems;\n pathListProblem(facts.surfaceTrees, 'surfaceTrees', problems);\n allowlistProblems(facts.allowlist, 'allowlist', problems);\n return problems;\n },\n build(facts) {\n const sample = (text: string) => ({ path: `${facts.surfaceTrees[0]}/sample.tsx`, text });\n return [\n reviewedAllowlist({\n title: 'a render surface reads a typed number from the whole input',\n description:\n 'parseInt and parseFloat read a numeric prefix (\"1.9\" becomes 1, \"12abc\" becomes 12), so a form that parses its text ' +\n 'with them submits a value nobody typed. Read the whole input: Number(...) with a finiteness or integer check, or the ' +\n \"app's one parser for the value.\",\n scope: APP_CODE,\n match: inSurfaces(facts.surfaceTrees, prefixNumberParses),\n allowlist: facts.allowlist,\n samples: {\n flags: [\n sample('const n = parseInt(text, 10);'),\n sample('const n = Number.parseInt(text, 10);'),\n sample('const n = parseFloat(amount);'),\n sample('const n = Number.parseFloat(amount);'),\n ],\n passes: [\n sample('const n = Number(text.trim());'),\n sample('const n = parseShareCount(text);'),\n ],\n },\n }),\n ];\n },\n};\n"]}
@@ -1,4 +1,5 @@
1
1
  import type { GenericGuardDefinition } from '../../node/suite/config.js';
2
+ import { type AccessibleControlFacts, type WholeInputNumberFacts } from './accessibility.js';
2
3
  import { type StylesheetEntryFacts, type TailwindSourceFacts, type ThemeTokenFacts } from './cascade.js';
3
4
  import { type EnumLabelFacts, type SchemaBoundFacts, type TextEntryFacts } from './definitions.js';
4
5
  import { type LayoutOrderFacts, type LoadingFeedbackFacts, type NavigationFacts, type PendingNameSeatFacts, type ShowMoreFacts } from './interaction.js';
@@ -25,6 +26,8 @@ export interface UiGuardFacts {
25
26
  textEntryBoxes: TextEntryFacts;
26
27
  schemaBounds: SchemaBoundFacts;
27
28
  pendingNameSeat: PendingNameSeatFacts;
29
+ accessibleControls: AccessibleControlFacts;
30
+ wholeInputNumbers: WholeInputNumberFacts;
28
31
  }
29
32
  export declare const UI_GUARDS: ReadonlyArray<GenericGuardDefinition<never>>;
30
33
  //# sourceMappingURL=guards.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"guards.d.ts","sourceRoot":"","sources":["../../../src/modules/ui/guards.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AACzE,OAAO,EAKL,KAAK,oBAAoB,EACzB,KAAK,mBAAmB,EACxB,KAAK,eAAe,EACrB,MAAM,cAAc,CAAC;AACtB,OAAO,EAIL,KAAK,cAAc,EACnB,KAAK,gBAAgB,EACrB,KAAK,cAAc,EACpB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAML,KAAK,gBAAgB,EACrB,KAAK,oBAAoB,EACzB,KAAK,eAAe,EACpB,KAAK,oBAAoB,EACzB,KAAK,aAAa,EACnB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAe,KAAK,WAAW,EAAE,MAAM,aAAa,CAAC;AAC5D,OAAO,EAOL,KAAK,iBAAiB,EACtB,KAAK,oBAAoB,EAC1B,MAAM,cAAc,CAAC;AAEtB,0DAA0D;AAC1D,MAAM,WAAW,YAAY;IAC3B,aAAa,EAAE,iBAAiB,CAAC;IACjC,iBAAiB,EAAE,oBAAoB,CAAC;IACxC,eAAe,EAAE,mBAAmB,CAAC;IACrC,eAAe,EAAE,oBAAoB,CAAC;IACtC,WAAW,EAAE,eAAe,CAAC;IAC7B,YAAY,EAAE,WAAW,CAAC;IAC1B,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IACvC,sBAAsB,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IAC9C,qBAAqB,EAAE,oBAAoB,CAAC;IAC5C,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IACnC,4BAA4B,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IACpD,gBAAgB,EAAE,eAAe,CAAC;IAClC,eAAe,EAAE,oBAAoB,CAAC;IACtC,eAAe,EAAE,gBAAgB,CAAC;IAClC,cAAc,EAAE,aAAa,CAAC;IAC9B,UAAU,EAAE,cAAc,CAAC;IAC3B,cAAc,EAAE,cAAc,CAAC;IAC/B,YAAY,EAAE,gBAAgB,CAAC;IAC/B,eAAe,EAAE,oBAAoB,CAAC;CACvC;AAED,eAAO,MAAM,SAAS,EAAE,aAAa,CAAC,sBAAsB,CAAC,KAAK,CAAC,CAoBjB,CAAC"}
1
+ {"version":3,"file":"guards.d.ts","sourceRoot":"","sources":["../../../src/modules/ui/guards.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AACzE,OAAO,EAGL,KAAK,sBAAsB,EAC3B,KAAK,qBAAqB,EAC3B,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAKL,KAAK,oBAAoB,EACzB,KAAK,mBAAmB,EACxB,KAAK,eAAe,EACrB,MAAM,cAAc,CAAC;AACtB,OAAO,EAIL,KAAK,cAAc,EACnB,KAAK,gBAAgB,EACrB,KAAK,cAAc,EACpB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAML,KAAK,gBAAgB,EACrB,KAAK,oBAAoB,EACzB,KAAK,eAAe,EACpB,KAAK,oBAAoB,EACzB,KAAK,aAAa,EACnB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAe,KAAK,WAAW,EAAE,MAAM,aAAa,CAAC;AAC5D,OAAO,EAOL,KAAK,iBAAiB,EACtB,KAAK,oBAAoB,EAC1B,MAAM,cAAc,CAAC;AAEtB,0DAA0D;AAC1D,MAAM,WAAW,YAAY;IAC3B,aAAa,EAAE,iBAAiB,CAAC;IACjC,iBAAiB,EAAE,oBAAoB,CAAC;IACxC,eAAe,EAAE,mBAAmB,CAAC;IACrC,eAAe,EAAE,oBAAoB,CAAC;IACtC,WAAW,EAAE,eAAe,CAAC;IAC7B,YAAY,EAAE,WAAW,CAAC;IAC1B,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IACvC,sBAAsB,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IAC9C,qBAAqB,EAAE,oBAAoB,CAAC;IAC5C,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IACnC,4BAA4B,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IACpD,gBAAgB,EAAE,eAAe,CAAC;IAClC,eAAe,EAAE,oBAAoB,CAAC;IACtC,eAAe,EAAE,gBAAgB,CAAC;IAClC,cAAc,EAAE,aAAa,CAAC;IAC9B,UAAU,EAAE,cAAc,CAAC;IAC3B,cAAc,EAAE,cAAc,CAAC;IAC/B,YAAY,EAAE,gBAAgB,CAAC;IAC/B,eAAe,EAAE,oBAAoB,CAAC;IACtC,kBAAkB,EAAE,sBAAsB,CAAC;IAC3C,iBAAiB,EAAE,qBAAqB,CAAC;CAC1C;AAED,eAAO,MAAM,SAAS,EAAE,aAAa,CAAC,sBAAsB,CAAC,KAAK,CAAC,CAsBjB,CAAC"}
@@ -1,5 +1,6 @@
1
1
  // The generic guards over the UI system: styling, theme tokens, motion, and shared interaction patterns. The registry runs every guard listed here; an app supplies
2
2
  // each one's facts under its id.
3
+ import { accessibleControlsGuard, wholeInputNumbersGuard, } from './accessibility.js';
3
4
  import { stylesheetEntryGuard, tailwindSourcesGuard, themeTokensGuard, utilityCollisionGuard, } from './cascade.js';
4
5
  import { enumLabelsGuard, schemaBoundsGuard, textEntryGuard, } from './definitions.js';
5
6
  import { layoutOrderGuard, loadingFeedbackGuard, navigationGuard, pendingNameSeatGuard, showMoreGuard, } from './interaction.js';
@@ -25,5 +26,7 @@ export const UI_GUARDS = Object.freeze([
25
26
  textEntryGuard,
26
27
  schemaBoundsGuard,
27
28
  pendingNameSeatGuard,
29
+ accessibleControlsGuard,
30
+ wholeInputNumbersGuard,
28
31
  ]);
29
32
  //# sourceMappingURL=guards.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"guards.js","sourceRoot":"","sources":["../../../src/modules/ui/guards.ts"],"names":[],"mappings":"AAAA,oKAAoK;AACpK,iCAAiC;AAGjC,OAAO,EACL,oBAAoB,EACpB,oBAAoB,EACpB,gBAAgB,EAChB,qBAAqB,GAItB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,eAAe,EACf,iBAAiB,EACjB,cAAc,GAIf,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,gBAAgB,EAChB,oBAAoB,EACpB,eAAe,EACf,oBAAoB,EACpB,aAAa,GAMd,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,WAAW,EAAoB,MAAM,aAAa,CAAC;AAC5D,OAAO,EACL,kBAAkB,EAClB,iBAAiB,EACjB,oBAAoB,EACpB,cAAc,EACd,qBAAqB,EACrB,0BAA0B,GAG3B,MAAM,cAAc,CAAC;AAyBtB,MAAM,CAAC,MAAM,SAAS,GAAiD,MAAM,CAAC,MAAM,CAAC;IACnF,kBAAkB;IAClB,iBAAiB;IACjB,oBAAoB;IACpB,oBAAoB;IACpB,gBAAgB;IAChB,WAAW;IACX,oBAAoB;IACpB,qBAAqB;IACrB,qBAAqB;IACrB,cAAc;IACd,0BAA0B;IAC1B,eAAe;IACf,oBAAoB;IACpB,gBAAgB;IAChB,aAAa;IACb,eAAe;IACf,cAAc;IACd,iBAAiB;IACjB,oBAAoB;CAC2B,CAAC,CAAC","sourcesContent":["// The generic guards over the UI system: styling, theme tokens, motion, and shared interaction patterns. The registry runs every guard listed here; an app supplies\n// each one's facts under its id.\n\nimport type { GenericGuardDefinition } from '../../node/suite/config.js';\nimport {\n stylesheetEntryGuard,\n tailwindSourcesGuard,\n themeTokensGuard,\n utilityCollisionGuard,\n type StylesheetEntryFacts,\n type TailwindSourceFacts,\n type ThemeTokenFacts,\n} from './cascade.js';\nimport {\n enumLabelsGuard,\n schemaBoundsGuard,\n textEntryGuard,\n type EnumLabelFacts,\n type SchemaBoundFacts,\n type TextEntryFacts,\n} from './definitions.js';\nimport {\n layoutOrderGuard,\n loadingFeedbackGuard,\n navigationGuard,\n pendingNameSeatGuard,\n showMoreGuard,\n type LayoutOrderFacts,\n type LoadingFeedbackFacts,\n type NavigationFacts,\n type PendingNameSeatFacts,\n type ShowMoreFacts,\n} from './interaction.js';\nimport { motionGuard, type MotionFacts } from './motion.js';\nimport {\n colorLiteralsGuard,\n scaleClassesGuard,\n shadowOwnershipGuard,\n stackRowsGuard,\n statusForegroundGuard,\n unavailableAffordanceGuard,\n type ColorLiteralFacts,\n type EntryStylesheetFacts,\n} from './styling.js';\n\n/** The facts each guard in this area needs, by its id. */\nexport interface UiGuardFacts {\n colorLiterals: ColorLiteralFacts;\n themeScaleClasses: EntryStylesheetFacts;\n tailwindSources: TailwindSourceFacts;\n stylesheetEntry: StylesheetEntryFacts;\n themeTokens: ThemeTokenFacts;\n motionTokens: MotionFacts;\n shadowOwnership: Record<string, never>;\n statusForegroundOnFill: Record<string, never>;\n semanticTextCollision: EntryStylesheetFacts;\n stackOnRows: Record<string, never>;\n unavailableControlAffordance: Record<string, never>;\n navigationHelper: NavigationFacts;\n loadingFeedback: LoadingFeedbackFacts;\n rootLayoutOrder: LayoutOrderFacts;\n showMoreToggle: ShowMoreFacts;\n enumLabels: EnumLabelFacts;\n textEntryBoxes: TextEntryFacts;\n schemaBounds: SchemaBoundFacts;\n pendingNameSeat: PendingNameSeatFacts;\n}\n\nexport const UI_GUARDS: ReadonlyArray<GenericGuardDefinition<never>> = Object.freeze([\n colorLiteralsGuard,\n scaleClassesGuard,\n tailwindSourcesGuard,\n stylesheetEntryGuard,\n themeTokensGuard,\n motionGuard,\n shadowOwnershipGuard,\n statusForegroundGuard,\n utilityCollisionGuard,\n stackRowsGuard,\n unavailableAffordanceGuard,\n navigationGuard,\n loadingFeedbackGuard,\n layoutOrderGuard,\n showMoreGuard,\n enumLabelsGuard,\n textEntryGuard,\n schemaBoundsGuard,\n pendingNameSeatGuard,\n] as ReadonlyArray<GenericGuardDefinition<never>>);\n"]}
1
+ {"version":3,"file":"guards.js","sourceRoot":"","sources":["../../../src/modules/ui/guards.ts"],"names":[],"mappings":"AAAA,oKAAoK;AACpK,iCAAiC;AAGjC,OAAO,EACL,uBAAuB,EACvB,sBAAsB,GAGvB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,oBAAoB,EACpB,oBAAoB,EACpB,gBAAgB,EAChB,qBAAqB,GAItB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,eAAe,EACf,iBAAiB,EACjB,cAAc,GAIf,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,gBAAgB,EAChB,oBAAoB,EACpB,eAAe,EACf,oBAAoB,EACpB,aAAa,GAMd,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,WAAW,EAAoB,MAAM,aAAa,CAAC;AAC5D,OAAO,EACL,kBAAkB,EAClB,iBAAiB,EACjB,oBAAoB,EACpB,cAAc,EACd,qBAAqB,EACrB,0BAA0B,GAG3B,MAAM,cAAc,CAAC;AA2BtB,MAAM,CAAC,MAAM,SAAS,GAAiD,MAAM,CAAC,MAAM,CAAC;IACnF,kBAAkB;IAClB,iBAAiB;IACjB,oBAAoB;IACpB,oBAAoB;IACpB,gBAAgB;IAChB,WAAW;IACX,oBAAoB;IACpB,qBAAqB;IACrB,qBAAqB;IACrB,cAAc;IACd,0BAA0B;IAC1B,eAAe;IACf,oBAAoB;IACpB,gBAAgB;IAChB,aAAa;IACb,eAAe;IACf,cAAc;IACd,iBAAiB;IACjB,oBAAoB;IACpB,uBAAuB;IACvB,sBAAsB;CACyB,CAAC,CAAC","sourcesContent":["// The generic guards over the UI system: styling, theme tokens, motion, and shared interaction patterns. The registry runs every guard listed here; an app supplies\n// each one's facts under its id.\n\nimport type { GenericGuardDefinition } from '../../node/suite/config.js';\nimport {\n accessibleControlsGuard,\n wholeInputNumbersGuard,\n type AccessibleControlFacts,\n type WholeInputNumberFacts,\n} from './accessibility.js';\nimport {\n stylesheetEntryGuard,\n tailwindSourcesGuard,\n themeTokensGuard,\n utilityCollisionGuard,\n type StylesheetEntryFacts,\n type TailwindSourceFacts,\n type ThemeTokenFacts,\n} from './cascade.js';\nimport {\n enumLabelsGuard,\n schemaBoundsGuard,\n textEntryGuard,\n type EnumLabelFacts,\n type SchemaBoundFacts,\n type TextEntryFacts,\n} from './definitions.js';\nimport {\n layoutOrderGuard,\n loadingFeedbackGuard,\n navigationGuard,\n pendingNameSeatGuard,\n showMoreGuard,\n type LayoutOrderFacts,\n type LoadingFeedbackFacts,\n type NavigationFacts,\n type PendingNameSeatFacts,\n type ShowMoreFacts,\n} from './interaction.js';\nimport { motionGuard, type MotionFacts } from './motion.js';\nimport {\n colorLiteralsGuard,\n scaleClassesGuard,\n shadowOwnershipGuard,\n stackRowsGuard,\n statusForegroundGuard,\n unavailableAffordanceGuard,\n type ColorLiteralFacts,\n type EntryStylesheetFacts,\n} from './styling.js';\n\n/** The facts each guard in this area needs, by its id. */\nexport interface UiGuardFacts {\n colorLiterals: ColorLiteralFacts;\n themeScaleClasses: EntryStylesheetFacts;\n tailwindSources: TailwindSourceFacts;\n stylesheetEntry: StylesheetEntryFacts;\n themeTokens: ThemeTokenFacts;\n motionTokens: MotionFacts;\n shadowOwnership: Record<string, never>;\n statusForegroundOnFill: Record<string, never>;\n semanticTextCollision: EntryStylesheetFacts;\n stackOnRows: Record<string, never>;\n unavailableControlAffordance: Record<string, never>;\n navigationHelper: NavigationFacts;\n loadingFeedback: LoadingFeedbackFacts;\n rootLayoutOrder: LayoutOrderFacts;\n showMoreToggle: ShowMoreFacts;\n enumLabels: EnumLabelFacts;\n textEntryBoxes: TextEntryFacts;\n schemaBounds: SchemaBoundFacts;\n pendingNameSeat: PendingNameSeatFacts;\n accessibleControls: AccessibleControlFacts;\n wholeInputNumbers: WholeInputNumberFacts;\n}\n\nexport const UI_GUARDS: ReadonlyArray<GenericGuardDefinition<never>> = Object.freeze([\n colorLiteralsGuard,\n scaleClassesGuard,\n tailwindSourcesGuard,\n stylesheetEntryGuard,\n themeTokensGuard,\n motionGuard,\n shadowOwnershipGuard,\n statusForegroundGuard,\n utilityCollisionGuard,\n stackRowsGuard,\n unavailableAffordanceGuard,\n navigationGuard,\n loadingFeedbackGuard,\n layoutOrderGuard,\n showMoreGuard,\n enumLabelsGuard,\n textEntryGuard,\n schemaBoundsGuard,\n pendingNameSeatGuard,\n accessibleControlsGuard,\n wholeInputNumbersGuard,\n] as ReadonlyArray<GenericGuardDefinition<never>>);\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ttt-productions/repo-guards",
3
- "version": "0.1.8",
3
+ "version": "0.1.9",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/ttt-productions/ttt-packages.git",