Does my EK9 package inherit its dependencies' capability declarations?

← Package Capability Security · Ref: Q1274

No — EK9 capability enforcement is STRICTLY PER-MODULE. Each publishable package declares only its own capabilities, and the compiler verifies those declarations against the package's own non-dev source code. Depending on another package does not inherit that package's capabilities, and the compiler performs no cross-module capability reasoning.

THE RULE

Your 'capabilities' list must cover every gated type that appears in YOUR OWN code — meaning every type your source file directly mentions, not types that dependencies happen to use internally. If you call a library function that returns an Integer, the type your code mentions is Integer; whether the function uses Stdout internally is the library's business, not yours.

THE ONE CASE WHERE IT APPLIES

If a dependency's public API takes or returns a gated type — for example, a function signature like 'makeConnection() as pure <- rtn as TCPConnection' — and your code calls that function and stores the result, then YOUR code mentions TCPConnection. TCPConnection is grouped under TCP, so your package must declare 'org.ek9.lang::TCP'. This is not transitive inheritance; it is the normal per-module rule applied to a cross-module reference: the type appears in your AST, so you must declare it.

WHY PER-MODULE, NOT TRANSITIVE

The compiler's responsibility stays tight and local: one question per module, no cross-module reasoning, no acknowledgment maps to maintain. Visibility into what dependencies declare is a repository/tooling concern — you can inspect any dependency's 'defines package' block in its source to see its capabilities, and the EK9 repository surfaces capability changes between versions for human review at upgrade time.

The trade-off is that a dependency silently adding a new capability does not automatically break consumer builds. Instead, the upgrade workflow handles it: you review what's changed when you pull the new version, not when you rebuild.

EXAMPLE

  // Library: declares Stdout, uses it internally, exports pure functions
  defines module my.library
    defines package
      publicAccess <- true
      version <- 1.0.0-0
      license <- "MIT"
      capabilities <- ["org.ek9.lang::Stdout"]
    defines function
      compute() as pure
        -> n as Integer
        <- rtn as Integer: n * 2
      internalLog()
        stdout <- Stdout()
        stdout.println("log")
  // Consumer: calls compute(), never references Stdout itself
  // Only needs its own capabilities — nothing to inherit from my.library
  defines module my.consumer
    references
      my.library::compute
    defines package
      publicAccess <- true
      version <- 1.0.0-0
      license <- "MIT"
      capabilities <- ["org.ek9.lang::Stderr"]
    defines function
      useCompute()
        stderr <- Stderr()
        result <- compute(21)
        stderr.println(`Got: ${result}`)

The consumer does NOT declare Stdout even though 'my.library' uses it internally. Only the types the consumer's own source mentions (Stderr, Integer, String) matter for its own capability check.

See Q1270 for the four-condition publishable rule. See Q1271 for declaration syntax. See Q1273 for the gated types list.

Example

defines module qa.packagecapabilities.permoduleenforcement

  defines package
    version <- 1.0.0-0
    description <- "Demonstrates per-module capability enforcement"
    license <- "MIT"
    publicAccess <- true
    capabilities <- ["org.ek9.lang::Stdout"]

  defines function

    pureHelper() as pure
      -> n as Integer
      <- rtn as Integer: n * 2

    usesOwnCapability()
      stdout <- Stdout()
      result <- pureHelper(21)
      stdout.println(`Result: ${result}`)

Common mistakes

E12010 — This module's code uses Stdout directly. Declaring Stderr instead does not cover Stdout — each gated type is checked independently against the module's own source. This illustrates per-module enforcement: even if a dependency declared Stdout, the consumer must still declare its own. See ek9 -h E12010.

Incorrect:

    capabilities <- ["org.ek9.lang::Stderr"]

Correct:

    capabilities <- ["org.ek9.lang::Stdout"]
Other ways to ask this
  • If I depend on a package that uses Stdout, do I also need to declare Stdout?
  • Does EK9 check that my dependencies' capabilities are covered in my package?
  • Are capability declarations transitive through dependencies in EK9?
  • When I call a library function that uses TCP internally, do I need to declare TCP in my own package?

Coming from another language?

Maven transitive dependencies: resolved automatically with no capability awareness. npm audit: flags CVEs but not capability changes. Cargo features: compile-time but orthogonal to permissions. Java jlink modules: declare service use but not system access. EK9: per-module capability enforcement, no transitive propagation — each module stands on its own declarations.

Keywords: inheritance, dependency, capability, enforcement, transitive, reference, per-module