hadara 0.1.0-rc.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.
- package/LICENSE +21 -0
- package/README.md +109 -0
- package/dist/agent/evidence.js +50 -0
- package/dist/agent/loop.js +124 -0
- package/dist/cli/args.js +70 -0
- package/dist/cli/dashboard.js +185 -0
- package/dist/cli/debt.js +41 -0
- package/dist/cli/doctor.js +68 -0
- package/dist/cli/errors.js +58 -0
- package/dist/cli/evidence-json.js +75 -0
- package/dist/cli/evidence.js +80 -0
- package/dist/cli/handoff.js +16 -0
- package/dist/cli/harness.js +57 -0
- package/dist/cli/hermes-json.js +31 -0
- package/dist/cli/hermes.js +28 -0
- package/dist/cli/init.js +142 -0
- package/dist/cli/install.js +34 -0
- package/dist/cli/main.js +216 -0
- package/dist/cli/mcp.js +15 -0
- package/dist/cli/package-smoke.js +37 -0
- package/dist/cli/policy-json.js +22 -0
- package/dist/cli/policy.js +43 -0
- package/dist/cli/release-artifact.js +47 -0
- package/dist/cli/release-dry-run.js +24 -0
- package/dist/cli/release-gate.js +28 -0
- package/dist/cli/release-publish.js +41 -0
- package/dist/cli/run-scaffold.js +68 -0
- package/dist/cli/run-state.js +41 -0
- package/dist/cli/run.js +191 -0
- package/dist/cli/smoke.js +58 -0
- package/dist/cli/status-json.js +6 -0
- package/dist/cli/status.js +26 -0
- package/dist/cli/task-json.js +8 -0
- package/dist/cli/task.js +64 -0
- package/dist/cli/tools.js +25 -0
- package/dist/cli/tui.js +72 -0
- package/dist/cli/write-preflight.js +27 -0
- package/dist/core/audit.js +41 -0
- package/dist/core/events.js +63 -0
- package/dist/core/fs.js +44 -0
- package/dist/core/paths.js +59 -0
- package/dist/core/redaction.js +178 -0
- package/dist/core/schema.js +253 -0
- package/dist/core/workspace.js +47 -0
- package/dist/evidence/evidence.js +170 -0
- package/dist/evidence/private-manifest.js +101 -0
- package/dist/handoff/handoff.js +49 -0
- package/dist/harness/replay.js +200 -0
- package/dist/harness/validate.js +465 -0
- package/dist/hermes/context-export.js +104 -0
- package/dist/index.js +29 -0
- package/dist/mcp/server.js +104 -0
- package/dist/mcp/tool-dispatch.js +159 -0
- package/dist/mcp/tool-registry.js +150 -0
- package/dist/mcp/tool-schemas.js +18 -0
- package/dist/policy/command-risk.js +39 -0
- package/dist/policy/permission-matrix.js +42 -0
- package/dist/policy/policy.js +20 -0
- package/dist/policy/preflight.js +47 -0
- package/dist/policy/presets.js +24 -0
- package/dist/policy/tokenizer.js +53 -0
- package/dist/providers/fallback-executor.js +46 -0
- package/dist/providers/mock-provider.js +49 -0
- package/dist/providers/provider-contract.js +2 -0
- package/dist/providers/provider-preparation.js +220 -0
- package/dist/providers/scripted-provider.js +69 -0
- package/dist/schemas/active-run-projection.schema.json +73 -0
- package/dist/schemas/active-run-resume.schema.json +68 -0
- package/dist/schemas/clean-checkout-smoke.schema.json +126 -0
- package/dist/schemas/context-export.schema.json +35 -0
- package/dist/schemas/event.schema.json +17 -0
- package/dist/schemas/evidence-list.schema.json +49 -0
- package/dist/schemas/feature-smoke.schema.json +67 -0
- package/dist/schemas/install-plan.schema.json +93 -0
- package/dist/schemas/package-smoke.schema.json +130 -0
- package/dist/schemas/private-evidence.schema.json +48 -0
- package/dist/schemas/provider-call.schema.json +42 -0
- package/dist/schemas/provider-config.schema.json +43 -0
- package/dist/schemas/release-artifact-manifest.schema.json +55 -0
- package/dist/schemas/release-artifact.schema.json +140 -0
- package/dist/schemas/release-dry-run.schema.json +141 -0
- package/dist/schemas/release-gate.schema.json +42 -0
- package/dist/schemas/release-publish.schema.json +114 -0
- package/dist/schemas/schema-index.json +145 -0
- package/dist/schemas/smoke-evidence-summary.schema.json +88 -0
- package/dist/schemas/tools-list.schema.json +78 -0
- package/dist/schemas/write-preflight.schema.json +47 -0
- package/dist/services/active-run-state.js +215 -0
- package/dist/services/capability-registry.js +540 -0
- package/dist/services/clean-checkout-smoke.js +393 -0
- package/dist/services/evidence-list.js +136 -0
- package/dist/services/feature-smoke.js +155 -0
- package/dist/services/harness-service.js +7 -0
- package/dist/services/install-plan.js +233 -0
- package/dist/services/operational-debt.js +767 -0
- package/dist/services/operations-status-service.js +195 -0
- package/dist/services/package-smoke.js +676 -0
- package/dist/services/policy-service.js +25 -0
- package/dist/services/project-read-model.js +101 -0
- package/dist/services/release-artifact-evidence.js +77 -0
- package/dist/services/release-artifact.js +351 -0
- package/dist/services/release-dry-run.js +253 -0
- package/dist/services/release-evidence.js +138 -0
- package/dist/services/release-publish.js +163 -0
- package/dist/services/smoke-evidence.js +104 -0
- package/dist/services/task-read-model.js +125 -0
- package/dist/services/tools-list.js +26 -0
- package/dist/services/write-preflight.js +240 -0
- package/dist/task/task-capsule.js +121 -0
- package/dist/tools/fake-shell.js +56 -0
- package/dist/tui/cache.js +341 -0
- package/dist/tui/constants.js +44 -0
- package/dist/tui/layout.js +140 -0
- package/dist/tui/markdown.js +238 -0
- package/dist/tui/read-model-worker.js +24 -0
- package/dist/tui/read-model.js +502 -0
- package/dist/tui/snapshot.js +434 -0
- package/dist/tui/state.js +229 -0
- package/dist/tui/terminal.js +475 -0
- package/dist/tui/theme.js +86 -0
- package/package.json +16 -0
|
@@ -0,0 +1,676 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.createPackageSmokeDryRunReport = createPackageSmokeDryRunReport;
|
|
7
|
+
exports.createPackageSmokeLocalReport = createPackageSmokeLocalReport;
|
|
8
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
9
|
+
const node_os_1 = __importDefault(require("node:os"));
|
|
10
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
11
|
+
const node_child_process_1 = require("node:child_process");
|
|
12
|
+
const schema_1 = require("../core/schema");
|
|
13
|
+
const smoke_evidence_1 = require("./smoke-evidence");
|
|
14
|
+
function createPackageSmokeDryRunReport(options) {
|
|
15
|
+
const issues = [];
|
|
16
|
+
if (options.dryRun === false) {
|
|
17
|
+
issues.push({
|
|
18
|
+
severity: 'error',
|
|
19
|
+
code: 'PACKAGE_SMOKE_EXECUTION_DISABLED',
|
|
20
|
+
message: 'Only package-smoke dry-run planning is implemented in this capsule.'
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
validateTaskId(options.taskId, issues);
|
|
24
|
+
validateTimeout(options.timeoutSeconds, issues);
|
|
25
|
+
const source = createSource(options.paths.projectRoot, options.from, issues);
|
|
26
|
+
const workspace = createWorkspace(options.workspace, options.keepTemp === true);
|
|
27
|
+
const steps = createDryRunSteps(source.kind, options);
|
|
28
|
+
const artifacts = createPlannedArtifacts(options);
|
|
29
|
+
const report = {
|
|
30
|
+
schemaVersion: 'hadara.packageSmoke.v1',
|
|
31
|
+
command: 'package.smoke',
|
|
32
|
+
ok: false,
|
|
33
|
+
mode: 'dry-run',
|
|
34
|
+
readOnly: true,
|
|
35
|
+
execution: {
|
|
36
|
+
npmPackExecuted: false,
|
|
37
|
+
packageInstallExecuted: false,
|
|
38
|
+
featureSmokeExecuted: false,
|
|
39
|
+
releaseMutationExecuted: false,
|
|
40
|
+
publishExecuted: false
|
|
41
|
+
},
|
|
42
|
+
workspace,
|
|
43
|
+
source,
|
|
44
|
+
steps,
|
|
45
|
+
artifacts,
|
|
46
|
+
privacy: {
|
|
47
|
+
rawLogsIncluded: false,
|
|
48
|
+
rawPackageContentsIncluded: false,
|
|
49
|
+
privatePathsIncluded: false,
|
|
50
|
+
environmentSecretsIncluded: false,
|
|
51
|
+
privateStorePathsIncluded: false
|
|
52
|
+
},
|
|
53
|
+
issues
|
|
54
|
+
};
|
|
55
|
+
report.ok = issues.every((issue) => issue.severity !== 'error');
|
|
56
|
+
(0, schema_1.assertSchema)('hadara.packageSmoke.v1', report);
|
|
57
|
+
return report;
|
|
58
|
+
}
|
|
59
|
+
function createPackageSmokeLocalReport(options) {
|
|
60
|
+
const issues = [];
|
|
61
|
+
validateTaskId(options.taskId, issues);
|
|
62
|
+
validateTimeout(options.timeoutSeconds, issues);
|
|
63
|
+
const source = createSource(options.paths.projectRoot, options.from, issues);
|
|
64
|
+
const workspaceSetup = prepareExecutionWorkspace(options.paths.projectRoot, options.workspace, options.keepTemp === true, issues);
|
|
65
|
+
const steps = [
|
|
66
|
+
{
|
|
67
|
+
id: 'validate-source',
|
|
68
|
+
label: 'Validate package source',
|
|
69
|
+
status: issues.some((issue) => issue.stepId === 'validate-source' || issue.code.startsWith('PACKAGE_SMOKE_SOURCE') || issue.code === 'PACKAGE_SMOKE_BIN_MISSING')
|
|
70
|
+
? 'failed'
|
|
71
|
+
: 'passed',
|
|
72
|
+
summary: 'Package source metadata was checked without exposing package contents.'
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
id: 'plan-workspace',
|
|
76
|
+
label: 'Prepare disposable workspace',
|
|
77
|
+
status: workspaceSetup.ok ? 'passed' : 'failed',
|
|
78
|
+
summary: workspaceSetup.ok ? 'Disposable package-smoke workspace was prepared outside the project source.' : 'Disposable package-smoke workspace could not be prepared safely.'
|
|
79
|
+
}
|
|
80
|
+
];
|
|
81
|
+
const execution = {
|
|
82
|
+
npmPackExecuted: false,
|
|
83
|
+
packageInstallExecuted: false,
|
|
84
|
+
featureSmokeExecuted: false,
|
|
85
|
+
releaseMutationExecuted: false,
|
|
86
|
+
publishExecuted: false
|
|
87
|
+
};
|
|
88
|
+
const artifacts = [
|
|
89
|
+
{
|
|
90
|
+
kind: 'summary',
|
|
91
|
+
visibility: 'temporary',
|
|
92
|
+
pathRedacted: true,
|
|
93
|
+
rawContentIncluded: false
|
|
94
|
+
}
|
|
95
|
+
];
|
|
96
|
+
let tarballPath = source.kind === 'source-checkout' ? undefined : resolveInputPath(options.paths.projectRoot, options.from);
|
|
97
|
+
const runner = options.runner ?? runCommand;
|
|
98
|
+
const timeoutMs = (options.timeoutSeconds ?? 120) * 1000;
|
|
99
|
+
try {
|
|
100
|
+
if (issues.some((issue) => issue.severity === 'error') || !workspaceSetup.ok) {
|
|
101
|
+
pushSkippedExecutionSteps(steps);
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
if (source.kind === 'source-checkout') {
|
|
105
|
+
execution.npmPackExecuted = true;
|
|
106
|
+
const pack = runner(npmCommand(), ['pack', '--json', '--pack-destination', workspaceSetup.path], {
|
|
107
|
+
cwd: options.paths.projectRoot,
|
|
108
|
+
timeoutMs
|
|
109
|
+
});
|
|
110
|
+
const packStep = commandStep('npm-pack', 'npm pack', 'npm pack --json --pack-destination <redacted-workspace>', pack);
|
|
111
|
+
tarballPath = parsePackTarball(pack.stdout, workspaceSetup.path);
|
|
112
|
+
if (pack.status !== 0 || !tarballPath) {
|
|
113
|
+
packStep.status = 'failed';
|
|
114
|
+
packStep.summary = pack.timedOut ? 'npm pack timed out.' : 'npm pack did not produce a reduced tarball result.';
|
|
115
|
+
issues.push({
|
|
116
|
+
severity: 'error',
|
|
117
|
+
code: pack.timedOut ? 'PACKAGE_SMOKE_NPM_PACK_TIMEOUT' : 'PACKAGE_SMOKE_NPM_PACK_FAILED',
|
|
118
|
+
message: pack.timedOut ? 'npm pack timed out during package smoke.' : 'npm pack failed or did not report a tarball.',
|
|
119
|
+
stepId: 'npm-pack'
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
packStep.summary = 'npm pack produced a temporary package tarball.';
|
|
124
|
+
const byteLength = safeFileSize(tarballPath);
|
|
125
|
+
artifacts.push({
|
|
126
|
+
kind: 'package-artifact',
|
|
127
|
+
visibility: 'temporary',
|
|
128
|
+
relativePath: node_path_1.default.basename(tarballPath),
|
|
129
|
+
pathRedacted: true,
|
|
130
|
+
rawContentIncluded: false,
|
|
131
|
+
...(byteLength === undefined ? {} : { byteLength })
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
steps.push(packStep);
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
steps.push({
|
|
138
|
+
id: 'npm-pack',
|
|
139
|
+
label: 'npm pack',
|
|
140
|
+
status: 'skipped',
|
|
141
|
+
summary: 'Skipped because a package artifact source was provided.'
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
if (tarballPath && !issues.some((issue) => issue.severity === 'error')) {
|
|
145
|
+
execution.packageInstallExecuted = true;
|
|
146
|
+
const installPrefix = node_path_1.default.join(workspaceSetup.path, 'prefix');
|
|
147
|
+
node_fs_1.default.mkdirSync(installPrefix, { recursive: true });
|
|
148
|
+
const install = runner(npmCommand(), ['install', '-g', '--prefix', installPrefix, '--no-audit', '--no-fund', tarballPath], {
|
|
149
|
+
cwd: workspaceSetup.path,
|
|
150
|
+
timeoutMs
|
|
151
|
+
});
|
|
152
|
+
const installStep = commandStep('install-cli', 'Install package into isolated prefix', 'npm install -g --prefix <redacted-prefix> <redacted-package-source>', install);
|
|
153
|
+
if (install.status !== 0) {
|
|
154
|
+
installStep.status = 'failed';
|
|
155
|
+
installStep.summary = install.timedOut ? 'Isolated package install timed out.' : 'Isolated package install failed.';
|
|
156
|
+
issues.push({
|
|
157
|
+
severity: 'error',
|
|
158
|
+
code: install.timedOut ? 'PACKAGE_SMOKE_INSTALL_TIMEOUT' : 'PACKAGE_SMOKE_INSTALL_FAILED',
|
|
159
|
+
message: install.timedOut ? 'Package install timed out in the isolated prefix.' : 'Package install failed in the isolated prefix.',
|
|
160
|
+
stepId: 'install-cli'
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
installStep.summary = 'Package installed into an isolated temporary prefix.';
|
|
165
|
+
artifacts.push({
|
|
166
|
+
kind: 'install-tree',
|
|
167
|
+
visibility: 'temporary',
|
|
168
|
+
relativePath: 'prefix',
|
|
169
|
+
pathRedacted: true,
|
|
170
|
+
rawContentIncluded: false
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
steps.push(installStep);
|
|
174
|
+
const installedBin = installedHadaraCommand(installPrefix);
|
|
175
|
+
if (install.status === 0) {
|
|
176
|
+
const doctor = runner(installedBin, ['doctor', '--json', '--project', options.paths.projectRoot], {
|
|
177
|
+
cwd: workspaceSetup.path,
|
|
178
|
+
timeoutMs,
|
|
179
|
+
env: installPathEnv(installPrefix)
|
|
180
|
+
});
|
|
181
|
+
const doctorStep = commandStep('doctor', 'Installed HADARA doctor', 'hadara doctor --json --project <redacted-project>', doctor);
|
|
182
|
+
if (!isOkJsonReport(doctor.stdout) || doctor.status !== 0) {
|
|
183
|
+
doctorStep.status = 'failed';
|
|
184
|
+
doctorStep.summary = doctor.timedOut ? 'Installed doctor timed out.' : 'Installed doctor did not return an ok JSON report.';
|
|
185
|
+
issues.push({
|
|
186
|
+
severity: 'error',
|
|
187
|
+
code: doctor.timedOut ? 'PACKAGE_SMOKE_DOCTOR_TIMEOUT' : 'PACKAGE_SMOKE_DOCTOR_FAILED',
|
|
188
|
+
message: doctor.timedOut ? 'Installed hadara doctor timed out.' : 'Installed hadara doctor failed or returned non-ok JSON.',
|
|
189
|
+
stepId: 'doctor'
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
doctorStep.summary = 'Installed hadara doctor returned an ok reduced JSON report.';
|
|
194
|
+
}
|
|
195
|
+
steps.push(doctorStep);
|
|
196
|
+
execution.featureSmokeExecuted = true;
|
|
197
|
+
const smoke = runner(installedBin, ['smoke', 'run', '--profile', 'core', '--json', '--project', options.paths.projectRoot], {
|
|
198
|
+
cwd: workspaceSetup.path,
|
|
199
|
+
timeoutMs,
|
|
200
|
+
env: installPathEnv(installPrefix)
|
|
201
|
+
});
|
|
202
|
+
const smokeStep = commandStep('feature-smoke-core', 'Core feature smoke via installed command', 'hadara smoke run --profile core --json --project <redacted-project>', smoke);
|
|
203
|
+
if (!isOkJsonReport(smoke.stdout) || smoke.status !== 0) {
|
|
204
|
+
smokeStep.status = 'failed';
|
|
205
|
+
smokeStep.summary = smoke.timedOut ? 'Installed core feature smoke timed out.' : 'Installed core feature smoke did not return an ok JSON report.';
|
|
206
|
+
issues.push({
|
|
207
|
+
severity: 'error',
|
|
208
|
+
code: smoke.timedOut ? 'PACKAGE_SMOKE_FEATURE_SMOKE_TIMEOUT' : 'PACKAGE_SMOKE_FEATURE_SMOKE_FAILED',
|
|
209
|
+
message: smoke.timedOut ? 'Installed core feature smoke timed out.' : 'Installed core feature smoke failed or returned non-ok JSON.',
|
|
210
|
+
stepId: 'feature-smoke-core'
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
smokeStep.summary = 'Installed command-form core feature smoke returned an ok reduced JSON report.';
|
|
215
|
+
}
|
|
216
|
+
steps.push(smokeStep);
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
steps.push({
|
|
220
|
+
id: 'doctor',
|
|
221
|
+
label: 'Installed HADARA doctor',
|
|
222
|
+
command: 'hadara doctor --json --project <redacted-project>',
|
|
223
|
+
status: 'skipped',
|
|
224
|
+
summary: 'Skipped because isolated package install failed.'
|
|
225
|
+
}, {
|
|
226
|
+
id: 'feature-smoke-core',
|
|
227
|
+
label: 'Core feature smoke via installed command',
|
|
228
|
+
command: 'hadara smoke run --profile core --json --project <redacted-project>',
|
|
229
|
+
status: 'skipped',
|
|
230
|
+
summary: 'Skipped because isolated package install failed.'
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
else if (!steps.some((step) => step.id === 'install-cli')) {
|
|
235
|
+
steps.push({
|
|
236
|
+
id: 'install-cli',
|
|
237
|
+
label: 'Install package into isolated prefix',
|
|
238
|
+
command: 'npm install -g --prefix <redacted-prefix> <redacted-package-source>',
|
|
239
|
+
status: 'skipped',
|
|
240
|
+
summary: 'Skipped because no package tarball was available.'
|
|
241
|
+
}, {
|
|
242
|
+
id: 'doctor',
|
|
243
|
+
label: 'Installed HADARA doctor',
|
|
244
|
+
command: 'hadara doctor --json --project <redacted-project>',
|
|
245
|
+
status: 'skipped',
|
|
246
|
+
summary: 'Skipped because no package tarball was available.'
|
|
247
|
+
}, {
|
|
248
|
+
id: 'feature-smoke-core',
|
|
249
|
+
label: 'Core feature smoke via installed command',
|
|
250
|
+
command: 'hadara smoke run --profile core --json --project <redacted-project>',
|
|
251
|
+
status: 'skipped',
|
|
252
|
+
summary: 'Skipped because no package tarball was available.'
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
finally {
|
|
258
|
+
const cleanup = cleanupWorkspace(workspaceSetup, options.keepTemp === true);
|
|
259
|
+
steps.push({
|
|
260
|
+
id: 'cleanup',
|
|
261
|
+
label: 'Cleanup disposable workspace',
|
|
262
|
+
status: cleanup.ok ? 'passed' : 'failed',
|
|
263
|
+
summary: cleanup.summary
|
|
264
|
+
});
|
|
265
|
+
if (!cleanup.ok) {
|
|
266
|
+
issues.push({
|
|
267
|
+
severity: 'warning',
|
|
268
|
+
code: 'PACKAGE_SMOKE_CLEANUP_FAILED',
|
|
269
|
+
message: 'Package-smoke cleanup could not remove the disposable workspace.',
|
|
270
|
+
stepId: 'cleanup'
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
const report = {
|
|
275
|
+
schemaVersion: 'hadara.packageSmoke.v1',
|
|
276
|
+
command: 'package.smoke',
|
|
277
|
+
ok: issues.every((issue) => issue.severity !== 'error') && steps.every((step) => step.status !== 'failed'),
|
|
278
|
+
mode: 'local',
|
|
279
|
+
readOnly: false,
|
|
280
|
+
execution,
|
|
281
|
+
workspace: {
|
|
282
|
+
kind: 'disposable',
|
|
283
|
+
displayPath: workspaceSetup.displayPath,
|
|
284
|
+
pathRedacted: true,
|
|
285
|
+
...(workspaceSetup.relativePath ? { relativePath: workspaceSetup.relativePath } : {}),
|
|
286
|
+
retention: options.keepTemp === true ? 'kept-temporary' : 'deleted'
|
|
287
|
+
},
|
|
288
|
+
source,
|
|
289
|
+
steps,
|
|
290
|
+
artifacts,
|
|
291
|
+
privacy: {
|
|
292
|
+
rawLogsIncluded: false,
|
|
293
|
+
rawPackageContentsIncluded: false,
|
|
294
|
+
privatePathsIncluded: false,
|
|
295
|
+
environmentSecretsIncluded: false,
|
|
296
|
+
privateStorePathsIncluded: false
|
|
297
|
+
},
|
|
298
|
+
issues
|
|
299
|
+
};
|
|
300
|
+
if (options.attachEvidence === true && options.taskId && options.noEvidence !== true) {
|
|
301
|
+
const evidence = (0, smoke_evidence_1.attachReducedSmokeEvidence)({
|
|
302
|
+
projectRoot: options.paths.projectRoot,
|
|
303
|
+
taskId: options.taskId,
|
|
304
|
+
category: 'package-smoke',
|
|
305
|
+
kind: 'command-log',
|
|
306
|
+
summary: `Package smoke ${report.mode} ${report.ok ? 'passed' : 'failed'} with reduced public evidence.`,
|
|
307
|
+
result: report.ok ? 'passed' : 'failed',
|
|
308
|
+
report
|
|
309
|
+
});
|
|
310
|
+
report.artifacts.push(evidence.artifact);
|
|
311
|
+
report.steps.push({
|
|
312
|
+
id: 'evidence',
|
|
313
|
+
label: 'Attach reduced public evidence',
|
|
314
|
+
status: 'passed',
|
|
315
|
+
summary: 'Reduced package-smoke summary was attached as public evidence after redaction checks.'
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
(0, schema_1.assertSchema)('hadara.packageSmoke.v1', report);
|
|
319
|
+
return report;
|
|
320
|
+
}
|
|
321
|
+
function createDryRunSteps(sourceKind, options) {
|
|
322
|
+
const packStatus = sourceKind === 'source-checkout' ? 'planned' : 'skipped';
|
|
323
|
+
const evidencePlanned = options.attachEvidence === true && options.taskId !== undefined && options.noEvidence !== true;
|
|
324
|
+
return [
|
|
325
|
+
{
|
|
326
|
+
id: 'validate-source',
|
|
327
|
+
label: 'Validate package source',
|
|
328
|
+
status: 'planned',
|
|
329
|
+
summary: 'Check package source metadata without reading package contents into the public report.'
|
|
330
|
+
},
|
|
331
|
+
{
|
|
332
|
+
id: 'plan-workspace',
|
|
333
|
+
label: 'Plan disposable workspace',
|
|
334
|
+
status: 'planned',
|
|
335
|
+
summary: 'Plan an isolated package-smoke workspace without creating directories.'
|
|
336
|
+
},
|
|
337
|
+
{
|
|
338
|
+
id: 'npm-pack',
|
|
339
|
+
label: 'npm pack',
|
|
340
|
+
...(sourceKind === 'source-checkout' ? { command: 'npm pack --json' } : {}),
|
|
341
|
+
status: packStatus,
|
|
342
|
+
summary: sourceKind === 'source-checkout'
|
|
343
|
+
? 'Would create a package tarball in the disposable workspace during a later execution capsule.'
|
|
344
|
+
: 'Skipped because a package artifact source was provided.'
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
id: 'install-cli',
|
|
348
|
+
label: 'Install package into isolated prefix',
|
|
349
|
+
command: 'npm install <redacted-package-source>',
|
|
350
|
+
status: 'planned',
|
|
351
|
+
summary: 'Would install into an isolated temporary prefix during a later execution capsule.'
|
|
352
|
+
},
|
|
353
|
+
{
|
|
354
|
+
id: 'feature-smoke-core',
|
|
355
|
+
label: 'Core feature smoke via installed command',
|
|
356
|
+
command: 'hadara smoke run --profile core --json',
|
|
357
|
+
status: 'planned',
|
|
358
|
+
summary: 'Would run core smoke through the package-installed command form; current dry-run does not execute an installed binary or subprocess.'
|
|
359
|
+
},
|
|
360
|
+
{
|
|
361
|
+
id: 'evidence',
|
|
362
|
+
label: 'Evidence handling',
|
|
363
|
+
status: evidencePlanned ? 'planned' : 'skipped',
|
|
364
|
+
summary: evidencePlanned
|
|
365
|
+
? 'Would attach a reduced public summary after redaction checks in a later evidence integration capsule.'
|
|
366
|
+
: 'No public evidence attachment is planned by default.'
|
|
367
|
+
}
|
|
368
|
+
];
|
|
369
|
+
}
|
|
370
|
+
function createPlannedArtifacts(options) {
|
|
371
|
+
const artifacts = [
|
|
372
|
+
{
|
|
373
|
+
kind: 'summary',
|
|
374
|
+
visibility: 'temporary',
|
|
375
|
+
pathRedacted: true,
|
|
376
|
+
rawContentIncluded: false
|
|
377
|
+
}
|
|
378
|
+
];
|
|
379
|
+
if (options.privateLogs === true) {
|
|
380
|
+
artifacts.push({
|
|
381
|
+
kind: 'command-log',
|
|
382
|
+
visibility: 'private',
|
|
383
|
+
pathRedacted: true,
|
|
384
|
+
rawContentIncluded: false
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
if (options.attachEvidence === true && options.taskId && options.noEvidence !== true) {
|
|
388
|
+
artifacts.push({
|
|
389
|
+
kind: 'summary',
|
|
390
|
+
visibility: 'public',
|
|
391
|
+
evidencePath: `${resolveTaskCapsulePath(options.paths.projectRoot, options.taskId)}/artifacts/package-smoke/dry-run-summary.json`,
|
|
392
|
+
rawContentIncluded: false
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
return artifacts;
|
|
396
|
+
}
|
|
397
|
+
function createSource(projectRoot, from, issues) {
|
|
398
|
+
if (!from) {
|
|
399
|
+
validateProjectPackageMetadata(projectRoot, issues);
|
|
400
|
+
return {
|
|
401
|
+
kind: 'source-checkout',
|
|
402
|
+
displayPath: '.',
|
|
403
|
+
pathRedacted: true,
|
|
404
|
+
relativePath: '.'
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
const relativePath = safeRelativePath(from);
|
|
408
|
+
const absolutePath = node_path_1.default.isAbsolute(from) ? from : node_path_1.default.resolve(projectRoot, from);
|
|
409
|
+
if (!node_fs_1.default.existsSync(absolutePath)) {
|
|
410
|
+
issues.push({
|
|
411
|
+
severity: 'error',
|
|
412
|
+
code: 'PACKAGE_SMOKE_SOURCE_MISSING',
|
|
413
|
+
message: 'Package-smoke source path does not exist.'
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
return {
|
|
417
|
+
kind: inferSourceKind(from),
|
|
418
|
+
displayPath: relativePath ? `./${relativePath}` : '<redacted-package-source>',
|
|
419
|
+
pathRedacted: true,
|
|
420
|
+
...(relativePath ? { relativePath } : {})
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
function createWorkspace(workspace, keepTemp) {
|
|
424
|
+
const relativePath = safeRelativePath(workspace);
|
|
425
|
+
return {
|
|
426
|
+
kind: 'disposable',
|
|
427
|
+
displayPath: relativePath ? `./${relativePath}` : '<redacted-disposable-workspace>',
|
|
428
|
+
pathRedacted: true,
|
|
429
|
+
...(relativePath ? { relativePath } : {}),
|
|
430
|
+
retention: keepTemp ? 'kept-temporary' : 'deleted'
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
function prepareExecutionWorkspace(projectRoot, workspace, keepTemp, issues) {
|
|
434
|
+
const resolved = workspace ? node_path_1.default.resolve(projectRoot, workspace) : node_fs_1.default.mkdtempSync(node_path_1.default.join(node_os_1.default.tmpdir(), 'hadara-package-smoke-'));
|
|
435
|
+
const relativePath = safeRelativePath(workspace);
|
|
436
|
+
if (isInsideProject(projectRoot, resolved)) {
|
|
437
|
+
issues.push({
|
|
438
|
+
severity: 'error',
|
|
439
|
+
code: 'PACKAGE_SMOKE_WORKSPACE_INSIDE_PROJECT',
|
|
440
|
+
message: 'Package-smoke execution workspace must be outside the project source tree.'
|
|
441
|
+
});
|
|
442
|
+
return {
|
|
443
|
+
ok: false,
|
|
444
|
+
path: resolved,
|
|
445
|
+
displayPath: relativePath ? `./${relativePath}` : '<redacted-disposable-workspace>',
|
|
446
|
+
...(relativePath ? { relativePath } : {}),
|
|
447
|
+
created: false,
|
|
448
|
+
cleanupAllowed: false
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
try {
|
|
452
|
+
node_fs_1.default.mkdirSync(resolved, { recursive: true });
|
|
453
|
+
return {
|
|
454
|
+
ok: true,
|
|
455
|
+
path: resolved,
|
|
456
|
+
displayPath: relativePath ? `./${relativePath}` : '<redacted-disposable-workspace>',
|
|
457
|
+
...(relativePath ? { relativePath } : {}),
|
|
458
|
+
created: true,
|
|
459
|
+
cleanupAllowed: !keepTemp
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
catch {
|
|
463
|
+
issues.push({
|
|
464
|
+
severity: 'error',
|
|
465
|
+
code: 'PACKAGE_SMOKE_WORKSPACE_CREATE_FAILED',
|
|
466
|
+
message: 'Package-smoke execution workspace could not be created.'
|
|
467
|
+
});
|
|
468
|
+
return {
|
|
469
|
+
ok: false,
|
|
470
|
+
path: resolved,
|
|
471
|
+
displayPath: relativePath ? `./${relativePath}` : '<redacted-disposable-workspace>',
|
|
472
|
+
...(relativePath ? { relativePath } : {}),
|
|
473
|
+
created: false,
|
|
474
|
+
cleanupAllowed: false
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
function pushSkippedExecutionSteps(steps) {
|
|
479
|
+
steps.push({
|
|
480
|
+
id: 'npm-pack',
|
|
481
|
+
label: 'npm pack',
|
|
482
|
+
command: 'npm pack --json --pack-destination <redacted-workspace>',
|
|
483
|
+
status: 'skipped',
|
|
484
|
+
summary: 'Skipped because package-smoke setup failed.'
|
|
485
|
+
}, {
|
|
486
|
+
id: 'install-cli',
|
|
487
|
+
label: 'Install package into isolated prefix',
|
|
488
|
+
command: 'npm install -g --prefix <redacted-prefix> <redacted-package-source>',
|
|
489
|
+
status: 'skipped',
|
|
490
|
+
summary: 'Skipped because package-smoke setup failed.'
|
|
491
|
+
}, {
|
|
492
|
+
id: 'doctor',
|
|
493
|
+
label: 'Installed HADARA doctor',
|
|
494
|
+
command: 'hadara doctor --json --project <redacted-project>',
|
|
495
|
+
status: 'skipped',
|
|
496
|
+
summary: 'Skipped because package-smoke setup failed.'
|
|
497
|
+
}, {
|
|
498
|
+
id: 'feature-smoke-core',
|
|
499
|
+
label: 'Core feature smoke via installed command',
|
|
500
|
+
command: 'hadara smoke run --profile core --json --project <redacted-project>',
|
|
501
|
+
status: 'skipped',
|
|
502
|
+
summary: 'Skipped because package-smoke setup failed.'
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
function commandStep(id, label, command, result) {
|
|
506
|
+
return {
|
|
507
|
+
id,
|
|
508
|
+
label,
|
|
509
|
+
command,
|
|
510
|
+
status: result.status === 0 && !result.timedOut ? 'passed' : 'failed',
|
|
511
|
+
exitCode: result.status,
|
|
512
|
+
elapsedMs: result.elapsedMs,
|
|
513
|
+
summary: result.status === 0 && !result.timedOut ? `${label} completed successfully.` : `${label} failed with a reduced exit summary.`
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
function parsePackTarball(stdout, workspace) {
|
|
517
|
+
try {
|
|
518
|
+
const parsed = JSON.parse(stdout);
|
|
519
|
+
if (!Array.isArray(parsed))
|
|
520
|
+
return undefined;
|
|
521
|
+
const filename = parsed[0]?.filename;
|
|
522
|
+
if (typeof filename !== 'string' || filename.includes('/') || filename.includes('\\'))
|
|
523
|
+
return undefined;
|
|
524
|
+
return node_path_1.default.join(workspace, filename);
|
|
525
|
+
}
|
|
526
|
+
catch {
|
|
527
|
+
return undefined;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
function isOkJsonReport(stdout) {
|
|
531
|
+
try {
|
|
532
|
+
const parsed = JSON.parse(stdout);
|
|
533
|
+
return parsed.ok === true;
|
|
534
|
+
}
|
|
535
|
+
catch {
|
|
536
|
+
return false;
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
function safeFileSize(filePath) {
|
|
540
|
+
try {
|
|
541
|
+
return node_fs_1.default.statSync(filePath).size;
|
|
542
|
+
}
|
|
543
|
+
catch {
|
|
544
|
+
return undefined;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
function cleanupWorkspace(workspace, keepTemp) {
|
|
548
|
+
if (keepTemp)
|
|
549
|
+
return { ok: true, summary: 'Disposable workspace retained because --keep-temp was set.' };
|
|
550
|
+
if (!workspace.cleanupAllowed)
|
|
551
|
+
return { ok: true, summary: 'No disposable workspace cleanup was required.' };
|
|
552
|
+
try {
|
|
553
|
+
node_fs_1.default.rmSync(workspace.path, { recursive: true, force: true });
|
|
554
|
+
return { ok: true, summary: 'Disposable workspace was removed.' };
|
|
555
|
+
}
|
|
556
|
+
catch {
|
|
557
|
+
return { ok: false, summary: 'Disposable workspace cleanup failed.' };
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
function runCommand(command, args, options) {
|
|
561
|
+
const started = Date.now();
|
|
562
|
+
const result = (0, node_child_process_1.spawnSync)(command, args, {
|
|
563
|
+
cwd: options.cwd,
|
|
564
|
+
env: options.env ?? process.env,
|
|
565
|
+
encoding: 'utf8',
|
|
566
|
+
timeout: options.timeoutMs,
|
|
567
|
+
maxBuffer: 1024 * 1024 * 4
|
|
568
|
+
});
|
|
569
|
+
return {
|
|
570
|
+
status: typeof result.status === 'number' ? result.status : null,
|
|
571
|
+
signal: result.signal,
|
|
572
|
+
stdout: result.stdout ?? '',
|
|
573
|
+
stderr: result.stderr ?? '',
|
|
574
|
+
elapsedMs: Date.now() - started,
|
|
575
|
+
timedOut: result.error?.name === 'TimeoutError' || result.signal === 'SIGTERM'
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
function npmCommand() {
|
|
579
|
+
return process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
580
|
+
}
|
|
581
|
+
function installedHadaraCommand(prefix) {
|
|
582
|
+
if (process.platform === 'win32')
|
|
583
|
+
return node_path_1.default.join(prefix, 'hadara.cmd');
|
|
584
|
+
return node_path_1.default.join(prefix, 'bin', 'hadara');
|
|
585
|
+
}
|
|
586
|
+
function installPathEnv(prefix) {
|
|
587
|
+
const binDir = process.platform === 'win32' ? prefix : node_path_1.default.join(prefix, 'bin');
|
|
588
|
+
return {
|
|
589
|
+
...process.env,
|
|
590
|
+
PATH: `${binDir}${node_path_1.default.delimiter}${process.env.PATH ?? ''}`
|
|
591
|
+
};
|
|
592
|
+
}
|
|
593
|
+
function resolveInputPath(projectRoot, value) {
|
|
594
|
+
if (!value)
|
|
595
|
+
return undefined;
|
|
596
|
+
return node_path_1.default.isAbsolute(value) ? value : node_path_1.default.resolve(projectRoot, value);
|
|
597
|
+
}
|
|
598
|
+
function isInsideProject(projectRoot, child) {
|
|
599
|
+
const relative = node_path_1.default.relative(node_path_1.default.resolve(projectRoot), node_path_1.default.resolve(child));
|
|
600
|
+
return relative === '' || (!relative.startsWith('..') && !node_path_1.default.isAbsolute(relative));
|
|
601
|
+
}
|
|
602
|
+
function inferSourceKind(from) {
|
|
603
|
+
if (/\.tgz$/i.test(from))
|
|
604
|
+
return 'tarball';
|
|
605
|
+
return 'release-artifact';
|
|
606
|
+
}
|
|
607
|
+
function validateProjectPackageMetadata(projectRoot, issues) {
|
|
608
|
+
const packageJsonPath = node_path_1.default.join(projectRoot, 'package.json');
|
|
609
|
+
let parsed;
|
|
610
|
+
try {
|
|
611
|
+
parsed = JSON.parse(node_fs_1.default.readFileSync(packageJsonPath, 'utf8'));
|
|
612
|
+
}
|
|
613
|
+
catch {
|
|
614
|
+
issues.push({
|
|
615
|
+
severity: 'error',
|
|
616
|
+
code: 'PACKAGE_SMOKE_PACKAGE_JSON_MISSING',
|
|
617
|
+
message: 'Package-smoke dry-run requires readable project package.json metadata.'
|
|
618
|
+
});
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
if (parsed.name !== 'hadara') {
|
|
622
|
+
issues.push({
|
|
623
|
+
severity: 'warning',
|
|
624
|
+
code: 'PACKAGE_SMOKE_PACKAGE_NAME_UNEXPECTED',
|
|
625
|
+
message: 'Package metadata name is not hadara.'
|
|
626
|
+
});
|
|
627
|
+
}
|
|
628
|
+
if (!isRecord(parsed.bin) || parsed.bin.hadara !== './dist/cli/main.js') {
|
|
629
|
+
issues.push({
|
|
630
|
+
severity: 'error',
|
|
631
|
+
code: 'PACKAGE_SMOKE_BIN_MISSING',
|
|
632
|
+
message: 'Package metadata must expose bin.hadara at ./dist/cli/main.js before package smoke.'
|
|
633
|
+
});
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
function validateTaskId(taskId, issues) {
|
|
637
|
+
if (taskId !== undefined && !/^T-[0-9]{4}$/.test(taskId)) {
|
|
638
|
+
issues.push({
|
|
639
|
+
severity: 'error',
|
|
640
|
+
code: 'PACKAGE_SMOKE_TASK_ID_INVALID',
|
|
641
|
+
message: 'Package-smoke task id must look like T-0000.'
|
|
642
|
+
});
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
function validateTimeout(timeoutSeconds, issues) {
|
|
646
|
+
if (timeoutSeconds !== undefined && (!Number.isSafeInteger(timeoutSeconds) || timeoutSeconds < 1)) {
|
|
647
|
+
issues.push({
|
|
648
|
+
severity: 'error',
|
|
649
|
+
code: 'PACKAGE_SMOKE_TIMEOUT_INVALID',
|
|
650
|
+
message: 'Package-smoke timeout must be a positive integer number of seconds.'
|
|
651
|
+
});
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
function resolveTaskCapsulePath(projectRoot, taskId) {
|
|
655
|
+
try {
|
|
656
|
+
const entry = node_fs_1.default.readdirSync(node_path_1.default.join(projectRoot, 'tasks')).find((name) => name.startsWith(`${taskId}-`));
|
|
657
|
+
if (entry)
|
|
658
|
+
return `tasks/${entry}`;
|
|
659
|
+
}
|
|
660
|
+
catch {
|
|
661
|
+
// Fall through to a redacted deterministic preview path.
|
|
662
|
+
}
|
|
663
|
+
return `tasks/${taskId}-package-smoke`;
|
|
664
|
+
}
|
|
665
|
+
function safeRelativePath(value) {
|
|
666
|
+
if (!value || node_path_1.default.isAbsolute(value) || /^[A-Za-z]:[\\/]/.test(value) || value.startsWith('~') || value.startsWith('<') || value.includes('%')) {
|
|
667
|
+
return undefined;
|
|
668
|
+
}
|
|
669
|
+
const normalized = value.split(/[\\/]+/).filter(Boolean).join('/');
|
|
670
|
+
if (!normalized || normalized === '.' || normalized.startsWith('..'))
|
|
671
|
+
return undefined;
|
|
672
|
+
return normalized;
|
|
673
|
+
}
|
|
674
|
+
function isRecord(value) {
|
|
675
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
676
|
+
}
|