Files
project-yukart/docs/REPOS.md
T
mikkeli 66b7269f3c svc-vision: new repo for the camera; decision 29 (Python for vision)
The live view moved from app-flutter's test tool to svc-vision, running as
yukart-vision.service on the car Orin.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DDwfd6Ki8UfG4hK6W5YpJz
2026-10-12 16:15:04 +09:00

11 KiB

type
type
reference

Repositories

Splitting rule

  • project-yukart — overview, roadmap, cross-cutting design. No code, ever.
  • orin-nano-env-setup — the host: kernel modules, drivers, OS packages, device permissions, systemd plumbing. Anything that must be true before an application starts.
  • One repo per application or application cluster. A cluster is a set of services that are developed, versioned and deployed together.
  • Helper/deployment repos stand alone when they are reusable outside Yukart.

Anything cross-repo — topic names, payload schemas, the DBC — belongs in the contracts repo, not copied into each consumer.

Current

Repository Role
project-yukart Hub: overview, roadmap, architecture
orin-nano-env-setup Jetson host bring-up
app-flutter Flutter display client
contracts Topic namespace, payload schemas, signal registry
svc-core Acquisition + processing cluster: recorder now; can-bridge, obd-poller, gnss, decoder to come
svc-gw Telemetry gateway — aggregation and the display boundary
svc-nav Navigation service — routing and guidance
svc-vision Camera: capture and the live view; recording and inference to come
tool-mapmint Map artifact minting, workstation-side
tool-mock Mocks and fixtures — a fake car on a workstation

Planned

Repository Role Why separate
app-esp32 ESP32 RLCD/e-ink display firmware (Class B) Embedded toolchain, flashed not deployed
tool-analysis Offline session analysis, reverse-engineering notebooks Runs off-car, no runtime coupling

Create these lazily — when there is something to put in them, not before.

Naming

Repositories are named <group>-<thing>, with no project prefix. The yukart Gitea org already namespaces everything, so a yukart- prefix would be a constant occupying the most significant characters of every name — pure noise in a sorted index, and the reason the earlier scheme scattered.

Group Meaning
svc- A service running on the Orin
app- A display client, running off the Orin
tool- Off-car tooling; never deployed to the car

The goal is that lexical order equals semantic grouping: opening the org listing puts every service together, every client together, every tool together. The group segment does that work because it is the first thing that varies.

Ungrouped singletons — contracts — are fine where a category has exactly one member and always will.

project-yukart and orin-nano-env-setup predate this and keep their names rather than paying a rename for two repos that are stable and easy to find.

Reusability

Nothing in a name marks a repo as reusable. A component that turns out to be genuinely reusable outside Yukart leaves the org rather than being renamed — location says it, not spelling.

Helper and deployment repos

Arduino and ESP32 work implies flashing, build and deployment scaffolding that is likely to be reused. Keep those generic and outside the org — e.g. an ESP32 project skeleton with the toolchain, flashing scripts and CI already wired up.

Gitea template repositories

Gitea supports these: repo Settings → Repository → Template, after which the repo offers "Use this template" to create new repos from it.

Use a template repo for scaffolding — a new ESP32 or Arduino project that should start with the toolchain, directory layout and flashing scripts in place.

Do not use one for code you intend to keep updating in one place. Template instantiation is a fork-at-create: the copy has no link back, so improvements to the template never reach repos already created from it. For genuinely shared code, use a git submodule or publish a package instead.

Rule of thumb: template for the starting shape, submodule/package for living code.

Linking convention

Cross-repo links use the gitea:owner/repo scheme, as established in the hub README — e.g. [Environment setup](gitea:yukart/orin-nano-env-setup).

Per-repo document layout

Following the convention already set by orin-nano-env-setup:

README.md            minimal portal, points everywhere else
docs/
  GUIDELINES.md      implementation practice for this repo
  BOM.md             hardware, where the repo owns hardware
  impl/
    plans/           what is intended
    reports/         what actually happened, and why

The impl/plans ÷ impl/reports split is worth keeping: plans go stale, reports do not. A report like PCAN-SETUP.md stays valuable precisely because it records the investigation and the dead ends, not just the final commands.

Scaffolding conventions

Component repos are scaffolded by hand. A template repo is for carrying a real toolchain, which is the ESP32 case above.

Every component repo carries:

README.md                 the shared blurb, "What this repo is about", then
                          links to guidelines and impl/
docs/GUIDELINES.md        numbered sections, ends "## N. To be added"
docs/impl/plans/          at least INITIAL.md
docs/impl/reports/
docs/BOM.md               only where the repo owns hardware

Languages

