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.
Files changed (121) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +109 -0
  3. package/dist/agent/evidence.js +50 -0
  4. package/dist/agent/loop.js +124 -0
  5. package/dist/cli/args.js +70 -0
  6. package/dist/cli/dashboard.js +185 -0
  7. package/dist/cli/debt.js +41 -0
  8. package/dist/cli/doctor.js +68 -0
  9. package/dist/cli/errors.js +58 -0
  10. package/dist/cli/evidence-json.js +75 -0
  11. package/dist/cli/evidence.js +80 -0
  12. package/dist/cli/handoff.js +16 -0
  13. package/dist/cli/harness.js +57 -0
  14. package/dist/cli/hermes-json.js +31 -0
  15. package/dist/cli/hermes.js +28 -0
  16. package/dist/cli/init.js +142 -0
  17. package/dist/cli/install.js +34 -0
  18. package/dist/cli/main.js +216 -0
  19. package/dist/cli/mcp.js +15 -0
  20. package/dist/cli/package-smoke.js +37 -0
  21. package/dist/cli/policy-json.js +22 -0
  22. package/dist/cli/policy.js +43 -0
  23. package/dist/cli/release-artifact.js +47 -0
  24. package/dist/cli/release-dry-run.js +24 -0
  25. package/dist/cli/release-gate.js +28 -0
  26. package/dist/cli/release-publish.js +41 -0
  27. package/dist/cli/run-scaffold.js +68 -0
  28. package/dist/cli/run-state.js +41 -0
  29. package/dist/cli/run.js +191 -0
  30. package/dist/cli/smoke.js +58 -0
  31. package/dist/cli/status-json.js +6 -0
  32. package/dist/cli/status.js +26 -0
  33. package/dist/cli/task-json.js +8 -0
  34. package/dist/cli/task.js +64 -0
  35. package/dist/cli/tools.js +25 -0
  36. package/dist/cli/tui.js +72 -0
  37. package/dist/cli/write-preflight.js +27 -0
  38. package/dist/core/audit.js +41 -0
  39. package/dist/core/events.js +63 -0
  40. package/dist/core/fs.js +44 -0
  41. package/dist/core/paths.js +59 -0
  42. package/dist/core/redaction.js +178 -0
  43. package/dist/core/schema.js +253 -0
  44. package/dist/core/workspace.js +47 -0
  45. package/dist/evidence/evidence.js +170 -0
  46. package/dist/evidence/private-manifest.js +101 -0
  47. package/dist/handoff/handoff.js +49 -0
  48. package/dist/harness/replay.js +200 -0
  49. package/dist/harness/validate.js +465 -0
  50. package/dist/hermes/context-export.js +104 -0
  51. package/dist/index.js +29 -0
  52. package/dist/mcp/server.js +104 -0
  53. package/dist/mcp/tool-dispatch.js +159 -0
  54. package/dist/mcp/tool-registry.js +150 -0
  55. package/dist/mcp/tool-schemas.js +18 -0
  56. package/dist/policy/command-risk.js +39 -0
  57. package/dist/policy/permission-matrix.js +42 -0
  58. package/dist/policy/policy.js +20 -0
  59. package/dist/policy/preflight.js +47 -0
  60. package/dist/policy/presets.js +24 -0
  61. package/dist/policy/tokenizer.js +53 -0
  62. package/dist/providers/fallback-executor.js +46 -0
  63. package/dist/providers/mock-provider.js +49 -0
  64. package/dist/providers/provider-contract.js +2 -0
  65. package/dist/providers/provider-preparation.js +220 -0
  66. package/dist/providers/scripted-provider.js +69 -0
  67. package/dist/schemas/active-run-projection.schema.json +73 -0
  68. package/dist/schemas/active-run-resume.schema.json +68 -0
  69. package/dist/schemas/clean-checkout-smoke.schema.json +126 -0
  70. package/dist/schemas/context-export.schema.json +35 -0
  71. package/dist/schemas/event.schema.json +17 -0
  72. package/dist/schemas/evidence-list.schema.json +49 -0
  73. package/dist/schemas/feature-smoke.schema.json +67 -0
  74. package/dist/schemas/install-plan.schema.json +93 -0
  75. package/dist/schemas/package-smoke.schema.json +130 -0
  76. package/dist/schemas/private-evidence.schema.json +48 -0
  77. package/dist/schemas/provider-call.schema.json +42 -0
  78. package/dist/schemas/provider-config.schema.json +43 -0
  79. package/dist/schemas/release-artifact-manifest.schema.json +55 -0
  80. package/dist/schemas/release-artifact.schema.json +140 -0
  81. package/dist/schemas/release-dry-run.schema.json +141 -0
  82. package/dist/schemas/release-gate.schema.json +42 -0
  83. package/dist/schemas/release-publish.schema.json +114 -0
  84. package/dist/schemas/schema-index.json +145 -0
  85. package/dist/schemas/smoke-evidence-summary.schema.json +88 -0
  86. package/dist/schemas/tools-list.schema.json +78 -0
  87. package/dist/schemas/write-preflight.schema.json +47 -0
  88. package/dist/services/active-run-state.js +215 -0
  89. package/dist/services/capability-registry.js +540 -0
  90. package/dist/services/clean-checkout-smoke.js +393 -0
  91. package/dist/services/evidence-list.js +136 -0
  92. package/dist/services/feature-smoke.js +155 -0
  93. package/dist/services/harness-service.js +7 -0
  94. package/dist/services/install-plan.js +233 -0
  95. package/dist/services/operational-debt.js +767 -0
  96. package/dist/services/operations-status-service.js +195 -0
  97. package/dist/services/package-smoke.js +676 -0
  98. package/dist/services/policy-service.js +25 -0
  99. package/dist/services/project-read-model.js +101 -0
  100. package/dist/services/release-artifact-evidence.js +77 -0
  101. package/dist/services/release-artifact.js +351 -0
  102. package/dist/services/release-dry-run.js +253 -0
  103. package/dist/services/release-evidence.js +138 -0
  104. package/dist/services/release-publish.js +163 -0
  105. package/dist/services/smoke-evidence.js +104 -0
  106. package/dist/services/task-read-model.js +125 -0
  107. package/dist/services/tools-list.js +26 -0
  108. package/dist/services/write-preflight.js +240 -0
  109. package/dist/task/task-capsule.js +121 -0
  110. package/dist/tools/fake-shell.js +56 -0
  111. package/dist/tui/cache.js +341 -0
  112. package/dist/tui/constants.js +44 -0
  113. package/dist/tui/layout.js +140 -0
  114. package/dist/tui/markdown.js +238 -0
  115. package/dist/tui/read-model-worker.js +24 -0
  116. package/dist/tui/read-model.js +502 -0
  117. package/dist/tui/snapshot.js +434 -0
  118. package/dist/tui/state.js +229 -0
  119. package/dist/tui/terminal.js +475 -0
  120. package/dist/tui/theme.js +86 -0
  121. package/package.json +16 -0
