Skip to content

Commit 558aa93

Browse files
docs(adr): record vendored-proto decision and Railway template plan
ADR 0001 captures the build-hermeticity decision: vendor rustyred.proto into vendor/proto/ rather than initializing the submodule in Docker, committing generated bindings, or relying on undocumented Railway submodule behavior. Documents the four-option matrix, consequences, reversibility, and operational impact on the local dev workflow. docs/plans/railway-template-publication.md is the orchestrate plan that produced this work-package, with stable RT-XXX checklist IDs across W1 build hermeticity, W2 README rewrite, W3 operator surface, W4 ADR, and W5 Railway dashboard runbook. The companion .report.md reconciles each ID against the executed work, including RT-106 (live Docker build) deferred to Railway's first deploy because the local Docker daemon was unavailable.
1 parent 6a35acf commit 558aa93

4 files changed

Lines changed: 634 additions & 0 deletions

File tree

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
# ADR 0001 — Vendor `rustyred.proto` for hermetic Docker / Railway builds
2+
3+
Status: Accepted
4+
Date: 2026-05-22
5+
Decision drivers: Travis Gilbert
6+
7+
## Context
8+
9+
The `rustyred-server` crate compiles `rustyred.v1` protobuf definitions
10+
into Rust tonic bindings at build time. The `.proto` file is sourced
11+
from a git submodule:
12+
13+
```
14+
proto/ -> submodule `theorem-protos`
15+
└── rustyred/v1/rustyred.proto
16+
```
17+
18+
`crates/rustyred-server/build.rs` reads from that path during `cargo
19+
build`. Locally, the file exists because the developer ran
20+
`git submodule update --init`. On Railway — and any other CI/CD that
21+
performs a default `git clone` without recursive submodule fetch — the
22+
file is missing. The build fails with the message:
23+
24+
> Cannot find theorem-protos at proto/rustyred/v1/rustyred.proto.
25+
> Run `git submodule update --init` before building.
26+
27+
This is a hermeticity gap: the Docker build context the Railway template
28+
sees does not match the build context the developer sees, even though
29+
they nominally run "the same" build.
30+
31+
RustyRed is intended to ship as a Railway template. The template path
32+
must build deterministically from a fresh public clone with no extra
33+
steps.
34+
35+
## Decision
36+
37+
Maintain an in-tree vendored copy of the proto at
38+
`vendor/proto/rustyred/v1/rustyred.proto`, kept byte-identical to the
39+
submodule via a sync script and a CI check. `build.rs` prefers the
40+
vendored copy and falls back to the submodule for developers actively
41+
editing the upstream proto.
42+
43+
Concretely:
44+
45+
1. The vendored file is committed to the repository and copied into the
46+
Docker build context.
47+
2. `scripts/sync-vendored-proto.sh` copies `proto/rustyred/v1/rustyred.proto`
48+
into `vendor/proto/rustyred/v1/rustyred.proto` and records the source
49+
commit hash in `vendor/proto/SOURCE_COMMIT`.
50+
3. `.github/workflows/vendored-proto-up-to-date.yml` runs
51+
`scripts/sync-vendored-proto.sh --check` on every PR that touches
52+
`proto/**`, `vendor/proto/**`, or the script itself, and fails the PR
53+
if the vendored copy drifts from the submodule.
54+
4. `crates/rustyred-server/build.rs` looks up the proto in this order:
55+
- `<workspace>/vendor/proto/rustyred/v1/rustyred.proto` (preferred,
56+
hermetic path)
57+
- `<workspace>/proto/rustyred/v1/rustyred.proto` (submodule fallback,
58+
used by developers editing the upstream contract)
59+
5. The Dockerfile adds `COPY vendor ./vendor` to the build context. It
60+
does not `COPY proto ./proto`.
61+
62+
The first vendored snapshot is taken at `theorem-protos` commit
63+
`b64a414950ff4d08e3be772c8a5de03665c11e39` — the submodule HEAD at the
64+
time of this ADR. There are no upstream tags yet, so this commit acts
65+
as the de facto pin.
66+
67+
## Alternatives considered
68+
69+
### Option A — Vendor the proto (chosen)
70+
71+
- **Upside:** Zero Railway-side magic. Docker build context is hermetic.
72+
Reproducible builds for any consumer who clones the public repo.
73+
Survives any future change in Railway's submodule handling.
74+
- **Risk:** Two sources of truth for the proto. Mitigated by the sync
75+
script and the CI guard. Adds one file to the repo and one workflow
76+
to CI.
77+
- **Validation:** `cargo build -p rustyred-server` succeeds with
78+
`proto/` absent. `docker build .` succeeds without a submodule init
79+
step. The CI check fails on intentional drift, passes after sync.
80+
81+
### Option B — Initialize the submodule inside the Dockerfile
82+
83+
- **Upside:** Single source of truth (the submodule).
84+
- **Rejected because:** Requires either `COPY .git` (which leaks the
85+
entire git history into the image layers and is wasteful), or an
86+
HTTPS clone of `theorem-protos` during the builder stage. The HTTPS
87+
approach adds a network dependency to the build and breaks air-gapped
88+
rebuilds, slowing CI for marginal benefit over Option A.
89+
90+
### Option C — Commit the generated tonic bindings
91+
92+
- **Upside:** Removes the proto dependency from build time entirely.
93+
Fastest Docker builds.
94+
- **Rejected because:** Generated code becomes hand-maintained. Every
95+
proto change requires a manual regen commit. Regeneration drift would
96+
not be caught for months at a time. The tonic codegen API also evolves
97+
faster than the proto itself, making the generated artifact tied to
98+
a specific `tonic_build` version rather than to the contract.
99+
100+
### Option D — Railway-side submodule flag
101+
102+
- **Upside:** Zero repo changes.
103+
- **Rejected because:** Railway does not currently expose a documented
104+
build-time submodule toggle, and tying the template to undocumented
105+
behavior is fragile. Option A is independent of platform.
106+
107+
## Consequences
108+
109+
### Positive
110+
111+
- Railway builds succeed from a fresh clone.
112+
- Any consumer (developer, CI runner, downstream subtree fork) can build
113+
without setting up submodules.
114+
- The downstream sync workflow (`scripts/sync-downstream-subtree.sh`,
115+
which uses `git subtree pull --squash`) carries `vendor/proto/` to
116+
downstream consumers automatically without script changes.
117+
- The submodule remains intact for developers who edit the upstream
118+
proto contract; the fallback path in `build.rs` lets them test
119+
changes before running the sync script.
120+
121+
### Negative
122+
123+
- The vendored proto must be kept in sync with the submodule. The CI
124+
guard enforces this on every PR. If a developer edits
125+
`proto/rustyred/v1/rustyred.proto` without running
126+
`scripts/sync-vendored-proto.sh`, their PR fails.
127+
- `vendor/proto/SOURCE_COMMIT` records the source commit, but the
128+
vendored copy is not cryptographically tied to it. The sync script
129+
rewrites both atomically; the CI check verifies both match the
130+
current submodule HEAD.
131+
132+
### Operational
133+
134+
- The local dev workflow no longer requires `git submodule update --init`
135+
for first build. The README "Build (local development)" section
136+
reflects this.
137+
- Developers actively editing the upstream proto must:
138+
1. `git submodule update --init` (one-time per clone)
139+
2. Edit `proto/rustyred/v1/rustyred.proto`
140+
3. Run `scripts/sync-vendored-proto.sh`
141+
4. Commit both the submodule pointer bump and the vendored copy
142+
143+
## Reversibility
144+
145+
Fully reversible. To revert:
146+
147+
1. Delete `vendor/proto/`.
148+
2. Restore `build.rs` to use the submodule path only.
149+
3. Restore the Dockerfile's `COPY proto ./proto` line (and ensure CI
150+
initializes submodules before building).
151+
4. Remove `scripts/sync-vendored-proto.sh` and the CI workflow.
152+
153+
The submodule itself is untouched by this ADR; reverting only removes
154+
the in-tree mirror.
155+
156+
## Related
157+
158+
- `crates/rustyred-server/build.rs` — proto source resolution logic.
159+
- `scripts/sync-vendored-proto.sh` — sync + check script.
160+
- `.github/workflows/vendored-proto-up-to-date.yml` — CI guard.
161+
- `Dockerfile``COPY vendor ./vendor` line.
162+
- `vendor/proto/SOURCE_COMMIT` — recorded source commit.
163+
- `docs/plans/railway-template-publication.md` — the plan this ADR
164+
records.

docs/adr/README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Architecture Decision Records
2+
3+
Numbered, append-only records of architecturally significant decisions
4+
in this repository. Each ADR explains the context, the decision, the
5+
alternatives rejected, and the consequences.
6+
7+
Numbering starts at `0001`. ADRs are not renumbered; superseded ADRs
8+
are marked as such and link to the replacement.
9+
10+
| # | Title | Status |
11+
|---|---|---|
12+
| [0001](0001-vendored-proto-for-railway-build.md) | Vendor `rustyred.proto` for hermetic Docker / Railway builds | Accepted |

0 commit comments

Comments
 (0)