Where Language Why it is there
svc-* — on the Orin, except svc-vision Rust No GC, low resident memory on a shared 8 GB pool, and fast start for the tier-1 capture services
svc-vision — on the Orin Python Camera pipelines (GStreamer) and inference live in the Python ecosystem (decision 29). Standard library and host packages only, until a dependency earns its place
tool-* — off-car Python Batch work where development speed is the only constraint that binds
app-flutter Dart Flutter

Python floors at 3.10, the Orin's version, for anything that might run there. The workstation has 3.14; do not rely on it.

Cargo.lock and pubspec.lock are committed — these are applications, not libraries.

Building for the Orin

Services build natively on the development Orin. The sysroot is then the target's own, so a binary cannot reference a symbol the car unit lacks — the guarantee is structural rather than configured.

Two Orin Nanos, and they have different jobs:

Unit Role
Development Orin (mikkeli-orin-nano-2) Build, CI runner, bench testing. Always powered, on the LAN
Car Orin (mikkeli-orin-nano) Deployment target only

The SIM7600 LTE/GNSS hat lives in the car unit only — its custom enclosure doesn't move between units. Anything touching the modem or gpsd is tested on the car unit; on the development unit, gnss runs against its own mock input (decision 19).

⚠ The guarantee is version parity, not architecture. An aarch64 binary built on a newer userland still fails to load on an older one. A JetPack or Ubuntu upgrade applied to one unit and not the other reintroduces the problem, silently and only at runtime. Upgrade both, or neither.

The check that catches drift between them is scripts/check-glibc.sh in each service repo:

scripts/check-glibc.sh target/release/svc-nav        # ceiling defaults to 2.35
Exit Meaning
0 Highest referenced version is at or below the ceiling
1 Ceiling exceeded — the responsible symbols are printed
2 Could not check — no objdump, missing binary, no versions found

It detects, it does not fix; the fix is building on a unit whose release matches the car's. Exit 2 is distinct on purpose: a check that cannot check must not report success.

The runner must build on the host, not in an arbitrary container

⚠ A container-mode runner discards the entire guarantee. Jobs then build in whatever image the workflow names — rust:latest carries a much newer glibc — and the Orin being the host is irrelevant. The binary compiles cleanly and fails to load on the car unit.

So either run act_runner in host mode, or give it an image built from the same Ubuntu release as the car Orin. scripts/check-glibc.sh catches the mistake either way, which is the second reason it is worth having.

The runner needs a writable toolchain: rustup is installed per-user, so the runner either runs as a user that owns one or gets its own. A root-owned shared install cannot have targets added to it without RUSTUP_ALLOW_SHARED_WRITE.

Cross-compiling from the workstation

Available as a fallback. Each service repo's .cargo/config.toml points at aarch64-linux-gnu-gcc, from the gcc-aarch64-linux-gnu package:

cargo build --release --target aarch64-unknown-linux-gnu

⚠ The linker binds each symbol to the newest version present in the sysroot, so the workstation's glibc 2.43 sets the floor — not the toolchain, which targets 2.17. Currently the highest referenced version is 2.34, below the Orin's 2.35, because everything above 2.30 comes from one event: glibc 2.34 merged libpthread and libdl into libc and re-versioned the moved symbols. Stable, but not guaranteed the way a native build is.

Writing style

Documents describe what is and what will be. They are not changelogs — git holds the sequence.

Instead of Write
"X was dropped because Y" what the design is
"this was a risk, now resolved" what runs, and the measurements
"the earlier decision assumed Z" the current constraint
"X was rejected in favour of Y" Y, stated as the rule

⚠ A sentence justifying a current state by a current reason becomes false when the reason changes, and nothing announces it — it stays, confident and well-written, and reads as fact.

Two things survive the cut, because they stay true when their subject is gone:

  • A mechanism that can bite again. Not "we picked the wrong extract once", but "driving side is a country-level property, so a clipped extract routes mirrored".
  • A condition that would change the decision, stated generally — "a permanently installed, car-powered panel" rather than a story about a panel that was considered.

Reports under impl/reports/ are the exception and the place for all of it: the investigation, the dead ends, what was measured and found wrong.

Front matter

Every markdown document carries front matter, with no exceptions for the hub or for any particular repo. The type discriminates:

type Applies to Extra fields
reference READMEs, OVERVIEW, ARCHITECTURE, BOM — things that describe what is —
guidelines GUIDELINES.md —
plan anything under impl/plans/, plus ROADMAP.md status, updated
report anything under impl/reports/ date

status is one of active, superseded or done.

status and updated make a plan's staleness visible — plans go stale and reports do not, and without them a plan from months ago reads identically to one written this morning. date on a report records the moment it describes.