Conflict Resolution¶
1. Purpose¶
Software engineering principles frequently protect different quality attributes. A conflict is not evidence that one principle is wrong; it means the project context must determine which concern has priority.
This document defines the central procedure for resolving conflicts without treating principle names as absolute commands.
2. Normative rule¶
When two valid rules point toward different decisions, the model MUST:
- identify the concrete decision under dispute;
- identify the quality attribute protected by each rule;
- apply decision precedence;
- evaluate evidence and constraints;
- compare failure consequences;
- consider scope and reversibility;
- distinguish public contracts from internal implementation;
- select the least complex decision that satisfies the higher-priority constraints;
- record significant trade-offs;
- avoid claiming that the losing rule is universally invalid.
3. Conflict-resolution procedure¶
Step 1: State the decision¶
Bad framing:
Should we follow DRY or YAGNI?
Better framing:
Should these two similar validation functions be unified behind a shared abstraction now?
Principle names are too broad to resolve without a concrete decision.
Step 2: Identify protected qualities¶
Examples:
- DRY protects consistency of knowledge and reduces divergent updates;
- YAGNI protects simplicity and prevents speculative generalization;
- immutability protects reasoning and concurrency safety;
- controlled mutation may protect performance or framework compatibility;
- backward compatibility protects consumers;
- refactoring freedom protects maintainability.
Step 3: Apply precedence¶
Use the precedence defined in SPECIFICATION.md.
A verified security requirement outranks a stylistic simplicity preference. An explicit public compatibility promise outranks a default desire to rename an API.
Step 4: Evaluate evidence¶
Relevant evidence includes:
- existing consumers;
- documented roadmap;
- repeated change history;
- measured performance;
- repository conventions;
- known data scale;
- production incidents;
- framework lifecycle requirements;
- explicit user constraints.
A hypothetical future possibility is weak evidence.
Step 5: Evaluate consequences¶
Consider:
- failure severity;
- blast radius;
- recoverability;
- security impact;
- data loss;
- compatibility breakage;
- operational cost;
- cognitive cost;
- future migration cost.
Step 6: Evaluate reversibility¶
Prefer reversible choices when evidence is incomplete.
Examples:
- keep two small functions separate until their shared abstraction is understood;
- introduce an internal adapter before making it public;
- isolate an optimized implementation behind tests;
- avoid a repository-wide migration during a local fix.
Step 7: Select and record¶
The resolution SHOULD state:
- selected rule or compromise;
- reason;
- scope;
- trigger for reconsideration.
Example:
Keep the implementations separate for now because they encode different domain concepts. Reconsider extraction after a third use or when the business rule becomes shared.
4. Central conflict matrix¶
| Principle or policy A | Principle or policy B | Default resolution |
|---|---|---|
| DRY | YAGNI | Remove duplicated knowledge, not every textual similarity. Delay abstraction until the shared concept and variation axis are understood. |
| Open/Closed Principle | KISS | Add extension points for known or strongly evidenced variation. Do not create plugin systems for hypothetical futures. |
| Single Responsibility | Locality of behavior | Keep behavior and data together when they change for the same reason. Split only when responsibilities have distinct change drivers. |
| Small functions | Local readability | Extract coherent operations or abstraction levels, not arbitrary line ranges. A longer linear function may be clearer than fragmented indirection. |
| Composition over inheritance | Framework or language contract | Prefer composition by default. Use inheritance for true substitutability, mandatory framework extension, or carefully controlled polymorphism. |
| Interface Segregation | Simplicity | Introduce an interface only when there is a real consumer contract, variation, test boundary, or architecture boundary. Do not mirror every class with an interface. |
| Dependency Inversion | Framework-native design | Isolate unstable or independently evolving boundaries. Do not wrap every framework service merely to claim framework independence. |
| Hexagonal architecture | CRUD simplicity | Use domain isolation when business rules or infrastructure independence justify it. Prefer framework-native modules for simple data-driven features. |
| Tell, Don’t Ask | DTO and pipeline transparency | Apply behavior-oriented encapsulation to domain models. Permit transparent data structures for transfer, configuration, and transformation pipelines. |
| Encapsulation | Serialization and interoperability | Protect invariants internally, but provide explicit serialization boundaries rather than exposing unrestricted mutation. |
| Immutability | ORM or UI state conventions | Prefer immutable values; permit controlled mutation for lifecycle-managed entities and state where it improves clarity or compatibility. |
| Immutability | Performance | Require evidence before replacing immutable design. Isolate mutable hot paths and protect them with tests and ownership rules. |
| Fail Fast | Tolerant external input | Normalize harmless syntactic variation at the boundary, but reject ambiguous, unsafe, or semantically invalid data. Keep internal contracts strict. |
| Postel’s Law | Parse, Don’t Validate | Be tolerant only where meaning remains unambiguous. Parse accepted forms into a strict internal representation. |
| Defensive programming | Error visibility | Defend at trust boundaries and resource boundaries. Do not swallow errors or silently substitute values that hide broken invariants. |
| Command–Query Separation | Performance or convenience | Keep conceptual commands and queries distinct. A command may return identifiers, status, or resulting metadata when this improves safe use without hiding side effects. |
| CQRS | Simplicity | Use separate read and write models only when scale, domain, authorization, or workflow complexity justifies the operational cost. |
| Single Source of Truth | Local caching | Maintain one authoritative source and make derived caches explicit, disposable, and governed by invalidation rules. |
| Idempotency | Throughput and storage cost | Require idempotency where retries or duplicate delivery are realistic. Scope deduplication windows and storage to actual risk. |
| Backward compatibility | API cleanup | Preserve declared public contracts. Use deprecation and migration paths; change internal contracts atomically. |
| Semantic versioning | Hyrum’s Law | SemVer governs intentional API promises, but maintainers should also inspect observable behavior that consumers may rely on. |
| Boy Scout Rule | Minimal task scope | Permit small local improvements that reduce risk or improve clarity. Move unrelated or broad refactoring into separate work. |
| Chesterton’s Fence | Simplification | Understand the reason and consumers of an existing mechanism before removal. If the reason no longer applies, remove it with evidence and tests. |
| Test pyramid | Risk-based testing | Treat the pyramid as a cost heuristic, not a required shape. Use the cheapest test level that provides adequate confidence. |
| Unit isolation | Framework integration confidence | Unit-test pure logic and use real framework integration where mocks would reproduce implementation details. |
| One assertion per test | Complete behavior verification | Test one coherent behavior; allow multiple related assertions that describe the same result. |
| TDD | Exploratory design | TDD is optional. Use it where behavior can be expressed incrementally; allow spikes, then convert learning into tests and maintainable implementation. |
| Premature optimization warning | Known hard constraints | Measurement is preferred, but explicit latency, memory, throughput, or real-time budgets are sufficient evidence to design accordingly. |
| Clarity | Hot-path optimization | Optimize only the constrained path. Isolate complexity, document the budget, and maintain correctness tests or a reference implementation. |
| Framework conventions | Portability | Follow framework conventions for application code. Isolate framework dependence when the component has a real portability or independent-lifecycle requirement. |
| Package by feature | Layered architecture | Organize top-level application code by capability where useful; use technical layers inside features when they improve clarity. Libraries may organize by public concept instead. |
| Rich domain model | Data-oriented pipeline | Use behavior-rich models for complex invariants. Use transparent records and staged transformations for high-volume data processing. |
| Strict typing | Readability | Prefer types that expose meaningful states and contracts. Avoid type-level complexity that is harder to understand than the runtime behavior it protects. |
| Static typing | Runtime validation | Static types protect trusted code paths; external data still requires runtime parsing and validation. |
| General reusable component | Accessibility and semantics | Prefer explicit semantic components over excessively generic abstractions that make correct accessible behavior difficult. |
| Global state reuse | Local state ownership | Keep state as close as possible to its consumers. Promote it only when sharing, persistence, coordination, or synchronization requires it. |
| Eventual consistency | Immediate user expectations | Explicitly communicate pending state, define reconciliation behavior, and use synchronous consistency where the user action requires immediate guarantees. |
| Retry | Duplicate side effects | Retry only idempotent operations or operations protected by deduplication, transactional outbox, or equivalent safeguards. |
| Logging detail | Privacy and security | Log enough for diagnosis without exposing secrets, credentials, personal data, or sensitive payloads. |
| Generalization | Existing code consistency | Prefer the existing stable convention for local changes unless the user authorizes redesign or the convention causes a material defect. |
5. Profile-specific tie breakers¶
When the matrix does not fully resolve a conflict, use the base profile's priority order.
Prototype¶
Typical order:
learning and correctness
> speed of iteration
> simplicity
> local readability
> extensibility
> strict compatibility
Reusable library¶
Typical order:
correctness
> public contract stability
> predictable behavior
> usability
> maintainability
> extensibility
> implementation convenience
Full-stack web application¶
Typical order:
correctness and security
> user-visible behavior
> maintainability
> operational reliability
> performance within budgets
> architectural purity
Legacy modernization¶
Typical order:
behavior preservation
> correctness and risk reduction
> reversibility
> test coverage of changed behavior
> local maintainability
> broad architectural cleanup
Real-time or embedded system¶
Typical order:
correctness and safety
> determinism
> resource bounds
> reliability
> measurable performance
> readability
> flexibility
6. Escalation conditions¶
The model SHOULD ask for a decision or explicitly surface alternatives when:
- two options have materially different public compatibility consequences;
- architecture authority is unknown and a redesign would be difficult to reverse;
- a security or data-integrity trade-off cannot be safely inferred;
- the user’s stated requirements are internally contradictory;
- choosing a runtime, framework, persistence model, or protocol would commit substantial project cost;
- repository evidence strongly conflicts with the user's assumption.
For ordinary local work, the model SHOULD make the safest conservative choice instead of blocking progress.
7. Required conflict record¶
A resolved policy may record a conflict as:
conflicts:
- id: dry-vs-yagni-validator-abstraction
decision: keep-separate
protected_attributes:
selected: simplicity
deferred: consistency
rationale: >-
The functions are textually similar but represent different domain rules.
No stable shared abstraction has been identified.
reconsider_when:
- a third consumer appears
- the rules become governed by one authoritative specification
8. Anti-patterns¶
The following conflict-resolution behaviors are prohibited:
- selecting a principle because it is more famous;
- counting principles on each side;
- treating all
SHOULDrules asMUSTrules; - using “clean code” as an unexplained verdict;
- introducing architecture solely to satisfy a diagram;
- hiding compatibility breakage inside a refactor;
- claiming performance benefits without evidence or constraints;
- using YAGNI to ignore explicit near-term requirements;
- using DRY to combine unrelated domain concepts;
- using framework convention to justify insecure behavior;
- using existing convention to preserve a correctness defect.
Canonical relationship integration¶
The conflict matrix is operational guidance. Canonical entry-level relationships are also recorded in principles/relationships.yaml for navigation and tooling.
When a conflict involves a catalogue entry, the resolver MUST use that entry’s canonical interpretation and rejected meanings. It MUST NOT resolve a conflict by importing a third-party Skill’s interpretation.