auditor-lambda 0.3.40 → 0.3.41

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 (148) hide show
  1. package/audit-code-wrapper-lib.mjs +20 -2
  2. package/dist/cli/args.d.ts +59 -0
  3. package/dist/cli/args.js +244 -0
  4. package/dist/cli/dispatch.d.ts +80 -0
  5. package/dist/cli/dispatch.js +528 -0
  6. package/dist/cli/prompts.d.ts +18 -0
  7. package/dist/cli/prompts.js +130 -0
  8. package/dist/cli/steps.d.ts +29 -0
  9. package/dist/cli/steps.js +30 -0
  10. package/dist/cli/waveManifest.d.ts +40 -0
  11. package/dist/cli/waveManifest.js +41 -0
  12. package/dist/cli/workerResult.d.ts +18 -0
  13. package/dist/cli/workerResult.js +42 -0
  14. package/dist/cli.d.ts +2 -22
  15. package/dist/cli.js +160 -973
  16. package/dist/extractors/browserExtension.d.ts +1 -3
  17. package/dist/extractors/browserExtension.js +2 -2
  18. package/dist/extractors/designAssessment.d.ts +1 -3
  19. package/dist/extractors/disposition.d.ts +2 -1
  20. package/dist/extractors/disposition.js +3 -0
  21. package/dist/extractors/flows.d.ts +1 -3
  22. package/dist/extractors/flows.js +2 -2
  23. package/dist/extractors/graph.d.ts +1 -2
  24. package/dist/extractors/graph.js +4 -326
  25. package/dist/extractors/graphManifestEdges.d.ts +1 -1
  26. package/dist/extractors/graphPathUtils.d.ts +1 -1
  27. package/dist/extractors/graphPythonImports.d.ts +3 -0
  28. package/dist/extractors/graphPythonImports.js +326 -0
  29. package/dist/extractors/risk.d.ts +1 -2
  30. package/dist/extractors/surfaces.d.ts +1 -3
  31. package/dist/extractors/surfaces.js +2 -2
  32. package/dist/io/artifacts.d.ts +1 -5
  33. package/dist/io/artifacts.js +1 -1
  34. package/dist/io/runArtifacts.js +1 -1
  35. package/dist/mcp/server.js +1 -1
  36. package/dist/orchestrator/advance.d.ts +1 -0
  37. package/dist/orchestrator/advance.js +8 -5
  38. package/dist/orchestrator/auditTaskUtils.d.ts +4 -0
  39. package/dist/orchestrator/auditTaskUtils.js +27 -0
  40. package/dist/orchestrator/fileAnchors.d.ts +1 -1
  41. package/dist/orchestrator/fileIntegrity.d.ts +7 -0
  42. package/dist/orchestrator/fileIntegrity.js +41 -0
  43. package/dist/orchestrator/flowCoverage.d.ts +1 -1
  44. package/dist/orchestrator/flowPlanning.d.ts +1 -1
  45. package/dist/orchestrator/flowRequeue.d.ts +1 -1
  46. package/dist/orchestrator/internalExecutors.d.ts +3 -1
  47. package/dist/orchestrator/internalExecutors.js +23 -5
  48. package/dist/orchestrator/nextStep.d.ts +2 -1
  49. package/dist/orchestrator/nextStep.js +1 -1
  50. package/dist/orchestrator/planning.d.ts +1 -1
  51. package/dist/orchestrator/requeueCommand.d.ts +1 -1
  52. package/dist/orchestrator/reviewPackets.d.ts +1 -1
  53. package/dist/orchestrator/reviewPackets.js +21 -113
  54. package/dist/orchestrator/runtimeValidation.d.ts +1 -1
  55. package/dist/orchestrator/taskBuilder.d.ts +1 -1
  56. package/dist/orchestrator/taskBuilder.js +1 -12
  57. package/dist/orchestrator/unionFind.d.ts +7 -0
  58. package/dist/orchestrator/unionFind.js +32 -0
  59. package/dist/orchestrator/unitBuilder.d.ts +2 -2
  60. package/dist/orchestrator/unitBuilder.js +4 -18
  61. package/dist/prompts/renderWorkerPrompt.js +18 -1
  62. package/dist/providers/claudeCodeProvider.d.ts +4 -4
  63. package/dist/providers/claudeCodeProvider.js +9 -3
  64. package/dist/providers/constants.d.ts +1 -1
  65. package/dist/providers/constants.js +1 -1
  66. package/dist/providers/index.d.ts +1 -2
  67. package/dist/providers/index.js +5 -4
  68. package/dist/providers/localSubprocessProvider.d.ts +2 -2
  69. package/dist/providers/localSubprocessProvider.js +1 -1
  70. package/dist/providers/opencodeProvider.d.ts +4 -4
  71. package/dist/providers/opencodeProvider.js +7 -2
  72. package/dist/providers/spawnLoggedCommand.d.ts +3 -1
  73. package/dist/providers/spawnLoggedCommand.js +21 -0
  74. package/dist/providers/subprocessTemplateProvider.d.ts +4 -4
  75. package/dist/providers/subprocessTemplateProvider.js +8 -3
  76. package/dist/providers/vscodeTaskProvider.d.ts +3 -4
  77. package/dist/providers/vscodeTaskProvider.js +2 -2
  78. package/dist/quota/discoveredLimits.js +1 -1
  79. package/dist/quota/hostLimits.d.ts +1 -2
  80. package/dist/quota/hostLimits.js +4 -46
  81. package/dist/quota/index.d.ts +18 -15
  82. package/dist/quota/index.js +4 -9
  83. package/dist/quota/scheduler.d.ts +1 -3
  84. package/dist/quota/scheduler.js +1 -2
  85. package/dist/reporting/synthesis.d.ts +1 -2
  86. package/dist/reporting/synthesis.js +2 -0
  87. package/dist/reporting/workBlocks.d.ts +1 -2
  88. package/dist/supervisor/operatorHandoff.js +1 -1
  89. package/dist/supervisor/runLedger.d.ts +1 -1
  90. package/dist/supervisor/runLedger.js +2 -2
  91. package/dist/supervisor/sessionConfig.d.ts +1 -1
  92. package/dist/supervisor/sessionConfig.js +1 -3
  93. package/dist/types/reviewPlanning.d.ts +1 -1
  94. package/dist/types/workerSession.d.ts +6 -0
  95. package/dist/validation/artifacts.d.ts +1 -1
  96. package/dist/validation/artifacts.js +1 -1
  97. package/dist/validation/auditResults.d.ts +1 -1
  98. package/dist/validation/auditResults.js +1 -1
  99. package/dist/validation/sessionConfig.d.ts +2 -3
  100. package/dist/validation/sessionConfig.js +2 -3
  101. package/package.json +4 -2
  102. package/scripts/postinstall.mjs +0 -1
  103. package/dist/io/json.d.ts +0 -10
  104. package/dist/io/json.js +0 -142
  105. package/dist/providers/types.d.ts +0 -33
  106. package/dist/providers/types.js +0 -1
  107. package/dist/quota/compositeQuotaSource.d.ts +0 -7
  108. package/dist/quota/compositeQuotaSource.js +0 -20
  109. package/dist/quota/errorParsers/claudeCodeErrorParser.d.ts +0 -6
  110. package/dist/quota/errorParsers/claudeCodeErrorParser.js +0 -39
  111. package/dist/quota/errorParsers/genericErrorParser.d.ts +0 -9
  112. package/dist/quota/errorParsers/genericErrorParser.js +0 -7
  113. package/dist/quota/errorParsers/index.d.ts +0 -5
  114. package/dist/quota/errorParsers/index.js +0 -12
  115. package/dist/quota/errorParsing.d.ts +0 -7
  116. package/dist/quota/errorParsing.js +0 -69
  117. package/dist/quota/fileLock.d.ts +0 -6
  118. package/dist/quota/fileLock.js +0 -64
  119. package/dist/quota/learnedQuotaSource.d.ts +0 -7
  120. package/dist/quota/learnedQuotaSource.js +0 -25
  121. package/dist/quota/limits.d.ts +0 -16
  122. package/dist/quota/limits.js +0 -77
  123. package/dist/quota/quotaSource.d.ts +0 -12
  124. package/dist/quota/quotaSource.js +0 -1
  125. package/dist/quota/slidingWindow.d.ts +0 -4
  126. package/dist/quota/slidingWindow.js +0 -28
  127. package/dist/quota/state.d.ts +0 -15
  128. package/dist/quota/state.js +0 -148
  129. package/dist/quota/types.d.ts +0 -67
  130. package/dist/quota/types.js +0 -1
  131. package/dist/reporting/rootCause.d.ts +0 -10
  132. package/dist/reporting/rootCause.js +0 -146
  133. package/dist/types/disposition.d.ts +0 -9
  134. package/dist/types/disposition.js +0 -1
  135. package/dist/types/flows.d.ts +0 -17
  136. package/dist/types/flows.js +0 -1
  137. package/dist/types/graph.d.ts +0 -22
  138. package/dist/types/graph.js +0 -1
  139. package/dist/types/risk.d.ts +0 -9
  140. package/dist/types/risk.js +0 -1
  141. package/dist/types/runLedger.d.ts +0 -17
  142. package/dist/types/runLedger.js +0 -6
  143. package/dist/types/sessionConfig.d.ts +0 -79
  144. package/dist/types/sessionConfig.js +0 -15
  145. package/dist/types/surfaces.d.ts +0 -15
  146. package/dist/types/surfaces.js +0 -1
  147. package/dist/validation/basic.d.ts +0 -13
  148. package/dist/validation/basic.js +0 -46
