Tooling
A small pricing app demonstrates carton check, carton rbs, and Steep. It needs the repository’s development bundle for these tools. For runtime boundaries alone, start with Minimal. Source on GitHub.
The app
Section titled “The app”The app imports Pricing and calls its exported method:
require 'carton'
# Carton boundary: the app uses only Pricing's exported method. Its RBS# declares the return type.Pricing = import_relative 'pricing'puts "Price with tax: #{Pricing.total(40)}"The pricing Entrypoint loads its own tax helper, makes Pricing::Tax private, and exports Pricing:
# The pricing Entrypoint loads its own Interior in the same Carton Box.require_relative 'pricing/tax'
module Pricing private_constant :Tax
def self.total(subtotal) = subtotal + Tax.on(subtotal)end
# Export: importing pricing returns this module.export Pricing# This helper belongs to the pricing Carton's Interior, not its Public Surface.module Pricing module Tax def self.on(subtotal) = subtotal * 0.1 endendBoundary checks
Section titled “Boundary checks”violations.rb deliberately reaches the private constant and imports an Interior file:
# Deliberately invalid code for `carton check`; main.rb is the working app.require 'carton'
Pricing = import_relative 'pricing'
# Ruby's private_constant keeps Tax out of Pricing's Public Surface.puts Pricing::Tax
# Only the Entrypoint can be imported, never an Interior file.import_relative 'pricing/tax'From the repository root, check the app and the violations without running either file:
bundle exec carton check examples/tooling/main.rbbundle exec carton check examples/tooling/violations.rb # exits 1 with two errorsThe test suite asserts both diagnostics. Running violations.rb as Ruby instead stops at its first NameError.
Pricing declares its public method’s argument and return types:
# Only the pricing Carton's Public Surface needs an RBS declaration.module Pricing def self.total: (Integer subtotal) -> Floatendcarton rbs main.rb generates a signature for the import. It refers to Pricing’s declaration instead of inferring the implementation’s types:
# Generated by `carton rbs` from the app's imports. Do not edit.
module Kernel private def import_relative: ("pricing" path) -> singleton(::Pricing) | ...endSteep reads those signatures and Carton’s own RBS, then checks the app:
# `carton rbs main.rb` types the import; sig/pricing.rbs types its Export.# In an installed app, `library 'carton'` replaces the '../../sig' path.target :app do signature 'sig', '../../sig' check 'main.rb'endcd examples/toolingbundle exec carton rbs main.rbbundle exec steep checkThe app passes. Adding Pricing.total(40).upcase fails because total returns a Float, which has no upcase method. The test suite checks that failure too. See Types and checks for the commands and their limits.
From the repository root:
bundle exec rake example:toolingThe task runs the app, checks its boundaries, regenerates the import signature, and runs Steep. CI runs the same task with the other examples.
