@haystackeditor/cli 0.15.25 → 0.15.27

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 (51) hide show
  1. package/README.md +39 -3
  2. package/dist/assets/hooks/package-lock.json +598 -0
  3. package/dist/assets/skills/map-cloud-verifier-universe/SKILL.md +2051 -0
  4. package/dist/assets/skills/map-cloud-verifier-universe/agents/openai.yaml +4 -0
  5. package/dist/assets/skills/map-cloud-verifier-universe/references/output-contract.md +3411 -0
  6. package/dist/assets/telemetry/browser-runtime.js +861 -0
  7. package/dist/assets/telemetry/runtime.cjs +1509 -0
  8. package/dist/commands/cloud-verifier-behaviors.d.ts +27 -0
  9. package/dist/commands/cloud-verifier-behaviors.js +218 -0
  10. package/dist/commands/cloud-verifier-data-store-census.d.ts +47 -0
  11. package/dist/commands/cloud-verifier-data-store-census.js +539 -0
  12. package/dist/commands/cloud-verifier-data-store-drift.d.ts +42 -0
  13. package/dist/commands/cloud-verifier-data-store-drift.js +158 -0
  14. package/dist/commands/cloud-verifier-identity-census.d.ts +88 -0
  15. package/dist/commands/cloud-verifier-identity-census.js +4057 -0
  16. package/dist/commands/cloud-verifier-materialization.d.ts +16 -0
  17. package/dist/commands/cloud-verifier-materialization.js +704 -0
  18. package/dist/commands/cloud-verifier-pascal-selector-census.d.ts +29 -0
  19. package/dist/commands/cloud-verifier-pascal-selector-census.js +1382 -0
  20. package/dist/commands/cloud-verifier-python-manifest-selector-census.d.ts +27 -0
  21. package/dist/commands/cloud-verifier-python-manifest-selector-census.js +2015 -0
  22. package/dist/commands/cloud-verifier-specialized-operational-census.d.ts +51 -0
  23. package/dist/commands/cloud-verifier-specialized-operational-census.js +11432 -0
  24. package/dist/commands/cloud-verifier-universe.d.ts +31 -0
  25. package/dist/commands/cloud-verifier-universe.js +10178 -0
  26. package/dist/commands/design-verify.d.ts +23 -0
  27. package/dist/commands/design-verify.js +142 -0
  28. package/dist/commands/hooks.js +2 -2
  29. package/dist/commands/prepare-universe-review.d.ts +115 -0
  30. package/dist/commands/prepare-universe-review.js +1092 -0
  31. package/dist/commands/production-source-deny-policy.d.ts +15 -0
  32. package/dist/commands/production-source-deny-policy.js +100 -0
  33. package/dist/commands/scaffold-provisional-universe.d.ts +468 -0
  34. package/dist/commands/scaffold-provisional-universe.js +808 -0
  35. package/dist/commands/schema-cmd.js +32 -0
  36. package/dist/commands/skills.d.ts +2 -2
  37. package/dist/commands/skills.js +415 -47
  38. package/dist/commands/telemetry.d.ts +53 -0
  39. package/dist/commands/telemetry.js +832 -0
  40. package/dist/commands/verify-hosted-reproducibility.d.ts +6 -2
  41. package/dist/commands/verify-hosted-reproducibility.js +65 -20
  42. package/dist/index.js +142 -13
  43. package/dist/triage/runner.js +7 -6
  44. package/dist/utils/design-verifier-api.d.ts +129 -0
  45. package/dist/utils/design-verifier-api.js +175 -0
  46. package/dist/utils/design-verifier-result.d.ts +52 -0
  47. package/dist/utils/design-verifier-result.js +299 -0
  48. package/dist/utils/haystack-api.d.ts +2 -0
  49. package/dist/utils/haystack-api.js +10 -3
  50. package/dist/utils/hooks.js +2 -1
  51. package/package.json +7 -2
