Separate meaning from mechanics
A product module should own its domain records and operations. Shared infrastructure can own generic policy, runtime mechanics, identity, persistence ports and receipts, but it should not silently decide what a domain record means.
This keeps a platform from becoming a collection of screens with unclear authority. It also gives each new module a real admission question: what is the smallest independently meaningful responsibility it owns?
Concrete design rule: If two modules would implement the same domain logic differently, that is a feature, not a bug — it means the logic genuinely belongs in each module's domain. Shared infrastructure should enforce policy, preserve receipts and provide persistence, not unify meaning.
Use typed boundaries
Typed inputs and outputs make a seam visible. In Rust, a lifecycle state can be represented so that a release-shaped operation is unavailable on a draft-shaped value. In a browser model, the same constraint can be shown as an explicit guard and a failing fixture.
The browser example is useful for explanation; compiler enforcement is useful for a narrower claim. They should not be conflated.
Demonstration: Fieldcraft article FC-029 shows a Rust typestate where a Draft article cannot be published — the publish() method is only available on Reviewed articles. The same article shows a browser model with the same constraint, using a guard and a failing fixture. The browser model explains; the Rust compiler enforces.
Hold indeterminate work
A model call or external operation can end in an indeterminate state: the result may have occurred, but the system cannot prove it. Automatically sending the work again risks duplicate effects and broken receipts.
A safer pattern records the uncertainty, preserves the key, polls or reconciles known state, and requires a deliberate next route. The point is not to avoid failure; it is to make recovery observable.
Worked example: A system dispatches a model call with an idempotency key. The call times out. Did the model process the input but return value was lost? Did it never receive the input? An automatic retry risks executing the effect twice. The correct response is to hold, preserve the key, and let a human or policy decide the next route. FC-027 demonstrates this lifecycle.
Build local proofs first
The proposed architecture favors provider-free fixtures, deterministic checks and reversible implementation slices before a broader runtime is introduced. A simple proof can establish that a contract accepts valid input, rejects an invalid state and produces a readable receipt.
That proof does not establish model quality, production readiness or adoption. It earns only the next bounded question.
Technique sequence: 1) Write the contract (input type, output type, valid transitions). 2) Write a pure validate function. 3) Write fixtures for valid, invalid and boundary cases. 4) Run them against the contract, not against a running service. 5) Extend to real I/O only after the contract is stable and the test passes. This sequence is demonstrated across Fieldcraft articles FC-001, FC-022, FC-023 and FC-032.
Idempotency is a contract, not a feature
An idempotency key binds a request to a digest. If the same key arrives with a different body, it is a conflict, not a replay. If the same key arrives with the same body, it is a safe replay. If the key is new, it is a new request.
FC-026 demonstrates this distinction with a deterministic check that correctly classifies new, replay, conflict and duplicate-history scenarios.
Fail closed, not open
When a guard's state is unknown, preserve the current state rather than allowing or denying the transition by default. FC-022 demonstrates a tri-state guard that returns a hold with visible evidence when the transition condition cannot be evaluated. The principle applies broadly: an unknown state is not a denial and not a grant; it is a separate condition requiring separate handling.
Lifecycle as a state machine
A product module's lifecycle controls which operations are available at each stage. A draft record cannot be published; a published record cannot be re-edited without a revision cycle. These constraints make the system's behavior predictable and reviewable.
State model (synthesized): Draft → Review → Published → Archived. Each transition requires specific evidence and authority. Draft→Review requires a completeness check. Review→Published requires a named reviewer disposition. Each transition also has a reverse path: Review→Draft returns the record with reviewer notes. Published→Revision returns it for a new review cycle.
Design rule: A state machine does not need every state to be reachable from every other. The transitions that exist should be the ones you have explicitly designed and tested. A 'back' button that can return any state to any other state is not a lifecycle — it is an Undo facility masquerading as a state machine.
Concrete example from Fieldcraft: FC-029 demonstrates a Rust typestate where Draft, Reviewed and Published are distinct types. The publish() function is only available on Reviewed — the compiler enforces the lifecycle. The same article provides a browser-based demonstration with explicit guards for readers without Rust experience.
Product modules and contract design
A product module owns its domain and communicates through typed contracts. The contract defines what the module accepts, what it returns and what side effects are permitted. This is the Exocore architectural pattern: modules are independent, contract-bound and locally falsifiable before integration.
Contract anatomy (synthesized): For each module interface, define: inputs (type, validation rules), outputs (type, success/failure structure), allowed side effects, idempotency semantics (new, replay, conflict), and rollback behavior (what happens if a downstream module fails).
Design rule: When two modules disagree about a contract (one expects a field the other does not send), that is an integration failure, not a bug in either module. The contract is the authority; each module's implementation must be reconciled to it. FC-025 demonstrates checking producer-consumer compatibility.
Boundary: Contracts define interfaces, not behavior. A module that satisfies a contract may still fail semantically (returning wrong data in the correct shape). Contracts are a necessary condition for reliable composition but not a sufficient one.
Product modularity in practice
A product module is the smallest independently useful responsibility. It should implement its core logic without depending on other modules' runtime state. Shared infrastructure (identity, persistence, policy) is provided through ports, not by making modules aware of each other.
Modularity test: Can this module be implemented as a standalone crate (or component) with its own test suite, its own lifecycle and its own interface contract? If the answer requires importing another module's types or dependending on its runtime state, the module boundary is wrong.
Failure mode to avoid: A 'shared' module that grows to hold domain logic for multiple features. Over time, no single feature can change without touching shared code. The modularity test is also a safety test: modules that pass it can be independently deployed, tested and retired.
Technique sequence: 1) Define the boundary — what data and operations belong exclusively to this module. 2) Write the interface contract before implementation. 3) Implement and test against the contract, not against another module's internal state. 4) Integration: verify that the module's contract matches its consumer's expectations.
Rust as a design tool
Rust is not only an implementation language. Its type system — ownership, lifetimes, enums, pattern matching — is a design tool for making invariants compiler-checked. A Rust crate can express: 'this state cannot exist,' 'this operation is only available in this state,' 'this resource is moved, not shared.'
Worked example: The Fieldcraft article on Rust typestate (FC-029) encodes Draft, Reviewed, and Published as distinct types. The publish() function is only defined on Reviewed. The compiler rejects code that would publish a Draft. This is not a runtime guard — it is a compile-time guarantee.
Boundary: Rust's type system enforces the shape of the code, not the correctness of its semantics. A function that compiles may still return wrong data or have a logic error. The type system is a powerful design tool but not a substitute for testing, review, and correct specification.
Contract reconciliation between modules
When two product modules must communicate, their interface contracts must be reconciled before integration. The reconciliation process: (1) Load both contracts from their canonical sources. (2) Compare input/output types, side effects, and idempotency semantics. (3) Report mismatches: field present in one but not the other, type differences, allowed side effects that differ. (4) Hold integration until contracts are aligned.
Concrete check: FC-025 demonstrates a producer-consumer compatibility check. Given a producer contract (what fields it sends) and a consumer contract (what fields it expects), the check reports matched, missing, and extra fields. This is a deterministic, local, provider-free operation.
Failure mode: Integrating two modules without contract reconciliation, then debugging a mysterious mismatch at runtime. The runtime error is difficult to distinguish from a transient network failure or a data corruption issue. Contract reconciliation before integration avoids this class of failure entirely.
Recovery and resilience patterns
A well-designed system anticipates failure modes and designs recovery paths for each. The recovery pattern: (1) Detect the failure — a check returns unexpected result, a timeout occurs, a resource is unavailable. (2) Classify the failure — transient (retryable), permanent (needs new input), indeterminate (state unknown). (3) Execute the recovery path — retry with backoff for transient, hold for indeterminate, return diagnostic for permanent. (4) Record the recovery event for review.
Resilience principle: A system that never fails in testing but fails catastrophically in production has not been tested against realistic failure modes. Design for failure by introducing controlled failure modes in testing: network partitions, resource exhaustion, corrupted inputs, timeout scenarios.
Worked example: FC-027 demonstrates holding an indeterminate model step — the system times out, preserves the idempotency key, and surfaces the hold for human review rather than silently retrying. FC-028 adds deterministic retry backoff with a cap.
Integration testing without a runtime
Integration tests should run without a full runtime. A product module can be tested against its contract using fixture-based tests: provide fixture input, call the module's public interface, inspect the output against expected shapes and values. This catches contract violations, type mismatches, and unexpected side effects without deploying a database, queue, or model provider.
Practice: Maintain a fixture directory per module with: valid inputs (happy path), invalid inputs (should be rejected), boundary inputs (edge cases), and malformed inputs (corruption scenarios). Run all fixture tests before any runtime-integration test.
Boundary: Fixture tests prove the module behaves correctly against a contract under controlled conditions. They do not prove the module behaves correctly under production load, with real data, or when dependencies are slow or unavailable.
KEEP THE CLAIM BOUNDED
Exocore references here are architectural and pedagogical. They do not assert a deployed runtime, provider availability, user data processing, product launch or service-level behavior. Browser examples illustrate concepts; they are not substitutes for compiler enforcement or production testing.