Skip to content

feat: Helm-first unified IaC backend (L1–L3, filesystem and Neo4j) - #2

Merged
rahlk merged 41 commits into
mainfrom
feat/helm-first-backend
Sep 5, 2026
Merged

feat: Helm-first unified IaC backend (L1–L3, filesystem and Neo4j)#2
rahlk merged 41 commits into
mainfrom
feat/helm-first-backend

Conversation

@rahlk

@rahlk rahlk commented Sep 5, 2026

Copy link
Copy Markdown
Collaborator

Implements codellm-devkit/.github#52 delivery slice: codeanalyzer-iac Helm-first backend through L3, filesystem and Neo4j inputs, JSON/Cypher/Neo4j projections with exact parity.

Closes #1.

What is in this branch

  • Typed IaC model pinned to accepted schema commit e127901 (schema.json, schema.neo4j.json byte-identical to the embedded copies).
  • Filesystem ingestion (traversal- and symlink-safe, os.OpenRoot) and paged Neo4j ingestion with SHA-256 verification.
  • Helm dialect: detection, L1 chart/values/templates/CRD/ignore facts, L2 membership/dependency/template/value resolution, L3 render profiles (--config), isolated Helm 4.2.4 SDK rendering (no network, no cluster, no fetch, no external $ref), Kubernetes decode with hash-only Secrets.
  • Deterministic orchestrator (--jobs byte-identical), compact JSON validated against the embedded schema, Cypher script, transactional Neo4j reconciliation with producer-scoped ownership and eager cleanup guard.
  • Live acceptance against pinned DayTrader and Quarkus Coffee Shop repositories with helm template 4.2.4 as an independent oracle; fuzz targets; CI.

Gates on fcc32d6

go vet, go test ./..., go test -race ./..., make schema-check, make fuzz-smoke, -j 1 vs -j 8 byte-identical (JSON and Cypher), accepted check_iac.py on L3, Neo4j parity/e2e/security with CI=true, reconcile integration, make test-live.

Known follow-ups

  • Release pipeline (thin PyPI wheel, GitHub Release binaries, Homebrew) does not exist yet; 0.1.0 tag waits on it.
  • live.yml pins codeanalyzer-schema@e127901, which must be pushed before the remote live lane can run.
  • Design record: .claude/SCHEMA_DECISIONS.md; deferred minors are listed in the review ledger and will be filed just-in-time.

rahlk added 30 commits September 2, 2026 19:52
rahlk added 11 commits September 4, 2026 20:17
Order load, detect, parse, resolve, profile and evaluate behind one
orchestrator, bound per-artifact parsing and per-profile rendering by
--jobs without letting completion order reach any identity, and emit the
canonical analysis document after validating it against both the model
invariants and the embedded schema.

An unusable configuration and, under --strict, any error-severity
diagnostic fail the analyzer only after the partial model has been
written. Graph input and graph emission report their own deterministic
unavailability until the Neo4j boundary lands.
Validate the --config selector and reject input for --emit schema in
options, so both are refused before any work and with one mode-neutral
message. Publish analysis.json at 0644 rather than the temporary file's
0600, and build the schema payload without writing into the embedded
buffer's spare capacity.

A profile that cannot be set up for rendering is now that profile's
diagnostic on the chart it targets instead of the whole evaluation's
failure, matching how the parse phase isolates one artifact. The
phase-order test now observes the load event it was asserting on.
Project a validated L3 analysis into the exact node and relationship rows
the accepted Neo4j catalog describes. The allowlist of labels, property
names and types, ownership and endpoint families is parsed from the
embedded schema.neo4j.json rather than derived from Go types, and every
projected row is checked against it, so the graph vocabulary can only be
the accepted contract's.

Artifacts stay one progressively typed node: neutral source, hash, path
and format remain unclaimed, an IaC facet adds only its labels and its
namespaced iac_*/helm_* properties, and nodes this analyzer creates in
full carry unprefixed producer metadata. Structured values become
deterministic *_json strings because Neo4j properties cannot nest;
scalar arrays stay native lists. Secret data reaches the graph as key
names and digests only.

Render deterministic Cypher and write directly over Bolt from the same
statements, so a script and a live write cannot drift. Nothing from the
analysis becomes Cypher syntax: values travel as parameters, and the only
spliced names are catalog labels, relationship types and property names,
all proven to be bare identifiers when the catalog compiles.

Reconciliation defaults to non-destructive upsert. Eager operation gives
back only this producer's stale facts for the selected application: owned
nodes, IaC facet labels, namespaced properties and IAC_* relationships.
Artifact, ConfigKey, Package, Application, HAS_ARTIFACT and DEFINES_CONFIG
are refused at the write boundary as well as at planning time. One managed
write transaction per generation means a failure leaves the graph as it
was. Filesystem input bound for a graph is wrapped so an Artifact the
graph holds at a different hash stays raw and diagnosed instead of being
enriched from text the graph disagrees with.
Constraint creation ran its statements without consuming their results and
discarded the session's close error, so a failure the server raised after
acknowledging RUN would have been lost and the generation would then have
written without the uniqueness constraints that make MERGE-by-id
idempotent. Constraint creation now checks the acknowledgement, consumes
the statement, and reports the session close error.

guardPlan checked label, property and relationship removals but never
node deletion, so a plan naming a shared Artifact ID was applied. Plans
now carry the labels each deleted node was observed with, and the write
boundary refuses any deletion those labels do not prove is wholly this
analyzer's, including one that carries no labels at all.

Both secret-plaintext tests searched for a canary the fixture never fed
in, so they held against a projector that leaks. The canary is now real
analysed text in the template artifact's source and the rendered Secret
carries its digest; the tests pin the one property that may contain it
and require every derived property, and the Cypher script outside that
one literal, to carry the digest instead.

Also: helm_roles no longer aliases the model's slice, the fixture render
uses a phase the accepted schema declares, and the vocabulary test now
requires the removal set to be empty rather than tolerating a desired
property in it.
Rendering a chart with a values.schema.json performed outbound HTTP and
arbitrary local file reads: chartutil.ToRenderValues unconditionally calls
Helm's ValidateAgainstSchema, whose compiler carries http, https and file
loaders, and the analyzer's denyExternalSchemaLoader guarded only the L1
diagnostic compiler. The render now skips Helm's validation and runs the same
check on the analyzer's own compiler, which refuses every external reference.

Render diagnostics no longer embed the SDK's error text. Values-schema and
goccy errors quote failing values and source lines, which broke README's
absolute claim that nothing derived from a render reproduces template
plaintext; a failed render now reports the phase, the profile and the chart,
the way the decode path already did.

A binary file in a repository was an error-severity IAC_SOURCE_NOT_TEXT, so
--strict failed on any real workspace, and the artifact written to the graph
with no source and a raw digest came back as IAC_GRAPH_SOURCE_HASH_MISMATCH
against a row the analyzer itself wrote. It is a warning in both input modes
now, and the graph reads such a row back as the same ineligible raw artifact.

One version string is threaded from main through core into the analysis
document, stamped by a new `make build` through -ldflags.

Also: delete the unwired parseConfig, order relationship projection, set
EvaluationInput.TempRoot explicitly and drop its unread Artifacts field,
discriminate ingest diagnostics that have no workspace-relative path, and
document that --emit cypher does not consult the target graph.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Ship the Helm-first unified IaC backend

1 participant