Deciding When Make Should Stop Owning the Problem¶
Page Maps¶
graph LR
family["Reproducible Research"]
program["Deep Dive Make"]
section["Migration Governance Tool Boundaries"]
page["Deciding When Make Should Stop Owning the Problem"]
capstone["Capstone evidence"]
family --> program --> section --> page
page -.applies in.-> capstone
flowchart LR
duty["name the responsibility"] --> truth["ask where its truth lives"]
truth --> fit["test whether Make models that truth honestly"]
fit --> handoff["define the handoff contract if another owner fits better"]
handoff --> proof["keep proof alive on both sides of the boundary"]
This page is about ownership, not fashion.
Teams usually get boundary decisions wrong in one of two ways:
- they keep work inside Make because it is already there
- they move work out of Make because another tool feels more modern
Both mistakes come from the same failure: nobody names what kind of responsibility is actually being owned.
Ownership has more than one dimension¶
“Make owns deployment” can hide five different claims:
| Ownership dimension | Question |
|---|---|
| decision | who decides that the action is allowed now? |
| execution | who starts and performs the work? |
| state | who records the authoritative current state? |
| recovery | who decides whether to retry, compensate, or stop? |
| evidence | who produces the durable acceptance or failure record? |
Make may own execution of a local recipe without owning approval, remote state, recovery, or acceptance evidence. Name each dimension before deciding whether ownership should move.
The sentence to keep¶
When you are unsure about a boundary, ask:
where does the truth of this responsibility live, and is Make still the most honest owner of that truth?
That question is stronger than asking whether Make is "capable." Many tools are capable. The real issue is whether the chosen owner makes the responsibility reviewable.
Start with the responsibility, not the tool¶
Do not begin with:
- "can Make call the script?"
- "does the new system have a feature for this?"
- "what do other teams usually use?"
Begin with a one-sentence responsibility statement:
- build a binary from declared source inputs
- generate a manifest that describes package contents
- sign a release using controlled external authority
- coordinate staged deployment across remote environments
If you cannot state the responsibility in one sentence, you are not ready to assign an owner.
What Make owns well¶
Make is a strong owner when the responsibility is dominated by repository-local truth:
- explicit inputs produce explicit outputs
- publication happens through files, manifests, or package artifacts
- incremental rebuild logic is meaningful
- review requires seeing why a route ran and what it produced
- failures can be diagnosed from repository-local evidence
Typical strong fits:
- compilation
- generated-file pipelines
- package assembly
- checksums and manifests tied to produced artifacts
- local audit and comparison routes
This is not nostalgia. It is a match between model and problem.
A practical test is whether the responsibility can be represented as a convergent file transaction:
flowchart LR
inputs["declared local inputs"] --> produce["deterministic producer"]
produce --> candidate["unpublished candidate"]
candidate --> verify["local validation"]
verify --> artifact["atomically published artifact"]
artifact --> quiet["unchanged request converges"]
When that model fits, Make can expose dependency, publication, and failure truth directly.
What Make often only imitates¶
Make is a weak owner when the responsibility is dominated by truth it cannot observe or govern honestly:
- long-lived remote state
- approvals granted by external systems or humans
- staged deployment controllers with rollback state
- service health, live traffic, and environment drift
- policy engines that encode authority, timing, or access control
Make can still trigger those systems. Triggering is not the same as owning.
Remote responsibilities usually look different:
stateDiagram-v2
[*] --> Submitted
Submitted --> Accepted: receiver validates request
Submitted --> Unknown: connection fails before response
Accepted --> Processing
Processing --> Published
Processing --> Rejected
Unknown --> Accepted: query by idempotency key
Unknown --> Submitted: retry only when receiver contract permits
The Unknown state is the boundary Make cannot resolve from a recipe exit code alone.
The receiver may have accepted the request before the connection failed. Blindly rerunning
a phony target can duplicate remote work.
Use one ownership test¶
Answer these questions in order:
- what is the published truth of this responsibility?
- where does that truth live while the responsibility is active?
- can Make model the important inputs, outputs, and state transitions directly?
- can failures be explained without hidden remote state?
- would another tool clarify the ownership boundary rather than just hide it?
If questions 2 through 4 keep pointing to remote mutable state or external authority, Make should probably stop owning the problem.
Distinguish a launch command from a state machine¶
Consider:
.PHONY: publish
publish: dist/report-bundle.tar.gz dist/report-bundle.tar.gz.sha256
./scripts/submit-release.sh $^
This can be a useful entrypoint, but its contract must stay narrow:
- Make ensures the local artifact and checksum exist
- the client submits an immutable artifact identity
- the receiver owns acceptance, retry, approval, and publication state
- success means the receiver returned a durable acceptance record, not that Make now owns remote truth
Do not model the remote system with a local published.stamp touched after an HTTP call.
The stamp can become true while the service later rejects the release, or remain absent
after the service accepted a request whose response was lost.
A quick decision table¶
| Responsibility shape | Better owner | Why |
|---|---|---|
| build graph over local files | Make | edges, outputs, and incremental rebuilds are first-class |
| package artifact creation | Make | publication can stay file-oriented and reviewable |
| artifact signing with external authority | hybrid | Make owns artifact production; signer owns authority |
| staged remote deployment | deployment system | truth lives in remote state and operational control loops |
| approval workflow with roles and windows | policy or release system | Make cannot honestly own the governing state |
| distributed workflow scheduling | workflow engine | scheduler state is the real truth, not local prerequisites |
The table is useful because it forces you to name the dominant truth boundary.
Do not confuse orchestration with ownership¶
This target does not tell you who owns deployment truth:
It only tells you that Make can start something.
A reviewable ownership statement is more explicit:
- Make owns artifact build, package assembly, and local verification
- the deployment system owns rollout state, health checks, approval gates, and rollback
- the handoff boundary is a named artifact plus release metadata
That distinction removes a large amount of inherited ambiguity.
Define the handoff contract when ownership changes¶
A boundary change is incomplete until the handoff contract is written down. The contract should answer:
- what produced thing is being handed over
- what evidence must travel with it
- who validates the handoff
- what failure means on each side of the boundary
Example:
| Question | Example answer |
|---|---|
| handoff object | dist/report-bundle.tar.gz |
| attached evidence | checksum manifest, build metadata, package contents manifest |
| sending owner | Make-driven package build |
| receiving owner | release signer and publication service |
| sender failure | artifact or evidence was not produced truthfully |
| receiver failure | signing, approval, or publication failed after handoff |
Without this contract, teams end up with vague "release failed" incidents that mix build truth and remote-state truth together.
Require an immutable handoff identity¶
A handoff object should be:
- complete before submission
- immutable for the lifetime of the request
- addressable by a digest or release identity
- accompanied by the metadata the receiver needs to validate it
- independent of mutable workspace paths after acceptance
An example request envelope:
{
"release_id": "report-2026-07",
"artifact": "report-bundle.tar.gz",
"sha256": "4e8b...d19a",
"manifest_sha256": "82d1...77c0"
}
The full digest values come from the actual artifacts. The release identity and artifact digest together can form an idempotency key if the receiver contract supports it.
Assign retry ownership before the first failure¶
| Failure point | What is known | Responsible owner | Safe action |
|---|---|---|---|
| local package creation fails | no handoff object exists | Make-owned build | repair input or producer, then rebuild |
| local verification fails | candidate is not acceptable | Make-owned build | reject before submission |
| receiver rejects before acceptance | request was evaluated and refused | receiver/client contract | report reason; do not rebuild artifact unless reason concerns artifact |
| connection fails with unknown acceptance | remote state is unknown | receiver state model | query by idempotency key before retry |
| processing fails after acceptance | receiver owns accepted request | receiver | retry or compensate using receiver policy |
| publication succeeds but local response is lost | remote success may already exist | receiver | retrieve durable receipt; do not submit a different artifact |
“Retry three times” is not a boundary design. The owner must know whether the operation is idempotent and whether acceptance occurred.
Make the receipt reviewable¶
The receiver's durable record should identify:
- request or idempotency key
- accepted artifact digest
- acceptance time and responsible system
- final state or a query route for current state
- failure or rejection reason when applicable
A local copy may be stored under artifacts/ for review, but it is evidence about remote
truth, not the authority that creates that truth. If the record is mutable, document how
reviewers refresh and authenticate it.
Keep proof alive on both sides of the boundary¶
Boundary work often gets harder because the team moves ownership and deletes the evidence that used to make the old route understandable.
Protect one proof route for each side:
- before handoff: a route that proves Make produced the expected artifact or manifest
- after handoff: a route that proves the external owner accepted, signed, published, or deployed the intended object
If either side lacks proof, the ownership split is not yet healthy.
Test the boundary with at least four receiver fixtures:
| Fixture response | Expected sender behavior |
|---|---|
| accepts and returns receipt | preserve receipt tied to submitted digest |
| rejects checksum | fail without changing local artifact |
| returns acceptance after duplicate idempotency key | treat as the same request, not a second publication |
| drops connection after accepting | query by key and recover receipt before any resubmission |
A success-only mock teaches invocation, not ownership. The unknown-outcome fixture is the one that reveals whether retry truth lives in the right system.
Good reasons to keep ownership in Make¶
Keep the responsibility in Make when all of these remain true:
- the truth is still primarily file-oriented
- the outputs can be named and reviewed
- another tool would add indirection but not better modeling
- incremental rebuild behavior is still meaningful
- repository-local debugging remains a major advantage
Example:
- code generation from declared schemas
- compilation
- packaging a reproducible archive with checksums
That is classic Make territory and should not be handed away casually.
Good reasons to hand ownership off¶
Move ownership away from Make when these begin to dominate:
- the responsibility depends on remote mutable state
- approvals or authorities live elsewhere
- the workflow is really about controllers, not prerequisites
- failure diagnosis depends on information Make cannot represent clearly
- the convenience wrapper is obscuring, not clarifying, the system
Example:
- progressive production deployment with approvals, health checks, pauses, and rollback
Make may still provide a launch or audit entrypoint. It should not pretend to be the source of operational truth.
Hybrid ownership is often the strongest answer¶
Many healthy systems are neither "all Make" nor "no Make."
Example hybrid:
- Make builds and packages the artifact
- a signing service attaches trusted release authority
- a release controller publishes or deploys the signed object
This works when the boundary is explicit:
- Make owns artifact truth
- the signer owns authority
- the release controller owns remote rollout state
Hybrid answers usually fail only when the handoff object or proof route is vague.
Compare three ownership designs¶
| Design | Local artifact truth | Remote state truth | Main defect or strength |
|---|---|---|---|
| Make recipe builds and uploads mutable workspace files | mixed | inferred from exit code | rebuild and retry can change the submitted object |
| Make builds immutable bundle; shell client owns ad hoc retry | clear | still partly hidden | handoff improves, but unknown outcomes remain weak |
| Make builds and verifies bundle; receiver owns idempotent submission and receipts | clear | authoritative receiver | responsibility and recovery align with truth location |
The middle design can be a useful bounded improvement. Do not describe it as the completed boundary if retry and acceptance remain opaque.
Failure signatures that reveal the wrong owner¶
These sentences are strong warning signs:
"Our Makefile mostly waits for remote systems now"¶
Make is probably acting as a weak wrapper around the real owner.
"We moved packaging out, but now nobody can inspect what actually shipped"¶
The handoff improved fashion but damaged evidence.
"Deploy failures and build failures look identical in review"¶
The boundary mixes two different truths under one target name.
"The new platform is powerful, but local review got much harder"¶
The team may have handed off a responsibility that was still naturally local.
A practical review route¶
When you audit a suspicious boundary, write a short packet:
- responsibility statement
- current owner
- where truth lives
- proof route before handoff
- proof route after handoff
- recommended owner and why
- retry and unknown-outcome owner
- durable acceptance evidence
If you cannot fill all six lines, the boundary is still under-described.
Review drill¶
Take one target from an inherited repository and answer:
- what responsibility does this target actually own?
- where does the truth of that responsibility live?
- is Make modeling that truth directly or hiding another owner?
- what handoff object and evidence would exist if ownership moved?
- who resolves a lost response after possible acceptance?
Exit check¶
You are ready to use this lesson when you can:
- explain why ownership is not the same as invocation
- identify when Make is still the most honest owner of a responsibility
- identify when another tool should own remote state, approval, or policy truth
- write a concrete handoff contract instead of saying "the other system handles it"
- assign retry, unknown-outcome recovery, and receipt ownership explicitly