@ttt-productions/repo-guards 0.1.7 → 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.
Files changed (28) hide show
  1. package/dist/modules/backend/audit.d.ts +29 -0
  2. package/dist/modules/backend/audit.d.ts.map +1 -1
  3. package/dist/modules/backend/audit.js +150 -1
  4. package/dist/modules/backend/audit.js.map +1 -1
  5. package/dist/modules/backend/guards.d.ts +2 -1
  6. package/dist/modules/backend/guards.d.ts.map +1 -1
  7. package/dist/modules/backend/guards.js +2 -1
  8. package/dist/modules/backend/guards.js.map +1 -1
  9. package/dist/modules/core-definitions/text-field-declarations.d.ts.map +1 -1
  10. package/dist/modules/core-definitions/text-field-declarations.js +10 -3
  11. package/dist/modules/core-definitions/text-field-declarations.js.map +1 -1
  12. package/dist/modules/import-paths/guards.d.ts +2 -0
  13. package/dist/modules/import-paths/guards.d.ts.map +1 -1
  14. package/dist/modules/import-paths/guards.js +2 -0
  15. package/dist/modules/import-paths/guards.js.map +1 -1
  16. package/dist/modules/import-paths/package-wildcard-imports.d.ts +7 -0
  17. package/dist/modules/import-paths/package-wildcard-imports.d.ts.map +1 -0
  18. package/dist/modules/import-paths/package-wildcard-imports.js +56 -0
  19. package/dist/modules/import-paths/package-wildcard-imports.js.map +1 -0
  20. package/dist/modules/ui/accessibility.d.ts +31 -0
  21. package/dist/modules/ui/accessibility.d.ts.map +1 -0
  22. package/dist/modules/ui/accessibility.js +272 -0
  23. package/dist/modules/ui/accessibility.js.map +1 -0
  24. package/dist/modules/ui/guards.d.ts +3 -0
  25. package/dist/modules/ui/guards.d.ts.map +1 -1
  26. package/dist/modules/ui/guards.js +3 -0
  27. package/dist/modules/ui/guards.js.map +1 -1
  28. package/package.json +1 -1
