DACIP DACIP

What DACIP checks

DACIP is a contract engine. For every boundary it supports, it statically extracts both sides, links them, and reports mismatches — 22 analyzers, no code execution, no LLM anywhere in the pipeline.

Output comes in two grades:

  • Full scan (dacip scan, dacip investigate) produces boundary censuses. Most mismatches surface as warnings or observations, because the other side of the contract may legitimately live outside the repo. A few full-scan checks are proven outright — an unregistered Celery beat task, a broken local import, CI that never runs your tests — because the evidence is complete in-repo.
  • The PR diff gate (dacip diff) produces defect-grade assertions: a PR that removes one side of a contract the rest of the code still uses. See the findings model for severity and confidence semantics.

Every analyzer below also names its ceiling. When DACIP can't see something, it says so instead of guessing.

HTTP routes vs frontend calls

The original boundary: backend route registrations vs the frontend calls that target them.

Side Supported
Backend Flask and Flask-RESTX; Django and DRF (include() trees, router expansion); drf-extensions nested router graphs; Flask-AppBuilder (ModelRestApi CRUD + @expose); FastAPI (include_router graphs with stacking prefixes); Express (app.get/post/… registrations, including servers living inside frontend/); Next.js App Router route.ts handlers
Frontend TypeScript/JavaScript fetch and axios calls, with baseURL resolution

Full-scan signals: UNMATCHED_CALLS, PARTIAL_ROUTES, DYNAMIC_CALLS, LOW_MATCH_RATE, NO_BACKEND_ROUTES, UNRESOLVED_FRONTEND_BASE.

Ceilings: a route prefix that isn't a constant makes the route partial, never a guess. Next.js Pages Router (pages/api/*) is not extracted — the coverage line says so. Frontend calls hidden inside wrapper functions are invisible until you teach them via coverage and teachable clients. Frontend extraction uses your repo's own TypeScript via Node; if unavailable, you get a TS_ANALYZER_UNAVAILABLE diagnostic, not silent gaps.

Every other boundary

Boundary Compares Example codes Stays silent when
Env vars os.environ/getenv, process.env reads vs .env*, compose environment:, Dockerfile ENV ENV_VAR_UNUSED (warning), ENV_VAR_UNDECLARED (one info line) No declaration file exists; dynamic key access suppresses UNUSED
OpenAPI spec Committed OpenAPI/Swagger paths vs routes extracted from source SPEC_ROUTE_NOT_IN_SOURCE, ROUTE_NOT_IN_SPEC The two route spaces don't demonstrably overlap; partial routes never compared
GraphQL Operation selection sets (.graphql/.gql, gql tags) vs SDL schema GRAPHQL_FIELD_NOT_IN_SCHEMA Enclosing type unknown; @client, introspection, interpolated fragments; more than one root schema
Events (Socket.io) emit vs on handlers, cross-language EVENT_EMITTED_NO_HANDLER (warning), EVENT_HANDLER_NEVER_EMITTED (info) Dynamic event names; reserved events (connect/disconnect) excluded
Celery Beat-scheduled and send_task names vs registered @shared_task/@task CELERY_TASK_NOT_FOUND, CELERY_TASK_NAME_MISMATCH (proven), CELERY_SEND_UNREGISTERED (warning) Sent tasks may be registered by a remote worker — warning, not defect
Kafka Produced topic literals vs in-repo consumers KAFKA_TOPIC_NO_CONSUMER (warning) Repo lacks either side; dynamic topic names
Django migrations Statically replayed migration state vs declared model fields ORM_FIELD_NOT_MIGRATED Whole app suppressed on squashed graphs or non-literal operations; only literal models.X(...) fields count
Feature flags In-source flag registry vs code references FLAG_UNUSED (info) No in-repo registry; referenced-but-undeclared is never asserted (remote/dashboard flags exist)
i18n keys t() / <FormattedMessage id> keys vs the union of locales/*.json I18N_KEY_MISSING (warning), I18N_KEY_UNUSED (info) No locale files; dynamic keys suppress the unused direction
Internal links <Link href>, router.push vs Next.js pages and react-router routes DEAD_INTERNAL_LINK (warning) Dynamic hrefs; rewrites and middleware are invisible, so warning only
Compose ports Compose container-side port vs the port the source binds COMPOSE_PORT_UNBOUND (warning) Any bind is dynamic or $PORT — which covers most production apps
Local imports Import paths vs the repo's file tree UNRESOLVED_IMPORT Third-party and stdlib imports — never checked, that's a type-checker's job
ORM manager contracts Managers whose create() provably requires a kwarg vs call sites MISSING_REQUIRED_KWARG (proven) Calls passing **kwargs; managers without a provable guard
CI config Test files present vs what workflows actually run CI_NO_TEST_RUN, CI_TEST_DISCOVERY (proven) — (reads workflow YAML directly)
Auth matrix Route census × extracted auth markers, as auth_matrix.json and a report section No finding — a descriptive artifact Frameworks without marker extraction show not_extracted; an empty cell never reads as "public"

What the diff gate asserts

On a PR, dacip diff promotes these to defect grade: BREAKING_ROUTE_REMOVED, CONTRACT_REGRESSION, AUTH_MARKER_REMOVED, REQUIRED_FIELD_ADDED, ENV_VAR_DECLARATION_REMOVED, ENV_VAR_INTRODUCED_UNDECLARED, EVENT_HANDLER_REMOVED, EVENT_EMIT_INTRODUCED_NO_HANDLER, GRAPHQL_SCHEMA_FIELD_REMOVED, SPEC_DRIFT_ROUTE_REMOVED, SPEC_DRIFT_UNDOCUMENTED_ADDED, ORM_FIELD_NOT_MIGRATED, FLAG_REGISTRY_REMOVED.

Every verdict is byte-identical across runs — verify that yourself on the proof dashboard, or start with the quickstart.