@@ -0,0 +1,253 @@
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.createReleaseDryRunReport = createReleaseDryRunReport;
7
+ exports.readCurrentGitCommit = readCurrentGitCommit;
8
+ const node_fs_1 = __importDefault(require("node:fs"));
9
+ const node_path_1 = __importDefault(require("node:path"));
10
+ const schema_1 = require("../core/schema");
11
+ const operational_debt_1 = require("./operational-debt");
12
+ const release_evidence_1 = require("./release-evidence");
13
+ function createReleaseDryRunReport(projectRoot) {
14
+ const packageMetadata = readPackageMetadata(projectRoot);
15
+ const gitCommit = readCurrentGitCommit(projectRoot);
16
+ const releaseGate = (0, operational_debt_1.createReleaseGateReport)(projectRoot, 'strict');
17
+ const evidenceRecords = (0, release_evidence_1.readReleaseEvidenceRecords)(projectRoot);
18
+ const evidence = evidenceRequirements().map((requirement) => validateEvidenceRequirement(evidenceRecords, requirement));
19
+ const checks = [
20
+ {
21
+ code: 'STRICT_RELEASE_GATE',
22
+ name: 'Strict release gate',
23
+ status: releaseGate.ok ? 'passed' : 'error',
24
+ summary: releaseGate.ok ? 'Strict read-only release gate passes before release dry-run planning.' : 'Strict read-only release gate must pass before release dry-run planning.'
25
+ },
26
+ ...evidence.map((item) => ({
27
+ code: item.code,
28
+ name: evidenceName(item.code),
29
+ status: evidenceCheckPassed(item, packageMetadata.version, gitCommit) ? 'passed' : 'error',
30
+ summary: evidenceSummary(item, packageMetadata.version, gitCommit)
31
+ })),
32
+ {
33
+ code: 'RELEASE_TARGETS',
34
+ name: 'Release target plan',
35
+ status: 'passed',
36
+ summary: 'Dry-run plans npm package first, GitHub Release second, and keeps Docker image publishing deferred.'
37
+ }
38
+ ];
39
+ const issues = checks
40
+ .filter((check) => check.status !== 'passed')
41
+ .map((check) => ({
42
+ severity: check.status === 'error' ? 'error' : 'warning',
43
+ code: `${check.code}_NOT_READY`,
44
+ message: `${check.name}: ${check.summary}`
45
+ }));
46
+ const report = {
47
+ schemaVersion: 'hadara.releaseDryRun.v1',
48
+ command: 'release.dryRun',
49
+ mode: 'dry-run',
50
+ ok: checks.every((check) => check.status !== 'error'),
51
+ current: {
52
+ packageName: packageMetadata.name,
53
+ packageVersion: packageMetadata.version,
54
+ ...(gitCommit ? { gitCommit } : {})
55
+ },
56
+ releaseTargets: {
57
+ primary: 'npm-package',
58
+ secondary: 'github-release',
59
+ dockerImage: 'deferred'
60
+ },
61
+ checks,
62
+ evidence,
63
+ plannedSteps: [
64
+ {
65
+ id: 'npm-publish',
66
+ target: 'npm-package',
67
+ willExecute: false,
68
+ requiresApproval: true,
69
+ summary: 'Would publish the package only in a later approval-gated release command with NPM_TOKEN presence checks.'
70
+ },
71
+ {
72
+ id: 'github-release',
73
+ target: 'github-release',
74
+ willExecute: false,
75
+ requiresApproval: true,
76
+ summary: 'Would create a GitHub Release with tarball, checksum, and manifest only in a later approval-gated release command.'
77
+ },
78
+ {
79
+ id: 'docker-image',
80
+ target: 'docker-image',
81
+ willExecute: false,
82
+ requiresApproval: true,
83
+ summary: 'Docker image publishing remains deferred unless the product/server runtime surface changes.'
84
+ }
85
+ ],
86
+ privacy: {
87
+ tokenValuesIncluded: false,
88
+ rawLogsIncluded: false,
89
+ privatePathsIncluded: false,
90
+ publishExecuted: false,
91
+ githubReleaseCreated: false,
92
+ dockerImageBuilt: false
93
+ },
94
+ issues
95
+ };
96
+ (0, schema_1.assertSchema)('hadara.releaseDryRun.v1', report);
97
+ return report;
98
+ }
99
+ function readCurrentGitCommit(projectRoot) {
100
+ const gitDir = node_path_1.default.join(projectRoot, '.git');
101
+ try {
102
+ const head = node_fs_1.default.readFileSync(node_path_1.default.join(gitDir, 'HEAD'), 'utf8').trim();
103
+ if (/^[a-f0-9]{40}$/.test(head))
104
+ return head;
105
+ const match = /^ref:\s*(.+)$/.exec(head);
106
+ if (!match)
107
+ return undefined;
108
+ const refPath = node_path_1.default.join(gitDir, match[1]);
109
+ const ref = node_fs_1.default.readFileSync(refPath, 'utf8').trim();
110
+ return /^[a-f0-9]{40}$/.test(ref) ? ref : undefined;
111
+ }
112
+ catch {
113
+ return undefined;
114
+ }
115
+ }
116
+ function validateEvidenceRequirement(records, requirement) {
117
+ const record = records.filter(requirement.predicate).sort((a, b) => b.time.localeCompare(a.time))[0];
118
+ if (!record) {
119
+ return {
120
+ code: requirement.code,
121
+ artifactExists: false
122
+ };
123
+ }
124
+ const artifact = (0, release_evidence_1.validateReleaseEvidenceArtifact)(record);
125
+ return {
126
+ code: requirement.code,
127
+ taskId: record.taskId,
128
+ time: record.time,
129
+ artifactExists: artifact.exists,
130
+ result: record.result,
131
+ ...(record.evidencePath ? { evidencePath: record.evidencePath } : {}),
132
+ ...(artifact.schemaValid === undefined ? {} : { artifactSchemaValid: artifact.schemaValid }),
133
+ ...(artifact.sourceOk === undefined ? {} : { sourceOk: artifact.sourceOk }),
134
+ ...(artifact.category ? { category: artifact.category } : {}),
135
+ ...(artifact.mode ? { mode: artifact.mode } : {}),
136
+ ...(artifact.packageVersion ? { packageVersion: artifact.packageVersion } : {}),
137
+ ...(artifact.gitCommit ? { gitCommit: artifact.gitCommit } : {}),
138
+ ...(artifact.manifestHash ? { manifestHash: artifact.manifestHash } : {})
139
+ };
140
+ }
141
+ function evidenceCheckPassed(item, packageVersion, gitCommit) {
142
+ if (item.result !== 'passed')
143
+ return false;
144
+ if (item.artifactExists !== true || item.artifactSchemaValid !== true || item.sourceOk !== true)
145
+ return false;
146
+ if (item.code === 'PACKAGE_SMOKE_EVIDENCE' && (item.category !== 'package-smoke' || item.mode !== 'local'))
147
+ return false;
148
+ if (item.code === 'CLEAN_CHECKOUT_SMOKE_EVIDENCE' && (item.category !== 'clean-checkout-smoke' || item.mode !== 'execute'))
149
+ return false;
150
+ if (item.code === 'RELEASE_ARTIFACT_EVIDENCE') {
151
+ if (item.category !== 'release-artifact' || item.mode !== 'execute')
152
+ return false;
153
+ if (item.packageVersion !== packageVersion)
154
+ return false;
155
+ if (!item.manifestHash)
156
+ return false;
157
+ }
158
+ if (gitCommit && item.gitCommit && item.gitCommit !== gitCommit)
159
+ return false;
160
+ return true;
161
+ }
162
+ function evidenceSummary(item, packageVersion, gitCommit) {
163
+ if (!item.taskId)
164
+ return 'No matching passed public evidence record was found.';
165
+ if (!item.artifactExists)
166
+ return `${item.taskId} at ${item.time} has no linked public evidence artifact.`;
167
+ if (item.artifactSchemaValid !== true)
168
+ return `${item.taskId} at ${item.time} has a linked artifact, but schema validation failed.`;
169
+ if (item.sourceOk !== true)
170
+ return `${item.taskId} at ${item.time} has a linked artifact, but source report ok is not true.`;
171
+ if (item.result !== 'passed')
172
+ return `${item.taskId} at ${item.time} is not a passed evidence record.`;
173
+ if (item.code === 'PACKAGE_SMOKE_EVIDENCE' && (item.category !== 'package-smoke' || item.mode !== 'local')) {
174
+ return `${item.taskId} at ${item.time} does not match expected package-smoke category/mode.`;
175
+ }
176
+ if (item.code === 'CLEAN_CHECKOUT_SMOKE_EVIDENCE' && (item.category !== 'clean-checkout-smoke' || item.mode !== 'execute')) {
177
+ return `${item.taskId} at ${item.time} does not match expected clean-checkout category/mode.`;
178
+ }
179
+ if (item.code === 'RELEASE_ARTIFACT_EVIDENCE') {
180
+ if (item.category !== 'release-artifact' || item.mode !== 'execute')
181
+ return `${item.taskId} at ${item.time} does not match expected release-artifact category/mode.`;
182
+ if (item.packageVersion !== packageVersion)
183
+ return `${item.taskId} at ${item.time} package version ${item.packageVersion ?? 'unknown'} does not match current ${packageVersion}.`;
184
+ if (!item.manifestHash)
185
+ return `${item.taskId} at ${item.time} does not expose a release artifact manifest hash.`;
186
+ }
187
+ if (gitCommit && item.gitCommit && item.gitCommit !== gitCommit)
188
+ return `${item.taskId} at ${item.time} git commit ${item.gitCommit} does not match current ${gitCommit}.`;
189
+ return `${item.taskId} at ${item.time} has a current schema-valid public artifact.`;
190
+ }
191
+ function evidenceRequirements() {
192
+ return [
193
+ {
194
+ code: 'PACKAGE_SMOKE_EVIDENCE',
195
+ name: 'Package smoke evidence',
196
+ category: 'package-smoke',
197
+ mode: 'local',
198
+ predicate: (record) => record.result === 'passed' &&
199
+ record.visibility === 'public' &&
200
+ record.evidencePath !== undefined &&
201
+ (includesAll(record.summary, ['package smoke', '--execute']) || includesAny(record.evidencePath, ['artifacts/package-smoke'])) &&
202
+ includesAny(`${record.summary}\n${record.evidencePath}`, ['--attach-evidence', 'artifacts/package-smoke', 'hadara.packageSmoke.v1'])
203
+ },
204
+ {
205
+ code: 'CLEAN_CHECKOUT_SMOKE_EVIDENCE',
206
+ name: 'Clean checkout smoke evidence',
207
+ category: 'clean-checkout-smoke',
208
+ mode: 'execute',
209
+ predicate: (record) => record.result === 'passed' &&
210
+ record.visibility === 'public' &&
211
+ record.evidencePath !== undefined &&
212
+ (includesAll(record.summary, ['smoke clean-checkout', '--execute']) || includesAny(record.evidencePath, ['artifacts/clean-checkout-smoke'])) &&
213
+ includesAny(`${record.summary}\n${record.evidencePath}`, ['--attach-evidence', 'artifacts/clean-checkout-smoke', 'hadara.cleanCheckoutSmoke.v1'])
214
+ },
215
+ {
216
+ code: 'RELEASE_ARTIFACT_EVIDENCE',
217
+ name: 'Release artifact evidence',
218
+ category: 'release-artifact',
219
+ mode: 'execute',
220
+ predicate: (record) => record.result === 'passed' &&
221
+ record.visibility === 'public' &&
222
+ record.evidencePath !== undefined &&
223
+ includesAll(record.summary, ['release artifact', '--execute']) &&
224
+ includesAny(record.summary, ['artifacts/release-artifact', 'hadara.releaseArtifact.v1', 'generated tarball/checksum/manifest'])
225
+ }
226
+ ];
227
+ }
228
+ function evidenceName(code) {
229
+ return evidenceRequirements().find((requirement) => requirement.code === code)?.name ?? code;
230
+ }
231
+ function readPackageMetadata(projectRoot) {
232
+ try {
233
+ const parsed = JSON.parse(node_fs_1.default.readFileSync(node_path_1.default.join(projectRoot, 'package.json'), 'utf8'));
234
+ if (!isRecord(parsed))
235
+ return { name: 'unknown', version: 'unknown' };
236
+ return {
237
+ name: typeof parsed.name === 'string' ? parsed.name : 'unknown',
238
+ version: typeof parsed.version === 'string' ? parsed.version : 'unknown'
239
+ };
240
+ }
241
+ catch {
242
+ return { name: 'unknown', version: 'unknown' };
243
+ }
244
+ }
245
+ function includesAll(text, needles) {
246
+ return needles.every((needle) => text.includes(needle));
247
+ }
248
+ function includesAny(text, needles) {
249
+ return needles.some((needle) => text.includes(needle));
250
+ }
251
+ function isRecord(value) {
252
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
253
+ }
@@ -0,0 +1,138 @@
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.readReleaseEvidenceRecords = readReleaseEvidenceRecords;
7
+ exports.validateReleaseEvidenceArtifact = validateReleaseEvidenceArtifact;
8
+ const node_crypto_1 = __importDefault(require("node:crypto"));
9
+ const node_fs_1 = __importDefault(require("node:fs"));
10
+ const node_path_1 = __importDefault(require("node:path"));
11
+ const schema_1 = require("../core/schema");
12
+ function readReleaseEvidenceRecords(projectRoot) {
13
+ const tasksDir = node_path_1.default.join(projectRoot, 'tasks');
14
+ if (!node_fs_1.default.existsSync(tasksDir))
15
+ return [];
16
+ return node_fs_1.default
17
+ .readdirSync(tasksDir, { withFileTypes: true })
18
+ .filter((entry) => entry.isDirectory() && /^T-\d{4}-/.test(entry.name))
19
+ .flatMap((entry) => readTaskEvidenceRecords(node_path_1.default.join(tasksDir, entry.name)));
20
+ }
21
+ function validateReleaseEvidenceArtifact(record) {
22
+ if (!record.evidencePath)
23
+ return { exists: false, issues: ['EVIDENCE_ARTIFACT_PATH_MISSING'] };
24
+ const taskRoot = node_path_1.default.resolve(record.taskDir);
25
+ const artifactPath = node_path_1.default.resolve(record.taskDir, record.evidencePath);
26
+ if (!artifactPath.startsWith(`${taskRoot}${node_path_1.default.sep}`))
27
+ return { exists: false, issues: ['EVIDENCE_ARTIFACT_OUTSIDE_TASK'] };
28
+ if (!node_fs_1.default.existsSync(artifactPath))
29
+ return { exists: false, issues: ['EVIDENCE_ARTIFACT_MISSING'] };
30
+ try {
31
+ const content = node_fs_1.default.readFileSync(artifactPath, 'utf8');
32
+ const parsed = JSON.parse(content);
33
+ if (!isRecord(parsed) || typeof parsed.schemaVersion !== 'string') {
34
+ return { exists: true, schemaValid: false, issues: ['EVIDENCE_ARTIFACT_SCHEMA_MISSING'] };
35
+ }
36
+ if (parsed.schemaVersion === 'hadara.smokeEvidenceSummary.v1') {
37
+ const sourceReport = isRecord(parsed.sourceReport) ? parsed.sourceReport : {};
38
+ return {
39
+ exists: true,
40
+ schemaVersion: parsed.schemaVersion,
41
+ schemaValid: (0, schema_1.validateSchema)('hadara.smokeEvidenceSummary.v1', parsed).ok,
42
+ sourceOk: sourceReport.ok === true,
43
+ category: typeof parsed.category === 'string' ? parsed.category : undefined,
44
+ mode: typeof sourceReport.mode === 'string' ? sourceReport.mode : undefined,
45
+ packageVersion: readOptionalString(parsed.packageVersion) ?? readOptionalString(sourceReport.packageVersion),
46
+ gitCommit: readOptionalString(parsed.gitCommit) ?? readOptionalString(sourceReport.gitCommit),
47
+ issues: []
48
+ };
49
+ }
50
+ if (parsed.schemaVersion === 'hadara.releaseArtifact.v1') {
51
+ const artifacts = Array.isArray(parsed.artifacts) ? parsed.artifacts : [];
52
+ const manifest = artifacts.find((artifact) => isRecord(artifact) && artifact.kind === 'manifest');
53
+ const evidence = isRecord(parsed.evidence) ? parsed.evidence : {};
54
+ return {
55
+ exists: true,
56
+ schemaVersion: parsed.schemaVersion,
57
+ schemaValid: (0, schema_1.validateSchema)('hadara.releaseArtifact.v1', parsed).ok,
58
+ sourceOk: parsed.ok === true,
59
+ category: 'release-artifact',
60
+ mode: typeof parsed.mode === 'string' ? parsed.mode : undefined,
61
+ packageVersion: isRecord(parsed.package) ? readOptionalString(parsed.package.version) : undefined,
62
+ gitCommit: readOptionalString(evidence.gitCommit) ?? readOptionalString(parsed.gitCommit),
63
+ manifestHash: readOptionalString(manifest?.hash),
64
+ issues: []
65
+ };
66
+ }
67
+ if (parsed.schemaVersion === 'hadara.releaseArtifact.manifest.v1') {
68
+ return {
69
+ exists: true,
70
+ schemaVersion: parsed.schemaVersion,
71
+ schemaValid: (0, schema_1.validateSchema)('hadara.releaseArtifact.manifest.v1', parsed).ok,
72
+ sourceOk: true,
73
+ category: 'release-artifact',
74
+ mode: 'execute',
75
+ packageVersion: isRecord(parsed.package) ? readOptionalString(parsed.package.version) : undefined,
76
+ manifestHash: `sha256:${sha256(content)}`,
77
+ issues: []
78
+ };
79
+ }
80
+ return {
81
+ exists: true,
82
+ schemaVersion: parsed.schemaVersion,
83
+ schemaValid: false,
84
+ issues: ['EVIDENCE_ARTIFACT_SCHEMA_UNSUPPORTED']
85
+ };
86
+ }
87
+ catch {
88
+ return { exists: true, schemaValid: false, issues: ['EVIDENCE_ARTIFACT_INVALID_JSON'] };
89
+ }
90
+ }
91
+ function readTaskEvidenceRecords(taskDir) {
92
+ const evidencePath = node_path_1.default.join(taskDir, 'evidence.jsonl');
93
+ if (!node_fs_1.default.existsSync(evidencePath))
94
+ return [];
95
+ return node_fs_1.default
96
+ .readFileSync(evidencePath, 'utf8')
97
+ .split(/\r?\n/)
98
+ .map((line) => {
99
+ if (line.trim() === '')
100
+ return null;
101
+ try {
102
+ const parsed = JSON.parse(line);
103
+ if (!isRecord(parsed))
104
+ return null;
105
+ if (parsed.schemaVersion !== 'hadara.evidence.v1')
106
+ return null;
107
+ if (typeof parsed.time !== 'string' || typeof parsed.summary !== 'string')
108
+ return null;
109
+ if (typeof parsed.taskId !== 'string' || typeof parsed.kind !== 'string')
110
+ return null;
111
+ if (typeof parsed.result !== 'string' || typeof parsed.visibility !== 'string')
112
+ return null;
113
+ return {
114
+ taskId: parsed.taskId,
115
+ taskDir,
116
+ time: parsed.time,
117
+ kind: parsed.kind,
118
+ summary: parsed.summary,
119
+ result: parsed.result,
120
+ visibility: parsed.visibility,
121
+ ...(typeof parsed.evidencePath === 'string' ? { evidencePath: parsed.evidencePath } : {})
122
+ };
123
+ }
124
+ catch {
125
+ return null;
126
+ }
127
+ })
128
+ .filter((record) => record !== null);
129
+ }
130
+ function readOptionalString(value) {
131
+ return typeof value === 'string' && value.trim() !== '' ? value : undefined;
132
+ }
133
+ function sha256(content) {
134
+ return node_crypto_1.default.createHash('sha256').update(content, 'utf8').digest('hex');
135
+ }
136
+ function isRecord(value) {
137
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
138
+ }
@@ -0,0 +1,163 @@
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.createReleasePublishReport = createReleasePublishReport;
7
+ const node_fs_1 = __importDefault(require("node:fs"));
8
+ const node_path_1 = __importDefault(require("node:path"));
9
+ const schema_1 = require("../core/schema");
10
+ const audit_1 = require("../core/audit");
11
+ const release_dry_run_1 = require("./release-dry-run");
12
+ const CONFIRMATION_PHRASE = 'publish-deploy';
13
+ function createReleasePublishReport(options) {
14
+ const mode = options.mode ?? 'dry-run';
15
+ const env = options.env ?? process.env;
16
+ const metadata = readPackageMetadata(options.projectRoot);
17
+ const metadataPublishable = !metadata.private && /^0\.1\.0-rc\.\d+$/.test(metadata.packageVersion);
18
+ const dryRun = (0, release_dry_run_1.createReleaseDryRunReport)(options.projectRoot);
19
+ const approval = {
20
+ required: true,
21
+ actorProvided: Boolean(options.approvalActor),
22
+ reasonProvided: Boolean(options.approvalReason),
23
+ confirmationProvided: options.confirm === CONFIRMATION_PHRASE
24
+ };
25
+ const checks = [
26
+ {
27
+ code: 'RELEASE_DRY_RUN',
28
+ name: 'Release dry-run readiness',
29
+ status: dryRun.ok ? 'passed' : 'error',
30
+ summary: dryRun.ok ? 'Release dry-run prerequisites passed.' : 'Release dry-run prerequisites must pass before publish/deploy.'
31
+ },
32
+ {
33
+ code: 'PACKAGE_PUBLISHABLE_METADATA',
34
+ name: 'Package publishable metadata',
35
+ status: metadataPublishable ? 'passed' : 'error',
36
+ summary: metadataPublishable ? 'Package metadata is in release-candidate mode.' : 'Package metadata must be 0.1.0-rc.N with private false before publish/deploy readiness can pass.'
37
+ },
38
+ {
39
+ code: 'APPROVAL_RECORD',
40
+ name: 'Approval record',
41
+ status: mode === 'dry-run' || (approval.actorProvided && approval.reasonProvided && approval.confirmationProvided) ? 'passed' : 'error',
42
+ summary: mode === 'dry-run'
43
+ ? 'Dry-run mode records approval requirements without requiring approval metadata.'
44
+ : 'Execute mode requires approval actor, reason, and confirmation phrase.'
45
+ },
46
+ {
47
+ code: 'NPM_TOKEN_PRESENCE',
48
+ name: 'NPM token presence',
49
+ status: env.NPM_TOKEN ? 'passed' : mode === 'dry-run' ? 'warning' : 'error',
50
+ summary: env.NPM_TOKEN ? 'NPM_TOKEN is present; token value is not read into the report.' : 'NPM_TOKEN is not present.'
51
+ },
52
+ {
53
+ code: 'GITHUB_RELEASE_TOKEN_PRESENCE',
54
+ name: 'GitHub Release token presence',
55
+ status: env.HADARA_GITHUB_RELEASE_TOKEN || env.GITHUB_TOKEN ? 'passed' : mode === 'dry-run' ? 'warning' : 'error',
56
+ summary: env.HADARA_GITHUB_RELEASE_TOKEN || env.GITHUB_TOKEN
57
+ ? 'A GitHub Release token name is present; token value is not read into the report.'
58
+ : 'No GitHub Release token is present.'
59
+ },
60
+ {
61
+ code: 'DOCKER_TARGET_DEFERRED',
62
+ name: 'Docker target deferred',
63
+ status: 'passed',
64
+ summary: 'Docker image publishing remains deferred.'
65
+ },
66
+ {
67
+ code: 'NO_MUTATION_EXECUTED',
68
+ name: 'No release mutation executed',
69
+ status: 'passed',
70
+ summary: 'This command reports gates and never publishes, creates GitHub Releases, or builds Docker images.'
71
+ }
72
+ ];
73
+ const issues = checks
74
+ .filter((check) => check.status !== 'passed')
75
+ .map((check) => ({
76
+ severity: check.status === 'error' ? 'error' : 'warning',
77
+ code: `${check.code}_${check.status === 'error' ? 'BLOCKED' : 'WARNING'}`,
78
+ message: `${check.name}: ${check.summary}`
79
+ }));
80
+ const audit = writeReleasePublishAudit(options, mode, issues);
81
+ const report = {
82
+ schemaVersion: 'hadara.releasePublish.v1',
83
+ command: 'release.publish',
84
+ mode,
85
+ ok: mode === 'dry-run' ? checks.every((check) => check.status !== 'error') : false,
86
+ current: metadata,
87
+ approval,
88
+ releaseTargets: [
89
+ {
90
+ id: 'npm-publish',
91
+ target: 'npm-package',
92
+ status: dryRun.ok && metadataPublishable && env.NPM_TOKEN ? 'ready' : 'blocked',
93
+ tokenName: 'NPM_TOKEN',
94
+ tokenPresent: Boolean(env.NPM_TOKEN),
95
+ willExecute: false,
96
+ summary: 'npm publish remains blocked until all gates pass and a future mutation-capable release runner is explicitly approved.'
97
+ },
98
+ {
99
+ id: 'github-release',
100
+ target: 'github-release',
101
+ status: dryRun.ok && metadataPublishable && (env.HADARA_GITHUB_RELEASE_TOKEN || env.GITHUB_TOKEN) ? 'ready' : 'blocked',
102
+ tokenName: env.HADARA_GITHUB_RELEASE_TOKEN ? 'HADARA_GITHUB_RELEASE_TOKEN' : 'GITHUB_TOKEN',
103
+ tokenPresent: Boolean(env.HADARA_GITHUB_RELEASE_TOKEN || env.GITHUB_TOKEN),
104
+ willExecute: false,
105
+ summary: 'GitHub Release creation remains blocked until all gates pass and a future mutation-capable release runner is explicitly approved.'
106
+ },
107
+ {
108
+ id: 'docker-image',
109
+ target: 'docker-image',
110
+ status: 'deferred',
111
+ willExecute: false,
112
+ summary: 'Docker image publishing is deferred by the current release target decision.'
113
+ }
114
+ ],
115
+ checks,
116
+ privacy: {
117
+ tokenValuesIncluded: false,
118
+ rawLogsIncluded: false,
119
+ privatePathsIncluded: false,
120
+ publishExecuted: false,
121
+ githubReleaseCreated: false,
122
+ dockerImageBuilt: false
123
+ },
124
+ ...(audit ? { audit } : {}),
125
+ issues
126
+ };
127
+ (0, schema_1.assertSchema)('hadara.releasePublish.v1', report);
128
+ return report;
129
+ }
130
+ function readPackageMetadata(projectRoot) {
131
+ const parsed = JSON.parse(node_fs_1.default.readFileSync(node_path_1.default.join(projectRoot, 'package.json'), 'utf8'));
132
+ return {
133
+ packageName: typeof parsed.name === 'string' ? parsed.name : 'unknown',
134
+ packageVersion: typeof parsed.version === 'string' ? parsed.version : 'unknown',
135
+ private: parsed.private === true
136
+ };
137
+ }
138
+ function writeReleasePublishAudit(options, mode, issues) {
139
+ if (mode !== 'execute')
140
+ return undefined;
141
+ if (!options.auditDir) {
142
+ return {
143
+ attempted: true,
144
+ written: false
145
+ };
146
+ }
147
+ (0, audit_1.writeAuditEvent)(options.auditDir, {
148
+ actor: 'user',
149
+ event_type: 'release.publish.execute.requested',
150
+ risk: 'blocked',
151
+ summary: 'Release publish/deploy execute request was blocked before mutation.',
152
+ payload: {
153
+ approvalActorProvided: Boolean(options.approvalActor),
154
+ approvalReasonProvided: Boolean(options.approvalReason),
155
+ confirmationProvided: options.confirm === CONFIRMATION_PHRASE,
156
+ issueCodes: issues.map((issue) => issue.code)
157
+ }
158
+ });
159
+ return {
160
+ attempted: true,
161
+ written: true
162
+ };
163
+ }
@@ -0,0 +1,104 @@
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.attachReducedSmokeEvidence = attachReducedSmokeEvidence;
7
+ const node_fs_1 = __importDefault(require("node:fs"));
8
+ const node_path_1 = __importDefault(require("node:path"));
9
+ const fs_1 = require("../core/fs");
10
+ const evidence_1 = require("../evidence/evidence");
11
+ const redaction_1 = require("../core/redaction");
12
+ function attachReducedSmokeEvidence(input) {
13
+ const taskDir = findTaskDir(input.projectRoot, input.taskId);
14
+ if (!taskDir) {
15
+ throw new Error(`Task capsule not found: ${input.taskId}`);
16
+ }
17
+ const time = new Date().toISOString();
18
+ const summaryContent = createReducedSummary(input, time);
19
+ const content = JSON.stringify(summaryContent, null, 2);
20
+ const policy = (0, evidence_1.createPublicEvidenceArtifactPolicyReport)(content);
21
+ if (policy.blocking) {
22
+ throw new evidence_1.EvidenceArtifactPolicyError('PUBLIC_ARTIFACT_SECRET_DETECTED', 'Public smoke evidence summary contains secret-like content; collect raw logs privately or reduce the report first.', policy.redaction);
23
+ }
24
+ const artifactDir = node_path_1.default.join(taskDir, 'artifacts', input.category);
25
+ (0, fs_1.ensureDir)(artifactDir);
26
+ const targetPath = node_path_1.default.join(artifactDir, `${safeFilePart(time)}-summary.json`);
27
+ node_fs_1.default.writeFileSync(targetPath, content, 'utf8');
28
+ const evidencePath = toPortablePath(node_path_1.default.relative(taskDir, targetPath));
29
+ const summary = (0, redaction_1.redactSecrets)(input.summary.replace(/\|/g, '/'));
30
+ const evidence = {
31
+ schemaVersion: 'hadara.evidence.v1',
32
+ time,
33
+ taskId: input.taskId,
34
+ kind: input.kind,
35
+ summary,
36
+ result: input.result,
37
+ visibility: 'public',
38
+ evidencePath
39
+ };
40
+ appendEvidenceMarkdown(taskDir, evidence);
41
+ node_fs_1.default.appendFileSync(node_path_1.default.join(taskDir, 'evidence.jsonl'), `${JSON.stringify(evidence)}\n`, 'utf8');
42
+ return {
43
+ evidence,
44
+ artifact: {
45
+ kind: 'summary',
46
+ visibility: 'public',
47
+ evidencePath: toPortablePath(node_path_1.default.join('tasks', node_path_1.default.basename(taskDir), evidencePath)),
48
+ rawContentIncluded: false
49
+ }
50
+ };
51
+ }
52
+ function createReducedSummary(input, time) {
53
+ return {
54
+ schemaVersion: 'hadara.smokeEvidenceSummary.v1',
55
+ time,
56
+ taskId: input.taskId,
57
+ category: input.category,
58
+ sourceReport: {
59
+ schemaVersion: input.report.schemaVersion,
60
+ command: input.report.command,
61
+ mode: input.report.mode,
62
+ ok: input.report.ok
63
+ },
64
+ execution: input.report.execution,
65
+ steps: input.report.steps.map((step) => ({
66
+ id: step.id,
67
+ label: step.label,
68
+ status: step.status,
69
+ ...(step.exitCode === undefined ? {} : { exitCode: step.exitCode }),
70
+ ...(step.elapsedMs === undefined ? {} : { elapsedMs: step.elapsedMs }),
71
+ summary: step.summary
72
+ })),
73
+ privacy: input.report.privacy,
74
+ issues: input.report.issues.map((issue) => ({
75
+ severity: issue.severity,
76
+ code: issue.code,
77
+ message: issue.message,
78
+ ...(issue.stepId ? { stepId: issue.stepId } : {})
79
+ })),
80
+ rawLogsIncluded: false,
81
+ privatePathsIncluded: false,
82
+ rawPackageContentsIncluded: false
83
+ };
84
+ }
85
+ function appendEvidenceMarkdown(taskDir, evidence) {
86
+ const markdownPath = node_path_1.default.join(taskDir, 'EVIDENCE.md');
87
+ if (!node_fs_1.default.existsSync(markdownPath)) {
88
+ node_fs_1.default.writeFileSync(markdownPath, '# Evidence\n\n| Time | Kind | Summary | Result |\n|---|---|---|---|\n', 'utf8');
89
+ }
90
+ node_fs_1.default.appendFileSync(markdownPath, `| ${evidence.time} | ${evidence.kind} | ${evidence.summary} (${evidence.evidencePath}) | ${evidence.result} |\n`, 'utf8');
91
+ }
92
+ function findTaskDir(projectRoot, taskId) {
93
+ const tasksDir = node_path_1.default.join(projectRoot, 'tasks');
94
+ if (!node_fs_1.default.existsSync(tasksDir))
95
+ return null;
96
+ const entry = node_fs_1.default.readdirSync(tasksDir).find((name) => name.startsWith(`${taskId}-`));
97
+ return entry ? node_path_1.default.join(tasksDir, entry) : null;
98
+ }
99
+ function safeFilePart(value) {
100
+ return value.replace(/[^A-Za-z0-9._-]+/g, '-').replace(/^-+|-+$/g, '') || 'artifact';
101
+ }
102
+ function toPortablePath(value) {
103
+ return value.split(node_path_1.default.sep).join('/');
104
+ }