This method call is legal:
$exporter->execute($accountId, true, true);It is also semantically wrong.
The first boolean in the retained exporter meant “source billing details from the effective payer.” The payload still targeted the original account and kept that account’s operational fields. The second boolean meant “the source’s billing mode is an authoritative instruction.”
An effective-payer export described who pays. It was not allowed to change the
original account’s billing mode. The true, true combination therefore had to
be neutralized by a later guard.
Two booleans made four mechanical states. The domain had three valid intents. That mismatch is a design signal stronger than the usual complaint that boolean arguments are hard to read.
Two words conceal four contracts
Named arguments make the call less cryptic:
$exporter->execute(
accountId: $accountId,
effective: true,
billingModeAuthoritative: true,
);Now the contradiction is visible, but it is still representable.
Write the matrix before choosing a replacement:
| Effective source | Authoritative mode | Apparent meaning | Valid |
|---|---|---|---|
| no | no | routine primary refresh | yes |
| no | yes | explicit primary mode change | yes |
| yes | no | effective-payer refresh | yes |
| yes | yes | effective payer changes primary mode | no |
The last row is not merely an unusual option. It crosses two kinds of authority.
The retained correction makes that rule explicit:
authoritative mode = not effective source and requested authorityClamping prevents the bad effect. It also admits that the method signature accepts a state the operation cannot honor.
Separate target, source, and authority
The word effective hid more than a lookup choice.
In the retained flow, three identities could matter:
target account
receives the exported profile
operational source
supplies identity and ordinary account fields
billing source
supplies invoice details from the effective payerFor an effective-payer refresh, the target and operational source remained the original account. Only the billing source changed. The payload also recorded the payer relationship.
The authority flag answered a different question:
Does the billing-mode field report current receiver state,
or instruct the receiver to change it?Routine synchronization read the receiving system’s current mode and echoed it. That prevented a profile refresh from overwriting a decision already owned by the receiver. An explicit change on the primary account carried source authority instead.
If the receiver had no account to read, the source value was a fallback, not an instruction. The receiver still had to preserve existing state if one existed.
These are protocol decisions. Calling them true and false does not make
them small.
The failure was an authority bug
Retained history records why the second flag appeared. After a legacy account table was removed, routine profile exports could overwrite the receiving system’s existing billing mode.
The first repair did two things:
- routine exports echoed the receiver’s current mode; and
- explicit source changes carried an authority marker.
It also propagated the marker to both primary and effective exports. A follow-up narrowed the rule: only a primary export may carry billing-mode authority. Focused tests now prove an explicit primary change remains authoritative and an effective export cannot become authoritative even if its input context asks for it.
That sequence is useful because it shows the cost of an unnamed flag pair. Each boolean was locally understandable. Their interaction encoded an ownership rule which was discovered one correction later.
The artifacts do not retain an affected-account count, duration, monetary impact, or production outcome. They establish the overwrite mechanism, authority correction, and invalid combination—not its scale.
Name the three intents
The callers were not thinking in flag tuples. They were doing one of three things:
refresh the primary profile
publish an explicit primary mode change
refresh billing details from the effective payerAn API can state those intents directly:
$exports->refreshPrimaryProfile($accountId);
$exports->publishPrimaryModeChange($accountId);
$exports->refreshFromEffectivePayer($accountId);Separate methods fit when each operation has distinct authorization, payload, or side-effect rules.
A validated intent type fits when the operations share enough transport and mapping machinery to travel through one pipeline:
enum ProfileExportIntent
{
case RoutinePrimaryRefresh;
case ExplicitPrimaryModeChange;
case EffectivePayerRefresh;
}The pipeline derives source and authority from the intent. Callers cannot ask for a fourth state because there is no enum case for it.
This is stronger than renaming booleans:
execute($id, useEffectivePayer: true, allowModeChange: true);Named arguments explain the invalid sentence. A closed intent vocabulary makes the sentence impossible to write.
The retained implementation still uses booleans and clamps the invalid pair. The named API is a proposed refactor, not a historical outcome.
Use policies when the axes really are independent
Not every pair of flags should become separate methods.
Suppose one decision chooses a profile source and another independently chooses an output format. Every combination may be valid:
primary + JSON
primary + CSV
effective payer + JSON
effective payer + CSVTwo enums or policy objects may express that product better than four methods.
The test is composability:
- Does each axis have meaning on its own?
- Is every combination valid?
- Can one policy change without changing the other’s authority?
- Do callers choose both decisions independently?
If yes, represent two policies. If no, model the valid combined intents.
In the retained case, source selection and mode authority were not independent. Effective source prohibited source authority. A combined intent therefore matched the domain more closely than two public switches.
Trace callers by reason, not argument
Before changing the signature, inventory calls in a table:
| Caller event | Current flags | Intended operation | Required authority |
|---|---|---|---|
| scheduled profile refresh | false, false | routine primary refresh | receiver |
| primary mode changed | false, true | explicit mode change | source |
| payer relationship refresh | true, false | effective-payer refresh | receiver |
| effective event with authority marker | true, true | invalid request | none |
This is more useful than searching for true and false. It tells you why the
call exists and which replacement it should use.
Compatibility can be staged:
old entry point
-> validate flag pair
-> translate to named intent
-> invoke new pipelineAdd telemetry or a hard failure for the invalid pair before removing the wrapper. Silent normalization is safe only when discarding the flag cannot hide a caller defect. An authority request which is ignored deserves visibility.
Keep booleans where two states are one operation
A boolean is not automatically bad design.
includeArchived on a local read may describe one bounded query option.
ascending may be clear inside a private sorting helper. A checkbox naturally
arrives as a boolean at the delivery boundary.
The cost rises when the flag changes:
- who owns a field;
- which identity receives an effect;
- whether work is a command or a query;
- which errors are possible;
- whether persistence occurs;
- whether an operation may be retried; or
- what result the caller should expect.
At that point, the two branches deserve the same design attention as two public methods.
Boolean count is only a prompt. One authority flag can be more consequential than five presentation options.
Test the semantic matrix
The public reproduction
executes all four synthetic flag pairs.
It proves that effective=true, authoritative=true collapses to effective,
non-authoritative behavior. It then derives the same policies from three named
intents and verifies that none can combine effective source with mode
authority.
The fixture models the contract. It does not run the private exporter or prove that the proposed enum would reduce defects.
For production code, test more than the returned marker:
- the original target identity never changes;
- operational fields remain from the original account;
- billing fields switch only for the effective-payer intent;
- routine exports echo receiver-owned mode;
- explicit primary change carries source authority;
- missing receiver data remains a non-authoritative fallback; and
- an invalid legacy tuple is reported or rejected.
A green test for execute($id, true, false) is not enough if nobody can say
what those arguments promise.
Flags are compact syntax. They are poor names for different authority contracts. When a method has to erase one combination, stop documenting the booleans and name the operations the system actually permits.