open-local-audit 0.61.0 → 0.63.0

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.
@@ -0,0 +1,85 @@
1
+ import { runDiscovery, type DiscoveryRunResult } from "./discovery-runner.js";
2
+ import { packageReport, type ReportPackResult } from "./report-pack.js";
3
+ import { summarizeReviewCsvFile } from "./review.js";
4
+ import { type ShortlistRunOptions } from "./shortlist-runner.js";
5
+ import type { ShortlistResult } from "./shortlist.js";
6
+ import { resolveGoogleMapsApiKey } from "./secrets.js";
7
+ import { readWorkflowConfig, type ResolvedWorkflowConfig, type WorkflowManagedPaths } from "./workflow-config.js";
8
+ export type WorkflowStatus = "success" | "failed";
9
+ export type WorkflowStageStatus = "success" | "failed" | "skipped" | "not-run";
10
+ export type WorkflowStageName = "discovery" | "shortlist" | "review" | "packaging";
11
+ export type WorkflowPackageStatus = "packaged" | "skipped" | "failed";
12
+ export interface WorkflowDiscoveryStageSummary {
13
+ status: WorkflowStageStatus;
14
+ totalCandidates?: number;
15
+ suppressedCandidates?: number;
16
+ audited?: number;
17
+ }
18
+ export interface WorkflowShortlistStageSummary {
19
+ status: WorkflowStageStatus;
20
+ totalRows?: number;
21
+ suppressedRows?: number;
22
+ filteredRows?: number;
23
+ selected?: number;
24
+ }
25
+ export interface WorkflowReviewStageSummary {
26
+ status: WorkflowStageStatus;
27
+ totalRows?: number;
28
+ reviewedRows?: number;
29
+ actionableLeads?: number;
30
+ staleRows?: number;
31
+ invalidReviewedAtRows?: number;
32
+ unreviewedRows?: number;
33
+ }
34
+ export interface WorkflowPackagingStageSummary {
35
+ status: WorkflowStageStatus;
36
+ packaged?: number;
37
+ skipped?: number;
38
+ failed?: number;
39
+ }
40
+ export interface WorkflowPackageEntry {
41
+ leadKey: string;
42
+ companyName: string;
43
+ status: WorkflowPackageStatus;
44
+ outDir?: string;
45
+ error?: string;
46
+ }
47
+ export interface WorkflowPackageSummary {
48
+ packaged: number;
49
+ skipped: number;
50
+ failed: number;
51
+ entries: WorkflowPackageEntry[];
52
+ }
53
+ export interface WorkflowSummary {
54
+ version: 1;
55
+ status: WorkflowStatus;
56
+ stages: {
57
+ discovery: WorkflowDiscoveryStageSummary;
58
+ shortlist: WorkflowShortlistStageSummary;
59
+ review: WorkflowReviewStageSummary;
60
+ packaging: WorkflowPackagingStageSummary;
61
+ };
62
+ outputs: WorkflowManagedPaths;
63
+ discoveredLeads: number;
64
+ selectedLeads: number;
65
+ packages: WorkflowPackageSummary;
66
+ error?: {
67
+ stage: WorkflowStageName;
68
+ message: string;
69
+ };
70
+ }
71
+ export interface WorkflowDependencies {
72
+ readWorkflowConfig: typeof readWorkflowConfig;
73
+ runDiscovery: (options: Parameters<typeof runDiscovery>[0]) => Promise<DiscoveryRunResult>;
74
+ runShortlistReport: (options: ShortlistRunOptions) => Promise<ShortlistResult>;
75
+ summarizeReviewCsvFile: typeof summarizeReviewCsvFile;
76
+ packageReport: (options: Parameters<typeof packageReport>[0]) => Promise<ReportPackResult>;
77
+ resolveGoogleMapsApiKey: typeof resolveGoogleMapsApiKey;
78
+ }
79
+ export declare class WorkflowRunError extends Error {
80
+ readonly summary: WorkflowSummary;
81
+ constructor(summary: WorkflowSummary);
82
+ }
83
+ export declare function safeLeadSlug(leadKey: string, companyName: string): string;
84
+ export declare function runWorkflow(configPath: string, dependencies?: Partial<WorkflowDependencies>): Promise<WorkflowSummary>;
85
+ export declare function runResolvedWorkflow(config: ResolvedWorkflowConfig, dependencies?: Partial<WorkflowDependencies>): Promise<WorkflowSummary>;
@@ -0,0 +1,382 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { lstat, mkdir, mkdtemp, realpath, rename, rm } from "node:fs/promises";
3
+ import { dirname, isAbsolute, join, relative, resolve } from "node:path";
4
+ import { runDiscovery } from "./discovery-runner.js";
5
+ import { packageReport } from "./report-pack.js";
6
+ import { summarizeReviewCsvFile } from "./review.js";
7
+ import { runShortlistReport } from "./shortlist-runner.js";
8
+ import { resolveGoogleMapsApiKey } from "./secrets.js";
9
+ import { readWorkflowConfig } from "./workflow-config.js";
10
+ import { prepareWorkflowManagedDirectories } from "./workflow-paths.js";
11
+ import { writeWorkflowOutputFile } from "./workflow-output.js";
12
+ export class WorkflowRunError extends Error {
13
+ summary;
14
+ constructor(summary) {
15
+ super(summary.error ? `Workflow failed during ${summary.error.stage}: ${summary.error.message}` : "Workflow failed");
16
+ this.name = "WorkflowRunError";
17
+ this.summary = summary;
18
+ }
19
+ }
20
+ const defaultDependencies = {
21
+ readWorkflowConfig,
22
+ runDiscovery,
23
+ runShortlistReport,
24
+ summarizeReviewCsvFile,
25
+ packageReport,
26
+ resolveGoogleMapsApiKey
27
+ };
28
+ const packageReportFileNames = [
29
+ "open-local-audit-report.json",
30
+ "open-local-audit-report.md",
31
+ "open-local-audit-report.html",
32
+ "open-local-audit-report.pdf"
33
+ ];
34
+ function slugify(value) {
35
+ return value
36
+ .normalize("NFKD")
37
+ .replace(/[\u0300-\u036f]/g, "")
38
+ .replace(/[^A-Za-z0-9]+/g, "-")
39
+ .replace(/^-+|-+$/g, "")
40
+ .toLowerCase();
41
+ }
42
+ function slugSourceFromLeadKey(leadKey) {
43
+ const trimmed = leadKey.trim();
44
+ if (!trimmed) {
45
+ return "";
46
+ }
47
+ if (trimmed.startsWith("url:")) {
48
+ const rawUrl = trimmed.slice(4);
49
+ try {
50
+ const parsed = new URL(rawUrl);
51
+ return `${parsed.hostname}${parsed.pathname}`.replace(/^www\./i, "");
52
+ }
53
+ catch {
54
+ return rawUrl;
55
+ }
56
+ }
57
+ return trimmed.replace(/^[a-z]+:/i, "");
58
+ }
59
+ export function safeLeadSlug(leadKey, companyName) {
60
+ const companySlug = slugify(companyName.trim());
61
+ if (companySlug) {
62
+ return companySlug;
63
+ }
64
+ const leadSlug = slugify(slugSourceFromLeadKey(leadKey));
65
+ return leadSlug || "lead";
66
+ }
67
+ function sanitizeErrorMessage(error, knownSecrets = []) {
68
+ if (error instanceof Error) {
69
+ const message = error.message.trim();
70
+ return knownSecrets
71
+ .filter((secret) => secret.length > 0)
72
+ .reduce((sanitized, secret) => sanitized.split(secret).join("[REDACTED]"), message || "Unknown error");
73
+ }
74
+ return "Unknown error";
75
+ }
76
+ function createInitialSummary(config) {
77
+ return {
78
+ version: 1,
79
+ status: "success",
80
+ stages: {
81
+ discovery: { status: "not-run" },
82
+ shortlist: { status: "not-run" },
83
+ review: { status: config.review ? "not-run" : "skipped" },
84
+ packaging: { status: config.packageReports ? "not-run" : "skipped" }
85
+ },
86
+ outputs: config.paths,
87
+ discoveredLeads: 0,
88
+ selectedLeads: 0,
89
+ packages: {
90
+ packaged: 0,
91
+ skipped: 0,
92
+ failed: 0,
93
+ entries: []
94
+ }
95
+ };
96
+ }
97
+ async function writePrettyJson(path, value) {
98
+ await mkdir(dirname(path), { recursive: true });
99
+ await writeWorkflowOutputFile(path, `${JSON.stringify(value, null, 2)}\n`);
100
+ }
101
+ async function writeWorkflowSummary(summary) {
102
+ await writePrettyJson(summary.outputs.workflowSummaryJson, summary);
103
+ }
104
+ async function throwPersistedWorkflowFailure(summary) {
105
+ try {
106
+ await writeWorkflowSummary(summary);
107
+ }
108
+ catch {
109
+ // The workflow failure remains authoritative if its summary cannot be persisted.
110
+ }
111
+ throw new WorkflowRunError(summary);
112
+ }
113
+ async function throwStageFailure(summary, stage, error, knownSecrets) {
114
+ markStageFailure(summary, stage, sanitizeErrorMessage(error, knownSecrets));
115
+ return throwPersistedWorkflowFailure(summary);
116
+ }
117
+ function markStageFailure(summary, stage, message) {
118
+ summary.status = "failed";
119
+ summary.stages[stage].status = "failed";
120
+ summary.error = {
121
+ stage,
122
+ message
123
+ };
124
+ }
125
+ async function resolvePackageInputDir(reportsDir, reportPath) {
126
+ const resolvedReportPath = resolve(reportsDir, reportPath);
127
+ const relativePath = relative(reportsDir, resolvedReportPath);
128
+ if (!relativePath || relativePath.startsWith("..") || isAbsolute(relativePath)) {
129
+ throw new Error("Report path escapes reports directory");
130
+ }
131
+ const inputDir = dirname(resolvedReportPath);
132
+ const [realReportsDir, realInputDir] = await Promise.all([realpath(reportsDir), realpath(inputDir)]);
133
+ const realRelativePath = relative(realReportsDir, realInputDir);
134
+ if (realRelativePath.startsWith("..") || isAbsolute(realRelativePath)) {
135
+ throw new Error("Report path escapes reports directory");
136
+ }
137
+ return { inputDir, realInputDir };
138
+ }
139
+ async function validatePackageSourceFiles(inputDir, realInputDir) {
140
+ for (const fileName of packageReportFileNames) {
141
+ const sourcePath = join(inputDir, fileName);
142
+ try {
143
+ const sourceInfo = await lstat(sourcePath);
144
+ if (sourceInfo.isSymbolicLink()) {
145
+ throw new Error("Linked report files are not allowed");
146
+ }
147
+ const realSourcePath = await realpath(sourcePath);
148
+ const relativeSourcePath = relative(realInputDir, realSourcePath);
149
+ if (relativeSourcePath.startsWith("..") || isAbsolute(relativeSourcePath)) {
150
+ throw new Error("Report file escapes input directory");
151
+ }
152
+ }
153
+ catch (error) {
154
+ if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
155
+ continue;
156
+ }
157
+ throw error;
158
+ }
159
+ }
160
+ }
161
+ async function promotePackage(tempDir, finalDir) {
162
+ const backupDir = `${finalDir}.backup-${randomUUID()}`;
163
+ let hasBackup = false;
164
+ let promoted = false;
165
+ try {
166
+ await rename(finalDir, backupDir);
167
+ hasBackup = true;
168
+ }
169
+ catch (error) {
170
+ if (!(error && typeof error === "object" && "code" in error && error.code === "ENOENT")) {
171
+ throw error;
172
+ }
173
+ }
174
+ try {
175
+ await rename(tempDir, finalDir);
176
+ promoted = true;
177
+ if (hasBackup) {
178
+ await rm(backupDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 50 });
179
+ }
180
+ }
181
+ catch (error) {
182
+ if (hasBackup) {
183
+ try {
184
+ if (promoted) {
185
+ await rename(finalDir, tempDir);
186
+ }
187
+ await rename(backupDir, finalDir);
188
+ }
189
+ catch {
190
+ // Best-effort rollback; the original promotion error remains authoritative.
191
+ }
192
+ }
193
+ throw error;
194
+ }
195
+ }
196
+ function updateDiscoveryStage(summary, result) {
197
+ summary.stages.discovery = {
198
+ status: "success",
199
+ totalCandidates: result.summary.totalCandidates,
200
+ suppressedCandidates: result.summary.suppressedCandidates,
201
+ audited: result.summary.audited
202
+ };
203
+ summary.discoveredLeads = result.rows.length;
204
+ }
205
+ function updateShortlistStage(summary, result) {
206
+ summary.stages.shortlist = {
207
+ status: "success",
208
+ totalRows: result.totalRows,
209
+ suppressedRows: result.suppressedRows,
210
+ filteredRows: result.filteredRows,
211
+ selected: result.selected
212
+ };
213
+ summary.selectedLeads = result.selected;
214
+ }
215
+ function updateReviewStage(summary, result) {
216
+ summary.stages.review = {
217
+ status: "success",
218
+ totalRows: result.totalRows,
219
+ reviewedRows: result.reviewedRows,
220
+ actionableLeads: result.actionableLeadKeys.length,
221
+ staleRows: result.staleRows,
222
+ invalidReviewedAtRows: result.invalidReviewedAtRows,
223
+ unreviewedRows: result.unreviewedRows
224
+ };
225
+ }
226
+ function updatePackagingStage(summary) {
227
+ summary.stages.packaging = {
228
+ status: summary.packages.failed > 0 ? "failed" : "success",
229
+ packaged: summary.packages.packaged,
230
+ skipped: summary.packages.skipped,
231
+ failed: summary.packages.failed
232
+ };
233
+ }
234
+ function nextUniqueSlug(baseSlug, counts) {
235
+ const nextCount = (counts.get(baseSlug) ?? 0) + 1;
236
+ counts.set(baseSlug, nextCount);
237
+ return nextCount === 1 ? baseSlug : `${baseSlug}-${nextCount}`;
238
+ }
239
+ export async function runWorkflow(configPath, dependencies = {}) {
240
+ const resolvedDependencies = {
241
+ ...defaultDependencies,
242
+ ...dependencies
243
+ };
244
+ const config = await resolvedDependencies.readWorkflowConfig(configPath);
245
+ return await runResolvedWorkflow(config, resolvedDependencies);
246
+ }
247
+ export async function runResolvedWorkflow(config, dependencies = {}) {
248
+ const resolvedDependencies = {
249
+ ...defaultDependencies,
250
+ ...dependencies
251
+ };
252
+ await prepareWorkflowManagedDirectories(config);
253
+ const summary = createInitialSummary(config);
254
+ const knownSecrets = [];
255
+ try {
256
+ const googleApiKey = config.discovery.provider === "google-places" ? resolvedDependencies.resolveGoogleMapsApiKey() : undefined;
257
+ if (googleApiKey) {
258
+ knownSecrets.push(googleApiKey);
259
+ }
260
+ const discoveryResult = await resolvedDependencies.runDiscovery({
261
+ provider: config.discovery.provider,
262
+ ...(config.discovery.provider === "manual-csv" ? { input: config.discovery.input } : {}),
263
+ ...(config.discovery.provider === "google-places" ? { query: config.discovery.query } : {}),
264
+ profile: config.discovery.profile,
265
+ outDir: config.paths.reportsDir,
266
+ managedOutputRoot: config.paths.reportsDir,
267
+ exportCsv: config.paths.leadsCsv,
268
+ summaryJson: config.paths.discoverySummaryJson,
269
+ reviewCsv: config.review?.csv,
270
+ dryRun: false,
271
+ concurrency: config.discovery.concurrency,
272
+ ...(config.discovery.maxAudits !== undefined ? { maxAudits: config.discovery.maxAudits } : {}),
273
+ ...(config.discovery.provider === "google-places" ? { limit: config.discovery.limit } : {}),
274
+ ...(googleApiKey !== undefined ? { apiKey: googleApiKey } : {})
275
+ });
276
+ updateDiscoveryStage(summary, discoveryResult);
277
+ const shortlistResult = await resolvedDependencies.runShortlistReport({
278
+ input: config.paths.leadsCsv,
279
+ out: config.paths.shortlistCsv,
280
+ summaryJson: config.paths.shortlistSummaryJson,
281
+ reviewCsv: config.review?.csv,
282
+ format: "csv",
283
+ shortlist: config.shortlist
284
+ });
285
+ updateShortlistStage(summary, shortlistResult);
286
+ if (config.review) {
287
+ const reviewSummary = await resolvedDependencies.summarizeReviewCsvFile(config.review.csv, {
288
+ staleBefore: config.review.staleBefore
289
+ });
290
+ await writePrettyJson(config.paths.reviewSummaryJson, reviewSummary);
291
+ updateReviewStage(summary, reviewSummary);
292
+ }
293
+ if (config.packageReports) {
294
+ const slugCounts = new Map();
295
+ for (const lead of shortlistResult.leads) {
296
+ const packageEntry = await packageLead(config.paths, lead, slugCounts, resolvedDependencies.packageReport, knownSecrets);
297
+ summary.packages.entries.push(packageEntry);
298
+ if (packageEntry.status === "packaged") {
299
+ summary.packages.packaged += 1;
300
+ }
301
+ else if (packageEntry.status === "skipped") {
302
+ summary.packages.skipped += 1;
303
+ }
304
+ else {
305
+ summary.packages.failed += 1;
306
+ }
307
+ }
308
+ updatePackagingStage(summary);
309
+ if (summary.packages.failed > 0) {
310
+ summary.status = "failed";
311
+ summary.error = {
312
+ stage: "packaging",
313
+ message: `${summary.packages.failed} package ${summary.packages.failed === 1 ? "entry" : "entries"} failed`
314
+ };
315
+ await throwPersistedWorkflowFailure(summary);
316
+ }
317
+ }
318
+ }
319
+ catch (error) {
320
+ if (error instanceof WorkflowRunError) {
321
+ throw error;
322
+ }
323
+ const failedStage = summary.stages.discovery.status !== "success"
324
+ ? "discovery"
325
+ : summary.stages.shortlist.status !== "success"
326
+ ? "shortlist"
327
+ : summary.stages.review.status === "not-run"
328
+ ? "review"
329
+ : "packaging";
330
+ await throwStageFailure(summary, failedStage, error, knownSecrets);
331
+ }
332
+ await writeWorkflowSummary(summary);
333
+ return summary;
334
+ }
335
+ async function packageLead(paths, lead, slugCounts, packageReportDependency, knownSecrets) {
336
+ const reportPath = lead.reportPath.trim();
337
+ if (!reportPath) {
338
+ return {
339
+ leadKey: lead.leadKey,
340
+ companyName: lead.companyName,
341
+ status: "skipped"
342
+ };
343
+ }
344
+ const slug = nextUniqueSlug(safeLeadSlug(lead.leadKey, lead.companyName), slugCounts);
345
+ const outDir = join(paths.packagesDir, slug);
346
+ let tempDir;
347
+ try {
348
+ const { inputDir, realInputDir } = await resolvePackageInputDir(paths.reportsDir, reportPath);
349
+ await validatePackageSourceFiles(inputDir, realInputDir);
350
+ await mkdir(paths.packagesDir, { recursive: true });
351
+ tempDir = await mkdtemp(join(paths.packagesDir, `.${slug}-tmp-`));
352
+ await packageReportDependency({
353
+ inputDir,
354
+ outDir: tempDir
355
+ });
356
+ await promotePackage(tempDir, outDir);
357
+ tempDir = undefined;
358
+ return {
359
+ leadKey: lead.leadKey,
360
+ companyName: lead.companyName,
361
+ status: "packaged",
362
+ outDir
363
+ };
364
+ }
365
+ catch (error) {
366
+ if (tempDir) {
367
+ try {
368
+ await rm(tempDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 50 });
369
+ }
370
+ catch {
371
+ // Keep the package failure useful even if temporary output cannot be removed.
372
+ }
373
+ }
374
+ return {
375
+ leadKey: lead.leadKey,
376
+ companyName: lead.companyName,
377
+ status: "failed",
378
+ error: sanitizeErrorMessage(error, knownSecrets)
379
+ };
380
+ }
381
+ }
382
+ //# sourceMappingURL=workflow.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workflow.js","sourceRoot":"","sources":["../src/workflow.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,106 @@
1
+ # Workflow Command
2
+
3
+ ## Purpose
4
+
5
+ The `workflow` command runs the existing local lead discovery, shortlist, review summary, and report packaging capabilities from one versioned JSON configuration file. It writes local artifacts; it does not send outreach, synchronize CRM records, or upload reports. A Google Places discovery run is the exception to local processing: it calls the configured Google service through the existing provider.
6
+
7
+ ```bash
8
+ open-local-audit workflow --config workflow.json
9
+ ```
10
+
11
+ The command does not store Google Places API keys. The Google provider resolves its key through the existing key resolver at runtime.
12
+
13
+ ## Configuration
14
+
15
+ The configuration uses a strict JSON contract. Only version `1` is accepted. Relative `outDir`, manual CSV `input`, and optional review `csv` paths are resolved from the configuration file directory.
16
+
17
+ ```json
18
+ {
19
+ "version": 1,
20
+ "outDir": "./workflow-output",
21
+ "discovery": {
22
+ "provider": "manual-csv",
23
+ "input": "./places.csv",
24
+ "profile": "dental",
25
+ "concurrency": 2,
26
+ "maxAudits": 10
27
+ },
28
+ "shortlist": {
29
+ "top": 10,
30
+ "minOpportunityScore": 70,
31
+ "sort": "opportunity-desc"
32
+ },
33
+ "review": {
34
+ "csv": "./review.csv",
35
+ "staleBefore": "2026-07-01"
36
+ },
37
+ "packageReports": true
38
+ }
39
+ ```
40
+
41
+ `version`, `outDir`, `discovery`, and `shortlist` are required. `review` and `packageReports` are optional. Unknown fields and invalid values are rejected before output files are created. `manual-csv` discovery requires `input`; `google-places` discovery requires `query` and resolves `GOOGLE_MAPS_API_KEY` only for that provider.
42
+
43
+ The discovery object accepts either the existing `manual-csv` input or the existing `google-places` provider and its query. Google Places workflows continue to require `GOOGLE_MAPS_API_KEY`, display the billing warning, and use the existing candidate and audit limits.
44
+
45
+ ## Managed Outputs
46
+
47
+ The command writes predictable paths below `outDir`:
48
+
49
+ - `leads.csv`
50
+ - `discovery-summary.json`
51
+ - `shortlist.csv`
52
+ - `shortlist-summary.json`
53
+ - `review-summary.json` when review configuration is present
54
+ - `packages/<safe-lead-slug>/` for selected leads with successful report artifacts when packaging is enabled
55
+ - `workflow-summary.json`
56
+
57
+ The same configuration resolves to the same managed output paths, so rerun destinations are deterministic. Reruns replace only those managed outputs and do not delete unrelated files. Existing operator decisions in the configured review CSV remain authoritative and are preserved by the discovery merge behavior.
58
+
59
+ ## Execution Model
60
+
61
+ The workflow runs these dependent stages in order:
62
+
63
+ ```text
64
+ discovery
65
+ -> shortlist
66
+ -> review summary (optional)
67
+ -> selected report packages (optional)
68
+ -> workflow summary
69
+ ```
70
+
71
+ The implementation reuses the same application services as the individual CLI commands. It does not duplicate discovery, ranking, review, or packaging rules.
72
+
73
+ Configuration validation completes before any output is written and returns exit code `1` when invalid. A discovery, shortlist, or review summary failure stops dependent stages and returns exit code `1`. Report packages are independent: one package failure does not prevent other selected reports from being packaged, but the final workflow status is `failed` and the command returns exit code `1`.
74
+
75
+ A selected lead without a successful report path is recorded as `skipped`, not failed.
76
+ Package directory names use a deterministic, file-system-safe slug derived from the selected lead identity; raw lead keys are not used as paths.
77
+
78
+ ## Workflow Summary
79
+
80
+ `workflow-summary.json` is written after configuration has been validated, the output directory has been prepared, and execution reaches a managed workflow stage. It contains:
81
+
82
+ - overall `status`;
83
+ - per-stage status and counts;
84
+ - generated output paths;
85
+ - discovered and selected lead counts;
86
+ - package success, skipped, and failure counts;
87
+ - sanitized failure messages.
88
+
89
+ The summary does not include the full configuration, environment variables, API keys, raw Google Places responses, or website response bodies.
90
+
91
+ ## Compatibility
92
+
93
+ The existing `discover`, `shortlist`, `review`, and `package-report` commands keep their current flags, outputs, and exit behavior. The workflow command is additive and uses configuration contract version `1` so future incompatible configuration changes can be rejected explicitly. `workflow --check` is also additive: it preflights the same configuration without changing normal workflow execution. Its read-only behavior, exit semantics, and versioned report contract are defined in the [workflow preflight contract](./workflow-preflight.md).
94
+
95
+ ## Verification
96
+
97
+ Coverage for this command includes:
98
+
99
+ - strict configuration validation and unknown-field rejection;
100
+ - configuration-relative path resolution;
101
+ - a successful manual CSV workflow using local fixtures;
102
+ - fail-fast behavior for discovery, shortlist, and review summary failures;
103
+ - partial package failure reporting and exit behavior;
104
+ - review decision preservation across repeated runs;
105
+ - CLI help and invalid-configuration errors;
106
+ - the full release gate, including build, dependency audit, package dry run, and clean consumer installation.