@haystackeditor/cli 0.25.1 → 0.27.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/README.md +39 -463
- package/dist/capture/app-config.js +30 -6
- package/dist/commands/capture-brief.js +45 -35
- package/dist/commands/capture-contract.js +4 -4
- package/dist/commands/crawl-report.js +196 -0
- package/dist/commands/db-profile-upload.js +3 -3
- package/dist/commands/feedback.js +66 -0
- package/dist/commands/init-telemetry.js +55 -9
- package/dist/commands/init.js +8 -3
- package/dist/commands/lockfile-pin.js +307 -0
- package/dist/commands/telemetry-token.js +3 -3
- package/dist/commands/tokens.js +4 -4
- package/dist/commands/verify-explore.js +3 -3
- package/dist/commands/verify-history.js +2 -2
- package/dist/commands/verify-onboarding.js +13 -16
- package/dist/commands/verify-precompute.js +3 -4
- package/dist/commands/verify.js +31 -118
- package/dist/index.js +55 -968
- package/dist/schema.js +4 -10
- package/dist/utils/haystack-api.js +8 -36
- package/package.json +1 -5
- package/schemas/feedback.v1.json +13 -0
- package/schemas/pre-verify.v2.json +240 -0
- package/schemas/verify-raw.v1.json +1132 -0
- package/schemas/verify.v2.json +655 -0
- package/dist/assets/hooks/agent-context/detect.ts +0 -316
- package/dist/assets/hooks/agent-context/format.ts +0 -100
- package/dist/assets/hooks/agent-context/index.ts +0 -41
- package/dist/assets/hooks/agent-context/parsers/claude.ts +0 -262
- package/dist/assets/hooks/agent-context/parsers/codex.ts +0 -416
- package/dist/assets/hooks/agent-context/parsers/gemini.ts +0 -155
- package/dist/assets/hooks/agent-context/parsers/opencode.ts +0 -174
- package/dist/assets/hooks/agent-context/tsconfig.json +0 -14
- package/dist/assets/hooks/agent-context/types.ts +0 -58
- package/dist/assets/hooks/llm-rules-template.md +0 -59
- package/dist/assets/hooks/package-lock.json +0 -598
- package/dist/assets/hooks/package.json +0 -12
- package/dist/assets/hooks/scripts/commit-msg.sh +0 -5
- package/dist/assets/hooks/scripts/post-commit.sh +0 -5
- package/dist/assets/hooks/scripts/pre-commit.sh +0 -175
- package/dist/assets/hooks/scripts/pre-push.sh +0 -25
- package/dist/assets/hooks/scripts/prepare-commit-msg.sh +0 -5
- package/dist/assets/hooks/truncation-checker/ast-analyzer.ts +0 -528
- package/dist/assets/hooks/truncation-checker/index.ts +0 -595
- package/dist/assets/hooks/truncation-checker/tsconfig.json +0 -13
- package/dist/assets/skills/map-cloud-verifier-universe/SKILL.md +0 -2051
- package/dist/assets/skills/map-cloud-verifier-universe/agents/openai.yaml +0 -4
- package/dist/assets/skills/map-cloud-verifier-universe/references/output-contract.md +0 -3411
- package/dist/assets/skills/map-your-system.md +0 -143
- package/dist/assets/skills/submit.md +0 -200
- package/dist/commands/ask.js +0 -20
- package/dist/commands/cloud-verifier-behaviors.js +0 -218
- package/dist/commands/cloud-verifier-data-store-census.js +0 -539
- package/dist/commands/cloud-verifier-data-store-drift.js +0 -158
- package/dist/commands/cloud-verifier-identity-census.js +0 -4060
- package/dist/commands/cloud-verifier-materialization.js +0 -704
- package/dist/commands/cloud-verifier-pascal-selector-census.js +0 -1382
- package/dist/commands/cloud-verifier-python-manifest-selector-census.js +0 -2015
- package/dist/commands/cloud-verifier-specialized-operational-census.js +0 -11432
- package/dist/commands/cloud-verifier-universe.js +0 -10178
- package/dist/commands/config.js +0 -549
- package/dist/commands/design-verify.js +0 -311
- package/dist/commands/dismiss.js +0 -159
- package/dist/commands/hooks.js +0 -226
- package/dist/commands/inbox.js +0 -137
- package/dist/commands/mcp.js +0 -201
- package/dist/commands/policy.js +0 -371
- package/dist/commands/pr-status.js +0 -207
- package/dist/commands/pr.js +0 -105
- package/dist/commands/prepare-universe-review.js +0 -1092
- package/dist/commands/production-source-deny-policy.js +0 -100
- package/dist/commands/request-review.js +0 -74
- package/dist/commands/review.js +0 -191
- package/dist/commands/rules.js +0 -98
- package/dist/commands/scaffold-provisional-universe.js +0 -806
- package/dist/commands/setup.js +0 -1170
- package/dist/commands/skills.js +0 -447
- package/dist/commands/status.js +0 -35
- package/dist/commands/submit.js +0 -745
- package/dist/commands/system-map.js +0 -228
- package/dist/commands/triage.js +0 -598
- package/dist/commands/webhooks.js +0 -241
- package/dist/states.js +0 -46
- package/dist/tools/detect.js +0 -832
- package/dist/triage/astra.js +0 -202
- package/dist/triage/prompts.js +0 -188
- package/dist/triage/runner.js +0 -200
- package/dist/triage/types.js +0 -7
- package/dist/types.js +0 -326
- package/dist/utils/action-output.js +0 -26
- package/dist/utils/analysis-api.js +0 -416
- package/dist/utils/config.js +0 -54
- package/dist/utils/design-verifier-api.js +0 -294
- package/dist/utils/design-verifier-history.js +0 -79
- package/dist/utils/design-verifier-result.js +0 -424
- package/dist/utils/github-api.js +0 -324
- package/dist/utils/pending-state.js +0 -86
- package/dist/utils/pr-ref.js +0 -56
- package/dist/utils/prompter.js +0 -328
- package/schemas/action.v1.json +0 -22
- package/schemas/ask.v1.json +0 -40
- package/schemas/inbox.v1.json +0 -27
- package/schemas/pr-status.v1.json +0 -61
- package/schemas/pr.v1.json +0 -97
- package/schemas/pr.v3.json +0 -45
- package/schemas/setup.v1.json +0 -75
- package/schemas/submit.v1.json +0 -90
- package/schemas/triage.v1.json +0 -103
- package/schemas/triage.v2.json +0 -64
|
@@ -1,806 +0,0 @@
|
|
|
1
|
-
import { closeSync, constants as fsConstants, fstatSync, lstatSync, mkdirSync, mkdtempSync, openSync, readFileSync, renameSync, rmSync, writeFileSync, } from 'node:fs';
|
|
2
|
-
import { createHash } from 'node:crypto';
|
|
3
|
-
import { execFileSync } from 'node:child_process';
|
|
4
|
-
import { basename, dirname, isAbsolute, join, normalize, parse as parsePath, relative, resolve, sep, } from 'node:path';
|
|
5
|
-
import { stringify as stringifyYaml } from 'yaml';
|
|
6
|
-
import { z } from 'zod';
|
|
7
|
-
import { findGitRoot } from '../utils/hooks.js';
|
|
8
|
-
import { VALIDATOR_CONTRACT_REVISION, validateUniverseArtifactTexts, } from './cloud-verifier-universe.js';
|
|
9
|
-
const STABLE_ID = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
10
|
-
const SOURCE_COMMIT = /^[0-9a-f]{40}$/;
|
|
11
|
-
const ARTIFACT_NAMES = ['universe.yml', 'evidence.yml', 'review.yml'];
|
|
12
|
-
const PRIMARY_CALL_LIMIT = 48;
|
|
13
|
-
const PRIMARY_ELAPSED_LIMIT_SECONDS = 900;
|
|
14
|
-
const AUTHORITY_CALL_LIMIT = 6;
|
|
15
|
-
const AUTHORITY_ELAPSED_LIMIT_SECONDS = 60;
|
|
16
|
-
export const TOPOLOGY_PRESENCE_SURFACE_KINDS = [
|
|
17
|
-
'runtime-entrypoints',
|
|
18
|
-
'package-workspace-manifests',
|
|
19
|
-
'container-descriptors',
|
|
20
|
-
'deployment-descriptors',
|
|
21
|
-
'structured-capability-indexes',
|
|
22
|
-
];
|
|
23
|
-
export const PROVISIONAL_UNIVERSE_INPUT_MAX_BYTES = 256 * 1024;
|
|
24
|
-
export const PROVISIONAL_ACCESS_REQUEST_FIELDS = [
|
|
25
|
-
'selected-application-id-and-environment-id',
|
|
26
|
-
'approved-replica-destination-policy-and-approval-ref',
|
|
27
|
-
'bounded-resource-kinds-opaque-catalog-handles-and-dependency-edges',
|
|
28
|
-
'artifact-runtime-and-non-secret-config-opaque-catalog-refs',
|
|
29
|
-
'external-integration-kinds-and-secret-reference-names',
|
|
30
|
-
'replica-capabilities-strategies-consistency-freshness-latency-and-cost-bounds',
|
|
31
|
-
'replica-only-golden-descriptor-and-lease-reference-contract',
|
|
32
|
-
];
|
|
33
|
-
const UNSAFE_VALUE_PATTERNS = [
|
|
34
|
-
[/[a-z][a-z0-9+.-]*:\/\//i, 'raw URLs are forbidden'],
|
|
35
|
-
[/(?:jdbc|postgres(?:ql)?|mysql|mariadb|redis|rediss|amqp|amqps|mongodb(?:\+srv)?|sqlserver):/i, 'connection strings are forbidden'],
|
|
36
|
-
[/\barn:(?:aws|aws-cn|aws-us-gov):/i, 'provider ARNs are forbidden'],
|
|
37
|
-
[/(?:^|[^a-z0-9])(?:acl|ami|eipalloc|eni|fs|i|igw|nat|rtb|sg|snap|subnet|vol|vpc|vpce)-[0-9a-f]{8,17}(?=$|[^a-z0-9])/i, 'raw provider resource IDs are forbidden'],
|
|
38
|
-
[/(?:^|[/])subscriptions\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}(?:[/]|$)/i, 'raw Azure resource IDs are forbidden'],
|
|
39
|
-
[/(?:^|[^a-z0-9])projects\/[^/\s]+\/(?:zones|regions|locations|instances|clusters|databases|buckets)(?:[/]|$)/i, 'raw Google Cloud resource IDs are forbidden'],
|
|
40
|
-
[/\bocid1\.[a-z0-9.-]+\.[a-z0-9.-]*[a-z0-9]\b/i, 'raw OCI resource IDs are forbidden'],
|
|
41
|
-
[/\b(?:AKIA|ASIA)[A-Z0-9]{16}\b/, 'credential values are forbidden'],
|
|
42
|
-
[/\b(?:gh[pousr]_[A-Za-z0-9]{20,}|github_pat_[A-Za-z0-9_]{20,}|sk_(?:live|test)_[A-Za-z0-9]+)\b/, 'credential values are forbidden'],
|
|
43
|
-
[/\bxox[baprs]-[A-Za-z0-9-]{10,}\b/, 'credential values are forbidden'],
|
|
44
|
-
[/\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b/, 'credential values are forbidden'],
|
|
45
|
-
[/\bBearer\s+[A-Za-z0-9._~+/-]+=*\b/i, 'credential values are forbidden'],
|
|
46
|
-
[/-----BEGIN [A-Z ]*PRIVATE KEY-----/, 'private key values are forbidden'],
|
|
47
|
-
[/(?:password|secret|token|api[_-]?key)\s*[:=]\s*[^\s,;]+/i, 'secret values are forbidden'],
|
|
48
|
-
];
|
|
49
|
-
function sanitizedStringSchema(schema, label) {
|
|
50
|
-
return schema.superRefine((value, context) => {
|
|
51
|
-
if (value.includes('\0')) {
|
|
52
|
-
context.addIssue({ code: 'custom', message: `${label} must not contain NUL` });
|
|
53
|
-
}
|
|
54
|
-
for (const [pattern, message] of UNSAFE_VALUE_PATTERNS) {
|
|
55
|
-
if (pattern.test(value)) {
|
|
56
|
-
context.addIssue({ code: 'custom', message: `${label}: ${message}` });
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
function safeStringSchema(label) {
|
|
62
|
-
return sanitizedStringSchema(z.string().min(1, `${label} must not be empty`), label);
|
|
63
|
-
}
|
|
64
|
-
const stableIdSchema = sanitizedStringSchema(z.string().regex(STABLE_ID, 'must be a lowercase kebab-case stable ID'), 'stable ID');
|
|
65
|
-
const sourceCommitSchema = sanitizedStringSchema(z.string().regex(SOURCE_COMMIT, 'must be a lowercase 40-character Git SHA'), 'source commit');
|
|
66
|
-
const environmentScopeSchema = z.enum([
|
|
67
|
-
'hosted-production',
|
|
68
|
-
'production-reference',
|
|
69
|
-
'self-host-reference',
|
|
70
|
-
'local-development',
|
|
71
|
-
'example',
|
|
72
|
-
'unknown',
|
|
73
|
-
]);
|
|
74
|
-
const fidelityCeilingSchema = z.enum([
|
|
75
|
-
'production-reference-only',
|
|
76
|
-
'self-host-reference-only',
|
|
77
|
-
'local-development-only',
|
|
78
|
-
'unknown',
|
|
79
|
-
]);
|
|
80
|
-
const repositoryPathSchema = safeStringSchema('repository path').superRefine((value, context) => {
|
|
81
|
-
if (value.includes('\\') ||
|
|
82
|
-
value.startsWith('/') ||
|
|
83
|
-
value.endsWith('/') ||
|
|
84
|
-
normalize(value) !== value ||
|
|
85
|
-
value.split('/').some((component) => component === '..' || component === '')) {
|
|
86
|
-
context.addIssue({
|
|
87
|
-
code: 'custom',
|
|
88
|
-
message: 'must be a normalized repository-relative POSIX path',
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
if (value.split('/').includes('.haystack')) {
|
|
92
|
-
context.addIssue({
|
|
93
|
-
code: 'custom',
|
|
94
|
-
message: 'must not reference generated .haystack artifacts',
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
});
|
|
98
|
-
const evidenceSourceSchema = z
|
|
99
|
-
.object({
|
|
100
|
-
path: repositoryPathSchema,
|
|
101
|
-
anchor: safeStringSchema('source anchor'),
|
|
102
|
-
kind: stableIdSchema,
|
|
103
|
-
locatorKind: z.enum([
|
|
104
|
-
'symbol',
|
|
105
|
-
'exact-snippet',
|
|
106
|
-
'line-span',
|
|
107
|
-
'manifest-key',
|
|
108
|
-
'config-key',
|
|
109
|
-
]),
|
|
110
|
-
lines: z
|
|
111
|
-
.object({
|
|
112
|
-
start: z.number().int().positive(),
|
|
113
|
-
end: z.number().int().positive(),
|
|
114
|
-
})
|
|
115
|
-
.strict()
|
|
116
|
-
.optional(),
|
|
117
|
-
})
|
|
118
|
-
.strict()
|
|
119
|
-
.superRefine((source, context) => {
|
|
120
|
-
if (source.locatorKind === 'line-span') {
|
|
121
|
-
if (!source.lines || source.lines.end < source.lines.start) {
|
|
122
|
-
context.addIssue({
|
|
123
|
-
code: 'custom',
|
|
124
|
-
path: ['lines'],
|
|
125
|
-
message: 'must contain positive start/end positions with end >= start for locatorKind line-span',
|
|
126
|
-
});
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
else if (source.lines !== undefined) {
|
|
130
|
-
context.addIssue({
|
|
131
|
-
code: 'custom',
|
|
132
|
-
path: ['lines'],
|
|
133
|
-
message: 'is only valid for locatorKind line-span',
|
|
134
|
-
});
|
|
135
|
-
}
|
|
136
|
-
});
|
|
137
|
-
const evidenceRecordSchema = z
|
|
138
|
-
.object({
|
|
139
|
-
id: stableIdSchema,
|
|
140
|
-
subjectIds: z.array(stableIdSchema).min(1),
|
|
141
|
-
assertion: safeStringSchema('evidence assertion'),
|
|
142
|
-
environmentScope: environmentScopeSchema,
|
|
143
|
-
authority: z.enum(['authoritative', 'indicative', 'non-production']),
|
|
144
|
-
confidence: z.enum(['proven', 'strong', 'tentative']),
|
|
145
|
-
claimKind: z.literal('topology-presence-census').optional(),
|
|
146
|
-
sources: z.array(evidenceSourceSchema).min(1),
|
|
147
|
-
})
|
|
148
|
-
.strict();
|
|
149
|
-
const budgetUsageSchema = z
|
|
150
|
-
.object({
|
|
151
|
-
repositoryInspectionCallsUsed: z.number().int().nonnegative(),
|
|
152
|
-
elapsedSeconds: z.number().nonnegative(),
|
|
153
|
-
})
|
|
154
|
-
.strict();
|
|
155
|
-
const provisionalUniverseScaffoldInputSchema = z
|
|
156
|
-
.object({
|
|
157
|
-
sourceCommit: sourceCommitSchema,
|
|
158
|
-
application: z
|
|
159
|
-
.object({
|
|
160
|
-
id: stableIdSchema,
|
|
161
|
-
requestedScope: stableIdSchema,
|
|
162
|
-
evaluationMode: z.literal('real-production', {
|
|
163
|
-
error: 'must be "real-production" because this scaffold records missing real-production authority',
|
|
164
|
-
}),
|
|
165
|
-
})
|
|
166
|
-
.strict(),
|
|
167
|
-
repositoryTopology: z
|
|
168
|
-
.object({
|
|
169
|
-
id: stableIdSchema,
|
|
170
|
-
environmentScope: environmentScopeSchema,
|
|
171
|
-
fidelityCeiling: fidelityCeilingSchema,
|
|
172
|
-
evidenceIds: z.array(stableIdSchema).min(1),
|
|
173
|
-
topologyPresenceCensus: z
|
|
174
|
-
.object({
|
|
175
|
-
status: z.literal('no-production-capable-topology'),
|
|
176
|
-
subjectId: stableIdSchema,
|
|
177
|
-
inspectedSurfaceKinds: z.tuple([
|
|
178
|
-
z.literal(TOPOLOGY_PRESENCE_SURFACE_KINDS[0]),
|
|
179
|
-
z.literal(TOPOLOGY_PRESENCE_SURFACE_KINDS[1]),
|
|
180
|
-
z.literal(TOPOLOGY_PRESENCE_SURFACE_KINDS[2]),
|
|
181
|
-
z.literal(TOPOLOGY_PRESENCE_SURFACE_KINDS[3]),
|
|
182
|
-
z.literal(TOPOLOGY_PRESENCE_SURFACE_KINDS[4]),
|
|
183
|
-
]),
|
|
184
|
-
productionCapableSurfaceCount: z.literal(0),
|
|
185
|
-
evidenceIds: z.array(stableIdSchema).min(1),
|
|
186
|
-
})
|
|
187
|
-
.strict(),
|
|
188
|
-
})
|
|
189
|
-
.strict(),
|
|
190
|
-
missingAuthority: z
|
|
191
|
-
.object({
|
|
192
|
-
id: stableIdSchema,
|
|
193
|
-
kind: stableIdSchema,
|
|
194
|
-
requiredFor: stableIdSchema,
|
|
195
|
-
status: z.literal('unavailable-in-repository'),
|
|
196
|
-
evidenceIds: z.array(stableIdSchema).min(1),
|
|
197
|
-
})
|
|
198
|
-
.strict(),
|
|
199
|
-
accessRequest: z
|
|
200
|
-
.object({
|
|
201
|
-
id: stableIdSchema,
|
|
202
|
-
source: stableIdSchema,
|
|
203
|
-
reason: stableIdSchema,
|
|
204
|
-
})
|
|
205
|
-
.strict(),
|
|
206
|
-
evidence: z.array(evidenceRecordSchema).min(1),
|
|
207
|
-
coverage: z
|
|
208
|
-
.object({
|
|
209
|
-
scannedRoots: z.array(repositoryPathSchema).min(1),
|
|
210
|
-
sourceKinds: z.array(stableIdSchema).min(1),
|
|
211
|
-
})
|
|
212
|
-
.strict(),
|
|
213
|
-
budget: z
|
|
214
|
-
.object({
|
|
215
|
-
primaryDiscovery: budgetUsageSchema,
|
|
216
|
-
authorityGate: budgetUsageSchema,
|
|
217
|
-
overrunBlockerIds: z
|
|
218
|
-
.object({
|
|
219
|
-
primaryDiscovery: stableIdSchema.optional(),
|
|
220
|
-
authorityGate: stableIdSchema.optional(),
|
|
221
|
-
})
|
|
222
|
-
.strict()
|
|
223
|
-
.optional(),
|
|
224
|
-
})
|
|
225
|
-
.strict(),
|
|
226
|
-
authorityBlocker: z
|
|
227
|
-
.object({
|
|
228
|
-
id: stableIdSchema,
|
|
229
|
-
kind: z.enum([
|
|
230
|
-
'missing-authoritative-source',
|
|
231
|
-
'production-authority-access-required',
|
|
232
|
-
]),
|
|
233
|
-
evidenceIds: z.array(stableIdSchema).min(1),
|
|
234
|
-
})
|
|
235
|
-
.strict(),
|
|
236
|
-
})
|
|
237
|
-
.strict();
|
|
238
|
-
function assertUnique(values, label) {
|
|
239
|
-
const seen = new Set();
|
|
240
|
-
for (const value of values) {
|
|
241
|
-
if (seen.has(value))
|
|
242
|
-
throw new Error(`${label} contains duplicate stable ID "${value}"`);
|
|
243
|
-
seen.add(value);
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
function requireEvidenceSupport(references, evidenceById, subjectId, label) {
|
|
247
|
-
for (const reference of references) {
|
|
248
|
-
const evidence = evidenceById.get(reference);
|
|
249
|
-
if (!evidence) {
|
|
250
|
-
throw new Error(`${label} references unknown evidence ID "${reference}"`);
|
|
251
|
-
}
|
|
252
|
-
if (!evidence.subjectIds.includes(subjectId)) {
|
|
253
|
-
throw new Error(`${label} evidence "${reference}" must include supported subject ID "${subjectId}"`);
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
function validateLinkedInput(input) {
|
|
258
|
-
const evidenceIds = input.evidence.map((record) => record.id);
|
|
259
|
-
assertUnique(evidenceIds, 'evidence');
|
|
260
|
-
const evidenceById = new Map(input.evidence.map((record) => [record.id, record]));
|
|
261
|
-
if (input.budget.primaryDiscovery.repositoryInspectionCallsUsed >
|
|
262
|
-
PRIMARY_CALL_LIMIT) {
|
|
263
|
-
throw new Error('budget.primaryDiscovery.repositoryInspectionCallsUsed must not exceed the scaffold\'s selected 48-call wave');
|
|
264
|
-
}
|
|
265
|
-
if (input.budget.authorityGate.repositoryInspectionCallsUsed >
|
|
266
|
-
AUTHORITY_CALL_LIMIT) {
|
|
267
|
-
throw new Error('budget.authorityGate.repositoryInspectionCallsUsed must not exceed the 6-call authority ceiling');
|
|
268
|
-
}
|
|
269
|
-
const primaryOverrun = input.budget.primaryDiscovery.elapsedSeconds > PRIMARY_ELAPSED_LIMIT_SECONDS;
|
|
270
|
-
const authorityOverrun = input.budget.authorityGate.elapsedSeconds > AUTHORITY_ELAPSED_LIMIT_SECONDS;
|
|
271
|
-
const configuredOverrunIds = input.budget.overrunBlockerIds;
|
|
272
|
-
if (primaryOverrun !== (configuredOverrunIds?.primaryDiscovery !== undefined)) {
|
|
273
|
-
throw new Error(primaryOverrun
|
|
274
|
-
? 'budget.overrunBlockerIds.primaryDiscovery is required for the exceeded budget.primary_discovery path'
|
|
275
|
-
: 'budget.overrunBlockerIds.primaryDiscovery is unsupported when budget.primary_discovery is within limits');
|
|
276
|
-
}
|
|
277
|
-
if (authorityOverrun !== (configuredOverrunIds?.authorityGate !== undefined)) {
|
|
278
|
-
throw new Error(authorityOverrun
|
|
279
|
-
? 'budget.overrunBlockerIds.authorityGate is required for the exceeded budget.primary_discovery.authority_gate path'
|
|
280
|
-
: 'budget.overrunBlockerIds.authorityGate is unsupported when the authority gate is within limits');
|
|
281
|
-
}
|
|
282
|
-
if (input.budget.authorityGate.repositoryInspectionCallsUsed >
|
|
283
|
-
input.budget.primaryDiscovery.repositoryInspectionCallsUsed ||
|
|
284
|
-
input.budget.authorityGate.elapsedSeconds > input.budget.primaryDiscovery.elapsedSeconds) {
|
|
285
|
-
throw new Error('authority-gate usage must not exceed total primary-discovery usage');
|
|
286
|
-
}
|
|
287
|
-
const universeIds = [
|
|
288
|
-
input.application.id,
|
|
289
|
-
input.repositoryTopology.id,
|
|
290
|
-
input.missingAuthority.id,
|
|
291
|
-
input.accessRequest.id,
|
|
292
|
-
input.authorityBlocker.id,
|
|
293
|
-
...(configuredOverrunIds?.primaryDiscovery
|
|
294
|
-
? [configuredOverrunIds.primaryDiscovery]
|
|
295
|
-
: []),
|
|
296
|
-
...(configuredOverrunIds?.authorityGate ? [configuredOverrunIds.authorityGate] : []),
|
|
297
|
-
...evidenceIds,
|
|
298
|
-
];
|
|
299
|
-
assertUnique(universeIds, 'scaffold input');
|
|
300
|
-
const evidenceSubjectIds = new Set([
|
|
301
|
-
input.application.id,
|
|
302
|
-
input.repositoryTopology.id,
|
|
303
|
-
input.missingAuthority.id,
|
|
304
|
-
]);
|
|
305
|
-
input.evidence.forEach((record) => {
|
|
306
|
-
assertUnique(record.subjectIds, `evidence record "${record.id}" subjectIds`);
|
|
307
|
-
record.subjectIds.forEach((subjectId) => {
|
|
308
|
-
if (!evidenceSubjectIds.has(subjectId)) {
|
|
309
|
-
throw new Error(`evidence record "${record.id}" references unknown universe subject ID "${subjectId}"`);
|
|
310
|
-
}
|
|
311
|
-
});
|
|
312
|
-
});
|
|
313
|
-
requireEvidenceSupport(input.repositoryTopology.evidenceIds, evidenceById, input.repositoryTopology.id, 'repositoryTopology.evidenceIds');
|
|
314
|
-
const presenceCensus = input.repositoryTopology.topologyPresenceCensus;
|
|
315
|
-
if (presenceCensus.subjectId !== input.repositoryTopology.id) {
|
|
316
|
-
throw new Error('repositoryTopology.topologyPresenceCensus.subjectId must equal repositoryTopology.id');
|
|
317
|
-
}
|
|
318
|
-
requireEvidenceSupport(presenceCensus.evidenceIds, evidenceById, presenceCensus.subjectId, 'repositoryTopology.topologyPresenceCensus.evidenceIds');
|
|
319
|
-
if (!presenceCensus.evidenceIds.some((evidenceId) => {
|
|
320
|
-
const record = evidenceById.get(evidenceId);
|
|
321
|
-
return (record?.claimKind === 'topology-presence-census' &&
|
|
322
|
-
record.subjectIds.length === 1 &&
|
|
323
|
-
record.subjectIds[0] === presenceCensus.subjectId);
|
|
324
|
-
})) {
|
|
325
|
-
throw new Error('repositoryTopology.topologyPresenceCensus.evidenceIds must include an exact topology-presence-census claim for repositoryTopology.id');
|
|
326
|
-
}
|
|
327
|
-
requireEvidenceSupport(input.missingAuthority.evidenceIds, evidenceById, input.missingAuthority.id, 'missingAuthority.evidenceIds');
|
|
328
|
-
requireEvidenceSupport(input.authorityBlocker.evidenceIds, evidenceById, input.missingAuthority.id, 'authorityBlocker.evidenceIds');
|
|
329
|
-
assertUnique(input.repositoryTopology.evidenceIds, 'repositoryTopology.evidenceIds');
|
|
330
|
-
assertUnique(presenceCensus.evidenceIds, 'repositoryTopology.topologyPresenceCensus.evidenceIds');
|
|
331
|
-
assertUnique(input.missingAuthority.evidenceIds, 'missingAuthority.evidenceIds');
|
|
332
|
-
assertUnique(input.authorityBlocker.evidenceIds, 'authorityBlocker.evidenceIds');
|
|
333
|
-
assertUnique(input.coverage.scannedRoots, 'coverage.scannedRoots');
|
|
334
|
-
assertUnique(input.coverage.sourceKinds, 'coverage.sourceKinds');
|
|
335
|
-
}
|
|
336
|
-
function yamlText(value) {
|
|
337
|
-
return stringifyYaml(value, { lineWidth: 0, aliasDuplicateObjects: false });
|
|
338
|
-
}
|
|
339
|
-
function renderArtifacts(input) {
|
|
340
|
-
const budgetBlockers = [];
|
|
341
|
-
const overrunIds = input.budget.overrunBlockerIds;
|
|
342
|
-
if (overrunIds?.primaryDiscovery) {
|
|
343
|
-
budgetBlockers.push({
|
|
344
|
-
id: overrunIds.primaryDiscovery,
|
|
345
|
-
kind: 'budget-exhausted',
|
|
346
|
-
budget_path: 'budget.primary_discovery',
|
|
347
|
-
});
|
|
348
|
-
}
|
|
349
|
-
if (overrunIds?.authorityGate) {
|
|
350
|
-
budgetBlockers.push({
|
|
351
|
-
id: overrunIds.authorityGate,
|
|
352
|
-
kind: 'budget-exhausted',
|
|
353
|
-
budget_path: 'budget.primary_discovery.authority_gate',
|
|
354
|
-
});
|
|
355
|
-
}
|
|
356
|
-
const universe = {
|
|
357
|
-
version: 1,
|
|
358
|
-
map_revision: {
|
|
359
|
-
source_commit: input.sourceCommit,
|
|
360
|
-
previous_source_commit: null,
|
|
361
|
-
mode: 'full-scan',
|
|
362
|
-
changed_scope_ids: [],
|
|
363
|
-
},
|
|
364
|
-
application: {
|
|
365
|
-
id: input.application.id,
|
|
366
|
-
requested_scope: input.application.requestedScope,
|
|
367
|
-
selected_production_scope: null,
|
|
368
|
-
scope_status: 'provisional',
|
|
369
|
-
evaluation_mode: input.application.evaluationMode,
|
|
370
|
-
},
|
|
371
|
-
production_basis: {
|
|
372
|
-
repository_proven_topology: {
|
|
373
|
-
environment_scope: input.repositoryTopology.environmentScope,
|
|
374
|
-
fidelity_ceiling: input.repositoryTopology.fidelityCeiling,
|
|
375
|
-
evidence_ids: input.repositoryTopology.evidenceIds,
|
|
376
|
-
topology_presence_census: {
|
|
377
|
-
status: input.repositoryTopology.topologyPresenceCensus.status,
|
|
378
|
-
subject_id: input.repositoryTopology.topologyPresenceCensus.subjectId,
|
|
379
|
-
inspected_surface_kinds: [
|
|
380
|
-
...input.repositoryTopology.topologyPresenceCensus
|
|
381
|
-
.inspectedSurfaceKinds,
|
|
382
|
-
],
|
|
383
|
-
production_capable_surface_count: input.repositoryTopology.topologyPresenceCensus
|
|
384
|
-
.productionCapableSurfaceCount,
|
|
385
|
-
evidence_ids: input.repositoryTopology.topologyPresenceCensus.evidenceIds,
|
|
386
|
-
},
|
|
387
|
-
},
|
|
388
|
-
authoritative_sources: [],
|
|
389
|
-
missing_authoritative_sources: [
|
|
390
|
-
{
|
|
391
|
-
id: input.missingAuthority.id,
|
|
392
|
-
kind: input.missingAuthority.kind,
|
|
393
|
-
required_for: input.missingAuthority.requiredFor,
|
|
394
|
-
status: input.missingAuthority.status,
|
|
395
|
-
evidence_ids: input.missingAuthority.evidenceIds,
|
|
396
|
-
},
|
|
397
|
-
],
|
|
398
|
-
access_request: {
|
|
399
|
-
id: input.accessRequest.id,
|
|
400
|
-
access: 'read-only',
|
|
401
|
-
source: input.accessRequest.source,
|
|
402
|
-
fields: [...PROVISIONAL_ACCESS_REQUEST_FIELDS],
|
|
403
|
-
reason: input.accessRequest.reason,
|
|
404
|
-
},
|
|
405
|
-
alternate_topologies: [
|
|
406
|
-
{
|
|
407
|
-
id: input.repositoryTopology.id,
|
|
408
|
-
environment_scope: input.repositoryTopology.environmentScope,
|
|
409
|
-
selected: false,
|
|
410
|
-
evidence_ids: input.repositoryTopology.evidenceIds,
|
|
411
|
-
},
|
|
412
|
-
],
|
|
413
|
-
},
|
|
414
|
-
deployment_profiles: [],
|
|
415
|
-
artifacts: [],
|
|
416
|
-
services: [],
|
|
417
|
-
interfaces: [],
|
|
418
|
-
physical_bindings: [],
|
|
419
|
-
resources: [],
|
|
420
|
-
external_dependencies: [],
|
|
421
|
-
consistency_groups: [],
|
|
422
|
-
unresolved: [],
|
|
423
|
-
};
|
|
424
|
-
const evidence = {
|
|
425
|
-
version: 1,
|
|
426
|
-
coverage: {
|
|
427
|
-
scanned_roots: input.coverage.scannedRoots,
|
|
428
|
-
excluded_paths: [],
|
|
429
|
-
source_kinds: input.coverage.sourceKinds,
|
|
430
|
-
external_integration_scope: {
|
|
431
|
-
method: 'not-entered-before-production-authority',
|
|
432
|
-
status: 'partial',
|
|
433
|
-
uncovered: ['active-hosted-integrations-and-secret-reference-names'],
|
|
434
|
-
},
|
|
435
|
-
inventory_closure: {
|
|
436
|
-
runtime_roles: {
|
|
437
|
-
status: 'partial',
|
|
438
|
-
candidates: [],
|
|
439
|
-
structured_registry_discovery: {
|
|
440
|
-
status: 'partial',
|
|
441
|
-
evidence_ids: input.repositoryTopology.evidenceIds,
|
|
442
|
-
registries: [],
|
|
443
|
-
uncovered: ['hosted-production-runtime-registries'],
|
|
444
|
-
},
|
|
445
|
-
uncovered: ['hosted-production-launch-surfaces'],
|
|
446
|
-
},
|
|
447
|
-
stateful_resources: {
|
|
448
|
-
status: 'partial',
|
|
449
|
-
candidates: [],
|
|
450
|
-
state_surface_discovery: {
|
|
451
|
-
status: 'partial',
|
|
452
|
-
evidence_ids: input.repositoryTopology.evidenceIds,
|
|
453
|
-
entries: [],
|
|
454
|
-
uncovered: ['hosted-production-state-surfaces'],
|
|
455
|
-
},
|
|
456
|
-
logical_state_census: {
|
|
457
|
-
status: 'partial',
|
|
458
|
-
evidence_ids: input.repositoryTopology.evidenceIds,
|
|
459
|
-
entries: [],
|
|
460
|
-
uncovered: ['hosted-production-logical-state-identities'],
|
|
461
|
-
},
|
|
462
|
-
structured_registry_discovery: {
|
|
463
|
-
status: 'partial',
|
|
464
|
-
evidence_ids: input.repositoryTopology.evidenceIds,
|
|
465
|
-
registries: [],
|
|
466
|
-
uncovered: ['hosted-production-state-registries'],
|
|
467
|
-
},
|
|
468
|
-
uncovered: ['hosted-production-state'],
|
|
469
|
-
},
|
|
470
|
-
external_dependencies: {
|
|
471
|
-
status: 'partial',
|
|
472
|
-
candidates: [],
|
|
473
|
-
structured_registry_discovery: {
|
|
474
|
-
status: 'partial',
|
|
475
|
-
evidence_ids: input.repositoryTopology.evidenceIds,
|
|
476
|
-
registries: [],
|
|
477
|
-
uncovered: ['hosted-production-integration-registries'],
|
|
478
|
-
},
|
|
479
|
-
uncovered: ['hosted-production-egress'],
|
|
480
|
-
},
|
|
481
|
-
},
|
|
482
|
-
operational_surface_census: {
|
|
483
|
-
status: 'partial',
|
|
484
|
-
evidence_ids: input.repositoryTopology.evidenceIds,
|
|
485
|
-
frontier_receipts: [
|
|
486
|
-
{
|
|
487
|
-
frontier: 'deployment-runtime-units',
|
|
488
|
-
status: 'partial',
|
|
489
|
-
surface_ids: [],
|
|
490
|
-
evidence_ids: input.repositoryTopology.evidenceIds,
|
|
491
|
-
uncovered: ['hosted-production-runtime-units'],
|
|
492
|
-
},
|
|
493
|
-
{
|
|
494
|
-
frontier: 'state-materialization-identities',
|
|
495
|
-
status: 'partial',
|
|
496
|
-
surface_ids: [],
|
|
497
|
-
evidence_ids: input.repositoryTopology.evidenceIds,
|
|
498
|
-
uncovered: ['hosted-production-state-identities'],
|
|
499
|
-
},
|
|
500
|
-
{
|
|
501
|
-
frontier: 'outbound-host-operation-contracts',
|
|
502
|
-
status: 'partial',
|
|
503
|
-
surface_ids: [],
|
|
504
|
-
evidence_ids: input.repositoryTopology.evidenceIds,
|
|
505
|
-
uncovered: ['hosted-production-outbound-host-operations'],
|
|
506
|
-
},
|
|
507
|
-
{
|
|
508
|
-
frontier: 'required-inbound-pull-contracts',
|
|
509
|
-
status: 'partial',
|
|
510
|
-
surface_ids: [],
|
|
511
|
-
evidence_ids: input.repositoryTopology.evidenceIds,
|
|
512
|
-
uncovered: ['hosted-production-inbound-pull-operations'],
|
|
513
|
-
},
|
|
514
|
-
],
|
|
515
|
-
surfaces: [],
|
|
516
|
-
uncovered: ['hosted-production-operational-surfaces'],
|
|
517
|
-
},
|
|
518
|
-
selector_surface_census: {
|
|
519
|
-
status: 'partial',
|
|
520
|
-
evidence_ids: input.repositoryTopology.evidenceIds,
|
|
521
|
-
discovery_roots: [],
|
|
522
|
-
surfaces: [],
|
|
523
|
-
uncovered: ['hosted-production-selector-surfaces'],
|
|
524
|
-
},
|
|
525
|
-
},
|
|
526
|
-
evidence: input.evidence.map((record) => ({
|
|
527
|
-
id: record.id,
|
|
528
|
-
subject_ids: record.subjectIds,
|
|
529
|
-
assertion: record.assertion,
|
|
530
|
-
...(record.claimKind === undefined
|
|
531
|
-
? {}
|
|
532
|
-
: { claim_kind: record.claimKind }),
|
|
533
|
-
environment_scope: record.environmentScope,
|
|
534
|
-
authority: record.authority,
|
|
535
|
-
sources: record.sources.map((source) => ({
|
|
536
|
-
path: source.path,
|
|
537
|
-
...(source.lines === undefined ? {} : { lines: source.lines }),
|
|
538
|
-
anchor: source.anchor,
|
|
539
|
-
kind: source.kind,
|
|
540
|
-
locator_kind: source.locatorKind,
|
|
541
|
-
})),
|
|
542
|
-
confidence: record.confidence,
|
|
543
|
-
})),
|
|
544
|
-
};
|
|
545
|
-
const review = {
|
|
546
|
-
version: 1,
|
|
547
|
-
inventory_closure_audit: {
|
|
548
|
-
status: 'deferred',
|
|
549
|
-
reason: 'inventory-closure-partial',
|
|
550
|
-
},
|
|
551
|
-
reviewer: {
|
|
552
|
-
status: 'deferred',
|
|
553
|
-
reason: 'production-authority-required',
|
|
554
|
-
},
|
|
555
|
-
budget: {
|
|
556
|
-
primary_discovery: {
|
|
557
|
-
call_limit: PRIMARY_CALL_LIMIT,
|
|
558
|
-
elapsed_limit_seconds: PRIMARY_ELAPSED_LIMIT_SECONDS,
|
|
559
|
-
repository_inspection_calls_used: input.budget.primaryDiscovery.repositoryInspectionCallsUsed,
|
|
560
|
-
elapsed_seconds: input.budget.primaryDiscovery.elapsedSeconds,
|
|
561
|
-
authority_gate: {
|
|
562
|
-
call_limit: AUTHORITY_CALL_LIMIT,
|
|
563
|
-
elapsed_limit_seconds: AUTHORITY_ELAPSED_LIMIT_SECONDS,
|
|
564
|
-
repository_inspection_calls_used: input.budget.authorityGate.repositoryInspectionCallsUsed,
|
|
565
|
-
elapsed_seconds: input.budget.authorityGate.elapsedSeconds,
|
|
566
|
-
},
|
|
567
|
-
},
|
|
568
|
-
},
|
|
569
|
-
termination: {
|
|
570
|
-
reason: 'missing-production-authority',
|
|
571
|
-
phase: 'authority-gate',
|
|
572
|
-
},
|
|
573
|
-
validation: {
|
|
574
|
-
command: 'haystack skills validate-universe --json',
|
|
575
|
-
validator_contract_revision: VALIDATOR_CONTRACT_REVISION,
|
|
576
|
-
status: 'passed',
|
|
577
|
-
},
|
|
578
|
-
status: 'blocked',
|
|
579
|
-
blocking_items: [
|
|
580
|
-
{
|
|
581
|
-
id: input.authorityBlocker.id,
|
|
582
|
-
kind: input.authorityBlocker.kind,
|
|
583
|
-
evidence_ids: input.authorityBlocker.evidenceIds,
|
|
584
|
-
},
|
|
585
|
-
...budgetBlockers,
|
|
586
|
-
],
|
|
587
|
-
};
|
|
588
|
-
return {
|
|
589
|
-
'universe.yml': yamlText(universe),
|
|
590
|
-
'evidence.yml': yamlText(evidence),
|
|
591
|
-
'review.yml': yamlText(review),
|
|
592
|
-
};
|
|
593
|
-
}
|
|
594
|
-
function assertSafeOutputDirectory(outputDirectory) {
|
|
595
|
-
if (!isAbsolute(outputDirectory) || normalize(outputDirectory) !== outputDirectory) {
|
|
596
|
-
throw new Error('output directory must be an absolute normalized path');
|
|
597
|
-
}
|
|
598
|
-
const resolved = resolve(outputDirectory);
|
|
599
|
-
const parent = dirname(resolved);
|
|
600
|
-
const repositoryRoot = dirname(parent);
|
|
601
|
-
if (basename(resolved) !== 'cloud-verifier' ||
|
|
602
|
-
basename(parent) !== '.haystack' ||
|
|
603
|
-
repositoryRoot === parsePath(resolved).root) {
|
|
604
|
-
throw new Error('output directory must be an absolute <repository>/.haystack/cloud-verifier path');
|
|
605
|
-
}
|
|
606
|
-
const root = parsePath(resolved).root;
|
|
607
|
-
let current = root;
|
|
608
|
-
for (const component of relative(root, resolved).split(sep)) {
|
|
609
|
-
current = join(current, component);
|
|
610
|
-
const metadata = lstatSync(current, { throwIfNoEntry: false });
|
|
611
|
-
if (metadata === undefined)
|
|
612
|
-
continue;
|
|
613
|
-
if (metadata.isSymbolicLink()) {
|
|
614
|
-
throw new Error(`output directory must not traverse symlink "${current}"`);
|
|
615
|
-
}
|
|
616
|
-
if (!metadata.isDirectory()) {
|
|
617
|
-
throw new Error(`output directory ancestor is not a directory: "${current}"`);
|
|
618
|
-
}
|
|
619
|
-
}
|
|
620
|
-
if (lstatSync(resolved, { throwIfNoEntry: false }) !== undefined) {
|
|
621
|
-
throw new Error('output directory already exists; refusing to overwrite an artifact set');
|
|
622
|
-
}
|
|
623
|
-
return resolved;
|
|
624
|
-
}
|
|
625
|
-
function digest(text) {
|
|
626
|
-
return `sha256:${createHash('sha256').update(text, 'utf8').digest('hex')}`;
|
|
627
|
-
}
|
|
628
|
-
/**
|
|
629
|
-
* Validates a pure, data-only provisional receipt input, validates the rendered
|
|
630
|
-
* artifacts against the universe contract, then publishes the three-file set by
|
|
631
|
-
* one directory rename. Cooperative scaffold calls serialize through a lock,
|
|
632
|
-
* and every destination observed before publication fails closed.
|
|
633
|
-
*/
|
|
634
|
-
export function scaffoldProvisionalUniverse(outputDirectory, rawInput) {
|
|
635
|
-
const input = provisionalUniverseScaffoldInputSchema.parse(rawInput);
|
|
636
|
-
validateLinkedInput(input);
|
|
637
|
-
const texts = renderArtifacts(input);
|
|
638
|
-
const validation = validateUniverseArtifactTexts(texts);
|
|
639
|
-
if (validation.status !== 'valid') {
|
|
640
|
-
throw new Error(`generated provisional artifacts failed validation: ${validation.issues
|
|
641
|
-
.map((issue) => `${issue.file}:${issue.path}: ${issue.message}`)
|
|
642
|
-
.join('; ')}`);
|
|
643
|
-
}
|
|
644
|
-
const safeOutputDirectory = assertSafeOutputDirectory(outputDirectory);
|
|
645
|
-
const parent = dirname(safeOutputDirectory);
|
|
646
|
-
mkdirSync(parent, { recursive: true, mode: 0o700 });
|
|
647
|
-
assertSafeOutputDirectory(safeOutputDirectory);
|
|
648
|
-
const lockDirectory = join(parent, '.cloud-verifier-scaffold-lock');
|
|
649
|
-
mkdirSync(lockDirectory, { mode: 0o700 });
|
|
650
|
-
let stagingDirectory = null;
|
|
651
|
-
try {
|
|
652
|
-
if (lstatSync(safeOutputDirectory, { throwIfNoEntry: false }) !== undefined) {
|
|
653
|
-
throw new Error('output directory appeared during scaffold; refusing to overwrite it');
|
|
654
|
-
}
|
|
655
|
-
stagingDirectory = mkdtempSync(join(parent, '.cloud-verifier-staging-'));
|
|
656
|
-
for (const name of ARTIFACT_NAMES) {
|
|
657
|
-
writeFileSync(join(stagingDirectory, name), texts[name], {
|
|
658
|
-
encoding: 'utf8',
|
|
659
|
-
flag: 'wx',
|
|
660
|
-
mode: 0o600,
|
|
661
|
-
});
|
|
662
|
-
}
|
|
663
|
-
if (lstatSync(safeOutputDirectory, { throwIfNoEntry: false }) !== undefined) {
|
|
664
|
-
throw new Error('output directory appeared during scaffold; refusing to overwrite it');
|
|
665
|
-
}
|
|
666
|
-
renameSync(stagingDirectory, safeOutputDirectory);
|
|
667
|
-
stagingDirectory = null;
|
|
668
|
-
}
|
|
669
|
-
finally {
|
|
670
|
-
if (stagingDirectory !== null) {
|
|
671
|
-
rmSync(stagingDirectory, { recursive: true, force: true });
|
|
672
|
-
}
|
|
673
|
-
rmSync(lockDirectory, { recursive: true, force: true });
|
|
674
|
-
}
|
|
675
|
-
return {
|
|
676
|
-
outputDirectory: safeOutputDirectory,
|
|
677
|
-
texts,
|
|
678
|
-
artifacts: {
|
|
679
|
-
'universe.yml': {
|
|
680
|
-
bytes: Buffer.byteLength(texts['universe.yml'], 'utf8'),
|
|
681
|
-
digest: digest(texts['universe.yml']),
|
|
682
|
-
},
|
|
683
|
-
'evidence.yml': {
|
|
684
|
-
bytes: Buffer.byteLength(texts['evidence.yml'], 'utf8'),
|
|
685
|
-
digest: digest(texts['evidence.yml']),
|
|
686
|
-
},
|
|
687
|
-
'review.yml': {
|
|
688
|
-
bytes: Buffer.byteLength(texts['review.yml'], 'utf8'),
|
|
689
|
-
digest: digest(texts['review.yml']),
|
|
690
|
-
},
|
|
691
|
-
},
|
|
692
|
-
validationSummary: {
|
|
693
|
-
universeIds: validation.summary.universe_ids,
|
|
694
|
-
evidenceIds: validation.summary.evidence_ids,
|
|
695
|
-
reviewIds: validation.summary.review_ids,
|
|
696
|
-
},
|
|
697
|
-
};
|
|
698
|
-
}
|
|
699
|
-
function readStrictJsonInput(inputPath) {
|
|
700
|
-
const metadata = lstatSync(inputPath, { throwIfNoEntry: false });
|
|
701
|
-
if (metadata === undefined) {
|
|
702
|
-
throw new Error(`provisional universe input file does not exist: "${inputPath}"`);
|
|
703
|
-
}
|
|
704
|
-
if (metadata.isSymbolicLink() || !metadata.isFile()) {
|
|
705
|
-
throw new Error('provisional universe input must be a non-symlink regular file');
|
|
706
|
-
}
|
|
707
|
-
if (metadata.size > PROVISIONAL_UNIVERSE_INPUT_MAX_BYTES) {
|
|
708
|
-
throw new Error(`provisional universe input exceeds ${PROVISIONAL_UNIVERSE_INPUT_MAX_BYTES} bytes`);
|
|
709
|
-
}
|
|
710
|
-
let descriptor = null;
|
|
711
|
-
let bytes;
|
|
712
|
-
try {
|
|
713
|
-
descriptor = openSync(inputPath, fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW);
|
|
714
|
-
const openedMetadata = fstatSync(descriptor);
|
|
715
|
-
if (!openedMetadata.isFile()) {
|
|
716
|
-
throw new Error('provisional universe input must be a non-symlink regular file');
|
|
717
|
-
}
|
|
718
|
-
if (openedMetadata.size > PROVISIONAL_UNIVERSE_INPUT_MAX_BYTES) {
|
|
719
|
-
throw new Error(`provisional universe input exceeds ${PROVISIONAL_UNIVERSE_INPUT_MAX_BYTES} bytes`);
|
|
720
|
-
}
|
|
721
|
-
bytes = readFileSync(descriptor);
|
|
722
|
-
}
|
|
723
|
-
finally {
|
|
724
|
-
if (descriptor !== null)
|
|
725
|
-
closeSync(descriptor);
|
|
726
|
-
}
|
|
727
|
-
if (bytes.byteLength > PROVISIONAL_UNIVERSE_INPUT_MAX_BYTES) {
|
|
728
|
-
throw new Error(`provisional universe input exceeds ${PROVISIONAL_UNIVERSE_INPUT_MAX_BYTES} bytes`);
|
|
729
|
-
}
|
|
730
|
-
let text;
|
|
731
|
-
try {
|
|
732
|
-
text = new TextDecoder('utf-8', { fatal: true }).decode(bytes);
|
|
733
|
-
}
|
|
734
|
-
catch {
|
|
735
|
-
throw new Error('provisional universe input must be valid UTF-8 JSON');
|
|
736
|
-
}
|
|
737
|
-
try {
|
|
738
|
-
return JSON.parse(text);
|
|
739
|
-
}
|
|
740
|
-
catch (error) {
|
|
741
|
-
throw new Error(`provisional universe input is malformed JSON: ${error.message}`);
|
|
742
|
-
}
|
|
743
|
-
}
|
|
744
|
-
function currentCleanRepositoryCommit(root) {
|
|
745
|
-
let head;
|
|
746
|
-
try {
|
|
747
|
-
head = execFileSync('git', ['rev-parse', '--verify', 'HEAD^{commit}'], {
|
|
748
|
-
cwd: root,
|
|
749
|
-
encoding: 'utf8',
|
|
750
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
751
|
-
}).trim();
|
|
752
|
-
}
|
|
753
|
-
catch {
|
|
754
|
-
throw new Error('scaffold-provisional-universe requires a Git repository with a current commit');
|
|
755
|
-
}
|
|
756
|
-
const changes = execFileSync('git', [
|
|
757
|
-
'status',
|
|
758
|
-
'--porcelain=v1',
|
|
759
|
-
'--untracked-files=all',
|
|
760
|
-
'--',
|
|
761
|
-
'.',
|
|
762
|
-
':(exclude).haystack',
|
|
763
|
-
':(exclude).haystack/**',
|
|
764
|
-
], {
|
|
765
|
-
cwd: root,
|
|
766
|
-
encoding: 'utf8',
|
|
767
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
768
|
-
maxBuffer: 4 * 1024 * 1024,
|
|
769
|
-
});
|
|
770
|
-
if (changes.length > 0) {
|
|
771
|
-
throw new Error('scaffold-provisional-universe requires a clean Git worktree outside .haystack');
|
|
772
|
-
}
|
|
773
|
-
return head;
|
|
774
|
-
}
|
|
775
|
-
/** CLI adapter for later command registration. Always emits one compact JSON line. */
|
|
776
|
-
export function scaffoldProvisionalUniverseCommand(options) {
|
|
777
|
-
if (typeof options.input !== 'string' || options.input.length === 0) {
|
|
778
|
-
throw new Error('--input must name an absolute or current-directory-relative JSON file');
|
|
779
|
-
}
|
|
780
|
-
const root = findGitRoot();
|
|
781
|
-
if (!root) {
|
|
782
|
-
throw new Error('scaffold-provisional-universe must be run inside a Git repository');
|
|
783
|
-
}
|
|
784
|
-
const inputPath = isAbsolute(options.input)
|
|
785
|
-
? normalize(options.input)
|
|
786
|
-
: resolve(process.cwd(), options.input);
|
|
787
|
-
const rawInput = readStrictJsonInput(inputPath);
|
|
788
|
-
const input = provisionalUniverseScaffoldInputSchema.parse(rawInput);
|
|
789
|
-
const head = currentCleanRepositoryCommit(root);
|
|
790
|
-
if (input.sourceCommit !== head) {
|
|
791
|
-
throw new Error(`sourceCommit must equal the current Git HEAD "${head}"`);
|
|
792
|
-
}
|
|
793
|
-
const result = scaffoldProvisionalUniverse(join(root, '.haystack', 'cloud-verifier'), input);
|
|
794
|
-
const metadata = {
|
|
795
|
-
status: 'created',
|
|
796
|
-
output_directory: result.outputDirectory,
|
|
797
|
-
artifacts: result.artifacts,
|
|
798
|
-
validation: {
|
|
799
|
-
validator_contract_revision: VALIDATOR_CONTRACT_REVISION,
|
|
800
|
-
universe_ids: result.validationSummary.universeIds,
|
|
801
|
-
evidence_ids: result.validationSummary.evidenceIds,
|
|
802
|
-
review_ids: result.validationSummary.reviewIds,
|
|
803
|
-
},
|
|
804
|
-
};
|
|
805
|
-
console.log(JSON.stringify(metadata));
|
|
806
|
-
}
|