in reply to pre-preprocess Moose args in constructor

There are several ways to do this, depending on how much validation you want to add. A lightweight method without any validation is to add a BUILDARGS routine to your class (see Moose::Manual::Construction):
sub BUILDARGS { my $class = shift; my %args = ref $_[0] ? %{$_[0]} : @_; $args{code} = uc $args{code} if exists $args{code}; return \%args; }
A more idiomatic method is to add a Moose type for the currency code, check for its validity, and then allow lowercase input by converting it under the hood by Moose coercion. Here's a complete example:
package UpperCaseDemo; use Moose; use Moose::Util::TypeConstraints; with 'MooseX::Getopt'; subtype 'CurrencyCode' => as 'Str' => where { /^[A-Z]{3}$/ } => message { 'Currency codes should be three characters' }, ; coerce 'CurrencyCode' => from 'Str' => via { uc } ; has 'code' => ( is => 'ro', isa => 'CurrencyCode', coerce => 1, ); 1; # ----------- # Usage: lookup --code abc or lookup --code ABC # package main; my $cc = UpperCaseDemo->new_with_options(); print "code is ", $cc->code // 'not set', "\n";
BTW: I don't think that triggers are supposed to be allowed to change readonly attributes. Where did you read that?