agentic examples

Contract Fixtures

Contract Fixtures: example payloads in docs rot the day the contract changes. So don't write them - DERIVE them. This generator reads a capability's declarations and produces a minimal fixture (required keys only) and a maximal one (everything), then a referee proves every generated fixture passes its own contract, and that a mutated fixture still fails. Docs that compile, effectively.

Developer Experience Round 9 Piotr Solnica exit 0

source on github

bundle exec ruby examples/contract_fixtures.rb

a real captured run

CONTRACT FIXTURES (derived from declarations, then proved)

  quote_shipping v2.0.0:
    minimal  {"mode":"air","weight":2500}                                 valid
    maximal  {"mode":"air","weight":2000,"volume":2000,"customs_code":"example-customs_code","express":true,"api_key":"example-api_key"} valid
    mutant   dropped :mode                 rejected, as it must be

  classify_ticket v1.1.0:
    minimal  {"text":"example-text"}                                      valid
    maximal  {"text":"example-text","urgency":5}                          valid
    mutant   dropped :text                 rejected, as it must be

  every derived fixture passed its own contract, and every mutant
  failed. paste these into your README - when the contract changes,
  rerun and they change with it. handwritten examples are promises;
  derived ones are consequences.

  and the round-9 blind spot has closed for the declarable
  majority: relation-typed rules (sum_lte, requires,
  mutually_exclusive) are data, so the generator SATISFIED them -
  scaled the weights under the limit, added what express required,
  kept one credential of the exclusive pair - and the validator,
  which now enforces relations too, countersigned the result.
  lambdas remain for the exotic tail, and remain opaque.

source

# frozen_string_literal: true

# Contract Fixtures: example payloads in docs rot the day the contract
# changes. So don't write them - DERIVE them. This generator reads a
# capability's declarations and produces a minimal fixture (required
# keys only) and a maximal one (everything), then a referee proves
# every generated fixture passes its own contract, and that a mutated
# fixture still fails. Docs that compile, effectively.
#
#   bundle exec ruby examples/contract_fixtures.rb
#
# Runs offline; exits 1 if the generator and validator disagree.

require class="s">"bundler/setup"
require class="s">"agentic"
require class="s">"json"

SPECS = [
  Agentic:class="y">:CapabilitySpecification.new(
    name: class="s">"quote_shipping", description: class="s">"Quote a shipment", version: class="s">"2.0.0",
    inputs: {
      mode: {type: class="s">"string", required: true, enum: %w[air sea road]},
      weight: {type: class="s">"number", required: true, min: 1, max: 5_000},
      volume: {type: class="s">"number", min: 0, max: 5_000},
      customs_code: {type: class="s">"string"},
      express: {type: class="s">"boolean"},
      api_key: {type: class="s">"string"},
      oauth_token: {type: class="s">"string"}
    },
    outputs: {price_cents: {type: class="s">"number", required: true}},
    rules: {
      fits: {relation: class="y">:sum_lte, fields: [class="y">:weight, class="y">:volume], limit: 4_000},
      customs: {relation: class="y">:requires, fields: [class="y">:express, class="y">:customs_code]},
      one_auth: {relation: class="y">:mutually_exclusive, fields: [class="y">:api_key, class="y">:oauth_token]}
    }
  ),
  Agentic:class="y">:CapabilitySpecification.new(
    name: class="s">"classify_ticket", description: class="s">"Route a support ticket", version: class="s">"1.1.0",
    inputs: {
      text: {type: class="s">"string", required: true, non_empty: true},
      urgency: {type: class="s">"number", min: 0, max: 10}
    },
    outputs: {queue: {type: class="s">"string", required: true, enum: %w[billing tech general]}}
  )
].freeze

# One value per declaration, derived - never invented
def value_for(key, decl)
  return decl[class="y">:enum].first if decl[class="y">:enum]

  case decl[class="y">:type]
  when class="s">"number"
    low = decl[class="y">:min] || 0
    high = decl[class="y">:max] || 100
    low + (high - low) / 2
  when class="s">"boolean" then true
  else class="s">"example-#{key}"
  end