@@ -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"]}
@@ -1 +1 @@
1
- {"version":3,"file":"text-field-declarations.d.ts","sourceRoot":"","sources":["../../../src/modules/core-definitions/text-field-declarations.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AACzE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAErD,OAAO,EAIL,KAAK,MAAM,EAEZ,MAAM,wBAAwB,CAAC;AAGhC,KAAK,cAAc,GAAG,aAAa,CAAC;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;CACb,CAAC,CAAC;AACH,KAAK,WAAW,GAAG;IAAE,SAAS,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,IAAI,CAAC,EAAE,OAAO,CAAA;KAAE,CAAA;CAAE,CAAC;AAE3F,MAAM,WAAW,yBAAyB;IACxC,qEAAqE;IACrE,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;IAC/B,qFAAqF;IACrF,gBAAgB,EAAE,MAAM,CAAC;IACzB,kGAAkG;IAClG,qBAAqB,EAAE,SAAS,MAAM,EAAE,CAAC;IACzC,8GAA8G;IAC9G,WAAW,EAAE,cAAc,CAAC;IAC5B,sGAAsG;IACtG,kBAAkB,EAAE,cAAc,CAAC;IACnC,qEAAqE;IACrE,WAAW,EAAE,aAAa,CAAC;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACjE,yCAAyC;IACzC,YAAY,EAAE,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;IACxD,gEAAgE;IAChE,WAAW,EAAE,MAAM,CAAC,CAAC,WAAW,EAAE,KAAK,KAAK,WAAW,CAAC,CAAC;CAC1D;AAiCD,UAAU,IAAI;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;CACd;AAED,6FAA6F;AAC7F,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,QAAQ,GAAG,IAAI,EAAE,CAyBxD;AAED,+GAA+G;AAC/G,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,QAAQ,EACd,WAAW,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,OAAO,GAC5C,IAAI,EAAE,CAiCR;AAqDD,eAAO,MAAM,0BAA0B,EAAE,sBAAsB,CAAC,yBAAyB,CAkKxF,CAAC"}
1
+ {"version":3,"file":"text-field-declarations.d.ts","sourceRoot":"","sources":["../../../src/modules/core-definitions/text-field-declarations.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AACzE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAErD,OAAO,EAIL,KAAK,MAAM,EAEZ,MAAM,wBAAwB,CAAC;AAGhC,KAAK,cAAc,GAAG,aAAa,CAAC;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;CACb,CAAC,CAAC;AACH,KAAK,WAAW,GAAG;IAAE,SAAS,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,IAAI,CAAC,EAAE,OAAO,CAAA;KAAE,CAAA;CAAE,CAAC;AAE3F,MAAM,WAAW,yBAAyB;IACxC,qEAAqE;IACrE,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;IAC/B,qFAAqF;IACrF,gBAAgB,EAAE,MAAM,CAAC;IACzB,kGAAkG;IAClG,qBAAqB,EAAE,SAAS,MAAM,EAAE,CAAC;IACzC,8GAA8G;IAC9G,WAAW,EAAE,cAAc,CAAC;IAC5B,sGAAsG;IACtG,kBAAkB,EAAE,cAAc,CAAC;IACnC,qEAAqE;IACrE,WAAW,EAAE,aAAa,CAAC;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACjE,yCAAyC;IACzC,YAAY,EAAE,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;IACxD,gEAAgE;IAChE,WAAW,EAAE,MAAM,CAAC,CAAC,WAAW,EAAE,KAAK,KAAK,WAAW,CAAC,CAAC;CAC1D;AAiCD,UAAU,IAAI;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;CACd;AAED,6FAA6F;AAC7F,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,QAAQ,GAAG,IAAI,EAAE,CAyBxD;AAED,+GAA+G;AAC/G,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,QAAQ,EACd,WAAW,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,OAAO,GAC5C,IAAI,EAAE,CAiCR;AA2DD,eAAO,MAAM,0BAA0B,EAAE,sBAAsB,CAAC,yBAAyB,CAmKxF,CAAC"}
@@ -114,6 +114,11 @@ function compareReviewed(sites, reviewed, what) {
114
114
  }
115
115
  return findings;
116
116
  }
117
+ /**
118
+ * input-format-core's format for free text — a description, a message, a chapter — which holds its
119
+ * spacing as typed; every other format holds a run of spaces to one.
120
+ */
121
+ const FREE_TEXT_FORMAT = 'none';
117
122
  const textDeclarations = (exports) => Object.entries(exports).filter((e) => typeof e[1] === 'object' && e[1] !== null && 'format' in e[1]);
118
123
  const reviewedCountProblems = (value, what, problems) => reviewedProblems(value, what, problems, {
119
124
  key: 'file',
@@ -231,10 +236,10 @@ export const textFieldDeclarationsGuard = {
231
236
  },
232
237
  },
233
238
  {
234
- title: 'every declared text field trims, keeps one space for each run of spaces, and a required one refuses blank text',
239
+ title: 'every declared text field trims, one that holds words keeps one space for each run of spaces, and a required one refuses blank text',
235
240
  description: "Each text field's wire schema, built from its declaration, refuses whitespace-only text when the field requires " +
236
- 'text (min of 1 or more), and trims the text of every field that does not keep it as typed; a field that accepts ' +
237
- 'a space between words keeps one space for a run of them — so the form, the wire, and the writer agree, and a ' +
241
+ 'text (min of 1 or more), and trims the text of every field that does not keep it as typed; a field whose format ' +
242
+ "holds words (every format but free text, 'none') keeps one space for a run of them — so the form, the wire, and the writer agree, and a " +
238
243
  'name spaced twice is never stored, searched, or reserved apart from the same name spaced once.',
239
244
  async run(context) {
240
245
  const findings = [];
@@ -260,6 +265,8 @@ export const textFieldDeclarationsGuard = {
260
265
  const parsed = schema.safeParse(` ${sample} `);
261
266
  if (!parsed.success || parsed.data !== sample)
262
267
  findings.push({ file: `(core) ${name}`, detail: 'does not trim its text' });
268
+ if (declaration.format === FREE_TEXT_FORMAT)
269
+ continue;
263
270
  const word = 'a'.repeat(Math.max(min - 2, 1));
264
271
  if (!schema.safeParse(`${word} a`).success)
265
272
  continue;