Skip to content

ConnectorValidator — declarative manifest validation

Per-field validation for connector manifests, with structured errors and recommendations served from the same source for both lm validate and lm apply.


1. Why a validator

Before connector-validator-v1, a malformed manifest hit lm apply and surfaced as either a 500 stack trace or a partially-persisted connector. Both broke the 60-second installer flow: the operator could not tell what was wrong, where it was wrong, or what to do next.

The validator gates lm apply with structured, per-field errors that point to the exact path in the manifest (e.g. spec.auth.token_env) with a recommendation:

✗ stripe.yaml — 1 error(s), 0 warning(s)

  ERROR    spec.auth.token_env
           required field missing (required when kind = bearer)

The same engine answers lm describe connector-type rest-generic, so the operator can see the schema before writing a single line of YAML.

2. Surface

Command Purpose
lm validate -f manifest.yaml Validate without applying. Exit 0 if valid (warnings allowed), 1 if errors.
lm apply -f manifest.yaml Apply, with the same validator gating the server. 400 + ValidationResult on errors, rendered by the shared formatter.
lm describe connector-type <kind> Show the schema (fields, types, required/optional, docs) for a connector type.

REST endpoints: - POST /api/platform/validator/validate — JSON or YAML body → ValidationResult{errors[], warnings[]} - GET /api/platform/validator/describe/{kind} → serialized ConfigDef (404 + {available: [...]} on unknown kind) - POST /api/platform/connectors/apply — gated; returns 400 + ValidationResult on error

3. Output examples

Valid

$ lm validate -f stripe.yaml
✓ stripe.yaml — valid

Errors only

$ lm validate -f bad.yaml
✗ bad.yaml — 2 error(s), 0 warning(s)

  ERROR    spec.auth.token_env
           required field missing (required when kind = bearer)

  ERROR    spec.endpoints[0].id
           must match kebab-case identifier

Fix errors above before running 'lm apply -f bad.yaml'.

Warnings only (still valid, exit 0)

$ lm validate -f notags.yaml
! notags.yaml — 0 error(s), 1 warning(s)

  WARN     spec.target.tags
           no tags defined — RAG scope filtering will be impossible
           → Add: target.tags: ["crm", "customers"]

4. Exit codes

Exit Meaning
0 No errors (warnings allowed)
1 One or more validation errors
2 Reserved: parse failure, file not found, server unreachable

4bis. Supported connector kinds (v1)

Kind Validator class Manifest doc
rest-generic RestConnectorValidator rest-generic.md
csv-file CsvFileConnectorValidator csv-file-manifest.md
jdbc-generic JdbcConnectorValidator jdbc-generic-manifest.md
kafka KafkaConnectorValidator kafka-inbound.md

Two endpoint kinds ride on the rest-generic engine (same envelope, per-endpoint kind:): graphql (graphql-generic.md) and soap (soap-generic.md).

5. Adding a new connector validator

To wire validation for a new connector kind (e.g. csv-file, jdbc-generic):

  1. Implement the SPI:
@ApplicationScoped
@ConnectorTypeId("my-kind")
public class MyConnectorValidator implements ConnectorValidator {

    @Override public String kind() { return "my-kind"; }

    @Override
    public ConfigDef configDef() {
        return new ConfigDef()
            .requireCustom("endpoint", Type.STRING, Importance.HIGH,
                "Service endpoint URL", ConfigDefValidators.isUrl())
            .requireNested("auth", Importance.HIGH, "Auth block", authBlock());
    }

    @Override
    public List<ValidationError> validateCrossField(JsonNode spec) {
        // Optional: rules that can't be expressed declaratively.
        return List.of();
    }

    private static ConfigDef authBlock() {
        return new ConfigDef()
            .requireEnum("kind", Importance.HIGH, "Auth method", "bearer", "basic");
    }
}

CDI discovery picks it up via Instance<ConnectorValidator> — no registration step needed.

  1. (Optional) Split sub-blocks (auth, endpoints, target) into *BlockValidator classes that expose static ConfigDef configDef(). The RestConnectorValidator uses AuthBlockValidator and EndpointsBlockValidator this way for reuse across HTTP-family connectors.

  2. Test with a @QuarkusTest and YAML fixtures under src/test/resources/validator/. Inject ConnectorValidationService and feed it the loaded manifest.

6. Built-in ConfigDefValidators helpers

Helper Purpose
isUrl() Asserts absolute URL (scheme + host)
matchesRegex(pattern, desc) Asserts the value matches a regex; desc appears in the error message
inRange(min, max) Asserts integer in [min, max]
nonEmpty() Asserts non-blank string

All return Function<JsonNode, Optional<String>> — drop into ConfigDef.requireCustom(...).

7. References

  • Source: lumnik-hub/src/main/java/io/lumnik/hub/validator/
  • Tests: lumnik-hub/src/test/java/io/lumnik/hub/validator/
  • Pilot validator: RestConnectorValidator (rest-generic)
  • Spec: docs/superpowers/specs/2026-05-25-connector-validator-design.md
  • Plan: docs/superpowers/plans/2026-05-25-connector-validator-plan.md
  • Ship snapshot: docs/snapshot/2026-05-25-connector-validator-v1-project-snapshot.md