@@ -1,146 +0,0 @@
1
- function severityRank(severity) {
2
- switch (severity) {
3
- case "critical":
4
- return 5;
5
- case "high":
6
- return 4;
7
- case "medium":
8
- return 3;
9
- case "low":
10
- return 2;
11
- case "info":
12
- return 1;
13
- }
14
- }
15
- function summarizeRuntime(report) {
16
- if (!report) {
17
- return "No runtime validation evidence attached.";
18
- }
19
- const counts = new Map();
20
- for (const result of report.results) {
21
- counts.set(result.status, (counts.get(result.status) ?? 0) + 1);
22
- }
23
- return [...counts.entries()]
24
- .map(([status, count]) => `${status}:${count}`)
25
- .join(", ");
26
- }
27
- function summarizeExternal(results) {
28
- if (!results)
29
- return "No external analyzer signals attached.";
30
- return `${results.tool}:${results.results.length}`;
31
- }
32
- const STOP_WORDS = new Set([
33
- "a",
34
- "an",
35
- "and",
36
- "are",
37
- "be",
38
- "by",
39
- "for",
40
- "from",
41
- "in",
42
- "into",
43
- "is",
44
- "missing",
45
- "not",
46
- "of",
47
- "on",
48
- "or",
49
- "that",
50
- "the",
51
- "this",
52
- "to",
53
- "under",
54
- "when",
55
- "with",
56
- ]);
57
- function normalizeToken(token) {
58
- if (token.endsWith("ies") && token.length > 4) {
59
- return `${token.slice(0, -3)}y`;
60
- }
61
- if (token.endsWith("ing") && token.length > 6) {
62
- return token.slice(0, -3);
63
- }
64
- if (token.endsWith("ed") && token.length > 5) {
65
- return token.slice(0, -2);
66
- }
67
- if (token.endsWith("s") && token.length > 4) {
68
- return token.slice(0, -1);
69
- }
70
- return token;
71
- }
72
- function extractSemanticTerms(finding) {
73
- const source = [
74
- finding.title,
75
- finding.summary,
76
- ...(finding.evidence ?? []).slice(0, 2),
77
- ]
78
- .join(" ")
79
- .toLowerCase()
80
- .replace(/[^a-z0-9]+/g, " ");
81
- const terms = source
82
- .split(/\s+/)
83
- .map(normalizeToken)
84
- .filter((token) => token.length >= 3 &&
85
- !STOP_WORDS.has(token) &&
86
- token !== finding.lens &&
87
- token !== finding.category &&
88
- !/^\d+$/.test(token));
89
- return [...new Set(terms)];
90
- }
91
- function clusterKey(finding) {
92
- const semanticTerms = extractSemanticTerms(finding).slice(0, 3).join(" ");
93
- return `${finding.lens}:${finding.category}:${semanticTerms || finding.title.toLowerCase()}`;
94
- }
95
- function representativeFinding(grouped) {
96
- return [...grouped].sort((a, b) => {
97
- const severityDelta = severityRank(b.severity) - severityRank(a.severity);
98
- if (severityDelta !== 0)
99
- return severityDelta;
100
- const systemicDelta = Number(Boolean(b.systemic)) - Number(Boolean(a.systemic));
101
- if (systemicDelta !== 0)
102
- return systemicDelta;
103
- return a.title.localeCompare(b.title);
104
- })[0];
105
- }
106
- function titleForCluster(grouped) {
107
- return representativeFinding(grouped).title;
108
- }
109
- function summarizeFiles(grouped) {
110
- const paths = [
111
- ...new Set(grouped.flatMap((finding) => finding.affected_files.map((file) => file.path))),
112
- ].sort();
113
- if (paths.length === 0) {
114
- return "no representative files recorded";
115
- }
116
- if (paths.length <= 3) {
117
- return paths.join(", ");
118
- }
119
- return `${paths.slice(0, 3).join(", ")}, +${paths.length - 3} more`;
120
- }
121
- export function buildRootCauseClusters(findings, runtimeReport, externalAnalyzerResults) {
122
- const groups = new Map();
123
- for (const finding of findings) {
124
- const key = clusterKey(finding);
125
- const existing = groups.get(key) ?? [];
126
- existing.push(finding);
127
- groups.set(key, existing);
128
- }
129
- const runtimeSummary = summarizeRuntime(runtimeReport);
130
- const externalSummary = summarizeExternal(externalAnalyzerResults);
131
- return [...groups.entries()]
132
- .map(([key, grouped], index) => {
133
- const representative = representativeFinding(grouped);
134
- const systemicCount = grouped.filter((finding) => finding.systemic).length;
135
- const theme = key.split(":").slice(2).join(":") || representative.title;
136
- return {
137
- id: `cluster-${index + 1}`,
138
- title: titleForCluster(grouped),
139
- summary: `Theme "${theme}" groups ${grouped.length} finding(s) across ${summarizeFiles(grouped)}; ` +
140
- `highest severity ${representative.severity}; systemic flags ${systemicCount}. ` +
141
- `Runtime validation status: ${runtimeSummary}. External analyzer summary: ${externalSummary}.`,
142
- finding_ids: grouped.map((finding) => finding.id),
143
- };
144
- })
145
- .sort((a, b) => a.title.localeCompare(b.title));
146
- }
@@ -1,9 +0,0 @@
1
- export type FileDispositionStatus = "included" | "excluded" | "generated" | "vendor" | "binary" | "doc_only";
2
- export interface FileDispositionItem {
3
- path: string;
4
- status: FileDispositionStatus;
5
- reason?: string;
6
- }
7
- export interface FileDisposition {
8
- files: FileDispositionItem[];
9
- }
@@ -1 +0,0 @@
1
- export {};
@@ -1,17 +0,0 @@
1
- export declare const FLOW_CONFIDENCE_LEVELS: readonly ["high", "low"];
2
- export type FlowConfidenceLevel = (typeof FLOW_CONFIDENCE_LEVELS)[number];
3
- /** A critical user or system flow that must be covered by the audit. */
4
- export interface CriticalFlow {
5
- id: string;
6
- name: string;
7
- entrypoints: string[];
8
- paths: string[];
9
- concerns: string[];
10
- confidence?: FlowConfidenceLevel;
11
- notes?: string[];
12
- }
13
- /** The set of critical flows inferred from intake artifacts. */
14
- export interface CriticalFlowManifest {
15
- flows: CriticalFlow[];
16
- fallback_required?: boolean;
17
- }
@@ -1 +0,0 @@
1
- export const FLOW_CONFIDENCE_LEVELS = ["high", "low"];
@@ -1,22 +0,0 @@
1
- export interface GraphEdge {
2
- from: string;
3
- to: string;
4
- kind?: string;
5
- direction?: "directed" | "undirected";
6
- confidence?: number;
7
- reason?: string;
8
- }
9
- export interface RouteEdge {
10
- path: string;
11
- handler: string;
12
- method?: string;
13
- }
14
- export interface GraphBundle {
15
- graphs: {
16
- imports?: GraphEdge[];
17
- calls?: GraphEdge[];
18
- references?: GraphEdge[];
19
- routes?: RouteEdge[];
20
- [key: string]: unknown;
21
- };
22
- }
@@ -1 +0,0 @@
1
- export {};
@@ -1,9 +0,0 @@
1
- export interface RiskItem {
2
- unit_id: string;
3
- risk_score: number;
4
- signals: string[];
5
- notes?: string[];
6
- }
7
- export interface RiskRegister {
8
- items: RiskItem[];
9
- }
@@ -1 +0,0 @@
1
- export {};
@@ -1,17 +0,0 @@
1
- export declare const RUN_LEDGER_STATUSES: readonly ["completed", "blocked", "failed", "no_progress"];
2
- export type RunLedgerStatus = (typeof RUN_LEDGER_STATUSES)[number];
3
- /** One persisted supervisor run entry, including the terminal worker outcome. */
4
- export interface RunLedgerEntry {
5
- run_id: string;
6
- provider: string;
7
- obligation_id: string | null;
8
- selected_executor: string | null;
9
- status: RunLedgerStatus;
10
- started_at: string;
11
- ended_at: string;
12
- result_path: string;
13
- }
14
- /** Append-only ledger used to explain how the audit advanced over time. */
15
- export interface RunLedger {
16
- runs: RunLedgerEntry[];
17
- }
@@ -1,6 +0,0 @@
1
- export const RUN_LEDGER_STATUSES = [
2
- "completed",
3
- "blocked",
4
- "failed",
5
- "no_progress",
6
- ];
@@ -1,79 +0,0 @@
1
- export declare const PROVIDER_NAMES: readonly ["auto", "local-subprocess", "subprocess-template", "claude-code", "opencode", "vscode-task"];
2
- export type ProviderName = (typeof PROVIDER_NAMES)[number];
3
- export type ResolvedProviderName = Exclude<ProviderName, "auto">;
4
- export declare const SESSION_UI_MODES: readonly ["visible", "headless"];
5
- export type SessionUiMode = (typeof SESSION_UI_MODES)[number];
6
- export interface SubprocessTemplateConfig {
7
- command_template: string[];
8
- env?: Record<string, string>;
9
- }
10
- export interface ClaudeCodeConfig {
11
- command?: string;
12
- extra_args?: string[];
13
- dangerously_skip_permissions?: boolean;
14
- }
15
- export interface OpenCodeConfig {
16
- command?: string;
17
- extra_args?: string[];
18
- }
19
- export interface VSCodeTaskConfig {
20
- command_template: string[];
21
- env?: Record<string, string>;
22
- }
23
- export interface QuotaModelLimits {
24
- context_tokens?: number;
25
- output_tokens?: number;
26
- requests_per_minute?: number;
27
- input_tokens_per_minute?: number;
28
- output_tokens_per_minute?: number;
29
- }
30
- export interface QuotaConfig {
31
- /** Set to false to disable all quota scheduling (default: true). */
32
- enabled?: boolean;
33
- /** Whether to probe the provider for live limits (default: "auto"). */
34
- probe?: "auto" | "never" | "force";
35
- /** Fraction of known limits to actually use (default: 0.8). */
36
- safety_margin?: number;
37
- /** Concurrency ceiling for hosted providers with no learned data (default: 1). */
38
- unknown_hosted_concurrency?: number;
39
- /** Concurrency for local providers with no learned data (default: "unlimited"). */
40
- unknown_local_concurrency?: number | "unlimited";
41
- /** Assumed context window when the model is not recognized (default: 32000). */
42
- default_context_tokens?: number;
43
- /** Tokens reserved for model output per request (default: 4096). */
44
- reserved_output_tokens?: number;
45
- /** Half-life of empirical success/failure evidence in hours (default: 24). */
46
- empirical_half_life_hours?: number;
47
- /** Allow the scheduler to try concurrency maxSafe+1 after consecutive successes (default: true). */
48
- ramp_up_enabled?: boolean;
49
- /** Conservative concurrency cap for the first wave when no learned history
50
- * and no discovered RPM/TPM limits exist (default: 3). */
51
- first_contact_concurrency?: number;
52
- /** Hard host ceiling for simultaneously active conversation subagents. */
53
- host_active_subagent_limit?: number;
54
- /** Per-model overrides keyed by "provider/model". */
55
- models?: Record<string, QuotaModelLimits>;
56
- }
57
- export declare const PROVIDER_SECTION_KEYS: {
58
- readonly "subprocess-template": "subprocess_template";
59
- readonly "claude-code": "claude_code";
60
- readonly opencode: "opencode";
61
- readonly "vscode-task": "vscode_task";
62
- };
63
- /**
64
- * Provider names use CLI-friendly hyphenation, while nested provider config
65
- * sections stay snake_case because they serialize directly into JSON files.
66
- */
67
- export interface SessionConfig {
68
- provider?: ProviderName;
69
- timeout_ms?: number;
70
- ui_mode?: SessionUiMode;
71
- host_can_dispatch_subagents?: boolean;
72
- subprocess_template?: SubprocessTemplateConfig;
73
- claude_code?: ClaudeCodeConfig;
74
- opencode?: OpenCodeConfig;
75
- vscode_task?: VSCodeTaskConfig;
76
- agent_task_batch_size?: number;
77
- parallel_workers?: number;
78
- quota?: QuotaConfig;
79
- }
@@ -1,15 +0,0 @@
1
- export const PROVIDER_NAMES = [
2
- "auto",
3
- "local-subprocess",
4
- "subprocess-template",
5
- "claude-code",
6
- "opencode",
7
- "vscode-task",
8
- ];
9
- export const SESSION_UI_MODES = ["visible", "headless"];
10
- export const PROVIDER_SECTION_KEYS = {
11
- "subprocess-template": "subprocess_template",
12
- "claude-code": "claude_code",
13
- opencode: "opencode",
14
- "vscode-task": "vscode_task",
15
- };
@@ -1,15 +0,0 @@
1
- export declare const SURFACE_KINDS: readonly ["interface", "background"];
2
- export type SurfaceKind = (typeof SURFACE_KINDS)[number];
3
- /** Discovered execution surfaces that define where the product can be reached. */
4
- export interface SurfaceRecord {
5
- id: string;
6
- kind: SurfaceKind;
7
- entrypoint: string;
8
- exposure?: string;
9
- methods?: string[];
10
- notes?: string[];
11
- }
12
- /** Intake output that summarizes externally reachable product surfaces. */
13
- export interface SurfaceManifest {
14
- surfaces: SurfaceRecord[];
15
- }
@@ -1 +0,0 @@
1
- export const SURFACE_KINDS = ["interface", "background"];
@@ -1,13 +0,0 @@
1
- export type ValidationSeverity = "error" | "warning";
2
- export interface ValidationIssue {
3
- path: string;
4
- message: string;
5
- severity: ValidationSeverity;
6
- }
7
- export declare function describeValue(value: unknown): string;
8
- export declare function isRecord(value: unknown): value is Record<string, unknown>;
9
- export declare function createValidationIssue(path: string, message: string, severity?: ValidationSeverity): ValidationIssue;
10
- export declare function pushValidationIssue(issues: ValidationIssue[], path: string, message: string, severity?: ValidationSeverity): void;
11
- export declare function prefixValidationIssues(prefix: string, issues: ValidationIssue[]): ValidationIssue[];
12
- export declare function formatValidationIssues(issues: ValidationIssue[]): string;
13
- export declare function requireKeys(value: unknown, path: string, keys: readonly string[]): ValidationIssue[];
@@ -1,46 +0,0 @@
1
- export function describeValue(value) {
2
- if (Array.isArray(value)) {
3
- return "array";
4
- }
5
- if (value === null) {
6
- return "null";
7
- }
8
- return typeof value;
9
- }
10
- export function isRecord(value) {
11
- return typeof value === "object" && value !== null && !Array.isArray(value);
12
- }
13
- export function createValidationIssue(path, message, severity = "error") {
14
- return { path, message, severity };
15
- }
16
- export function pushValidationIssue(issues, path, message, severity = "error") {
17
- issues.push(createValidationIssue(path, message, severity));
18
- }
19
- export function prefixValidationIssues(prefix, issues) {
20
- return issues.map((issue) => ({
21
- ...issue,
22
- path: issue.path.length === 0
23
- ? prefix
24
- : issue.path === prefix || issue.path.startsWith(`${prefix}.`)
25
- ? issue.path
26
- : `${prefix}.${issue.path}`,
27
- }));
28
- }
29
- export function formatValidationIssues(issues) {
30
- return issues
31
- .map((issue) => ` [${issue.severity}] ${issue.path}: ${issue.message}`)
32
- .join("\n");
33
- }
34
- export function requireKeys(value, path, keys) {
35
- const issues = [];
36
- if (!isRecord(value)) {
37
- pushValidationIssue(issues, path, `Expected an object, got ${describeValue(value)}.`);
38
- return issues;
39
- }
40
- for (const key of keys) {
41
- if (!(key in value)) {
42
- pushValidationIssue(issues, path, `Missing required key: ${key}`);
43
- }
44
- }
45
- return issues;
46
- }