From 215fdc93278a11ff7f95ac4475e523af0515118e Mon Sep 17 00:00:00 2001 From: Rahul Krishna Date: Wed, 19 Aug 2026 14:28:39 -0400 Subject: [PATCH 01/16] feat: add v1 schemas for java, python, and typescript MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Lay out the versioned tree (v1/ and v2/, one directory per language) and fill v1 from the analyzer releases that emit the v1 shape: java v2.4.1 — the repo is on its 2.x version line, but the schema it emits is still v1; the v2 migration has not begun. Generated from the Lombok @Data entity classes plus the edge types in SystemDependencyGraph.java. Property names are the Gson LOWER_CASE_WITH_UNDERSCORES rendering of the Java fields, and each property keeps its original field name and Java type under x-java. Gson runs with serializeNulls(), so every reference-typed field is typed nullable — arrays and maps included. python v0.3.2 — the last pre-1.0 release. Generated from the pydantic models (PyApplication). typescript v0.4.3 — the last release before src/schema/v2 landed, and the one python-sdk pins. Generated from the TypeScript interfaces (TSApplication). Every schema is JSON Schema draft 2020-12 and carries an x-cldk block recording the source repo, release, and where the schema is defined in that release, plus the known consumers. Checked against real analyzer output: java validates clean against both java fixtures in python-sdk; typescript validates clean once synthesized_callables is backfilled (the fixture predates that field, which v0.4.3 always emits); python validates clean except for a provenance value written by a later 1.x analyzer. v1/analysis.template.schema.json is the starting point for adding another language to v1. scripts/check.py validates every schema in the repo, and any *.sample.json placed next to one. --- scripts/check.py | 52 + v1/analysis.template.schema.json | 223 +++ v1/java/analysis.schema.json | 2078 ++++++++++++++++++++++++++++ v1/python/analysis.schema.json | 994 +++++++++++++ v1/typescript/analysis.schema.json | 1502 ++++++++++++++++++++ v2/java/.gitkeep | 0 v2/python/.gitkeep | 0 v2/typescript/.gitkeep | 0 8 files changed, 4849 insertions(+) create mode 100644 scripts/check.py create mode 100644 v1/analysis.template.schema.json create mode 100644 v1/java/analysis.schema.json create mode 100644 v1/python/analysis.schema.json create mode 100644 v1/typescript/analysis.schema.json create mode 100644 v2/java/.gitkeep create mode 100644 v2/python/.gitkeep create mode 100644 v2/typescript/.gitkeep diff --git a/scripts/check.py b/scripts/check.py new file mode 100644 index 0000000..9b329c7 --- /dev/null +++ b/scripts/check.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python3 +"""Check every schema in this repo is a valid JSON Schema, and validate any +sample documents placed next to it. + +A sample is any *.sample.json sitting in the same directory as a schema; it is +validated against that directory's analysis.schema.json. + + python3 scripts/check.py # all versions + python3 scripts/check.py v1/java # one directory +""" +import json +import sys +from pathlib import Path + +from jsonschema import Draft202012Validator + +ROOT = Path(__file__).resolve().parent.parent + + +def check(schema_path: Path) -> int: + schema = json.loads(schema_path.read_text()) + Draft202012Validator.check_schema(schema) + rel = schema_path.relative_to(ROOT) + print(f"ok {rel}") + + failures = 0 + validator = Draft202012Validator(schema) + for sample in sorted(schema_path.parent.glob("*.sample.json")): + errors = sorted(validator.iter_errors(json.loads(sample.read_text())), + key=lambda e: list(e.absolute_path)) + if errors: + failures += 1 + print(f"FAIL {sample.relative_to(ROOT)}: {len(errors)} error(s)") + for e in errors[:5]: + where = "/".join(str(p) for p in e.absolute_path) or "" + print(f" {where}: {e.message[:160]}") + else: + print(f"ok {sample.relative_to(ROOT)}") + return failures + + +def main() -> int: + where = ROOT / sys.argv[1] if len(sys.argv) > 1 else ROOT + schemas = sorted(where.rglob("*.schema.json")) + if not schemas: + print(f"no schemas under {where}") + return 1 + return min(sum(check(s) for s in schemas), 1) + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/v1/analysis.template.schema.json b/v1/analysis.template.schema.json new file mode 100644 index 0000000..fc44da9 --- /dev/null +++ b/v1/analysis.template.schema.json @@ -0,0 +1,223 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://codellm-devkit.github.io/schema/v1/LANG/analysis.schema.json", + "title": "codeanalyzer-LANG v1 analysis output", + "description": "Template for a v1 (pre-canonical) analysis.json. Copy into v1//, replace LANG, fill the $defs from the analyzer release named in x-cldk.source, and delete anything the language does not emit. v1 is descriptive, not prescriptive: each analyzer's v1 shape is whatever that release actually produced.", + + "x-cldk": { + "schemaVersion": 1, + "language": "LANG", + "source": { + "repo": "codellm-devkit/codeanalyzer-LANG", + "release": "vX.Y.Z", + "definedIn": "path/to/schema/source/in/that/release" + }, + "consumers": [ + { "repo": "codellm-devkit/python-sdk", "release": "v1.5.x" } + ] + }, + + "type": "object", + "required": ["symbol_table"], + "additionalProperties": true, + + "properties": { + "symbol_table": { + "description": "Per-file analysis, keyed by project-relative POSIX path (Java keys on compilation unit path, Python/TypeScript on module file path).", + "type": "object", + "additionalProperties": { "$ref": "#/$defs/CompilationUnit" } + }, + "call_graph": { + "description": "Flat edge list. Endpoints are callable signatures, using the same canonicalizer caller- and callee-side.", + "type": "array", + "items": { "$ref": "#/$defs/CallEdge" }, + "default": [] + }, + "external_symbols": { + "description": "Call-graph endpoints not declared in symbol_table (library / builtin members), keyed by signature. Omit if the analyzer does not emit it.", + "type": "object", + "additionalProperties": { "$ref": "#/$defs/ExternalSymbol" } + }, + "analyzer": { "$ref": "#/$defs/AnalyzerInfo" }, + "repository": { "$ref": "#/$defs/RepositoryInfo" }, + "version": { + "description": "Analyzer version string. Java emits this at the top level instead of an analyzer object.", + "type": "string" + } + }, + + "$defs": { + "CompilationUnit": { + "description": "One source file. Java: JavaCompilationUnit. Python: PyModule. TypeScript: TSModule. Fill in the language's own members (classes, functions/callables, imports, comments, variables, and any language-only members such as TS interfaces/enums/type aliases/namespaces or Java record components).", + "type": "object", + "properties": { + "file_path": { "type": "string" }, + "imports": { "type": "array", "items": { "$ref": "#/$defs/Import" } }, + "comments": { "type": "array", "items": { "$ref": "#/$defs/Comment" } }, + "classes": { "type": "object", "additionalProperties": { "$ref": "#/$defs/Class" } }, + "callables": { "type": "object", "additionalProperties": { "$ref": "#/$defs/Callable" } } + } + }, + + "Class": { + "description": "Type-like declaration (class, record, interface, enum). Key in the parent map is the signature.", + "type": "object", + "properties": { + "name": { "type": "string" }, + "signature": { "type": "string" }, + "code": { "type": ["string", "null"] }, + "comments": { "type": "array", "items": { "$ref": "#/$defs/Comment" } }, + "callables": { "type": "object", "additionalProperties": { "$ref": "#/$defs/Callable" } }, + "attributes": { "type": "object", "additionalProperties": { "$ref": "#/$defs/Field" } }, + "inner_classes": { "type": "object", "additionalProperties": { "$ref": "#/$defs/Class" } } + }, + "allOf": [{ "$ref": "#/$defs/SourceRange" }] + }, + + "Callable": { + "description": "Function, method, constructor, lambda, or other callable. `signature` is the call-graph identity and MUST match the endpoints used in call_graph.", + "type": "object", + "required": ["signature"], + "properties": { + "name": { "type": "string" }, + "signature": { "type": "string" }, + "code": { "type": ["string", "null"] }, + "return_type": { "type": ["string", "null"] }, + "parameters": { "type": "array", "items": { "$ref": "#/$defs/Parameter" } }, + "comments": { "type": "array", "items": { "$ref": "#/$defs/Comment" } }, + "call_sites": { "type": "array", "items": { "$ref": "#/$defs/CallSite" } }, + "accessed_symbols": { "type": "array", "items": { "$ref": "#/$defs/Symbol" } }, + "local_variables": { "type": "array", "items": { "$ref": "#/$defs/VariableDeclaration" } }, + "cyclomatic_complexity": { "type": "integer" } + }, + "allOf": [{ "$ref": "#/$defs/SourceRange" }] + }, + + "CallSite": { + "description": "A call expression inside a callable body. `callee_signature` links to a Callable or an ExternalSymbol.", + "type": "object", + "properties": { + "method_name": { "type": "string" }, + "receiver_expr": { "type": ["string", "null"] }, + "receiver_type": { "type": ["string", "null"] }, + "argument_types": { "type": "array", "items": { "type": "string" } }, + "return_type": { "type": ["string", "null"] }, + "callee_signature": { "type": ["string", "null"] }, + "is_constructor_call": { "type": "boolean" } + }, + "allOf": [{ "$ref": "#/$defs/SourceRange" }] + }, + + "CallEdge": { + "type": "object", + "required": ["source", "target"], + "properties": { + "source": { "description": "Caller callable signature.", "type": "string" }, + "target": { "description": "Callee callable signature.", "type": "string" }, + "type": { "type": "string", "default": "CALL_DEP" }, + "weight": { "type": "integer", "default": 1 } + } + }, + + "ExternalSymbol": { + "type": "object", + "properties": { + "name": { "description": "Member/short name, e.g. `get` for `requests.get`.", "type": "string" }, + "module": { "type": ["string", "null"] } + } + }, + + "Parameter": { + "type": "object", + "properties": { + "name": { "type": "string" }, + "type": { "type": ["string", "null"] }, + "default_value": { "type": ["string", "null"] } + }, + "allOf": [{ "$ref": "#/$defs/SourceRange" }] + }, + + "Field": { + "type": "object", + "properties": { + "name": { "type": "string" }, + "type": { "type": ["string", "null"] }, + "initializer": { "type": ["string", "null"] }, + "comments": { "type": "array", "items": { "$ref": "#/$defs/Comment" } } + }, + "allOf": [{ "$ref": "#/$defs/SourceRange" }] + }, + + "VariableDeclaration": { + "type": "object", + "properties": { + "name": { "type": "string" }, + "type": { "type": ["string", "null"] }, + "initializer": { "type": ["string", "null"] }, + "scope": { "type": "string" } + }, + "allOf": [{ "$ref": "#/$defs/SourceRange" }] + }, + + "Symbol": { + "type": "object", + "properties": { + "name": { "type": "string" }, + "qualified_name": { "type": ["string", "null"] }, + "kind": { "type": "string" }, + "scope": { "type": "string" }, + "type": { "type": ["string", "null"] } + } + }, + + "Import": { + "type": "object", + "properties": { + "module": { "type": "string" }, + "name": { "type": ["string", "null"] }, + "alias": { "type": ["string", "null"] }, + "resolved_module": { "type": ["string", "null"] } + }, + "allOf": [{ "$ref": "#/$defs/SourceRange" }] + }, + + "Comment": { + "type": "object", + "properties": { + "content": { "type": "string" }, + "is_docstring": { "type": "boolean" } + }, + "allOf": [{ "$ref": "#/$defs/SourceRange" }] + }, + + "AnalyzerInfo": { + "description": "Which analyzer produced this snapshot, and how it was configured.", + "type": "object", + "properties": { + "name": { "type": "string" }, + "version": { "type": "string" }, + "config": { "type": "object" } + } + }, + + "RepositoryInfo": { + "type": "object", + "properties": { + "uri": { "type": ["string", "null"] }, + "revision": { "type": "string" }, + "dirty": { "type": "boolean" } + } + }, + + "SourceRange": { + "description": "Positions are 1-based lines, 0-based columns, -1 when unknown. Some analyzers only emit the line pair.", + "type": "object", + "properties": { + "start_line": { "type": "integer", "default": -1 }, + "end_line": { "type": "integer", "default": -1 }, + "start_column": { "type": "integer", "default": -1 }, + "end_column": { "type": "integer", "default": -1 } + } + } + } +} diff --git a/v1/java/analysis.schema.json b/v1/java/analysis.schema.json new file mode 100644 index 0000000..4b18a9c --- /dev/null +++ b/v1/java/analysis.schema.json @@ -0,0 +1,2078 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://codellm-devkit.github.io/schema/v1/java/analysis.schema.json", + "title": "codeanalyzer-java v1 analysis output", + "description": "Generated from the Lombok @Data entity classes of codeanalyzer-java v2.4.1. The repo is on its 2.x version line; the schema it emits is still v1 \u2014 the v2 schema migration has not begun. Property names are the Gson LOWER_CASE_WITH_UNDERSCORES rendering of the Java field names, which CodeAnalyzer configures; each property keeps its Java field under x-java.", + "x-cldk": { + "schemaVersion": 1, + "language": "java", + "source": { + "repo": "codellm-devkit/codeanalyzer-java", + "release": "v2.4.1", + "definedIn": "src/main/java/com/ibm/cldk/entities/ (+ SystemDependencyGraph.java)" + }, + "consumers": [ + { + "repo": "codellm-devkit/python-sdk", + "release": "v1.5.x" + } + ] + }, + "type": "object", + "required": [ + "symbol_table" + ], + "additionalProperties": false, + "properties": { + "symbol_table": { + "description": "Per-compilation-unit analysis, keyed by absolute source file path.", + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/JavaCompilationUnit" + } + }, + "call_graph": { + "description": "Call graph / system dependency graph edges. Present only at analysis level >= 2.", + "type": "array", + "items": { + "$ref": "#/$defs/Dependency" + } + }, + "version": { + "description": "Analyzer version string, or \"unknown\" / \"error retrieving version\" when it could not be resolved.", + "type": "string" + } + }, + "$defs": { + "AbstractGraphEdge": { + "type": "object", + "properties": { + "context": { + "type": [ + "string", + "null" + ], + "description": "The Context. -- GETTER -- Gets context. @return the context", + "x-java": { + "field": "context", + "type": "String" + } + }, + "weight": { + "type": [ + "integer", + "null" + ], + "description": "The Weight. -- GETTER -- Gets weight. @return the weight", + "x-java": { + "field": "weight", + "type": "Integer" + } + } + }, + "additionalProperties": false + }, + "CRUDOperation": { + "type": "object", + "properties": { + "line_number": { + "type": "integer", + "x-java": { + "field": "lineNumber", + "type": "int" + } + }, + "operation_type": { + "oneOf": [ + { + "$ref": "#/$defs/CRUDOperationType" + }, + { + "type": "null" + } + ], + "x-java": { + "field": "operationType", + "type": "CRUDOperationType" + } + }, + "target_table": { + "type": [ + "string", + "null" + ], + "x-java": { + "field": "targetTable", + "type": "String" + } + }, + "involved_columns": { + "type": [ + "array", + "null" + ], + "items": { + "type": [ + "string", + "null" + ] + }, + "x-java": { + "field": "involvedColumns", + "type": "List" + } + }, + "condition": { + "type": [ + "string", + "null" + ], + "x-java": { + "field": "condition", + "type": "String" + } + }, + "joined_tables": { + "type": [ + "array", + "null" + ], + "items": { + "type": [ + "string", + "null" + ] + }, + "x-java": { + "field": "joinedTables", + "type": "List" + } + } + }, + "additionalProperties": false + }, + "CRUDOperationType": { + "type": "string", + "enum": [ + "CREATE", + "READ", + "UPDATE", + "DELETE" + ] + }, + "CRUDQuery": { + "type": "object", + "properties": { + "line_number": { + "type": "integer", + "x-java": { + "field": "lineNumber", + "type": "int" + } + }, + "query_arguments": { + "type": [ + "array", + "null" + ], + "items": { + "type": [ + "string", + "null" + ] + }, + "x-java": { + "field": "queryArguments", + "type": "List" + } + }, + "query_type": { + "oneOf": [ + { + "$ref": "#/$defs/CRUDQueryType" + }, + { + "type": "null" + } + ], + "x-java": { + "field": "queryType", + "type": "CRUDQueryType" + } + } + }, + "additionalProperties": false + }, + "CRUDQueryType": { + "type": "string", + "enum": [ + "READ", + "WRITE", + "NAMED" + ] + }, + "CallEdge": { + "type": "object", + "properties": { + "context": { + "type": [ + "string", + "null" + ], + "description": "The Context. -- GETTER -- Gets context. @return the context", + "x-java": { + "field": "context", + "type": "String" + } + }, + "weight": { + "type": [ + "integer", + "null" + ], + "description": "The Weight. -- GETTER -- Gets weight. @return the weight", + "x-java": { + "field": "weight", + "type": "Integer" + } + }, + "type": { + "type": [ + "string", + "null" + ], + "description": "The Type.", + "x-java": { + "field": "type", + "type": "String" + } + } + }, + "additionalProperties": false, + "x-java-extends": "AbstractGraphEdge" + }, + "CallSite": { + "type": "object", + "properties": { + "method_name": { + "type": [ + "string", + "null" + ], + "description": "Name of the method being called", + "x-java": { + "field": "methodName", + "type": "String" + } + }, + "comment": { + "oneOf": [ + { + "$ref": "#/$defs/Comment" + }, + { + "type": "null" + } + ], + "description": "Comment associated with the call site", + "x-java": { + "field": "comment", + "type": "Comment" + } + }, + "receiver_expr": { + "type": [ + "string", + "null" + ], + "description": "Expression representing the receiver of the method call", + "x-java": { + "field": "receiverExpr", + "type": "String" + } + }, + "receiver_type": { + "type": [ + "string", + "null" + ], + "description": "Type of the receiver object", + "x-java": { + "field": "receiverType", + "type": "String" + } + }, + "argument_types": { + "type": [ + "array", + "null" + ], + "items": { + "type": [ + "string", + "null" + ] + }, + "description": "List of argument types for the method call", + "x-java": { + "field": "argumentTypes", + "type": "List" + } + }, + "argument_expr": { + "type": [ + "array", + "null" + ], + "items": { + "type": [ + "string", + "null" + ] + }, + "description": "List of argument expressions for the method call", + "x-java": { + "field": "argumentExpr", + "type": "List" + } + }, + "return_type": { + "type": [ + "string", + "null" + ], + "description": "Return type of the called method", + "x-java": { + "field": "returnType", + "type": "String" + } + }, + "callee_signature": { + "type": [ + "string", + "null" + ], + "description": "Full signature of the callee method", + "x-java": { + "field": "calleeSignature", + "type": "String" + } + }, + "is_public": { + "type": "boolean", + "description": "Flag indicating if the method has public access", + "x-java": { + "field": "isPublic", + "type": "boolean" + } + }, + "is_protected": { + "type": "boolean", + "description": "Flag indicating if the method has protected access", + "x-java": { + "field": "isProtected", + "type": "boolean" + } + }, + "is_private": { + "type": "boolean", + "description": "Flag indicating if the method has private access", + "x-java": { + "field": "isPrivate", + "type": "boolean" + } + }, + "is_unspecified": { + "type": "boolean", + "description": "Flag indicating if the method has unspecified access", + "x-java": { + "field": "isUnspecified", + "type": "boolean" + } + }, + "is_static_call": { + "type": "boolean", + "description": "Flag indicating if this is a static method call", + "x-java": { + "field": "isStaticCall", + "type": "boolean" + } + }, + "is_constructor_call": { + "type": "boolean", + "description": "Flag indicating if this is a constructor call", + "x-java": { + "field": "isConstructorCall", + "type": "boolean" + } + }, + "crud_operation": { + "oneOf": [ + { + "$ref": "#/$defs/CRUDOperation" + }, + { + "type": "null" + } + ], + "description": "CRUD operation associated with this call site, if any", + "x-java": { + "field": "crudOperation", + "type": "CRUDOperation" + } + }, + "crud_query": { + "oneOf": [ + { + "$ref": "#/$defs/CRUDQuery" + }, + { + "type": "null" + } + ], + "description": "CRUD query associated with this call site, if any", + "x-java": { + "field": "crudQuery", + "type": "CRUDQuery" + } + }, + "start_line": { + "type": "integer", + "description": "Starting line number of the call site in the source file", + "x-java": { + "field": "startLine", + "type": "int" + } + }, + "start_column": { + "type": "integer", + "description": "Starting column number of the call site in the source file", + "x-java": { + "field": "startColumn", + "type": "int" + } + }, + "end_line": { + "type": "integer", + "description": "Ending line number of the call site in the source file", + "x-java": { + "field": "endLine", + "type": "int" + } + }, + "end_column": { + "type": "integer", + "description": "Ending column number of the call site in the source file", + "x-java": { + "field": "endColumn", + "type": "int" + } + } + }, + "additionalProperties": false + }, + "Callable": { + "type": "object", + "properties": { + "file_path": { + "type": [ + "string", + "null" + ], + "description": "The file path where the callable entity is defined.", + "x-java": { + "field": "filePath", + "type": "String" + } + }, + "signature": { + "type": [ + "string", + "null" + ], + "description": "The signature of the callable entity.", + "x-java": { + "field": "signature", + "type": "String" + } + }, + "comments": { + "type": [ + "array", + "null" + ], + "items": { + "oneOf": [ + { + "$ref": "#/$defs/Comment" + }, + { + "type": "null" + } + ] + }, + "description": "A list of comments associated with the callable entity.", + "x-java": { + "field": "comments", + "type": "List" + } + }, + "annotations": { + "type": [ + "array", + "null" + ], + "items": { + "type": [ + "string", + "null" + ] + }, + "description": "A list of annotations applied to the callable entity.", + "x-java": { + "field": "annotations", + "type": "List" + } + }, + "modifiers": { + "type": [ + "array", + "null" + ], + "items": { + "type": [ + "string", + "null" + ] + }, + "description": "A list of modifiers applied to the callable entity (e.g., public, private).", + "x-java": { + "field": "modifiers", + "type": "List" + } + }, + "thrown_exceptions": { + "type": [ + "array", + "null" + ], + "items": { + "type": [ + "string", + "null" + ] + }, + "description": "A list of exceptions thrown by the callable entity.", + "x-java": { + "field": "thrownExceptions", + "type": "List" + } + }, + "declaration": { + "type": [ + "string", + "null" + ], + "description": "The declaration of the callable entity.", + "x-java": { + "field": "declaration", + "type": "String" + } + }, + "parameters": { + "type": [ + "array", + "null" + ], + "items": { + "oneOf": [ + { + "$ref": "#/$defs/ParameterInCallable" + }, + { + "type": "null" + } + ] + }, + "description": "A list of parameters for the callable entity.", + "x-java": { + "field": "parameters", + "type": "List" + } + }, + "code": { + "type": [ + "string", + "null" + ], + "description": "The code of the callable entity.", + "x-java": { + "field": "code", + "type": "String" + } + }, + "start_line": { + "type": "integer", + "description": "The starting line number of the callable entity in the source file.", + "x-java": { + "field": "startLine", + "type": "int" + } + }, + "end_line": { + "type": "integer", + "description": "The ending line number of the callable entity in the source file.", + "x-java": { + "field": "endLine", + "type": "int" + } + }, + "code_start_line": { + "type": "integer", + "description": "The starting line number of the callable code in the source file.", + "x-java": { + "field": "codeStartLine", + "type": "int" + } + }, + "return_type": { + "type": [ + "string", + "null" + ], + "description": "The return type of the callable entity.", + "x-java": { + "field": "returnType", + "type": "String" + } + }, + "is_implicit": { + "type": "boolean", + "description": "Indicates whether the callable entity is implicit.", + "x-java": { + "field": "isImplicit", + "type": "boolean" + } + }, + "is_constructor": { + "type": "boolean", + "description": "Indicates whether the callable entity is a constructor.", + "x-java": { + "field": "isConstructor", + "type": "boolean" + } + }, + "referenced_types": { + "type": [ + "array", + "null" + ], + "items": { + "type": [ + "string", + "null" + ] + }, + "description": "A list of types referenced by the callable entity.", + "x-java": { + "field": "referencedTypes", + "type": "List" + } + }, + "accessed_fields": { + "type": [ + "array", + "null" + ], + "items": { + "type": [ + "string", + "null" + ] + }, + "description": "A list of fields accessed by the callable entity.", + "x-java": { + "field": "accessedFields", + "type": "List" + } + }, + "call_sites": { + "type": [ + "array", + "null" + ], + "items": { + "oneOf": [ + { + "$ref": "#/$defs/CallSite" + }, + { + "type": "null" + } + ] + }, + "description": "A list of call sites within the callable entity.", + "x-java": { + "field": "callSites", + "type": "List" + } + }, + "variable_declarations": { + "type": [ + "array", + "null" + ], + "items": { + "oneOf": [ + { + "$ref": "#/$defs/VariableDeclaration" + }, + { + "type": "null" + } + ] + }, + "description": "A list of variable declarations within the callable entity.", + "x-java": { + "field": "variableDeclarations", + "type": "List" + } + }, + "crud_operations": { + "type": [ + "array", + "null" + ], + "items": { + "oneOf": [ + { + "$ref": "#/$defs/CRUDOperation" + }, + { + "type": "null" + } + ] + }, + "description": "A list of CRUD operations associated with the callable entity.", + "x-java": { + "field": "crudOperations", + "type": "List" + } + }, + "crud_queries": { + "type": [ + "array", + "null" + ], + "items": { + "oneOf": [ + { + "$ref": "#/$defs/CRUDQuery" + }, + { + "type": "null" + } + ] + }, + "description": "A list of CRUD queries associated with the callable entity.", + "x-java": { + "field": "crudQueries", + "type": "List" + } + }, + "cyclomatic_complexity": { + "type": "integer", + "description": "The cyclomatic complexity of the callable entity.", + "x-java": { + "field": "cyclomaticComplexity", + "type": "int" + } + }, + "is_entrypoint": { + "type": "boolean", + "description": "Indicates whether the callable entity is an entry point.", + "x-java": { + "field": "isEntrypoint", + "type": "boolean" + } + } + }, + "additionalProperties": false + }, + "CallableVertex": { + "type": "object", + "properties": { + "file_path": { + "type": [ + "string", + "null" + ], + "x-java": { + "field": "filePath", + "type": "String" + } + }, + "type_declaration": { + "type": [ + "string", + "null" + ], + "x-java": { + "field": "typeDeclaration", + "type": "String" + } + }, + "signature": { + "type": [ + "string", + "null" + ], + "x-java": { + "field": "signature", + "type": "String" + } + }, + "callable_declaration": { + "type": [ + "string", + "null" + ], + "x-java": { + "field": "callableDeclaration", + "type": "String" + } + } + }, + "additionalProperties": false + }, + "Comment": { + "type": "object", + "properties": { + "content": { + "type": [ + "string", + "null" + ], + "description": "The textual content of the comment.", + "x-java": { + "field": "content", + "type": "String" + } + }, + "start_line": { + "type": "integer", + "description": "The starting line number of the comment in the source file.

Defaults to {@code -1} if the position is unknown.

", + "x-java": { + "field": "startLine", + "type": "int" + } + }, + "end_line": { + "type": "integer", + "description": "The ending line number of the comment in the source file.

Defaults to {@code -1} if the position is unknown.

", + "x-java": { + "field": "endLine", + "type": "int" + } + }, + "start_column": { + "type": "integer", + "description": "The starting column number of the comment in the source file.

Defaults to {@code -1} if the position is unknown.

", + "x-java": { + "field": "startColumn", + "type": "int" + } + }, + "end_column": { + "type": "integer", + "description": "The ending column number of the comment in the source file.

Defaults to {@code -1} if the position is unknown.

", + "x-java": { + "field": "endColumn", + "type": "int" + } + }, + "is_javadoc": { + "type": "boolean", + "description": "Indicates whether the comment is a Javadoc comment.

Javadoc comments are special block comments used for generating documentation and typically start with {@code }.

", + "x-java": { + "field": "isJavadoc", + "type": "boolean" + } + } + }, + "additionalProperties": false + }, + "Dependency": { + "description": "One call-graph / SDG edge. Emitted only at analysis level >= 2. SDG edges additionally carry source_kind and destination_kind.", + "type": "object", + "properties": { + "source": { + "$ref": "#/$defs/CallableVertex" + }, + "target": { + "$ref": "#/$defs/CallableVertex" + }, + "type": { + "type": "string" + }, + "weight": { + "type": "string", + "description": "Stringified edge weight." + }, + "source_kind": { + "type": [ + "string", + "null" + ] + }, + "destination_kind": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "source", + "target", + "type", + "weight" + ], + "additionalProperties": false, + "x-java": { + "declaredIn": "src/main/java/com/ibm/cldk/SystemDependencyGraph.java", + "classes": [ + "Dependency", + "SDGDependency", + "CallDependency" + ] + } + }, + "EnumConstant": { + "type": "object", + "properties": { + "name": { + "type": [ + "string", + "null" + ], + "x-java": { + "field": "name", + "type": "String" + } + }, + "arguments": { + "type": [ + "array", + "null" + ], + "items": { + "type": [ + "string", + "null" + ] + }, + "x-java": { + "field": "arguments", + "type": "List" + } + } + }, + "additionalProperties": false + }, + "Field": { + "type": "object", + "properties": { + "comment": { + "oneOf": [ + { + "$ref": "#/$defs/Comment" + }, + { + "type": "null" + } + ], + "x-java": { + "field": "comment", + "type": "Comment" + } + }, + "name": { + "type": [ + "string", + "null" + ], + "x-java": { + "field": "name", + "type": "String" + } + }, + "type": { + "type": [ + "string", + "null" + ], + "x-java": { + "field": "type", + "type": "String" + } + }, + "start_line": { + "type": [ + "integer", + "null" + ], + "x-java": { + "field": "startLine", + "type": "Integer" + } + }, + "end_line": { + "type": [ + "integer", + "null" + ], + "x-java": { + "field": "endLine", + "type": "Integer" + } + }, + "variables": { + "type": [ + "array", + "null" + ], + "items": { + "type": [ + "string", + "null" + ] + }, + "x-java": { + "field": "variables", + "type": "List" + } + }, + "modifiers": { + "type": [ + "array", + "null" + ], + "items": { + "type": [ + "string", + "null" + ] + }, + "x-java": { + "field": "modifiers", + "type": "List" + } + }, + "annotations": { + "type": [ + "array", + "null" + ], + "items": { + "type": [ + "string", + "null" + ] + }, + "x-java": { + "field": "annotations", + "type": "List" + } + }, + "variable_initializers": { + "type": [ + "object", + "null" + ], + "additionalProperties": { + "type": [ + "string", + "null" + ] + }, + "x-java": { + "field": "variableInitializers", + "type": "Map" + } + } + }, + "additionalProperties": false + }, + "Import": { + "type": "object", + "properties": { + "path": { + "type": [ + "string", + "null" + ], + "x-java": { + "field": "path", + "type": "String" + } + }, + "is_static": { + "type": "boolean", + "x-java": { + "field": "isStatic", + "type": "boolean" + } + }, + "is_wildcard": { + "type": "boolean", + "x-java": { + "field": "isWildcard", + "type": "boolean" + } + } + }, + "additionalProperties": false + }, + "InitializationBlock": { + "type": "object", + "properties": { + "file_path": { + "type": [ + "string", + "null" + ], + "x-java": { + "field": "filePath", + "type": "String" + } + }, + "comments": { + "type": [ + "array", + "null" + ], + "items": { + "oneOf": [ + { + "$ref": "#/$defs/Comment" + }, + { + "type": "null" + } + ] + }, + "x-java": { + "field": "comments", + "type": "List" + } + }, + "annotations": { + "type": [ + "array", + "null" + ], + "items": { + "type": [ + "string", + "null" + ] + }, + "x-java": { + "field": "annotations", + "type": "List" + } + }, + "thrown_exceptions": { + "type": [ + "array", + "null" + ], + "items": { + "type": [ + "string", + "null" + ] + }, + "x-java": { + "field": "thrownExceptions", + "type": "List" + } + }, + "code": { + "type": [ + "string", + "null" + ], + "x-java": { + "field": "code", + "type": "String" + } + }, + "start_line": { + "type": "integer", + "x-java": { + "field": "startLine", + "type": "int" + } + }, + "end_line": { + "type": "integer", + "x-java": { + "field": "endLine", + "type": "int" + } + }, + "is_static": { + "type": "boolean", + "x-java": { + "field": "isStatic", + "type": "boolean" + } + }, + "referenced_types": { + "type": [ + "array", + "null" + ], + "items": { + "type": [ + "string", + "null" + ] + }, + "x-java": { + "field": "referencedTypes", + "type": "List" + } + }, + "accessed_fields": { + "type": [ + "array", + "null" + ], + "items": { + "type": [ + "string", + "null" + ] + }, + "x-java": { + "field": "accessedFields", + "type": "List" + } + }, + "call_sites": { + "type": [ + "array", + "null" + ], + "items": { + "oneOf": [ + { + "$ref": "#/$defs/CallSite" + }, + { + "type": "null" + } + ] + }, + "x-java": { + "field": "callSites", + "type": "List" + } + }, + "variable_declarations": { + "type": [ + "array", + "null" + ], + "items": { + "oneOf": [ + { + "$ref": "#/$defs/VariableDeclaration" + }, + { + "type": "null" + } + ] + }, + "x-java": { + "field": "variableDeclarations", + "type": "List" + } + }, + "cyclomatic_complexity": { + "type": "integer", + "x-java": { + "field": "cyclomaticComplexity", + "type": "int" + } + } + }, + "additionalProperties": false + }, + "JPAQueryMethod": { + "type": "string", + "enum": [ + "GET_SINGLE_RESULT", + "GET_FIRST_RESULT", + "GET_MAX_RESULTS", + "GET_HINTS", + "GET_LOCK_MODE", + "GET_PARAMETER", + "GET_PARAMETERS", + "GET_PARAMETER_VALUE", + "IS_BOUND", + "UNWRAP" + ] + }, + "JavaCompilationUnit": { + "type": "object", + "properties": { + "file_path": { + "type": [ + "string", + "null" + ], + "x-java": { + "field": "filePath", + "type": "String" + } + }, + "package_name": { + "type": [ + "string", + "null" + ], + "x-java": { + "field": "packageName", + "type": "String" + } + }, + "comments": { + "type": [ + "array", + "null" + ], + "items": { + "oneOf": [ + { + "$ref": "#/$defs/Comment" + }, + { + "type": "null" + } + ] + }, + "x-java": { + "field": "comments", + "type": "List" + } + }, + "imports": { + "type": [ + "array", + "null" + ], + "items": { + "oneOf": [ + { + "$ref": "#/$defs/Import" + }, + { + "type": "null" + } + ] + }, + "x-java": { + "field": "imports", + "type": "List" + } + }, + "type_declarations": { + "type": [ + "object", + "null" + ], + "additionalProperties": { + "oneOf": [ + { + "$ref": "#/$defs/Type" + }, + { + "type": "null" + } + ] + }, + "x-java": { + "field": "typeDeclarations", + "type": "Map" + } + }, + "is_modified": { + "type": "boolean", + "x-java": { + "field": "isModified", + "type": "boolean" + } + } + }, + "additionalProperties": false + }, + "ParameterInCallable": { + "type": "object", + "properties": { + "type": { + "type": [ + "string", + "null" + ], + "description": "The type of the parameter (e.g., int, String).", + "x-java": { + "field": "type", + "type": "String" + } + }, + "name": { + "type": [ + "string", + "null" + ], + "description": "The name of the parameter.", + "x-java": { + "field": "name", + "type": "String" + } + }, + "annotations": { + "type": [ + "array", + "null" + ], + "items": { + "type": [ + "string", + "null" + ] + }, + "description": "A list of annotations applied to the parameter.", + "x-java": { + "field": "annotations", + "type": "List" + } + }, + "modifiers": { + "type": [ + "array", + "null" + ], + "items": { + "type": [ + "string", + "null" + ] + }, + "description": "A list of modifiers applied to the parameter (e.g., final, static).", + "x-java": { + "field": "modifiers", + "type": "List" + } + }, + "start_line": { + "type": "integer", + "description": "The starting line number of the parameter in the source file.", + "x-java": { + "field": "startLine", + "type": "int" + } + }, + "end_line": { + "type": "integer", + "description": "The ending line number of the parameter in the source file.", + "x-java": { + "field": "endLine", + "type": "int" + } + }, + "start_column": { + "type": "integer", + "description": "The starting column number of the parameter in the source file.", + "x-java": { + "field": "startColumn", + "type": "int" + } + }, + "end_column": { + "type": "integer", + "description": "The ending column number of the parameter in the source file.", + "x-java": { + "field": "endColumn", + "type": "int" + } + } + }, + "additionalProperties": false + }, + "RecordComponent": { + "type": "object", + "properties": { + "comment": { + "oneOf": [ + { + "$ref": "#/$defs/Comment" + }, + { + "type": "null" + } + ], + "description": "The comment associated with the record component.", + "x-java": { + "field": "comment", + "type": "Comment" + } + }, + "name": { + "type": [ + "string", + "null" + ], + "description": "The name of the record component.", + "x-java": { + "field": "name", + "type": "String" + } + }, + "type": { + "type": [ + "string", + "null" + ], + "description": "The type of the record component.", + "x-java": { + "field": "type", + "type": "String" + } + }, + "modifiers": { + "type": [ + "array", + "null" + ], + "items": { + "type": [ + "string", + "null" + ] + }, + "description": "A list of modifiers applied to the record component (e.g., final, static).", + "x-java": { + "field": "modifiers", + "type": "List" + } + }, + "annotations": { + "type": [ + "array", + "null" + ], + "items": { + "type": [ + "string", + "null" + ] + }, + "description": "A list of annotations applied to the record component.", + "x-java": { + "field": "annotations", + "type": "List" + } + }, + "default_value": { + "description": "The default value of the record component, stored as a string representation.", + "x-java": { + "field": "defaultValue", + "type": "Object" + } + }, + "is_var_args": { + "type": "boolean", + "description": "Indicates whether the record component is a varargs parameter.", + "x-java": { + "field": "isVarArgs", + "type": "boolean" + } + } + }, + "additionalProperties": false + }, + "SystemDepEdge": { + "type": "object", + "properties": { + "context": { + "type": [ + "string", + "null" + ], + "description": "The Context. -- GETTER -- Gets context. @return the context", + "x-java": { + "field": "context", + "type": "String" + } + }, + "weight": { + "type": [ + "integer", + "null" + ], + "description": "The Weight. -- GETTER -- Gets weight. @return the weight", + "x-java": { + "field": "weight", + "type": "Integer" + } + }, + "source_pos": { + "type": [ + "integer", + "null" + ], + "description": "The Source pos.", + "x-java": { + "field": "sourcePos", + "type": "Integer" + } + }, + "destination_pos": { + "type": [ + "integer", + "null" + ], + "description": "The Destination pos.", + "x-java": { + "field": "destinationPos", + "type": "Integer" + } + }, + "type": { + "type": [ + "string", + "null" + ], + "description": "The Type.", + "x-java": { + "field": "type", + "type": "String" + } + }, + "source_kind": { + "type": [ + "string", + "null" + ], + "x-java": { + "field": "sourceKind", + "type": "String" + } + }, + "destination_kind": { + "type": [ + "string", + "null" + ], + "x-java": { + "field": "destinationKind", + "type": "String" + } + } + }, + "additionalProperties": false, + "x-java-extends": "AbstractGraphEdge" + }, + "Type": { + "type": "object", + "properties": { + "is_nested_type": { + "type": "boolean", + "description": "Indicates if this type is nested.", + "x-java": { + "field": "isNestedType", + "type": "boolean" + } + }, + "is_class_or_interface_declaration": { + "type": "boolean", + "description": "Indicates if this type is a class or interface declaration.", + "x-java": { + "field": "isClassOrInterfaceDeclaration", + "type": "boolean" + } + }, + "is_enum_declaration": { + "type": "boolean", + "description": "Indicates if this type is an enum declaration.", + "x-java": { + "field": "isEnumDeclaration", + "type": "boolean" + } + }, + "is_annotation_declaration": { + "type": "boolean", + "description": "Indicates if this type is an annotation declaration.", + "x-java": { + "field": "isAnnotationDeclaration", + "type": "boolean" + } + }, + "is_record_declaration": { + "type": "boolean", + "description": "Indicates if this type is a record declaration.", + "x-java": { + "field": "isRecordDeclaration", + "type": "boolean" + } + }, + "is_interface": { + "type": "boolean", + "description": "Indicates if this type is an interface.", + "x-java": { + "field": "isInterface", + "type": "boolean" + } + }, + "is_inner_class": { + "type": "boolean", + "description": "Indicates if this type is an inner class.", + "x-java": { + "field": "isInnerClass", + "type": "boolean" + } + }, + "is_local_class": { + "type": "boolean", + "description": "Indicates if this type is a local class.", + "x-java": { + "field": "isLocalClass", + "type": "boolean" + } + }, + "extends_list": { + "type": [ + "array", + "null" + ], + "items": { + "type": [ + "string", + "null" + ] + }, + "description": "List of types that this type extends.", + "x-java": { + "field": "extendsList", + "type": "List" + } + }, + "comments": { + "type": [ + "array", + "null" + ], + "items": { + "oneOf": [ + { + "$ref": "#/$defs/Comment" + }, + { + "type": "null" + } + ] + }, + "description": "List of comments associated with this type.", + "x-java": { + "field": "comments", + "type": "List" + } + }, + "implements_list": { + "type": [ + "array", + "null" + ], + "items": { + "type": [ + "string", + "null" + ] + }, + "description": "List of interfaces that this type implements.", + "x-java": { + "field": "implementsList", + "type": "List" + } + }, + "modifiers": { + "type": [ + "array", + "null" + ], + "items": { + "type": [ + "string", + "null" + ] + }, + "description": "List of modifiers for this type.", + "x-java": { + "field": "modifiers", + "type": "List" + } + }, + "annotations": { + "type": [ + "array", + "null" + ], + "items": { + "type": [ + "string", + "null" + ] + }, + "description": "List of annotations for this type.", + "x-java": { + "field": "annotations", + "type": "List" + } + }, + "parent_type": { + "type": [ + "string", + "null" + ], + "description": "The parent type of this type.", + "x-java": { + "field": "parentType", + "type": "String" + } + }, + "nested_type_declarations": { + "type": [ + "array", + "null" + ], + "items": { + "type": [ + "string", + "null" + ] + }, + "description": "List of nested type declarations within this type.", + "x-java": { + "field": "nestedTypeDeclarations", + "type": "List" + } + }, + "callable_declarations": { + "type": [ + "object", + "null" + ], + "additionalProperties": { + "oneOf": [ + { + "$ref": "#/$defs/Callable" + }, + { + "type": "null" + } + ] + }, + "description": "Map of callable declarations within this type.", + "x-java": { + "field": "callableDeclarations", + "type": "Map" + } + }, + "field_declarations": { + "type": [ + "array", + "null" + ], + "items": { + "oneOf": [ + { + "$ref": "#/$defs/Field" + }, + { + "type": "null" + } + ] + }, + "description": "List of field declarations within this type.", + "x-java": { + "field": "fieldDeclarations", + "type": "List" + } + }, + "enum_constants": { + "type": [ + "array", + "null" + ], + "items": { + "oneOf": [ + { + "$ref": "#/$defs/EnumConstant" + }, + { + "type": "null" + } + ] + }, + "description": "List of enum constants within this type.", + "x-java": { + "field": "enumConstants", + "type": "List" + } + }, + "record_components": { + "type": [ + "array", + "null" + ], + "items": { + "oneOf": [ + { + "$ref": "#/$defs/RecordComponent" + }, + { + "type": "null" + } + ] + }, + "description": "List of record components within this type.", + "x-java": { + "field": "recordComponents", + "type": "List" + } + }, + "initialization_blocks": { + "type": [ + "array", + "null" + ], + "items": { + "oneOf": [ + { + "$ref": "#/$defs/InitializationBlock" + }, + { + "type": "null" + } + ] + }, + "description": "List of initialization blocks within this type.", + "x-java": { + "field": "initializationBlocks", + "type": "List" + } + }, + "is_entrypoint_class": { + "type": "boolean", + "description": "Indicates if this type is an entry point class.", + "x-java": { + "field": "isEntrypointClass", + "type": "boolean" + } + } + }, + "additionalProperties": false + }, + "VariableDeclaration": { + "type": "object", + "properties": { + "comment": { + "oneOf": [ + { + "$ref": "#/$defs/Comment" + }, + { + "type": "null" + } + ], + "description": "The comment associated with the variable declaration.", + "x-java": { + "field": "comment", + "type": "Comment" + } + }, + "name": { + "type": [ + "string", + "null" + ], + "description": "The name of the variable.", + "x-java": { + "field": "name", + "type": "String" + } + }, + "type": { + "type": [ + "string", + "null" + ], + "description": "The type of the variable.", + "x-java": { + "field": "type", + "type": "String" + } + }, + "initializer": { + "type": [ + "string", + "null" + ], + "description": "The initializer of the variable, stored as a string representation.", + "x-java": { + "field": "initializer", + "type": "String" + } + }, + "start_line": { + "type": "integer", + "description": "The starting line number of the variable declaration in the source file.", + "x-java": { + "field": "startLine", + "type": "int" + } + }, + "start_column": { + "type": "integer", + "description": "The starting column number of the variable declaration in the source file.", + "x-java": { + "field": "startColumn", + "type": "int" + } + }, + "end_line": { + "type": "integer", + "description": "The ending line number of the variable declaration in the source file.", + "x-java": { + "field": "endLine", + "type": "int" + } + }, + "end_column": { + "type": "integer", + "description": "The ending column number of the variable declaration in the source file.", + "x-java": { + "field": "endColumn", + "type": "int" + } + } + }, + "additionalProperties": false + } + } +} diff --git a/v1/python/analysis.schema.json b/v1/python/analysis.schema.json new file mode 100644 index 0000000..c431de7 --- /dev/null +++ b/v1/python/analysis.schema.json @@ -0,0 +1,994 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://codellm-devkit.github.io/schema/v1/python/analysis.schema.json", + "title": "codeanalyzer-python v1 analysis output", + "description": "Represents a Python application.", + "x-cldk": { + "schemaVersion": 1, + "language": "python", + "source": { + "repo": "codellm-devkit/codeanalyzer-python", + "release": "v0.3.2", + "definedIn": "codeanalyzer/schema/py_schema.py (PyApplication)" + }, + "consumers": [ + { + "repo": "codellm-devkit/python-sdk", + "release": "v1.5.x" + } + ] + }, + "$defs": { + "PyAnalyzerInfo": { + "description": "Which analyzer produced this snapshot, and how it was configured.", + "properties": { + "name": { + "title": "Name", + "type": "string" + }, + "version": { + "title": "Version", + "type": "string" + }, + "config": { + "additionalProperties": true, + "default": {}, + "title": "Config", + "type": "object" + } + }, + "required": [ + "name", + "version" + ], + "title": "PyAnalyzerInfo", + "type": "object" + }, + "PyCallArgument": { + "description": "One call-site argument: AST category + inferred type, kept separate.\n\nThe legacy ``PyCallsite.argument_types`` mixed these two vocabularies\nin one list; this model is the disambiguated replacement (#86).", + "properties": { + "ast_kind": { + "title": "Ast Kind", + "type": "string" + }, + "inferred_type": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Inferred Type" + } + }, + "required": [ + "ast_kind" + ], + "title": "PyCallArgument", + "type": "object" + }, + "PyCallEdge": { + "description": "Identity-only call-graph edge with weight.\n\nMirrors Java's ``CallDependency``. ``source`` and ``target`` are\n``PyCallable.signature`` strings \u2014 nodes of the graph are the existing\n``PyCallable`` entries in the symbol table, not a separate vertex type.\nRich per-call metadata (receiver, arguments, location, ...) lives on\n``PyCallsite`` inside the source ``PyCallable.call_sites``.", + "properties": { + "source": { + "title": "Source", + "type": "string" + }, + "target": { + "title": "Target", + "type": "string" + }, + "type": { + "const": "CALL_DEP", + "default": "CALL_DEP", + "title": "Type", + "type": "string" + }, + "weight": { + "default": 1, + "title": "Weight", + "type": "integer" + }, + "provenance": { + "default": [], + "items": { + "enum": [ + "jedi", + "pycg", + "joern" + ], + "type": "string" + }, + "title": "Provenance", + "type": "array" + } + }, + "required": [ + "source", + "target" + ], + "title": "PyCallEdge", + "type": "object" + }, + "PyCallable": { + "description": "Represents a Python callable (function/method).", + "properties": { + "name": { + "title": "Name", + "type": "string" + }, + "path": { + "title": "Path", + "type": "string" + }, + "signature": { + "title": "Signature", + "type": "string" + }, + "comments": { + "default": [], + "items": { + "$ref": "#/$defs/PyComment" + }, + "title": "Comments", + "type": "array" + }, + "decorators": { + "default": [], + "items": { + "type": "string" + }, + "title": "Decorators", + "type": "array" + }, + "parameters": { + "default": [], + "items": { + "$ref": "#/$defs/PyCallableParameter" + }, + "title": "Parameters", + "type": "array" + }, + "return_type": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Return Type" + }, + "code": { + "default": null, + "title": "Code", + "type": "string" + }, + "start_line": { + "default": -1, + "title": "Start Line", + "type": "integer" + }, + "end_line": { + "default": -1, + "title": "End Line", + "type": "integer" + }, + "code_start_line": { + "default": -1, + "title": "Code Start Line", + "type": "integer" + }, + "accessed_symbols": { + "default": [], + "items": { + "$ref": "#/$defs/PySymbol" + }, + "title": "Accessed Symbols", + "type": "array" + }, + "call_sites": { + "default": [], + "items": { + "$ref": "#/$defs/PyCallsite" + }, + "title": "Call Sites", + "type": "array" + }, + "inner_callables": { + "additionalProperties": { + "$ref": "#/$defs/PyCallable" + }, + "default": {}, + "title": "Inner Callables", + "type": "object" + }, + "inner_classes": { + "additionalProperties": { + "$ref": "#/$defs/PyClass" + }, + "default": {}, + "title": "Inner Classes", + "type": "object" + }, + "local_variables": { + "default": [], + "items": { + "$ref": "#/$defs/PyVariableDeclaration" + }, + "title": "Local Variables", + "type": "array" + }, + "cyclomatic_complexity": { + "default": 0, + "title": "Cyclomatic Complexity", + "type": "integer" + } + }, + "required": [ + "name", + "path", + "signature" + ], + "title": "PyCallable", + "type": "object" + }, + "PyCallableParameter": { + "description": "Represents a parameter of a Python callable (function/method).", + "properties": { + "name": { + "title": "Name", + "type": "string" + }, + "type": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Type" + }, + "default_value": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Default Value" + }, + "start_line": { + "default": -1, + "title": "Start Line", + "type": "integer" + }, + "end_line": { + "default": -1, + "title": "End Line", + "type": "integer" + }, + "start_column": { + "default": -1, + "title": "Start Column", + "type": "integer" + }, + "end_column": { + "default": -1, + "title": "End Column", + "type": "integer" + } + }, + "required": [ + "name" + ], + "title": "PyCallableParameter", + "type": "object" + }, + "PyCallsite": { + "description": "Represents a Python call site (function or method invocation) with contextual metadata.", + "properties": { + "method_name": { + "title": "Method Name", + "type": "string" + }, + "receiver_expr": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Receiver Expr" + }, + "receiver_type": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Receiver Type" + }, + "argument_types": { + "default": [], + "items": { + "type": "string" + }, + "title": "Argument Types", + "type": "array" + }, + "arguments": { + "default": [], + "items": { + "$ref": "#/$defs/PyCallArgument" + }, + "title": "Arguments", + "type": "array" + }, + "return_type": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Return Type" + }, + "callee_signature": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Callee Signature" + }, + "is_constructor_call": { + "default": false, + "title": "Is Constructor Call", + "type": "boolean" + }, + "start_line": { + "default": -1, + "title": "Start Line", + "type": "integer" + }, + "start_column": { + "default": -1, + "title": "Start Column", + "type": "integer" + }, + "end_line": { + "default": -1, + "title": "End Line", + "type": "integer" + }, + "end_column": { + "default": -1, + "title": "End Column", + "type": "integer" + } + }, + "required": [ + "method_name" + ], + "title": "PyCallsite", + "type": "object" + }, + "PyClass": { + "description": "Represents a Python class.", + "properties": { + "name": { + "title": "Name", + "type": "string" + }, + "signature": { + "title": "Signature", + "type": "string" + }, + "comments": { + "default": [], + "items": { + "$ref": "#/$defs/PyComment" + }, + "title": "Comments", + "type": "array" + }, + "code": { + "default": null, + "title": "Code", + "type": "string" + }, + "base_classes": { + "default": [], + "items": { + "type": "string" + }, + "title": "Base Classes", + "type": "array" + }, + "methods": { + "additionalProperties": { + "$ref": "#/$defs/PyCallable" + }, + "default": {}, + "title": "Methods", + "type": "object" + }, + "attributes": { + "additionalProperties": { + "$ref": "#/$defs/PyClassAttribute" + }, + "default": {}, + "title": "Attributes", + "type": "object" + }, + "inner_classes": { + "additionalProperties": { + "$ref": "#/$defs/PyClass" + }, + "default": {}, + "title": "Inner Classes", + "type": "object" + }, + "start_line": { + "default": -1, + "title": "Start Line", + "type": "integer" + }, + "end_line": { + "default": -1, + "title": "End Line", + "type": "integer" + } + }, + "required": [ + "name", + "signature" + ], + "title": "PyClass", + "type": "object" + }, + "PyClassAttribute": { + "description": "Represents a Python class attribute.", + "properties": { + "name": { + "title": "Name", + "type": "string" + }, + "type": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Type" + }, + "initializer": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Initializer" + }, + "comments": { + "default": [], + "items": { + "$ref": "#/$defs/PyComment" + }, + "title": "Comments", + "type": "array" + }, + "start_line": { + "default": -1, + "title": "Start Line", + "type": "integer" + }, + "end_line": { + "default": -1, + "title": "End Line", + "type": "integer" + } + }, + "required": [ + "name" + ], + "title": "PyClassAttribute", + "type": "object" + }, + "PyComment": { + "description": "Represents a Python comment.", + "properties": { + "content": { + "title": "Content", + "type": "string" + }, + "start_line": { + "default": -1, + "title": "Start Line", + "type": "integer" + }, + "end_line": { + "default": -1, + "title": "End Line", + "type": "integer" + }, + "start_column": { + "default": -1, + "title": "Start Column", + "type": "integer" + }, + "end_column": { + "default": -1, + "title": "End Column", + "type": "integer" + }, + "is_docstring": { + "default": false, + "title": "Is Docstring", + "type": "boolean" + } + }, + "required": [ + "content" + ], + "title": "PyComment", + "type": "object" + }, + "PyExternalSymbol": { + "description": "A call-graph target outside the analyzed project -- an imported library or\nbuiltin member. Mirrors codeanalyzer-typescript's ``TSExternalSymbol`` and is\nkeyed in ``PyApplication.external_symbols`` by its call-graph signature.", + "properties": { + "name": { + "title": "Name", + "type": "string" + }, + "module": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Module" + } + }, + "required": [ + "name" + ], + "title": "PyExternalSymbol", + "type": "object" + }, + "PyImport": { + "description": "Represents a Python import statement.", + "properties": { + "module": { + "title": "Module", + "type": "string" + }, + "name": { + "title": "Name", + "type": "string" + }, + "alias": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Alias" + }, + "resolved_module": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Resolved Module" + }, + "start_line": { + "default": -1, + "title": "Start Line", + "type": "integer" + }, + "end_line": { + "default": -1, + "title": "End Line", + "type": "integer" + }, + "start_column": { + "default": -1, + "title": "Start Column", + "type": "integer" + }, + "end_column": { + "default": -1, + "title": "End Column", + "type": "integer" + } + }, + "required": [ + "module", + "name" + ], + "title": "PyImport", + "type": "object" + }, + "PyModule": { + "description": "Represents a Python module.", + "properties": { + "file_path": { + "title": "File Path", + "type": "string" + }, + "module_name": { + "title": "Module Name", + "type": "string" + }, + "imports": { + "default": [], + "items": { + "$ref": "#/$defs/PyImport" + }, + "title": "Imports", + "type": "array" + }, + "comments": { + "default": [], + "items": { + "$ref": "#/$defs/PyComment" + }, + "title": "Comments", + "type": "array" + }, + "classes": { + "additionalProperties": { + "$ref": "#/$defs/PyClass" + }, + "default": {}, + "title": "Classes", + "type": "object" + }, + "functions": { + "additionalProperties": { + "$ref": "#/$defs/PyCallable" + }, + "default": {}, + "title": "Functions", + "type": "object" + }, + "variables": { + "default": [], + "items": { + "$ref": "#/$defs/PyVariableDeclaration" + }, + "title": "Variables", + "type": "array" + }, + "content_hash": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Content Hash" + }, + "last_modified": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Last Modified" + }, + "file_size": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "default": null, + "title": "File Size" + } + }, + "required": [ + "file_path", + "module_name" + ], + "title": "PyModule", + "type": "object" + }, + "PyRepositoryInfo": { + "description": "Where the analyzed source came from: git provenance captured at analysis time.", + "properties": { + "uri": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Uri" + }, + "revision": { + "title": "Revision", + "type": "string" + }, + "dirty": { + "default": false, + "title": "Dirty", + "type": "boolean" + } + }, + "required": [ + "revision" + ], + "title": "PyRepositoryInfo", + "type": "object" + }, + "PySymbol": { + "description": "Represents a symbol used or declared in Python code.", + "properties": { + "name": { + "title": "Name", + "type": "string" + }, + "scope": { + "enum": [ + "local", + "nonlocal", + "global", + "class", + "module" + ], + "title": "Scope", + "type": "string" + }, + "kind": { + "enum": [ + "variable", + "parameter", + "attribute", + "function", + "class", + "module" + ], + "title": "Kind", + "type": "string" + }, + "type": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Type" + }, + "qualified_name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Qualified Name" + }, + "is_builtin": { + "default": false, + "title": "Is Builtin", + "type": "boolean" + }, + "lineno": { + "default": -1, + "title": "Lineno", + "type": "integer" + }, + "col_offset": { + "default": -1, + "title": "Col Offset", + "type": "integer" + } + }, + "required": [ + "name", + "scope", + "kind" + ], + "title": "PySymbol", + "type": "object" + }, + "PyVariableDeclaration": { + "description": "Represents a Python variable declaration.", + "properties": { + "name": { + "title": "Name", + "type": "string" + }, + "type": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Type" + }, + "initializer": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Initializer" + }, + "value": { + "anyOf": [ + {}, + { + "type": "null" + } + ], + "default": null, + "title": "Value" + }, + "scope": { + "default": "module", + "enum": [ + "module", + "class", + "function" + ], + "title": "Scope", + "type": "string" + }, + "start_line": { + "default": -1, + "title": "Start Line", + "type": "integer" + }, + "end_line": { + "default": -1, + "title": "End Line", + "type": "integer" + }, + "start_column": { + "default": -1, + "title": "Start Column", + "type": "integer" + }, + "end_column": { + "default": -1, + "title": "End Column", + "type": "integer" + } + }, + "required": [ + "name", + "type" + ], + "title": "PyVariableDeclaration", + "type": "object" + } + }, + "properties": { + "symbol_table": { + "additionalProperties": { + "$ref": "#/$defs/PyModule" + }, + "title": "Symbol Table", + "type": "object" + }, + "call_graph": { + "default": [], + "items": { + "$ref": "#/$defs/PyCallEdge" + }, + "title": "Call Graph", + "type": "array" + }, + "external_symbols": { + "additionalProperties": { + "$ref": "#/$defs/PyExternalSymbol" + }, + "default": {}, + "title": "External Symbols", + "type": "object" + }, + "analyzer": { + "anyOf": [ + { + "$ref": "#/$defs/PyAnalyzerInfo" + }, + { + "type": "null" + } + ], + "default": null + }, + "repository": { + "anyOf": [ + { + "$ref": "#/$defs/PyRepositoryInfo" + }, + { + "type": "null" + } + ], + "default": null + } + }, + "required": [ + "symbol_table" + ], + "type": "object" +} diff --git a/v1/typescript/analysis.schema.json b/v1/typescript/analysis.schema.json new file mode 100644 index 0000000..ae2b9ce --- /dev/null +++ b/v1/typescript/analysis.schema.json @@ -0,0 +1,1502 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://codellm-devkit.github.io/schema/v1/typescript/analysis.schema.json", + "title": "codeanalyzer-typescript v1 analysis output", + "description": "Generated from the TypeScript interfaces of codeanalyzer-typescript v0.4.3 \u2014 the last release before src/schema/v2 landed, and the release python-sdk pins.", + "x-cldk": { + "schemaVersion": 1, + "language": "typescript", + "source": { + "repo": "codellm-devkit/codeanalyzer-typescript", + "release": "v0.4.3", + "definedIn": "src/schema/schema.ts (TSApplication)" + }, + "consumers": [ + { + "repo": "codellm-devkit/python-sdk", + "release": "v1.5.x" + } + ] + }, + "$defs": { + "TSCallEdge": { + "additionalProperties": false, + "properties": { + "provenance": { + "items": { + "type": "string" + }, + "type": "array" + }, + "source": { + "type": "string" + }, + "tags": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "target": { + "type": "string" + }, + "type": { + "const": "CALL_DEP", + "type": "string" + }, + "weight": { + "type": "number" + } + }, + "required": [ + "source", + "target", + "type", + "weight", + "provenance", + "tags" + ], + "type": "object" + }, + "TSCallable": { + "additionalProperties": false, + "properties": { + "accessed_symbols": { + "items": { + "$ref": "#/$defs/TSSymbol" + }, + "type": "array" + }, + "accessibility": { + "type": [ + "string", + "null" + ] + }, + "accessor_kind": { + "type": [ + "string", + "null" + ] + }, + "call_sites": { + "items": { + "$ref": "#/$defs/TSCallsite" + }, + "type": "array" + }, + "code": { + "type": [ + "string", + "null" + ] + }, + "code_start_line": { + "type": "number" + }, + "comments": { + "items": { + "$ref": "#/$defs/TSComment" + }, + "type": "array" + }, + "cyclomatic_complexity": { + "type": "number" + }, + "decorators": { + "items": { + "$ref": "#/$defs/TSDecorator" + }, + "type": "array" + }, + "end_line": { + "type": "number" + }, + "entrypoints": { + "items": { + "$ref": "#/$defs/TSEntrypoint" + }, + "type": "array" + }, + "inner_callables": { + "additionalProperties": { + "$ref": "#/$defs/TSCallable" + }, + "type": "object" + }, + "inner_classes": { + "additionalProperties": { + "$ref": "#/$defs/TSClass" + }, + "type": "object" + }, + "is_abstract": { + "type": "boolean" + }, + "is_ambient": { + "type": "boolean" + }, + "is_async": { + "type": "boolean" + }, + "is_exported": { + "type": "boolean" + }, + "is_generator": { + "type": "boolean" + }, + "is_implicit": { + "type": "boolean" + }, + "is_optional": { + "type": "boolean" + }, + "is_readonly": { + "type": "boolean" + }, + "is_static": { + "type": "boolean" + }, + "kind": { + "$ref": "#/$defs/TSCallableKind" + }, + "local_variables": { + "items": { + "$ref": "#/$defs/TSVariableDeclaration" + }, + "type": "array" + }, + "name": { + "type": "string" + }, + "overload_signatures": { + "items": { + "$ref": "#/$defs/TSOverloadSignature" + }, + "type": "array" + }, + "parameters": { + "items": { + "$ref": "#/$defs/TSCallableParameter" + }, + "type": "array" + }, + "path": { + "type": "string" + }, + "return_type": { + "type": [ + "string", + "null" + ] + }, + "signature": { + "type": "string" + }, + "start_line": { + "type": "number" + }, + "type_parameters": { + "items": { + "$ref": "#/$defs/TSTypeParameter" + }, + "type": "array" + } + }, + "required": [ + "name", + "path", + "signature", + "comments", + "decorators", + "parameters", + "type_parameters", + "return_type", + "code", + "start_line", + "end_line", + "code_start_line", + "accessed_symbols", + "call_sites", + "inner_callables", + "inner_classes", + "local_variables", + "cyclomatic_complexity", + "entrypoints", + "kind", + "accessibility", + "is_static", + "is_abstract", + "is_async", + "is_generator", + "is_optional", + "is_readonly", + "is_exported", + "is_ambient", + "is_implicit", + "accessor_kind", + "overload_signatures" + ], + "type": "object" + }, + "TSCallableKind": { + "enum": [ + "function", + "method", + "constructor", + "getter", + "setter", + "arrow", + "function_expression" + ], + "type": "string" + }, + "TSCallableParameter": { + "additionalProperties": false, + "properties": { + "accessibility": { + "type": [ + "string", + "null" + ] + }, + "decorators": { + "items": { + "$ref": "#/$defs/TSDecorator" + }, + "type": "array" + }, + "default_value": { + "type": [ + "string", + "null" + ] + }, + "end_column": { + "type": "number" + }, + "end_line": { + "type": "number" + }, + "is_optional": { + "type": "boolean" + }, + "is_readonly": { + "type": "boolean" + }, + "is_rest": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "start_column": { + "type": "number" + }, + "start_line": { + "type": "number" + }, + "type": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "name", + "type", + "default_value", + "is_optional", + "is_rest", + "is_readonly", + "accessibility", + "decorators", + "start_line", + "end_line", + "start_column", + "end_column" + ], + "type": "object" + }, + "TSCallsite": { + "additionalProperties": false, + "properties": { + "argument_types": { + "items": { + "type": "string" + }, + "type": "array" + }, + "callee_signature": { + "type": [ + "string", + "null" + ] + }, + "end_column": { + "type": "number" + }, + "end_line": { + "type": "number" + }, + "is_constructor_call": { + "type": "boolean" + }, + "is_optional_chain": { + "type": "boolean" + }, + "method_name": { + "type": "string" + }, + "receiver_expr": { + "type": [ + "string", + "null" + ] + }, + "receiver_type": { + "type": [ + "string", + "null" + ] + }, + "return_type": { + "type": [ + "string", + "null" + ] + }, + "start_column": { + "type": "number" + }, + "start_line": { + "type": "number" + }, + "type_arguments": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "method_name", + "receiver_expr", + "receiver_type", + "argument_types", + "type_arguments", + "return_type", + "callee_signature", + "is_constructor_call", + "is_optional_chain", + "start_line", + "start_column", + "end_line", + "end_column" + ], + "type": "object" + }, + "TSClass": { + "additionalProperties": false, + "properties": { + "attributes": { + "additionalProperties": { + "$ref": "#/$defs/TSClassAttribute" + }, + "type": "object" + }, + "base_classes": { + "items": { + "type": "string" + }, + "type": "array" + }, + "code": { + "type": [ + "string", + "null" + ] + }, + "comments": { + "items": { + "$ref": "#/$defs/TSComment" + }, + "type": "array" + }, + "decorators": { + "items": { + "$ref": "#/$defs/TSDecorator" + }, + "type": "array" + }, + "end_line": { + "type": "number" + }, + "entrypoints": { + "items": { + "$ref": "#/$defs/TSEntrypoint" + }, + "type": "array" + }, + "implements_types": { + "items": { + "type": "string" + }, + "type": "array" + }, + "inner_classes": { + "additionalProperties": { + "$ref": "#/$defs/TSClass" + }, + "type": "object" + }, + "is_abstract": { + "type": "boolean" + }, + "is_ambient": { + "type": "boolean" + }, + "is_exported": { + "type": "boolean" + }, + "methods": { + "additionalProperties": { + "$ref": "#/$defs/TSCallable" + }, + "type": "object" + }, + "name": { + "type": "string" + }, + "signature": { + "type": "string" + }, + "start_line": { + "type": "number" + }, + "type_parameters": { + "items": { + "$ref": "#/$defs/TSTypeParameter" + }, + "type": "array" + } + }, + "required": [ + "name", + "signature", + "comments", + "code", + "decorators", + "base_classes", + "implements_types", + "type_parameters", + "methods", + "attributes", + "inner_classes", + "entrypoints", + "is_abstract", + "is_exported", + "is_ambient", + "start_line", + "end_line" + ], + "type": "object" + }, + "TSClassAttribute": { + "additionalProperties": false, + "properties": { + "accessibility": { + "type": [ + "string", + "null" + ] + }, + "comments": { + "items": { + "$ref": "#/$defs/TSComment" + }, + "type": "array" + }, + "decorators": { + "items": { + "$ref": "#/$defs/TSDecorator" + }, + "type": "array" + }, + "end_line": { + "type": "number" + }, + "initializer": { + "type": [ + "string", + "null" + ] + }, + "is_abstract": { + "type": "boolean" + }, + "is_optional": { + "type": "boolean" + }, + "is_readonly": { + "type": "boolean" + }, + "is_static": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "start_line": { + "type": "number" + }, + "type": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "name", + "type", + "comments", + "decorators", + "initializer", + "accessibility", + "is_static", + "is_readonly", + "is_optional", + "is_abstract", + "start_line", + "end_line" + ], + "type": "object" + }, + "TSComment": { + "additionalProperties": false, + "properties": { + "content": { + "type": "string" + }, + "end_column": { + "type": "number" + }, + "end_line": { + "type": "number" + }, + "is_docstring": { + "type": "boolean" + }, + "start_column": { + "type": "number" + }, + "start_line": { + "type": "number" + } + }, + "required": [ + "content", + "is_docstring", + "start_line", + "end_line", + "start_column", + "end_column" + ], + "type": "object" + }, + "TSDecorator": { + "additionalProperties": false, + "properties": { + "end_column": { + "type": "number" + }, + "end_line": { + "type": "number" + }, + "keyword_arguments": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "name": { + "type": "string" + }, + "positional_arguments": { + "items": { + "type": "string" + }, + "type": "array" + }, + "qualified_name": { + "type": [ + "string", + "null" + ] + }, + "start_column": { + "type": "number" + }, + "start_line": { + "type": "number" + } + }, + "required": [ + "name", + "qualified_name", + "positional_arguments", + "keyword_arguments", + "start_line", + "end_line", + "start_column", + "end_column" + ], + "type": "object" + }, + "TSEntrypoint": { + "additionalProperties": false, + "properties": { + "detection_source": { + "type": "string" + }, + "framework": { + "type": "string" + }, + "http_methods": { + "items": { + "type": "string" + }, + "type": "array" + }, + "route_path": { + "type": [ + "string", + "null" + ] + }, + "tags": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + } + }, + "required": [ + "framework", + "detection_source", + "route_path", + "http_methods", + "tags" + ], + "type": "object" + }, + "TSEnum": { + "additionalProperties": false, + "properties": { + "code": { + "type": [ + "string", + "null" + ] + }, + "comments": { + "items": { + "$ref": "#/$defs/TSComment" + }, + "type": "array" + }, + "end_line": { + "type": "number" + }, + "is_ambient": { + "type": "boolean" + }, + "is_const": { + "type": "boolean" + }, + "is_exported": { + "type": "boolean" + }, + "members": { + "items": { + "$ref": "#/$defs/TSEnumMember" + }, + "type": "array" + }, + "name": { + "type": "string" + }, + "signature": { + "type": "string" + }, + "start_line": { + "type": "number" + } + }, + "required": [ + "name", + "signature", + "comments", + "code", + "members", + "is_const", + "is_exported", + "is_ambient", + "start_line", + "end_line" + ], + "type": "object" + }, + "TSEnumMember": { + "additionalProperties": false, + "properties": { + "end_line": { + "type": "number" + }, + "name": { + "type": "string" + }, + "start_line": { + "type": "number" + }, + "value": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "name", + "value", + "start_line", + "end_line" + ], + "type": "object" + }, + "TSExport": { + "additionalProperties": false, + "properties": { + "alias": { + "type": [ + "string", + "null" + ] + }, + "end_column": { + "type": "number" + }, + "end_line": { + "type": "number" + }, + "export_kind": { + "enum": [ + "named", + "default", + "namespace", + "re_export" + ], + "type": "string" + }, + "is_type_only": { + "type": "boolean" + }, + "module": { + "type": [ + "string", + "null" + ] + }, + "name": { + "type": "string" + }, + "start_column": { + "type": "number" + }, + "start_line": { + "type": "number" + } + }, + "required": [ + "module", + "name", + "alias", + "is_type_only", + "export_kind", + "start_line", + "end_line", + "start_column", + "end_column" + ], + "type": "object" + }, + "TSExternalSymbol": { + "additionalProperties": false, + "properties": { + "module": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "name", + "module" + ], + "type": "object" + }, + "TSImport": { + "additionalProperties": false, + "description": "The canonical CLDK analysis schema for TypeScript.\n\nMirrors the identity-only Python schema (codeanalyzer-python/.../py_schema.py) field for field on the invariant spine \u2014 `TSApplication { symbol_table, call_graph, entrypoints }`, `Module \u2192 Class/Callable` nesting, identity-only `TSCallEdge` whose `source`/`target` are bare signature strings \u2014 and extends it at the leaves with TypeScript-native node kinds (interface / type-alias / enum / namespace) and typed fields (generics, modifiers, ...). See SCHEMA_DECISIONS.md.\n\nAll field names are snake_case so `JSON.stringify` emits keys the SDK Pydantic models parse. The matching Pydantic models live in python-sdk/cldk/models/typescript/models.py and MUST be co-evolved with this file.", + "properties": { + "alias": { + "type": [ + "string", + "null" + ] + }, + "end_column": { + "type": "number" + }, + "end_line": { + "type": "number" + }, + "import_kind": { + "enum": [ + "named", + "default", + "namespace", + "side_effect" + ], + "type": "string" + }, + "is_type_only": { + "type": "boolean" + }, + "module": { + "type": "string" + }, + "name": { + "type": "string" + }, + "start_column": { + "type": "number" + }, + "start_line": { + "type": "number" + } + }, + "required": [ + "module", + "name", + "alias", + "is_type_only", + "import_kind", + "start_line", + "end_line", + "start_column", + "end_column" + ], + "type": "object" + }, + "TSInterface": { + "additionalProperties": false, + "properties": { + "base_classes": { + "items": { + "type": "string" + }, + "type": "array" + }, + "call_signatures": { + "items": { + "type": "string" + }, + "type": "array" + }, + "code": { + "type": [ + "string", + "null" + ] + }, + "comments": { + "items": { + "$ref": "#/$defs/TSComment" + }, + "type": "array" + }, + "end_line": { + "type": "number" + }, + "index_signatures": { + "items": { + "type": "string" + }, + "type": "array" + }, + "is_ambient": { + "type": "boolean" + }, + "is_exported": { + "type": "boolean" + }, + "methods": { + "additionalProperties": { + "$ref": "#/$defs/TSCallable" + }, + "type": "object" + }, + "name": { + "type": "string" + }, + "properties": { + "additionalProperties": { + "$ref": "#/$defs/TSClassAttribute" + }, + "type": "object" + }, + "signature": { + "type": "string" + }, + "start_line": { + "type": "number" + }, + "type_parameters": { + "items": { + "$ref": "#/$defs/TSTypeParameter" + }, + "type": "array" + } + }, + "required": [ + "name", + "signature", + "comments", + "code", + "base_classes", + "type_parameters", + "methods", + "properties", + "call_signatures", + "index_signatures", + "is_exported", + "is_ambient", + "start_line", + "end_line" + ], + "type": "object" + }, + "TSModule": { + "additionalProperties": false, + "properties": { + "classes": { + "additionalProperties": { + "$ref": "#/$defs/TSClass" + }, + "type": "object" + }, + "comments": { + "items": { + "$ref": "#/$defs/TSComment" + }, + "type": "array" + }, + "content_hash": { + "type": [ + "string", + "null" + ] + }, + "enums": { + "additionalProperties": { + "$ref": "#/$defs/TSEnum" + }, + "type": "object" + }, + "exports": { + "items": { + "$ref": "#/$defs/TSExport" + }, + "type": "array" + }, + "file_path": { + "type": "string" + }, + "file_size": { + "type": [ + "number", + "null" + ] + }, + "functions": { + "additionalProperties": { + "$ref": "#/$defs/TSCallable" + }, + "type": "object" + }, + "imports": { + "items": { + "$ref": "#/$defs/TSImport" + }, + "type": "array" + }, + "interfaces": { + "additionalProperties": { + "$ref": "#/$defs/TSInterface" + }, + "type": "object" + }, + "is_declaration_file": { + "type": "boolean" + }, + "is_tsx": { + "type": "boolean" + }, + "last_modified": { + "type": [ + "number", + "null" + ] + }, + "module_name": { + "type": "string" + }, + "namespaces": { + "additionalProperties": { + "$ref": "#/$defs/TSNamespace" + }, + "type": "object" + }, + "type_aliases": { + "additionalProperties": { + "$ref": "#/$defs/TSTypeAlias" + }, + "type": "object" + }, + "variables": { + "items": { + "$ref": "#/$defs/TSVariableDeclaration" + }, + "type": "array" + } + }, + "required": [ + "file_path", + "module_name", + "imports", + "exports", + "comments", + "classes", + "interfaces", + "enums", + "type_aliases", + "functions", + "namespaces", + "variables", + "is_tsx", + "is_declaration_file", + "content_hash", + "last_modified", + "file_size" + ], + "type": "object" + }, + "TSNamespace": { + "additionalProperties": false, + "properties": { + "classes": { + "additionalProperties": { + "$ref": "#/$defs/TSClass" + }, + "type": "object" + }, + "comments": { + "items": { + "$ref": "#/$defs/TSComment" + }, + "type": "array" + }, + "end_line": { + "type": "number" + }, + "enums": { + "additionalProperties": { + "$ref": "#/$defs/TSEnum" + }, + "type": "object" + }, + "functions": { + "additionalProperties": { + "$ref": "#/$defs/TSCallable" + }, + "type": "object" + }, + "interfaces": { + "additionalProperties": { + "$ref": "#/$defs/TSInterface" + }, + "type": "object" + }, + "is_ambient": { + "type": "boolean" + }, + "is_exported": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "namespaces": { + "additionalProperties": { + "$ref": "#/$defs/TSNamespace" + }, + "type": "object" + }, + "signature": { + "type": "string" + }, + "start_line": { + "type": "number" + }, + "type_aliases": { + "additionalProperties": { + "$ref": "#/$defs/TSTypeAlias" + }, + "type": "object" + }, + "variables": { + "items": { + "$ref": "#/$defs/TSVariableDeclaration" + }, + "type": "array" + } + }, + "required": [ + "name", + "signature", + "comments", + "classes", + "interfaces", + "enums", + "type_aliases", + "functions", + "variables", + "namespaces", + "is_exported", + "is_ambient", + "start_line", + "end_line" + ], + "type": "object" + }, + "TSOverloadSignature": { + "additionalProperties": false, + "properties": { + "end_line": { + "type": "number" + }, + "parameters": { + "items": { + "$ref": "#/$defs/TSCallableParameter" + }, + "type": "array" + }, + "return_type": { + "type": [ + "string", + "null" + ] + }, + "start_line": { + "type": "number" + }, + "type_parameters": { + "items": { + "$ref": "#/$defs/TSTypeParameter" + }, + "type": "array" + } + }, + "required": [ + "parameters", + "return_type", + "type_parameters", + "start_line", + "end_line" + ], + "type": "object" + }, + "TSSymbol": { + "additionalProperties": false, + "properties": { + "col_offset": { + "type": "number" + }, + "is_builtin": { + "type": "boolean" + }, + "kind": { + "type": "string" + }, + "lineno": { + "type": "number" + }, + "name": { + "type": "string" + }, + "qualified_name": { + "type": [ + "string", + "null" + ] + }, + "scope": { + "type": "string" + }, + "type": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "name", + "scope", + "kind", + "type", + "qualified_name", + "is_builtin", + "lineno", + "col_offset" + ], + "type": "object" + }, + "TSSynthesizedCallable": { + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "start_column": { + "type": "number" + }, + "start_line": { + "type": "number" + } + }, + "required": [ + "name", + "path", + "start_line", + "start_column" + ], + "type": "object" + }, + "TSTypeAlias": { + "additionalProperties": false, + "properties": { + "aliased_type": { + "type": "string" + }, + "code": { + "type": [ + "string", + "null" + ] + }, + "comments": { + "items": { + "$ref": "#/$defs/TSComment" + }, + "type": "array" + }, + "end_line": { + "type": "number" + }, + "is_ambient": { + "type": "boolean" + }, + "is_exported": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "signature": { + "type": "string" + }, + "start_line": { + "type": "number" + }, + "type_parameters": { + "items": { + "$ref": "#/$defs/TSTypeParameter" + }, + "type": "array" + } + }, + "required": [ + "name", + "signature", + "comments", + "code", + "aliased_type", + "type_parameters", + "is_exported", + "is_ambient", + "start_line", + "end_line" + ], + "type": "object" + }, + "TSTypeParameter": { + "additionalProperties": false, + "properties": { + "constraint": { + "type": [ + "string", + "null" + ] + }, + "default": { + "type": [ + "string", + "null" + ] + }, + "name": { + "type": "string" + } + }, + "required": [ + "name", + "constraint", + "default" + ], + "type": "object" + }, + "TSVariableDeclaration": { + "additionalProperties": false, + "properties": { + "declaration_kind": { + "enum": [ + "const", + "let", + "var", + "using", + "unknown" + ], + "type": "string" + }, + "end_column": { + "type": "number" + }, + "end_line": { + "type": "number" + }, + "initializer": { + "type": [ + "string", + "null" + ] + }, + "is_exported": { + "type": "boolean" + }, + "is_readonly": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "scope": { + "enum": [ + "module", + "namespace", + "class", + "function", + "block" + ], + "type": "string" + }, + "start_column": { + "type": "number" + }, + "start_line": { + "type": "number" + }, + "type": { + "type": [ + "string", + "null" + ] + }, + "value": { + "anyOf": [ + {}, + { + "type": "null" + } + ] + } + }, + "required": [ + "name", + "type", + "initializer", + "value", + "scope", + "declaration_kind", + "is_readonly", + "is_exported", + "start_line", + "end_line", + "start_column", + "end_column" + ], + "type": "object" + } + }, + "additionalProperties": false, + "properties": { + "call_graph": { + "items": { + "$ref": "#/$defs/TSCallEdge" + }, + "type": "array" + }, + "external_symbols": { + "additionalProperties": { + "$ref": "#/$defs/TSExternalSymbol" + }, + "type": "object" + }, + "symbol_table": { + "additionalProperties": { + "$ref": "#/$defs/TSModule" + }, + "type": "object" + }, + "synthesized_callables": { + "additionalProperties": { + "$ref": "#/$defs/TSSynthesizedCallable" + }, + "type": "object" + } + }, + "required": [ + "symbol_table", + "call_graph", + "external_symbols", + "synthesized_callables" + ], + "type": "object" +} diff --git a/v2/java/.gitkeep b/v2/java/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/v2/python/.gitkeep b/v2/python/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/v2/typescript/.gitkeep b/v2/typescript/.gitkeep new file mode 100644 index 0000000..e69de29 From 984c78a2c4b7dd5c86d1a073159bd7dc318713eb Mon Sep 17 00:00:00 2001 From: Rahul Krishna Date: Wed, 19 Aug 2026 14:33:03 -0400 Subject: [PATCH 02/16] feat: track the Neo4j graph contracts and add a meta-schema for them MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Each analyzer emits a schema.neo4j.json with `--emit schema`: the node labels, relationship types, constraints, and indexes that describe the graph it writes. That document is a contract about a graph, not a JSON Schema for analysis.json, and its schema_version runs on its own line — an analyzer can sit on analysis schema v1 while its graph contract is on 2.x. So it gets its own top-level directory rather than a slot under v1/. neo4j/contract.schema.json validates the shape of those documents. The three copies under neo4j// are byte-identical to the tagged release they came from, so a diff against the analyzer repo is the drift check: java v2.4.1 contract 1.0.0 16 node labels, 20 relationship types python v1.1.1 contract 2.0.0 11 node labels, 18 relationship types typescript v1.0.0 contract 2.0.0 12 node labels, 15 relationship types They are pinned to the latest release, not to main, which is ahead in two repos: codeanalyzer-java main carries contract 1.1.0 (23 relationship types) and codeanalyzer-typescript main carries 2.1.0. Repin once those ship. One divergence is recorded rather than smoothed over: Java and Python spell the merge label merge_label, TypeScript spells it mergeLabel. The meta-schema accepts either and says why. scripts/check.py now also validates whatever a schema claims to cover through x-cldk.validates, so the three contracts are checked on every run. --- neo4j/contract.schema.json | 129 +++++++ neo4j/java/schema.neo4j.json | 517 +++++++++++++++++++++++++++++ neo4j/python/schema.neo4j.json | 388 ++++++++++++++++++++++ neo4j/typescript/schema.neo4j.json | 401 ++++++++++++++++++++++ scripts/check.py | 14 +- 5 files changed, 1444 insertions(+), 5 deletions(-) create mode 100644 neo4j/contract.schema.json create mode 100644 neo4j/java/schema.neo4j.json create mode 100644 neo4j/python/schema.neo4j.json create mode 100644 neo4j/typescript/schema.neo4j.json diff --git a/neo4j/contract.schema.json b/neo4j/contract.schema.json new file mode 100644 index 0000000..7d604af --- /dev/null +++ b/neo4j/contract.schema.json @@ -0,0 +1,129 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://codellm-devkit.github.io/schema/neo4j/contract.schema.json", + "title": "codeanalyzer Neo4j graph contract", + "description": "Meta-schema for the schema.neo4j.json document each codeanalyzer emits with `--emit schema`. The document describes the graph an analyzer writes — node labels, relationship types, and the constraints and indexes the loader creates — so it is a contract about a graph, not a JSON Schema for analysis.json. Its schema_version is an independent line from the analysis schema version: an analyzer can sit on analysis schema v1 while its graph contract is on 2.x.", + + "x-cldk": { + "kind": "neo4j-graph-contract", + "sources": [ + { "repo": "codellm-devkit/codeanalyzer-java", "release": "v2.4.1", "schemaVersion": "1.0.0", "path": "schema.neo4j.json" }, + { "repo": "codellm-devkit/codeanalyzer-python", "release": "v1.1.1", "schemaVersion": "2.0.0", "path": "schema.neo4j.json" }, + { "repo": "codellm-devkit/codeanalyzer-typescript", "release": "v1.0.0", "schemaVersion": "2.0.0", "path": "schema.neo4j.json" } + ], + "note": "The copies under neo4j// are byte-identical to the tagged release, so `diff` against the analyzer repo is the drift check. They are pinned to the latest release, not to whatever sits on main.", + "validates": ["*/schema.neo4j.json"] + }, + + "type": "object", + "required": ["schema_version", "generator", "node_labels", "relationship_types"], + "additionalProperties": false, + + "properties": { + "schema_version": { + "description": "Version of the graph contract itself, independent of the analysis schema version.", + "type": "string", + "pattern": "^\\d+\\.\\d+\\.\\d+$" + }, + "generator": { + "description": "Analyzer that emitted this contract, e.g. codeanalyzer-java.", + "type": "string" + }, + "marker_labels": { + "description": "Extra labels attached to nodes that satisfy some property (e.g. JEntrypoint), rather than to a node kind.", + "type": "array", + "items": { "type": "string" } + }, + "node_labels": { + "type": "array", + "items": { "$ref": "#/$defs/NodeLabel" }, + "minItems": 1 + }, + "relationship_types": { + "type": "array", + "items": { "$ref": "#/$defs/RelationshipType" }, + "minItems": 1 + }, + "constraints": { + "description": "Cypher CREATE CONSTRAINT statements the loader runs before ingest.", + "type": "array", + "items": { "type": "string", "pattern": "^CREATE CONSTRAINT " } + }, + "indexes": { + "description": "Cypher CREATE INDEX statements the loader runs before ingest.", + "type": "array", + "items": { "type": "string", "pattern": "^CREATE (INDEX|FULLTEXT INDEX|VECTOR INDEX) " } + } + }, + + "$defs": { + "NodeLabel": { + "type": "object", + "required": ["label", "key", "properties"], + "additionalProperties": false, + "properties": { + "label": { + "description": "Neo4j label written on the node.", + "type": "string", + "pattern": "^[A-Za-z][A-Za-z0-9_]*$" + }, + "merge_label": { + "description": "Label used in the MERGE that upserts the node, when it differs from `label`. Java and Python spell this merge_label; TypeScript spells it mergeLabel. Either is accepted here — the divergence is real in the shipped contracts.", + "type": "string" + }, + "mergeLabel": { + "description": "TypeScript's spelling of merge_label. See the note on merge_label.", + "type": "string" + }, + "key": { + "description": "Property that uniquely identifies a node with this label.", + "type": "string" + }, + "properties": { + "description": "Property name to property type. Values are contract type names, not JSON Schema types.", + "type": "object", + "additionalProperties": { "$ref": "#/$defs/PropertyType" } + } + }, + "anyOf": [ + { "required": ["merge_label"] }, + { "required": ["mergeLabel"] } + ] + }, + + "RelationshipType": { + "type": "object", + "required": ["type", "from", "to"], + "additionalProperties": false, + "properties": { + "type": { + "description": "Relationship type written on the edge, e.g. PY_HAS_MODULE.", + "type": "string", + "pattern": "^[A-Z][A-Z0-9_]*$" + }, + "from": { + "description": "Node labels this edge may start at.", + "type": "array", + "items": { "type": "string" }, + "minItems": 1 + }, + "to": { + "description": "Node labels this edge may end at.", + "type": "array", + "items": { "type": "string" }, + "minItems": 1 + }, + "properties": { + "type": "object", + "additionalProperties": { "$ref": "#/$defs/PropertyType" } + } + } + }, + + "PropertyType": { + "description": "Contract-level property type. This is the set the three shipped contracts use; extend it when an analyzer starts emitting a new one.", + "type": "string", + "enum": ["string", "integer", "float", "boolean", "string[]", "integer[]", "float[]", "boolean[]"] + } + } +} diff --git a/neo4j/java/schema.neo4j.json b/neo4j/java/schema.neo4j.json new file mode 100644 index 0000000..09fff6b --- /dev/null +++ b/neo4j/java/schema.neo4j.json @@ -0,0 +1,517 @@ +{ + "schema_version": "1.0.0", + "generator": "codeanalyzer-java", + "marker_labels": [ + "JEntrypoint" + ], + "node_labels": [ + { + "label": "JApplication", + "merge_label": "JApplication", + "key": "name", + "properties": { + "name": "string", + "schema_version": "string" + } + }, + { + "label": "JCompilationUnit", + "merge_label": "JCompilationUnit", + "key": "file_key", + "properties": { + "file_key": "string", + "file_path": "string", + "package_name": "string", + "content_hash": "string", + "comment_count": "integer", + "is_modified": "boolean", + "_module": "string" + } + }, + { + "label": "JType", + "merge_label": "JSymbol", + "key": "id", + "properties": { + "id": "string", + "name": "string", + "fqn": "string", + "kind": "string", + "modifiers": "string[]", + "annotations": "string[]", + "extends_list": "string[]", + "implements_list": "string[]", + "nested_type_declarations": "string[]", + "is_interface": "boolean", + "is_nested_type": "boolean", + "is_inner_class": "boolean", + "is_local_class": "boolean", + "is_entrypoint_class": "boolean", + "parent_type": "string", + "docstring": "string", + "_module": "string" + } + }, + { + "label": "JCallable", + "merge_label": "JSymbol", + "key": "id", + "properties": { + "id": "string", + "name": "string", + "signature": "string", + "file_path": "string", + "declaration": "string", + "return_type": "string", + "modifiers": "string[]", + "annotations": "string[]", + "thrown_exceptions": "string[]", + "parameter_types": "string[]", + "referenced_types": "string[]", + "accessed_fields": "string[]", + "code": "string", + "code_start_line": "integer", + "start_line": "integer", + "end_line": "integer", + "cyclomatic_complexity": "integer", + "is_constructor": "boolean", + "is_implicit": "boolean", + "is_entrypoint": "boolean", + "docstring": "string", + "_module": "string" + } + }, + { + "label": "JField", + "merge_label": "JField", + "key": "id", + "properties": { + "id": "string", + "name": "string", + "type": "string", + "modifiers": "string[]", + "annotations": "string[]", + "variables": "string[]", + "variable_initializers_json": "string", + "start_line": "integer", + "end_line": "integer", + "docstring": "string", + "_module": "string" + } + }, + { + "label": "JParameter", + "merge_label": "JParameter", + "key": "id", + "properties": { + "id": "string", + "name": "string", + "type": "string", + "annotations": "string[]", + "modifiers": "string[]", + "start_line": "integer", + "end_line": "integer", + "start_column": "integer", + "end_column": "integer", + "_module": "string" + } + }, + { + "label": "JVariable", + "merge_label": "JVariable", + "key": "id", + "properties": { + "id": "string", + "name": "string", + "type": "string", + "initializer": "string", + "start_line": "integer", + "end_line": "integer", + "start_column": "integer", + "end_column": "integer", + "docstring": "string", + "_module": "string" + } + }, + { + "label": "JCallSite", + "merge_label": "JCallSite", + "key": "id", + "properties": { + "id": "string", + "method_name": "string", + "receiver_expr": "string", + "receiver_type": "string", + "return_type": "string", + "callee_signature": "string", + "argument_types": "string[]", + "argument_expr": "string[]", + "is_static_call": "boolean", + "is_constructor_call": "boolean", + "is_public": "boolean", + "is_private": "boolean", + "is_protected": "boolean", + "is_unspecified": "boolean", + "start_line": "integer", + "start_column": "integer", + "end_line": "integer", + "end_column": "integer", + "docstring": "string", + "_module": "string" + } + }, + { + "label": "JEnumConstant", + "merge_label": "JEnumConstant", + "key": "id", + "properties": { + "id": "string", + "name": "string", + "arguments": "string[]", + "_module": "string" + } + }, + { + "label": "JRecordComponent", + "merge_label": "JRecordComponent", + "key": "id", + "properties": { + "id": "string", + "name": "string", + "type": "string", + "modifiers": "string[]", + "annotations": "string[]", + "default_value": "string", + "is_var_args": "boolean", + "docstring": "string", + "_module": "string" + } + }, + { + "label": "JInitializationBlock", + "merge_label": "JInitializationBlock", + "key": "id", + "properties": { + "id": "string", + "file_path": "string", + "code": "string", + "annotations": "string[]", + "thrown_exceptions": "string[]", + "referenced_types": "string[]", + "accessed_fields": "string[]", + "is_static": "boolean", + "cyclomatic_complexity": "integer", + "start_line": "integer", + "end_line": "integer", + "docstring": "string", + "_module": "string" + } + }, + { + "label": "JCrudOperation", + "merge_label": "JCrudOperation", + "key": "id", + "properties": { + "id": "string", + "line_number": "integer", + "operation_type": "string", + "target_table": "string", + "involved_columns": "string[]", + "condition": "string", + "joined_tables": "string[]", + "_module": "string" + } + }, + { + "label": "JCrudQuery", + "merge_label": "JCrudQuery", + "key": "id", + "properties": { + "id": "string", + "line_number": "integer", + "query_type": "string", + "query_arguments": "string[]", + "_module": "string" + } + }, + { + "label": "JComment", + "merge_label": "JComment", + "key": "id", + "properties": { + "id": "string", + "content": "string", + "is_javadoc": "boolean", + "start_line": "integer", + "start_column": "integer", + "end_line": "integer", + "end_column": "integer", + "_module": "string" + } + }, + { + "label": "JPackage", + "merge_label": "JPackage", + "key": "name", + "properties": { + "name": "string" + } + }, + { + "label": "JAnnotation", + "merge_label": "JAnnotation", + "key": "name", + "properties": { + "name": "string" + } + } + ], + "relationship_types": [ + { + "type": "J_HAS_UNIT", + "from": [ + "JApplication" + ], + "to": [ + "JCompilationUnit" + ], + "properties": {} + }, + { + "type": "J_DECLARES_TYPE", + "from": [ + "JCompilationUnit" + ], + "to": [ + "JType" + ], + "properties": {} + }, + { + "type": "J_HAS_NESTED_TYPE", + "from": [ + "JType" + ], + "to": [ + "JType" + ], + "properties": {} + }, + { + "type": "J_HAS_CALLABLE", + "from": [ + "JType" + ], + "to": [ + "JCallable" + ], + "properties": {} + }, + { + "type": "J_HAS_FIELD", + "from": [ + "JType" + ], + "to": [ + "JField" + ], + "properties": {} + }, + { + "type": "J_HAS_PARAMETER", + "from": [ + "JCallable" + ], + "to": [ + "JParameter" + ], + "properties": {} + }, + { + "type": "J_HAS_CALLSITE", + "from": [ + "JCallable", + "JInitializationBlock" + ], + "to": [ + "JCallSite" + ], + "properties": {} + }, + { + "type": "J_DECLARES_VAR", + "from": [ + "JCallable", + "JInitializationBlock" + ], + "to": [ + "JVariable" + ], + "properties": {} + }, + { + "type": "J_HAS_ENUM_CONSTANT", + "from": [ + "JType" + ], + "to": [ + "JEnumConstant" + ], + "properties": {} + }, + { + "type": "J_HAS_RECORD_COMPONENT", + "from": [ + "JType" + ], + "to": [ + "JRecordComponent" + ], + "properties": {} + }, + { + "type": "J_HAS_INIT_BLOCK", + "from": [ + "JType" + ], + "to": [ + "JInitializationBlock" + ], + "properties": {} + }, + { + "type": "J_EXTENDS", + "from": [ + "JType" + ], + "to": [ + "JType" + ], + "properties": {} + }, + { + "type": "J_IMPLEMENTS", + "from": [ + "JType" + ], + "to": [ + "JType" + ], + "properties": {} + }, + { + "type": "J_ANNOTATED_BY", + "from": [ + "JType", + "JCallable", + "JField" + ], + "to": [ + "JAnnotation" + ], + "properties": {} + }, + { + "type": "J_IMPORTS", + "from": [ + "JCompilationUnit" + ], + "to": [ + "JType", + "JPackage" + ], + "properties": { + "path": "string", + "is_static": "boolean", + "is_wildcard": "boolean" + } + }, + { + "type": "J_RESOLVES_TO", + "from": [ + "JCallSite" + ], + "to": [ + "JCallable" + ], + "properties": {} + }, + { + "type": "J_CALLS", + "from": [ + "JCallable" + ], + "to": [ + "JCallable" + ], + "properties": { + "type": "string", + "weight": "integer", + "source_kind": "string", + "destination_kind": "string" + } + }, + { + "type": "J_HAS_CRUD_OPERATION", + "from": [ + "JCallable", + "JCallSite" + ], + "to": [ + "JCrudOperation" + ], + "properties": {} + }, + { + "type": "J_HAS_CRUD_QUERY", + "from": [ + "JCallable", + "JCallSite" + ], + "to": [ + "JCrudQuery" + ], + "properties": {} + }, + { + "type": "J_HAS_COMMENT", + "from": [ + "JCompilationUnit", + "JType", + "JCallable", + "JField", + "JCallSite", + "JVariable", + "JRecordComponent", + "JInitializationBlock" + ], + "to": [ + "JComment" + ], + "properties": {} + } + ], + "constraints": [ + "CREATE CONSTRAINT j_symbol_id IF NOT EXISTS FOR (s:JSymbol) REQUIRE s.id IS UNIQUE", + "CREATE CONSTRAINT j_application_name IF NOT EXISTS FOR (a:JApplication) REQUIRE a.name IS UNIQUE", + "CREATE CONSTRAINT j_compilation_unit_key IF NOT EXISTS FOR (c:JCompilationUnit) REQUIRE c.file_key IS UNIQUE", + "CREATE CONSTRAINT j_package_name IF NOT EXISTS FOR (p:JPackage) REQUIRE p.name IS UNIQUE", + "CREATE CONSTRAINT j_annotation_name IF NOT EXISTS FOR (an:JAnnotation) REQUIRE an.name IS UNIQUE", + "CREATE CONSTRAINT j_callsite_id IF NOT EXISTS FOR (cs:JCallSite) REQUIRE cs.id IS UNIQUE", + "CREATE CONSTRAINT j_field_id IF NOT EXISTS FOR (f:JField) REQUIRE f.id IS UNIQUE", + "CREATE CONSTRAINT j_parameter_id IF NOT EXISTS FOR (p:JParameter) REQUIRE p.id IS UNIQUE", + "CREATE CONSTRAINT j_variable_id IF NOT EXISTS FOR (v:JVariable) REQUIRE v.id IS UNIQUE", + "CREATE CONSTRAINT j_enum_constant_id IF NOT EXISTS FOR (e:JEnumConstant) REQUIRE e.id IS UNIQUE", + "CREATE CONSTRAINT j_record_component_id IF NOT EXISTS FOR (r:JRecordComponent) REQUIRE r.id IS UNIQUE", + "CREATE CONSTRAINT j_init_block_id IF NOT EXISTS FOR (ib:JInitializationBlock) REQUIRE ib.id IS UNIQUE", + "CREATE CONSTRAINT j_crud_operation_id IF NOT EXISTS FOR (co:JCrudOperation) REQUIRE co.id IS UNIQUE", + "CREATE CONSTRAINT j_crud_query_id IF NOT EXISTS FOR (cq:JCrudQuery) REQUIRE cq.id IS UNIQUE", + "CREATE CONSTRAINT j_comment_id IF NOT EXISTS FOR (cm:JComment) REQUIRE cm.id IS UNIQUE" + ], + "indexes": [ + "CREATE INDEX j_callable_name IF NOT EXISTS FOR (c:JCallable) ON (c.name)", + "CREATE INDEX j_type_name IF NOT EXISTS FOR (t:JType) ON (t.name)", + "CREATE INDEX j_annotation_name_idx IF NOT EXISTS FOR (an:JAnnotation) ON (an.name)", + "CREATE FULLTEXT INDEX j_code_fts IF NOT EXISTS FOR (c:JCallable) ON EACH [c.code, c.docstring]" + ] +} diff --git a/neo4j/python/schema.neo4j.json b/neo4j/python/schema.neo4j.json new file mode 100644 index 0000000..32f8422 --- /dev/null +++ b/neo4j/python/schema.neo4j.json @@ -0,0 +1,388 @@ +{ + "schema_version": "2.0.0", + "generator": "codeanalyzer-python", + "marker_labels": [], + "node_labels": [ + { + "label": "PyApplication", + "merge_label": "PyApplication", + "key": "name", + "properties": { + "name": "string", + "schema_version": "string", + "analyzer_name": "string", + "analyzer_version": "string", + "repo_uri": "string", + "source_revision": "string", + "repo_dirty": "boolean" + } + }, + { + "label": "PyModule", + "merge_label": "PyModule", + "key": "id", + "properties": { + "id": "string", + "file_key": "string", + "module_name": "string", + "content_hash": "string", + "last_modified": "float", + "file_size": "integer", + "_module": "string" + } + }, + { + "label": "PyClass", + "merge_label": "PySymbol", + "key": "id", + "properties": { + "id": "string", + "signature": "string", + "name": "string", + "code": "string", + "base_classes": "string[]", + "docstring": "string", + "start_line": "integer", + "end_line": "integer", + "_module": "string" + } + }, + { + "label": "PyCallable", + "merge_label": "PySymbol", + "key": "id", + "properties": { + "id": "string", + "signature": "string", + "name": "string", + "path": "string", + "return_type": "string", + "cyclomatic_complexity": "integer", + "code": "string", + "code_start_line": "integer", + "start_line": "integer", + "end_line": "integer", + "docstring": "string", + "decorators": "string[]", + "parameters_json": "string", + "accessed_symbols_json": "string", + "_module": "string" + } + }, + { + "label": "PyExternal", + "merge_label": "PySymbol", + "key": "id", + "properties": { + "id": "string", + "name": "string", + "module": "string" + } + }, + { + "label": "PyPackage", + "merge_label": "PyPackage", + "key": "name", + "properties": { + "name": "string" + } + }, + { + "label": "PyDecorator", + "merge_label": "PyDecorator", + "key": "name", + "properties": { + "name": "string" + } + }, + { + "label": "PyCallSite", + "merge_label": "PyCallSite", + "key": "id", + "properties": { + "id": "string", + "method_name": "string", + "receiver_expr": "string", + "receiver_type": "string", + "argument_types": "string[]", + "arguments_json": "string", + "return_type": "string", + "callee_signature": "string", + "is_constructor_call": "boolean", + "start_line": "integer", + "start_column": "integer", + "end_line": "integer", + "end_column": "integer", + "_module": "string" + } + }, + { + "label": "PyAttribute", + "merge_label": "PyAttribute", + "key": "id", + "properties": { + "id": "string", + "name": "string", + "type": "string", + "initializer": "string", + "docstring": "string", + "start_line": "integer", + "end_line": "integer", + "_module": "string" + } + }, + { + "label": "PyVariable", + "merge_label": "PyVariable", + "key": "id", + "properties": { + "id": "string", + "name": "string", + "type": "string", + "initializer": "string", + "scope": "string", + "start_line": "integer", + "end_line": "integer", + "_module": "string" + } + }, + { + "label": "PyCFGNode", + "merge_label": "PyCFGNode", + "key": "id", + "properties": { + "id": "string", + "kind": "string", + "var": "string", + "call_node": "string", + "start_line": "integer", + "end_line": "integer", + "_module": "string" + } + } + ], + "relationship_types": [ + { + "type": "PY_HAS_MODULE", + "from": [ + "PyApplication" + ], + "to": [ + "PyModule" + ], + "properties": {} + }, + { + "type": "PY_DECLARES", + "from": [ + "PyModule", + "PyClass", + "PyCallable" + ], + "to": [ + "PyClass", + "PyCallable" + ], + "properties": {} + }, + { + "type": "PY_HAS_METHOD", + "from": [ + "PyClass" + ], + "to": [ + "PyCallable" + ], + "properties": {} + }, + { + "type": "PY_HAS_ATTRIBUTE", + "from": [ + "PyClass" + ], + "to": [ + "PyAttribute" + ], + "properties": {} + }, + { + "type": "PY_DECLARES_VAR", + "from": [ + "PyModule", + "PyCallable" + ], + "to": [ + "PyVariable" + ], + "properties": {} + }, + { + "type": "PY_HAS_CALLSITE", + "from": [ + "PyCallable" + ], + "to": [ + "PyCallSite" + ], + "properties": {} + }, + { + "type": "PY_RESOLVES_TO", + "from": [ + "PyCallSite" + ], + "to": [ + "PyCallable", + "PyExternal" + ], + "properties": {} + }, + { + "type": "PY_CALLS", + "from": [ + "PyCallable", + "PyExternal" + ], + "to": [ + "PyCallable", + "PyExternal" + ], + "properties": { + "weight": "integer", + "prov": "string[]" + } + }, + { + "type": "PY_EXTENDS", + "from": [ + "PyClass" + ], + "to": [ + "PyClass" + ], + "properties": {} + }, + { + "type": "PY_IMPORTS", + "from": [ + "PyModule" + ], + "to": [ + "PyModule", + "PyPackage" + ], + "properties": { + "spellings": "string[]", + "imported_names": "string[]", + "aliases": "string[]" + } + }, + { + "type": "PY_DECORATED_BY", + "from": [ + "PyCallable" + ], + "to": [ + "PyDecorator" + ], + "properties": {} + }, + { + "type": "PY_HAS_CFG_NODE", + "from": [ + "PyCallable" + ], + "to": [ + "PyCFGNode" + ], + "properties": {} + }, + { + "type": "PY_CFG_NEXT", + "from": [ + "PyCFGNode" + ], + "to": [ + "PyCFGNode" + ], + "properties": { + "kind": "string", + "_k": "string" + } + }, + { + "type": "PY_CDG", + "from": [ + "PyCFGNode" + ], + "to": [ + "PyCFGNode" + ], + "properties": {} + }, + { + "type": "PY_DDG", + "from": [ + "PyCFGNode" + ], + "to": [ + "PyCFGNode" + ], + "properties": { + "var": "string", + "prov": "string[]", + "_k": "string" + } + }, + { + "type": "PY_PARAM_IN", + "from": [ + "PyCFGNode" + ], + "to": [ + "PyCFGNode" + ], + "properties": { + "var": "string" + } + }, + { + "type": "PY_PARAM_OUT", + "from": [ + "PyCFGNode" + ], + "to": [ + "PyCFGNode" + ], + "properties": { + "var": "string" + } + }, + { + "type": "PY_SUMMARY", + "from": [ + "PyCFGNode" + ], + "to": [ + "PyCFGNode" + ], + "properties": {} + } + ], + "constraints": [ + "CREATE CONSTRAINT pyapplication_name IF NOT EXISTS FOR (x:PyApplication) REQUIRE x.name IS UNIQUE", + "CREATE CONSTRAINT pymodule_id IF NOT EXISTS FOR (x:PyModule) REQUIRE x.id IS UNIQUE", + "CREATE CONSTRAINT pysymbol_id IF NOT EXISTS FOR (x:PySymbol) REQUIRE x.id IS UNIQUE", + "CREATE CONSTRAINT pypackage_name IF NOT EXISTS FOR (x:PyPackage) REQUIRE x.name IS UNIQUE", + "CREATE CONSTRAINT pydecorator_name IF NOT EXISTS FOR (x:PyDecorator) REQUIRE x.name IS UNIQUE", + "CREATE CONSTRAINT pycallsite_id IF NOT EXISTS FOR (x:PyCallSite) REQUIRE x.id IS UNIQUE", + "CREATE CONSTRAINT pyattribute_id IF NOT EXISTS FOR (x:PyAttribute) REQUIRE x.id IS UNIQUE", + "CREATE CONSTRAINT pyvariable_id IF NOT EXISTS FOR (x:PyVariable) REQUIRE x.id IS UNIQUE", + "CREATE CONSTRAINT pycfgnode_id IF NOT EXISTS FOR (x:PyCFGNode) REQUIRE x.id IS UNIQUE" + ], + "indexes": [ + "CREATE INDEX py_callable_name IF NOT EXISTS FOR (c:PyCallable) ON (c.name)", + "CREATE INDEX py_class_name IF NOT EXISTS FOR (c:PyClass) ON (c.name)", + "CREATE FULLTEXT INDEX py_code_fts IF NOT EXISTS FOR (c:PyCallable) ON EACH [c.code, c.docstring]" + ] +} diff --git a/neo4j/typescript/schema.neo4j.json b/neo4j/typescript/schema.neo4j.json new file mode 100644 index 0000000..dc97524 --- /dev/null +++ b/neo4j/typescript/schema.neo4j.json @@ -0,0 +1,401 @@ +{ + "schema_version": "2.0.0", + "generator": "codeanalyzer-typescript", + "marker_labels": [], + "node_labels": [ + { + "label": "TSApplication", + "mergeLabel": "Application", + "key": "id", + "properties": { + "id": "string", + "schema_version": "string", + "language": "string", + "max_level": "integer", + "k_limit": "integer", + "analyzer_name": "string", + "analyzer_version": "string" + } + }, + { + "label": "TSModule", + "mergeLabel": "CanNode", + "key": "id", + "properties": { + "id": "string", + "kind": "string", + "_module": "string", + "name": "string", + "is_tsx": "boolean", + "is_declaration_file": "boolean", + "content_hash": "string", + "start_line": "integer", + "end_line": "integer" + } + }, + { + "label": "TSClass", + "mergeLabel": "CanNode", + "key": "id", + "properties": { + "id": "string", + "kind": "string", + "_module": "string", + "signature": "string", + "name": "string", + "base_classes": "string[]", + "implements_types": "string[]", + "is_abstract": "boolean", + "is_exported": "boolean", + "is_ambient": "boolean", + "start_line": "integer", + "end_line": "integer" + } + }, + { + "label": "TSInterface", + "mergeLabel": "CanNode", + "key": "id", + "properties": { + "id": "string", + "kind": "string", + "_module": "string", + "signature": "string", + "name": "string", + "base_classes": "string[]", + "is_exported": "boolean", + "is_ambient": "boolean", + "start_line": "integer", + "end_line": "integer" + } + }, + { + "label": "TSEnum", + "mergeLabel": "CanNode", + "key": "id", + "properties": { + "id": "string", + "kind": "string", + "_module": "string", + "signature": "string", + "name": "string", + "is_const": "boolean", + "is_exported": "boolean", + "is_ambient": "boolean", + "start_line": "integer", + "end_line": "integer" + } + }, + { + "label": "TSTypeAlias", + "mergeLabel": "CanNode", + "key": "id", + "properties": { + "id": "string", + "kind": "string", + "_module": "string", + "signature": "string", + "name": "string", + "aliased_type": "string", + "is_exported": "boolean", + "is_ambient": "boolean", + "start_line": "integer", + "end_line": "integer" + } + }, + { + "label": "TSNamespace", + "mergeLabel": "CanNode", + "key": "id", + "properties": { + "id": "string", + "kind": "string", + "_module": "string", + "signature": "string", + "name": "string", + "is_exported": "boolean", + "is_ambient": "boolean", + "start_line": "integer", + "end_line": "integer" + } + }, + { + "label": "TSCallable", + "mergeLabel": "CanNode", + "key": "id", + "properties": { + "id": "string", + "kind": "string", + "_module": "string", + "signature": "string", + "name": "string", + "return_type": "string", + "cyclomatic_complexity": "integer", + "accessibility": "string", + "accessor_kind": "string", + "is_static": "boolean", + "is_abstract": "boolean", + "is_async": "boolean", + "is_generator": "boolean", + "is_exported": "boolean", + "is_ambient": "boolean", + "is_implicit": "boolean", + "start_line": "integer", + "end_line": "integer" + } + }, + { + "label": "TSField", + "mergeLabel": "CanNode", + "key": "id", + "properties": { + "id": "string", + "kind": "string", + "_module": "string", + "name": "string", + "type": "string", + "start_line": "integer", + "end_line": "integer" + } + }, + { + "label": "TSBodyNode", + "mergeLabel": "CanNode", + "key": "id", + "properties": { + "id": "string", + "kind": "string", + "_module": "string", + "of": "string", + "parent": "string", + "callee": "string", + "start_line": "integer", + "end_line": "integer" + } + }, + { + "label": "TSExternal", + "mergeLabel": "CanNode", + "key": "id", + "properties": { + "id": "string", + "kind": "string", + "_module": "string", + "name": "string", + "module": "string" + } + }, + { + "label": "TSAnonymousCallable", + "mergeLabel": "CanNode", + "key": "id", + "properties": { + "id": "string", + "kind": "string", + "_module": "string", + "name": "string", + "path": "string", + "start_line": "integer", + "start_column": "integer" + } + } + ], + "relationship_types": [ + { + "type": "TS_HAS_MODULE", + "from": [ + "TSApplication" + ], + "to": [ + "TSModule" + ], + "properties": {} + }, + { + "type": "TS_DECLARES", + "from": [ + "TSModule", + "TSNamespace", + "TSCallable" + ], + "to": [ + "TSClass", + "TSInterface", + "TSEnum", + "TSTypeAlias", + "TSNamespace", + "TSCallable" + ], + "properties": {} + }, + { + "type": "TS_HAS_METHOD", + "from": [ + "TSClass", + "TSInterface" + ], + "to": [ + "TSCallable" + ], + "properties": {} + }, + { + "type": "TS_HAS_FIELD", + "from": [ + "TSModule", + "TSClass", + "TSInterface", + "TSEnum", + "TSNamespace" + ], + "to": [ + "TSField" + ], + "properties": {} + }, + { + "type": "TS_HAS_BODY_NODE", + "from": [ + "TSCallable", + "TSAnonymousCallable" + ], + "to": [ + "TSBodyNode" + ], + "properties": {} + }, + { + "type": "TS_RESOLVES_TO", + "from": [ + "TSBodyNode" + ], + "to": [ + "TSCallable", + "TSExternal", + "TSAnonymousCallable" + ], + "properties": {} + }, + { + "type": "TS_CALLS", + "from": [ + "TSCallable", + "TSAnonymousCallable" + ], + "to": [ + "TSCallable", + "TSExternal", + "TSAnonymousCallable" + ], + "properties": { + "weight": "integer", + "prov": "string[]" + } + }, + { + "type": "TS_CFG_NEXT", + "from": [ + "TSBodyNode" + ], + "to": [ + "TSBodyNode" + ], + "properties": { + "kind": "string", + "_k": "string" + } + }, + { + "type": "TS_CDG", + "from": [ + "TSBodyNode" + ], + "to": [ + "TSBodyNode" + ], + "properties": {} + }, + { + "type": "TS_DDG", + "from": [ + "TSBodyNode" + ], + "to": [ + "TSBodyNode" + ], + "properties": { + "var": "string", + "prov": "string[]", + "_k": "string" + } + }, + { + "type": "TS_SUMMARY", + "from": [ + "TSBodyNode" + ], + "to": [ + "TSBodyNode" + ], + "properties": { + "var": "string" + } + }, + { + "type": "TS_PARAM_IN", + "from": [ + "TSBodyNode" + ], + "to": [ + "TSBodyNode" + ], + "properties": { + "var": "string" + } + }, + { + "type": "TS_PARAM_OUT", + "from": [ + "TSBodyNode" + ], + "to": [ + "TSBodyNode" + ], + "properties": { + "var": "string" + } + }, + { + "type": "TS_EXTENDS", + "from": [ + "TSClass", + "TSInterface" + ], + "to": [ + "TSClass", + "TSInterface" + ], + "properties": {} + }, + { + "type": "TS_IMPLEMENTS", + "from": [ + "TSClass" + ], + "to": [ + "TSInterface", + "TSClass" + ], + "properties": {} + } + ], + "constraints": [ + "CREATE CONSTRAINT application_id IF NOT EXISTS FOR (x:Application) REQUIRE x.id IS UNIQUE", + "CREATE CONSTRAINT cannode_id IF NOT EXISTS FOR (x:CanNode) REQUIRE x.id IS UNIQUE" + ], + "indexes": [ + "CREATE INDEX callable_name IF NOT EXISTS FOR (c:TSCallable) ON (c.name)", + "CREATE INDEX cannode_kind IF NOT EXISTS FOR (n:CanNode) ON (n.kind)", + "CREATE INDEX cannode_module IF NOT EXISTS FOR (n:CanNode) ON (n._module)" + ] +} diff --git a/scripts/check.py b/scripts/check.py index 9b329c7..3f55354 100644 --- a/scripts/check.py +++ b/scripts/check.py @@ -1,9 +1,9 @@ #!/usr/bin/env python3 -"""Check every schema in this repo is a valid JSON Schema, and validate any -sample documents placed next to it. +"""Check every schema in this repo is a valid JSON Schema, and validate the +documents it claims to cover. -A sample is any *.sample.json sitting in the same directory as a schema; it is -validated against that directory's analysis.schema.json. +A schema validates any *.sample.json sitting in its own directory, plus every +file matched by the globs in its x-cldk.validates list (relative to the schema). python3 scripts/check.py # all versions python3 scripts/check.py v1/java # one directory @@ -23,9 +23,13 @@ def check(schema_path: Path) -> int: rel = schema_path.relative_to(ROOT) print(f"ok {rel}") + covered = list(schema_path.parent.glob("*.sample.json")) + for pattern in schema.get("x-cldk", {}).get("validates", []): + covered += schema_path.parent.glob(pattern) + failures = 0 validator = Draft202012Validator(schema) - for sample in sorted(schema_path.parent.glob("*.sample.json")): + for sample in sorted(set(covered)): errors = sorted(validator.iter_errors(json.loads(sample.read_text())), key=lambda e: list(e.absolute_path)) if errors: From b7d4b7f23e56ccf05da79b238708342337651849 Mon Sep 17 00:00:00 2001 From: Rahul Krishna Date: Wed, 19 Aug 2026 14:36:04 -0400 Subject: [PATCH 03/16] feat: enforce the language prefix on every label in the Neo4j contracts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every label an analyzer writes must carry its language prefix, everywhere it appears: node labels, merge labels, marker labels, relationship endpoints, and the labels referenced inside the Cypher constraint and index statements. Relationship types carry the uppercase form. J/J_ for Java, Py/PY_ for Python, TS/TS_ for TypeScript. The rule exists because these graphs share a database. An unprefixed or generic label is not a style question — two analyzers loading into one Neo4j instance collide on it, and a query written against one language silently picks up nodes from another. generator is now a closed enum, so a new analyzer cannot validate until its prefix is registered under $defs/prefixRules. Forgetting to pick a prefix is a failure, not a default. This turns the check red, and correctly so: codeanalyzer-typescript v1.0.0 violates the rule in 16 places. Its Application node merges as `Application`, and eleven of its twelve node kinds merge as a shared `CanNode`, with four Cypher statements referencing those two unprefixed labels. Java and Python are clean. The TypeScript copy stays byte-identical to the release rather than being edited to pass — the contract is what shipped, and the check is what says it is wrong. Fixing it means renaming to TSApplication and TSCanNode in the analyzer, then repinning here. Verified the rules bite rather than pass vacuously: unprefixed node, merge, marker, endpoint and Cypher labels, an unprefixed relationship type, a wrong-language prefix, and an unknown generator are each rejected on both clean contracts. --- neo4j/contract.schema.json | 374 ++++++++++++++++++++++++++++++++++--- 1 file changed, 344 insertions(+), 30 deletions(-) diff --git a/neo4j/contract.schema.json b/neo4j/contract.schema.json index 7d604af..6da7fb0 100644 --- a/neo4j/contract.schema.json +++ b/neo4j/contract.schema.json @@ -2,23 +2,42 @@ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://codellm-devkit.github.io/schema/neo4j/contract.schema.json", "title": "codeanalyzer Neo4j graph contract", - "description": "Meta-schema for the schema.neo4j.json document each codeanalyzer emits with `--emit schema`. The document describes the graph an analyzer writes — node labels, relationship types, and the constraints and indexes the loader creates — so it is a contract about a graph, not a JSON Schema for analysis.json. Its schema_version is an independent line from the analysis schema version: an analyzer can sit on analysis schema v1 while its graph contract is on 2.x.", - + "description": "Meta-schema for the schema.neo4j.json document each codeanalyzer emits with `--emit schema`. The document describes the graph an analyzer writes \u2014 node labels, relationship types, and the constraints and indexes the loader creates \u2014 so it is a contract about a graph, not a JSON Schema for analysis.json. Its schema_version is an independent line from the analysis schema version: an analyzer can sit on analysis schema v1 while its graph contract is on 2.x. Every label and relationship type must carry its language prefix \u2014 J/J_ for Java, Py/PY_ for Python, TS/TS_ for TypeScript \u2014 everywhere it appears, including inside the Cypher constraint and index statements.", "x-cldk": { "kind": "neo4j-graph-contract", "sources": [ - { "repo": "codellm-devkit/codeanalyzer-java", "release": "v2.4.1", "schemaVersion": "1.0.0", "path": "schema.neo4j.json" }, - { "repo": "codellm-devkit/codeanalyzer-python", "release": "v1.1.1", "schemaVersion": "2.0.0", "path": "schema.neo4j.json" }, - { "repo": "codellm-devkit/codeanalyzer-typescript", "release": "v1.0.0", "schemaVersion": "2.0.0", "path": "schema.neo4j.json" } + { + "repo": "codellm-devkit/codeanalyzer-java", + "release": "v2.4.1", + "schemaVersion": "1.0.0", + "path": "schema.neo4j.json" + }, + { + "repo": "codellm-devkit/codeanalyzer-python", + "release": "v1.1.1", + "schemaVersion": "2.0.0", + "path": "schema.neo4j.json" + }, + { + "repo": "codellm-devkit/codeanalyzer-typescript", + "release": "v1.0.0", + "schemaVersion": "2.0.0", + "path": "schema.neo4j.json" + } ], "note": "The copies under neo4j// are byte-identical to the tagged release, so `diff` against the analyzer repo is the drift check. They are pinned to the latest release, not to whatever sits on main.", - "validates": ["*/schema.neo4j.json"] + "validates": [ + "*/schema.neo4j.json" + ] }, - "type": "object", - "required": ["schema_version", "generator", "node_labels", "relationship_types"], + "required": [ + "schema_version", + "generator", + "node_labels", + "relationship_types" + ], "additionalProperties": false, - "properties": { "schema_version": { "description": "Version of the graph contract itself, independent of the analysis schema version.", @@ -26,40 +45,60 @@ "pattern": "^\\d+\\.\\d+\\.\\d+$" }, "generator": { - "description": "Analyzer that emitted this contract, e.g. codeanalyzer-java.", - "type": "string" + "description": "Analyzer that emitted this contract. Closed set \u2014 a new analyzer must register its label prefix under $defs/prefixRules before its contract can validate.", + "type": "string", + "enum": [ + "codeanalyzer-java", + "codeanalyzer-python", + "codeanalyzer-typescript" + ] }, "marker_labels": { "description": "Extra labels attached to nodes that satisfy some property (e.g. JEntrypoint), rather than to a node kind.", "type": "array", - "items": { "type": "string" } + "items": { + "type": "string" + } }, "node_labels": { "type": "array", - "items": { "$ref": "#/$defs/NodeLabel" }, + "items": { + "$ref": "#/$defs/NodeLabel" + }, "minItems": 1 }, "relationship_types": { "type": "array", - "items": { "$ref": "#/$defs/RelationshipType" }, + "items": { + "$ref": "#/$defs/RelationshipType" + }, "minItems": 1 }, "constraints": { "description": "Cypher CREATE CONSTRAINT statements the loader runs before ingest.", "type": "array", - "items": { "type": "string", "pattern": "^CREATE CONSTRAINT " } + "items": { + "type": "string", + "pattern": "^CREATE CONSTRAINT " + } }, "indexes": { "description": "Cypher CREATE INDEX statements the loader runs before ingest.", "type": "array", - "items": { "type": "string", "pattern": "^CREATE (INDEX|FULLTEXT INDEX|VECTOR INDEX) " } + "items": { + "type": "string", + "pattern": "^CREATE (INDEX|FULLTEXT INDEX|VECTOR INDEX) " + } } }, - "$defs": { "NodeLabel": { "type": "object", - "required": ["label", "key", "properties"], + "required": [ + "label", + "key", + "properties" + ], "additionalProperties": false, "properties": { "label": { @@ -68,7 +107,7 @@ "pattern": "^[A-Za-z][A-Za-z0-9_]*$" }, "merge_label": { - "description": "Label used in the MERGE that upserts the node, when it differs from `label`. Java and Python spell this merge_label; TypeScript spells it mergeLabel. Either is accepted here — the divergence is real in the shipped contracts.", + "description": "Label used in the MERGE that upserts the node, when it differs from `label`. Java and Python spell this merge_label; TypeScript spells it mergeLabel. Either is accepted here \u2014 the divergence is real in the shipped contracts.", "type": "string" }, "mergeLabel": { @@ -82,18 +121,31 @@ "properties": { "description": "Property name to property type. Values are contract type names, not JSON Schema types.", "type": "object", - "additionalProperties": { "$ref": "#/$defs/PropertyType" } + "additionalProperties": { + "$ref": "#/$defs/PropertyType" + } } }, "anyOf": [ - { "required": ["merge_label"] }, - { "required": ["mergeLabel"] } + { + "required": [ + "merge_label" + ] + }, + { + "required": [ + "mergeLabel" + ] + } ] }, - "RelationshipType": { "type": "object", - "required": ["type", "from", "to"], + "required": [ + "type", + "from", + "to" + ], "additionalProperties": false, "properties": { "type": { @@ -104,26 +156,288 @@ "from": { "description": "Node labels this edge may start at.", "type": "array", - "items": { "type": "string" }, + "items": { + "type": "string" + }, "minItems": 1 }, "to": { "description": "Node labels this edge may end at.", "type": "array", - "items": { "type": "string" }, + "items": { + "type": "string" + }, "minItems": 1 }, "properties": { "type": "object", - "additionalProperties": { "$ref": "#/$defs/PropertyType" } + "additionalProperties": { + "$ref": "#/$defs/PropertyType" + } } } }, - "PropertyType": { "description": "Contract-level property type. This is the set the three shipped contracts use; extend it when an analyzer starts emitting a new one.", "type": "string", - "enum": ["string", "integer", "float", "boolean", "string[]", "integer[]", "float[]", "boolean[]"] + "enum": [ + "string", + "integer", + "float", + "boolean", + "string[]", + "integer[]", + "float[]", + "boolean[]" + ] + }, + "prefixRules": { + "description": "Per-language naming rules. Every label an analyzer writes \u2014 node label, merge label, marker label, relationship endpoint, and every label referenced from a Cypher constraint or index \u2014 carries that language's prefix, and every relationship type carries the uppercase form of it. No exceptions: a shared or generic label is a contract violation, because two analyzers loading into one database would collide on it.", + "java": { + "properties": { + "marker_labels": { + "items": { + "type": "string", + "pattern": "^J[A-Z][A-Za-z0-9_]*$" + } + }, + "node_labels": { + "items": { + "properties": { + "label": { + "type": "string", + "pattern": "^J[A-Z][A-Za-z0-9_]*$" + }, + "merge_label": { + "type": "string", + "pattern": "^J[A-Z][A-Za-z0-9_]*$" + }, + "mergeLabel": { + "type": "string", + "pattern": "^J[A-Z][A-Za-z0-9_]*$" + } + } + } + }, + "relationship_types": { + "items": { + "properties": { + "type": { + "type": "string", + "pattern": "^J_[A-Z][A-Z0-9_]*$" + }, + "from": { + "items": { + "type": "string", + "pattern": "^J[A-Z][A-Za-z0-9_]*$" + } + }, + "to": { + "items": { + "type": "string", + "pattern": "^J[A-Z][A-Za-z0-9_]*$" + } + } + } + } + }, + "constraints": { + "items": { + "type": "string", + "not": { + "pattern": "\\([A-Za-z_][A-Za-z0-9_]*:(?!J[A-Z])" + } + } + }, + "indexes": { + "items": { + "type": "string", + "not": { + "pattern": "\\([A-Za-z_][A-Za-z0-9_]*:(?!J[A-Z])" + } + } + } + } + }, + "python": { + "properties": { + "marker_labels": { + "items": { + "type": "string", + "pattern": "^Py[A-Z][A-Za-z0-9_]*$" + } + }, + "node_labels": { + "items": { + "properties": { + "label": { + "type": "string", + "pattern": "^Py[A-Z][A-Za-z0-9_]*$" + }, + "merge_label": { + "type": "string", + "pattern": "^Py[A-Z][A-Za-z0-9_]*$" + }, + "mergeLabel": { + "type": "string", + "pattern": "^Py[A-Z][A-Za-z0-9_]*$" + } + } + } + }, + "relationship_types": { + "items": { + "properties": { + "type": { + "type": "string", + "pattern": "^PY_[A-Z][A-Z0-9_]*$" + }, + "from": { + "items": { + "type": "string", + "pattern": "^Py[A-Z][A-Za-z0-9_]*$" + } + }, + "to": { + "items": { + "type": "string", + "pattern": "^Py[A-Z][A-Za-z0-9_]*$" + } + } + } + } + }, + "constraints": { + "items": { + "type": "string", + "not": { + "pattern": "\\([A-Za-z_][A-Za-z0-9_]*:(?!Py[A-Z])" + } + } + }, + "indexes": { + "items": { + "type": "string", + "not": { + "pattern": "\\([A-Za-z_][A-Za-z0-9_]*:(?!Py[A-Z])" + } + } + } + } + }, + "typescript": { + "properties": { + "marker_labels": { + "items": { + "type": "string", + "pattern": "^TS[A-Z][A-Za-z0-9_]*$" + } + }, + "node_labels": { + "items": { + "properties": { + "label": { + "type": "string", + "pattern": "^TS[A-Z][A-Za-z0-9_]*$" + }, + "merge_label": { + "type": "string", + "pattern": "^TS[A-Z][A-Za-z0-9_]*$" + }, + "mergeLabel": { + "type": "string", + "pattern": "^TS[A-Z][A-Za-z0-9_]*$" + } + } + } + }, + "relationship_types": { + "items": { + "properties": { + "type": { + "type": "string", + "pattern": "^TS_[A-Z][A-Z0-9_]*$" + }, + "from": { + "items": { + "type": "string", + "pattern": "^TS[A-Z][A-Za-z0-9_]*$" + } + }, + "to": { + "items": { + "type": "string", + "pattern": "^TS[A-Z][A-Za-z0-9_]*$" + } + } + } + } + }, + "constraints": { + "items": { + "type": "string", + "not": { + "pattern": "\\([A-Za-z_][A-Za-z0-9_]*:(?!TS[A-Z])" + } + } + }, + "indexes": { + "items": { + "type": "string", + "not": { + "pattern": "\\([A-Za-z_][A-Za-z0-9_]*:(?!TS[A-Z])" + } + } + } + } + } + } + }, + "allOf": [ + { + "if": { + "properties": { + "generator": { + "const": "codeanalyzer-java" + } + }, + "required": [ + "generator" + ] + }, + "then": { + "$ref": "#/$defs/prefixRules/java" + } + }, + { + "if": { + "properties": { + "generator": { + "const": "codeanalyzer-python" + } + }, + "required": [ + "generator" + ] + }, + "then": { + "$ref": "#/$defs/prefixRules/python" + } + }, + { + "if": { + "properties": { + "generator": { + "const": "codeanalyzer-typescript" + } + }, + "required": [ + "generator" + ] + }, + "then": { + "$ref": "#/$defs/prefixRules/typescript" + } } - } + ] } From af4178aed8c5c3e5f273fdccd12db60d71de15d9 Mon Sep 17 00:00:00 2001 From: Rahul Krishna Date: Wed, 19 Aug 2026 15:38:39 -0400 Subject: [PATCH 04/16] feat: pin the v1 Neo4j contracts to the v1-era analyzer releases MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follow the v1/{json,neo4j}/ split and repin each graph contract to the same release its v1 analysis schema came from, so both halves of v1 describe one analyzer version rather than drifting apart: java v2.4.1 contract 1.0.0 16 node labels, 20 relationship types python v0.3.2 contract 1.2.0 10 node labels, 11 relationship types typescript v0.4.3 contract 1.0.0 15 node labels, 14 relationship types The earlier python and typescript copies were pinned to v1.1.1 and v1.0.0, both past the v1 line. The TypeScript copy is normalized, not byte-identical: v0.4.3 predates prefixing on that line and emitted every label and relationship type bare — Application, Module, Callable, CALLS, HAS_MODULE, marker Entrypoint. It is rewritten onto the TS/TS_ rule, including the label positions inside the Cypher statements, and carries an x-cldk block naming v0.4.3 as its origin and saying what changed. The meta-schema now accepts that annotation, and requires it to name an origin, so a normalized copy cannot pass as an as-released one. Checked the rewrite is a renaming and nothing more: schema_version, node and relationship counts, property maps, endpoint cardinalities, and the constraint and index identifiers all survive unchanged, and every label and type maps back to the original by stripping the prefix. Python needed nothing — v0.3.2 was already fully prefixed. The repo is green. --- neo4j/typescript/schema.neo4j.json | 401 --------------- v1/{ => json}/analysis.template.schema.json | 0 v1/{ => json}/java/analysis.schema.json | 0 v1/{ => json}/python/analysis.schema.json | 0 v1/{ => json}/typescript/analysis.schema.json | 0 {neo4j => v1/neo4j}/contract.schema.json | 50 +- {neo4j => v1/neo4j}/java/schema.neo4j.json | 0 {neo4j => v1/neo4j}/python/schema.neo4j.json | 119 +---- v1/neo4j/typescript/schema.neo4j.json | 470 ++++++++++++++++++ 9 files changed, 524 insertions(+), 516 deletions(-) delete mode 100644 neo4j/typescript/schema.neo4j.json rename v1/{ => json}/analysis.template.schema.json (100%) rename v1/{ => json}/java/analysis.schema.json (100%) rename v1/{ => json}/python/analysis.schema.json (100%) rename v1/{ => json}/typescript/analysis.schema.json (100%) rename {neo4j => v1/neo4j}/contract.schema.json (88%) rename {neo4j => v1/neo4j}/java/schema.neo4j.json (100%) rename {neo4j => v1/neo4j}/python/schema.neo4j.json (74%) create mode 100644 v1/neo4j/typescript/schema.neo4j.json diff --git a/neo4j/typescript/schema.neo4j.json b/neo4j/typescript/schema.neo4j.json deleted file mode 100644 index dc97524..0000000 --- a/neo4j/typescript/schema.neo4j.json +++ /dev/null @@ -1,401 +0,0 @@ -{ - "schema_version": "2.0.0", - "generator": "codeanalyzer-typescript", - "marker_labels": [], - "node_labels": [ - { - "label": "TSApplication", - "mergeLabel": "Application", - "key": "id", - "properties": { - "id": "string", - "schema_version": "string", - "language": "string", - "max_level": "integer", - "k_limit": "integer", - "analyzer_name": "string", - "analyzer_version": "string" - } - }, - { - "label": "TSModule", - "mergeLabel": "CanNode", - "key": "id", - "properties": { - "id": "string", - "kind": "string", - "_module": "string", - "name": "string", - "is_tsx": "boolean", - "is_declaration_file": "boolean", - "content_hash": "string", - "start_line": "integer", - "end_line": "integer" - } - }, - { - "label": "TSClass", - "mergeLabel": "CanNode", - "key": "id", - "properties": { - "id": "string", - "kind": "string", - "_module": "string", - "signature": "string", - "name": "string", - "base_classes": "string[]", - "implements_types": "string[]", - "is_abstract": "boolean", - "is_exported": "boolean", - "is_ambient": "boolean", - "start_line": "integer", - "end_line": "integer" - } - }, - { - "label": "TSInterface", - "mergeLabel": "CanNode", - "key": "id", - "properties": { - "id": "string", - "kind": "string", - "_module": "string", - "signature": "string", - "name": "string", - "base_classes": "string[]", - "is_exported": "boolean", - "is_ambient": "boolean", - "start_line": "integer", - "end_line": "integer" - } - }, - { - "label": "TSEnum", - "mergeLabel": "CanNode", - "key": "id", - "properties": { - "id": "string", - "kind": "string", - "_module": "string", - "signature": "string", - "name": "string", - "is_const": "boolean", - "is_exported": "boolean", - "is_ambient": "boolean", - "start_line": "integer", - "end_line": "integer" - } - }, - { - "label": "TSTypeAlias", - "mergeLabel": "CanNode", - "key": "id", - "properties": { - "id": "string", - "kind": "string", - "_module": "string", - "signature": "string", - "name": "string", - "aliased_type": "string", - "is_exported": "boolean", - "is_ambient": "boolean", - "start_line": "integer", - "end_line": "integer" - } - }, - { - "label": "TSNamespace", - "mergeLabel": "CanNode", - "key": "id", - "properties": { - "id": "string", - "kind": "string", - "_module": "string", - "signature": "string", - "name": "string", - "is_exported": "boolean", - "is_ambient": "boolean", - "start_line": "integer", - "end_line": "integer" - } - }, - { - "label": "TSCallable", - "mergeLabel": "CanNode", - "key": "id", - "properties": { - "id": "string", - "kind": "string", - "_module": "string", - "signature": "string", - "name": "string", - "return_type": "string", - "cyclomatic_complexity": "integer", - "accessibility": "string", - "accessor_kind": "string", - "is_static": "boolean", - "is_abstract": "boolean", - "is_async": "boolean", - "is_generator": "boolean", - "is_exported": "boolean", - "is_ambient": "boolean", - "is_implicit": "boolean", - "start_line": "integer", - "end_line": "integer" - } - }, - { - "label": "TSField", - "mergeLabel": "CanNode", - "key": "id", - "properties": { - "id": "string", - "kind": "string", - "_module": "string", - "name": "string", - "type": "string", - "start_line": "integer", - "end_line": "integer" - } - }, - { - "label": "TSBodyNode", - "mergeLabel": "CanNode", - "key": "id", - "properties": { - "id": "string", - "kind": "string", - "_module": "string", - "of": "string", - "parent": "string", - "callee": "string", - "start_line": "integer", - "end_line": "integer" - } - }, - { - "label": "TSExternal", - "mergeLabel": "CanNode", - "key": "id", - "properties": { - "id": "string", - "kind": "string", - "_module": "string", - "name": "string", - "module": "string" - } - }, - { - "label": "TSAnonymousCallable", - "mergeLabel": "CanNode", - "key": "id", - "properties": { - "id": "string", - "kind": "string", - "_module": "string", - "name": "string", - "path": "string", - "start_line": "integer", - "start_column": "integer" - } - } - ], - "relationship_types": [ - { - "type": "TS_HAS_MODULE", - "from": [ - "TSApplication" - ], - "to": [ - "TSModule" - ], - "properties": {} - }, - { - "type": "TS_DECLARES", - "from": [ - "TSModule", - "TSNamespace", - "TSCallable" - ], - "to": [ - "TSClass", - "TSInterface", - "TSEnum", - "TSTypeAlias", - "TSNamespace", - "TSCallable" - ], - "properties": {} - }, - { - "type": "TS_HAS_METHOD", - "from": [ - "TSClass", - "TSInterface" - ], - "to": [ - "TSCallable" - ], - "properties": {} - }, - { - "type": "TS_HAS_FIELD", - "from": [ - "TSModule", - "TSClass", - "TSInterface", - "TSEnum", - "TSNamespace" - ], - "to": [ - "TSField" - ], - "properties": {} - }, - { - "type": "TS_HAS_BODY_NODE", - "from": [ - "TSCallable", - "TSAnonymousCallable" - ], - "to": [ - "TSBodyNode" - ], - "properties": {} - }, - { - "type": "TS_RESOLVES_TO", - "from": [ - "TSBodyNode" - ], - "to": [ - "TSCallable", - "TSExternal", - "TSAnonymousCallable" - ], - "properties": {} - }, - { - "type": "TS_CALLS", - "from": [ - "TSCallable", - "TSAnonymousCallable" - ], - "to": [ - "TSCallable", - "TSExternal", - "TSAnonymousCallable" - ], - "properties": { - "weight": "integer", - "prov": "string[]" - } - }, - { - "type": "TS_CFG_NEXT", - "from": [ - "TSBodyNode" - ], - "to": [ - "TSBodyNode" - ], - "properties": { - "kind": "string", - "_k": "string" - } - }, - { - "type": "TS_CDG", - "from": [ - "TSBodyNode" - ], - "to": [ - "TSBodyNode" - ], - "properties": {} - }, - { - "type": "TS_DDG", - "from": [ - "TSBodyNode" - ], - "to": [ - "TSBodyNode" - ], - "properties": { - "var": "string", - "prov": "string[]", - "_k": "string" - } - }, - { - "type": "TS_SUMMARY", - "from": [ - "TSBodyNode" - ], - "to": [ - "TSBodyNode" - ], - "properties": { - "var": "string" - } - }, - { - "type": "TS_PARAM_IN", - "from": [ - "TSBodyNode" - ], - "to": [ - "TSBodyNode" - ], - "properties": { - "var": "string" - } - }, - { - "type": "TS_PARAM_OUT", - "from": [ - "TSBodyNode" - ], - "to": [ - "TSBodyNode" - ], - "properties": { - "var": "string" - } - }, - { - "type": "TS_EXTENDS", - "from": [ - "TSClass", - "TSInterface" - ], - "to": [ - "TSClass", - "TSInterface" - ], - "properties": {} - }, - { - "type": "TS_IMPLEMENTS", - "from": [ - "TSClass" - ], - "to": [ - "TSInterface", - "TSClass" - ], - "properties": {} - } - ], - "constraints": [ - "CREATE CONSTRAINT application_id IF NOT EXISTS FOR (x:Application) REQUIRE x.id IS UNIQUE", - "CREATE CONSTRAINT cannode_id IF NOT EXISTS FOR (x:CanNode) REQUIRE x.id IS UNIQUE" - ], - "indexes": [ - "CREATE INDEX callable_name IF NOT EXISTS FOR (c:TSCallable) ON (c.name)", - "CREATE INDEX cannode_kind IF NOT EXISTS FOR (n:CanNode) ON (n.kind)", - "CREATE INDEX cannode_module IF NOT EXISTS FOR (n:CanNode) ON (n._module)" - ] -} diff --git a/v1/analysis.template.schema.json b/v1/json/analysis.template.schema.json similarity index 100% rename from v1/analysis.template.schema.json rename to v1/json/analysis.template.schema.json diff --git a/v1/java/analysis.schema.json b/v1/json/java/analysis.schema.json similarity index 100% rename from v1/java/analysis.schema.json rename to v1/json/java/analysis.schema.json diff --git a/v1/python/analysis.schema.json b/v1/json/python/analysis.schema.json similarity index 100% rename from v1/python/analysis.schema.json rename to v1/json/python/analysis.schema.json diff --git a/v1/typescript/analysis.schema.json b/v1/json/typescript/analysis.schema.json similarity index 100% rename from v1/typescript/analysis.schema.json rename to v1/json/typescript/analysis.schema.json diff --git a/neo4j/contract.schema.json b/v1/neo4j/contract.schema.json similarity index 88% rename from neo4j/contract.schema.json rename to v1/neo4j/contract.schema.json index 6da7fb0..ef39ff1 100644 --- a/neo4j/contract.schema.json +++ b/v1/neo4j/contract.schema.json @@ -14,18 +14,19 @@ }, { "repo": "codellm-devkit/codeanalyzer-python", - "release": "v1.1.1", - "schemaVersion": "2.0.0", + "release": "v0.3.2", + "schemaVersion": "1.2.0", "path": "schema.neo4j.json" }, { "repo": "codellm-devkit/codeanalyzer-typescript", - "release": "v1.0.0", - "schemaVersion": "2.0.0", - "path": "schema.neo4j.json" + "release": "v0.4.3", + "schemaVersion": "1.0.0", + "path": "schema.neo4j.json", + "normalized": true } ], - "note": "The copies under neo4j// are byte-identical to the tagged release, so `diff` against the analyzer repo is the drift check. They are pinned to the latest release, not to whatever sits on main.", + "note": "The copies under v1/neo4j// are byte-identical to the tagged release, so `diff` against the analyzer repo is the drift check. Each is pinned to the same release as the v1 analysis schema for that language: the graph contract an analyzer emitted while it was still on analysis schema v1. The contract's own schema_version runs on an independent line and does not track v1/v2. One exception: the TypeScript copy is normalized rather than byte-identical, because v0.4.3 predates prefixing on that line. It carries an x-cldk block saying so.", "validates": [ "*/schema.neo4j.json" ] @@ -89,6 +90,43 @@ "type": "string", "pattern": "^CREATE (INDEX|FULLTEXT INDEX|VECTOR INDEX) " } + }, + "x-cldk": { + "description": "Provenance annotation carried by a tracked copy, not something an analyzer emits. Present only on a copy that is not byte-identical to its release, where it records the origin and what was changed.", + "type": "object", + "required": [ + "normalized", + "origin" + ], + "properties": { + "normalized": { + "type": "boolean" + }, + "origin": { + "type": "object", + "required": [ + "repo", + "release" + ], + "properties": { + "repo": { + "type": "string" + }, + "release": { + "type": "string" + }, + "path": { + "type": "string" + }, + "schemaVersion": { + "type": "string" + } + } + }, + "note": { + "type": "string" + } + } } }, "$defs": { diff --git a/neo4j/java/schema.neo4j.json b/v1/neo4j/java/schema.neo4j.json similarity index 100% rename from neo4j/java/schema.neo4j.json rename to v1/neo4j/java/schema.neo4j.json diff --git a/neo4j/python/schema.neo4j.json b/v1/neo4j/python/schema.neo4j.json similarity index 74% rename from neo4j/python/schema.neo4j.json rename to v1/neo4j/python/schema.neo4j.json index 32f8422..7b0fd42 100644 --- a/neo4j/python/schema.neo4j.json +++ b/v1/neo4j/python/schema.neo4j.json @@ -1,5 +1,5 @@ { - "schema_version": "2.0.0", + "schema_version": "1.2.0", "generator": "codeanalyzer-python", "marker_labels": [], "node_labels": [ @@ -20,9 +20,8 @@ { "label": "PyModule", "merge_label": "PyModule", - "key": "id", + "key": "file_key", "properties": { - "id": "string", "file_key": "string", "module_name": "string", "content_hash": "string", @@ -34,9 +33,8 @@ { "label": "PyClass", "merge_label": "PySymbol", - "key": "id", + "key": "signature", "properties": { - "id": "string", "signature": "string", "name": "string", "code": "string", @@ -50,9 +48,8 @@ { "label": "PyCallable", "merge_label": "PySymbol", - "key": "id", + "key": "signature", "properties": { - "id": "string", "signature": "string", "name": "string", "path": "string", @@ -72,9 +69,9 @@ { "label": "PyExternal", "merge_label": "PySymbol", - "key": "id", + "key": "signature", "properties": { - "id": "string", + "signature": "string", "name": "string", "module": "string" } @@ -145,20 +142,6 @@ "end_line": "integer", "_module": "string" } - }, - { - "label": "PyCFGNode", - "merge_label": "PyCFGNode", - "key": "id", - "properties": { - "id": "string", - "kind": "string", - "var": "string", - "call_node": "string", - "start_line": "integer", - "end_line": "integer", - "_module": "string" - } } ], "relationship_types": [ @@ -249,7 +232,7 @@ ], "properties": { "weight": "integer", - "prov": "string[]" + "provenance": "string[]" } }, { @@ -286,99 +269,17 @@ "PyDecorator" ], "properties": {} - }, - { - "type": "PY_HAS_CFG_NODE", - "from": [ - "PyCallable" - ], - "to": [ - "PyCFGNode" - ], - "properties": {} - }, - { - "type": "PY_CFG_NEXT", - "from": [ - "PyCFGNode" - ], - "to": [ - "PyCFGNode" - ], - "properties": { - "kind": "string", - "_k": "string" - } - }, - { - "type": "PY_CDG", - "from": [ - "PyCFGNode" - ], - "to": [ - "PyCFGNode" - ], - "properties": {} - }, - { - "type": "PY_DDG", - "from": [ - "PyCFGNode" - ], - "to": [ - "PyCFGNode" - ], - "properties": { - "var": "string", - "prov": "string[]", - "_k": "string" - } - }, - { - "type": "PY_PARAM_IN", - "from": [ - "PyCFGNode" - ], - "to": [ - "PyCFGNode" - ], - "properties": { - "var": "string" - } - }, - { - "type": "PY_PARAM_OUT", - "from": [ - "PyCFGNode" - ], - "to": [ - "PyCFGNode" - ], - "properties": { - "var": "string" - } - }, - { - "type": "PY_SUMMARY", - "from": [ - "PyCFGNode" - ], - "to": [ - "PyCFGNode" - ], - "properties": {} } ], "constraints": [ "CREATE CONSTRAINT pyapplication_name IF NOT EXISTS FOR (x:PyApplication) REQUIRE x.name IS UNIQUE", - "CREATE CONSTRAINT pymodule_id IF NOT EXISTS FOR (x:PyModule) REQUIRE x.id IS UNIQUE", - "CREATE CONSTRAINT pysymbol_id IF NOT EXISTS FOR (x:PySymbol) REQUIRE x.id IS UNIQUE", + "CREATE CONSTRAINT pymodule_file_key IF NOT EXISTS FOR (x:PyModule) REQUIRE x.file_key IS UNIQUE", + "CREATE CONSTRAINT pysymbol_signature IF NOT EXISTS FOR (x:PySymbol) REQUIRE x.signature IS UNIQUE", "CREATE CONSTRAINT pypackage_name IF NOT EXISTS FOR (x:PyPackage) REQUIRE x.name IS UNIQUE", "CREATE CONSTRAINT pydecorator_name IF NOT EXISTS FOR (x:PyDecorator) REQUIRE x.name IS UNIQUE", "CREATE CONSTRAINT pycallsite_id IF NOT EXISTS FOR (x:PyCallSite) REQUIRE x.id IS UNIQUE", "CREATE CONSTRAINT pyattribute_id IF NOT EXISTS FOR (x:PyAttribute) REQUIRE x.id IS UNIQUE", - "CREATE CONSTRAINT pyvariable_id IF NOT EXISTS FOR (x:PyVariable) REQUIRE x.id IS UNIQUE", - "CREATE CONSTRAINT pycfgnode_id IF NOT EXISTS FOR (x:PyCFGNode) REQUIRE x.id IS UNIQUE" + "CREATE CONSTRAINT pyvariable_id IF NOT EXISTS FOR (x:PyVariable) REQUIRE x.id IS UNIQUE" ], "indexes": [ "CREATE INDEX py_callable_name IF NOT EXISTS FOR (c:PyCallable) ON (c.name)", diff --git a/v1/neo4j/typescript/schema.neo4j.json b/v1/neo4j/typescript/schema.neo4j.json new file mode 100644 index 0000000..131f384 --- /dev/null +++ b/v1/neo4j/typescript/schema.neo4j.json @@ -0,0 +1,470 @@ +{ + "schema_version": "1.0.0", + "generator": "codeanalyzer-typescript", + "marker_labels": [ + "TSEntrypoint" + ], + "node_labels": [ + { + "label": "TSApplication", + "mergeLabel": "TSApplication", + "key": "name", + "properties": { + "name": "string", + "schema_version": "string" + } + }, + { + "label": "TSModule", + "mergeLabel": "TSModule", + "key": "file_key", + "properties": { + "file_key": "string", + "module_name": "string", + "is_tsx": "boolean", + "is_declaration_file": "boolean", + "content_hash": "string", + "last_modified": "integer", + "file_size": "integer", + "_module": "string" + } + }, + { + "label": "TSClass", + "mergeLabel": "TSSymbol", + "key": "signature", + "properties": { + "signature": "string", + "name": "string", + "code": "string", + "base_classes": "string[]", + "implements_types": "string[]", + "type_parameter_names": "string[]", + "docstring": "string", + "is_abstract": "boolean", + "is_exported": "boolean", + "is_ambient": "boolean", + "start_line": "integer", + "end_line": "integer", + "framework": "string", + "detection_source": "string", + "route_path": "string", + "http_methods": "string[]", + "entrypoint_count": "integer", + "_module": "string" + } + }, + { + "label": "TSInterface", + "mergeLabel": "TSSymbol", + "key": "signature", + "properties": { + "signature": "string", + "name": "string", + "code": "string", + "base_classes": "string[]", + "type_parameter_names": "string[]", + "call_signatures": "string[]", + "index_signatures": "string[]", + "docstring": "string", + "is_exported": "boolean", + "is_ambient": "boolean", + "start_line": "integer", + "end_line": "integer", + "_module": "string" + } + }, + { + "label": "TSEnum", + "mergeLabel": "TSSymbol", + "key": "signature", + "properties": { + "signature": "string", + "name": "string", + "code": "string", + "member_names": "string[]", + "member_values": "string[]", + "docstring": "string", + "is_const": "boolean", + "is_exported": "boolean", + "is_ambient": "boolean", + "start_line": "integer", + "end_line": "integer", + "_module": "string" + } + }, + { + "label": "TSTypeAlias", + "mergeLabel": "TSSymbol", + "key": "signature", + "properties": { + "signature": "string", + "name": "string", + "code": "string", + "aliased_type": "string", + "type_parameter_names": "string[]", + "docstring": "string", + "is_exported": "boolean", + "is_ambient": "boolean", + "start_line": "integer", + "end_line": "integer", + "_module": "string" + } + }, + { + "label": "TSNamespace", + "mergeLabel": "TSSymbol", + "key": "signature", + "properties": { + "signature": "string", + "name": "string", + "docstring": "string", + "is_exported": "boolean", + "is_ambient": "boolean", + "start_line": "integer", + "end_line": "integer", + "_module": "string" + } + }, + { + "label": "TSCallable", + "mergeLabel": "TSSymbol", + "key": "signature", + "properties": { + "signature": "string", + "name": "string", + "path": "string", + "kind": "string", + "return_type": "string", + "cyclomatic_complexity": "integer", + "code": "string", + "code_start_line": "integer", + "start_line": "integer", + "end_line": "integer", + "accessibility": "string", + "accessor_kind": "string", + "docstring": "string", + "type_parameter_names": "string[]", + "parameters_json": "string", + "accessed_symbols_json": "string", + "is_static": "boolean", + "is_abstract": "boolean", + "is_async": "boolean", + "is_generator": "boolean", + "is_optional": "boolean", + "is_readonly": "boolean", + "is_exported": "boolean", + "is_ambient": "boolean", + "is_implicit": "boolean", + "framework": "string", + "detection_source": "string", + "route_path": "string", + "http_methods": "string[]", + "entrypoint_count": "integer", + "_module": "string" + } + }, + { + "label": "TSExternal", + "mergeLabel": "TSSymbol", + "key": "signature", + "properties": { + "signature": "string", + "name": "string", + "module": "string" + } + }, + { + "label": "TSAnonymousCallable", + "mergeLabel": "TSSymbol", + "key": "signature", + "properties": { + "signature": "string", + "name": "string", + "path": "string", + "start_line": "integer", + "start_column": "integer", + "_module": "string" + } + }, + { + "label": "TSPackage", + "mergeLabel": "TSPackage", + "key": "name", + "properties": { + "name": "string" + } + }, + { + "label": "TSDecorator", + "mergeLabel": "TSDecorator", + "key": "qualified_name", + "properties": { + "qualified_name": "string", + "name": "string" + } + }, + { + "label": "TSCallSite", + "mergeLabel": "TSCallSite", + "key": "id", + "properties": { + "id": "string", + "method_name": "string", + "receiver_expr": "string", + "receiver_type": "string", + "argument_types": "string[]", + "type_arguments": "string[]", + "return_type": "string", + "callee_signature": "string", + "is_constructor_call": "boolean", + "is_optional_chain": "boolean", + "start_line": "integer", + "start_column": "integer", + "end_line": "integer", + "end_column": "integer", + "_module": "string" + } + }, + { + "label": "TSAttribute", + "mergeLabel": "TSAttribute", + "key": "id", + "properties": { + "id": "string", + "name": "string", + "type": "string", + "initializer": "string", + "accessibility": "string", + "docstring": "string", + "is_static": "boolean", + "is_readonly": "boolean", + "is_optional": "boolean", + "is_abstract": "boolean", + "start_line": "integer", + "end_line": "integer", + "_module": "string" + } + }, + { + "label": "TSVariable", + "mergeLabel": "TSVariable", + "key": "id", + "properties": { + "id": "string", + "name": "string", + "type": "string", + "initializer": "string", + "scope": "string", + "declaration_kind": "string", + "is_readonly": "boolean", + "is_exported": "boolean", + "start_line": "integer", + "end_line": "integer", + "_module": "string" + } + } + ], + "relationship_types": [ + { + "type": "TS_HAS_MODULE", + "from": [ + "TSApplication" + ], + "to": [ + "TSModule" + ], + "properties": {} + }, + { + "type": "TS_DECLARES", + "from": [ + "TSModule", + "TSNamespace", + "TSClass", + "TSCallable" + ], + "to": [ + "TSClass", + "TSInterface", + "TSEnum", + "TSTypeAlias", + "TSNamespace", + "TSCallable" + ], + "properties": {} + }, + { + "type": "TS_HAS_METHOD", + "from": [ + "TSClass", + "TSInterface" + ], + "to": [ + "TSCallable" + ], + "properties": {} + }, + { + "type": "TS_HAS_ATTRIBUTE", + "from": [ + "TSClass", + "TSInterface" + ], + "to": [ + "TSAttribute" + ], + "properties": {} + }, + { + "type": "TS_DECLARES_VAR", + "from": [ + "TSModule", + "TSNamespace", + "TSCallable" + ], + "to": [ + "TSVariable" + ], + "properties": {} + }, + { + "type": "TS_HAS_CALLSITE", + "from": [ + "TSCallable" + ], + "to": [ + "TSCallSite" + ], + "properties": {} + }, + { + "type": "TS_RESOLVES_TO", + "from": [ + "TSCallSite" + ], + "to": [ + "TSCallable", + "TSExternal" + ], + "properties": {} + }, + { + "type": "TS_CALLS", + "from": [ + "TSCallable" + ], + "to": [ + "TSCallable", + "TSExternal" + ], + "properties": { + "weight": "integer", + "provenance": "string[]", + "dispatch": "string", + "external": "boolean", + "module": "string" + } + }, + { + "type": "TS_EXTENDS", + "from": [ + "TSClass", + "TSInterface" + ], + "to": [ + "TSClass", + "TSInterface" + ], + "properties": {} + }, + { + "type": "TS_IMPLEMENTS", + "from": [ + "TSClass" + ], + "to": [ + "TSInterface" + ], + "properties": {} + }, + { + "type": "TS_IMPORTS", + "from": [ + "TSModule" + ], + "to": [ + "TSModule", + "TSPackage" + ], + "properties": { + "imported_names": "string[]", + "import_kinds": "string[]", + "is_type_only": "boolean" + } + }, + { + "type": "TS_RE_EXPORTS", + "from": [ + "TSModule" + ], + "to": [ + "TSModule", + "TSPackage" + ], + "properties": {} + }, + { + "type": "TS_MEMBER_OF", + "from": [ + "TSExternal" + ], + "to": [ + "TSPackage" + ], + "properties": {} + }, + { + "type": "TS_DECORATED_BY", + "from": [ + "TSClass", + "TSCallable", + "TSAttribute" + ], + "to": [ + "TSDecorator" + ], + "properties": { + "positional_arguments": "string[]", + "keyword_arguments_json": "string", + "start_line": "integer", + "end_line": "integer" + } + } + ], + "constraints": [ + "CREATE CONSTRAINT application_name IF NOT EXISTS FOR (x:TSApplication) REQUIRE x.name IS UNIQUE", + "CREATE CONSTRAINT module_file_key IF NOT EXISTS FOR (x:TSModule) REQUIRE x.file_key IS UNIQUE", + "CREATE CONSTRAINT symbol_signature IF NOT EXISTS FOR (x:TSSymbol) REQUIRE x.signature IS UNIQUE", + "CREATE CONSTRAINT package_name IF NOT EXISTS FOR (x:TSPackage) REQUIRE x.name IS UNIQUE", + "CREATE CONSTRAINT decorator_qualified_name IF NOT EXISTS FOR (x:TSDecorator) REQUIRE x.qualified_name IS UNIQUE", + "CREATE CONSTRAINT callsite_id IF NOT EXISTS FOR (x:TSCallSite) REQUIRE x.id IS UNIQUE", + "CREATE CONSTRAINT attribute_id IF NOT EXISTS FOR (x:TSAttribute) REQUIRE x.id IS UNIQUE", + "CREATE CONSTRAINT variable_id IF NOT EXISTS FOR (x:TSVariable) REQUIRE x.id IS UNIQUE" + ], + "indexes": [ + "CREATE INDEX callable_name IF NOT EXISTS FOR (c:TSCallable) ON (c.name)", + "CREATE INDEX decorator_name IF NOT EXISTS FOR (d:TSDecorator) ON (d.name)", + "CREATE FULLTEXT INDEX code_fts IF NOT EXISTS FOR (c:TSCallable) ON EACH [c.code, c.docstring]" + ], + "x-cldk": { + "normalized": true, + "origin": { + "repo": "codellm-devkit/codeanalyzer-typescript", + "release": "v0.4.3", + "path": "schema.neo4j.json", + "schemaVersion": "1.0.0" + }, + "note": "Not byte-identical to the release. codeanalyzer-typescript v0.4.3 emitted every label and relationship type unprefixed; this copy applies the TS/TS_ prefix rule to labels, relationship types, and the label positions inside the Cypher statements. Property names, cardinalities, constraint and index identifiers, and schema_version are untouched." + } +} From 208e585d4d8471312060318cb847c361dc74477a Mon Sep 17 00:00:00 2001 From: Rahul Krishna Date: Wed, 2 Sep 2026 17:37:29 -0400 Subject: [PATCH 05/16] feat(schema): add IaC v2 artifact envelope --- v2/iac/json/analysis.l1.sample.json | 58 +++++++ v2/iac/json/analysis.schema.json | 230 ++++++++++++++++++++++++++++ 2 files changed, 288 insertions(+) create mode 100644 v2/iac/json/analysis.l1.sample.json create mode 100644 v2/iac/json/analysis.schema.json diff --git a/v2/iac/json/analysis.l1.sample.json b/v2/iac/json/analysis.l1.sample.json new file mode 100644 index 0000000..fc68ffa --- /dev/null +++ b/v2/iac/json/analysis.l1.sample.json @@ -0,0 +1,58 @@ +{ + "schema_version": "2.0.0", + "language": "iac", + "max_level": 1, + "analyzer": {"name": "codeanalyzer-iac", "version": "0.1.0"}, + "application": { + "id": "can://iac/payments", + "kind": "application", + "artifacts": { + "README.md": { + "id": "can://artifact/payments/README.md", + "kind": "artifact", + "path": "README.md", + "format": "text", + "source": "payments infrastructure\n", + "sha256": "79accdf71a72e00a8b25fe1284094368e885e2fe55db0cf686a83c5359bf43df", + "size_bytes": 24, + "config_keys": {}, + "aliases": [] + }, + "charts/api/Chart.yaml": { + "id": "can://artifact/payments/charts/api/Chart.yaml", + "kind": "artifact", + "path": "charts/api/Chart.yaml", + "format": "yaml", + "source": "apiVersion: v2\nname: api\nversion: 1.2.3\ntype: application\n", + "sha256": "4a0bf85edced2838bb82ae1a52bf8830a718462ff54f4c6fb11ce539fabb53af", + "size_bytes": 58, + "config_keys": {}, + "aliases": [{ + "id": "can://iac/payments/helm/chart/charts/api", + "kind": "helm_chart", + "target": "can://artifact/payments/charts/api/Chart.yaml" + }], + "iac": { + "dialect": "helm", + "kind": "helm_chart", + "status": "complete", + "api_version": "v2", + "name": "api", + "version": "1.2.3", + "chart_type": "application", + "maintainers": [], + "keywords": [], + "sources": [], + "annotations": {}, + "dependencies": {}, + "renders": {} + } + } + }, + "packages": {}, + "external_chart_references": {}, + "kubernetes_resource_addresses": {}, + "diagnostics": {}, + "edges": {} + } +} diff --git a/v2/iac/json/analysis.schema.json b/v2/iac/json/analysis.schema.json new file mode 100644 index 0000000..036778e --- /dev/null +++ b/v2/iac/json/analysis.schema.json @@ -0,0 +1,230 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://codellm-devkit.github.io/schema/v2/iac/analysis.schema.json", + "title": "codeanalyzer-iac schema v2 analysis output", + "x-cldk": { + "schemaVersion": "2.0.0", + "language": "iac", + "source": { + "repo": "codellm-devkit/codeanalyzer-iac", + "release": "v0.1.0", + "definedIn": "internal/model" + } + }, + "$ref": "#/$defs/Analysis", + "$defs": { + "Analysis": { + "type": "object", + "additionalProperties": false, + "required": ["schema_version", "language", "max_level", "analyzer", "application"], + "properties": { + "schema_version": {"const": "2.0.0"}, + "language": {"const": "iac"}, + "max_level": {"type": "integer", "minimum": 1, "maximum": 3}, + "analyzer": {"$ref": "#/$defs/Analyzer"}, + "application": {"$ref": "#/$defs/Application"} + } + }, + "Analyzer": { + "type": "object", + "additionalProperties": false, + "required": ["name", "version"], + "properties": { + "name": {"const": "codeanalyzer-iac"}, + "version": {"type": "string", "minLength": 1} + } + }, + "Application": { + "type": "object", + "additionalProperties": false, + "required": [ + "id", + "kind", + "artifacts", + "packages", + "external_chart_references", + "kubernetes_resource_addresses", + "diagnostics", + "edges" + ], + "properties": { + "id": {"type": "string", "pattern": "^can://iac/[^/]+$"}, + "kind": {"const": "application"}, + "artifacts": { + "type": "object", + "additionalProperties": {"$ref": "#/$defs/Artifact"} + }, + "packages": { + "type": "object", + "additionalProperties": {"$ref": "#/$defs/Package"} + }, + "external_chart_references": { + "type": "object", + "additionalProperties": {"$ref": "#/$defs/HelmChartReference"} + }, + "kubernetes_resource_addresses": { + "type": "object", + "additionalProperties": {"$ref": "#/$defs/KubernetesResourceAddress"} + }, + "diagnostics": { + "type": "object", + "additionalProperties": {"$ref": "#/$defs/Diagnostic"} + }, + "edges": { + "type": "object", + "additionalProperties": {"$ref": "#/$defs/Edge"} + } + } + }, + "Artifact": { + "type": "object", + "additionalProperties": false, + "required": ["id", "kind", "path", "format", "source", "sha256", "size_bytes", "config_keys", "aliases"], + "properties": { + "id": {"type": "string", "pattern": "^can://artifact/[^/]+/.+"}, + "kind": {"const": "artifact"}, + "path": { + "type": "string", + "pattern": "^(?!/)(?!.*(?:^|/)\\.{1,2}(?:/|$))[^\\\\]+$" + }, + "format": {"type": "string", "minLength": 1}, + "source": {"type": "string"}, + "sha256": {"type": "string", "pattern": "^[0-9a-f]{64}$"}, + "size_bytes": {"type": "integer", "minimum": 0}, + "config_keys": { + "type": "object", + "additionalProperties": {"$ref": "#/$defs/ConfigKey"} + }, + "aliases": { + "type": "array", + "items": {"$ref": "#/$defs/IdentityAlias"} + }, + "iac": {"$ref": "#/$defs/IaCFacet"}, + "codeanalyzer_iac_config": {"$ref": "#/$defs/CodeAnalyzerIaCConfig"} + } + }, + "Span": { + "type": "object", + "additionalProperties": false, + "required": ["start", "end", "bytes"], + "properties": { + "start": { + "type": "array", + "minItems": 2, + "maxItems": 2, + "items": {"type": "integer", "minimum": 1} + }, + "end": { + "type": "array", + "minItems": 2, + "maxItems": 2, + "items": {"type": "integer", "minimum": 1} + }, + "bytes": { + "type": "array", + "minItems": 2, + "maxItems": 2, + "items": {"type": "integer", "minimum": 0} + } + } + }, + "ConfigKey": { + "type": "object", + "additionalProperties": false, + "required": ["id", "kind", "name", "path", "span"], + "properties": { + "id": {"type": "string", "pattern": "^can://iac/"}, + "kind": {"const": "config_key"}, + "name": {"type": "string", "minLength": 1}, + "path": {"type": "string", "minLength": 1}, + "span": {"$ref": "#/$defs/Span"}, + "iac": {"$ref": "#/$defs/HelmValueFacet"} + } + }, + "IdentityAlias": { + "type": "object", + "additionalProperties": false, + "required": ["id", "kind", "target"], + "properties": { + "id": {"type": "string", "pattern": "^can://iac/"}, + "kind": {"type": "string", "minLength": 1}, + "target": {"type": "string", "pattern": "^can://(artifact|iac)/"} + } + }, + "Diagnostic": { + "type": "object", + "additionalProperties": false, + "required": ["id", "kind", "severity", "code", "message"], + "properties": { + "id": {"type": "string", "pattern": "^can://iac/"}, + "kind": {"const": "diagnostic"}, + "severity": {"enum": ["info", "warning", "error"]}, + "code": {"type": "string", "minLength": 1}, + "message": {"type": "string", "minLength": 1}, + "phase": {"enum": ["load", "dependency", "values", "template", "decode"]}, + "artifact_id": {"type": "string", "pattern": "^can://artifact/"}, + "span": {"$ref": "#/$defs/Span"} + } + }, + "Package": { + "type": "object", + "additionalProperties": false, + "required": ["id", "kind", "purl"], + "properties": { + "id": {"type": "string", "pattern": "^pkg:[a-z][a-z0-9.+-]*/.+"}, + "kind": {"const": "package"}, + "purl": {"type": "string", "pattern": "^pkg:[a-z][a-z0-9.+-]*/.+"} + } + }, + "Edge": { + "type": "object", + "additionalProperties": false, + "required": ["src", "dst"], + "properties": { + "src": {"type": "string", "minLength": 1}, + "dst": {"type": "string", "minLength": 1} + } + }, + "IaCFacet": { + "oneOf": [ + {"$ref": "#/$defs/HelmChart"} + ] + }, + "HelmChart": { + "type": "object", + "additionalProperties": false, + "required": ["dialect", "kind", "status", "api_version", "name", "version", "dependencies", "renders"], + "properties": { + "dialect": {"const": "helm"}, + "kind": {"const": "helm_chart"}, + "status": {"enum": ["complete", "partial", "failed"]}, + "api_version": {"enum": ["v1", "v2"]}, + "name": {"type": "string", "minLength": 1}, + "version": {"type": "string", "minLength": 1}, + "kube_version": {"type": "string"}, + "description": {"type": "string"}, + "chart_type": {"enum": ["application", "library"]}, + "keywords": {"type": "array", "items": {"type": "string"}}, + "home": {"type": "string"}, + "sources": {"type": "array", "items": {"type": "string"}}, + "maintainers": {"type": "array", "items": {"$ref": "#/$defs/HelmMaintainer"}}, + "icon": {"type": "string"}, + "app_version": {"type": "string"}, + "deprecated": {"type": "boolean"}, + "annotations": {"type": "object", "additionalProperties": {"type": "string"}}, + "dependencies": {"type": "object", "additionalProperties": {"$ref": "#/$defs/HelmDependency"}}, + "renders": {"type": "object", "additionalProperties": {"$ref": "#/$defs/HelmRender"}} + } + }, + "HelmMaintainer": { + "type": "object", + "additionalProperties": false, + "required": ["name"], + "properties": { + "name": {"type": "string", "minLength": 1}, + "email": {"type": "string", "minLength": 1}, + "url": {"type": "string", "minLength": 1} + } + } + } +} From e2a1d2f9570ac6498500994ec86756e98afbb1b0 Mon Sep 17 00:00:00 2001 From: Rahul Krishna Date: Wed, 2 Sep 2026 17:47:08 -0400 Subject: [PATCH 06/16] fix(schema): constrain IaC artifact URI paths --- v2/iac/json/analysis.schema.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/v2/iac/json/analysis.schema.json b/v2/iac/json/analysis.schema.json index 036778e..e0646b9 100644 --- a/v2/iac/json/analysis.schema.json +++ b/v2/iac/json/analysis.schema.json @@ -81,7 +81,7 @@ "additionalProperties": false, "required": ["id", "kind", "path", "format", "source", "sha256", "size_bytes", "config_keys", "aliases"], "properties": { - "id": {"type": "string", "pattern": "^can://artifact/[^/]+/.+"}, + "id": {"type": "string", "pattern": "^can://artifact/(?:[A-Za-z0-9._~-]|%[0-9A-Fa-f]{2})+/(?:[A-Za-z0-9._~-]|%[0-9A-Fa-f]{2})+(?:/(?:[A-Za-z0-9._~-]|%[0-9A-Fa-f]{2})+)*$"}, "kind": {"const": "artifact"}, "path": { "type": "string", From 416924af0856b83c0266a4fdc6934c2e20dc187a Mon Sep 17 00:00:00 2001 From: Rahul Krishna Date: Wed, 2 Sep 2026 17:51:06 -0400 Subject: [PATCH 07/16] fix(schema): reject trailing Artifact URI characters --- v2/iac/json/analysis.schema.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/v2/iac/json/analysis.schema.json b/v2/iac/json/analysis.schema.json index e0646b9..fd2a77d 100644 --- a/v2/iac/json/analysis.schema.json +++ b/v2/iac/json/analysis.schema.json @@ -81,7 +81,7 @@ "additionalProperties": false, "required": ["id", "kind", "path", "format", "source", "sha256", "size_bytes", "config_keys", "aliases"], "properties": { - "id": {"type": "string", "pattern": "^can://artifact/(?:[A-Za-z0-9._~-]|%[0-9A-Fa-f]{2})+/(?:[A-Za-z0-9._~-]|%[0-9A-Fa-f]{2})+(?:/(?:[A-Za-z0-9._~-]|%[0-9A-Fa-f]{2})+)*$"}, + "id": {"type": "string", "pattern": "^can://artifact/(?:[A-Za-z0-9._~-]|%[0-9A-Fa-f]{2})+/(?:[A-Za-z0-9._~-]|%[0-9A-Fa-f]{2})+(?:/(?:[A-Za-z0-9._~-]|%[0-9A-Fa-f]{2})+)*(?![\\s\\S])"}, "kind": {"const": "artifact"}, "path": { "type": "string", From 035cd9c4b72ba9869f212c42faded81d95803e81 Mon Sep 17 00:00:00 2001 From: Rahul Krishna Date: Wed, 2 Sep 2026 17:59:22 -0400 Subject: [PATCH 08/16] feat(schema): define Helm source facts --- v2/iac/json/analysis.l1.sample.json | 88 +++++++++++ v2/iac/json/analysis.schema.json | 229 +++++++++++++++++++++++++++- 2 files changed, 315 insertions(+), 2 deletions(-) diff --git a/v2/iac/json/analysis.l1.sample.json b/v2/iac/json/analysis.l1.sample.json index fc68ffa..093efda 100644 --- a/v2/iac/json/analysis.l1.sample.json +++ b/v2/iac/json/analysis.l1.sample.json @@ -47,6 +47,94 @@ "dependencies": {}, "renders": {} } + }, + "charts/api/values.yaml": { + "id": "can://artifact/payments/charts/api/values.yaml", + "kind": "artifact", + "path": "charts/api/values.yaml", + "format": "yaml", + "source": "image:\n tag: 1.2.3x\n", + "sha256": "3d3ecdd860e9d3e2fedf29a4fb6e1667c8295ed8e500aae00901ce43e79bf497", + "size_bytes": 21, + "config_keys": { + "image.tag": { + "id": "can://artifact/payments/charts/api/values.yaml@key/image.tag", + "kind": "config_key", + "name": "tag", + "path": "image.tag", + "span": {"start": [2, 3], "end": [2, 14], "bytes": [9, 20]}, + "iac": {"kind": "helm_value"} + } + }, + "aliases": [], + "iac": { + "dialect": "helm", + "kind": "helm_values", + "status": "complete", + "roles": ["default"] + } + }, + "charts/api/templates/deployment.yaml": { + "id": "can://artifact/payments/charts/api/templates/deployment.yaml", + "kind": "artifact", + "path": "charts/api/templates/deployment.yaml", + "format": "yaml", + "source": "apiVersion: v1\nkind: Deployment\nmetadata:\n a: \n x: hello!!\n name: {{include \"api.fullname\" .}}\nspec:\n template:\n containers: # comment!\n image: {{.Values.image.tag }}\n lookup: {{lookup \"\" \"v1\" \"ConfigMap\" \"default\" \"settings\"}}\n{{define \"api.fullname\"}}api{{end}}\n", + "sha256": "e8367060ea9d4118f7bf38113b4fdccf7ea2d5f24442c580b07e487dbcdb103e", + "size_bytes": 285, + "config_keys": {}, + "aliases": [], + "iac": { + "dialect": "helm", + "kind": "helm_template", + "status": "complete", + "roles": ["resource"], + "named_templates": { + "12:1": { + "id": "can://iac/payments/helm/charts/api/templates/deployment.yaml/named-template/api.fullname", + "kind": "helm_named_template", + "name": "api.fullname", + "span": {"start": [12, 1], "end": [12, 36], "bytes": [249, 284]} + } + }, + "template_calls": { + "6:11": { + "id": "can://iac/payments/helm/charts/api/templates/deployment.yaml/template-call@6:11", + "kind": "helm_template_call", + "call_kind": "include", + "name_expression": "api.fullname", + "span": {"start": [6, 11], "end": [6, 39], "bytes": [73, 101]} + } + }, + "value_references": { + "10:16": { + "id": "can://iac/payments/helm/charts/api/templates/deployment.yaml/value-reference@10:16", + "kind": "helm_value_reference", + "path_expression": "image.tag", + "span": {"start": [10, 16], "end": [10, 33], "bytes": [162, 179]} + } + }, + "resource_templates": { + "1:1": { + "id": "can://iac/payments/helm/charts/api/templates/deployment.yaml/resource-template@1:1", + "kind": "helm_resource_template", + "document_index": 0, + "span": {"start": [1, 1], "end": [12, 1], "bytes": [0, 249]} + } + }, + "lookup_references": { + "11:15": { + "id": "can://iac/payments/helm/charts/api/templates/deployment.yaml/lookup-reference@11:15", + "kind": "helm_lookup_reference", + "group_expression": "\"\"", + "version_expression": "\"v1\"", + "resource_kind_expression": "\"ConfigMap\"", + "namespace_expression": "\"default\"", + "name_expression": "\"settings\"", + "span": {"start": [11, 15], "end": [11, 66], "bytes": [197, 248]} + } + } + } } }, "packages": {}, diff --git a/v2/iac/json/analysis.schema.json b/v2/iac/json/analysis.schema.json index fd2a77d..153570d 100644 --- a/v2/iac/json/analysis.schema.json +++ b/v2/iac/json/analysis.schema.json @@ -133,7 +133,7 @@ "additionalProperties": false, "required": ["id", "kind", "name", "path", "span"], "properties": { - "id": {"type": "string", "pattern": "^can://iac/"}, + "id": {"type": "string", "pattern": "^can://artifact/"}, "kind": {"const": "config_key"}, "name": {"type": "string", "minLength": 1}, "path": {"type": "string", "minLength": 1}, @@ -187,7 +187,14 @@ }, "IaCFacet": { "oneOf": [ - {"$ref": "#/$defs/HelmChart"} + {"$ref": "#/$defs/HelmChart"}, + {"$ref": "#/$defs/HelmRequirements"}, + {"$ref": "#/$defs/HelmLock"}, + {"$ref": "#/$defs/HelmValues"}, + {"$ref": "#/$defs/HelmValuesSchema"}, + {"$ref": "#/$defs/HelmTemplate"}, + {"$ref": "#/$defs/HelmCRD"}, + {"$ref": "#/$defs/HelmIgnore"} ] }, "HelmChart": { @@ -225,6 +232,224 @@ "email": {"type": "string", "minLength": 1}, "url": {"type": "string", "minLength": 1} } + }, + "HelmRequirements": { + "type": "object", + "additionalProperties": false, + "required": ["dialect", "kind", "status", "roles", "dependencies"], + "properties": { + "dialect": {"const": "helm"}, + "kind": {"const": "helm_requirements"}, + "status": {"enum": ["complete", "partial", "failed"]}, + "roles": {"const": ["legacy_dependency_manifest"]}, + "dependencies": {"type": "object", "additionalProperties": {"$ref": "#/$defs/HelmDependency"}} + } + }, + "HelmLock": { + "type": "object", + "additionalProperties": false, + "required": ["dialect", "kind", "status", "roles", "dependencies"], + "properties": { + "dialect": {"const": "helm"}, + "kind": {"const": "helm_lock"}, + "status": {"enum": ["complete", "partial", "failed"]}, + "roles": { + "type": "array", + "uniqueItems": true, + "items": {"enum": ["dependency_lock", "legacy_dependency_lock"]} + }, + "dependencies": {"type": "object", "additionalProperties": {"$ref": "#/$defs/HelmDependency"}} + } + }, + "HelmValues": { + "type": "object", + "additionalProperties": false, + "required": ["dialect", "kind", "status", "roles"], + "properties": { + "dialect": {"const": "helm"}, + "kind": {"const": "helm_values"}, + "status": {"enum": ["complete", "partial", "failed"]}, + "roles": { + "type": "array", + "uniqueItems": true, + "items": {"enum": ["default", "parent", "override"]} + } + } + }, + "HelmValuesSchema": { + "type": "object", + "additionalProperties": false, + "required": ["dialect", "kind", "status", "roles"], + "properties": { + "dialect": {"const": "helm"}, + "kind": {"const": "helm_values_schema"}, + "status": {"enum": ["complete", "partial", "failed"]}, + "roles": {"const": ["validation_schema"]} + } + }, + "HelmTemplate": { + "type": "object", + "additionalProperties": false, + "required": [ + "dialect", + "kind", + "status", + "roles", + "named_templates", + "template_calls", + "value_references", + "resource_templates", + "lookup_references" + ], + "properties": { + "dialect": {"const": "helm"}, + "kind": {"const": "helm_template"}, + "status": {"enum": ["complete", "partial", "failed"]}, + "roles": { + "type": "array", + "uniqueItems": true, + "items": {"enum": ["resource", "helper", "notes", "test", "hook"]} + }, + "named_templates": {"type": "object", "additionalProperties": {"$ref": "#/$defs/HelmNamedTemplate"}}, + "template_calls": {"type": "object", "additionalProperties": {"$ref": "#/$defs/HelmTemplateCall"}}, + "value_references": {"type": "object", "additionalProperties": {"$ref": "#/$defs/HelmValueReference"}}, + "resource_templates": {"type": "object", "additionalProperties": {"$ref": "#/$defs/HelmResourceTemplate"}}, + "lookup_references": {"type": "object", "additionalProperties": {"$ref": "#/$defs/HelmLookupReference"}} + } + }, + "HelmCRD": { + "type": "object", + "additionalProperties": false, + "required": ["dialect", "kind", "status", "roles"], + "properties": { + "dialect": {"const": "helm"}, + "kind": {"const": "helm_crd"}, + "status": {"enum": ["complete", "partial", "failed"]}, + "roles": {"const": ["custom_resource_definition"]} + } + }, + "HelmIgnore": { + "type": "object", + "additionalProperties": false, + "required": ["dialect", "kind", "status", "roles"], + "properties": { + "dialect": {"const": "helm"}, + "kind": {"const": "helm_ignore"}, + "status": {"enum": ["complete", "partial", "failed"]}, + "roles": {"const": ["ignore_rules"]} + } + }, + "HelmDependency": { + "type": "object", + "additionalProperties": false, + "required": ["id", "kind", "name", "version_constraint", "span"], + "properties": { + "id": {"type": "string", "pattern": "^can://iac/"}, + "kind": {"const": "helm_dependency"}, + "name": {"type": "string", "minLength": 1}, + "version_constraint": {"type": "string", "minLength": 1}, + "alias": {"type": "string", "minLength": 1}, + "repository": {"type": "string", "minLength": 1}, + "condition": {"type": "string", "minLength": 1}, + "tags": {"type": "array", "items": {"type": "string"}}, + "import_values": { + "type": "array", + "items": { + "oneOf": [ + {"type": "string"}, + { + "type": "object", + "additionalProperties": false, + "required": ["child", "parent"], + "properties": { + "child": {"type": "string"}, + "parent": {"type": "string"} + } + } + ] + } + }, + "span": {"$ref": "#/$defs/Span"} + } + }, + "HelmNamedTemplate": { + "type": "object", + "additionalProperties": false, + "required": ["id", "kind", "name", "span"], + "properties": { + "id": {"type": "string", "pattern": "^can://iac/"}, + "kind": {"const": "helm_named_template"}, + "name": {"type": "string", "minLength": 1}, + "span": {"$ref": "#/$defs/Span"} + } + }, + "HelmTemplateCall": { + "type": "object", + "additionalProperties": false, + "required": ["id", "kind", "call_kind", "name_expression", "span"], + "properties": { + "id": {"type": "string", "pattern": "^can://iac/"}, + "kind": {"const": "helm_template_call"}, + "call_kind": {"enum": ["template", "include", "block", "tpl"]}, + "name_expression": {"type": "string"}, + "target_id": {"type": "string", "pattern": "^can://iac/"}, + "span": {"$ref": "#/$defs/Span"} + } + }, + "HelmValueReference": { + "type": "object", + "additionalProperties": false, + "required": ["id", "kind", "path_expression", "span"], + "properties": { + "id": {"type": "string", "pattern": "^can://iac/"}, + "kind": {"const": "helm_value_reference"}, + "path_expression": {"type": "string", "minLength": 1}, + "target_id": {"type": "string", "pattern": "^can://(artifact|iac)/"}, + "span": {"$ref": "#/$defs/Span"} + } + }, + "HelmResourceTemplate": { + "type": "object", + "additionalProperties": false, + "required": ["id", "kind", "document_index", "span"], + "properties": { + "id": {"type": "string", "pattern": "^can://iac/"}, + "kind": {"const": "helm_resource_template"}, + "document_index": {"type": "integer", "minimum": 0}, + "span": {"$ref": "#/$defs/Span"} + } + }, + "HelmLookupReference": { + "type": "object", + "additionalProperties": false, + "required": [ + "id", + "kind", + "group_expression", + "version_expression", + "resource_kind_expression", + "namespace_expression", + "name_expression", + "span" + ], + "properties": { + "id": {"type": "string", "pattern": "^can://iac/"}, + "kind": {"const": "helm_lookup_reference"}, + "group_expression": {"type": "string"}, + "version_expression": {"type": "string"}, + "resource_kind_expression": {"type": "string"}, + "namespace_expression": {"type": "string"}, + "name_expression": {"type": "string"}, + "span": {"$ref": "#/$defs/Span"} + } + }, + "HelmValueFacet": { + "type": "object", + "additionalProperties": false, + "required": ["kind"], + "properties": { + "kind": {"const": "helm_value"} + } } } } From edb9a1683fd1381edc8d712a888aca8625476aa9 Mon Sep 17 00:00:00 2001 From: Rahul Krishna Date: Wed, 2 Sep 2026 18:05:04 -0400 Subject: [PATCH 09/16] fix(schema): constrain config key identities --- v2/iac/json/analysis.schema.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/v2/iac/json/analysis.schema.json b/v2/iac/json/analysis.schema.json index 153570d..5f3fe0f 100644 --- a/v2/iac/json/analysis.schema.json +++ b/v2/iac/json/analysis.schema.json @@ -133,7 +133,7 @@ "additionalProperties": false, "required": ["id", "kind", "name", "path", "span"], "properties": { - "id": {"type": "string", "pattern": "^can://artifact/"}, + "id": {"type": "string", "pattern": "^can://artifact/(?:[A-Za-z0-9._~-]|%[0-9A-Fa-f]{2})+/(?:[A-Za-z0-9._~-]|%[0-9A-Fa-f]{2})+(?:/(?:[A-Za-z0-9._~-]|%[0-9A-Fa-f]{2})+)*@key/(?:[A-Za-z0-9._~-]|%[0-9A-Fa-f]{2})+(?:/(?:[A-Za-z0-9._~-]|%[0-9A-Fa-f]{2})+)*(?![\\s\\S])"}, "kind": {"const": "config_key"}, "name": {"type": "string", "minLength": 1}, "path": {"type": "string", "minLength": 1}, From b5ffe00ed8954db12ca421d6cb0897d4125e279b Mon Sep 17 00:00:00 2001 From: Rahul Krishna Date: Wed, 2 Sep 2026 18:14:56 -0400 Subject: [PATCH 10/16] feat(schema): add Helm resolution and render contracts --- v2/iac/json/analysis.l2.sample.json | 187 ++++++++++++++++++++++++++++ v2/iac/json/analysis.l3.sample.json | 86 +++++++++++++ v2/iac/json/analysis.schema.json | 154 ++++++++++++++++++++++- 3 files changed, 423 insertions(+), 4 deletions(-) create mode 100644 v2/iac/json/analysis.l2.sample.json create mode 100644 v2/iac/json/analysis.l3.sample.json diff --git a/v2/iac/json/analysis.l2.sample.json b/v2/iac/json/analysis.l2.sample.json new file mode 100644 index 0000000..d3c58f4 --- /dev/null +++ b/v2/iac/json/analysis.l2.sample.json @@ -0,0 +1,187 @@ +{ + "schema_version": "2.0.0", + "language": "iac", + "max_level": 2, + "analyzer": {"name": "codeanalyzer-iac", "version": "0.1.0"}, + "application": { + "id": "can://iac/payments", + "kind": "application", + "artifacts": { + "README.md": { + "id": "can://artifact/payments/README.md", + "kind": "artifact", + "path": "README.md", + "format": "text", + "source": "payments infrastructure\n", + "sha256": "79accdf71a72e00a8b25fe1284094368e885e2fe55db0cf686a83c5359bf43df", + "size_bytes": 24, + "config_keys": {}, + "aliases": [] + }, + "charts/api/Chart.yaml": { + "id": "can://artifact/payments/charts/api/Chart.yaml", + "kind": "artifact", + "path": "charts/api/Chart.yaml", + "format": "yaml", + "source": "apiVersion: v2\nname: api\nversion: 1.2.3\ntype: application\n", + "sha256": "4a0bf85edced2838bb82ae1a52bf8830a718462ff54f4c6fb11ce539fabb53af", + "size_bytes": 58, + "config_keys": {}, + "aliases": [{ + "id": "can://iac/payments/helm/chart/charts/api", + "kind": "helm_chart", + "target": "can://artifact/payments/charts/api/Chart.yaml" + }], + "iac": { + "dialect": "helm", + "kind": "helm_chart", + "status": "complete", + "api_version": "v2", + "name": "api", + "version": "1.2.3", + "chart_type": "application", + "maintainers": [], + "keywords": [], + "sources": [], + "annotations": {}, + "dependencies": {}, + "renders": {} + } + }, + "charts/api/values.yaml": { + "id": "can://artifact/payments/charts/api/values.yaml", + "kind": "artifact", + "path": "charts/api/values.yaml", + "format": "yaml", + "source": "image:\n tag: 1.2.3x\n", + "sha256": "3d3ecdd860e9d3e2fedf29a4fb6e1667c8295ed8e500aae00901ce43e79bf497", + "size_bytes": 21, + "config_keys": { + "image.tag": { + "id": "can://artifact/payments/charts/api/values.yaml@key/image.tag", + "kind": "config_key", + "name": "tag", + "path": "image.tag", + "span": {"start": [2, 3], "end": [2, 14], "bytes": [9, 20]}, + "iac": {"kind": "helm_value"} + } + }, + "aliases": [], + "iac": { + "dialect": "helm", + "kind": "helm_values", + "status": "complete", + "roles": ["default"] + } + }, + "charts/api/templates/deployment.yaml": { + "id": "can://artifact/payments/charts/api/templates/deployment.yaml", + "kind": "artifact", + "path": "charts/api/templates/deployment.yaml", + "format": "yaml", + "source": "apiVersion: v1\nkind: Deployment\nmetadata:\n a: \n x: hello!!\n name: {{include \"api.fullname\" .}}\nspec:\n template:\n containers: # comment!\n image: {{.Values.image.tag }}\n lookup: {{lookup \"\" \"v1\" \"ConfigMap\" \"default\" \"settings\"}}\n{{define \"api.fullname\"}}api{{end}}\n", + "sha256": "e8367060ea9d4118f7bf38113b4fdccf7ea2d5f24442c580b07e487dbcdb103e", + "size_bytes": 285, + "config_keys": {}, + "aliases": [], + "iac": { + "dialect": "helm", + "kind": "helm_template", + "status": "complete", + "roles": ["resource"], + "named_templates": { + "12:1": { + "id": "can://iac/payments/helm/charts/api/templates/deployment.yaml/named-template/api.fullname", + "kind": "helm_named_template", + "name": "api.fullname", + "span": {"start": [12, 1], "end": [12, 36], "bytes": [249, 284]} + } + }, + "template_calls": { + "6:11": { + "id": "can://iac/payments/helm/charts/api/templates/deployment.yaml/template-call@6:11", + "kind": "helm_template_call", + "call_kind": "include", + "name_expression": "api.fullname", + "target_id": "can://iac/payments/helm/charts/api/templates/deployment.yaml/named-template/api.fullname", + "span": {"start": [6, 11], "end": [6, 39], "bytes": [73, 101]} + } + }, + "value_references": { + "10:16": { + "id": "can://iac/payments/helm/charts/api/templates/deployment.yaml/value-reference@10:16", + "kind": "helm_value_reference", + "path_expression": "image.tag", + "target_id": "can://artifact/payments/charts/api/values.yaml@key/image.tag", + "span": {"start": [10, 16], "end": [10, 33], "bytes": [162, 179]} + } + }, + "resource_templates": { + "1:1": { + "id": "can://iac/payments/helm/charts/api/templates/deployment.yaml/resource-template@1:1", + "kind": "helm_resource_template", + "document_index": 0, + "span": {"start": [1, 1], "end": [12, 1], "bytes": [0, 249]} + } + }, + "lookup_references": { + "11:15": { + "id": "can://iac/payments/helm/charts/api/templates/deployment.yaml/lookup-reference@11:15", + "kind": "helm_lookup_reference", + "group_expression": "\"\"", + "version_expression": "\"v1\"", + "resource_kind_expression": "\"ConfigMap\"", + "namespace_expression": "\"default\"", + "name_expression": "\"settings\"", + "span": {"start": [11, 15], "end": [11, 66], "bytes": [197, 248]} + } + } + } + } + }, + "packages": {}, + "external_chart_references": { + "bitnami/postgresql": { + "id": "can://iac/payments/helm/chart-reference/bitnami/postgresql", + "kind": "helm_chart_reference", + "name": "postgresql", + "version_constraint": "12.1.0", + "repository": "https://charts.bitnami.com/bitnami" + } + }, + "kubernetes_resource_addresses": {}, + "diagnostics": {}, + "edges": { + "iac_defines_template": { + "charts/api/templates/deployment.yaml@12:1": { + "src": "can://artifact/payments/charts/api/templates/deployment.yaml", + "dst": "can://iac/payments/helm/charts/api/templates/deployment.yaml/named-template/api.fullname" + } + }, + "iac_has_template_call": { + "charts/api/templates/deployment.yaml@6:11": { + "src": "can://artifact/payments/charts/api/templates/deployment.yaml", + "dst": "can://iac/payments/helm/charts/api/templates/deployment.yaml/template-call@6:11" + } + }, + "iac_calls_template": { + "charts/api/templates/deployment.yaml@6:11": { + "src": "can://iac/payments/helm/charts/api/templates/deployment.yaml/template-call@6:11", + "dst": "can://iac/payments/helm/charts/api/templates/deployment.yaml/named-template/api.fullname" + } + }, + "iac_has_value_reference": { + "charts/api/templates/deployment.yaml@10:16": { + "src": "can://artifact/payments/charts/api/templates/deployment.yaml", + "dst": "can://iac/payments/helm/charts/api/templates/deployment.yaml/value-reference@10:16" + } + }, + "iac_references_value": { + "charts/api/templates/deployment.yaml@10:16": { + "src": "can://iac/payments/helm/charts/api/templates/deployment.yaml/value-reference@10:16", + "dst": "can://artifact/payments/charts/api/values.yaml@key/image.tag" + } + } + } + } +} diff --git a/v2/iac/json/analysis.l3.sample.json b/v2/iac/json/analysis.l3.sample.json new file mode 100644 index 0000000..91ca876 --- /dev/null +++ b/v2/iac/json/analysis.l3.sample.json @@ -0,0 +1,86 @@ +{ + "schema_version": "2.0.0", + "language": "iac", + "max_level": 3, + "analyzer": {"name": "codeanalyzer-iac", "version": "0.1.0"}, + "application": { + "id": "can://iac/payments", + "kind": "application", + "artifacts": { + "README.md": {"id": "can://artifact/payments/README.md", "kind": "artifact", "path": "README.md", "format": "text", "source": "payments infrastructure\n", "sha256": "79accdf71a72e00a8b25fe1284094368e885e2fe55db0cf686a83c5359bf43df", "size_bytes": 24, "config_keys": {}, "aliases": []}, + "charts/api/Chart.yaml": { + "id": "can://artifact/payments/charts/api/Chart.yaml", "kind": "artifact", "path": "charts/api/Chart.yaml", "format": "yaml", "source": "apiVersion: v2\nname: api\nversion: 1.2.3\ntype: application\n", "sha256": "4a0bf85edced2838bb82ae1a52bf8830a718462ff54f4c6fb11ce539fabb53af", "size_bytes": 58, "config_keys": {}, + "aliases": [{"id": "can://iac/payments/helm/chart/charts/api", "kind": "helm_chart", "target": "can://artifact/payments/charts/api/Chart.yaml"}], + "iac": { + "dialect": "helm", "kind": "helm_chart", "status": "complete", "api_version": "v2", "name": "api", "version": "1.2.3", "chart_type": "application", "maintainers": [], "keywords": [], "sources": [], "annotations": {}, "dependencies": {}, + "render_profiles": { + "default": { + "id": "can://iac/payments/helm/render-profile/charts/api/default", "kind": "helm_render_profile", "name": "default", "origin": "default", "chart_id": "can://iac/payments/helm/chart/charts/api", "release_name": "api", "namespace": "default", + "value_layers": { + "0000": {"id": "can://iac/payments/helm/render-profile/charts/api/default/value-layer/0000", "kind": "helm_value_layer", "ordinal": 0, "source_id": "can://artifact/payments/charts/api/values.yaml"}, + "0001": {"id": "can://iac/payments/helm/render-profile/charts/api/default/value-layer/0001", "kind": "helm_value_layer", "ordinal": 1, "source_id": "can://artifact/payments/codeanalyzer-iac.yaml"} + }, + "api_versions": [] + } + }, + "renders": { + "default": { + "id": "can://iac/payments/helm/render/charts/api/default", "kind": "helm_render", "status": "succeeded", "profile_id": "can://iac/payments/helm/render-profile/charts/api/default", "renderer_name": "helm", "renderer_version": "3.14.0", "value_layer_ids": ["can://iac/payments/helm/render-profile/charts/api/default/value-layer/0000", "can://iac/payments/helm/render-profile/charts/api/default/value-layer/0001"], "effective_values_sha256": "3d3ecdd860e9d3e2fedf29a4fb6e1667c8295ed8e500aae00901ce43e79bf497", "diagnostics": {}, + "resources": { + "apps/v1/Deployment/default/api": {"id": "can://iac/payments/kubernetes-resource/apps/v1/deployment/default/api", "kind": "kubernetes_resource", "api_version": "apps/v1", "resource_kind": "Deployment", "manifest_sha256": "e1b97b673a0cfecc5150d46004a6216ada2d48f59c6593f44061139420a8506a", "render_id": "can://iac/payments/helm/render/charts/api/default", "origin_ids": ["can://iac/payments/helm/charts/api/templates/deployment.yaml/resource-template@1:1"], "namespace": "default", "name": "api", "labels": {}, "annotations": {}, "plural": "deployments", "address_id": "can://iac/payments/kubernetes-resource-address/apps/deployments/default/api"}, + "v1/Secret/default/api-credentials": {"id": "can://iac/payments/kubernetes-resource/v1/secret/default/api-credentials", "kind": "kubernetes_resource", "api_version": "v1", "resource_kind": "Secret", "manifest_sha256": "5e65acdc38d1a0309e4c39fc4db1d62d290b3906f8415de307876a614ae957f6", "render_id": "can://iac/payments/helm/render/charts/api/default", "origin_ids": ["can://iac/payments/helm/charts/api/templates/deployment.yaml/resource-template@1:1"], "namespace": "default", "name": "api-credentials", "labels": {}, "annotations": {}, "plural": "secrets", "address_id": "can://iac/payments/kubernetes-resource-address/core/secrets/default/api-credentials", "secret_data": {"password": {"key": "password", "sha256": "5994471abb01112afcc18159f6cc74b4f511b99806da59b3caf5a9c173cacfc5"}}} + } + }, + "explicit": {"id": "can://iac/payments/helm/render/charts/api/explicit", "kind": "helm_render", "status": "failed", "profile_id": "can://iac/payments/helm/render-profile/config/explicit", "renderer_name": "helm", "renderer_version": "3.14.0", "value_layer_ids": [], "effective_values_sha256": "3d3ecdd860e9d3e2fedf29a4fb6e1667c8295ed8e500aae00901ce43e79bf497", "diagnostics": {}, "resources": {}} + } + } + }, + "charts/api/values.yaml": { + "id": "can://artifact/payments/charts/api/values.yaml", "kind": "artifact", "path": "charts/api/values.yaml", "format": "yaml", "source": "image:\n tag: 1.2.3x\n", "sha256": "3d3ecdd860e9d3e2fedf29a4fb6e1667c8295ed8e500aae00901ce43e79bf497", "size_bytes": 21, + "config_keys": {"image.tag": {"id": "can://artifact/payments/charts/api/values.yaml@key/image.tag", "kind": "config_key", "name": "tag", "path": "image.tag", "span": {"start": [2, 3], "end": [2, 14], "bytes": [9, 20]}, "iac": {"kind": "helm_value"}}}, + "aliases": [], "iac": {"dialect": "helm", "kind": "helm_values", "status": "complete", "roles": ["default"]} + }, + "charts/api/templates/deployment.yaml": { + "id": "can://artifact/payments/charts/api/templates/deployment.yaml", "kind": "artifact", "path": "charts/api/templates/deployment.yaml", "format": "yaml", "source": "apiVersion: v1\nkind: Deployment\nmetadata:\n a: \n x: hello!!\n name: {{include \"api.fullname\" .}}\nspec:\n template:\n containers: # comment!\n image: {{.Values.image.tag }}\n lookup: {{lookup \"\" \"v1\" \"ConfigMap\" \"default\" \"settings\"}}\n{{define \"api.fullname\"}}api{{end}}\n", "sha256": "e8367060ea9d4118f7bf38113b4fdccf7ea2d5f24442c580b07e487dbcdb103e", "size_bytes": 285, "config_keys": {}, "aliases": [], + "iac": { + "dialect": "helm", "kind": "helm_template", "status": "complete", "roles": ["resource"], + "named_templates": {"12:1": {"id": "can://iac/payments/helm/charts/api/templates/deployment.yaml/named-template/api.fullname", "kind": "helm_named_template", "name": "api.fullname", "span": {"start": [12, 1], "end": [12, 36], "bytes": [249, 284]}}}, + "template_calls": {"6:11": {"id": "can://iac/payments/helm/charts/api/templates/deployment.yaml/template-call@6:11", "kind": "helm_template_call", "call_kind": "include", "name_expression": "api.fullname", "target_id": "can://iac/payments/helm/charts/api/templates/deployment.yaml/named-template/api.fullname", "span": {"start": [6, 11], "end": [6, 39], "bytes": [73, 101]}}}, + "value_references": {"10:16": {"id": "can://iac/payments/helm/charts/api/templates/deployment.yaml/value-reference@10:16", "kind": "helm_value_reference", "path_expression": "image.tag", "target_id": "can://artifact/payments/charts/api/values.yaml@key/image.tag", "span": {"start": [10, 16], "end": [10, 33], "bytes": [162, 179]}}}, + "resource_templates": {"1:1": {"id": "can://iac/payments/helm/charts/api/templates/deployment.yaml/resource-template@1:1", "kind": "helm_resource_template", "document_index": 0, "span": {"start": [1, 1], "end": [12, 1], "bytes": [0, 249]}}}, + "lookup_references": {"11:15": {"id": "can://iac/payments/helm/charts/api/templates/deployment.yaml/lookup-reference@11:15", "kind": "helm_lookup_reference", "group_expression": "\"\"", "version_expression": "\"v1\"", "resource_kind_expression": "\"ConfigMap\"", "namespace_expression": "\"default\"", "name_expression": "\"settings\"", "span": {"start": [11, 15], "end": [11, 66], "bytes": [197, 248]}}} + } + }, + "codeanalyzer-iac.yaml": { + "id": "can://artifact/payments/codeanalyzer-iac.yaml", "kind": "artifact", "path": "codeanalyzer-iac.yaml", "format": "yaml", "source": "render_profiles:\n explicit: {}\n", "sha256": "2b986cbe3c065eb9a5ade85b1b4ddc603774f51a356dac7ca8fbd9cb0cfbe9a2", "size_bytes": 32, "config_keys": {}, "aliases": [], + "codeanalyzer_iac_config": {"kind": "codeanalyzer_iac_config", "config_version": 1, "render_profiles": {"explicit": {"id": "can://iac/payments/helm/render-profile/config/explicit", "kind": "helm_render_profile", "name": "explicit", "origin": "config", "chart_id": "can://iac/payments/helm/chart/charts/api", "release_name": "api-explicit", "namespace": "default", "value_layers": {}, "api_versions": []}}} + } + }, + "packages": {}, + "external_chart_references": {"bitnami/postgresql": {"id": "can://iac/payments/helm/chart-reference/bitnami/postgresql", "kind": "helm_chart_reference", "name": "postgresql", "version_constraint": "12.1.0", "repository": "https://charts.bitnami.com/bitnami"}}, + "kubernetes_resource_addresses": { + "apps/deployments/default/api": {"id": "can://iac/payments/kubernetes-resource-address/apps/deployments/default/api", "kind": "kubernetes_resource_address", "group": "apps", "resource_kind": "Deployment", "namespace": "default", "name": "api", "plural": "deployments"}, + "core/secrets/default/api-credentials": {"id": "can://iac/payments/kubernetes-resource-address/core/secrets/default/api-credentials", "kind": "kubernetes_resource_address", "group": "", "resource_kind": "Secret", "namespace": "default", "name": "api-credentials", "plural": "secrets"} + }, + "diagnostics": {}, + "edges": { + "iac_defines_template": {"charts/api/templates/deployment.yaml@12:1": {"src": "can://artifact/payments/charts/api/templates/deployment.yaml", "dst": "can://iac/payments/helm/charts/api/templates/deployment.yaml/named-template/api.fullname"}}, + "iac_has_template_call": {"charts/api/templates/deployment.yaml@6:11": {"src": "can://artifact/payments/charts/api/templates/deployment.yaml", "dst": "can://iac/payments/helm/charts/api/templates/deployment.yaml/template-call@6:11"}}, + "iac_calls_template": {"charts/api/templates/deployment.yaml@6:11": {"src": "can://iac/payments/helm/charts/api/templates/deployment.yaml/template-call@6:11", "dst": "can://iac/payments/helm/charts/api/templates/deployment.yaml/named-template/api.fullname"}}, + "iac_has_value_reference": {"charts/api/templates/deployment.yaml@10:16": {"src": "can://artifact/payments/charts/api/templates/deployment.yaml", "dst": "can://iac/payments/helm/charts/api/templates/deployment.yaml/value-reference@10:16"}}, + "iac_references_value": {"charts/api/templates/deployment.yaml@10:16": {"src": "can://iac/payments/helm/charts/api/templates/deployment.yaml/value-reference@10:16", "dst": "can://artifact/payments/charts/api/values.yaml@key/image.tag"}}, + "has_artifact": {"codeanalyzer-iac.yaml": {"src": "can://iac/payments", "dst": "can://artifact/payments/codeanalyzer-iac.yaml"}}, + "defines_config": {"codeanalyzer-iac.yaml@explicit": {"src": "can://artifact/payments/codeanalyzer-iac.yaml", "dst": "can://iac/payments/helm/render-profile/config/explicit"}}, + "iac_declares_profile": {"chart@default": {"src": "can://iac/payments/helm/chart/charts/api", "dst": "can://iac/payments/helm/render-profile/charts/api/default"}}, + "iac_renders_chart": {"default": {"src": "can://iac/payments/helm/render-profile/charts/api/default", "dst": "can://iac/payments/helm/chart/charts/api"}, "explicit": {"src": "can://iac/payments/helm/render-profile/config/explicit", "dst": "can://iac/payments/helm/chart/charts/api"}}, + "iac_has_value_layer": {"default@0000": {"src": "can://iac/payments/helm/render-profile/charts/api/default", "dst": "can://iac/payments/helm/render-profile/charts/api/default/value-layer/0000"}, "default@0001": {"src": "can://iac/payments/helm/render-profile/charts/api/default", "dst": "can://iac/payments/helm/render-profile/charts/api/default/value-layer/0001"}}, + "iac_reads_from": {"default@0000": {"src": "can://iac/payments/helm/render-profile/charts/api/default/value-layer/0000", "dst": "can://artifact/payments/charts/api/values.yaml"}, "default@0001": {"src": "can://iac/payments/helm/render-profile/charts/api/default/value-layer/0001", "dst": "can://artifact/payments/codeanalyzer-iac.yaml"}}, + "iac_has_render": {"default": {"src": "can://iac/payments/helm/chart/charts/api", "dst": "can://iac/payments/helm/render/charts/api/default"}, "explicit": {"src": "can://iac/payments/helm/chart/charts/api", "dst": "can://iac/payments/helm/render/charts/api/explicit"}}, + "iac_configured_by": {"default": {"src": "can://iac/payments/helm/render/charts/api/default", "dst": "can://iac/payments/helm/render-profile/charts/api/default"}, "explicit": {"src": "can://iac/payments/helm/render/charts/api/explicit", "dst": "can://iac/payments/helm/render-profile/config/explicit"}}, + "iac_produces": {"default@deployment": {"src": "can://iac/payments/helm/render/charts/api/default", "dst": "can://iac/payments/kubernetes-resource/apps/v1/deployment/default/api"}, "default@secret": {"src": "can://iac/payments/helm/render/charts/api/default", "dst": "can://iac/payments/kubernetes-resource/v1/secret/default/api-credentials"}}, + "iac_targets_resource": {"deployment": {"src": "can://iac/payments/helm/charts/api/templates/deployment.yaml/resource-template@1:1", "dst": "can://iac/payments/kubernetes-resource/apps/v1/deployment/default/api"}, "secret": {"src": "can://iac/payments/helm/charts/api/templates/deployment.yaml/resource-template@1:1", "dst": "can://iac/payments/kubernetes-resource/v1/secret/default/api-credentials"}}, + "iac_derived_from": {"deployment": {"src": "can://iac/payments/kubernetes-resource/apps/v1/deployment/default/api", "dst": "can://iac/payments/helm/charts/api/templates/deployment.yaml/resource-template@1:1"}, "secret": {"src": "can://iac/payments/kubernetes-resource/v1/secret/default/api-credentials", "dst": "can://iac/payments/helm/charts/api/templates/deployment.yaml/resource-template@1:1"}}, + "iac_alias_of": {"deployment": {"src": "can://iac/payments/kubernetes-resource/apps/v1/deployment/default/api", "dst": "can://iac/payments/kubernetes-resource-address/apps/deployments/default/api"}, "secret": {"src": "can://iac/payments/kubernetes-resource/v1/secret/default/api-credentials", "dst": "can://iac/payments/kubernetes-resource-address/core/secrets/default/api-credentials"}} + } + } +} diff --git a/v2/iac/json/analysis.schema.json b/v2/iac/json/analysis.schema.json index 5f3fe0f..58f27ad 100644 --- a/v2/iac/json/analysis.schema.json +++ b/v2/iac/json/analysis.schema.json @@ -70,10 +70,7 @@ "type": "object", "additionalProperties": {"$ref": "#/$defs/Diagnostic"} }, - "edges": { - "type": "object", - "additionalProperties": {"$ref": "#/$defs/Edge"} - } + "edges": {"$ref": "#/$defs/Edges"} } }, "Artifact": { @@ -220,6 +217,7 @@ "deprecated": {"type": "boolean"}, "annotations": {"type": "object", "additionalProperties": {"type": "string"}}, "dependencies": {"type": "object", "additionalProperties": {"$ref": "#/$defs/HelmDependency"}}, + "render_profiles": {"type": "object", "additionalProperties": {"$ref": "#/$defs/HelmRenderProfile"}}, "renders": {"type": "object", "additionalProperties": {"$ref": "#/$defs/HelmRender"}} } }, @@ -443,6 +441,154 @@ "span": {"$ref": "#/$defs/Span"} } }, + "EdgeMap": { + "type": "object", + "additionalProperties": {"$ref": "#/$defs/Edge"} + }, + "Edges": { + "type": "object", + "additionalProperties": false, + "properties": { + "has_artifact": {"$ref": "#/$defs/EdgeMap"}, + "defines_config": {"$ref": "#/$defs/EdgeMap"}, + "iac_part_of_chart": {"$ref": "#/$defs/EdgeMap"}, + "iac_declares_dependency": {"$ref": "#/$defs/EdgeMap"}, + "iac_targets_chart_reference": {"$ref": "#/$defs/EdgeMap"}, + "iac_resolves_to_chart": {"$ref": "#/$defs/EdgeMap"}, + "iac_identified_by_package": {"$ref": "#/$defs/EdgeMap"}, + "iac_defines_template": {"$ref": "#/$defs/EdgeMap"}, + "iac_has_template_call": {"$ref": "#/$defs/EdgeMap"}, + "iac_calls_template": {"$ref": "#/$defs/EdgeMap"}, + "iac_has_value_reference": {"$ref": "#/$defs/EdgeMap"}, + "iac_references_value": {"$ref": "#/$defs/EdgeMap"}, + "iac_declares_profile": {"$ref": "#/$defs/EdgeMap"}, + "iac_renders_chart": {"$ref": "#/$defs/EdgeMap"}, + "iac_has_value_layer": {"$ref": "#/$defs/EdgeMap"}, + "iac_reads_from": {"$ref": "#/$defs/EdgeMap"}, + "iac_has_render": {"$ref": "#/$defs/EdgeMap"}, + "iac_configured_by": {"$ref": "#/$defs/EdgeMap"}, + "iac_has_diagnostic": {"$ref": "#/$defs/EdgeMap"}, + "iac_produces": {"$ref": "#/$defs/EdgeMap"}, + "iac_targets_resource": {"$ref": "#/$defs/EdgeMap"}, + "iac_derived_from": {"$ref": "#/$defs/EdgeMap"}, + "iac_alias_of": {"$ref": "#/$defs/EdgeMap"} + } + }, + "HelmChartReference": { + "type": "object", + "additionalProperties": false, + "required": ["id", "kind", "name", "version_constraint"], + "properties": { + "id": {"type": "string", "pattern": "^can://iac/"}, + "kind": {"const": "helm_chart_reference"}, + "name": {"type": "string", "minLength": 1}, + "version_constraint": {"type": "string", "minLength": 1}, + "repository": {"type": "string", "minLength": 1}, + "purl": {"type": "string", "pattern": "^pkg:[a-z][a-z0-9.+-]*/.+"}, + "resolved_chart_id": {"type": "string", "pattern": "^can://iac/"} + } + }, + "CodeAnalyzerIaCConfig": { + "type": "object", + "additionalProperties": false, + "required": ["kind", "config_version", "render_profiles"], + "properties": { + "kind": {"const": "codeanalyzer_iac_config"}, + "config_version": {"const": 1}, + "render_profiles": {"type": "object", "additionalProperties": {"$ref": "#/$defs/HelmRenderProfile"}} + } + }, + "HelmRenderProfile": { + "type": "object", + "additionalProperties": false, + "required": ["id", "kind", "name", "origin", "chart_id", "release_name", "namespace", "value_layers", "api_versions"], + "properties": { + "id": {"type": "string", "pattern": "^can://iac/"}, + "kind": {"const": "helm_render_profile"}, + "name": {"type": "string", "minLength": 1}, + "origin": {"enum": ["default", "config"]}, + "chart_id": {"type": "string", "pattern": "^can://iac/"}, + "release_name": {"type": "string", "minLength": 1}, + "namespace": {"type": "string", "minLength": 1}, + "value_layers": {"type": "object", "additionalProperties": {"$ref": "#/$defs/HelmValueLayer"}}, + "api_versions": {"type": "array", "items": {"type": "string", "minLength": 1}}, + "kube_version": {"type": "string", "minLength": 1} + } + }, + "HelmValueLayer": { + "type": "object", + "additionalProperties": false, + "required": ["id", "kind", "ordinal", "source_id"], + "properties": { + "id": {"type": "string", "pattern": "^can://iac/"}, + "kind": {"const": "helm_value_layer"}, + "ordinal": {"type": "integer", "minimum": 0}, + "source_id": {"type": "string", "pattern": "^can://(artifact|iac)/"} + } + }, + "HelmRender": { + "type": "object", + "additionalProperties": false, + "required": ["id", "kind", "status", "profile_id", "renderer_name", "renderer_version", "value_layer_ids", "effective_values_sha256", "diagnostics", "resources"], + "properties": { + "id": {"type": "string", "pattern": "^can://iac/"}, + "kind": {"const": "helm_render"}, + "status": {"enum": ["succeeded", "partial", "failed", "skipped"]}, + "profile_id": {"type": "string", "pattern": "^can://iac/"}, + "renderer_name": {"type": "string", "minLength": 1}, + "renderer_version": {"type": "string", "minLength": 1}, + "value_layer_ids": {"type": "array", "items": {"type": "string", "pattern": "^can://iac/"}}, + "effective_values_sha256": {"type": "string", "pattern": "^[0-9a-f]{64}$"}, + "diagnostics": {"type": "object", "additionalProperties": {"$ref": "#/$defs/Diagnostic"}}, + "resources": {"type": "object", "additionalProperties": {"$ref": "#/$defs/KubernetesResource"}}, + "phase": {"enum": ["load", "dependency", "values", "template", "decode"]} + } + }, + "KubernetesResource": { + "type": "object", + "additionalProperties": false, + "required": ["id", "kind", "api_version", "resource_kind", "manifest_sha256", "render_id", "origin_ids"], + "properties": { + "id": {"type": "string", "pattern": "^can://iac/"}, + "kind": {"const": "kubernetes_resource"}, + "api_version": {"type": "string", "minLength": 1}, + "resource_kind": {"type": "string", "minLength": 1}, + "manifest_sha256": {"type": "string", "pattern": "^[0-9a-f]{64}$"}, + "render_id": {"type": "string", "pattern": "^can://iac/"}, + "origin_ids": {"type": "array", "items": {"type": "string", "pattern": "^can://(artifact|iac)/"}}, + "namespace": {"type": "string", "minLength": 1}, + "name": {"type": "string", "minLength": 1}, + "generate_name": {"type": "string", "minLength": 1}, + "labels": {"type": "object", "additionalProperties": {"type": "string"}}, + "annotations": {"type": "object", "additionalProperties": {"type": "string"}}, + "plural": {"type": "string", "minLength": 1}, + "address_id": {"type": "string", "pattern": "^can://iac/"}, + "secret_data": {"type": "object", "additionalProperties": {"$ref": "#/$defs/KubernetesSecretDatum"}} + } + }, + "KubernetesSecretDatum": { + "type": "object", + "additionalProperties": false, + "required": ["key", "sha256"], + "properties": { + "key": {"type": "string", "minLength": 1}, + "sha256": {"type": "string", "pattern": "^[0-9a-f]{64}$"} + } + }, + "KubernetesResourceAddress": { + "type": "object", + "additionalProperties": false, + "required": ["id", "kind", "group", "resource_kind", "namespace", "name"], + "properties": { + "id": {"type": "string", "pattern": "^can://iac/"}, + "kind": {"const": "kubernetes_resource_address"}, + "group": {"type": "string"}, + "resource_kind": {"type": "string", "minLength": 1}, + "namespace": {"type": "string", "minLength": 1}, + "name": {"type": "string", "minLength": 1}, + "plural": {"type": "string", "minLength": 1} + } + }, "HelmValueFacet": { "type": "object", "additionalProperties": false, From 955b7682dc36b4482026c81249a40a13d79748d5 Mon Sep 17 00:00:00 2001 From: Rahul Krishna Date: Wed, 2 Sep 2026 18:24:05 -0400 Subject: [PATCH 11/16] feat(schema): register IaC Neo4j catalog --- v2/iac/neo4j/contract.schema.json | 72 ++++++++++++++++++++++ v2/iac/neo4j/schema.neo4j.sample.json | 87 +++++++++++++++++++++++++++ 2 files changed, 159 insertions(+) create mode 100644 v2/iac/neo4j/contract.schema.json create mode 100644 v2/iac/neo4j/schema.neo4j.sample.json diff --git a/v2/iac/neo4j/contract.schema.json b/v2/iac/neo4j/contract.schema.json new file mode 100644 index 0000000..ed52100 --- /dev/null +++ b/v2/iac/neo4j/contract.schema.json @@ -0,0 +1,72 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://codellm-devkit.github.io/schema/v2/iac/neo4j/contract.schema.json", + "title": "codeanalyzer-iac Neo4j graph contract", + "x-cldk": { + "kind": "neo4j-graph-contract", + "validates": ["schema.neo4j.sample.json"] + }, + "type": "object", + "additionalProperties": false, + "required": ["schema_version", "generator", "marker_labels", "node_labels", "relationship_types", "constraints", "indexes"], + "properties": { + "schema_version": {"const": "1.0.0"}, + "generator": {"const": "codeanalyzer-iac"}, + "marker_labels": {"type": "array", "items": {"type": "string"}, "uniqueItems": true}, + "node_labels": {"type": "array", "items": {"$ref": "#/$defs/NodeLabel"}, "minItems": 1}, + "relationship_types": {"type": "array", "items": {"$ref": "#/$defs/RelationshipType"}, "minItems": 1}, + "constraints": {"type": "array", "items": {"type": "string", "pattern": "^CREATE CONSTRAINT "}}, + "indexes": {"type": "array", "items": {"type": "string", "pattern": "^CREATE (INDEX|FULLTEXT INDEX|VECTOR INDEX) "}} + }, + "$defs": { + "NodeLabel": { + "type": "object", + "additionalProperties": false, + "required": ["label", "merge_label", "key", "properties"], + "properties": { + "label": {"$ref": "#/$defs/LabelName"}, + "merge_label": {"$ref": "#/$defs/LabelName"}, + "key": {"type": "string", "minLength": 1}, + "properties": { + "type": "object", + "additionalProperties": {"$ref": "#/$defs/PropertyType"} + } + } + }, + "RelationshipType": { + "type": "object", + "additionalProperties": false, + "required": ["type", "from", "to", "properties"], + "properties": { + "type": { + "type": "string", + "pattern": "^(HAS_ARTIFACT|DEFINES_CONFIG|IAC_[A-Z0-9_]+)$" + }, + "from": { + "type": "array", + "items": {"$ref": "#/$defs/LabelName"}, + "minItems": 1, + "uniqueItems": true + }, + "to": { + "type": "array", + "items": {"$ref": "#/$defs/LabelName"}, + "minItems": 1, + "uniqueItems": true + }, + "properties": { + "type": "object", + "additionalProperties": {"$ref": "#/$defs/PropertyType"} + } + } + }, + "LabelName": { + "type": "string", + "pattern": "^(Application|Artifact|ConfigKey|Package|IdentityAlias|(IaC|Helm|Kubernetes|CodeAnalyzerIaC)[A-Za-z0-9_]*)$" + }, + "PropertyType": { + "type": "string", + "enum": ["string", "integer", "float", "boolean", "string[]", "integer[]", "float[]", "boolean[]"] + } + } +} diff --git a/v2/iac/neo4j/schema.neo4j.sample.json b/v2/iac/neo4j/schema.neo4j.sample.json new file mode 100644 index 0000000..6f6028b --- /dev/null +++ b/v2/iac/neo4j/schema.neo4j.sample.json @@ -0,0 +1,87 @@ +{ + "schema_version": "1.0.0", + "generator": "codeanalyzer-iac", + "marker_labels": [], + "node_labels": [ + {"label": "Application", "merge_label": "Application", "key": "id", "properties": {"id": "string"}}, + {"label": "IaCApplication", "merge_label": "Application", "key": "id", "properties": {"id": "string"}}, + {"label": "Artifact", "merge_label": "Artifact", "key": "id", "properties": {"id": "string"}}, + {"label": "IaCArtifact", "merge_label": "Artifact", "key": "id", "properties": {"id": "string"}}, + {"label": "HelmArtifact", "merge_label": "Artifact", "key": "id", "properties": {"id": "string"}}, + {"label": "HelmChart", "merge_label": "Artifact", "key": "id", "properties": {"id": "string"}}, + {"label": "HelmRequirements", "merge_label": "Artifact", "key": "id", "properties": {"id": "string"}}, + {"label": "HelmLock", "merge_label": "Artifact", "key": "id", "properties": {"id": "string"}}, + {"label": "HelmValues", "merge_label": "Artifact", "key": "id", "properties": {"id": "string"}}, + {"label": "HelmValuesSchema", "merge_label": "Artifact", "key": "id", "properties": {"id": "string"}}, + {"label": "HelmTemplate", "merge_label": "Artifact", "key": "id", "properties": {"id": "string"}}, + {"label": "HelmCRD", "merge_label": "Artifact", "key": "id", "properties": {"id": "string"}}, + {"label": "HelmIgnore", "merge_label": "Artifact", "key": "id", "properties": {"id": "string"}}, + {"label": "ConfigKey", "merge_label": "ConfigKey", "key": "id", "properties": {"id": "string"}}, + {"label": "IaCValue", "merge_label": "ConfigKey", "key": "id", "properties": {"id": "string"}}, + {"label": "HelmValue", "merge_label": "ConfigKey", "key": "id", "properties": {"id": "string"}}, + {"label": "CodeAnalyzerIaCConfig", "merge_label": "Artifact", "key": "id", "properties": {"id": "string"}}, + {"label": "HelmDependency", "merge_label": "HelmDependency", "key": "id", "properties": {"id": "string"}}, + {"label": "HelmChartReference", "merge_label": "HelmChartReference", "key": "id", "properties": {"id": "string"}}, + {"label": "HelmNamedTemplate", "merge_label": "HelmNamedTemplate", "key": "id", "properties": {"id": "string"}}, + {"label": "HelmTemplateCall", "merge_label": "HelmTemplateCall", "key": "id", "properties": {"id": "string"}}, + {"label": "HelmValueReference", "merge_label": "HelmValueReference", "key": "id", "properties": {"id": "string"}}, + {"label": "HelmResourceTemplate", "merge_label": "HelmResourceTemplate", "key": "id", "properties": {"id": "string"}}, + {"label": "HelmLookupReference", "merge_label": "HelmLookupReference", "key": "id", "properties": {"id": "string"}}, + {"label": "HelmRenderProfile", "merge_label": "HelmRenderProfile", "key": "id", "properties": {"id": "string", "origin": "string"}}, + {"label": "HelmValueLayer", "merge_label": "HelmValueLayer", "key": "id", "properties": {"id": "string", "ordinal": "integer", "source_id": "string"}}, + {"label": "HelmRender", "merge_label": "HelmRender", "key": "id", "properties": {"id": "string", "status": "string", "profile_id": "string", "value_layer_ids": "string[]", "effective_values_sha256": "string"}}, + {"label": "IaCDiagnostic", "merge_label": "IaCDiagnostic", "key": "id", "properties": {"id": "string", "severity": "string", "code": "string", "message": "string"}}, + {"label": "HelmDiagnostic", "merge_label": "IaCDiagnostic", "key": "id", "properties": {"id": "string", "severity": "string", "code": "string", "message": "string"}}, + {"label": "KubernetesResource", "merge_label": "KubernetesResource", "key": "id", "properties": {"id": "string", "api_version": "string", "resource_kind": "string", "manifest_sha256": "string", "render_id": "string", "origin_ids": "string[]"}}, + {"label": "KubernetesResourceAddress", "merge_label": "KubernetesResourceAddress", "key": "id", "properties": {"id": "string", "group": "string", "resource_kind": "string", "namespace": "string", "name": "string"}}, + {"label": "IdentityAlias", "merge_label": "IdentityAlias", "key": "id", "properties": {"id": "string", "target": "string"}}, + {"label": "IaCAlias", "merge_label": "IdentityAlias", "key": "id", "properties": {"id": "string", "target": "string"}}, + {"label": "Package", "merge_label": "Package", "key": "id", "properties": {"id": "string", "purl": "string"}} + ], + "relationship_types": [ + {"type": "HAS_ARTIFACT", "from": ["IaCApplication"], "to": ["Artifact"], "properties": {}}, + {"type": "DEFINES_CONFIG", "from": ["Artifact"], "to": ["ConfigKey"], "properties": {}}, + {"type": "IAC_PART_OF_CHART", "from": ["HelmArtifact"], "to": ["HelmChart"], "properties": {}}, + {"type": "IAC_DECLARES_DEPENDENCY", "from": ["HelmChart"], "to": ["HelmDependency"], "properties": {}}, + {"type": "IAC_TARGETS_CHART_REFERENCE", "from": ["HelmDependency"], "to": ["HelmChartReference"], "properties": {}}, + {"type": "IAC_RESOLVES_TO_CHART", "from": ["HelmChartReference"], "to": ["HelmChart"], "properties": {}}, + {"type": "IAC_IDENTIFIED_BY_PACKAGE", "from": ["HelmChartReference"], "to": ["Package"], "properties": {}}, + {"type": "IAC_DEFINES_TEMPLATE", "from": ["HelmTemplate"], "to": ["HelmNamedTemplate"], "properties": {}}, + {"type": "IAC_HAS_TEMPLATE_CALL", "from": ["HelmTemplate"], "to": ["HelmTemplateCall"], "properties": {}}, + {"type": "IAC_CALLS_TEMPLATE", "from": ["HelmTemplateCall"], "to": ["HelmNamedTemplate"], "properties": {}}, + {"type": "IAC_HAS_VALUE_REFERENCE", "from": ["HelmTemplate"], "to": ["HelmValueReference"], "properties": {}}, + {"type": "IAC_REFERENCES_VALUE", "from": ["HelmValueReference"], "to": ["HelmValue"], "properties": {}}, + {"type": "IAC_DECLARES_PROFILE", "from": ["CodeAnalyzerIaCConfig", "HelmChart"], "to": ["HelmRenderProfile"], "properties": {}}, + {"type": "IAC_RENDERS_CHART", "from": ["HelmRenderProfile"], "to": ["HelmChart"], "properties": {}}, + {"type": "IAC_HAS_VALUE_LAYER", "from": ["HelmRenderProfile"], "to": ["HelmValueLayer"], "properties": {}}, + {"type": "IAC_READS_FROM", "from": ["HelmValueLayer"], "to": ["Artifact", "ConfigKey"], "properties": {}}, + {"type": "IAC_HAS_RENDER", "from": ["HelmChart"], "to": ["HelmRender"], "properties": {}}, + {"type": "IAC_CONFIGURED_BY", "from": ["HelmRender"], "to": ["HelmRenderProfile"], "properties": {}}, + {"type": "IAC_HAS_DIAGNOSTIC", "from": ["Artifact", "HelmRender"], "to": ["HelmDiagnostic"], "properties": {}}, + {"type": "IAC_PRODUCES", "from": ["HelmRender"], "to": ["KubernetesResource"], "properties": {}}, + {"type": "IAC_TARGETS_RESOURCE", "from": ["KubernetesResource"], "to": ["KubernetesResourceAddress"], "properties": {}}, + {"type": "IAC_DERIVED_FROM", "from": ["KubernetesResource"], "to": ["HelmResourceTemplate"], "properties": {}}, + {"type": "IAC_ALIAS_OF", "from": ["IaCAlias"], "to": ["Artifact", "KubernetesResourceAddress"], "properties": {}} + ], + "constraints": [ + "CREATE CONSTRAINT application_id IF NOT EXISTS FOR (n:Application) REQUIRE n.id IS UNIQUE", + "CREATE CONSTRAINT artifact_id IF NOT EXISTS FOR (n:Artifact) REQUIRE n.id IS UNIQUE", + "CREATE CONSTRAINT config_key_id IF NOT EXISTS FOR (n:ConfigKey) REQUIRE n.id IS UNIQUE", + "CREATE CONSTRAINT helm_dependency_id IF NOT EXISTS FOR (n:HelmDependency) REQUIRE n.id IS UNIQUE", + "CREATE CONSTRAINT helm_chart_reference_id IF NOT EXISTS FOR (n:HelmChartReference) REQUIRE n.id IS UNIQUE", + "CREATE CONSTRAINT helm_named_template_id IF NOT EXISTS FOR (n:HelmNamedTemplate) REQUIRE n.id IS UNIQUE", + "CREATE CONSTRAINT helm_template_call_id IF NOT EXISTS FOR (n:HelmTemplateCall) REQUIRE n.id IS UNIQUE", + "CREATE CONSTRAINT helm_value_reference_id IF NOT EXISTS FOR (n:HelmValueReference) REQUIRE n.id IS UNIQUE", + "CREATE CONSTRAINT helm_resource_template_id IF NOT EXISTS FOR (n:HelmResourceTemplate) REQUIRE n.id IS UNIQUE", + "CREATE CONSTRAINT helm_lookup_reference_id IF NOT EXISTS FOR (n:HelmLookupReference) REQUIRE n.id IS UNIQUE", + "CREATE CONSTRAINT helm_render_profile_id IF NOT EXISTS FOR (n:HelmRenderProfile) REQUIRE n.id IS UNIQUE", + "CREATE CONSTRAINT helm_value_layer_id IF NOT EXISTS FOR (n:HelmValueLayer) REQUIRE n.id IS UNIQUE", + "CREATE CONSTRAINT helm_render_id IF NOT EXISTS FOR (n:HelmRender) REQUIRE n.id IS UNIQUE", + "CREATE CONSTRAINT iac_diagnostic_id IF NOT EXISTS FOR (n:IaCDiagnostic) REQUIRE n.id IS UNIQUE", + "CREATE CONSTRAINT kubernetes_resource_id IF NOT EXISTS FOR (n:KubernetesResource) REQUIRE n.id IS UNIQUE", + "CREATE CONSTRAINT kubernetes_resource_address_id IF NOT EXISTS FOR (n:KubernetesResourceAddress) REQUIRE n.id IS UNIQUE", + "CREATE CONSTRAINT identity_alias_id IF NOT EXISTS FOR (n:IdentityAlias) REQUIRE n.id IS UNIQUE", + "CREATE CONSTRAINT package_id IF NOT EXISTS FOR (n:Package) REQUIRE n.id IS UNIQUE" + ], + "indexes": [] +} From c1cc3036f74c2ae4a3a6e79af3dd8fd0bce721d9 Mon Sep 17 00:00:00 2001 From: Rahul Krishna Date: Wed, 2 Sep 2026 18:30:52 -0400 Subject: [PATCH 12/16] fix(schema): align IaC fixture graph identities --- v2/iac/json/analysis.l1.sample.json | 21 +++++++++++++++++- v2/iac/json/analysis.l2.sample.json | 18 +++++++++++++++ v2/iac/json/analysis.l3.sample.json | 34 ++++++++++++++--------------- v2/iac/json/analysis.schema.json | 2 +- 4 files changed, 56 insertions(+), 19 deletions(-) diff --git a/v2/iac/json/analysis.l1.sample.json b/v2/iac/json/analysis.l1.sample.json index 093efda..859238a 100644 --- a/v2/iac/json/analysis.l1.sample.json +++ b/v2/iac/json/analysis.l1.sample.json @@ -141,6 +141,25 @@ "external_chart_references": {}, "kubernetes_resource_addresses": {}, "diagnostics": {}, - "edges": {} + "edges": { + "has_artifact": { + "README.md": {"src": "can://iac/payments", "dst": "can://artifact/payments/README.md"}, + "charts/api/Chart.yaml": {"src": "can://iac/payments", "dst": "can://artifact/payments/charts/api/Chart.yaml"}, + "charts/api/values.yaml": {"src": "can://iac/payments", "dst": "can://artifact/payments/charts/api/values.yaml"}, + "charts/api/templates/deployment.yaml": {"src": "can://iac/payments", "dst": "can://artifact/payments/charts/api/templates/deployment.yaml"} + }, + "defines_config": { + "charts/api/values.yaml@key/image.tag": { + "src": "can://artifact/payments/charts/api/values.yaml", + "dst": "can://artifact/payments/charts/api/values.yaml@key/image.tag" + } + }, + "iac_alias_of": { + "charts/api/Chart.yaml@helm-chart": { + "src": "can://iac/payments/helm/chart/charts/api", + "dst": "can://artifact/payments/charts/api/Chart.yaml" + } + } + } } } diff --git a/v2/iac/json/analysis.l2.sample.json b/v2/iac/json/analysis.l2.sample.json index d3c58f4..9d15e61 100644 --- a/v2/iac/json/analysis.l2.sample.json +++ b/v2/iac/json/analysis.l2.sample.json @@ -152,6 +152,24 @@ "kubernetes_resource_addresses": {}, "diagnostics": {}, "edges": { + "has_artifact": { + "README.md": {"src": "can://iac/payments", "dst": "can://artifact/payments/README.md"}, + "charts/api/Chart.yaml": {"src": "can://iac/payments", "dst": "can://artifact/payments/charts/api/Chart.yaml"}, + "charts/api/values.yaml": {"src": "can://iac/payments", "dst": "can://artifact/payments/charts/api/values.yaml"}, + "charts/api/templates/deployment.yaml": {"src": "can://iac/payments", "dst": "can://artifact/payments/charts/api/templates/deployment.yaml"} + }, + "defines_config": { + "charts/api/values.yaml@key/image.tag": { + "src": "can://artifact/payments/charts/api/values.yaml", + "dst": "can://artifact/payments/charts/api/values.yaml@key/image.tag" + } + }, + "iac_alias_of": { + "charts/api/Chart.yaml@helm-chart": { + "src": "can://iac/payments/helm/chart/charts/api", + "dst": "can://artifact/payments/charts/api/Chart.yaml" + } + }, "iac_defines_template": { "charts/api/templates/deployment.yaml@12:1": { "src": "can://artifact/payments/charts/api/templates/deployment.yaml", diff --git a/v2/iac/json/analysis.l3.sample.json b/v2/iac/json/analysis.l3.sample.json index 91ca876..0088037 100644 --- a/v2/iac/json/analysis.l3.sample.json +++ b/v2/iac/json/analysis.l3.sample.json @@ -15,7 +15,7 @@ "dialect": "helm", "kind": "helm_chart", "status": "complete", "api_version": "v2", "name": "api", "version": "1.2.3", "chart_type": "application", "maintainers": [], "keywords": [], "sources": [], "annotations": {}, "dependencies": {}, "render_profiles": { "default": { - "id": "can://iac/payments/helm/render-profile/charts/api/default", "kind": "helm_render_profile", "name": "default", "origin": "default", "chart_id": "can://iac/payments/helm/chart/charts/api", "release_name": "api", "namespace": "default", + "id": "can://iac/payments/helm/render-profile/charts/api/default", "kind": "helm_render_profile", "name": "default", "origin": "default", "chart_id": "can://artifact/payments/charts/api/Chart.yaml", "release_name": "api", "namespace": "default", "value_layers": { "0000": {"id": "can://iac/payments/helm/render-profile/charts/api/default/value-layer/0000", "kind": "helm_value_layer", "ordinal": 0, "source_id": "can://artifact/payments/charts/api/values.yaml"}, "0001": {"id": "can://iac/payments/helm/render-profile/charts/api/default/value-layer/0001", "kind": "helm_value_layer", "ordinal": 1, "source_id": "can://artifact/payments/codeanalyzer-iac.yaml"} @@ -25,13 +25,13 @@ }, "renders": { "default": { - "id": "can://iac/payments/helm/render/charts/api/default", "kind": "helm_render", "status": "succeeded", "profile_id": "can://iac/payments/helm/render-profile/charts/api/default", "renderer_name": "helm", "renderer_version": "3.14.0", "value_layer_ids": ["can://iac/payments/helm/render-profile/charts/api/default/value-layer/0000", "can://iac/payments/helm/render-profile/charts/api/default/value-layer/0001"], "effective_values_sha256": "3d3ecdd860e9d3e2fedf29a4fb6e1667c8295ed8e500aae00901ce43e79bf497", "diagnostics": {}, + "id": "can://iac/payments/helm/render/charts/api/default", "kind": "helm_render", "status": "succeeded", "profile_id": "can://iac/payments/helm/render-profile/charts/api/default", "renderer_name": "helm", "renderer_version": "4.2.4", "value_layer_ids": ["can://iac/payments/helm/render-profile/charts/api/default/value-layer/0000", "can://iac/payments/helm/render-profile/charts/api/default/value-layer/0001"], "effective_values_sha256": "3d3ecdd860e9d3e2fedf29a4fb6e1667c8295ed8e500aae00901ce43e79bf497", "diagnostics": {}, "resources": { - "apps/v1/Deployment/default/api": {"id": "can://iac/payments/kubernetes-resource/apps/v1/deployment/default/api", "kind": "kubernetes_resource", "api_version": "apps/v1", "resource_kind": "Deployment", "manifest_sha256": "e1b97b673a0cfecc5150d46004a6216ada2d48f59c6593f44061139420a8506a", "render_id": "can://iac/payments/helm/render/charts/api/default", "origin_ids": ["can://iac/payments/helm/charts/api/templates/deployment.yaml/resource-template@1:1"], "namespace": "default", "name": "api", "labels": {}, "annotations": {}, "plural": "deployments", "address_id": "can://iac/payments/kubernetes-resource-address/apps/deployments/default/api"}, - "v1/Secret/default/api-credentials": {"id": "can://iac/payments/kubernetes-resource/v1/secret/default/api-credentials", "kind": "kubernetes_resource", "api_version": "v1", "resource_kind": "Secret", "manifest_sha256": "5e65acdc38d1a0309e4c39fc4db1d62d290b3906f8415de307876a614ae957f6", "render_id": "can://iac/payments/helm/render/charts/api/default", "origin_ids": ["can://iac/payments/helm/charts/api/templates/deployment.yaml/resource-template@1:1"], "namespace": "default", "name": "api-credentials", "labels": {}, "annotations": {}, "plural": "secrets", "address_id": "can://iac/payments/kubernetes-resource-address/core/secrets/default/api-credentials", "secret_data": {"password": {"key": "password", "sha256": "5994471abb01112afcc18159f6cc74b4f511b99806da59b3caf5a9c173cacfc5"}}} + "apps/Deployment/default/api": {"id": "can://iac/payments/helm/render/charts/api/default/kubernetes/apps/Deployment/default/api", "kind": "kubernetes_resource", "api_version": "apps/v1", "resource_kind": "Deployment", "manifest_sha256": "e1b97b673a0cfecc5150d46004a6216ada2d48f59c6593f44061139420a8506a", "render_id": "can://iac/payments/helm/render/charts/api/default", "origin_ids": ["can://iac/payments/helm/charts/api/templates/deployment.yaml/resource-template@1:1"], "namespace": "default", "name": "api", "labels": {}, "annotations": {}, "plural": "deployments", "address_id": "can://iac/payments/kubernetes/address/apps/Deployment/default/api"}, + "core/Secret/default/api-credentials": {"id": "can://iac/payments/helm/render/charts/api/default/kubernetes/core/Secret/default/api-credentials", "kind": "kubernetes_resource", "api_version": "v1", "resource_kind": "Secret", "manifest_sha256": "5e65acdc38d1a0309e4c39fc4db1d62d290b3906f8415de307876a614ae957f6", "render_id": "can://iac/payments/helm/render/charts/api/default", "origin_ids": ["can://iac/payments/helm/charts/api/templates/deployment.yaml/resource-template@1:1"], "namespace": "default", "name": "api-credentials", "labels": {}, "annotations": {}, "plural": "secrets", "address_id": "can://iac/payments/kubernetes/address/core/Secret/default/api-credentials", "secret_data": {"password": {"key": "password", "sha256": "5994471abb01112afcc18159f6cc74b4f511b99806da59b3caf5a9c173cacfc5"}}} } }, - "explicit": {"id": "can://iac/payments/helm/render/charts/api/explicit", "kind": "helm_render", "status": "failed", "profile_id": "can://iac/payments/helm/render-profile/config/explicit", "renderer_name": "helm", "renderer_version": "3.14.0", "value_layer_ids": [], "effective_values_sha256": "3d3ecdd860e9d3e2fedf29a4fb6e1667c8295ed8e500aae00901ce43e79bf497", "diagnostics": {}, "resources": {}} + "explicit": {"id": "can://iac/payments/helm/render/charts/api/explicit", "kind": "helm_render", "status": "failed", "profile_id": "can://iac/payments/helm/render-profile/config/explicit", "renderer_name": "helm", "renderer_version": "4.2.4", "value_layer_ids": [], "effective_values_sha256": "3d3ecdd860e9d3e2fedf29a4fb6e1667c8295ed8e500aae00901ce43e79bf497", "diagnostics": {}, "resources": {}} } } }, @@ -53,14 +53,14 @@ }, "codeanalyzer-iac.yaml": { "id": "can://artifact/payments/codeanalyzer-iac.yaml", "kind": "artifact", "path": "codeanalyzer-iac.yaml", "format": "yaml", "source": "render_profiles:\n explicit: {}\n", "sha256": "2b986cbe3c065eb9a5ade85b1b4ddc603774f51a356dac7ca8fbd9cb0cfbe9a2", "size_bytes": 32, "config_keys": {}, "aliases": [], - "codeanalyzer_iac_config": {"kind": "codeanalyzer_iac_config", "config_version": 1, "render_profiles": {"explicit": {"id": "can://iac/payments/helm/render-profile/config/explicit", "kind": "helm_render_profile", "name": "explicit", "origin": "config", "chart_id": "can://iac/payments/helm/chart/charts/api", "release_name": "api-explicit", "namespace": "default", "value_layers": {}, "api_versions": []}}} + "codeanalyzer_iac_config": {"kind": "codeanalyzer_iac_config", "config_version": 1, "render_profiles": {"explicit": {"id": "can://iac/payments/helm/render-profile/config/explicit", "kind": "helm_render_profile", "name": "explicit", "origin": "config", "chart_id": "can://artifact/payments/charts/api/Chart.yaml", "release_name": "api-explicit", "namespace": "default", "value_layers": {}, "api_versions": []}}} } }, "packages": {}, "external_chart_references": {"bitnami/postgresql": {"id": "can://iac/payments/helm/chart-reference/bitnami/postgresql", "kind": "helm_chart_reference", "name": "postgresql", "version_constraint": "12.1.0", "repository": "https://charts.bitnami.com/bitnami"}}, "kubernetes_resource_addresses": { - "apps/deployments/default/api": {"id": "can://iac/payments/kubernetes-resource-address/apps/deployments/default/api", "kind": "kubernetes_resource_address", "group": "apps", "resource_kind": "Deployment", "namespace": "default", "name": "api", "plural": "deployments"}, - "core/secrets/default/api-credentials": {"id": "can://iac/payments/kubernetes-resource-address/core/secrets/default/api-credentials", "kind": "kubernetes_resource_address", "group": "", "resource_kind": "Secret", "namespace": "default", "name": "api-credentials", "plural": "secrets"} + "apps/Deployment/default/api": {"id": "can://iac/payments/kubernetes/address/apps/Deployment/default/api", "kind": "kubernetes_resource_address", "group": "apps", "resource_kind": "Deployment", "namespace": "default", "name": "api", "plural": "deployments"}, + "core/Secret/default/api-credentials": {"id": "can://iac/payments/kubernetes/address/core/Secret/default/api-credentials", "kind": "kubernetes_resource_address", "group": "", "resource_kind": "Secret", "namespace": "default", "name": "api-credentials", "plural": "secrets"} }, "diagnostics": {}, "edges": { @@ -69,18 +69,18 @@ "iac_calls_template": {"charts/api/templates/deployment.yaml@6:11": {"src": "can://iac/payments/helm/charts/api/templates/deployment.yaml/template-call@6:11", "dst": "can://iac/payments/helm/charts/api/templates/deployment.yaml/named-template/api.fullname"}}, "iac_has_value_reference": {"charts/api/templates/deployment.yaml@10:16": {"src": "can://artifact/payments/charts/api/templates/deployment.yaml", "dst": "can://iac/payments/helm/charts/api/templates/deployment.yaml/value-reference@10:16"}}, "iac_references_value": {"charts/api/templates/deployment.yaml@10:16": {"src": "can://iac/payments/helm/charts/api/templates/deployment.yaml/value-reference@10:16", "dst": "can://artifact/payments/charts/api/values.yaml@key/image.tag"}}, - "has_artifact": {"codeanalyzer-iac.yaml": {"src": "can://iac/payments", "dst": "can://artifact/payments/codeanalyzer-iac.yaml"}}, - "defines_config": {"codeanalyzer-iac.yaml@explicit": {"src": "can://artifact/payments/codeanalyzer-iac.yaml", "dst": "can://iac/payments/helm/render-profile/config/explicit"}}, - "iac_declares_profile": {"chart@default": {"src": "can://iac/payments/helm/chart/charts/api", "dst": "can://iac/payments/helm/render-profile/charts/api/default"}}, - "iac_renders_chart": {"default": {"src": "can://iac/payments/helm/render-profile/charts/api/default", "dst": "can://iac/payments/helm/chart/charts/api"}, "explicit": {"src": "can://iac/payments/helm/render-profile/config/explicit", "dst": "can://iac/payments/helm/chart/charts/api"}}, + "has_artifact": {"README.md": {"src": "can://iac/payments", "dst": "can://artifact/payments/README.md"}, "charts/api/Chart.yaml": {"src": "can://iac/payments", "dst": "can://artifact/payments/charts/api/Chart.yaml"}, "charts/api/values.yaml": {"src": "can://iac/payments", "dst": "can://artifact/payments/charts/api/values.yaml"}, "charts/api/templates/deployment.yaml": {"src": "can://iac/payments", "dst": "can://artifact/payments/charts/api/templates/deployment.yaml"}, "codeanalyzer-iac.yaml": {"src": "can://iac/payments", "dst": "can://artifact/payments/codeanalyzer-iac.yaml"}}, + "defines_config": {"charts/api/values.yaml@key/image.tag": {"src": "can://artifact/payments/charts/api/values.yaml", "dst": "can://artifact/payments/charts/api/values.yaml@key/image.tag"}}, + "iac_declares_profile": {"chart@default": {"src": "can://artifact/payments/charts/api/Chart.yaml", "dst": "can://iac/payments/helm/render-profile/charts/api/default"}, "codeanalyzer-iac.yaml@explicit": {"src": "can://artifact/payments/codeanalyzer-iac.yaml", "dst": "can://iac/payments/helm/render-profile/config/explicit"}}, + "iac_renders_chart": {"default": {"src": "can://iac/payments/helm/render-profile/charts/api/default", "dst": "can://artifact/payments/charts/api/Chart.yaml"}, "explicit": {"src": "can://iac/payments/helm/render-profile/config/explicit", "dst": "can://artifact/payments/charts/api/Chart.yaml"}}, "iac_has_value_layer": {"default@0000": {"src": "can://iac/payments/helm/render-profile/charts/api/default", "dst": "can://iac/payments/helm/render-profile/charts/api/default/value-layer/0000"}, "default@0001": {"src": "can://iac/payments/helm/render-profile/charts/api/default", "dst": "can://iac/payments/helm/render-profile/charts/api/default/value-layer/0001"}}, "iac_reads_from": {"default@0000": {"src": "can://iac/payments/helm/render-profile/charts/api/default/value-layer/0000", "dst": "can://artifact/payments/charts/api/values.yaml"}, "default@0001": {"src": "can://iac/payments/helm/render-profile/charts/api/default/value-layer/0001", "dst": "can://artifact/payments/codeanalyzer-iac.yaml"}}, - "iac_has_render": {"default": {"src": "can://iac/payments/helm/chart/charts/api", "dst": "can://iac/payments/helm/render/charts/api/default"}, "explicit": {"src": "can://iac/payments/helm/chart/charts/api", "dst": "can://iac/payments/helm/render/charts/api/explicit"}}, + "iac_has_render": {"default": {"src": "can://artifact/payments/charts/api/Chart.yaml", "dst": "can://iac/payments/helm/render/charts/api/default"}, "explicit": {"src": "can://artifact/payments/charts/api/Chart.yaml", "dst": "can://iac/payments/helm/render/charts/api/explicit"}}, "iac_configured_by": {"default": {"src": "can://iac/payments/helm/render/charts/api/default", "dst": "can://iac/payments/helm/render-profile/charts/api/default"}, "explicit": {"src": "can://iac/payments/helm/render/charts/api/explicit", "dst": "can://iac/payments/helm/render-profile/config/explicit"}}, - "iac_produces": {"default@deployment": {"src": "can://iac/payments/helm/render/charts/api/default", "dst": "can://iac/payments/kubernetes-resource/apps/v1/deployment/default/api"}, "default@secret": {"src": "can://iac/payments/helm/render/charts/api/default", "dst": "can://iac/payments/kubernetes-resource/v1/secret/default/api-credentials"}}, - "iac_targets_resource": {"deployment": {"src": "can://iac/payments/helm/charts/api/templates/deployment.yaml/resource-template@1:1", "dst": "can://iac/payments/kubernetes-resource/apps/v1/deployment/default/api"}, "secret": {"src": "can://iac/payments/helm/charts/api/templates/deployment.yaml/resource-template@1:1", "dst": "can://iac/payments/kubernetes-resource/v1/secret/default/api-credentials"}}, - "iac_derived_from": {"deployment": {"src": "can://iac/payments/kubernetes-resource/apps/v1/deployment/default/api", "dst": "can://iac/payments/helm/charts/api/templates/deployment.yaml/resource-template@1:1"}, "secret": {"src": "can://iac/payments/kubernetes-resource/v1/secret/default/api-credentials", "dst": "can://iac/payments/helm/charts/api/templates/deployment.yaml/resource-template@1:1"}}, - "iac_alias_of": {"deployment": {"src": "can://iac/payments/kubernetes-resource/apps/v1/deployment/default/api", "dst": "can://iac/payments/kubernetes-resource-address/apps/deployments/default/api"}, "secret": {"src": "can://iac/payments/kubernetes-resource/v1/secret/default/api-credentials", "dst": "can://iac/payments/kubernetes-resource-address/core/secrets/default/api-credentials"}} + "iac_produces": {"default@deployment": {"src": "can://iac/payments/helm/render/charts/api/default", "dst": "can://iac/payments/helm/render/charts/api/default/kubernetes/apps/Deployment/default/api"}, "default@secret": {"src": "can://iac/payments/helm/render/charts/api/default", "dst": "can://iac/payments/helm/render/charts/api/default/kubernetes/core/Secret/default/api-credentials"}}, + "iac_targets_resource": {"deployment": {"src": "can://iac/payments/helm/render/charts/api/default/kubernetes/apps/Deployment/default/api", "dst": "can://iac/payments/kubernetes/address/apps/Deployment/default/api"}, "secret": {"src": "can://iac/payments/helm/render/charts/api/default/kubernetes/core/Secret/default/api-credentials", "dst": "can://iac/payments/kubernetes/address/core/Secret/default/api-credentials"}}, + "iac_derived_from": {"deployment": {"src": "can://iac/payments/helm/render/charts/api/default/kubernetes/apps/Deployment/default/api", "dst": "can://iac/payments/helm/charts/api/templates/deployment.yaml/resource-template@1:1"}, "secret": {"src": "can://iac/payments/helm/render/charts/api/default/kubernetes/core/Secret/default/api-credentials", "dst": "can://iac/payments/helm/charts/api/templates/deployment.yaml/resource-template@1:1"}}, + "iac_alias_of": {"charts/api/Chart.yaml@helm-chart": {"src": "can://iac/payments/helm/chart/charts/api", "dst": "can://artifact/payments/charts/api/Chart.yaml"}} } } } diff --git a/v2/iac/json/analysis.schema.json b/v2/iac/json/analysis.schema.json index 58f27ad..effd33d 100644 --- a/v2/iac/json/analysis.schema.json +++ b/v2/iac/json/analysis.schema.json @@ -507,7 +507,7 @@ "kind": {"const": "helm_render_profile"}, "name": {"type": "string", "minLength": 1}, "origin": {"enum": ["default", "config"]}, - "chart_id": {"type": "string", "pattern": "^can://iac/"}, + "chart_id": {"type": "string", "pattern": "^can://artifact/"}, "release_name": {"type": "string", "minLength": 1}, "namespace": {"type": "string", "minLength": 1}, "value_layers": {"type": "object", "additionalProperties": {"$ref": "#/$defs/HelmValueLayer"}}, From 59f9be95cf7bb25320a9ec26fee841ffea4205d3 Mon Sep 17 00:00:00 2001 From: Rahul Krishna Date: Wed, 2 Sep 2026 18:45:33 -0400 Subject: [PATCH 13/16] test(schema): enforce IaC contract invariants --- README.md | 54 +++++++- scripts/check.py | 73 ++++++++++- scripts/check_iac.py | 275 ++++++++++++++++++++++++++++++++++++++++ tests/test_check_iac.py | 224 ++++++++++++++++++++++++++++++++ 4 files changed, 624 insertions(+), 2 deletions(-) create mode 100644 scripts/check_iac.py create mode 100644 tests/test_check_iac.py diff --git a/README.md b/README.md index 5121a5f..cf61108 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,54 @@ # codeanalyzer-schema -Versioned schema + +Versioned contracts for CodeLLM analyzers and SDK consumers. + +## v1 snapshots + +The `v1/` tree contains the published language-analysis and Neo4j contracts. +These files are compatibility snapshots: new schema-v2 work must not rewrite +them. A path selector can validate one snapshot independently, for example: + +```bash +python3 scripts/check.py v1/json/java +``` + +## IaC schema v2 contract + +The files under `v2/iac/` are the normative contract for +`codeanalyzer-iac`: + +- `v2/iac/json/analysis.schema.json` is the normative JSON output schema. + Analyzer golden outputs must validate against it at their declared analysis + level and must also pass `scripts/check_iac.py` semantic validation. +- `v2/iac/json/analysis.l1.sample.json`, `analysis.l2.sample.json`, and + `analysis.l3.sample.json` demonstrate the additive level contract. Higher + levels preserve every lower-level fact; resolution fields may be added but + existing facts may not be replaced. +- `v2/iac/neo4j/contract.schema.json` describes the graph-catalog format. + The `codeanalyzer-iac` catalog must byte-match + `v2/iac/neo4j/schema.neo4j.sample.json`. + +Draft 2020-12 validation covers document structure. The repository semantic +gate additionally enforces globally unique node IDs across all named +collections, real edge endpoints, source digests and span bounds, relative +artifact paths, contiguous render-profile layer ordinals, hash-only Secret +data, and `Package.id == Package.purl`. Every identity alias—including a Helm +Chart alias—must target a collected canonical node, must not self-target or +target another alias, and must have exactly one matching `iac_alias_of` edge. +The graph catalog admits only the shared neutral relationships +`HAS_ARTIFACT` and `DEFINES_CONFIG`; IaC-owned names use the `IAC_*` namespace, +relationship properties are empty, and all referenced labels must exist. + +Install the checker dependency once, then run the repository gates: + +```bash +python3 -m pip install jsonschema +python3 scripts/check.py +python3 -m unittest discover -s tests -v +``` + +The unit and repository check suites use only checked-in files and stay +network-free. The two live repositories are a downstream backend-consumer +gate, not inputs fetched by this repository: preserve emitted JSON from each +pinned consumer, validate it with `v2/iac/json/analysis.schema.json`, and run +it through `scripts/check_iac.py` without weakening either contract. diff --git a/scripts/check.py b/scripts/check.py index 3f55354..d7c116e 100644 --- a/scripts/check.py +++ b/scripts/check.py @@ -14,6 +14,11 @@ from jsonschema import Draft202012Validator +if __package__: + from .check_iac import assert_monotone, check_catalog, check_document +else: + from check_iac import assert_monotone, check_catalog, check_document + ROOT = Path(__file__).resolve().parent.parent @@ -43,13 +48,79 @@ def check(schema_path: Path) -> int: return failures +def selected_iac_root(where: Path) -> Path | None: + """Return the IaC contract root only when the selected path includes it.""" + iac_root = ROOT / "v2/iac" + if not iac_root.exists(): + return None + try: + iac_root.relative_to(where) + return iac_root + except ValueError: + pass + try: + where.relative_to(iac_root) + return iac_root + except ValueError: + return None + + +def check_iac_contract(iac_root: Path) -> int: + """Run semantic and level-monotonicity checks for the IaC contract.""" + failures = 0 + analysis_paths = sorted((iac_root / "json").glob("analysis.l*.sample.json")) + analyses: list[tuple[Path, dict]] = [] + + for path in analysis_paths: + document = json.loads(path.read_text()) + analyses.append((path, document)) + errors = check_document(document) + if errors: + failures += 1 + print(f"FAIL {path.relative_to(ROOT)}: {len(errors)} semantic error(s)") + for error in errors: + print(f" {error}") + else: + print(f"ok {path.relative_to(ROOT)} (semantic)") + + catalog_path = iac_root / "neo4j/schema.neo4j.sample.json" + if catalog_path.exists(): + errors = check_catalog(json.loads(catalog_path.read_text())) + if errors: + failures += 1 + print(f"FAIL {catalog_path.relative_to(ROOT)}: {len(errors)} semantic error(s)") + for error in errors: + print(f" {error}") + else: + print(f"ok {catalog_path.relative_to(ROOT)} (semantic)") + + for (lower_path, lower), (higher_path, higher) in zip(analyses, analyses[1:]): + errors = assert_monotone(lower, higher) + label = f"{lower_path.name} <= {higher_path.name}" + if errors: + failures += 1 + print(f"FAIL {label}: {len(errors)} monotonicity error(s)") + for error in errors: + print(f" {error}") + else: + print(f"ok {label} (monotone)") + + return failures + + def main() -> int: where = ROOT / sys.argv[1] if len(sys.argv) > 1 else ROOT schemas = sorted(where.rglob("*.schema.json")) if not schemas: print(f"no schemas under {where}") return 1 - return min(sum(check(s) for s in schemas), 1) + failures = sum(check(schema) for schema in schemas) + if failures: + return 1 + iac_root = selected_iac_root(where) + if iac_root is not None: + failures += check_iac_contract(iac_root) + return min(failures, 1) if __name__ == "__main__": diff --git a/scripts/check_iac.py b/scripts/check_iac.py new file mode 100644 index 0000000..23526ae --- /dev/null +++ b/scripts/check_iac.py @@ -0,0 +1,275 @@ +#!/usr/bin/env python3 +"""Semantic conformance checks for the schema-v2 IaC contract.""" + +from __future__ import annotations + +import hashlib +from pathlib import PurePosixPath +import re + + +NEUTRAL_RELATIONSHIP_TYPES = {"DEFINES_CONFIG", "HAS_ARTIFACT"} +_LABEL_REFERENCE = re.compile(r":\s*`?([A-Za-z][A-Za-z0-9_]*)`?") + + +def _walk(value: object): + yield value + if isinstance(value, dict): + for child in value.values(): + yield from _walk(child) + elif isinstance(value, list): + for child in value: + yield from _walk(child) + + +def _node_items(application: dict): + for value in _walk(application): + if not isinstance(value, dict): + continue + node_id = value.get("id") + kind = value.get("kind") + if isinstance(node_id, str) and isinstance(kind, str): + yield node_id, value + + +def collect_nodes(application: dict) -> dict[str, dict]: + """Collect all recursively nested nodes and reject every duplicate ID.""" + nodes: dict[str, dict] = {} + duplicate_ids: set[str] = set() + for node_id, node in _node_items(application): + if node_id in nodes: + duplicate_ids.add(node_id) + else: + nodes[node_id] = node + if duplicate_ids: + raise ValueError( + "\n".join(f"duplicate node id: {node_id}" for node_id in sorted(duplicate_ids)) + ) + return nodes + + +def _aliases(application: dict): + for value in _walk(application): + if not isinstance(value, dict): + continue + aliases = value.get("aliases") + if isinstance(aliases, list): + yield from (alias for alias in aliases if isinstance(alias, dict)) + + +def _edges(application: dict): + edge_groups = application.get("edges", {}) + if not isinstance(edge_groups, dict): + return + for edge_type, edge_map in edge_groups.items(): + if not isinstance(edge_map, dict): + continue + for edge_name, edge in edge_map.items(): + if isinstance(edge, dict): + yield edge_type, edge_name, edge + + +def _path_is_relative(path: str) -> bool: + parsed = PurePosixPath(path) + return ( + bool(path) + and not parsed.is_absolute() + and "\\" not in path + and all(part not in {".", ".."} for part in path.split("/")) + ) + + +def _check_artifact(artifact: dict, errors: list[str]) -> None: + artifact_id = artifact.get("id", "") + path = artifact.get("path") + if isinstance(path, str) and not _path_is_relative(path): + errors.append(f"artifact path is not relative: {artifact_id}: {path}") + + source = artifact.get("source") + if not isinstance(source, str): + return + source_bytes = source.encode("utf-8") + if source: + digest = hashlib.sha256(source_bytes).hexdigest() + if artifact.get("sha256") != digest: + errors.append(f"artifact sha256 does not match source: {artifact_id}") + + for value in _walk(artifact): + if not isinstance(value, dict) or not isinstance(value.get("span"), dict): + continue + byte_range = value["span"].get("bytes") + if ( + not isinstance(byte_range, list) + or len(byte_range) != 2 + or not all(isinstance(offset, int) for offset in byte_range) + or byte_range[0] < 0 + or byte_range[0] > byte_range[1] + or byte_range[1] > len(source_bytes) + ): + node_id = value.get("id", "") + errors.append(f"span bytes out of bounds: {node_id} in {artifact_id}") + + +def check_document(document: dict) -> list[str]: + """Return sorted violations of IaC invariants JSON Schema cannot express.""" + errors: list[str] = [] + application = document.get("application", {}) + if not isinstance(application, dict): + return ["application must be an object"] + + try: + nodes = collect_nodes(application) + except ValueError as error: + errors.extend(str(error).splitlines()) + # Retain the first node for each ID so independent checks can still run. + nodes = {} + for node_id, node in _node_items(application): + nodes.setdefault(node_id, node) + + edge_rows = list(_edges(application)) + for edge_type, edge_name, edge in edge_rows: + src = edge.get("src") + dst = edge.get("dst") + if src not in nodes: + errors.append(f"dangling edge source: {edge_type}/{edge_name}: {src}") + if dst not in nodes: + errors.append(f"dangling edge destination: {edge_type}/{edge_name}: {dst}") + + aliases = list(_aliases(application)) + alias_ids = {alias.get("id") for alias in aliases} + alias_edges = [edge for edge_type, _, edge in edge_rows if edge_type == "iac_alias_of"] + for alias in aliases: + alias_id = alias.get("id") + target = alias.get("target") + if alias_id == target or target not in nodes or target in alias_ids: + errors.append(f"alias target is not canonical: {alias_id}: {target}") + matching_edges = sum( + edge.get("src") == alias_id and edge.get("dst") == target for edge in alias_edges + ) + if matching_edges != 1: + errors.append( + f"alias must have exactly one matching iac_alias_of edge: {alias_id}: found {matching_edges}" + ) + + for node_id, node in nodes.items(): + kind = node.get("kind") + if kind == "artifact": + _check_artifact(node, errors) + elif kind == "package" and node.get("id") != node.get("purl"): + errors.append(f"package id must equal purl: {node_id}") + elif kind == "helm_render_profile": + layers = node.get("value_layers", {}) + if isinstance(layers, dict): + ordinals = [ + layer.get("ordinal") + for layer in layers.values() + if isinstance(layer, dict) + ] + if ( + len(ordinals) != len(layers) + or not all(isinstance(ordinal, int) for ordinal in ordinals) + or sorted(ordinals) != list(range(len(layers))) + ): + errors.append( + f"profile layer ordinals must be contiguous from zero: {node_id}" + ) + elif kind == "kubernetes_resource" and node.get("resource_kind") == "Secret": + secret_data = node.get("secret_data", {}) + if isinstance(secret_data, dict): + for datum_name, datum in secret_data.items(): + if not isinstance(datum, dict) or set(datum) != {"key", "sha256"}: + errors.append( + f"Secret data must contain only key and sha256: {node_id}: {datum_name}" + ) + + return sorted(errors) + + +def assert_monotone(lower: dict, higher: dict) -> list[str]: + """Require the higher analysis level to preserve the lower projection.""" + errors: list[str] = [] + + def walk(left: object, right: object, path: tuple[str, ...]) -> None: + if isinstance(left, dict): + if not isinstance(right, dict): + errors.append(f"type changed at {'/'.join(path)}") + return + for key, value in left.items(): + if key == "max_level": + continue + if key not in right: + errors.append(f"removed {'/'.join(path + (key,))}") + else: + walk(value, right[key], path + (key,)) + return + if isinstance(left, list): + if not isinstance(right, list) or left != right[: len(left)]: + errors.append(f"list changed at {'/'.join(path)}") + return + if path and path[-1] == "target_id" and left is None and isinstance(right, str): + return + if left != right: + errors.append(f"value changed at {'/'.join(path)}") + + walk(lower, higher, ()) + return sorted(errors) + + +def _duplicates(values: list[str]) -> list[str]: + seen: set[str] = set() + duplicates: set[str] = set() + for value in values: + if value in seen: + duplicates.add(value) + seen.add(value) + return sorted(duplicates) + + +def check_catalog(catalog: dict) -> list[str]: + """Return sorted semantic violations in the Neo4j graph catalog.""" + errors: list[str] = [] + labels = [ + entry.get("label") + for entry in catalog.get("node_labels", []) + if isinstance(entry, dict) and isinstance(entry.get("label"), str) + ] + label_set = set(labels) + for label in _duplicates(labels): + errors.append(f"duplicate node label: {label}") + + relationships = [ + entry for entry in catalog.get("relationship_types", []) if isinstance(entry, dict) + ] + relationship_types = [ + entry.get("type") + for entry in relationships + if isinstance(entry.get("type"), str) + ] + for relationship_type in _duplicates(relationship_types): + errors.append(f"duplicate relationship type: {relationship_type}") + + for relationship in relationships: + relationship_type = relationship.get("type", "") + if relationship.get("properties"): + errors.append(f"relationship properties must be empty: {relationship_type}") + if ( + relationship_type not in NEUTRAL_RELATIONSHIP_TYPES + and not str(relationship_type).startswith("IAC_") + ): + errors.append( + f"relationship type must be IAC_* or neutral allowlisted: {relationship_type}" + ) + for endpoint in ("from", "to"): + for label in relationship.get(endpoint, []): + if label not in label_set: + errors.append( + f"unknown relationship endpoint label: {relationship_type}/{endpoint}: {label}" + ) + + for entry_kind, collection_name in (("constraint", "constraints"), ("index", "indexes")): + for statement in catalog.get(collection_name, []): + for label in _LABEL_REFERENCE.findall(statement): + if label not in label_set: + errors.append(f"{entry_kind} mentions unknown label: {label}") + + return sorted(errors) diff --git a/tests/test_check_iac.py b/tests/test_check_iac.py new file mode 100644 index 0000000..714a873 --- /dev/null +++ b/tests/test_check_iac.py @@ -0,0 +1,224 @@ +from copy import deepcopy +import json +from pathlib import Path +import shutil +import subprocess +import sys +import tempfile +import unittest + +from scripts.check_iac import assert_monotone, check_catalog, check_document, collect_nodes + + +ROOT = Path(__file__).resolve().parents[1] + + +class CheckIaCTest(unittest.TestCase): + def load(self, level: int) -> dict: + path = ROOT / f"v2/iac/json/analysis.l{level}.sample.json" + return json.loads(path.read_text()) + + def load_catalog(self) -> dict: + path = ROOT / "v2/iac/neo4j/schema.neo4j.sample.json" + return json.loads(path.read_text()) + + def run_checker_copy(self, mutate=None, selector="v2/iac") -> subprocess.CompletedProcess: + with tempfile.TemporaryDirectory() as directory: + checkout = Path(directory) + shutil.copytree(ROOT / "scripts", checkout / "scripts") + shutil.copytree(ROOT / "v1", checkout / "v1") + shutil.copytree(ROOT / "v2" / "iac", checkout / "v2" / "iac") + if mutate is not None: + mutate(checkout) + return subprocess.run( + [sys.executable, "scripts/check.py", selector], + cwd=checkout, + text=True, + capture_output=True, + check=False, + ) + + def test_fixtures_satisfy_semantic_contract(self): + for level in (1, 2, 3): + with self.subTest(level=level): + self.assertEqual([], check_document(self.load(level))) + self.assertEqual([], check_catalog(self.load_catalog())) + + def test_repository_checker_rejects_semantically_invalid_iac_fixture(self): + def corrupt_digest(checkout: Path) -> None: + path = checkout / "v2/iac/json/analysis.l1.sample.json" + document = json.loads(path.read_text()) + document["application"]["artifacts"]["README.md"]["source"] += "changed" + path.write_text(json.dumps(document)) + + result = self.run_checker_copy(corrupt_digest) + self.assertEqual(1, result.returncode) + self.assertIn("sha256 does not match source", result.stdout) + + def test_v1_selector_does_not_run_iac_semantic_checks(self): + def corrupt_digest(checkout: Path) -> None: + path = checkout / "v2/iac/json/analysis.l1.sample.json" + document = json.loads(path.read_text()) + document["application"]["artifacts"]["README.md"]["source"] += "changed" + path.write_text(json.dumps(document)) + + result = self.run_checker_copy(corrupt_digest, "v1/json/java") + self.assertEqual(0, result.returncode, result.stdout + result.stderr) + self.assertNotIn("sha256 does not match source", result.stdout) + + def test_collects_nodes_recursively(self): + nodes = collect_nodes(self.load(3)["application"]) + self.assertIn("can://iac/payments", nodes) + self.assertIn( + "can://iac/payments/helm/render/charts/api/default/kubernetes/core/Secret/default/api-credentials", + nodes, + ) + + def test_rejects_duplicate_ids_across_named_collections(self): + doc = self.load(2) + duplicate = doc["application"]["external_chart_references"]["bitnami/postgresql"] + duplicate["id"] = doc["application"]["artifacts"]["README.md"]["id"] + self.assertIn("duplicate node id", "\n".join(check_document(doc))) + + def test_rejects_dangling_edge(self): + doc = self.load(2) + doc["application"]["edges"]["iac_references_value"]["bad"] = { + "src": "can://iac/payments/missing", + "dst": "can://artifact/payments/charts/api/values.yaml@key/image.tag", + } + self.assertIn("dangling edge source", "\n".join(check_document(doc))) + + def test_returns_violations_in_sorted_order(self): + doc = self.load(2) + edges = doc["application"]["edges"]["iac_references_value"] + edges["z"] = {"src": "can://z", "dst": "can://artifact/payments/README.md"} + edges["a"] = {"src": "can://a", "dst": "can://artifact/payments/README.md"} + errors = check_document(doc) + self.assertEqual(sorted(errors), errors) + + def test_rejects_alias_chain(self): + doc = self.load(1) + alias = doc["application"]["artifacts"]["charts/api/Chart.yaml"]["aliases"][0] + alias["target"] = alias["id"] + self.assertIn("alias target is not canonical", "\n".join(check_document(doc))) + + def test_rejects_alias_targeting_another_alias(self): + doc = self.load(1) + aliases = doc["application"]["artifacts"]["charts/api/Chart.yaml"]["aliases"] + aliases.append( + { + "id": "can://iac/payments/helm/chart/second", + "kind": "helm_chart", + "target": aliases[0]["id"], + } + ) + self.assertIn("alias target is not canonical", "\n".join(check_document(doc))) + + def test_requires_exactly_one_matching_alias_edge(self): + doc = self.load(1) + doc["application"]["edges"]["iac_alias_of"].clear() + self.assertIn("alias must have exactly one matching iac_alias_of edge", "\n".join(check_document(doc))) + + def test_rejects_duplicate_matching_alias_edges(self): + doc = self.load(1) + edges = doc["application"]["edges"]["iac_alias_of"] + edges["duplicate"] = deepcopy(next(iter(edges.values()))) + self.assertIn("alias must have exactly one matching iac_alias_of edge", "\n".join(check_document(doc))) + + def test_rejects_bad_source_digest(self): + doc = self.load(1) + doc["application"]["artifacts"]["README.md"]["source"] += "changed" + self.assertIn("sha256 does not match source", "\n".join(check_document(doc))) + + def test_empty_artifact_source_is_digest_ineligible(self): + doc = self.load(1) + artifact = doc["application"]["artifacts"]["README.md"] + artifact["source"] = "" + self.assertNotIn("sha256 does not match source", "\n".join(check_document(doc))) + + def test_rejects_absolute_artifact_path(self): + doc = self.load(1) + doc["application"]["artifacts"]["README.md"]["path"] = "/README.md" + self.assertIn("artifact path is not relative", "\n".join(check_document(doc))) + + def test_rejects_artifact_path_dot_segment(self): + doc = self.load(1) + doc["application"]["artifacts"]["README.md"]["path"] = "./README.md" + self.assertIn("artifact path is not relative", "\n".join(check_document(doc))) + + def test_rejects_span_outside_artifact_source(self): + doc = self.load(1) + span = doc["application"]["artifacts"]["charts/api/values.yaml"]["config_keys"]["image.tag"]["span"] + span["bytes"] = [9, 10_000] + self.assertIn("span bytes out of bounds", "\n".join(check_document(doc))) + + def test_rejects_non_contiguous_profile_layer_ordinals(self): + doc = self.load(3) + profile = doc["application"]["artifacts"]["charts/api/Chart.yaml"]["iac"]["render_profiles"]["default"] + profile["value_layers"]["0001"]["ordinal"] = 3 + self.assertIn("profile layer ordinals must be contiguous from zero", "\n".join(check_document(doc))) + + def test_rejects_package_id_different_from_purl(self): + doc = self.load(3) + doc["application"]["packages"]["postgresql"] = { + "id": "can://package/postgresql", + "kind": "package", + "purl": "pkg:helm/postgresql@12.1.0", + } + self.assertIn("package id must equal purl", "\n".join(check_document(doc))) + + def test_rejects_plaintext_secret_amplification(self): + doc = self.load(3) + resources = next( + iter(doc["application"]["artifacts"]["charts/api/Chart.yaml"]["iac"]["renders"].values()) + )["resources"] + secret = next(value for value in resources.values() if value["resource_kind"] == "Secret") + secret["secret_data"]["password"]["value"] = "hunter2" + self.assertIn("Secret data must contain only key and sha256", "\n".join(check_document(doc))) + + def test_monotonic_fixtures_only_add_information(self): + self.assertEqual([], assert_monotone(self.load(1), self.load(2))) + self.assertEqual([], assert_monotone(self.load(2), self.load(3))) + + def test_monotonicity_rejects_changed_lower_level_value(self): + lower = self.load(1) + higher = deepcopy(self.load(2)) + higher["application"]["artifacts"]["README.md"]["path"] = "renamed.md" + self.assertIn("value changed at application/artifacts/README.md/path", assert_monotone(lower, higher)) + + def test_catalog_relationships_are_identity_only(self): + catalog = self.load_catalog() + catalog["relationship_types"][0]["properties"]["ordinal"] = "integer" + self.assertIn("relationship properties must be empty", "\n".join(check_catalog(catalog))) + + def test_catalog_rejects_duplicate_labels_and_types(self): + catalog = self.load_catalog() + catalog["node_labels"].append(deepcopy(catalog["node_labels"][0])) + catalog["relationship_types"].append(deepcopy(catalog["relationship_types"][0])) + errors = "\n".join(check_catalog(catalog)) + self.assertIn("duplicate node label", errors) + self.assertIn("duplicate relationship type", errors) + + def test_catalog_rejects_unknown_endpoint_labels(self): + catalog = self.load_catalog() + catalog["relationship_types"][0]["from"].append("MissingLabel") + self.assertIn("unknown relationship endpoint label", "\n".join(check_catalog(catalog))) + + def test_catalog_rejects_non_allowlisted_neutral_relationship(self): + catalog = self.load_catalog() + catalog["relationship_types"][2]["type"] = "PART_OF_CHART" + self.assertIn("relationship type must be IAC_* or neutral allowlisted", "\n".join(check_catalog(catalog))) + + def test_catalog_rejects_constraint_and_index_for_unknown_label(self): + catalog = self.load_catalog() + catalog["constraints"].append( + "CREATE CONSTRAINT missing_id IF NOT EXISTS FOR (n:MissingLabel) REQUIRE n.id IS UNIQUE" + ) + catalog["indexes"].append("CREATE INDEX missing_name IF NOT EXISTS FOR (n:AlsoMissing) ON (n.name)") + errors = "\n".join(check_catalog(catalog)) + self.assertIn("constraint mentions unknown label", errors) + self.assertIn("index mentions unknown label", errors) + + +if __name__ == "__main__": + unittest.main() From e0eb4c4efc677d897707a087ed848c7aceb9ca48 Mon Sep 17 00:00:00 2001 From: Rahul Krishna Date: Wed, 2 Sep 2026 18:50:39 -0400 Subject: [PATCH 14/16] fix(schema): preserve monotone scalar values --- README.md | 16 ++++++++++++---- scripts/check_iac.py | 2 -- tests/test_check_iac.py | 21 +++++++++++++++++++++ 3 files changed, 33 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index cf61108..1e302a7 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,15 @@ python3 -m unittest discover -s tests -v ``` The unit and repository check suites use only checked-in files and stay -network-free. The two live repositories are a downstream backend-consumer -gate, not inputs fetched by this repository: preserve emitted JSON from each -pinned consumer, validate it with `v2/iac/json/analysis.schema.json`, and run -it through `scripts/check_iac.py` without weakening either contract. +network-free. These live repositories are downstream backend-consumer gates, +not inputs fetched by this repository: + +- `sample-daytrader/sample.daytrader.microservices`: each emitted document + must pass structural validation with `v2/iac/json/analysis.schema.json` and + semantic `scripts/check_iac.py` validation. +- `quarkuscoffeeshop/quarkuscoffeeshop-helm`: each emitted document must pass + structural validation with `v2/iac/json/analysis.schema.json` and semantic + `scripts/check_iac.py` validation. + +Preserve the emitted JSON from both pinned repositories and do not weaken +either contract to admit a consumer output. diff --git a/scripts/check_iac.py b/scripts/check_iac.py index 23526ae..106bd04 100644 --- a/scripts/check_iac.py +++ b/scripts/check_iac.py @@ -206,8 +206,6 @@ def walk(left: object, right: object, path: tuple[str, ...]) -> None: if not isinstance(right, list) or left != right[: len(left)]: errors.append(f"list changed at {'/'.join(path)}") return - if path and path[-1] == "target_id" and left is None and isinstance(right, str): - return if left != right: errors.append(f"value changed at {'/'.join(path)}") diff --git a/tests/test_check_iac.py b/tests/test_check_iac.py index 714a873..ef22910 100644 --- a/tests/test_check_iac.py +++ b/tests/test_check_iac.py @@ -186,6 +186,27 @@ def test_monotonicity_rejects_changed_lower_level_value(self): higher["application"]["artifacts"]["README.md"]["path"] = "renamed.md" self.assertIn("value changed at application/artifacts/README.md/path", assert_monotone(lower, higher)) + def test_monotonicity_allows_absent_target_id_to_be_added(self): + lower = {"max_level": 1, "reference": {}} + higher = {"max_level": 2, "reference": {"target_id": "can://iac/payments/target"}} + self.assertEqual([], assert_monotone(lower, higher)) + + def test_monotonicity_rejects_present_null_target_id_replacement(self): + lower = {"max_level": 1, "reference": {"target_id": None}} + higher = {"max_level": 2, "reference": {"target_id": "can://iac/payments/target"}} + self.assertEqual( + ["value changed at reference/target_id"], + assert_monotone(lower, higher), + ) + + def test_monotonicity_rejects_changed_target_id(self): + lower = {"max_level": 1, "reference": {"target_id": "can://iac/payments/one"}} + higher = {"max_level": 2, "reference": {"target_id": "can://iac/payments/two"}} + self.assertEqual( + ["value changed at reference/target_id"], + assert_monotone(lower, higher), + ) + def test_catalog_relationships_are_identity_only(self): catalog = self.load_catalog() catalog["relationship_types"][0]["properties"]["ordinal"] = "integer" From e127901f8ee072d44888f769b35fd5353393c3a3 Mon Sep 17 00:00:00 2001 From: Rahul Krishna Date: Wed, 2 Sep 2026 19:33:03 -0400 Subject: [PATCH 15/16] fix(schema): close IaC contract review findings --- README.md | 6 +- scripts/check_iac.py | 398 +++++++++++++++- tests/test_check_iac.py | 657 +++++++++++++++++++++++++- v2/iac/json/analysis.l1.sample.json | 53 +++ v2/iac/json/analysis.l2.sample.json | 54 ++- v2/iac/json/analysis.l3.sample.json | 32 +- v2/iac/json/analysis.schema.json | 125 +++-- v2/iac/neo4j/schema.neo4j.sample.json | 64 +-- 8 files changed, 1300 insertions(+), 89 deletions(-) diff --git a/README.md b/README.md index 1e302a7..c994232 100644 --- a/README.md +++ b/README.md @@ -51,10 +51,12 @@ The unit and repository check suites use only checked-in files and stay network-free. These live repositories are downstream backend-consumer gates, not inputs fetched by this repository: -- `sample-daytrader/sample.daytrader.microservices`: each emitted document +- `sample-daytrader/sample.daytrader.microservices@8a68b59430a94a242c54384763da9eb7682728b4`: + each emitted document must pass structural validation with `v2/iac/json/analysis.schema.json` and semantic `scripts/check_iac.py` validation. -- `quarkuscoffeeshop/quarkuscoffeeshop-helm`: each emitted document must pass +- `quarkuscoffeeshop/quarkuscoffeeshop-helm@aa3c842658e0fc7e44fa25132d8b817eab225cbe`: + each emitted document must pass structural validation with `v2/iac/json/analysis.schema.json` and semantic `scripts/check_iac.py` validation. diff --git a/scripts/check_iac.py b/scripts/check_iac.py index 106bd04..876717c 100644 --- a/scripts/check_iac.py +++ b/scripts/check_iac.py @@ -4,12 +4,75 @@ from __future__ import annotations import hashlib -from pathlib import PurePosixPath +import json +from pathlib import Path, PurePosixPath import re NEUTRAL_RELATIONSHIP_TYPES = {"DEFINES_CONFIG", "HAS_ARTIFACT"} +SUPPORTED_PURL_PREFIX = "pkg:oci/" _LABEL_REFERENCE = re.compile(r":\s*`?([A-Za-z][A-Za-z0-9_]*)`?") +_CATALOG_PATH = ( + Path(__file__).resolve().parents[1] + / "v2" + / "iac" + / "neo4j" + / "schema.neo4j.sample.json" +) + +_FACET_LABELS = { + "helm_chart": "HelmChart", + "helm_requirements": "HelmRequirements", + "helm_lock": "HelmLock", + "helm_values": "HelmValues", + "helm_values_schema": "HelmValuesSchema", + "helm_template": "HelmTemplate", + "helm_crd": "HelmCRD", + "helm_ignore": "HelmIgnore", +} +_KIND_LABELS = { + "helm_dependency": {"HelmDependency"}, + "helm_chart_reference": {"HelmChartReference"}, + "helm_named_template": {"HelmNamedTemplate"}, + "helm_template_call": {"HelmTemplateCall"}, + "helm_value_reference": {"HelmValueReference"}, + "helm_resource_template": {"HelmResourceTemplate"}, + "helm_lookup_reference": {"HelmLookupReference"}, + "helm_render_profile": {"HelmRenderProfile"}, + "helm_value_layer": {"HelmValueLayer"}, + "helm_render": {"HelmRender"}, + "diagnostic": {"IaCDiagnostic", "HelmDiagnostic"}, + "kubernetes_resource": {"KubernetesResource"}, + "kubernetes_resource_address": {"KubernetesResourceAddress"}, + "package": {"Package"}, +} +_L2_KINDS = {"helm_chart_reference", "package"} +_L3_KINDS = { + "helm_render_profile", + "helm_value_layer", + "helm_render", + "kubernetes_resource", + "kubernetes_resource_address", +} +_L2_EDGES = { + "iac_part_of_chart", + "iac_targets_chart_reference", + "iac_resolves_to_chart", + "iac_identified_by_package", + "iac_calls_template", + "iac_references_value", +} +_L3_EDGES = { + "iac_declares_profile", + "iac_renders_chart", + "iac_has_value_layer", + "iac_reads_from", + "iac_has_render", + "iac_configured_by", + "iac_produces", + "iac_targets_resource", + "iac_derived_from", +} def _walk(value: object): @@ -89,6 +152,17 @@ def _check_artifact(artifact: dict, errors: list[str]) -> None: if not isinstance(source, str): return source_bytes = source.encode("utf-8") + if artifact.get("size_bytes") != len(source_bytes): + errors.append(f"artifact size_bytes does not match UTF-8 source length: {artifact_id}") + if not source and any( + ( + artifact.get("iac") is not None, + artifact.get("codeanalyzer_iac_config") is not None, + bool(artifact.get("config_keys")), + bool(artifact.get("aliases")), + ) + ): + errors.append(f"empty-source artifact must remain raw: {artifact_id}") if source: digest = hashlib.sha256(source_bytes).hexdigest() if artifact.get("sha256") != digest: @@ -110,6 +184,291 @@ def _check_artifact(artifact: dict, errors: list[str]) -> None: errors.append(f"span bytes out of bounds: {node_id} in {artifact_id}") +def _projected_labels(node_id: str, node: dict, alias_ids: set[object]) -> set[str]: + if node_id in alias_ids: + return {"IdentityAlias", "IaCAlias"} + kind = node.get("kind") + if kind == "application": + return {"Application", "IaCApplication"} + if kind == "artifact": + labels = {"Artifact"} + facet = node.get("iac") + if isinstance(facet, dict): + labels.update({"IaCArtifact", "HelmArtifact"}) + label = _FACET_LABELS.get(facet.get("kind")) + if label: + labels.add(label) + if isinstance(node.get("codeanalyzer_iac_config"), dict): + labels.add("CodeAnalyzerIaCConfig") + return labels + if kind == "config_key": + labels = {"ConfigKey"} + facet = node.get("iac") + if isinstance(facet, dict) and facet.get("kind") == "helm_value": + labels.update({"IaCValue", "HelmValue"}) + return labels + return set(_KIND_LABELS.get(kind, ())) + + +def _relationship_contract() -> dict[str, dict]: + catalog = json.loads(_CATALOG_PATH.read_text()) + return { + relationship["type"].lower(): relationship + for relationship in catalog.get("relationship_types", []) + if isinstance(relationship, dict) and isinstance(relationship.get("type"), str) + } + + +def _edge_count(edge_rows: list[tuple[str, str, dict]], edge_type: str, src: object, dst: object) -> int: + return sum( + candidate_type == edge_type + and edge.get("src") == src + and edge.get("dst") == dst + for candidate_type, _, edge in edge_rows + ) + + +def _require_edge( + edge_rows: list[tuple[str, str, dict]], + edge_type: str, + src: object, + dst: object, + description: str, + errors: list[str], +) -> None: + count = _edge_count(edge_rows, edge_type, src, dst) + if count != 1: + errors.append(f"{description} must have exactly one matching {edge_type} edge: {src}: {dst}: found {count}") + + +def _check_level(document: dict, application: dict, errors: list[str]) -> None: + max_level = document.get("max_level") + if not isinstance(max_level, int): + return + + if max_level < 2: + for _, node in _node_items(application): + kind = node.get("kind") + if kind in _L2_KINDS: + errors.append(f"fact requires max_level 2: {kind}") + if "target_id" in node: + errors.append("fact requires max_level 2: target_id") + if kind == "helm_chart_reference" and any( + field in node for field in ("purl", "resolved_chart_id") + ): + errors.append("fact requires max_level 2: helm_chart_reference resolution") + for edge_type, _, _ in _edges(application): + if edge_type in _L2_EDGES: + errors.append(f"edge requires max_level 2: {edge_type}") + + if max_level < 3: + for _, node in _node_items(application): + kind = node.get("kind") + if kind in _L3_KINDS: + errors.append(f"fact requires max_level 3: {kind}") + for artifact in application.get("artifacts", {}).values(): + if isinstance(artifact, dict) and "codeanalyzer_iac_config" in artifact: + errors.append("fact requires max_level 3: codeanalyzer_iac_config") + for edge_type, _, _ in _edges(application): + if edge_type in _L3_EDGES: + errors.append(f"edge requires max_level 3: {edge_type}") + + +def _check_scalar_references( + nodes: dict[str, dict], + alias_ids: set[object], + edge_rows: list[tuple[str, str, dict]], + errors: list[str], +) -> None: + def has_label(node_id: object, label: str) -> bool: + node = nodes.get(node_id) + return isinstance(node, dict) and label in _projected_labels(str(node_id), node, alias_ids) + + for node_id, node in nodes.items(): + kind = node.get("kind") + if kind == "helm_template_call" and "target_id" in node: + target = node.get("target_id") + if not has_label(target, "HelmNamedTemplate"): + errors.append(f"template target_id must target a HelmNamedTemplate: {node_id}: {target}") + _require_edge(edge_rows, "iac_calls_template", node_id, target, "target_id", errors) + elif kind == "helm_value_reference" and "target_id" in node: + target = node.get("target_id") + if not has_label(target, "HelmValue"): + errors.append(f"value target_id must target a HelmValue: {node_id}: {target}") + _require_edge(edge_rows, "iac_references_value", node_id, target, "target_id", errors) + elif kind == "helm_chart_reference": + target = node.get("resolved_chart_id") + if target is not None: + if not has_label(target, "HelmChart"): + errors.append(f"resolved_chart_id must target a HelmChart Artifact: {node_id}: {target}") + _require_edge(edge_rows, "iac_resolves_to_chart", node_id, target, "resolved_chart_id", errors) + purl = node.get("purl") + if purl is not None: + if not has_label(purl, "Package"): + errors.append(f"purl must target a Package: {node_id}: {purl}") + _require_edge(edge_rows, "iac_identified_by_package", node_id, purl, "purl", errors) + elif kind == "helm_render_profile": + chart_id = node.get("chart_id") + if not has_label(chart_id, "HelmChart"): + errors.append(f"chart_id must target a HelmChart Artifact: {node_id}: {chart_id}") + _require_edge(edge_rows, "iac_renders_chart", node_id, chart_id, "chart_id", errors) + layers = node.get("value_layers", {}) + if isinstance(layers, dict): + for layer in layers.values(): + if not isinstance(layer, dict): + continue + layer_id = layer.get("id") + source_id = layer.get("source_id") + _require_edge(edge_rows, "iac_has_value_layer", node_id, layer_id, "value layer", errors) + if not (has_label(source_id, "Artifact") or has_label(source_id, "ConfigKey")): + errors.append(f"source_id must target an Artifact or ConfigKey: {layer_id}: {source_id}") + _require_edge(edge_rows, "iac_reads_from", layer_id, source_id, "source_id", errors) + elif kind == "helm_render": + profile_id = node.get("profile_id") + if not has_label(profile_id, "HelmRenderProfile"): + errors.append(f"profile_id must target a HelmRenderProfile: {node_id}: {profile_id}") + _require_edge(edge_rows, "iac_configured_by", node_id, profile_id, "profile_id", errors) + profile = nodes.get(profile_id) + if isinstance(profile, dict): + layers = profile.get("value_layers", {}) + if isinstance(layers, dict): + expected = [ + layer.get("id") + for layer in sorted( + (value for value in layers.values() if isinstance(value, dict)), + key=lambda value: value.get("ordinal", -1), + ) + ] + if node.get("value_layer_ids") != expected: + errors.append(f"render value_layer_ids do not match profile layers: {node_id}") + elif kind == "diagnostic" and "artifact_id" in node: + artifact_id = node.get("artifact_id") + if not has_label(artifact_id, "Artifact"): + errors.append(f"artifact_id must target an Artifact: {node_id}: {artifact_id}") + elif kind == "kubernetes_resource": + address_id = node.get("address_id") + if address_id is not None: + if not has_label(address_id, "KubernetesResourceAddress"): + errors.append(f"address_id must target a KubernetesResourceAddress: {node_id}: {address_id}") + _require_edge(edge_rows, "iac_targets_resource", node_id, address_id, "address_id", errors) + for origin_id in node.get("origin_ids", []): + if not has_label(origin_id, "HelmResourceTemplate"): + errors.append(f"origin_id must target a HelmResourceTemplate: {node_id}: {origin_id}") + _require_edge(edge_rows, "iac_derived_from", node_id, origin_id, "origin_id", errors) + + +def _check_containment( + application: dict, + edge_rows: list[tuple[str, str, dict]], + max_level: object, + errors: list[str], +) -> None: + app_id = application.get("id") + artifacts = application.get("artifacts", {}) + if not isinstance(artifacts, dict): + return + + chart_artifacts: list[tuple[str, str]] = [] + for path, artifact in artifacts.items(): + if not isinstance(artifact, dict): + continue + facet = artifact.get("iac") + if isinstance(facet, dict) and facet.get("kind") == "helm_chart": + chart_artifacts.append((str(PurePosixPath(path).parent), artifact.get("id"))) + + for path, artifact in artifacts.items(): + if not isinstance(artifact, dict): + continue + artifact_id = artifact.get("id") + _require_edge(edge_rows, "has_artifact", app_id, artifact_id, "artifact containment", errors) + for config_key in artifact.get("config_keys", {}).values(): + if isinstance(config_key, dict): + _require_edge(edge_rows, "defines_config", artifact_id, config_key.get("id"), "config-key containment", errors) + + facet = artifact.get("iac") + if isinstance(facet, dict): + facet_kind = facet.get("kind") + if facet_kind == "helm_template": + for child in facet.get("named_templates", {}).values(): + if isinstance(child, dict): + _require_edge(edge_rows, "iac_defines_template", artifact_id, child.get("id"), "named-template containment", errors) + for child in facet.get("template_calls", {}).values(): + if isinstance(child, dict): + _require_edge(edge_rows, "iac_has_template_call", artifact_id, child.get("id"), "template-call containment", errors) + for child in facet.get("value_references", {}).values(): + if isinstance(child, dict): + _require_edge(edge_rows, "iac_has_value_reference", artifact_id, child.get("id"), "value-reference containment", errors) + elif facet_kind == "helm_chart": + for child in facet.get("dependencies", {}).values(): + if isinstance(child, dict): + dependency_id = child.get("id") + _require_edge( + edge_rows, + "iac_declares_dependency", + artifact_id, + dependency_id, + "dependency containment", + errors, + ) + if isinstance(max_level, int) and max_level >= 2: + target_count = sum( + edge_type == "iac_targets_chart_reference" + and edge.get("src") == dependency_id + for edge_type, _, edge in edge_rows + ) + if target_count != 1: + errors.append( + "dependency must have exactly one " + "iac_targets_chart_reference edge: " + f"{dependency_id}: found {target_count}" + ) + for child in facet.get("render_profiles", {}).values(): + if isinstance(child, dict): + _require_edge(edge_rows, "iac_declares_profile", artifact_id, child.get("id"), "profile containment", errors) + for render in facet.get("renders", {}).values(): + if not isinstance(render, dict): + continue + render_id = render.get("id") + _require_edge(edge_rows, "iac_has_render", artifact_id, render_id, "render containment", errors) + for diagnostic in render.get("diagnostics", {}).values(): + if isinstance(diagnostic, dict): + _require_edge(edge_rows, "iac_has_diagnostic", render_id, diagnostic.get("id"), "diagnostic containment", errors) + for resource in render.get("resources", {}).values(): + if not isinstance(resource, dict): + continue + resource_id = resource.get("id") + if resource.get("render_id") != render_id: + errors.append(f"resource render_id differs from containing render: {resource_id}") + _require_edge(edge_rows, "iac_produces", render_id, resource_id, "resource containment", errors) + producing_edges = sum( + edge_type == "iac_produces" + and edge.get("dst") == resource_id + for edge_type, _, edge in edge_rows + ) + if producing_edges != 1: + errors.append( + "resource must have exactly one containing " + f"iac_produces edge: {resource_id}: found {producing_edges}" + ) + + if facet_kind != "helm_chart" and isinstance(max_level, int) and max_level >= 2: + artifact_parent = str(PurePosixPath(path).parent) + candidates = [ + (chart_dir, chart_id) + for chart_dir, chart_id in chart_artifacts + if artifact_parent == chart_dir or artifact_parent.startswith(f"{chart_dir}/") + ] + if candidates: + _, chart_id = max(candidates, key=lambda candidate: len(candidate[0])) + _require_edge(edge_rows, "iac_part_of_chart", artifact_id, chart_id, "chart membership", errors) + + config_facet = artifact.get("codeanalyzer_iac_config") + if isinstance(config_facet, dict): + for profile in config_facet.get("render_profiles", {}).values(): + if isinstance(profile, dict): + _require_edge(edge_rows, "iac_declares_profile", artifact_id, profile.get("id"), "profile containment", errors) + + def check_document(document: dict) -> list[str]: """Return sorted violations of IaC invariants JSON Schema cannot express.""" errors: list[str] = [] @@ -127,16 +486,33 @@ def check_document(document: dict) -> list[str]: nodes.setdefault(node_id, node) edge_rows = list(_edges(application)) + aliases = list(_aliases(application)) + alias_ids = {alias.get("id") for alias in aliases} + relationship_contract = _relationship_contract() for edge_type, edge_name, edge in edge_rows: src = edge.get("src") dst = edge.get("dst") if src not in nodes: errors.append(f"dangling edge source: {edge_type}/{edge_name}: {src}") + else: + contract = relationship_contract.get(edge_type) + if contract is None: + errors.append(f"edge family is absent from graph catalog: {edge_type}") + elif not ( + _projected_labels(str(src), nodes[src], alias_ids) + & set(contract.get("from", [])) + ): + errors.append(f"edge endpoint type violation: {edge_type}/{edge_name}/src: {src}") if dst not in nodes: errors.append(f"dangling edge destination: {edge_type}/{edge_name}: {dst}") + else: + contract = relationship_contract.get(edge_type) + if contract is not None and not ( + _projected_labels(str(dst), nodes[dst], alias_ids) + & set(contract.get("to", [])) + ): + errors.append(f"edge endpoint type violation: {edge_type}/{edge_name}/dst: {dst}") - aliases = list(_aliases(application)) - alias_ids = {alias.get("id") for alias in aliases} alias_edges = [edge for edge_type, _, edge in edge_rows if edge_type == "iac_alias_of"] for alias in aliases: alias_id = alias.get("id") @@ -155,8 +531,14 @@ def check_document(document: dict) -> list[str]: kind = node.get("kind") if kind == "artifact": _check_artifact(node, errors) - elif kind == "package" and node.get("id") != node.get("purl"): - errors.append(f"package id must equal purl: {node_id}") + elif kind == "package": + if node.get("id") != node.get("purl"): + errors.append(f"package id must equal purl: {node_id}") + if not str(node.get("purl", "")).startswith(SUPPORTED_PURL_PREFIX): + errors.append(f"unsupported package URL type: {node_id}") + elif kind == "helm_chart_reference" and "purl" in node: + if not str(node.get("purl", "")).startswith(SUPPORTED_PURL_PREFIX): + errors.append(f"unsupported package URL type: {node_id}") elif kind == "helm_render_profile": layers = node.get("value_layers", {}) if isinstance(layers, dict): @@ -182,7 +564,11 @@ def check_document(document: dict) -> list[str]: f"Secret data must contain only key and sha256: {node_id}: {datum_name}" ) - return sorted(errors) + _check_level(document, application, errors) + _check_scalar_references(nodes, alias_ids, edge_rows, errors) + _check_containment(application, edge_rows, document.get("max_level"), errors) + + return sorted(set(errors)) def assert_monotone(lower: dict, higher: dict) -> list[str]: diff --git a/tests/test_check_iac.py b/tests/test_check_iac.py index ef22910..396ac19 100644 --- a/tests/test_check_iac.py +++ b/tests/test_check_iac.py @@ -7,21 +7,377 @@ import tempfile import unittest +from jsonschema import Draft202012Validator + from scripts.check_iac import assert_monotone, check_catalog, check_document, collect_nodes ROOT = Path(__file__).resolve().parents[1] +OWNERSHIP_PROPERTIES = { + "neutral": {}, + "shared_facet": { + "iac_producer": "string", + "iac_analyzer_version": "string", + "iac_app_id": "string", + }, + "owned": { + "producer": "string", + "analyzer_version": "string", + "iac_app_id": "string", + }, +} + + +# Normative JSON-kind/facet -> Neo4j projection table. Each entry names the +# complete label stack, the label whose property contract is checked, its +# ownership posture, and every JSON field projected onto that label. +EXPECTED_NODE_PROJECTIONS = { + "application:neutral": { + "definition": "Application", + "labels": ["Application"], + "catalog_label": "Application", + "ownership": "neutral", + "fields": {"id": ("id", "string")}, + "omitted": {"kind", "artifacts", "packages", "external_chart_references", "kubernetes_resource_addresses", "diagnostics", "edges"}, + }, + "application:iac": { + "definition": None, + "labels": ["Application", "IaCApplication"], + "catalog_label": "IaCApplication", + "ownership": "shared_facet", + "fields": {"$node_id": ("id", "string")}, + "omitted": set(), + }, + "artifact:neutral": { + "definition": "Artifact", + "labels": ["Artifact"], + "catalog_label": "Artifact", + "ownership": "neutral", + "fields": { + "id": ("id", "string"), + "path": ("path", "string"), + "format": ("format", "string"), + "source": ("source", "string"), + "sha256": ("sha256", "string"), + "size_bytes": ("size_bytes", "integer"), + }, + "omitted": {"kind", "config_keys", "aliases", "iac", "codeanalyzer_iac_config"}, + }, + "artifact:iac": { + "definition": None, + "labels": ["Artifact", "IaCArtifact"], + "catalog_label": "IaCArtifact", + "ownership": "shared_facet", + "fields": { + "$node_id": ("id", "string"), + "dialect": ("iac_dialect", "string"), + "kind": ("iac_kind", "string"), + "status": ("iac_status", "string"), + }, + "omitted": set(), + }, + "artifact:helm": { + "definition": None, + "labels": ["Artifact", "IaCArtifact", "HelmArtifact"], + "catalog_label": "HelmArtifact", + "ownership": "shared_facet", + "fields": { + "$node_id": ("id", "string"), + "dialect": ("iac_dialect", "string"), + "kind": ("iac_kind", "string"), + "status": ("iac_status", "string"), + }, + "omitted": set(), + }, + "helm_chart": { + "definition": "HelmChart", + "labels": ["Artifact", "IaCArtifact", "HelmArtifact", "HelmChart"], + "catalog_label": "HelmChart", + "ownership": "shared_facet", + "fields": { + "$node_id": ("id", "string"), + "dialect": ("iac_dialect", "string"), + "kind": ("iac_kind", "string"), + "status": ("iac_status", "string"), + "api_version": ("helm_api_version", "string"), + "name": ("helm_name", "string"), + "version": ("helm_version", "string"), + "kube_version": ("helm_kube_version", "string"), + "description": ("helm_description", "string"), + "chart_type": ("helm_chart_type", "string"), + "keywords": ("helm_keywords", "string[]"), + "home": ("helm_home", "string"), + "sources": ("helm_sources", "string[]"), + "maintainers": ("helm_maintainers_json", "string"), + "icon": ("helm_icon", "string"), + "app_version": ("helm_app_version", "string"), + "deprecated": ("helm_deprecated", "boolean"), + "annotations": ("helm_annotations_json", "string"), + }, + "omitted": {"dependencies", "render_profiles", "renders"}, + }, + "helm_requirements": { + "definition": "HelmRequirements", + "labels": ["Artifact", "IaCArtifact", "HelmArtifact", "HelmRequirements"], + "catalog_label": "HelmRequirements", + "ownership": "shared_facet", + "fields": { + "$node_id": ("id", "string"), "dialect": ("iac_dialect", "string"), + "kind": ("iac_kind", "string"), "status": ("iac_status", "string"), + "roles": ("helm_roles", "string[]"), + }, + "omitted": {"dependencies"}, + }, + "helm_lock": { + "definition": "HelmLock", + "labels": ["Artifact", "IaCArtifact", "HelmArtifact", "HelmLock"], + "catalog_label": "HelmLock", + "ownership": "shared_facet", + "fields": { + "$node_id": ("id", "string"), "dialect": ("iac_dialect", "string"), + "kind": ("iac_kind", "string"), "status": ("iac_status", "string"), + "roles": ("helm_roles", "string[]"), + }, + "omitted": {"dependencies"}, + }, + "helm_values": { + "definition": "HelmValues", + "labels": ["Artifact", "IaCArtifact", "HelmArtifact", "HelmValues"], + "catalog_label": "HelmValues", + "ownership": "shared_facet", + "fields": { + "$node_id": ("id", "string"), "dialect": ("iac_dialect", "string"), + "kind": ("iac_kind", "string"), "status": ("iac_status", "string"), + "roles": ("helm_roles", "string[]"), + }, + "omitted": set(), + }, + "helm_values_schema": { + "definition": "HelmValuesSchema", + "labels": ["Artifact", "IaCArtifact", "HelmArtifact", "HelmValuesSchema"], + "catalog_label": "HelmValuesSchema", + "ownership": "shared_facet", + "fields": { + "$node_id": ("id", "string"), "dialect": ("iac_dialect", "string"), + "kind": ("iac_kind", "string"), "status": ("iac_status", "string"), + "roles": ("helm_roles", "string[]"), + }, + "omitted": set(), + }, + "helm_template": { + "definition": "HelmTemplate", + "labels": ["Artifact", "IaCArtifact", "HelmArtifact", "HelmTemplate"], + "catalog_label": "HelmTemplate", + "ownership": "shared_facet", + "fields": { + "$node_id": ("id", "string"), "dialect": ("iac_dialect", "string"), + "kind": ("iac_kind", "string"), "status": ("iac_status", "string"), + "roles": ("helm_roles", "string[]"), + }, + "omitted": {"named_templates", "template_calls", "value_references", "resource_templates", "lookup_references"}, + }, + "helm_crd": { + "definition": "HelmCRD", + "labels": ["Artifact", "IaCArtifact", "HelmArtifact", "HelmCRD"], + "catalog_label": "HelmCRD", + "ownership": "shared_facet", + "fields": { + "$node_id": ("id", "string"), "dialect": ("iac_dialect", "string"), + "kind": ("iac_kind", "string"), "status": ("iac_status", "string"), + "roles": ("helm_roles", "string[]"), + }, + "omitted": set(), + }, + "helm_ignore": { + "definition": "HelmIgnore", + "labels": ["Artifact", "IaCArtifact", "HelmArtifact", "HelmIgnore"], + "catalog_label": "HelmIgnore", + "ownership": "shared_facet", + "fields": { + "$node_id": ("id", "string"), "dialect": ("iac_dialect", "string"), + "kind": ("iac_kind", "string"), "status": ("iac_status", "string"), + "roles": ("helm_roles", "string[]"), + }, + "omitted": set(), + }, + "config_key:neutral": { + "definition": "ConfigKey", + "labels": ["ConfigKey"], + "catalog_label": "ConfigKey", + "ownership": "neutral", + "fields": { + "id": ("id", "string"), "name": ("name", "string"), + "path": ("path", "string"), "span": ("span_json", "string"), + }, + "omitted": {"kind", "iac"}, + }, + "config_key:iac_value": { + "definition": "HelmValueFacet", + "labels": ["ConfigKey", "IaCValue"], + "catalog_label": "IaCValue", + "ownership": "shared_facet", + "fields": {"$node_id": ("id", "string"), "kind": ("iac_kind", "string")}, + "omitted": set(), + }, + "config_key:helm_value": { + "definition": "HelmValueFacet", + "labels": ["ConfigKey", "IaCValue", "HelmValue"], + "catalog_label": "HelmValue", + "ownership": "shared_facet", + "fields": {"$node_id": ("id", "string"), "kind": ("iac_kind", "string")}, + "omitted": set(), + }, + "codeanalyzer_iac_config": { + "definition": "CodeAnalyzerIaCConfig", + "labels": ["Artifact", "CodeAnalyzerIaCConfig"], + "catalog_label": "CodeAnalyzerIaCConfig", + "ownership": "shared_facet", + "fields": {"$node_id": ("id", "string"), "config_version": ("iac_config_version", "integer")}, + "omitted": {"kind", "render_profiles"}, + }, + "helm_dependency": { + "definition": "HelmDependency", "labels": ["HelmDependency"], "catalog_label": "HelmDependency", "ownership": "owned", + "fields": { + "id": ("id", "string"), "name": ("name", "string"), "version_constraint": ("version_constraint", "string"), + "alias": ("alias", "string"), "repository": ("repository", "string"), "condition": ("condition", "string"), + "tags": ("tags", "string[]"), "import_values": ("import_values_json", "string"), "span": ("span_json", "string"), + }, "omitted": {"kind"}, + }, + "helm_chart_reference": { + "definition": "HelmChartReference", "labels": ["HelmChartReference"], "catalog_label": "HelmChartReference", "ownership": "owned", + "fields": { + "id": ("id", "string"), "name": ("name", "string"), "version_constraint": ("version_constraint", "string"), + "repository": ("repository", "string"), "purl": ("purl", "string"), "resolved_chart_id": ("resolved_chart_id", "string"), + }, "omitted": {"kind"}, + }, + "helm_named_template": { + "definition": "HelmNamedTemplate", "labels": ["HelmNamedTemplate"], "catalog_label": "HelmNamedTemplate", "ownership": "owned", + "fields": {"id": ("id", "string"), "name": ("name", "string"), "span": ("span_json", "string")}, "omitted": {"kind"}, + }, + "helm_template_call": { + "definition": "HelmTemplateCall", "labels": ["HelmTemplateCall"], "catalog_label": "HelmTemplateCall", "ownership": "owned", + "fields": { + "id": ("id", "string"), "call_kind": ("call_kind", "string"), "name_expression": ("name_expression", "string"), + "target_id": ("target_id", "string"), "span": ("span_json", "string"), + }, "omitted": {"kind"}, + }, + "helm_value_reference": { + "definition": "HelmValueReference", "labels": ["HelmValueReference"], "catalog_label": "HelmValueReference", "ownership": "owned", + "fields": { + "id": ("id", "string"), "path_expression": ("path_expression", "string"), + "target_id": ("target_id", "string"), "span": ("span_json", "string"), + }, "omitted": {"kind"}, + }, + "helm_resource_template": { + "definition": "HelmResourceTemplate", "labels": ["HelmResourceTemplate"], "catalog_label": "HelmResourceTemplate", "ownership": "owned", + "fields": {"id": ("id", "string"), "document_index": ("document_index", "integer"), "span": ("span_json", "string")}, "omitted": {"kind"}, + }, + "helm_lookup_reference": { + "definition": "HelmLookupReference", "labels": ["HelmLookupReference"], "catalog_label": "HelmLookupReference", "ownership": "owned", + "fields": { + "id": ("id", "string"), "group_expression": ("group_expression", "string"), + "version_expression": ("version_expression", "string"), "resource_kind_expression": ("resource_kind_expression", "string"), + "namespace_expression": ("namespace_expression", "string"), "name_expression": ("name_expression", "string"), + "span": ("span_json", "string"), + }, "omitted": {"kind"}, + }, + "helm_render_profile": { + "definition": "HelmRenderProfile", "labels": ["HelmRenderProfile"], "catalog_label": "HelmRenderProfile", "ownership": "owned", + "fields": { + "id": ("id", "string"), "name": ("name", "string"), "origin": ("origin", "string"), "chart_id": ("chart_id", "string"), + "release_name": ("release_name", "string"), "namespace": ("namespace", "string"), "api_versions": ("api_versions", "string[]"), + "kube_version": ("kube_version", "string"), + }, "omitted": {"kind", "value_layers"}, + }, + "helm_value_layer": { + "definition": "HelmValueLayer", "labels": ["HelmValueLayer"], "catalog_label": "HelmValueLayer", "ownership": "owned", + "fields": {"id": ("id", "string"), "ordinal": ("ordinal", "integer"), "source_id": ("source_id", "string")}, "omitted": {"kind"}, + }, + "helm_render": { + "definition": "HelmRender", "labels": ["HelmRender"], "catalog_label": "HelmRender", "ownership": "owned", + "fields": { + "id": ("id", "string"), "status": ("status", "string"), "profile_id": ("profile_id", "string"), + "renderer_name": ("renderer_name", "string"), "renderer_version": ("renderer_version", "string"), + "value_layer_ids": ("value_layer_ids", "string[]"), "effective_values_sha256": ("effective_values_sha256", "string"), + "phase": ("phase", "string"), + }, "omitted": {"kind", "diagnostics", "resources"}, + }, + "diagnostic:iac": { + "definition": "Diagnostic", "labels": ["IaCDiagnostic"], "catalog_label": "IaCDiagnostic", "ownership": "owned", + "fields": { + "id": ("id", "string"), "severity": ("severity", "string"), "code": ("code", "string"), "message": ("message", "string"), + "phase": ("phase", "string"), "artifact_id": ("artifact_id", "string"), "span": ("span_json", "string"), + }, "omitted": {"kind"}, + }, + "diagnostic:helm": { + "definition": "Diagnostic", "labels": ["IaCDiagnostic", "HelmDiagnostic"], "catalog_label": "HelmDiagnostic", "ownership": "owned", + "fields": { + "id": ("id", "string"), "severity": ("severity", "string"), "code": ("code", "string"), "message": ("message", "string"), + "phase": ("phase", "string"), "artifact_id": ("artifact_id", "string"), "span": ("span_json", "string"), + }, "omitted": {"kind"}, + }, + "kubernetes_resource": { + "definition": "KubernetesResource", "labels": ["KubernetesResource"], "catalog_label": "KubernetesResource", "ownership": "owned", + "fields": { + "id": ("id", "string"), "api_version": ("api_version", "string"), "resource_kind": ("resource_kind", "string"), + "manifest_sha256": ("manifest_sha256", "string"), "render_id": ("render_id", "string"), "origin_ids": ("origin_ids", "string[]"), + "namespace": ("namespace", "string"), "name": ("name", "string"), "generate_name": ("generate_name", "string"), + "labels": ("labels_json", "string"), "annotations": ("annotations_json", "string"), "plural": ("plural", "string"), + "address_id": ("address_id", "string"), "secret_data": ("secret_data_json", "string"), + }, "omitted": {"kind"}, + }, + "kubernetes_resource_address": { + "definition": "KubernetesResourceAddress", "labels": ["KubernetesResourceAddress"], "catalog_label": "KubernetesResourceAddress", "ownership": "owned", + "fields": { + "id": ("id", "string"), "group": ("group", "string"), "resource_kind": ("resource_kind", "string"), + "namespace": ("namespace", "string"), "name": ("name", "string"), "plural": ("plural", "string"), + }, "omitted": {"kind"}, + }, + "identity_alias:neutral": { + "definition": "IdentityAlias", "labels": ["IdentityAlias"], "catalog_label": "IdentityAlias", "ownership": "owned", + "fields": {"id": ("id", "string"), "kind": ("iac_kind", "string"), "target": ("target", "string")}, "omitted": set(), + }, + "identity_alias:iac": { + "definition": "IdentityAlias", "labels": ["IdentityAlias", "IaCAlias"], "catalog_label": "IaCAlias", "ownership": "owned", + "fields": {"id": ("id", "string"), "kind": ("iac_kind", "string"), "target": ("target", "string")}, "omitted": set(), + }, + "package": { + "definition": "Package", "labels": ["Package"], "catalog_label": "Package", "ownership": "neutral", + "fields": {"id": ("id", "string"), "purl": ("purl", "string")}, "omitted": {"kind"}, + }, +} + + class CheckIaCTest(unittest.TestCase): def load(self, level: int) -> dict: path = ROOT / f"v2/iac/json/analysis.l{level}.sample.json" return json.loads(path.read_text()) + def load_schema(self) -> dict: + path = ROOT / "v2/iac/json/analysis.schema.json" + return json.loads(path.read_text()) + def load_catalog(self) -> dict: path = ROOT / "v2/iac/neo4j/schema.neo4j.sample.json" return json.loads(path.read_text()) + def schema_errors(self, document: dict) -> list[str]: + validator = Draft202012Validator(self.load_schema()) + return [error.message for error in validator.iter_errors(document)] + + def definition_errors(self, definition: str, value: object) -> list[str]: + schema = self.load_schema() + validator = Draft202012Validator( + { + "$schema": schema["$schema"], + "$defs": schema["$defs"], + "$ref": f"#/$defs/{definition}", + } + ) + return [error.message for error in validator.iter_errors(value)] + def run_checker_copy(self, mutate=None, selector="v2/iac") -> subprocess.CompletedProcess: with tempfile.TemporaryDirectory() as directory: checkout = Path(directory) @@ -41,9 +397,123 @@ def run_checker_copy(self, mutate=None, selector="v2/iac") -> subprocess.Complet def test_fixtures_satisfy_semantic_contract(self): for level in (1, 2, 3): with self.subTest(level=level): + self.assertEqual([], self.schema_errors(self.load(level))) self.assertEqual([], check_document(self.load(level))) self.assertEqual([], check_catalog(self.load_catalog())) + def test_resolved_vendored_chart_uses_canonical_artifact_identity(self): + document = self.load(2) + reference = document["application"]["external_chart_references"]["bitnami/postgresql"] + self.assertEqual( + "can://artifact/payments/charts/api/charts/postgresql/Chart.yaml", + reference.get("resolved_chart_id"), + ) + self.assertEqual([], self.schema_errors(document)) + + reference["resolved_chart_id"] = "can://iac/payments/helm/chart/charts/api/charts/postgresql" + self.assertTrue(self.schema_errors(document)) + + def test_canonical_id_definitions_reject_noncanonical_text(self): + cases = { + "ApplicationId": ( + "can://iac/payments", + ["can://iac/payments/extra", "can://iac/pay ments", "can://iac/payments\n"], + ), + "ArtifactId": ( + "can://artifact/pay%20ments/charts/api/Chart.yaml", + [ + "can://artifact/payments/charts//Chart.yaml", + "can://artifact/payments/charts/%ZZ.yaml", + "can://artifact/payments/charts/api?x/Chart.yaml", + "can://artifact/payments/charts/api/Chart.yaml\r", + ], + ), + "ConfigKeyId": ( + "can://artifact/payments/charts/api/values.yaml@key/image.tag", + [ + "can://artifact/payments/charts/api/values.yaml@key/", + "can://artifact/payments/charts/api/values.yaml@key/image tag", + "can://artifact/payments/charts/api/values.yaml@key/image/%GG", + ], + ), + "SemanticId": ( + "can://iac/payments/helm/templates/deployment.yaml/template-call@6:11", + [ + "can://iac/payments/helm//template-call", + "can://iac/payments/helm/bad name", + "can://iac/payments/helm/%XX", + "can://iac/payments/helm/template-call@6:11\n", + ], + ), + } + for definition, (valid, invalid_values) in cases.items(): + self.assertIn(definition, self.load_schema()["$defs"]) + with self.subTest(definition=definition, value=valid): + self.assertEqual([], self.definition_errors(definition, valid)) + for invalid in invalid_values: + with self.subTest(definition=definition, value=invalid): + self.assertTrue(self.definition_errors(definition, invalid)) + + def test_reference_fields_use_complete_canonical_id_definitions(self): + schema = self.load_schema()["$defs"] + direct_refs = { + ("Artifact", "id"): "ArtifactId", + ("ConfigKey", "id"): "ConfigKeyId", + ("IdentityAlias", "id"): "SemanticId", + ("IdentityAlias", "target"): "AliasTargetId", + ("Diagnostic", "id"): "SemanticId", + ("Diagnostic", "artifact_id"): "ArtifactId", + ("Package", "id"): "Purl", + ("Package", "purl"): "Purl", + ("Edge", "src"): "NodeId", + ("Edge", "dst"): "NodeId", + ("HelmDependency", "id"): "SemanticId", + ("HelmNamedTemplate", "id"): "SemanticId", + ("HelmTemplateCall", "id"): "SemanticId", + ("HelmTemplateCall", "target_id"): "SemanticId", + ("HelmValueReference", "id"): "SemanticId", + ("HelmValueReference", "target_id"): "ConfigKeyId", + ("HelmResourceTemplate", "id"): "SemanticId", + ("HelmLookupReference", "id"): "SemanticId", + ("HelmChartReference", "id"): "SemanticId", + ("HelmChartReference", "purl"): "Purl", + ("HelmChartReference", "resolved_chart_id"): "ArtifactId", + ("HelmRenderProfile", "id"): "SemanticId", + ("HelmRenderProfile", "chart_id"): "ArtifactId", + ("HelmValueLayer", "id"): "SemanticId", + ("HelmValueLayer", "source_id"): "ValueSourceId", + ("HelmRender", "id"): "SemanticId", + ("HelmRender", "profile_id"): "SemanticId", + ("KubernetesResource", "id"): "SemanticId", + ("KubernetesResource", "render_id"): "SemanticId", + ("KubernetesResource", "address_id"): "SemanticId", + ("KubernetesResourceAddress", "id"): "SemanticId", + } + for (definition, field), target in direct_refs.items(): + with self.subTest(definition=definition, field=field): + self.assertEqual( + {"$ref": f"#/$defs/{target}"}, + schema[definition]["properties"][field], + ) + + array_refs = { + ("HelmRender", "value_layer_ids"): "SemanticId", + ("KubernetesResource", "origin_ids"): "OriginId", + } + for (definition, field), target in array_refs.items(): + with self.subTest(definition=definition, field=field): + self.assertEqual( + {"$ref": f"#/$defs/{target}"}, + schema[definition]["properties"][field]["items"], + ) + + def test_purl_contract_supports_only_registered_oci_type(self): + self.assertIn("Purl", self.load_schema()["$defs"]) + self.assertEqual([], self.definition_errors("Purl", "pkg:oci/postgresql@sha256%3Aabcd")) + for invalid in ("pkg:helm/postgresql@12.1.0", "pkg:generic/postgresql@12.1.0", "pkg:npm/postgresql"): + with self.subTest(invalid=invalid): + self.assertTrue(self.definition_errors("Purl", invalid)) + def test_repository_checker_rejects_semantically_invalid_iac_fixture(self): def corrupt_digest(checkout: Path) -> None: path = checkout / "v2/iac/json/analysis.l1.sample.json" @@ -88,6 +558,80 @@ def test_rejects_dangling_edge(self): } self.assertIn("dangling edge source", "\n".join(check_document(doc))) + def test_rejects_edge_family_endpoint_type_violation(self): + doc = self.load(2) + edge = doc["application"]["edges"]["iac_calls_template"][ + "charts/api/templates/deployment.yaml@6:11" + ] + edge["dst"] = "can://artifact/payments/README.md" + self.assertIn("edge endpoint type violation", "\n".join(check_document(doc))) + + def test_requires_scalar_reference_edge_counterparts(self): + doc = self.load(3) + chart = doc["application"]["artifacts"]["charts/api/Chart.yaml"]["iac"] + chart["renders"]["default"]["profile_id"] = chart["render_profiles"]["default"]["id"] + doc["application"]["edges"]["iac_configured_by"].pop("default") + self.assertIn( + "profile_id must have exactly one matching iac_configured_by edge", + "\n".join(check_document(doc)), + ) + + def test_requires_dependency_target_edge_counterpart(self): + doc = self.load(2) + chart = doc["application"]["artifacts"]["charts/api/Chart.yaml"] + dependency_id = "can://iac/payments/helm/charts/api/dependency/postgresql" + chart["iac"]["dependencies"]["postgresql"] = { + "id": dependency_id, + "kind": "helm_dependency", + "name": "postgresql", + "version_constraint": "12.1.0", + "span": {"start": [1, 1], "end": [1, 2], "bytes": [0, 1]}, + } + doc["application"]["edges"].setdefault("iac_declares_dependency", {})[ + "charts/api/Chart.yaml:postgresql" + ] = {"src": chart["id"], "dst": dependency_id} + + self.assertEqual([], self.schema_errors(doc)) + self.assertIn( + "dependency must have exactly one iac_targets_chart_reference edge", + "\n".join(check_document(doc)), + ) + + def test_rejects_diagnostic_artifact_reference_to_non_artifact(self): + doc = self.load(3) + diagnostic = doc["application"]["artifacts"]["charts/api/Chart.yaml"]["iac"]["renders"]["explicit"]["diagnostics"]["render-failed"] + diagnostic["artifact_id"] = "can://iac/payments/helm/render-profile/config/explicit" + self.assertIn("artifact_id must target an Artifact", "\n".join(check_document(doc))) + + def test_rejects_resource_outside_its_containing_render(self): + doc = self.load(3) + chart = doc["application"]["artifacts"]["charts/api/Chart.yaml"]["iac"] + resource = chart["renders"]["default"]["resources"]["apps/Deployment/default/api"] + resource["render_id"] = chart["renders"]["explicit"]["id"] + self.assertIn("resource render_id differs from containing render", "\n".join(check_document(doc))) + + def test_rejects_resource_produced_by_multiple_renders(self): + doc = self.load(3) + chart = doc["application"]["artifacts"]["charts/api/Chart.yaml"]["iac"] + resource = chart["renders"]["default"]["resources"]["apps/Deployment/default/api"] + doc["application"]["edges"]["iac_produces"]["explicit@deployment"] = { + "src": chart["renders"]["explicit"]["id"], + "dst": resource["id"], + } + self.assertIn( + "resource must have exactly one containing iac_produces edge", + "\n".join(check_document(doc)), + ) + + def test_requires_address_and_origin_targets_and_edges(self): + doc = self.load(3) + resource = doc["application"]["artifacts"]["charts/api/Chart.yaml"]["iac"]["renders"]["default"]["resources"]["apps/Deployment/default/api"] + resource["address_id"] = "can://iac/payments/kubernetes/address/core/Secret/default/api-credentials" + resource["origin_ids"] = ["can://artifact/payments/charts/api/values.yaml"] + errors = "\n".join(check_document(doc)) + self.assertIn("address_id must have exactly one matching iac_targets_resource edge", errors) + self.assertIn("origin_id must target a HelmResourceTemplate", errors) + def test_returns_violations_in_sorted_order(self): doc = self.load(2) edges = doc["application"]["edges"]["iac_references_value"] @@ -134,8 +678,24 @@ def test_empty_artifact_source_is_digest_ineligible(self): doc = self.load(1) artifact = doc["application"]["artifacts"]["README.md"] artifact["source"] = "" + artifact["size_bytes"] = 0 self.assertNotIn("sha256 does not match source", "\n".join(check_document(doc))) + def test_rejects_empty_source_with_semantic_enrichment(self): + doc = self.load(1) + artifact = doc["application"]["artifacts"]["charts/api/Chart.yaml"] + artifact["source"] = "" + artifact["size_bytes"] = 0 + self.assertIn("empty-source artifact must remain raw", "\n".join(check_document(doc))) + + def test_rejects_size_bytes_different_from_utf8_source_length(self): + doc = self.load(1) + artifact = doc["application"]["artifacts"]["README.md"] + artifact["source"] = "café\n" + artifact["sha256"] = "7b49b9e063bd91a4f9252b413261f5557b9c570aa61516989499f64a62dbcdd6" + artifact["size_bytes"] = len(artifact["source"]) + self.assertIn("size_bytes does not match UTF-8 source length", "\n".join(check_document(doc))) + def test_rejects_absolute_artifact_path(self): doc = self.load(1) doc["application"]["artifacts"]["README.md"]["path"] = "/README.md" @@ -154,7 +714,7 @@ def test_rejects_span_outside_artifact_source(self): def test_rejects_non_contiguous_profile_layer_ordinals(self): doc = self.load(3) - profile = doc["application"]["artifacts"]["charts/api/Chart.yaml"]["iac"]["render_profiles"]["default"] + profile = doc["application"]["artifacts"]["codeanalyzer-iac.yaml"]["codeanalyzer_iac_config"]["render_profiles"]["explicit"] profile["value_layers"]["0001"]["ordinal"] = 3 self.assertIn("profile layer ordinals must be contiguous from zero", "\n".join(check_document(doc))) @@ -167,6 +727,69 @@ def test_rejects_package_id_different_from_purl(self): } self.assertIn("package id must equal purl", "\n".join(check_document(doc))) + def test_rejects_unsupported_purl_type_even_when_id_equals_purl(self): + doc = self.load(2) + doc["application"]["packages"]["postgresql"] = { + "id": "pkg:helm/postgresql@12.1.0", + "kind": "package", + "purl": "pkg:helm/postgresql@12.1.0", + } + self.assertIn("unsupported package URL type", "\n".join(check_document(doc))) + + def test_rejects_resolution_facts_below_level_two(self): + doc = self.load(2) + doc["max_level"] = 1 + errors = "\n".join(check_document(doc)) + self.assertIn("fact requires max_level 2: helm_chart_reference", errors) + self.assertIn("fact requires max_level 2: target_id", errors) + self.assertIn("edge requires max_level 2: iac_calls_template", errors) + + def test_rejects_evaluation_facts_below_level_three(self): + doc = self.load(3) + doc["max_level"] = 2 + errors = "\n".join(check_document(doc)) + for fact in ( + "codeanalyzer_iac_config", + "helm_render_profile", + "helm_value_layer", + "helm_render", + "kubernetes_resource", + "kubernetes_resource_address", + ): + with self.subTest(fact=fact): + self.assertIn(f"fact requires max_level 3: {fact}", errors) + + def test_render_status_requires_truthful_phase_shape(self): + document = self.load(3) + chart = document["application"]["artifacts"]["charts/api/Chart.yaml"]["iac"] + failed = chart["renders"]["explicit"] + self.assertEqual("template", failed.get("phase")) + self.assertTrue(failed.get("diagnostics")) + self.assertEqual([], self.schema_errors(document)) + + failed.pop("phase", None) + self.assertTrue(self.schema_errors(document)) + + succeeded = chart["renders"]["default"] + succeeded["phase"] = "template" + self.assertTrue(self.schema_errors(document)) + + def test_profiles_keep_configuration_layers_out_of_default_render(self): + doc = self.load(3) + artifacts = doc["application"]["artifacts"] + chart = artifacts["charts/api/Chart.yaml"]["iac"] + config = artifacts["codeanalyzer-iac.yaml"] + default_profile = chart["render_profiles"]["default"] + explicit_profile = config["codeanalyzer_iac_config"]["render_profiles"]["explicit"] + + self.assertEqual( + ["can://artifact/payments/charts/api/values.yaml"], + [layer["source_id"] for layer in default_profile["value_layers"].values()], + ) + explicit_sources = [layer["source_id"] for layer in explicit_profile["value_layers"].values()] + self.assertEqual("can://artifact/payments/charts/api/values.yaml", explicit_sources[0]) + self.assertIn(explicit_sources[1], {key["id"] for key in config["config_keys"].values()}) + def test_rejects_plaintext_secret_amplification(self): doc = self.load(3) resources = next( @@ -212,6 +835,38 @@ def test_catalog_relationships_are_identity_only(self): catalog["relationship_types"][0]["properties"]["ordinal"] = "integer" self.assertIn("relationship properties must be empty", "\n".join(check_catalog(catalog))) + def test_catalog_exactly_matches_node_projection_table(self): + catalog = self.load_catalog() + actual = {entry["label"]: entry for entry in catalog["node_labels"]} + self.assertEqual( + {entry["catalog_label"] for entry in EXPECTED_NODE_PROJECTIONS.values()}, + set(actual), + ) + for projection_name, projection in EXPECTED_NODE_PROJECTIONS.items(): + expected_properties = { + graph_name: graph_type + for graph_name, graph_type in projection["fields"].values() + } + expected_properties.update(OWNERSHIP_PROPERTIES[projection["ownership"]]) + entry = actual[projection["catalog_label"]] + with self.subTest(projection=projection_name): + self.assertEqual("id", entry["key"]) + self.assertEqual(expected_properties, entry["properties"]) + + def test_projection_table_covers_each_json_node_property(self): + definitions = self.load_schema()["$defs"] + for projection_name, projection in EXPECTED_NODE_PROJECTIONS.items(): + definition_name = projection["definition"] + if definition_name is None: + continue + json_fields = {name for name in projection["fields"] if not name.startswith("$")} + expected_fields = json_fields | projection["omitted"] + with self.subTest(projection=projection_name): + self.assertEqual( + expected_fields, + set(definitions[definition_name]["properties"]), + ) + def test_catalog_rejects_duplicate_labels_and_types(self): catalog = self.load_catalog() catalog["node_labels"].append(deepcopy(catalog["node_labels"][0])) diff --git a/v2/iac/json/analysis.l1.sample.json b/v2/iac/json/analysis.l1.sample.json index 859238a..6b94e69 100644 --- a/v2/iac/json/analysis.l1.sample.json +++ b/v2/iac/json/analysis.l1.sample.json @@ -48,6 +48,36 @@ "renders": {} } }, + "charts/api/charts/postgresql/Chart.yaml": { + "id": "can://artifact/payments/charts/api/charts/postgresql/Chart.yaml", + "kind": "artifact", + "path": "charts/api/charts/postgresql/Chart.yaml", + "format": "yaml", + "source": "apiVersion: v2\nname: postgresql\nversion: 12.1.0\ntype: application\n", + "sha256": "57ea5ae02fdfc54e1a02eac39e9b1d98d289c6f182c77d90d80d7a9124d11161", + "size_bytes": 66, + "config_keys": {}, + "aliases": [{ + "id": "can://iac/payments/helm/chart/charts/api/charts/postgresql", + "kind": "helm_chart", + "target": "can://artifact/payments/charts/api/charts/postgresql/Chart.yaml" + }], + "iac": { + "dialect": "helm", + "kind": "helm_chart", + "status": "complete", + "api_version": "v2", + "name": "postgresql", + "version": "12.1.0", + "chart_type": "application", + "maintainers": [], + "keywords": [], + "sources": [], + "annotations": {}, + "dependencies": {}, + "renders": {} + } + }, "charts/api/values.yaml": { "id": "can://artifact/payments/charts/api/values.yaml", "kind": "artifact", @@ -145,6 +175,7 @@ "has_artifact": { "README.md": {"src": "can://iac/payments", "dst": "can://artifact/payments/README.md"}, "charts/api/Chart.yaml": {"src": "can://iac/payments", "dst": "can://artifact/payments/charts/api/Chart.yaml"}, + "charts/api/charts/postgresql/Chart.yaml": {"src": "can://iac/payments", "dst": "can://artifact/payments/charts/api/charts/postgresql/Chart.yaml"}, "charts/api/values.yaml": {"src": "can://iac/payments", "dst": "can://artifact/payments/charts/api/values.yaml"}, "charts/api/templates/deployment.yaml": {"src": "can://iac/payments", "dst": "can://artifact/payments/charts/api/templates/deployment.yaml"} }, @@ -158,6 +189,28 @@ "charts/api/Chart.yaml@helm-chart": { "src": "can://iac/payments/helm/chart/charts/api", "dst": "can://artifact/payments/charts/api/Chart.yaml" + }, + "charts/api/charts/postgresql/Chart.yaml@helm-chart": { + "src": "can://iac/payments/helm/chart/charts/api/charts/postgresql", + "dst": "can://artifact/payments/charts/api/charts/postgresql/Chart.yaml" + } + }, + "iac_defines_template": { + "charts/api/templates/deployment.yaml@12:1": { + "src": "can://artifact/payments/charts/api/templates/deployment.yaml", + "dst": "can://iac/payments/helm/charts/api/templates/deployment.yaml/named-template/api.fullname" + } + }, + "iac_has_template_call": { + "charts/api/templates/deployment.yaml@6:11": { + "src": "can://artifact/payments/charts/api/templates/deployment.yaml", + "dst": "can://iac/payments/helm/charts/api/templates/deployment.yaml/template-call@6:11" + } + }, + "iac_has_value_reference": { + "charts/api/templates/deployment.yaml@10:16": { + "src": "can://artifact/payments/charts/api/templates/deployment.yaml", + "dst": "can://iac/payments/helm/charts/api/templates/deployment.yaml/value-reference@10:16" } } } diff --git a/v2/iac/json/analysis.l2.sample.json b/v2/iac/json/analysis.l2.sample.json index 9d15e61..cecf7c2 100644 --- a/v2/iac/json/analysis.l2.sample.json +++ b/v2/iac/json/analysis.l2.sample.json @@ -48,6 +48,36 @@ "renders": {} } }, + "charts/api/charts/postgresql/Chart.yaml": { + "id": "can://artifact/payments/charts/api/charts/postgresql/Chart.yaml", + "kind": "artifact", + "path": "charts/api/charts/postgresql/Chart.yaml", + "format": "yaml", + "source": "apiVersion: v2\nname: postgresql\nversion: 12.1.0\ntype: application\n", + "sha256": "57ea5ae02fdfc54e1a02eac39e9b1d98d289c6f182c77d90d80d7a9124d11161", + "size_bytes": 66, + "config_keys": {}, + "aliases": [{ + "id": "can://iac/payments/helm/chart/charts/api/charts/postgresql", + "kind": "helm_chart", + "target": "can://artifact/payments/charts/api/charts/postgresql/Chart.yaml" + }], + "iac": { + "dialect": "helm", + "kind": "helm_chart", + "status": "complete", + "api_version": "v2", + "name": "postgresql", + "version": "12.1.0", + "chart_type": "application", + "maintainers": [], + "keywords": [], + "sources": [], + "annotations": {}, + "dependencies": {}, + "renders": {} + } + }, "charts/api/values.yaml": { "id": "can://artifact/payments/charts/api/values.yaml", "kind": "artifact", @@ -146,7 +176,8 @@ "kind": "helm_chart_reference", "name": "postgresql", "version_constraint": "12.1.0", - "repository": "https://charts.bitnami.com/bitnami" + "repository": "https://charts.bitnami.com/bitnami", + "resolved_chart_id": "can://artifact/payments/charts/api/charts/postgresql/Chart.yaml" } }, "kubernetes_resource_addresses": {}, @@ -155,6 +186,7 @@ "has_artifact": { "README.md": {"src": "can://iac/payments", "dst": "can://artifact/payments/README.md"}, "charts/api/Chart.yaml": {"src": "can://iac/payments", "dst": "can://artifact/payments/charts/api/Chart.yaml"}, + "charts/api/charts/postgresql/Chart.yaml": {"src": "can://iac/payments", "dst": "can://artifact/payments/charts/api/charts/postgresql/Chart.yaml"}, "charts/api/values.yaml": {"src": "can://iac/payments", "dst": "can://artifact/payments/charts/api/values.yaml"}, "charts/api/templates/deployment.yaml": {"src": "can://iac/payments", "dst": "can://artifact/payments/charts/api/templates/deployment.yaml"} }, @@ -168,6 +200,26 @@ "charts/api/Chart.yaml@helm-chart": { "src": "can://iac/payments/helm/chart/charts/api", "dst": "can://artifact/payments/charts/api/Chart.yaml" + }, + "charts/api/charts/postgresql/Chart.yaml@helm-chart": { + "src": "can://iac/payments/helm/chart/charts/api/charts/postgresql", + "dst": "can://artifact/payments/charts/api/charts/postgresql/Chart.yaml" + } + }, + "iac_part_of_chart": { + "charts/api/values.yaml": { + "src": "can://artifact/payments/charts/api/values.yaml", + "dst": "can://artifact/payments/charts/api/Chart.yaml" + }, + "charts/api/templates/deployment.yaml": { + "src": "can://artifact/payments/charts/api/templates/deployment.yaml", + "dst": "can://artifact/payments/charts/api/Chart.yaml" + } + }, + "iac_resolves_to_chart": { + "bitnami/postgresql": { + "src": "can://iac/payments/helm/chart-reference/bitnami/postgresql", + "dst": "can://artifact/payments/charts/api/charts/postgresql/Chart.yaml" } }, "iac_defines_template": { diff --git a/v2/iac/json/analysis.l3.sample.json b/v2/iac/json/analysis.l3.sample.json index 0088037..b8aceab 100644 --- a/v2/iac/json/analysis.l3.sample.json +++ b/v2/iac/json/analysis.l3.sample.json @@ -17,24 +17,28 @@ "default": { "id": "can://iac/payments/helm/render-profile/charts/api/default", "kind": "helm_render_profile", "name": "default", "origin": "default", "chart_id": "can://artifact/payments/charts/api/Chart.yaml", "release_name": "api", "namespace": "default", "value_layers": { - "0000": {"id": "can://iac/payments/helm/render-profile/charts/api/default/value-layer/0000", "kind": "helm_value_layer", "ordinal": 0, "source_id": "can://artifact/payments/charts/api/values.yaml"}, - "0001": {"id": "can://iac/payments/helm/render-profile/charts/api/default/value-layer/0001", "kind": "helm_value_layer", "ordinal": 1, "source_id": "can://artifact/payments/codeanalyzer-iac.yaml"} + "0000": {"id": "can://iac/payments/helm/render-profile/charts/api/default/value-layer/0000", "kind": "helm_value_layer", "ordinal": 0, "source_id": "can://artifact/payments/charts/api/values.yaml"} }, "api_versions": [] } }, "renders": { "default": { - "id": "can://iac/payments/helm/render/charts/api/default", "kind": "helm_render", "status": "succeeded", "profile_id": "can://iac/payments/helm/render-profile/charts/api/default", "renderer_name": "helm", "renderer_version": "4.2.4", "value_layer_ids": ["can://iac/payments/helm/render-profile/charts/api/default/value-layer/0000", "can://iac/payments/helm/render-profile/charts/api/default/value-layer/0001"], "effective_values_sha256": "3d3ecdd860e9d3e2fedf29a4fb6e1667c8295ed8e500aae00901ce43e79bf497", "diagnostics": {}, + "id": "can://iac/payments/helm/render/charts/api/default", "kind": "helm_render", "status": "succeeded", "profile_id": "can://iac/payments/helm/render-profile/charts/api/default", "renderer_name": "helm", "renderer_version": "4.2.4", "value_layer_ids": ["can://iac/payments/helm/render-profile/charts/api/default/value-layer/0000"], "effective_values_sha256": "3d3ecdd860e9d3e2fedf29a4fb6e1667c8295ed8e500aae00901ce43e79bf497", "diagnostics": {}, "resources": { "apps/Deployment/default/api": {"id": "can://iac/payments/helm/render/charts/api/default/kubernetes/apps/Deployment/default/api", "kind": "kubernetes_resource", "api_version": "apps/v1", "resource_kind": "Deployment", "manifest_sha256": "e1b97b673a0cfecc5150d46004a6216ada2d48f59c6593f44061139420a8506a", "render_id": "can://iac/payments/helm/render/charts/api/default", "origin_ids": ["can://iac/payments/helm/charts/api/templates/deployment.yaml/resource-template@1:1"], "namespace": "default", "name": "api", "labels": {}, "annotations": {}, "plural": "deployments", "address_id": "can://iac/payments/kubernetes/address/apps/Deployment/default/api"}, "core/Secret/default/api-credentials": {"id": "can://iac/payments/helm/render/charts/api/default/kubernetes/core/Secret/default/api-credentials", "kind": "kubernetes_resource", "api_version": "v1", "resource_kind": "Secret", "manifest_sha256": "5e65acdc38d1a0309e4c39fc4db1d62d290b3906f8415de307876a614ae957f6", "render_id": "can://iac/payments/helm/render/charts/api/default", "origin_ids": ["can://iac/payments/helm/charts/api/templates/deployment.yaml/resource-template@1:1"], "namespace": "default", "name": "api-credentials", "labels": {}, "annotations": {}, "plural": "secrets", "address_id": "can://iac/payments/kubernetes/address/core/Secret/default/api-credentials", "secret_data": {"password": {"key": "password", "sha256": "5994471abb01112afcc18159f6cc74b4f511b99806da59b3caf5a9c173cacfc5"}}} } }, - "explicit": {"id": "can://iac/payments/helm/render/charts/api/explicit", "kind": "helm_render", "status": "failed", "profile_id": "can://iac/payments/helm/render-profile/config/explicit", "renderer_name": "helm", "renderer_version": "4.2.4", "value_layer_ids": [], "effective_values_sha256": "3d3ecdd860e9d3e2fedf29a4fb6e1667c8295ed8e500aae00901ce43e79bf497", "diagnostics": {}, "resources": {}} + "explicit": {"id": "can://iac/payments/helm/render/charts/api/explicit", "kind": "helm_render", "status": "failed", "phase": "template", "profile_id": "can://iac/payments/helm/render-profile/config/explicit", "renderer_name": "helm", "renderer_version": "4.2.4", "value_layer_ids": ["can://iac/payments/helm/render-profile/config/explicit/value-layer/0000", "can://iac/payments/helm/render-profile/config/explicit/value-layer/0001"], "effective_values_sha256": "3d3ecdd860e9d3e2fedf29a4fb6e1667c8295ed8e500aae00901ce43e79bf497", "diagnostics": {"render-failed": {"id": "can://iac/payments/helm/render/charts/api/explicit/diagnostic/IAC_HELM_RENDER_FAILED", "kind": "diagnostic", "severity": "error", "code": "IAC_HELM_RENDER_FAILED", "message": "template rendering failed", "phase": "template", "artifact_id": "can://artifact/payments/charts/api/Chart.yaml"}}, "resources": {}} } } }, + "charts/api/charts/postgresql/Chart.yaml": { + "id": "can://artifact/payments/charts/api/charts/postgresql/Chart.yaml", "kind": "artifact", "path": "charts/api/charts/postgresql/Chart.yaml", "format": "yaml", "source": "apiVersion: v2\nname: postgresql\nversion: 12.1.0\ntype: application\n", "sha256": "57ea5ae02fdfc54e1a02eac39e9b1d98d289c6f182c77d90d80d7a9124d11161", "size_bytes": 66, "config_keys": {}, + "aliases": [{"id": "can://iac/payments/helm/chart/charts/api/charts/postgresql", "kind": "helm_chart", "target": "can://artifact/payments/charts/api/charts/postgresql/Chart.yaml"}], + "iac": {"dialect": "helm", "kind": "helm_chart", "status": "complete", "api_version": "v2", "name": "postgresql", "version": "12.1.0", "chart_type": "application", "maintainers": [], "keywords": [], "sources": [], "annotations": {}, "dependencies": {}, "renders": {}} + }, "charts/api/values.yaml": { "id": "can://artifact/payments/charts/api/values.yaml", "kind": "artifact", "path": "charts/api/values.yaml", "format": "yaml", "source": "image:\n tag: 1.2.3x\n", "sha256": "3d3ecdd860e9d3e2fedf29a4fb6e1667c8295ed8e500aae00901ce43e79bf497", "size_bytes": 21, "config_keys": {"image.tag": {"id": "can://artifact/payments/charts/api/values.yaml@key/image.tag", "kind": "config_key", "name": "tag", "path": "image.tag", "span": {"start": [2, 3], "end": [2, 14], "bytes": [9, 20]}, "iac": {"kind": "helm_value"}}}, @@ -52,12 +56,13 @@ } }, "codeanalyzer-iac.yaml": { - "id": "can://artifact/payments/codeanalyzer-iac.yaml", "kind": "artifact", "path": "codeanalyzer-iac.yaml", "format": "yaml", "source": "render_profiles:\n explicit: {}\n", "sha256": "2b986cbe3c065eb9a5ade85b1b4ddc603774f51a356dac7ca8fbd9cb0cfbe9a2", "size_bytes": 32, "config_keys": {}, "aliases": [], - "codeanalyzer_iac_config": {"kind": "codeanalyzer_iac_config", "config_version": 1, "render_profiles": {"explicit": {"id": "can://iac/payments/helm/render-profile/config/explicit", "kind": "helm_render_profile", "name": "explicit", "origin": "config", "chart_id": "can://artifact/payments/charts/api/Chart.yaml", "release_name": "api-explicit", "namespace": "default", "value_layers": {}, "api_versions": []}}} + "id": "can://artifact/payments/codeanalyzer-iac.yaml", "kind": "artifact", "path": "codeanalyzer-iac.yaml", "format": "yaml", "source": "render_profiles:\n explicit:\n set:\n image.tag: 2.0.0\n", "sha256": "d0928860f1949a7b5437ff0c9b1ce6982efd998c18104f7a4685b4066f8be06e", "size_bytes": 61, + "config_keys": {"render_profiles.explicit.set.image.tag": {"id": "can://artifact/payments/codeanalyzer-iac.yaml@key/render_profiles.explicit.set.image.tag", "kind": "config_key", "name": "image.tag", "path": "render_profiles.explicit.set.image.tag", "span": {"start": [4, 7], "end": [4, 23], "bytes": [44, 60]}}}, "aliases": [], + "codeanalyzer_iac_config": {"kind": "codeanalyzer_iac_config", "config_version": 1, "render_profiles": {"explicit": {"id": "can://iac/payments/helm/render-profile/config/explicit", "kind": "helm_render_profile", "name": "explicit", "origin": "config", "chart_id": "can://artifact/payments/charts/api/Chart.yaml", "release_name": "api-explicit", "namespace": "default", "value_layers": {"0000": {"id": "can://iac/payments/helm/render-profile/config/explicit/value-layer/0000", "kind": "helm_value_layer", "ordinal": 0, "source_id": "can://artifact/payments/charts/api/values.yaml"}, "0001": {"id": "can://iac/payments/helm/render-profile/config/explicit/value-layer/0001", "kind": "helm_value_layer", "ordinal": 1, "source_id": "can://artifact/payments/codeanalyzer-iac.yaml@key/render_profiles.explicit.set.image.tag"}}, "api_versions": []}}} } }, "packages": {}, - "external_chart_references": {"bitnami/postgresql": {"id": "can://iac/payments/helm/chart-reference/bitnami/postgresql", "kind": "helm_chart_reference", "name": "postgresql", "version_constraint": "12.1.0", "repository": "https://charts.bitnami.com/bitnami"}}, + "external_chart_references": {"bitnami/postgresql": {"id": "can://iac/payments/helm/chart-reference/bitnami/postgresql", "kind": "helm_chart_reference", "name": "postgresql", "version_constraint": "12.1.0", "repository": "https://charts.bitnami.com/bitnami", "resolved_chart_id": "can://artifact/payments/charts/api/charts/postgresql/Chart.yaml"}}, "kubernetes_resource_addresses": { "apps/Deployment/default/api": {"id": "can://iac/payments/kubernetes/address/apps/Deployment/default/api", "kind": "kubernetes_resource_address", "group": "apps", "resource_kind": "Deployment", "namespace": "default", "name": "api", "plural": "deployments"}, "core/Secret/default/api-credentials": {"id": "can://iac/payments/kubernetes/address/core/Secret/default/api-credentials", "kind": "kubernetes_resource_address", "group": "", "resource_kind": "Secret", "namespace": "default", "name": "api-credentials", "plural": "secrets"} @@ -69,18 +74,21 @@ "iac_calls_template": {"charts/api/templates/deployment.yaml@6:11": {"src": "can://iac/payments/helm/charts/api/templates/deployment.yaml/template-call@6:11", "dst": "can://iac/payments/helm/charts/api/templates/deployment.yaml/named-template/api.fullname"}}, "iac_has_value_reference": {"charts/api/templates/deployment.yaml@10:16": {"src": "can://artifact/payments/charts/api/templates/deployment.yaml", "dst": "can://iac/payments/helm/charts/api/templates/deployment.yaml/value-reference@10:16"}}, "iac_references_value": {"charts/api/templates/deployment.yaml@10:16": {"src": "can://iac/payments/helm/charts/api/templates/deployment.yaml/value-reference@10:16", "dst": "can://artifact/payments/charts/api/values.yaml@key/image.tag"}}, - "has_artifact": {"README.md": {"src": "can://iac/payments", "dst": "can://artifact/payments/README.md"}, "charts/api/Chart.yaml": {"src": "can://iac/payments", "dst": "can://artifact/payments/charts/api/Chart.yaml"}, "charts/api/values.yaml": {"src": "can://iac/payments", "dst": "can://artifact/payments/charts/api/values.yaml"}, "charts/api/templates/deployment.yaml": {"src": "can://iac/payments", "dst": "can://artifact/payments/charts/api/templates/deployment.yaml"}, "codeanalyzer-iac.yaml": {"src": "can://iac/payments", "dst": "can://artifact/payments/codeanalyzer-iac.yaml"}}, - "defines_config": {"charts/api/values.yaml@key/image.tag": {"src": "can://artifact/payments/charts/api/values.yaml", "dst": "can://artifact/payments/charts/api/values.yaml@key/image.tag"}}, + "has_artifact": {"README.md": {"src": "can://iac/payments", "dst": "can://artifact/payments/README.md"}, "charts/api/Chart.yaml": {"src": "can://iac/payments", "dst": "can://artifact/payments/charts/api/Chart.yaml"}, "charts/api/charts/postgresql/Chart.yaml": {"src": "can://iac/payments", "dst": "can://artifact/payments/charts/api/charts/postgresql/Chart.yaml"}, "charts/api/values.yaml": {"src": "can://iac/payments", "dst": "can://artifact/payments/charts/api/values.yaml"}, "charts/api/templates/deployment.yaml": {"src": "can://iac/payments", "dst": "can://artifact/payments/charts/api/templates/deployment.yaml"}, "codeanalyzer-iac.yaml": {"src": "can://iac/payments", "dst": "can://artifact/payments/codeanalyzer-iac.yaml"}}, + "defines_config": {"charts/api/values.yaml@key/image.tag": {"src": "can://artifact/payments/charts/api/values.yaml", "dst": "can://artifact/payments/charts/api/values.yaml@key/image.tag"}, "codeanalyzer-iac.yaml@key/render_profiles.explicit.set.image.tag": {"src": "can://artifact/payments/codeanalyzer-iac.yaml", "dst": "can://artifact/payments/codeanalyzer-iac.yaml@key/render_profiles.explicit.set.image.tag"}}, + "iac_part_of_chart": {"charts/api/values.yaml": {"src": "can://artifact/payments/charts/api/values.yaml", "dst": "can://artifact/payments/charts/api/Chart.yaml"}, "charts/api/templates/deployment.yaml": {"src": "can://artifact/payments/charts/api/templates/deployment.yaml", "dst": "can://artifact/payments/charts/api/Chart.yaml"}}, + "iac_resolves_to_chart": {"bitnami/postgresql": {"src": "can://iac/payments/helm/chart-reference/bitnami/postgresql", "dst": "can://artifact/payments/charts/api/charts/postgresql/Chart.yaml"}}, "iac_declares_profile": {"chart@default": {"src": "can://artifact/payments/charts/api/Chart.yaml", "dst": "can://iac/payments/helm/render-profile/charts/api/default"}, "codeanalyzer-iac.yaml@explicit": {"src": "can://artifact/payments/codeanalyzer-iac.yaml", "dst": "can://iac/payments/helm/render-profile/config/explicit"}}, "iac_renders_chart": {"default": {"src": "can://iac/payments/helm/render-profile/charts/api/default", "dst": "can://artifact/payments/charts/api/Chart.yaml"}, "explicit": {"src": "can://iac/payments/helm/render-profile/config/explicit", "dst": "can://artifact/payments/charts/api/Chart.yaml"}}, - "iac_has_value_layer": {"default@0000": {"src": "can://iac/payments/helm/render-profile/charts/api/default", "dst": "can://iac/payments/helm/render-profile/charts/api/default/value-layer/0000"}, "default@0001": {"src": "can://iac/payments/helm/render-profile/charts/api/default", "dst": "can://iac/payments/helm/render-profile/charts/api/default/value-layer/0001"}}, - "iac_reads_from": {"default@0000": {"src": "can://iac/payments/helm/render-profile/charts/api/default/value-layer/0000", "dst": "can://artifact/payments/charts/api/values.yaml"}, "default@0001": {"src": "can://iac/payments/helm/render-profile/charts/api/default/value-layer/0001", "dst": "can://artifact/payments/codeanalyzer-iac.yaml"}}, + "iac_has_value_layer": {"default@0000": {"src": "can://iac/payments/helm/render-profile/charts/api/default", "dst": "can://iac/payments/helm/render-profile/charts/api/default/value-layer/0000"}, "explicit@0000": {"src": "can://iac/payments/helm/render-profile/config/explicit", "dst": "can://iac/payments/helm/render-profile/config/explicit/value-layer/0000"}, "explicit@0001": {"src": "can://iac/payments/helm/render-profile/config/explicit", "dst": "can://iac/payments/helm/render-profile/config/explicit/value-layer/0001"}}, + "iac_reads_from": {"default@0000": {"src": "can://iac/payments/helm/render-profile/charts/api/default/value-layer/0000", "dst": "can://artifact/payments/charts/api/values.yaml"}, "explicit@0000": {"src": "can://iac/payments/helm/render-profile/config/explicit/value-layer/0000", "dst": "can://artifact/payments/charts/api/values.yaml"}, "explicit@0001": {"src": "can://iac/payments/helm/render-profile/config/explicit/value-layer/0001", "dst": "can://artifact/payments/codeanalyzer-iac.yaml@key/render_profiles.explicit.set.image.tag"}}, "iac_has_render": {"default": {"src": "can://artifact/payments/charts/api/Chart.yaml", "dst": "can://iac/payments/helm/render/charts/api/default"}, "explicit": {"src": "can://artifact/payments/charts/api/Chart.yaml", "dst": "can://iac/payments/helm/render/charts/api/explicit"}}, "iac_configured_by": {"default": {"src": "can://iac/payments/helm/render/charts/api/default", "dst": "can://iac/payments/helm/render-profile/charts/api/default"}, "explicit": {"src": "can://iac/payments/helm/render/charts/api/explicit", "dst": "can://iac/payments/helm/render-profile/config/explicit"}}, + "iac_has_diagnostic": {"explicit@render-failed": {"src": "can://iac/payments/helm/render/charts/api/explicit", "dst": "can://iac/payments/helm/render/charts/api/explicit/diagnostic/IAC_HELM_RENDER_FAILED"}}, "iac_produces": {"default@deployment": {"src": "can://iac/payments/helm/render/charts/api/default", "dst": "can://iac/payments/helm/render/charts/api/default/kubernetes/apps/Deployment/default/api"}, "default@secret": {"src": "can://iac/payments/helm/render/charts/api/default", "dst": "can://iac/payments/helm/render/charts/api/default/kubernetes/core/Secret/default/api-credentials"}}, "iac_targets_resource": {"deployment": {"src": "can://iac/payments/helm/render/charts/api/default/kubernetes/apps/Deployment/default/api", "dst": "can://iac/payments/kubernetes/address/apps/Deployment/default/api"}, "secret": {"src": "can://iac/payments/helm/render/charts/api/default/kubernetes/core/Secret/default/api-credentials", "dst": "can://iac/payments/kubernetes/address/core/Secret/default/api-credentials"}}, "iac_derived_from": {"deployment": {"src": "can://iac/payments/helm/render/charts/api/default/kubernetes/apps/Deployment/default/api", "dst": "can://iac/payments/helm/charts/api/templates/deployment.yaml/resource-template@1:1"}, "secret": {"src": "can://iac/payments/helm/render/charts/api/default/kubernetes/core/Secret/default/api-credentials", "dst": "can://iac/payments/helm/charts/api/templates/deployment.yaml/resource-template@1:1"}}, - "iac_alias_of": {"charts/api/Chart.yaml@helm-chart": {"src": "can://iac/payments/helm/chart/charts/api", "dst": "can://artifact/payments/charts/api/Chart.yaml"}} + "iac_alias_of": {"charts/api/Chart.yaml@helm-chart": {"src": "can://iac/payments/helm/chart/charts/api", "dst": "can://artifact/payments/charts/api/Chart.yaml"}, "charts/api/charts/postgresql/Chart.yaml@helm-chart": {"src": "can://iac/payments/helm/chart/charts/api/charts/postgresql", "dst": "can://artifact/payments/charts/api/charts/postgresql/Chart.yaml"}} } } } diff --git a/v2/iac/json/analysis.schema.json b/v2/iac/json/analysis.schema.json index effd33d..f4444cf 100644 --- a/v2/iac/json/analysis.schema.json +++ b/v2/iac/json/analysis.schema.json @@ -13,6 +13,54 @@ }, "$ref": "#/$defs/Analysis", "$defs": { + "ApplicationId": { + "type": "string", + "pattern": "^can://iac/(?:[A-Za-z0-9._~-]|%[0-9A-Fa-f]{2})+(?![\\s\\S])" + }, + "ArtifactId": { + "type": "string", + "pattern": "^can://artifact/(?:[A-Za-z0-9._~-]|%[0-9A-Fa-f]{2})+/(?:[A-Za-z0-9._~-]|%[0-9A-Fa-f]{2})+(?:/(?:[A-Za-z0-9._~-]|%[0-9A-Fa-f]{2})+)*(?![\\s\\S])" + }, + "ConfigKeyId": { + "type": "string", + "pattern": "^can://artifact/(?:[A-Za-z0-9._~-]|%[0-9A-Fa-f]{2})+/(?:[A-Za-z0-9._~-]|%[0-9A-Fa-f]{2})+(?:/(?:[A-Za-z0-9._~-]|%[0-9A-Fa-f]{2})+)*@key/(?:[A-Za-z0-9._~-]|%[0-9A-Fa-f]{2})+(?:/(?:[A-Za-z0-9._~-]|%[0-9A-Fa-f]{2})+)*(?![\\s\\S])" + }, + "SemanticId": { + "type": "string", + "pattern": "^can://iac/(?:[A-Za-z0-9._~-]|%[0-9A-Fa-f]{2})+/(?:[A-Za-z0-9._~-]|%[0-9A-Fa-f]{2})+(?:/(?:[A-Za-z0-9._~-]|%[0-9A-Fa-f]{2})+)*(?:@(?:[A-Za-z0-9._~-]|%[0-9A-Fa-f]{2})+(?::(?:[A-Za-z0-9._~-]|%[0-9A-Fa-f]{2})+)*)?(?![\\s\\S])" + }, + "Purl": { + "type": "string", + "pattern": "^pkg:oci/(?:[A-Za-z0-9._~-]|%[0-9A-Fa-f]{2})+(?:/(?:[A-Za-z0-9._~-]|%[0-9A-Fa-f]{2})+)*(?:@(?:[A-Za-z0-9._~-]|%[0-9A-Fa-f]{2})+)?(?:\\?[a-z][a-z0-9._-]*=(?:[A-Za-z0-9._~-]|%[0-9A-Fa-f]{2})+(?:&[a-z][a-z0-9._-]*=(?:[A-Za-z0-9._~-]|%[0-9A-Fa-f]{2})+)*)?(?:#(?:[A-Za-z0-9._~-]|%[0-9A-Fa-f]{2})+(?:/(?:[A-Za-z0-9._~-]|%[0-9A-Fa-f]{2})+)*)?(?![\\s\\S])" + }, + "AliasTargetId": { + "oneOf": [ + {"$ref": "#/$defs/ArtifactId"}, + {"$ref": "#/$defs/SemanticId"} + ] + }, + "ValueSourceId": { + "oneOf": [ + {"$ref": "#/$defs/ArtifactId"}, + {"$ref": "#/$defs/ConfigKeyId"} + ] + }, + "OriginId": { + "oneOf": [ + {"$ref": "#/$defs/ArtifactId"}, + {"$ref": "#/$defs/ConfigKeyId"}, + {"$ref": "#/$defs/SemanticId"} + ] + }, + "NodeId": { + "oneOf": [ + {"$ref": "#/$defs/ApplicationId"}, + {"$ref": "#/$defs/ArtifactId"}, + {"$ref": "#/$defs/ConfigKeyId"}, + {"$ref": "#/$defs/SemanticId"}, + {"$ref": "#/$defs/Purl"} + ] + }, "Analysis": { "type": "object", "additionalProperties": false, @@ -48,7 +96,7 @@ "edges" ], "properties": { - "id": {"type": "string", "pattern": "^can://iac/[^/]+$"}, + "id": {"$ref": "#/$defs/ApplicationId"}, "kind": {"const": "application"}, "artifacts": { "type": "object", @@ -78,7 +126,7 @@ "additionalProperties": false, "required": ["id", "kind", "path", "format", "source", "sha256", "size_bytes", "config_keys", "aliases"], "properties": { - "id": {"type": "string", "pattern": "^can://artifact/(?:[A-Za-z0-9._~-]|%[0-9A-Fa-f]{2})+/(?:[A-Za-z0-9._~-]|%[0-9A-Fa-f]{2})+(?:/(?:[A-Za-z0-9._~-]|%[0-9A-Fa-f]{2})+)*(?![\\s\\S])"}, + "id": {"$ref": "#/$defs/ArtifactId"}, "kind": {"const": "artifact"}, "path": { "type": "string", @@ -130,7 +178,7 @@ "additionalProperties": false, "required": ["id", "kind", "name", "path", "span"], "properties": { - "id": {"type": "string", "pattern": "^can://artifact/(?:[A-Za-z0-9._~-]|%[0-9A-Fa-f]{2})+/(?:[A-Za-z0-9._~-]|%[0-9A-Fa-f]{2})+(?:/(?:[A-Za-z0-9._~-]|%[0-9A-Fa-f]{2})+)*@key/(?:[A-Za-z0-9._~-]|%[0-9A-Fa-f]{2})+(?:/(?:[A-Za-z0-9._~-]|%[0-9A-Fa-f]{2})+)*(?![\\s\\S])"}, + "id": {"$ref": "#/$defs/ConfigKeyId"}, "kind": {"const": "config_key"}, "name": {"type": "string", "minLength": 1}, "path": {"type": "string", "minLength": 1}, @@ -143,9 +191,9 @@ "additionalProperties": false, "required": ["id", "kind", "target"], "properties": { - "id": {"type": "string", "pattern": "^can://iac/"}, + "id": {"$ref": "#/$defs/SemanticId"}, "kind": {"type": "string", "minLength": 1}, - "target": {"type": "string", "pattern": "^can://(artifact|iac)/"} + "target": {"$ref": "#/$defs/AliasTargetId"} } }, "Diagnostic": { @@ -153,13 +201,13 @@ "additionalProperties": false, "required": ["id", "kind", "severity", "code", "message"], "properties": { - "id": {"type": "string", "pattern": "^can://iac/"}, + "id": {"$ref": "#/$defs/SemanticId"}, "kind": {"const": "diagnostic"}, "severity": {"enum": ["info", "warning", "error"]}, "code": {"type": "string", "minLength": 1}, "message": {"type": "string", "minLength": 1}, "phase": {"enum": ["load", "dependency", "values", "template", "decode"]}, - "artifact_id": {"type": "string", "pattern": "^can://artifact/"}, + "artifact_id": {"$ref": "#/$defs/ArtifactId"}, "span": {"$ref": "#/$defs/Span"} } }, @@ -168,9 +216,9 @@ "additionalProperties": false, "required": ["id", "kind", "purl"], "properties": { - "id": {"type": "string", "pattern": "^pkg:[a-z][a-z0-9.+-]*/.+"}, + "id": {"$ref": "#/$defs/Purl"}, "kind": {"const": "package"}, - "purl": {"type": "string", "pattern": "^pkg:[a-z][a-z0-9.+-]*/.+"} + "purl": {"$ref": "#/$defs/Purl"} } }, "Edge": { @@ -178,8 +226,8 @@ "additionalProperties": false, "required": ["src", "dst"], "properties": { - "src": {"type": "string", "minLength": 1}, - "dst": {"type": "string", "minLength": 1} + "src": {"$ref": "#/$defs/NodeId"}, + "dst": {"$ref": "#/$defs/NodeId"} } }, "IaCFacet": { @@ -342,7 +390,7 @@ "additionalProperties": false, "required": ["id", "kind", "name", "version_constraint", "span"], "properties": { - "id": {"type": "string", "pattern": "^can://iac/"}, + "id": {"$ref": "#/$defs/SemanticId"}, "kind": {"const": "helm_dependency"}, "name": {"type": "string", "minLength": 1}, "version_constraint": {"type": "string", "minLength": 1}, @@ -375,7 +423,7 @@ "additionalProperties": false, "required": ["id", "kind", "name", "span"], "properties": { - "id": {"type": "string", "pattern": "^can://iac/"}, + "id": {"$ref": "#/$defs/SemanticId"}, "kind": {"const": "helm_named_template"}, "name": {"type": "string", "minLength": 1}, "span": {"$ref": "#/$defs/Span"} @@ -386,11 +434,11 @@ "additionalProperties": false, "required": ["id", "kind", "call_kind", "name_expression", "span"], "properties": { - "id": {"type": "string", "pattern": "^can://iac/"}, + "id": {"$ref": "#/$defs/SemanticId"}, "kind": {"const": "helm_template_call"}, "call_kind": {"enum": ["template", "include", "block", "tpl"]}, "name_expression": {"type": "string"}, - "target_id": {"type": "string", "pattern": "^can://iac/"}, + "target_id": {"$ref": "#/$defs/SemanticId"}, "span": {"$ref": "#/$defs/Span"} } }, @@ -399,10 +447,10 @@ "additionalProperties": false, "required": ["id", "kind", "path_expression", "span"], "properties": { - "id": {"type": "string", "pattern": "^can://iac/"}, + "id": {"$ref": "#/$defs/SemanticId"}, "kind": {"const": "helm_value_reference"}, "path_expression": {"type": "string", "minLength": 1}, - "target_id": {"type": "string", "pattern": "^can://(artifact|iac)/"}, + "target_id": {"$ref": "#/$defs/ConfigKeyId"}, "span": {"$ref": "#/$defs/Span"} } }, @@ -411,7 +459,7 @@ "additionalProperties": false, "required": ["id", "kind", "document_index", "span"], "properties": { - "id": {"type": "string", "pattern": "^can://iac/"}, + "id": {"$ref": "#/$defs/SemanticId"}, "kind": {"const": "helm_resource_template"}, "document_index": {"type": "integer", "minimum": 0}, "span": {"$ref": "#/$defs/Span"} @@ -431,7 +479,7 @@ "span" ], "properties": { - "id": {"type": "string", "pattern": "^can://iac/"}, + "id": {"$ref": "#/$defs/SemanticId"}, "kind": {"const": "helm_lookup_reference"}, "group_expression": {"type": "string"}, "version_expression": {"type": "string"}, @@ -479,13 +527,13 @@ "additionalProperties": false, "required": ["id", "kind", "name", "version_constraint"], "properties": { - "id": {"type": "string", "pattern": "^can://iac/"}, + "id": {"$ref": "#/$defs/SemanticId"}, "kind": {"const": "helm_chart_reference"}, "name": {"type": "string", "minLength": 1}, "version_constraint": {"type": "string", "minLength": 1}, "repository": {"type": "string", "minLength": 1}, - "purl": {"type": "string", "pattern": "^pkg:[a-z][a-z0-9.+-]*/.+"}, - "resolved_chart_id": {"type": "string", "pattern": "^can://iac/"} + "purl": {"$ref": "#/$defs/Purl"}, + "resolved_chart_id": {"$ref": "#/$defs/ArtifactId"} } }, "CodeAnalyzerIaCConfig": { @@ -503,11 +551,11 @@ "additionalProperties": false, "required": ["id", "kind", "name", "origin", "chart_id", "release_name", "namespace", "value_layers", "api_versions"], "properties": { - "id": {"type": "string", "pattern": "^can://iac/"}, + "id": {"$ref": "#/$defs/SemanticId"}, "kind": {"const": "helm_render_profile"}, "name": {"type": "string", "minLength": 1}, "origin": {"enum": ["default", "config"]}, - "chart_id": {"type": "string", "pattern": "^can://artifact/"}, + "chart_id": {"$ref": "#/$defs/ArtifactId"}, "release_name": {"type": "string", "minLength": 1}, "namespace": {"type": "string", "minLength": 1}, "value_layers": {"type": "object", "additionalProperties": {"$ref": "#/$defs/HelmValueLayer"}}, @@ -520,10 +568,10 @@ "additionalProperties": false, "required": ["id", "kind", "ordinal", "source_id"], "properties": { - "id": {"type": "string", "pattern": "^can://iac/"}, + "id": {"$ref": "#/$defs/SemanticId"}, "kind": {"const": "helm_value_layer"}, "ordinal": {"type": "integer", "minimum": 0}, - "source_id": {"type": "string", "pattern": "^can://(artifact|iac)/"} + "source_id": {"$ref": "#/$defs/ValueSourceId"} } }, "HelmRender": { @@ -531,38 +579,45 @@ "additionalProperties": false, "required": ["id", "kind", "status", "profile_id", "renderer_name", "renderer_version", "value_layer_ids", "effective_values_sha256", "diagnostics", "resources"], "properties": { - "id": {"type": "string", "pattern": "^can://iac/"}, + "id": {"$ref": "#/$defs/SemanticId"}, "kind": {"const": "helm_render"}, "status": {"enum": ["succeeded", "partial", "failed", "skipped"]}, - "profile_id": {"type": "string", "pattern": "^can://iac/"}, + "profile_id": {"$ref": "#/$defs/SemanticId"}, "renderer_name": {"type": "string", "minLength": 1}, "renderer_version": {"type": "string", "minLength": 1}, - "value_layer_ids": {"type": "array", "items": {"type": "string", "pattern": "^can://iac/"}}, + "value_layer_ids": {"type": "array", "items": {"$ref": "#/$defs/SemanticId"}}, "effective_values_sha256": {"type": "string", "pattern": "^[0-9a-f]{64}$"}, "diagnostics": {"type": "object", "additionalProperties": {"$ref": "#/$defs/Diagnostic"}}, "resources": {"type": "object", "additionalProperties": {"$ref": "#/$defs/KubernetesResource"}}, "phase": {"enum": ["load", "dependency", "values", "template", "decode"]} - } + }, + "allOf": [ + { + "if": {"properties": {"status": {"const": "succeeded"}}}, + "then": {"not": {"required": ["phase"]}}, + "else": {"required": ["phase"]} + } + ] }, "KubernetesResource": { "type": "object", "additionalProperties": false, "required": ["id", "kind", "api_version", "resource_kind", "manifest_sha256", "render_id", "origin_ids"], "properties": { - "id": {"type": "string", "pattern": "^can://iac/"}, + "id": {"$ref": "#/$defs/SemanticId"}, "kind": {"const": "kubernetes_resource"}, "api_version": {"type": "string", "minLength": 1}, "resource_kind": {"type": "string", "minLength": 1}, "manifest_sha256": {"type": "string", "pattern": "^[0-9a-f]{64}$"}, - "render_id": {"type": "string", "pattern": "^can://iac/"}, - "origin_ids": {"type": "array", "items": {"type": "string", "pattern": "^can://(artifact|iac)/"}}, + "render_id": {"$ref": "#/$defs/SemanticId"}, + "origin_ids": {"type": "array", "items": {"$ref": "#/$defs/OriginId"}}, "namespace": {"type": "string", "minLength": 1}, "name": {"type": "string", "minLength": 1}, "generate_name": {"type": "string", "minLength": 1}, "labels": {"type": "object", "additionalProperties": {"type": "string"}}, "annotations": {"type": "object", "additionalProperties": {"type": "string"}}, "plural": {"type": "string", "minLength": 1}, - "address_id": {"type": "string", "pattern": "^can://iac/"}, + "address_id": {"$ref": "#/$defs/SemanticId"}, "secret_data": {"type": "object", "additionalProperties": {"$ref": "#/$defs/KubernetesSecretDatum"}} } }, @@ -580,7 +635,7 @@ "additionalProperties": false, "required": ["id", "kind", "group", "resource_kind", "namespace", "name"], "properties": { - "id": {"type": "string", "pattern": "^can://iac/"}, + "id": {"$ref": "#/$defs/SemanticId"}, "kind": {"const": "kubernetes_resource_address"}, "group": {"type": "string"}, "resource_kind": {"type": "string", "minLength": 1}, diff --git a/v2/iac/neo4j/schema.neo4j.sample.json b/v2/iac/neo4j/schema.neo4j.sample.json index 6f6028b..83a1444 100644 --- a/v2/iac/neo4j/schema.neo4j.sample.json +++ b/v2/iac/neo4j/schema.neo4j.sample.json @@ -4,38 +4,38 @@ "marker_labels": [], "node_labels": [ {"label": "Application", "merge_label": "Application", "key": "id", "properties": {"id": "string"}}, - {"label": "IaCApplication", "merge_label": "Application", "key": "id", "properties": {"id": "string"}}, - {"label": "Artifact", "merge_label": "Artifact", "key": "id", "properties": {"id": "string"}}, - {"label": "IaCArtifact", "merge_label": "Artifact", "key": "id", "properties": {"id": "string"}}, - {"label": "HelmArtifact", "merge_label": "Artifact", "key": "id", "properties": {"id": "string"}}, - {"label": "HelmChart", "merge_label": "Artifact", "key": "id", "properties": {"id": "string"}}, - {"label": "HelmRequirements", "merge_label": "Artifact", "key": "id", "properties": {"id": "string"}}, - {"label": "HelmLock", "merge_label": "Artifact", "key": "id", "properties": {"id": "string"}}, - {"label": "HelmValues", "merge_label": "Artifact", "key": "id", "properties": {"id": "string"}}, - {"label": "HelmValuesSchema", "merge_label": "Artifact", "key": "id", "properties": {"id": "string"}}, - {"label": "HelmTemplate", "merge_label": "Artifact", "key": "id", "properties": {"id": "string"}}, - {"label": "HelmCRD", "merge_label": "Artifact", "key": "id", "properties": {"id": "string"}}, - {"label": "HelmIgnore", "merge_label": "Artifact", "key": "id", "properties": {"id": "string"}}, - {"label": "ConfigKey", "merge_label": "ConfigKey", "key": "id", "properties": {"id": "string"}}, - {"label": "IaCValue", "merge_label": "ConfigKey", "key": "id", "properties": {"id": "string"}}, - {"label": "HelmValue", "merge_label": "ConfigKey", "key": "id", "properties": {"id": "string"}}, - {"label": "CodeAnalyzerIaCConfig", "merge_label": "Artifact", "key": "id", "properties": {"id": "string"}}, - {"label": "HelmDependency", "merge_label": "HelmDependency", "key": "id", "properties": {"id": "string"}}, - {"label": "HelmChartReference", "merge_label": "HelmChartReference", "key": "id", "properties": {"id": "string"}}, - {"label": "HelmNamedTemplate", "merge_label": "HelmNamedTemplate", "key": "id", "properties": {"id": "string"}}, - {"label": "HelmTemplateCall", "merge_label": "HelmTemplateCall", "key": "id", "properties": {"id": "string"}}, - {"label": "HelmValueReference", "merge_label": "HelmValueReference", "key": "id", "properties": {"id": "string"}}, - {"label": "HelmResourceTemplate", "merge_label": "HelmResourceTemplate", "key": "id", "properties": {"id": "string"}}, - {"label": "HelmLookupReference", "merge_label": "HelmLookupReference", "key": "id", "properties": {"id": "string"}}, - {"label": "HelmRenderProfile", "merge_label": "HelmRenderProfile", "key": "id", "properties": {"id": "string", "origin": "string"}}, - {"label": "HelmValueLayer", "merge_label": "HelmValueLayer", "key": "id", "properties": {"id": "string", "ordinal": "integer", "source_id": "string"}}, - {"label": "HelmRender", "merge_label": "HelmRender", "key": "id", "properties": {"id": "string", "status": "string", "profile_id": "string", "value_layer_ids": "string[]", "effective_values_sha256": "string"}}, - {"label": "IaCDiagnostic", "merge_label": "IaCDiagnostic", "key": "id", "properties": {"id": "string", "severity": "string", "code": "string", "message": "string"}}, - {"label": "HelmDiagnostic", "merge_label": "IaCDiagnostic", "key": "id", "properties": {"id": "string", "severity": "string", "code": "string", "message": "string"}}, - {"label": "KubernetesResource", "merge_label": "KubernetesResource", "key": "id", "properties": {"id": "string", "api_version": "string", "resource_kind": "string", "manifest_sha256": "string", "render_id": "string", "origin_ids": "string[]"}}, - {"label": "KubernetesResourceAddress", "merge_label": "KubernetesResourceAddress", "key": "id", "properties": {"id": "string", "group": "string", "resource_kind": "string", "namespace": "string", "name": "string"}}, - {"label": "IdentityAlias", "merge_label": "IdentityAlias", "key": "id", "properties": {"id": "string", "target": "string"}}, - {"label": "IaCAlias", "merge_label": "IdentityAlias", "key": "id", "properties": {"id": "string", "target": "string"}}, + {"label": "IaCApplication", "merge_label": "Application", "key": "id", "properties": {"iac_analyzer_version": "string", "iac_app_id": "string", "iac_producer": "string", "id": "string"}}, + {"label": "Artifact", "merge_label": "Artifact", "key": "id", "properties": {"format": "string", "id": "string", "path": "string", "sha256": "string", "size_bytes": "integer", "source": "string"}}, + {"label": "IaCArtifact", "merge_label": "Artifact", "key": "id", "properties": {"iac_analyzer_version": "string", "iac_app_id": "string", "iac_dialect": "string", "iac_kind": "string", "iac_producer": "string", "iac_status": "string", "id": "string"}}, + {"label": "HelmArtifact", "merge_label": "Artifact", "key": "id", "properties": {"iac_analyzer_version": "string", "iac_app_id": "string", "iac_dialect": "string", "iac_kind": "string", "iac_producer": "string", "iac_status": "string", "id": "string"}}, + {"label": "HelmChart", "merge_label": "Artifact", "key": "id", "properties": {"helm_annotations_json": "string", "helm_api_version": "string", "helm_app_version": "string", "helm_chart_type": "string", "helm_deprecated": "boolean", "helm_description": "string", "helm_home": "string", "helm_icon": "string", "helm_keywords": "string[]", "helm_kube_version": "string", "helm_maintainers_json": "string", "helm_name": "string", "helm_sources": "string[]", "helm_version": "string", "iac_analyzer_version": "string", "iac_app_id": "string", "iac_dialect": "string", "iac_kind": "string", "iac_producer": "string", "iac_status": "string", "id": "string"}}, + {"label": "HelmRequirements", "merge_label": "Artifact", "key": "id", "properties": {"helm_roles": "string[]", "iac_analyzer_version": "string", "iac_app_id": "string", "iac_dialect": "string", "iac_kind": "string", "iac_producer": "string", "iac_status": "string", "id": "string"}}, + {"label": "HelmLock", "merge_label": "Artifact", "key": "id", "properties": {"helm_roles": "string[]", "iac_analyzer_version": "string", "iac_app_id": "string", "iac_dialect": "string", "iac_kind": "string", "iac_producer": "string", "iac_status": "string", "id": "string"}}, + {"label": "HelmValues", "merge_label": "Artifact", "key": "id", "properties": {"helm_roles": "string[]", "iac_analyzer_version": "string", "iac_app_id": "string", "iac_dialect": "string", "iac_kind": "string", "iac_producer": "string", "iac_status": "string", "id": "string"}}, + {"label": "HelmValuesSchema", "merge_label": "Artifact", "key": "id", "properties": {"helm_roles": "string[]", "iac_analyzer_version": "string", "iac_app_id": "string", "iac_dialect": "string", "iac_kind": "string", "iac_producer": "string", "iac_status": "string", "id": "string"}}, + {"label": "HelmTemplate", "merge_label": "Artifact", "key": "id", "properties": {"helm_roles": "string[]", "iac_analyzer_version": "string", "iac_app_id": "string", "iac_dialect": "string", "iac_kind": "string", "iac_producer": "string", "iac_status": "string", "id": "string"}}, + {"label": "HelmCRD", "merge_label": "Artifact", "key": "id", "properties": {"helm_roles": "string[]", "iac_analyzer_version": "string", "iac_app_id": "string", "iac_dialect": "string", "iac_kind": "string", "iac_producer": "string", "iac_status": "string", "id": "string"}}, + {"label": "HelmIgnore", "merge_label": "Artifact", "key": "id", "properties": {"helm_roles": "string[]", "iac_analyzer_version": "string", "iac_app_id": "string", "iac_dialect": "string", "iac_kind": "string", "iac_producer": "string", "iac_status": "string", "id": "string"}}, + {"label": "ConfigKey", "merge_label": "ConfigKey", "key": "id", "properties": {"id": "string", "name": "string", "path": "string", "span_json": "string"}}, + {"label": "IaCValue", "merge_label": "ConfigKey", "key": "id", "properties": {"iac_analyzer_version": "string", "iac_app_id": "string", "iac_kind": "string", "iac_producer": "string", "id": "string"}}, + {"label": "HelmValue", "merge_label": "ConfigKey", "key": "id", "properties": {"iac_analyzer_version": "string", "iac_app_id": "string", "iac_kind": "string", "iac_producer": "string", "id": "string"}}, + {"label": "CodeAnalyzerIaCConfig", "merge_label": "Artifact", "key": "id", "properties": {"iac_analyzer_version": "string", "iac_app_id": "string", "iac_config_version": "integer", "iac_producer": "string", "id": "string"}}, + {"label": "HelmDependency", "merge_label": "HelmDependency", "key": "id", "properties": {"alias": "string", "analyzer_version": "string", "condition": "string", "iac_app_id": "string", "id": "string", "import_values_json": "string", "name": "string", "producer": "string", "repository": "string", "span_json": "string", "tags": "string[]", "version_constraint": "string"}}, + {"label": "HelmChartReference", "merge_label": "HelmChartReference", "key": "id", "properties": {"analyzer_version": "string", "iac_app_id": "string", "id": "string", "name": "string", "producer": "string", "purl": "string", "repository": "string", "resolved_chart_id": "string", "version_constraint": "string"}}, + {"label": "HelmNamedTemplate", "merge_label": "HelmNamedTemplate", "key": "id", "properties": {"analyzer_version": "string", "iac_app_id": "string", "id": "string", "name": "string", "producer": "string", "span_json": "string"}}, + {"label": "HelmTemplateCall", "merge_label": "HelmTemplateCall", "key": "id", "properties": {"analyzer_version": "string", "call_kind": "string", "iac_app_id": "string", "id": "string", "name_expression": "string", "producer": "string", "span_json": "string", "target_id": "string"}}, + {"label": "HelmValueReference", "merge_label": "HelmValueReference", "key": "id", "properties": {"analyzer_version": "string", "iac_app_id": "string", "id": "string", "path_expression": "string", "producer": "string", "span_json": "string", "target_id": "string"}}, + {"label": "HelmResourceTemplate", "merge_label": "HelmResourceTemplate", "key": "id", "properties": {"analyzer_version": "string", "document_index": "integer", "iac_app_id": "string", "id": "string", "producer": "string", "span_json": "string"}}, + {"label": "HelmLookupReference", "merge_label": "HelmLookupReference", "key": "id", "properties": {"analyzer_version": "string", "group_expression": "string", "iac_app_id": "string", "id": "string", "name_expression": "string", "namespace_expression": "string", "producer": "string", "resource_kind_expression": "string", "span_json": "string", "version_expression": "string"}}, + {"label": "HelmRenderProfile", "merge_label": "HelmRenderProfile", "key": "id", "properties": {"analyzer_version": "string", "api_versions": "string[]", "chart_id": "string", "iac_app_id": "string", "id": "string", "kube_version": "string", "name": "string", "namespace": "string", "origin": "string", "producer": "string", "release_name": "string"}}, + {"label": "HelmValueLayer", "merge_label": "HelmValueLayer", "key": "id", "properties": {"analyzer_version": "string", "iac_app_id": "string", "id": "string", "ordinal": "integer", "producer": "string", "source_id": "string"}}, + {"label": "HelmRender", "merge_label": "HelmRender", "key": "id", "properties": {"analyzer_version": "string", "effective_values_sha256": "string", "iac_app_id": "string", "id": "string", "phase": "string", "producer": "string", "profile_id": "string", "renderer_name": "string", "renderer_version": "string", "status": "string", "value_layer_ids": "string[]"}}, + {"label": "IaCDiagnostic", "merge_label": "IaCDiagnostic", "key": "id", "properties": {"analyzer_version": "string", "artifact_id": "string", "code": "string", "iac_app_id": "string", "id": "string", "message": "string", "phase": "string", "producer": "string", "severity": "string", "span_json": "string"}}, + {"label": "HelmDiagnostic", "merge_label": "IaCDiagnostic", "key": "id", "properties": {"analyzer_version": "string", "artifact_id": "string", "code": "string", "iac_app_id": "string", "id": "string", "message": "string", "phase": "string", "producer": "string", "severity": "string", "span_json": "string"}}, + {"label": "KubernetesResource", "merge_label": "KubernetesResource", "key": "id", "properties": {"address_id": "string", "analyzer_version": "string", "annotations_json": "string", "api_version": "string", "generate_name": "string", "iac_app_id": "string", "id": "string", "labels_json": "string", "manifest_sha256": "string", "name": "string", "namespace": "string", "origin_ids": "string[]", "plural": "string", "producer": "string", "render_id": "string", "resource_kind": "string", "secret_data_json": "string"}}, + {"label": "KubernetesResourceAddress", "merge_label": "KubernetesResourceAddress", "key": "id", "properties": {"analyzer_version": "string", "group": "string", "iac_app_id": "string", "id": "string", "name": "string", "namespace": "string", "plural": "string", "producer": "string", "resource_kind": "string"}}, + {"label": "IdentityAlias", "merge_label": "IdentityAlias", "key": "id", "properties": {"analyzer_version": "string", "iac_app_id": "string", "iac_kind": "string", "id": "string", "producer": "string", "target": "string"}}, + {"label": "IaCAlias", "merge_label": "IdentityAlias", "key": "id", "properties": {"analyzer_version": "string", "iac_app_id": "string", "iac_kind": "string", "id": "string", "producer": "string", "target": "string"}}, {"label": "Package", "merge_label": "Package", "key": "id", "properties": {"id": "string", "purl": "string"}} ], "relationship_types": [ From b84428f1accebe2b259d32c15387f04a00167f2c Mon Sep 17 00:00:00 2001 From: Rahul Krishna Date: Sat, 5 Sep 2026 16:11:10 -0400 Subject: [PATCH 16/16] feat(schema): add IaC containment edges for resource templates, lookups, and aliases --- scripts/check_iac.py | 9 +++ tests/test_check_iac.py | 87 +++++++++++++++++++++++++++ v2/iac/json/analysis.l1.sample.json | 22 +++++++ v2/iac/json/analysis.l2.sample.json | 22 +++++++ v2/iac/json/analysis.l3.sample.json | 3 + v2/iac/json/analysis.schema.json | 3 + v2/iac/neo4j/schema.neo4j.sample.json | 3 + 7 files changed, 149 insertions(+) diff --git a/scripts/check_iac.py b/scripts/check_iac.py index 876717c..18d2a5c 100644 --- a/scripts/check_iac.py +++ b/scripts/check_iac.py @@ -384,6 +384,9 @@ def _check_containment( for config_key in artifact.get("config_keys", {}).values(): if isinstance(config_key, dict): _require_edge(edge_rows, "defines_config", artifact_id, config_key.get("id"), "config-key containment", errors) + for alias in artifact.get("aliases", []): + if isinstance(alias, dict): + _require_edge(edge_rows, "iac_has_alias", artifact_id, alias.get("id"), "alias containment", errors) facet = artifact.get("iac") if isinstance(facet, dict): @@ -398,6 +401,12 @@ def _check_containment( for child in facet.get("value_references", {}).values(): if isinstance(child, dict): _require_edge(edge_rows, "iac_has_value_reference", artifact_id, child.get("id"), "value-reference containment", errors) + for child in facet.get("resource_templates", {}).values(): + if isinstance(child, dict): + _require_edge(edge_rows, "iac_has_resource_template", artifact_id, child.get("id"), "resource-template containment", errors) + for child in facet.get("lookup_references", {}).values(): + if isinstance(child, dict): + _require_edge(edge_rows, "iac_has_lookup_reference", artifact_id, child.get("id"), "lookup-reference containment", errors) elif facet_kind == "helm_chart": for child in facet.get("dependencies", {}).values(): if isinstance(child, dict): diff --git a/tests/test_check_iac.py b/tests/test_check_iac.py index 396ac19..c8c1e2d 100644 --- a/tests/test_check_iac.py +++ b/tests/test_check_iac.py @@ -401,6 +401,23 @@ def test_fixtures_satisfy_semantic_contract(self): self.assertEqual([], check_document(self.load(level))) self.assertEqual([], check_catalog(self.load_catalog())) + def test_fixtures_carry_containment_edges_from_level_one(self): + for level in (1, 2, 3): + with self.subTest(level=level): + edges = self.load(level)["application"]["edges"] + self.assertEqual( + ["charts/api/templates/deployment.yaml@1:1"], + list(edges.get("iac_has_resource_template", {})), + ) + self.assertEqual( + ["charts/api/templates/deployment.yaml@11:15"], + list(edges.get("iac_has_lookup_reference", {})), + ) + self.assertEqual( + sorted(edges.get("iac_alias_of", {})), + sorted(edges.get("iac_has_alias", {})), + ) + def test_resolved_vendored_chart_uses_canonical_artifact_identity(self): document = self.load(2) reference = document["application"]["external_chart_references"]["bitnami/postgresql"] @@ -669,6 +686,76 @@ def test_rejects_duplicate_matching_alias_edges(self): edges["duplicate"] = deepcopy(next(iter(edges.values()))) self.assertIn("alias must have exactly one matching iac_alias_of edge", "\n".join(check_document(doc))) + def test_requires_resource_template_containment_edge(self): + doc = self.load(1) + doc["application"]["edges"]["iac_has_resource_template"].clear() + self.assertIn( + "resource-template containment must have exactly one matching iac_has_resource_template edge", + "\n".join(check_document(doc)), + ) + + def test_rejects_dangling_resource_template_containment_edge(self): + doc = self.load(1) + doc["application"]["edges"]["iac_has_resource_template"]["bad"] = { + "src": "can://iac/payments/missing", + "dst": "can://iac/payments/helm/charts/api/templates/deployment.yaml/resource-template@1:1", + } + self.assertIn("dangling edge source", "\n".join(check_document(doc))) + + def test_rejects_reversed_resource_template_containment_edge(self): + doc = self.load(1) + edge = doc["application"]["edges"]["iac_has_resource_template"][ + "charts/api/templates/deployment.yaml@1:1" + ] + edge["src"], edge["dst"] = edge["dst"], edge["src"] + self.assertIn("edge endpoint type violation", "\n".join(check_document(doc))) + + def test_requires_lookup_reference_containment_edge(self): + doc = self.load(1) + doc["application"]["edges"]["iac_has_lookup_reference"].clear() + self.assertIn( + "lookup-reference containment must have exactly one matching iac_has_lookup_reference edge", + "\n".join(check_document(doc)), + ) + + def test_rejects_dangling_lookup_reference_containment_edge(self): + doc = self.load(1) + doc["application"]["edges"]["iac_has_lookup_reference"]["bad"] = { + "src": "can://iac/payments/missing", + "dst": "can://iac/payments/helm/charts/api/templates/deployment.yaml/lookup-reference@11:15", + } + self.assertIn("dangling edge source", "\n".join(check_document(doc))) + + def test_rejects_reversed_lookup_reference_containment_edge(self): + doc = self.load(1) + edge = doc["application"]["edges"]["iac_has_lookup_reference"][ + "charts/api/templates/deployment.yaml@11:15" + ] + edge["src"], edge["dst"] = edge["dst"], edge["src"] + self.assertIn("edge endpoint type violation", "\n".join(check_document(doc))) + + def test_requires_alias_containment_edge(self): + doc = self.load(1) + doc["application"]["edges"]["iac_has_alias"].clear() + self.assertIn( + "alias containment must have exactly one matching iac_has_alias edge", + "\n".join(check_document(doc)), + ) + + def test_rejects_dangling_alias_containment_edge(self): + doc = self.load(1) + doc["application"]["edges"]["iac_has_alias"]["bad"] = { + "src": "can://iac/payments/missing", + "dst": "can://iac/payments/helm/chart/charts/api", + } + self.assertIn("dangling edge source", "\n".join(check_document(doc))) + + def test_rejects_reversed_alias_containment_edge(self): + doc = self.load(1) + edge = doc["application"]["edges"]["iac_has_alias"]["charts/api/Chart.yaml@helm-chart"] + edge["src"], edge["dst"] = edge["dst"], edge["src"] + self.assertIn("edge endpoint type violation", "\n".join(check_document(doc))) + def test_rejects_bad_source_digest(self): doc = self.load(1) doc["application"]["artifacts"]["README.md"]["source"] += "changed" diff --git a/v2/iac/json/analysis.l1.sample.json b/v2/iac/json/analysis.l1.sample.json index 6b94e69..451794d 100644 --- a/v2/iac/json/analysis.l1.sample.json +++ b/v2/iac/json/analysis.l1.sample.json @@ -185,6 +185,16 @@ "dst": "can://artifact/payments/charts/api/values.yaml@key/image.tag" } }, + "iac_has_alias": { + "charts/api/Chart.yaml@helm-chart": { + "src": "can://artifact/payments/charts/api/Chart.yaml", + "dst": "can://iac/payments/helm/chart/charts/api" + }, + "charts/api/charts/postgresql/Chart.yaml@helm-chart": { + "src": "can://artifact/payments/charts/api/charts/postgresql/Chart.yaml", + "dst": "can://iac/payments/helm/chart/charts/api/charts/postgresql" + } + }, "iac_alias_of": { "charts/api/Chart.yaml@helm-chart": { "src": "can://iac/payments/helm/chart/charts/api", @@ -212,6 +222,18 @@ "src": "can://artifact/payments/charts/api/templates/deployment.yaml", "dst": "can://iac/payments/helm/charts/api/templates/deployment.yaml/value-reference@10:16" } + }, + "iac_has_resource_template": { + "charts/api/templates/deployment.yaml@1:1": { + "src": "can://artifact/payments/charts/api/templates/deployment.yaml", + "dst": "can://iac/payments/helm/charts/api/templates/deployment.yaml/resource-template@1:1" + } + }, + "iac_has_lookup_reference": { + "charts/api/templates/deployment.yaml@11:15": { + "src": "can://artifact/payments/charts/api/templates/deployment.yaml", + "dst": "can://iac/payments/helm/charts/api/templates/deployment.yaml/lookup-reference@11:15" + } } } } diff --git a/v2/iac/json/analysis.l2.sample.json b/v2/iac/json/analysis.l2.sample.json index cecf7c2..61d1220 100644 --- a/v2/iac/json/analysis.l2.sample.json +++ b/v2/iac/json/analysis.l2.sample.json @@ -196,6 +196,16 @@ "dst": "can://artifact/payments/charts/api/values.yaml@key/image.tag" } }, + "iac_has_alias": { + "charts/api/Chart.yaml@helm-chart": { + "src": "can://artifact/payments/charts/api/Chart.yaml", + "dst": "can://iac/payments/helm/chart/charts/api" + }, + "charts/api/charts/postgresql/Chart.yaml@helm-chart": { + "src": "can://artifact/payments/charts/api/charts/postgresql/Chart.yaml", + "dst": "can://iac/payments/helm/chart/charts/api/charts/postgresql" + } + }, "iac_alias_of": { "charts/api/Chart.yaml@helm-chart": { "src": "can://iac/payments/helm/chart/charts/api", @@ -251,6 +261,18 @@ "src": "can://iac/payments/helm/charts/api/templates/deployment.yaml/value-reference@10:16", "dst": "can://artifact/payments/charts/api/values.yaml@key/image.tag" } + }, + "iac_has_resource_template": { + "charts/api/templates/deployment.yaml@1:1": { + "src": "can://artifact/payments/charts/api/templates/deployment.yaml", + "dst": "can://iac/payments/helm/charts/api/templates/deployment.yaml/resource-template@1:1" + } + }, + "iac_has_lookup_reference": { + "charts/api/templates/deployment.yaml@11:15": { + "src": "can://artifact/payments/charts/api/templates/deployment.yaml", + "dst": "can://iac/payments/helm/charts/api/templates/deployment.yaml/lookup-reference@11:15" + } } } } diff --git a/v2/iac/json/analysis.l3.sample.json b/v2/iac/json/analysis.l3.sample.json index b8aceab..daf57f2 100644 --- a/v2/iac/json/analysis.l3.sample.json +++ b/v2/iac/json/analysis.l3.sample.json @@ -74,6 +74,8 @@ "iac_calls_template": {"charts/api/templates/deployment.yaml@6:11": {"src": "can://iac/payments/helm/charts/api/templates/deployment.yaml/template-call@6:11", "dst": "can://iac/payments/helm/charts/api/templates/deployment.yaml/named-template/api.fullname"}}, "iac_has_value_reference": {"charts/api/templates/deployment.yaml@10:16": {"src": "can://artifact/payments/charts/api/templates/deployment.yaml", "dst": "can://iac/payments/helm/charts/api/templates/deployment.yaml/value-reference@10:16"}}, "iac_references_value": {"charts/api/templates/deployment.yaml@10:16": {"src": "can://iac/payments/helm/charts/api/templates/deployment.yaml/value-reference@10:16", "dst": "can://artifact/payments/charts/api/values.yaml@key/image.tag"}}, + "iac_has_resource_template": {"charts/api/templates/deployment.yaml@1:1": {"src": "can://artifact/payments/charts/api/templates/deployment.yaml", "dst": "can://iac/payments/helm/charts/api/templates/deployment.yaml/resource-template@1:1"}}, + "iac_has_lookup_reference": {"charts/api/templates/deployment.yaml@11:15": {"src": "can://artifact/payments/charts/api/templates/deployment.yaml", "dst": "can://iac/payments/helm/charts/api/templates/deployment.yaml/lookup-reference@11:15"}}, "has_artifact": {"README.md": {"src": "can://iac/payments", "dst": "can://artifact/payments/README.md"}, "charts/api/Chart.yaml": {"src": "can://iac/payments", "dst": "can://artifact/payments/charts/api/Chart.yaml"}, "charts/api/charts/postgresql/Chart.yaml": {"src": "can://iac/payments", "dst": "can://artifact/payments/charts/api/charts/postgresql/Chart.yaml"}, "charts/api/values.yaml": {"src": "can://iac/payments", "dst": "can://artifact/payments/charts/api/values.yaml"}, "charts/api/templates/deployment.yaml": {"src": "can://iac/payments", "dst": "can://artifact/payments/charts/api/templates/deployment.yaml"}, "codeanalyzer-iac.yaml": {"src": "can://iac/payments", "dst": "can://artifact/payments/codeanalyzer-iac.yaml"}}, "defines_config": {"charts/api/values.yaml@key/image.tag": {"src": "can://artifact/payments/charts/api/values.yaml", "dst": "can://artifact/payments/charts/api/values.yaml@key/image.tag"}, "codeanalyzer-iac.yaml@key/render_profiles.explicit.set.image.tag": {"src": "can://artifact/payments/codeanalyzer-iac.yaml", "dst": "can://artifact/payments/codeanalyzer-iac.yaml@key/render_profiles.explicit.set.image.tag"}}, "iac_part_of_chart": {"charts/api/values.yaml": {"src": "can://artifact/payments/charts/api/values.yaml", "dst": "can://artifact/payments/charts/api/Chart.yaml"}, "charts/api/templates/deployment.yaml": {"src": "can://artifact/payments/charts/api/templates/deployment.yaml", "dst": "can://artifact/payments/charts/api/Chart.yaml"}}, @@ -88,6 +90,7 @@ "iac_produces": {"default@deployment": {"src": "can://iac/payments/helm/render/charts/api/default", "dst": "can://iac/payments/helm/render/charts/api/default/kubernetes/apps/Deployment/default/api"}, "default@secret": {"src": "can://iac/payments/helm/render/charts/api/default", "dst": "can://iac/payments/helm/render/charts/api/default/kubernetes/core/Secret/default/api-credentials"}}, "iac_targets_resource": {"deployment": {"src": "can://iac/payments/helm/render/charts/api/default/kubernetes/apps/Deployment/default/api", "dst": "can://iac/payments/kubernetes/address/apps/Deployment/default/api"}, "secret": {"src": "can://iac/payments/helm/render/charts/api/default/kubernetes/core/Secret/default/api-credentials", "dst": "can://iac/payments/kubernetes/address/core/Secret/default/api-credentials"}}, "iac_derived_from": {"deployment": {"src": "can://iac/payments/helm/render/charts/api/default/kubernetes/apps/Deployment/default/api", "dst": "can://iac/payments/helm/charts/api/templates/deployment.yaml/resource-template@1:1"}, "secret": {"src": "can://iac/payments/helm/render/charts/api/default/kubernetes/core/Secret/default/api-credentials", "dst": "can://iac/payments/helm/charts/api/templates/deployment.yaml/resource-template@1:1"}}, + "iac_has_alias": {"charts/api/Chart.yaml@helm-chart": {"src": "can://artifact/payments/charts/api/Chart.yaml", "dst": "can://iac/payments/helm/chart/charts/api"}, "charts/api/charts/postgresql/Chart.yaml@helm-chart": {"src": "can://artifact/payments/charts/api/charts/postgresql/Chart.yaml", "dst": "can://iac/payments/helm/chart/charts/api/charts/postgresql"}}, "iac_alias_of": {"charts/api/Chart.yaml@helm-chart": {"src": "can://iac/payments/helm/chart/charts/api", "dst": "can://artifact/payments/charts/api/Chart.yaml"}, "charts/api/charts/postgresql/Chart.yaml@helm-chart": {"src": "can://iac/payments/helm/chart/charts/api/charts/postgresql", "dst": "can://artifact/payments/charts/api/charts/postgresql/Chart.yaml"}} } } diff --git a/v2/iac/json/analysis.schema.json b/v2/iac/json/analysis.schema.json index f4444cf..b08aee6 100644 --- a/v2/iac/json/analysis.schema.json +++ b/v2/iac/json/analysis.schema.json @@ -509,6 +509,8 @@ "iac_calls_template": {"$ref": "#/$defs/EdgeMap"}, "iac_has_value_reference": {"$ref": "#/$defs/EdgeMap"}, "iac_references_value": {"$ref": "#/$defs/EdgeMap"}, + "iac_has_resource_template": {"$ref": "#/$defs/EdgeMap"}, + "iac_has_lookup_reference": {"$ref": "#/$defs/EdgeMap"}, "iac_declares_profile": {"$ref": "#/$defs/EdgeMap"}, "iac_renders_chart": {"$ref": "#/$defs/EdgeMap"}, "iac_has_value_layer": {"$ref": "#/$defs/EdgeMap"}, @@ -519,6 +521,7 @@ "iac_produces": {"$ref": "#/$defs/EdgeMap"}, "iac_targets_resource": {"$ref": "#/$defs/EdgeMap"}, "iac_derived_from": {"$ref": "#/$defs/EdgeMap"}, + "iac_has_alias": {"$ref": "#/$defs/EdgeMap"}, "iac_alias_of": {"$ref": "#/$defs/EdgeMap"} } }, diff --git a/v2/iac/neo4j/schema.neo4j.sample.json b/v2/iac/neo4j/schema.neo4j.sample.json index 83a1444..9b5655c 100644 --- a/v2/iac/neo4j/schema.neo4j.sample.json +++ b/v2/iac/neo4j/schema.neo4j.sample.json @@ -51,6 +51,8 @@ {"type": "IAC_CALLS_TEMPLATE", "from": ["HelmTemplateCall"], "to": ["HelmNamedTemplate"], "properties": {}}, {"type": "IAC_HAS_VALUE_REFERENCE", "from": ["HelmTemplate"], "to": ["HelmValueReference"], "properties": {}}, {"type": "IAC_REFERENCES_VALUE", "from": ["HelmValueReference"], "to": ["HelmValue"], "properties": {}}, + {"type": "IAC_HAS_RESOURCE_TEMPLATE", "from": ["HelmTemplate"], "to": ["HelmResourceTemplate"], "properties": {}}, + {"type": "IAC_HAS_LOOKUP_REFERENCE", "from": ["HelmTemplate"], "to": ["HelmLookupReference"], "properties": {}}, {"type": "IAC_DECLARES_PROFILE", "from": ["CodeAnalyzerIaCConfig", "HelmChart"], "to": ["HelmRenderProfile"], "properties": {}}, {"type": "IAC_RENDERS_CHART", "from": ["HelmRenderProfile"], "to": ["HelmChart"], "properties": {}}, {"type": "IAC_HAS_VALUE_LAYER", "from": ["HelmRenderProfile"], "to": ["HelmValueLayer"], "properties": {}}, @@ -61,6 +63,7 @@ {"type": "IAC_PRODUCES", "from": ["HelmRender"], "to": ["KubernetesResource"], "properties": {}}, {"type": "IAC_TARGETS_RESOURCE", "from": ["KubernetesResource"], "to": ["KubernetesResourceAddress"], "properties": {}}, {"type": "IAC_DERIVED_FROM", "from": ["KubernetesResource"], "to": ["HelmResourceTemplate"], "properties": {}}, + {"type": "IAC_HAS_ALIAS", "from": ["Artifact"], "to": ["IaCAlias"], "properties": {}}, {"type": "IAC_ALIAS_OF", "from": ["IaCAlias"], "to": ["Artifact", "KubernetesResourceAddress"], "properties": {}} ], "constraints": [