Capstone Architecture Guide¶
Page Maps¶
graph LR
family["Reproducible Research"]
program["Deep Dive Make"]
section["Capstone"]
page["Capstone Architecture Guide"]
capstone["Capstone evidence"]
family --> program --> section --> page
page -.applies in.-> capstone
flowchart LR
orient["Orient on the page map"] --> read["Read the main claim and examples"]
read --> inspect["Inspect the related code, proof, or capstone surface"]
inspect --> verify["Run or review the verification path"]
verify --> apply["Apply the idea back to the module and capstone"]
Use this page when the question is about build ownership rather than about one command. The Make capstone stays reviewable only if public targets, graph modeling, helper logic, proof, and failure specimens each keep a readable home.
The architecture has two axes¶
The directory tree is only one axis. The other is the lifecycle of a build decision:
flowchart LR
parse["Parse and expand"]
graph["Construct graph"]
schedule["Choose runnable work"]
execute["Execute recipes"]
publish["Publish outputs"]
verify["Verify claims"]
parse --> graph --> schedule --> execute --> publish --> verify
A good architecture tells you both:
- where responsibility lives in the repository
- when that responsibility acts in Make's lifecycle
For example, mk/objects.mk owns deterministic source membership during parsing.
Object rules in Makefile own compilation and publication during execution.
tests/run.sh challenges the resulting behavior after execution. Calling all three
“build logic” would hide the lifecycle boundaries that make the repository reviewable.
Boundary map¶
| Boundary | First files to inspect | Lifecycle | What that boundary owns | What it must not silently own |
|---|---|---|---|---|
| public build contract | capstone/Makefile |
parse and graph | supported targets, composition, primary rules | undocumented remote deployment |
| capability contract | capstone/mk/contract.mk |
parse | required advertised Make features | guesses from version prefixes |
| shared compile policy | capstone/mk/common.mk |
parse | include paths and shared flags | graph membership |
| recipe helpers | capstone/mk/macros.mk |
recipe expansion and execution | small, named shell contracts | target discovery or hidden side effects |
| graph membership | capstone/mk/objects.mk |
parse and graph | rooted, sorted source-to-output mapping | publication recipes |
| modeled hidden inputs | capstone/mk/stamps.mk |
parse, graph, execution | flag identity and invalidation edges | all environment provenance |
| optional rule generation | capstone/mk/rules_eval.mk |
parse and graph | quarantined generated-rule demonstration | ordinary core targets |
| proof harness | capstone/tests/run.sh and Python tests |
execution and verification | convergence, equivalence, rejection behavior | implementation policy |
| release policy and actions | capstone/release/ and release scripts |
graph, execution, verification | package membership, candidate identity, contained install | remote publication |
| failure specimens | capstone/repro/ |
controlled execution | one broken mechanism per specimen | healthy build behavior |
The final column is as important as ownership. Boundaries erode when a convenient file starts making decisions that reviewers would reasonably look for elsewhere.
Read include order as dependency direction¶
The top-level Makefile includes the ordinary layers in this order:
Read the order as a dependency rule:
- the capability contract can stand alone
- shared compile policy may rely on top-level layout variables
- helpers may rely on the chosen shell contract
- graph membership relies on layout names, not on recipes
- state stamps rely on the final tool and flag values
rules_eval.mk is different. It is included only when USE_EVAL=yes, so its generated
rules cannot become an invisible prerequisite for the ordinary build.
An include-direction check¶
For any variable referenced by an included file:
- find where it is first assigned
- identify whether callers may override it
- verify that its final value exists before a state identity is computed
- check whether the include mutates a public variable globally
This is why stamps.mk comes after the final link-flag scrubbing in Makefile: the
stamp must represent the flags recipes will actually consume.
Trace one output across the architecture¶
Use app as a worked example.
Membership¶
mk/objects.mk discovers rooted C sources, sorts them, and maps them to object paths.
The stable ordering supports review and reproducible command construction. It does not
compile anything.
Invalidation¶
Each object depends on its source, compiler-observed depfile edges, and the canonical
flag stamp. mk/stamps.mk turns selected non-file inputs into a content-bearing file so
a flag change becomes graph-visible.
Production¶
The object and link rules live in Makefile. Their recipes publish through
process-specific candidates and rename only after success. The graph owns when work is
needed; the recipe owns how a complete output becomes visible.
Proof¶
tests/run.sh exercises convergence and schedule equivalence. That is stronger than
running gmake app, because successful production alone cannot show that a second build
is quiet or that -j preserves the same inventory.
flowchart TD
sources["src/**/*.c"]
membership["objects.mk\nsorted source mapping"]
state["stamps.mk\nselected flag identity"]
objects["build/**/*.o + depfiles"]
app["app"]
harness["tests/run.sh"]
report["selftest evidence"]
sources --> membership --> objects --> app
state --> objects
harness -. challenges .-> objects
harness -. challenges .-> app
harness --> report
The dashed arrows mean “tests a claim about,” not “is a Make prerequisite of.”
Architecture decisions worth noticing¶
One top-level composition surface¶
Public target composition remains in Makefile. Included files contribute policy,
membership, helpers, or state modeling without creating a second public entrance.
Generated content and publication are separate¶
Python generators decide bytes. Make rules decide prerequisites, output paths, and publication timing. This makes it possible to test content generation separately from graph correctness.
Evidence leaves the source tree¶
Audit, proof, performance, and release evidence are rooted under repository
artifacts/. The source tree remains an input surface rather than accumulating the
results used to judge it.
Broken examples remain outside the healthy graph¶
Files under repro/ are invoked explicitly. A failure specimen may be intentionally
unsafe, but it must not become a prerequisite or include of the ordinary build.
Review a proposed change by impact¶
Suppose you add a generated version header.
| Question | Architectural answer |
|---|---|
| who defines its bytes? | a named generator under scripts/ |
| who declares its prerequisites and output? | a rule in the graph-owning Make surface |
| which binaries consume it? | explicit edges or compiler depfiles |
| which policy inputs affect it? | a content-bearing manifest or stamp if they are non-file state |
| how is incomplete output hidden? | process-local candidate plus atomic rename |
| how is rebuild scope tested? | a focused incremental assertion |
| does it enter a release? | only through declared package policy |
If the proposed change cannot answer one row, it is not architecturally complete.
Architecture smells and first questions¶
| Smell | First question |
|---|---|
| an include file defines user-facing targets and compile flags | which responsibility should move back to the public contract or policy layer? |
a helper macro discovers files with $(shell find ...) |
who owns the discovery root and ordering? |
| a generator writes directly into an authored source directory | is generated state being confused with source ownership? |
| a proof script repairs outputs before inspecting them | can the check still reject the original failure? |
| a release script decides membership by walking a directory | where is package policy declared independently? |
| a repro file is included by the healthy build | can the failure remain isolated and explicitly invoked? |
Read the repository in this order¶
- Start with
Makefileandgmake helpto see the public contract. - Choose one target; do not review the whole graph at once.
- Follow its membership and invalidation through the relevant
mk/files. - Read its recipe or called script for publication behavior.
- Read the matching proof before making a trust claim.
- Read
repro/only when the question is about a named failure class.
What this guide should prevent¶
- mistaking helper reuse for ownership clarity
- treating repro files as part of the healthy build path
- judging parallel safety without checking the proof harness
- reading include order as arbitrary file organization
- allowing proof scripts to own production policy
- treating a directory boundary as sufficient evidence of a responsibility boundary
Architecture review drill¶
Choose app, one dynamic binary, the selftest report, or the release candidate. Draw
five boxes:
Write one owning file in each box and one prohibited responsibility beneath it. Then compare your drawing with Capstone File Guide and the actual repository.
You are ready to stop when you can explain one output without saying “the Makefile does it” or “the scripts handle it.” Those phrases are too broad to support a review.