@@ -0,0 +1,27 @@
1
+ export declare const MAX_BEHAVIOR_ARTIFACT_BYTES: number;
2
+ type RecordValue = Record<string, any>;
3
+ export interface BehaviorValidationIssue {
4
+ path: string;
5
+ message: string;
6
+ }
7
+ export interface BehaviorValidationResult {
8
+ version: 'haystack-behavior-validation-v1';
9
+ status: 'valid' | 'invalid';
10
+ issues: BehaviorValidationIssue[];
11
+ summary: {
12
+ actions: number;
13
+ boundaries: number;
14
+ };
15
+ }
16
+ export declare function derivedBehaviorActionId(repository: string, trigger: RecordValue): string;
17
+ export declare function derivedBehaviorSymbolId(repository: string, sourcePath: string, qualifiedName: string): string;
18
+ export declare function derivedBehaviorBoundaryId(repository: string, kind: string, identityKey: string): string;
19
+ export declare function validateBehaviorArtifact(value: RecordValue, references: {
20
+ universe: RecordValue;
21
+ evidence: RecordValue;
22
+ }): BehaviorValidationResult;
23
+ export declare function resolveBehaviorArtifactRoot(gitRoot: string | null, currentDirectory?: string): string;
24
+ export declare function cloudVerifierBehaviorsValidateCommand(options: {
25
+ json?: boolean;
26
+ }): Promise<void>;
27
+ export {};
@@ -0,0 +1,218 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { lstatSync, readFileSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import chalk from 'chalk';
5
+ import { parseDocument } from 'yaml';
6
+ import { findGitRoot } from '../utils/hooks.js';
7
+ // Keep behavior validation on the same bounded artifact envelope as universe
8
+ // validation. Large maps must not need a lossy reference projection merely to
9
+ // validate their exact interface and evidence joins.
10
+ export const MAX_BEHAVIOR_ARTIFACT_BYTES = 32 * 1024 * 1024;
11
+ const GIT_COMMIT = /^[0-9a-f]{40}$/;
12
+ const STABLE_UNIVERSE_ID = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
13
+ const REPOSITORY = /^[A-Za-z0-9._-]+\/[A-Za-z0-9._-]+$/;
14
+ const RUNTIME_ID = /^(?:action|symbol|boundary)-[0-9a-f]{24}$/;
15
+ const BOUNDARY_KINDS = new Set(['response', 'state', 'event', 'external', 'ui', 'cli', 'job']);
16
+ const TRIGGER_KINDS = new Set(['http', 'event', 'job', 'cli', 'ui']);
17
+ function stableIdentity(prefix, parts) {
18
+ const framed = parts.map(part => `${Buffer.byteLength(part, 'utf8')}:${part}`).join('|');
19
+ return `${prefix}-${createHash('sha256').update(framed).digest('hex').slice(0, 24)}`;
20
+ }
21
+ function normalizeRepository(repository) {
22
+ if (!REPOSITORY.test(repository))
23
+ throw new Error('must be a canonical owner/repository identity');
24
+ return repository.toLowerCase();
25
+ }
26
+ function normalizePath(sourcePath) {
27
+ const normalized = sourcePath.replace(/\\/g, '/').replace(/^\.\//, '').replace(/^\/+/, '');
28
+ if (!normalized || normalized.split('/').some(part => !part || part === '..')) {
29
+ throw new Error('must be a normalized repository-relative source path');
30
+ }
31
+ return normalized;
32
+ }
33
+ export function derivedBehaviorActionId(repository, trigger) {
34
+ let identity;
35
+ if (trigger?.kind === 'http') {
36
+ if (typeof trigger.method !== 'string' || typeof trigger.route_template !== 'string') {
37
+ throw new Error('HTTP trigger requires method and route_template');
38
+ }
39
+ identity = `${trigger.method.toUpperCase()} ${trigger.route_template}`;
40
+ }
41
+ else if (trigger?.kind === 'event') {
42
+ if (typeof trigger.channel_identity !== 'string' || typeof trigger.event_type !== 'string') {
43
+ throw new Error('event trigger requires channel_identity and event_type');
44
+ }
45
+ identity = `${trigger.channel_identity}\0${trigger.event_type}`;
46
+ }
47
+ else if (trigger?.kind === 'job')
48
+ identity = trigger.job_identity;
49
+ else if (trigger?.kind === 'cli')
50
+ identity = trigger.command_identity;
51
+ else if (trigger?.kind === 'ui')
52
+ identity = trigger.action_identity;
53
+ else
54
+ throw new Error('trigger kind must be http, event, job, cli, or ui');
55
+ if (typeof identity !== 'string' || !identity.trim())
56
+ throw new Error('trigger identity must be non-empty');
57
+ return stableIdentity('action', [normalizeRepository(repository), trigger.kind, identity]);
58
+ }
59
+ export function derivedBehaviorSymbolId(repository, sourcePath, qualifiedName) {
60
+ if (typeof qualifiedName !== 'string' || !qualifiedName.trim())
61
+ throw new Error('qualified_name must be non-empty');
62
+ return stableIdentity('symbol', [normalizeRepository(repository), normalizePath(sourcePath), qualifiedName.trim()]);
63
+ }
64
+ export function derivedBehaviorBoundaryId(repository, kind, identityKey) {
65
+ if (!BOUNDARY_KINDS.has(kind))
66
+ throw new Error('boundary kind is unsupported');
67
+ if (typeof identityKey !== 'string' || !identityKey.trim())
68
+ throw new Error('identity_key must be non-empty');
69
+ return stableIdentity('boundary', [normalizeRepository(repository), kind, identityKey.trim()]);
70
+ }
71
+ function parseYaml(text, name) {
72
+ const document = parseDocument(text, { prettyErrors: false });
73
+ if (document.errors.length)
74
+ throw new Error(`${name} is malformed YAML: ${document.errors[0].message}`);
75
+ const value = document.toJS({ maxAliasCount: 50 });
76
+ if (!value || typeof value !== 'object' || Array.isArray(value))
77
+ throw new Error(`${name} must contain a mapping`);
78
+ return value;
79
+ }
80
+ export function validateBehaviorArtifact(value, references) {
81
+ const issues = [];
82
+ const fail = (path, message) => issues.push({ path, message });
83
+ if (value?.version !== 1)
84
+ fail('version', 'must equal 1');
85
+ if (value?.schema_version !== 'haystack-behavior-map-v1') {
86
+ fail('schema_version', 'must equal haystack-behavior-map-v1');
87
+ }
88
+ let repository = '';
89
+ try {
90
+ repository = normalizeRepository(value?.repository);
91
+ }
92
+ catch (error) {
93
+ fail('repository', error.message);
94
+ }
95
+ if (!GIT_COMMIT.test(value?.source_commit || ''))
96
+ fail('source_commit', 'must be an exact Git commit');
97
+ const universeCommit = references.universe?.map_revision?.source_commit;
98
+ if (value?.source_commit !== universeCommit)
99
+ fail('source_commit', 'must exactly match universe.yml map_revision.source_commit');
100
+ const interfaceIds = new Set((Array.isArray(references.universe?.interfaces)
101
+ ? references.universe.interfaces : []).map((item) => item?.id));
102
+ const evidenceIds = new Set((Array.isArray(references.evidence?.evidence)
103
+ ? references.evidence.evidence : []).map((item) => item?.id));
104
+ if (!Array.isArray(value?.actions) || value.actions.length === 0) {
105
+ fail('actions', 'must contain at least one meaningful customer action');
106
+ return { version: 'haystack-behavior-validation-v1', status: 'invalid', issues, summary: { actions: 0, boundaries: 0 } };
107
+ }
108
+ const actionIds = new Set();
109
+ const triggerIds = new Set();
110
+ let boundaryCount = 0;
111
+ for (const [index, action] of value.actions.entries()) {
112
+ const path = `actions[${index}]`;
113
+ if (!action || typeof action !== 'object' || Array.isArray(action)) {
114
+ fail(path, 'must be a mapping');
115
+ continue;
116
+ }
117
+ let expectedAction = '';
118
+ try {
119
+ expectedAction = derivedBehaviorActionId(repository || value.repository, action.trigger);
120
+ }
121
+ catch (error) {
122
+ fail(`${path}.trigger`, error.message);
123
+ }
124
+ if (!RUNTIME_ID.test(action.id || '') || action.id !== expectedAction)
125
+ fail(`${path}.id`, `must equal derived exact id ${expectedAction}`);
126
+ if (actionIds.has(action.id))
127
+ fail(`${path}.id`, 'must be unique');
128
+ actionIds.add(action.id);
129
+ if (!TRIGGER_KINDS.has(action.trigger?.kind))
130
+ fail(`${path}.trigger.kind`, 'is unsupported');
131
+ if (triggerIds.has(expectedAction))
132
+ fail(`${path}.trigger`, 'duplicates another structured trigger');
133
+ triggerIds.add(expectedAction);
134
+ if (!STABLE_UNIVERSE_ID.test(action.interface_id || '') || !interfaceIds.has(action.interface_id)) {
135
+ fail(`${path}.interface_id`, 'must reference an exact universe.yml interface id');
136
+ }
137
+ let expectedSymbol = '';
138
+ try {
139
+ expectedSymbol = derivedBehaviorSymbolId(repository || value.repository, action.handler?.source_path, action.handler?.qualified_name);
140
+ }
141
+ catch (error) {
142
+ fail(`${path}.handler`, error.message);
143
+ }
144
+ if (action.handler?.symbol_id !== expectedSymbol)
145
+ fail(`${path}.handler.symbol_id`, `must equal derived exact id ${expectedSymbol}`);
146
+ if (!Array.isArray(action.observable_boundaries) || action.observable_boundaries.length === 0) {
147
+ fail(`${path}.observable_boundaries`, 'must contain a customer-relevant boundary');
148
+ }
149
+ else {
150
+ const boundaryIds = new Set();
151
+ for (const [boundaryIndex, boundary] of action.observable_boundaries.entries()) {
152
+ boundaryCount += 1;
153
+ const boundaryPath = `${path}.observable_boundaries[${boundaryIndex}]`;
154
+ let expectedBoundary = '';
155
+ try {
156
+ expectedBoundary = derivedBehaviorBoundaryId(repository || value.repository, boundary?.kind, boundary?.identity_key);
157
+ }
158
+ catch (error) {
159
+ fail(boundaryPath, error.message);
160
+ }
161
+ if (boundary?.id !== expectedBoundary)
162
+ fail(`${boundaryPath}.id`, `must equal derived exact id ${expectedBoundary}`);
163
+ if (boundaryIds.has(boundary?.id))
164
+ fail(`${boundaryPath}.id`, 'must be unique in the action');
165
+ boundaryIds.add(boundary?.id);
166
+ }
167
+ }
168
+ if (!Array.isArray(action.evidence_ids) || action.evidence_ids.length === 0) {
169
+ fail(`${path}.evidence_ids`, 'must contain exact repository evidence');
170
+ }
171
+ else {
172
+ for (const evidenceId of action.evidence_ids) {
173
+ if (!evidenceIds.has(evidenceId))
174
+ fail(`${path}.evidence_ids`, `references unknown evidence id ${String(evidenceId)}`);
175
+ }
176
+ }
177
+ }
178
+ return {
179
+ version: 'haystack-behavior-validation-v1',
180
+ status: issues.length ? 'invalid' : 'valid',
181
+ issues,
182
+ summary: { actions: value.actions.length, boundaries: boundaryCount },
183
+ };
184
+ }
185
+ function readArtifact(root, name) {
186
+ const path = join(root, '.haystack', 'cloud-verifier', name);
187
+ const metadata = lstatSync(path, { throwIfNoEntry: false });
188
+ if (!metadata || !metadata.isFile() || metadata.isSymbolicLink())
189
+ throw new Error(`${name} must be a regular non-symlink file`);
190
+ if (metadata.size > MAX_BEHAVIOR_ARTIFACT_BYTES) {
191
+ throw new Error(`${name} exceeds ${MAX_BEHAVIOR_ARTIFACT_BYTES} bytes`);
192
+ }
193
+ return readFileSync(path, 'utf8');
194
+ }
195
+ export function resolveBehaviorArtifactRoot(gitRoot, currentDirectory = process.cwd()) {
196
+ return gitRoot ?? currentDirectory;
197
+ }
198
+ export async function cloudVerifierBehaviorsValidateCommand(options) {
199
+ // Source-only evaluation archives deliberately omit Git metadata. The
200
+ // behavior validator only reads verifier artifacts, so current-directory
201
+ // fallback is both sufficient and consistent with validate-universe.
202
+ const root = resolveBehaviorArtifactRoot(findGitRoot());
203
+ const value = parseYaml(readArtifact(root, 'behaviors.yml'), 'behaviors.yml');
204
+ const universe = parseYaml(readArtifact(root, 'universe.yml'), 'universe.yml');
205
+ const evidence = parseYaml(readArtifact(root, 'evidence.yml'), 'evidence.yml');
206
+ const result = validateBehaviorArtifact(value, { universe, evidence });
207
+ if (options.json)
208
+ console.log(JSON.stringify(result, null, 2));
209
+ else if (result.status === 'valid') {
210
+ console.log(chalk.green(`Behavior map valid: ${result.summary.actions} actions, ${result.summary.boundaries} boundaries`));
211
+ }
212
+ else {
213
+ for (const issue of result.issues)
214
+ console.error(chalk.red(`behaviors.yml:${issue.path}: ${issue.message}`));
215
+ }
216
+ if (result.status !== 'valid')
217
+ process.exitCode = 1;
218
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Data-store census.
3
+ *
4
+ * The one job the Cloud Verifier needs from a repository map: what data stores
5
+ * does this application use, so we can clone each one into a replica. Not
6
+ * processes, not API calls, not code structure — the databases, caches, object
7
+ * stores, search indexes, and queues that hold state a test must run against.
8
+ *
9
+ * Four source signals, strongest first:
10
+ * 1. a service image in a compose/k8s manifest (`image: postgres:16`) — a
11
+ * store instance the app is wired to run beside, the surest signal;
12
+ * 2. a connection URL scheme or conventional env var (`postgres://`,
13
+ * `REDIS_URL`) — an instance named in config;
14
+ * 3. a config declaration that names a store without an image, library, or URL
15
+ * (Django `ENGINE`, PHP PDO, Laravel `DB_CONNECTION`, Rails `adapter:`);
16
+ * 4. a client library in a dependency manifest (`pg`, `redis`, `mongoose`) —
17
+ * the app talks to that engine.
18
+ *
19
+ * Signals are folded per engine into one data store, with the evidence, a
20
+ * confidence, and how to reproduce it. Everything here is a general convention,
21
+ * never a repository-specific constant.
22
+ */
23
+ export type DataStoreCategory = 'relational-database' | 'document-database' | 'key-value-store' | 'wide-column-database' | 'columnar-database' | 'graph-database' | 'vector-database' | 'object-storage' | 'search-index' | 'message-queue' | 'time-series-database' | 'embedded-database';
24
+ export interface DataStoreSignal {
25
+ kind: 'service-image' | 'client-library' | 'connection-url' | 'config-declaration';
26
+ value: string;
27
+ path: string;
28
+ }
29
+ export type DataStoreConfidence = 'running' | 'configured' | 'library';
30
+ export interface DataStore {
31
+ engine: string;
32
+ category: DataStoreCategory;
33
+ clone_strategy: string;
34
+ confidence: DataStoreConfidence;
35
+ signals: DataStoreSignal[];
36
+ }
37
+ export interface DataStoreCensusInput {
38
+ path: string;
39
+ lowerName: string;
40
+ text: string;
41
+ }
42
+ /**
43
+ * Fold a stream of manifest/compose/config files into the distinct data stores
44
+ * the application uses. `inputs` is every scanned text file with its lowercased
45
+ * basename; the detector reads only the manifests and configs it recognizes.
46
+ */
47
+ export declare function detectDataStores(inputs: DataStoreCensusInput[]): DataStore[];