@truefoundry/tfy-infra-engine 0.0.0-canary.6233945

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,589 @@
1
+ /**
2
+ * Engine error codes for programmatic error handling.
3
+ */
4
+ declare enum EngineErrorCode {
5
+ TEMPLATE_NOT_FOUND = "TEMPLATE_NOT_FOUND",
6
+ NETWORK_ERROR = "NETWORK_ERROR",
7
+ GITHUB_NOT_FOUND = "GITHUB_NOT_FOUND",
8
+ GITHUB_RATE_LIMITED = "GITHUB_RATE_LIMITED",
9
+ FILE_NOT_FOUND = "FILE_NOT_FOUND",
10
+ HTTPS_AUTH_FAILED = "HTTPS_AUTH_FAILED",
11
+ SCHEMA_INVALID = "SCHEMA_INVALID",
12
+ INPUT_VALIDATION_FAILED = "INPUT_VALIDATION_FAILED",
13
+ TEMPLATE_JSON_NOT_FOUND = "TEMPLATE_JSON_NOT_FOUND",
14
+ TEMPLATE_JSON_INVALID = "TEMPLATE_JSON_INVALID",
15
+ TEMPLATE_SYNTAX_ERROR = "TEMPLATE_SYNTAX_ERROR",
16
+ TOFU_NOT_FOUND = "TOFU_NOT_FOUND",
17
+ TOFU_FMT_FAILED = "TOFU_FMT_FAILED",
18
+ CACHE_ERROR = "CACHE_ERROR",
19
+ ENVELOPE_VALIDATION_FAILED = "ENVELOPE_VALIDATION_FAILED",
20
+ MANIFEST_PARSE_ERROR = "MANIFEST_PARSE_ERROR"
21
+ }
22
+ /**
23
+ * Custom error class for engine errors.
24
+ * Provides structured error information including error code, cause, and details.
25
+ */
26
+ declare class EngineError extends Error {
27
+ readonly code: EngineErrorCode;
28
+ readonly details?: Record<string, unknown>;
29
+ constructor(message: string, code: EngineErrorCode, cause?: Error, details?: Record<string, unknown>);
30
+ /**
31
+ * Create a string representation including error code.
32
+ */
33
+ toString(): string;
34
+ /**
35
+ * Convert error to JSON-serializable object.
36
+ */
37
+ toJSON(): Record<string, unknown>;
38
+ }
39
+
40
+ /**
41
+ * JSON Schema type definitions.
42
+ *
43
+ * CHANGE (008): All Zod-based converter functions and zod-to-json-schema
44
+ * import removed. Only the JSONSchema7 type interface remains.
45
+ * Templates provide JSON Schema directly via template.json.
46
+ */
47
+ /**
48
+ * JSON Schema Draft-07 type.
49
+ */
50
+ interface JSONSchema7 {
51
+ $schema?: string;
52
+ $ref?: string;
53
+ type?: string;
54
+ properties?: Record<string, JSONSchema7>;
55
+ required?: string[];
56
+ default?: unknown;
57
+ description?: string;
58
+ items?: JSONSchema7;
59
+ additionalProperties?: boolean | JSONSchema7;
60
+ oneOf?: JSONSchema7[];
61
+ anyOf?: JSONSchema7[];
62
+ allOf?: JSONSchema7[];
63
+ enum?: unknown[];
64
+ const?: unknown;
65
+ pattern?: string;
66
+ minimum?: number;
67
+ maximum?: number;
68
+ minLength?: number;
69
+ maxLength?: number;
70
+ discriminator?: {
71
+ propertyName: string;
72
+ };
73
+ [key: string]: unknown;
74
+ }
75
+
76
+ /**
77
+ * Core type definitions for the HCL templating engine.
78
+ *
79
+ * These interfaces define the data model for templates, schemas, rendering,
80
+ * and the integrity engine (zone classification, file headers, manifests,
81
+ * drift reports, and the four-operation API).
82
+ *
83
+ * CHANGE (008): TemplateSchema, InputDefinition, OutputDefinition removed.
84
+ * Replaced by TemplateMetadata + JSONSchema7. sideOutputs removed from results.
85
+ *
86
+ * References:
87
+ * - specs/004-infra-integrity-engine/contracts/engine-api.ts
88
+ * - specs/004-infra-integrity-engine/data-model.md
89
+ * - specs/008-json-schema-engine/data-model.md
90
+ */
91
+
92
+ /**
93
+ * File ownership zone classification.
94
+ * (FR-001, FR-002)
95
+ */
96
+ type Zone = 'platform' | 'user';
97
+ /**
98
+ * Drift finding classification.
99
+ * (FR-015, FR-028, FR-042)
100
+ */
101
+ type DriftType = 'content_drift' | 'metadata_inconsistency' | 'source_mismatch' | 'missing_file' | 'unexpected_file';
102
+ /**
103
+ * Structured metadata embedded in every Platform Zone file as HCL comments.
104
+ * (FR-025)
105
+ *
106
+ * Serialized format (R-001):
107
+ * ```hcl
108
+ * # @tfy-status:begin
109
+ * # {"managed":true,"source":"...","version":"...","intent_id":"...","content_hash":"sha256:..."}
110
+ * # @tfy-status:end
111
+ * ```
112
+ *
113
+ * Note: JSON field names use snake_case (intent_id, content_hash) for HCL
114
+ * convention consistency. TypeScript interface uses camelCase.
115
+ */
116
+ interface TfyStatusHeader {
117
+ /** Always true for Platform Zone files */
118
+ managed: boolean;
119
+ /** Template origin URI (e.g., "tfy-registry/aws/eks-core") */
120
+ source: string;
121
+ /** Template semver tag (e.g., "v2.4.1") */
122
+ version: string;
123
+ /** Cluster identity — same value for all files in a cluster */
124
+ intentId: string;
125
+ /** SHA-256 of file content excluding the header ("sha256:<hex>") */
126
+ contentHash: string;
127
+ }
128
+ /**
129
+ * A single entry in the engine's output file map.
130
+ * (FR-039, FR-040)
131
+ */
132
+ interface FileEntry {
133
+ /** Full file content (includes @tfy-status header for platform files) */
134
+ content: string;
135
+ /** File ownership zone */
136
+ zone: Zone;
137
+ /** Parsed header metadata (present only for platform zone files) */
138
+ header?: TfyStatusHeader;
139
+ }
140
+ /**
141
+ * Zone-tagged file map — the engine's standard output format.
142
+ * Keys are relative file paths.
143
+ * (FR-021, FR-039)
144
+ */
145
+ type FileMap = Map<string, FileEntry>;
146
+ /**
147
+ * The engine's input configuration.
148
+ *
149
+ * CHANGE (006): `template` field accepts a resolved `Template` object
150
+ * instead of a URI string. The caller is responsible for fetching
151
+ * and constructing the Template before calling the engine.
152
+ * (FR-031, FR-032)
153
+ */
154
+ interface Envelope {
155
+ /** Resolved template (caller fetches, engine consumes) */
156
+ template: Template;
157
+ /** Input values to pass to template */
158
+ inputs: Record<string, unknown>;
159
+ /** Optional rendering options */
160
+ options?: RenderOptions;
161
+ /** Cluster identity — propagated to file headers and Manifest (FR-031) */
162
+ intentId: string;
163
+ /** Filename prefix for Platform Zone files (default: "tfy_") */
164
+ platformPrefix?: string;
165
+ }
166
+ /**
167
+ * Options for rendering operations.
168
+ *
169
+ * CHANGE (006): Removed `noCache`, `cacheDir`, `timeout` — these were
170
+ * resolver options that no longer apply to the pure engine.
171
+ */
172
+ interface RenderOptions {
173
+ /** Skip tofu fmt formatting (default: false) */
174
+ skipFormat?: boolean;
175
+ }
176
+ /**
177
+ * Metadata about a template, sourced from the "template" key in template.json.
178
+ */
179
+ interface TemplateMetadata {
180
+ /** Template name */
181
+ name: string;
182
+ /** Human-readable description */
183
+ description?: string;
184
+ /** Template version (semver) */
185
+ version: string;
186
+ }
187
+ /**
188
+ * A remote template package containing source files and schema.
189
+ *
190
+ * CHANGE (008): Replaced `schema: TemplateSchema` with `metadata` + `jsonSchema`.
191
+ * Metadata comes from template.json "template" key; schema from "schema" key.
192
+ */
193
+ interface Template {
194
+ /** Template metadata from template.json → "template" key */
195
+ metadata: TemplateMetadata;
196
+ /** JSON Schema Draft-07 from template.json → "schema" key */
197
+ jsonSchema: JSONSchema7;
198
+ /** Map of output paths to HBS content (rendered via Handlebars) */
199
+ files: Map<string, string>;
200
+ /** Map of output paths to static content (copied verbatim, no rendering) */
201
+ staticFiles: Map<string, string>;
202
+ /** Template source URI */
203
+ source: string;
204
+ /** Resolved version info */
205
+ version: VersionInfo;
206
+ }
207
+ /**
208
+ * Version information for a template.
209
+ */
210
+ interface VersionInfo {
211
+ /** Semantic version (e.g., "0.1.0") */
212
+ semver: string;
213
+ /** API version directory from the URI path (e.g., "v1"), for display/logging */
214
+ apiVersion?: string;
215
+ }
216
+
217
+ /**
218
+ * Per-file entry in the Manifest.
219
+ * (FR-035)
220
+ */
221
+ interface ManifestFileEntry {
222
+ /** Relative file path */
223
+ path: string;
224
+ /** SHA-256 content hash, header-agnostic ("sha256:<hex>") */
225
+ hash: string;
226
+ /** File size in bytes */
227
+ size: number;
228
+ /** Template origin for this file */
229
+ source: string;
230
+ /** Template semver for this file */
231
+ version: string;
232
+ /** File ownership zone */
233
+ zone: Zone;
234
+ }
235
+ /**
236
+ * Manifest (State Passport) — JSON format.
237
+ * (FR-007, FR-033, FR-034, FR-036)
238
+ */
239
+ interface Manifest {
240
+ /** Format version discriminator */
241
+ manifestVersion: '1.0';
242
+ /** Cluster identity (matches envelope.intentId) */
243
+ intentId: string;
244
+ /** ISO-8601 generation timestamp */
245
+ generatedAt: string;
246
+ /** Primary template origin URI */
247
+ templateSource: string;
248
+ /** Primary template semver */
249
+ templateVersion: string;
250
+ /** Aggregate Platform Hash ("sha256:<hex>") */
251
+ aggregateHash: string;
252
+ /** Engine metadata */
253
+ engine: {
254
+ version: string;
255
+ formatted: boolean;
256
+ };
257
+ /** Resolved input values used during generation (005) */
258
+ inputs: Record<string, unknown>;
259
+ /** Filename prefix for Platform Zone files (005) */
260
+ platformPrefix: string;
261
+ /** Per-file entries, sorted lexicographically by path */
262
+ files: ManifestFileEntry[];
263
+ }
264
+ /**
265
+ * A single drift finding.
266
+ * (FR-042)
267
+ */
268
+ interface DriftEntry {
269
+ /** File path (relative) */
270
+ path: string;
271
+ /** Classification of the finding */
272
+ type: DriftType;
273
+ /** Human-readable description of the mismatch */
274
+ details: string;
275
+ }
276
+ /**
277
+ * Quick counts for CLI display and telemetry.
278
+ */
279
+ interface DriftSummary {
280
+ /** Total Platform Zone files checked */
281
+ totalFiles: number;
282
+ /** Files with content_drift */
283
+ driftedFiles: number;
284
+ /** Files with metadata_inconsistency */
285
+ inconsistentFiles: number;
286
+ /** Files in Manifest but not found */
287
+ missingFiles: number;
288
+ /** Platform-prefixed files not in Manifest */
289
+ unexpectedFiles: number;
290
+ /** Files with source_mismatch */
291
+ sourceMismatches: number;
292
+ }
293
+ /**
294
+ * Structured drift report from Verify and Upgrade operations.
295
+ * (FR-041, FR-043)
296
+ */
297
+ interface DriftReport {
298
+ /** True if no drift entries (all files match) */
299
+ valid: boolean;
300
+ /** Aggregate hash comparison */
301
+ aggregateHash: {
302
+ expected: string;
303
+ actual: string;
304
+ };
305
+ /** Per-file findings (a file may appear multiple times with different types) */
306
+ entries: DriftEntry[];
307
+ /** Quick counts for display/telemetry */
308
+ summary: DriftSummary;
309
+ }
310
+ /**
311
+ * Result of the Install operation.
312
+ * (FR-012)
313
+ *
314
+ * CHANGE (008): sideOutputs removed — templates own all Terraform output via .hbs files.
315
+ */
316
+ interface InstallResult {
317
+ /** Zone-tagged file map (platform + user boilerplate files) */
318
+ files: FileMap;
319
+ /** Initial Manifest */
320
+ manifest: Manifest;
321
+ }
322
+ /**
323
+ * Result of the Upgrade operation.
324
+ * (FR-014, FR-015, FR-016)
325
+ *
326
+ * CHANGE (008): sideOutputs removed — templates own all Terraform output via .hbs files.
327
+ */
328
+ interface UpgradeResult {
329
+ /** New zone-tagged file map */
330
+ files: FileMap;
331
+ /** Updated Manifest */
332
+ manifest: Manifest;
333
+ /** Pre-upgrade drift analysis */
334
+ driftReport: DriftReport;
335
+ /**
336
+ * True if the upgrade was blocked due to source mismatch (FR-028).
337
+ * When true, `files` and `manifest` reflect the CURRENT state (not upgraded).
338
+ */
339
+ sourceBlocked: boolean;
340
+ }
341
+ /**
342
+ * Result of the Verify operation.
343
+ * (FR-015, FR-041)
344
+ */
345
+ interface VerifyResult {
346
+ /** Full drift analysis */
347
+ driftReport: DriftReport;
348
+ }
349
+ /**
350
+ * Result of the Hash-Only operation.
351
+ * (FR-022, FR-023)
352
+ */
353
+ interface HashOnlyResult {
354
+ /** Aggregate Platform Hash ("sha256:<hex>") */
355
+ aggregateHash: string;
356
+ /** Template source used */
357
+ templateSource: string;
358
+ /** Template version used */
359
+ templateVersion: string;
360
+ /** Number of Platform Zone files that would be generated */
361
+ fileCount: number;
362
+ }
363
+ /**
364
+ * Options for resolver operations.
365
+ */
366
+ interface ResolverOptions {
367
+ /** Request timeout in ms */
368
+ timeout?: number;
369
+ /** Number of retries */
370
+ retries?: number;
371
+ /** Skip cache lookup */
372
+ noCache?: boolean;
373
+ }
374
+ /**
375
+ * Interface for fetching templates from different sources.
376
+ */
377
+ interface Resolver {
378
+ /** Check if resolver handles this URI scheme */
379
+ canResolve(uri: string): boolean;
380
+ /** Fetch template from source */
381
+ resolve(uri: string, options?: ResolverOptions): Promise<Template>;
382
+ }
383
+ /**
384
+ * The HCL Engine public API.
385
+ *
386
+ * CHANGE (006): Narrowed to four core operations. Removed:
387
+ * - getSchema(uri) — caller reads template.schema directly
388
+ * - validate(uri, inputs) — caller uses exported validateInputs()
389
+ * - clearCache(uri?) — caching is caller's concern
390
+ * - EngineConfig — deleted entirely (all fields were resolver-related)
391
+ *
392
+ * (FR-037, FR-038, FR-024)
393
+ */
394
+ interface HclEngine {
395
+ /**
396
+ * Install: Generate all files for a new cluster.
397
+ * Input: Envelope with intentId, resolved Template, and inputs.
398
+ * Output: Zone-tagged file map + initial Manifest.
399
+ * (FR-012, FR-025, FR-039)
400
+ */
401
+ install(envelope: Envelope): Promise<InstallResult>;
402
+ /**
403
+ * Upgrade: Update an existing cluster to new templates.
404
+ * Input: Envelope + current file map (from disk) + previous Manifest.
405
+ * Output: New file map + new Manifest + drift report.
406
+ * (FR-014, FR-015, FR-016, FR-027, FR-028, FR-047)
407
+ */
408
+ upgrade(envelope: Envelope, currentFiles: FileMap, previousManifest: Manifest): Promise<UpgradeResult>;
409
+ /**
410
+ * Verify: Check integrity of existing files against a Manifest.
411
+ * Input: Current file map (from disk) + previous Manifest.
412
+ * Output: Drift report only (no file generation).
413
+ * (FR-015, FR-041, FR-047)
414
+ */
415
+ verify(currentFiles: FileMap, previousManifest: Manifest): Promise<VerifyResult>;
416
+ /**
417
+ * Hash-Only: Calculate expected aggregate hash without full generation.
418
+ * Input: Envelope (same as install).
419
+ * Output: Aggregate Platform Hash + metadata.
420
+ * (FR-022, FR-023)
421
+ */
422
+ hashOnly(envelope: Envelope): Promise<HashOnlyResult>;
423
+ }
424
+
425
+ /**
426
+ * Template JSON structure validation using AJV.
427
+ *
428
+ * Validates the top-level structure of template.json against the
429
+ * templateJsonSchema (JSON Schema Draft-07) defined in template-json-schema.ts.
430
+ *
431
+ * Reference: R-006, data-model.md validator.ts
432
+ */
433
+
434
+ /**
435
+ * Result of validating a template.json file.
436
+ */
437
+ interface TemplateJsonResult {
438
+ metadata: TemplateMetadata;
439
+ jsonSchema: JSONSchema7;
440
+ }
441
+ /**
442
+ * Validate the structure of a parsed template.json file.
443
+ *
444
+ * Checks:
445
+ * - Data is an object with "template" and "schema" keys
446
+ * - template.name is a non-empty string
447
+ * - template.version is a semver string (x.y.z)
448
+ * - template.description is an optional string
449
+ * - schema is a non-null object with type: "object" and properties
450
+ *
451
+ * @param data - Parsed JSON content from template.json
452
+ * @returns Validated metadata and JSON Schema
453
+ * @throws EngineError with TEMPLATE_JSON_INVALID on failure
454
+ */
455
+ declare function validateTemplateJson(data: unknown): TemplateJsonResult;
456
+
457
+ /**
458
+ * AJV-based JSON Schema input validation.
459
+ *
460
+ * Provides:
461
+ * - createInputValidator(): Compiles a JSON Schema into an AJV validator
462
+ * - validateAndApplyDefaults(): Validates inputs + applies defaults in one pass
463
+ * - validateJsonSchemaStructure(): Asserts root type: "object" + properties key
464
+ *
465
+ * Local $ref pointers (e.g., "#/components/schemas/...") are supported natively
466
+ * by AJV and resolved automatically during schema compilation.
467
+ *
468
+ * CHANGE (008): New module replacing hand-rolled validateInputs() and applyDefaults().
469
+ *
470
+ * Reference: R-001, R-007, R-010
471
+ */
472
+
473
+ /**
474
+ * Validate that a JSON Schema has the required structure for template inputs.
475
+ * Must be an object with type: "object" and a properties key.
476
+ *
477
+ * @param schema - Schema to validate
478
+ * @throws EngineError if structure is invalid
479
+ */
480
+ declare function validateJsonSchemaStructure(schema: JSONSchema7): void;
481
+
482
+ /**
483
+ * JSON Schema (Draft-07) for template.json files.
484
+ *
485
+ * This is the source of truth for template.json validation.
486
+ * Used by validator.ts via AJV for runtime validation.
487
+ *
488
+ * Validates:
489
+ * - "template" key: name (required, non-empty), version (semver, required), description (optional string)
490
+ * - "schema" key: must be a JSON Schema object with type: "object" and a properties key
491
+ *
492
+ * Note: No additionalProperties: false at root level — extra top-level keys are allowed
493
+ * for forward compatibility.
494
+ */
495
+
496
+ declare const templateJsonSchema: JSONSchema7;
497
+
498
+ /**
499
+ * Check if tofu is available in the system.
500
+ *
501
+ * @returns true if tofu is available
502
+ */
503
+ declare function isTofuAvailable(): Promise<boolean>;
504
+
505
+ /**
506
+ * Zone classification for file ownership.
507
+ *
508
+ * Classifies files as Platform Zone or User Zone based on filename prefix.
509
+ * Reference: R-007, FR-001, FR-002
510
+ */
511
+
512
+ /**
513
+ * Default platform filename prefix.
514
+ */
515
+ declare const DEFAULT_PREFIX = "tfy_";
516
+ /**
517
+ * Classify a file as Platform Zone or User Zone based on its filename.
518
+ *
519
+ * Rules (R-007):
520
+ * 1. Only the basename matters (not the directory path).
521
+ * 2. The prefix check is case-sensitive.
522
+ * 3. The Manifest file itself (manifest.json) is classified as Platform Zone.
523
+ * 4. Files without .tf extension that have the prefix are still Platform Zone.
524
+ * 5. The prefix is configurable per envelope/configuration.
525
+ *
526
+ * @param filePath - Relative or absolute file path
527
+ * @param prefix - Platform Zone filename prefix (default: "tfy_")
528
+ * @returns Zone classification ("platform" or "user")
529
+ */
530
+ declare function classifyFile(filePath: string, prefix?: string): Zone;
531
+
532
+ /**
533
+ * @tfy-status header processing: parse, strip, inject.
534
+ *
535
+ * The header is a structured metadata block embedded as Terraform comment lines
536
+ * at the top of every Platform Zone file.
537
+ *
538
+ * Format (R-001):
539
+ * ```hcl
540
+ * # @tfy-status:begin
541
+ * # {"managed":true,"source":"...","version":"...","intent_id":"...","content_hash":"sha256:..."}
542
+ * # @tfy-status:end
543
+ * ```
544
+ *
545
+ * Reference: R-001, R-002, R-010, FR-044 through FR-046
546
+ */
547
+
548
+ /**
549
+ * Parse a @tfy-status header from file content.
550
+ * Returns undefined if no valid header is found.
551
+ * (FR-044)
552
+ *
553
+ * @param content - File content possibly containing a header
554
+ * @returns Parsed header or undefined
555
+ */
556
+ declare function parseHeader(content: string): TfyStatusHeader | undefined;
557
+
558
+ /**
559
+ * HCL Engine implementation — four-operation integrity API.
560
+ *
561
+ * Operations:
562
+ * - install(envelope): Generate all files for a new cluster
563
+ * - upgrade(envelope, currentFiles, previousManifest): Update to new templates
564
+ * - verify(currentFiles, previousManifest): Check integrity
565
+ * - hashOnly(envelope): Calculate expected aggregate hash
566
+ *
567
+ * Reference: R-006, FR-037, FR-038, plan.md Operation Pipelines
568
+ */
569
+
570
+ /**
571
+ * Create a new HCL engine instance.
572
+ *
573
+ * @returns Engine instance
574
+ *
575
+ * @example
576
+ * ```typescript
577
+ * import { createEngine } from '@truefoundry/tfy-infra-engine';
578
+ *
579
+ * const engine = createEngine();
580
+ * const result = await engine.install({
581
+ * template, // pre-resolved Template object
582
+ * inputs: { cluster_name: 'prod' },
583
+ * intentId: 'cluster-prod-001',
584
+ * });
585
+ * ```
586
+ */
587
+ declare function createEngine(): HclEngine;
588
+
589
+ export { DEFAULT_PREFIX, type DriftReport, type DriftType, EngineError, EngineErrorCode, type Envelope, type FileEntry, type FileMap, type HashOnlyResult, type InstallResult, type JSONSchema7, type Manifest, type Resolver, type ResolverOptions, type Template, type UpgradeResult, type VerifyResult, type VersionInfo, classifyFile, createEngine, isTofuAvailable, parseHeader, templateJsonSchema, validateJsonSchemaStructure, validateTemplateJson };