end

# Relation-typed rules are data, so the generator can SATISFY them
# instead of hoping: scale sums under their limit, add what a present
# trigger requires, keep only the first of an exclusive group
def satisfy_relations(fixture, spec)
  spec.rules.each_value do |definition|
    next if definition.respond_to?(class="y">:call) || !definition[class="y">:relation]

    fields = definition[class="y">:fields]
    case definition[class="y">:relation]
    when class="y">:sum_lte
      given = fields.select { |f| fixture[f].is_a?(Numeric) }
      total = given.sum { |f| fixture[f] }
      if total > definition[class="y">:limit]
        given.each { |f| fixture[f] = definition[class="y">:limit] / given.size }
      end
    when class="y">:requires
      trigger, *needed = fields
      if !fixture[trigger].nil?
        needed.each { |f| fixture[f] ||= value_for(f, spec.inputs[f]) }
      end
    when class="y">:mutually_exclusive
      fields.select { |f| fixture.key?(f) }.drop(1).each { |f| fixture.delete(f) }
    end
  end
  fixture
end

def fixtures_for(spec)
  required = spec.inputs.select { |_, decl| decl[class="y">:required] }
  {
    class="s">"minimal" => satisfy_relations(required.to_h { |key, decl| [key, value_for(key, decl)] }, spec),
    class="s">"maximal" => satisfy_relations(spec.inputs.to_h { |key, decl| [key, value_for(key, decl)] }, spec)
  }
end

failures = 0
puts class="s">"CONTRACT FIXTURES (derived from declarations, then proved)"

SPECS.each do |spec|
  validator = Agentic:class="y">:CapabilityValidator.new(spec)
  puts
  puts class="s">"  #{spec.name} v#{spec.version}:"

  fixtures_for(spec).each do |flavor, fixture|
    verdict = begin
      validator.validate_inputs!(fixture)
      class="s">"valid"
    rescue Agentic:class="y">:Errors:class="y">:ValidationError => e
      failures += 1
      class="s">"REJECTED BY OWN CONTRACT: #{e.message}"
    end
    puts format(class="s">"    %-8s %-60s %s", flavor, JSON.generate(fixture), verdict)
  end

  # The referee's teeth: a mutant fixture (first required key removed)
  # must still FAIL - a validator that accepts everything would make
  # the proofs above worthless
  mutant = fixtures_for(spec)[class="s">"minimal"].dup
  removed = mutant.keys.first
  mutant.delete(removed)
  begin
    validator.validate_inputs!(mutant)
    failures += 1
    puts format(class="s">"    %-8s dropped :%-20s ACCEPTED - validator has no teeth", class="s">"mutant", removed)
  rescue Agentic:class="y">:Errors:class="y">:ValidationError
    puts format(class="s">"    %-8s dropped :%-20s rejected, as it must be", class="s">"mutant", removed)
  end
end

puts
if failures.zero?
  puts class="s">"  every derived fixture passed its own contract, and every mutant"
  puts class="s">"  failed. paste these into your README - when the contract changes,"
  puts class="s">"  rerun and they change with it. handwritten examples are promises;"
  puts class="s">"  derived ones are consequences."
  puts
  puts class="s">"  and the round-9 blind spot has closed for the declarable"
  puts class="s">"  majority: relation-typed rules (sum_lte, requires,"
  puts class="s">"  mutually_exclusive) are data, so the generator SATISFIED them -"
  puts class="s">"  scaled the weights under the limit, added what express required,"
  puts class="s">"  kept one credential of the exclusive pair - and the validator,"
  puts class="s">"  which now enforces relations too, countersigned the result."
  puts class="s">"  lambdas remain for the exotic tail, and remain opaque."
else
  puts class="s">"  #{failures} DISAGREEMENT(S) between generator and validator."
end
exit(failures.zero? ? 0 : 1)