Rule Shapes
Rule Shapes: the same policy - "express shipments need a customs code" - written three ways: a lambda, a structured check, and a relation. Then four consumers try to use each shape: the validator, the message deriver, the fixture generator, and the schema export. Representation isn't style; it's a decision about who else gets to understand you.
Data & Pipelines
Round 10
Sandi Metz
exit 0
bundle exec ruby examples/rule_shapes.rb
a real captured run
RULE SHAPES: one policy, three representations, four consumers shape enforced explains generatable projects lambda yes no no no structured check yes yes no no relation yes yes yes yes all three shapes enforce - if enforcement were the whole job, they'd be interchangeable and you'd pick by taste. but the lambda answers ONE message (call) so it has ONE consumer; the structured check adds fields: and message:, so violations can explain themselves; and the relation makes the predicate itself data, so tools that never RUN it - the generator, the schema export, round 10's diff - can still read it. choose the representation by counting who must understand it: code keeps secrets, data makes friends. save lambdas for policies that are genuinely secrets.
source
# frozen_string_literal: true # Rule Shapes: the same policy - "express shipments need a customs # code" - written three ways: a lambda, a structured check, and a # relation. Then four consumers try to use each shape: the validator, # the message deriver, the fixture generator, and the schema export. # Representation isn't style; it's a decision about who else gets to # understand you. # # bundle exec ruby examples/rule_shapes.rb # # Runs offline; the table is the argument. require class="s">"bundler/setup" require class="s">"agentic" INPUTS = { express: {type: class="s">"boolean"}, customs_code: {type: class="s">"string"} }.freeze SHAPES = { class="s">"lambda" => { class="s">"express needs customs" => ->(i) { !i[class="y">:express] || !i[class="y">:customs_code].nil? } }, class="s">"structured check" => { customs: {message: class="s">"express shipments need a customs code", fields: [class="y">:express, class="y">:customs_code], check: ->(i) { !i[class="y">:express] || !i[class="y">:customs_code].nil? }} }, class="s">"relation" => { customs: {relation: class="y">:requires, fields: [class="y">:express, class="y">:customs_code]} } }.freeze def spec_with(rules) Agentic:class="y">:CapabilitySpecification.new( name: class="s">"ship", description: class="s">"Ship it", version: class="s">"1.0.0", inputs: INPUTS, rules: rules ) end # Consumer 1: can the validator enforce it? def enforces?(spec) Agentic:class="y">:CapabilityValidator.new(spec).validate_inputs!(express: true) false rescue Agentic:class="y">:Errors:class="y">:ValidationError true end # Consumer 2: does a violation point at its fields, with a real message? def explains?(spec) Agentic:class="y">:CapabilityValidator.new(spec).validate_inputs!(express: true) false rescue Agentic:class="y">:Errors:class="y">:ValidationError => e violation = e.rule_violations.first violation[class="y">:fields].any? && !violation[class="y">:message].match?(/\A(rule_)?\d*\z/) end # Consumer 3: can a generator SATISFY it without running it blind? # (Only a declared predicate can be satisfied constructively) def generatable?(rules) rules.values.all? { |d| !d.respond_to?(class="y">:call) && d[class="y">:relation] } end # Consumer 4: does it reach the JSON Schema export as a real keyword? def projects?(spec) schema = spec.to_json_schema !(schema[class="s">"dependencies"] || schema[class="s">"allOf"]).nil? end puts class="s">"RULE SHAPES: one policy, three representations, four consumers" puts puts format(class="s">" %-22s %-10s %-10s %-12s %s", class="s">"shape", class="s">"enforced", class="s">"explains", class="s">"generatable", class="s">"projects") SHAPES.each do |name, rules| spec = spec_with(rules) puts format(class="s">" %-22s %-10s %-10s %-12s %s", name, enforces?(spec) ? class="s">"yes" : class="s">"NO", explains?(spec) ? class="s">"yes" : class="s">"no", generatable?(rules) ? class="s">"yes" : class="s">"no", projects?(spec) ? class="s">"yes" : class="s">"no") end puts puts class="s">" all three shapes enforce - if enforcement were the whole job," puts class="s">" they'd be interchangeable and you'd pick by taste. but the" puts class="s">" lambda answers ONE message (call) so it has ONE consumer; the" puts class="s">" structured check adds fields: and message:, so violations can" puts class="s">" explain themselves; and the relation makes the predicate itself" puts class="s">" data, so tools that never RUN it - the generator, the schema" puts class="s">" export, round 10's diff - can still read it. choose the" puts class="s">" representation by counting who must understand it: code keeps" puts class="s">" secrets, data makes friends. save lambdas for policies that" puts class="s">" are genuinely secrets."