Developer Onboarding
This guide is the starting point for contributors who need to understand tin's architecture, make a focused change, and know which evidence should move with that change.
tin is intentionally split into small surfaces. The fastest way to become productive is to learn the dependency direction first, then choose the narrowest layer that owns the behavior you are changing.
First Day Reading Path
Read these in order:
- product_overview.md: the problem tin is solving and the top-down/bottom-up motivation.
- framework_walkthrough.md: one measured value moving from driver input to bounded channel, typed event, HSM transition, and output adapter.
- minimal_tin_app.md: a small complete application shape using a channel, payload event, queued runtime, and caller-owned loop.
- architecture.md: package boundaries and dependency direction.
- decision_guide.md: where a change belongs and which runtime shape to choose.
- design_invariants.md: the properties that should remain true across framework changes.
- tsm.md: state-machine authoring, runtime configurations, and the behavior layer.
- runtime.md: queues, channels, actors, tasks, ticks, and resource accounting.
- embedded_framework.md: the driver/runtime/task/HSM separation used for embedded targets.
- testing_map.md: test layers, labels, platform smoke gates, and what each gate proves.
After that, use the package-specific guides only when your change touches that surface:
Onboarding Checkpoint
Before making a framework change, you should be able to explain:
- which layer owns the change and which layers should not know about it;
- how one input value moves from adapter code into bounded runtime storage and then into a typed event;
- who calls
step, drain, or an executor, and what one step is allowed to do;
- where queue capacity, channel capacity, task frames, and timer slots are declared;
- how target-specific code is kept outside shared behavior;
- which focused test proves the primitive behavior and which broader gate proves integration.
Architecture In One Page
The main dependency rule is:
application/domain code
|
v
tsm behavior tio I/O contracts
| |
v v
runtime primitives thal platform contracts
\ /
v v
tin facade
Each layer has one job:
tsm owns legal product behavior: states, events, transitions, guards, actions, hierarchy, history, orthogonal regions, deferred events, and HSM runtime dispatch.
runtime owns mechanical movement: queues, channels, actor links, coroutine tasks, tick timers, cancellation, event sinks/sources, and resource manifests.
tio owns portable driver-facing contracts and small payload records.
thal owns target assumptions, compiler profile contracts, and platform/HAL adapter boundaries.
tin is the combined API for runtime, state-machine, I/O, and platform contracts.
- Application code owns product naming, middleware policy, vendor SDK objects, board support, simulation engines, and domain models.
When a change is hard to place, ask: "Is this product behavior, event movement,
driver shape, target integration, or user-facing packaging?" The answer usually names the layer.
Repository Map
Primary implementation:
Examples:
Tests and evidence:
- test/hsm_authoring_semantics_test.cpp: authoring and semantic behavior.
- test/framework_integration_test.cpp: runtime, task, platform, transport, generated trace, and integration tests.
- test/CMakeLists.txt: CTest labels, compile-fail tests, generated tooling tests, platform smoke gates.
- spec: Lean, TLA+, IR schemas, requirements, traceability, and generated evidence.
- tools: CLI tooling, trace generation, profile checks, platform smoke, performance harness, and evidence helpers.
Development Environment
The normal host build assumes CMake, a C++20 compiler, Python 3.11+, Catch2, and the tools required by enabled quality tests.
Use an out-of-tree build:
cmake -S . -B build/host -DCMAKE_BUILD_TYPE=Debug
cmake --build build/host
ctest --test-dir build/host --output-on-failure
Some development shells export include or library paths that can poison the system compiler. When that happens, run host tests with the compiler environment sanitized:
env -u CPATH -u CPLUS_INCLUDE_PATH -u OBJCPLUS_INCLUDE_PATH \
-u C_INCLUDE_PATH -u OBJC_INCLUDE_PATH \
-u LIBRARY_PATH -u LD_LIBRARY_PATH \
ctest --test-dir build/host --output-on-failure
Target smoke tests use real target toolchains or workspaces when present:
export TSM_PLATFORM_ROOT="$HOME/opt/tin-platforms"
ctest --test-dir build/host -L platform --output-on-failure
The runner searches $TSM_PLATFORM_ROOT/FreeRTOS-Kernel and $TSM_PLATFORM_ROOT/zephyrproject/zephyr, with TSM_FREERTOS_KERNEL_SOURCE and ZEPHYR_BASE available as explicit overrides.
How To Make A Change
Start by classifying the change:
| Change type | Start here | Also update |
| HSM authoring syntax or transition semantics | include/tsm.h, include/tsm/core_algorithms.h, test/hsm_authoring_semantics_test.cpp | Lean traces or trace-codegen when semantics change |
| Runtime queue/channel/actor behavior | include/tsm/runtime/*, test/framework_integration_test.cpp | docs/runtime.md, docs/channels.md, docs/actors.md |
| Coroutine task/executor behavior | include/tsm/runtime/coroutine.h, include/tsm/runtime/executor.h | docs/coroutines.md, resource tests |
| Platform adapter behavior | include/tsm/{bare_metal,freertos,zephyr,linux,qnx}.h | platform smoke tests and target examples |
| Tooling or IR schema | tools/tsm_tool.py, spec/ir, test/tsm_tool* | docs/tooling.md, docs/machine_ir_json.md, generated artifact tests |
| Public API packaging | include/tin/*, CMake package files | docs/tin.md, public API boundary tests |
| Performance work | tools/performance_harness.cpp | docs/performance_roadmap.md, benchmark notes |
Keep changes narrow. Do not move code across package boundaries just because a caller happens to need it. Prefer a small adapter or facade when a target or domain policy needs to connect to the framework.
Design Rules
- Behavior definitions should be target-independent. Do not include STM32, Zephyr, FreeRTOS, Linux, QNX, or middleware headers in an HSM definition.
- Events are ordinary C++ types. Use payload fields when product behavior needs measured data, diagnostic codes, IDs, timestamps, or limits.
- Runtime storage must remain explicit and bounded. Queue capacity and overflow policy should be visible in the type or runtime policy.
- Platform wakeups are scheduling signals, not event storage. Store events in channels or runtime queues.
- Time enters the framework as integer ticks or typed timer events. HSM definitions should not read wall-clock time directly.
- Target adapters may use vendor APIs, but the public behavior surface should depend on tin/tio/thal contracts.
- Resource accounting should move with embedded-facing features. If a feature adds queue slots, task frames, timer slots, or heap use, expose it in resource snapshots or manifests.
Testing Expectations
Run the smallest useful test first, then the full suite before handing off.
Useful focused commands:
./build/host/bin/tsm_test [hsm_authoring]
./build/host/bin/tsm_test [runtime]
./build/host/bin/tsm_test [embedded]
ctest --test-dir build/host -L tooling --output-on-failure
ctest --test-dir build/host -L platform --output-on-failure
Full local gate:
ctest --test-dir build/host --output-on-failure
Add tests where the risk lives:
- authoring tests for definition-language changes;
- semantic tests for transition behavior;
- runtime/task tests for queueing, wakeups, ticks, cancellation, and resource accounting;
- tooling smoke tests for CLI, IR, schema, and generated artifacts;
- platform smoke updates for target adapter or example changes;
- realistic examples only after primitive behavior has direct coverage.
Adding A Feature
Before implementing, write down:
- the layer that owns the feature;
- the public API entry point, if any;
- the fixed resource impact;
- whether it changes behavior semantics or only runtime mechanics;
- which tests will prove the change.
During implementation:
- follow existing naming and header organization;
- keep domain examples out of core headers;
- carry event data in typed payloads;
- prefer compile-time checks for invalid configurations;
- keep host tests target-independent unless the point is platform integration.
After implementation:
- update the package guide for the touched surface;
- update this onboarding guide only if the workflow or architecture changes;
- run focused tests plus the full CTest gate;
- record platform or performance caveats in docs when the result depends on a particular toolchain or workspace.
Review Checklist
Before requesting review, verify:
- The change belongs to the layer it modifies.
- Public examples use payload events when data matters.
- Target-specific code is behind a facade, adapter, or platform header.
- Queue capacities, overflow policies, task counts, and timer slots are explicit.
- Tests cover the primitive behavior directly.
- Docs mention any new user-facing API, IR shape, platform assumption, or performance claim.
ctest --test-dir build/host --output-on-failure passes, or the exact unavailable dependency is documented.
Common Pitfalls
- Putting vendor HAL calls inside state-machine definitions.
- Treating platform smoke examples as replacements for primitive unit tests.
- Adding unbounded containers to embedded-facing runtime paths.
- Hiding overflow behavior in helper functions.
- Sending marker events when the behavior needs measured data as a payload.
- Making tooling accept an IR shape without adding schema and smoke coverage.
- Updating generated or evidence-adjacent behavior without linking it to the relevant docs and tests.
Where To Ask Questions In The Code
Use rg to orient quickly:
rg "struct .*Machine|using transitions" examples test include
rg "send_event|try_send|actor_link|actor_group" include test examples
rg "resource_snapshot|resource_contract" include test examples
rg "PlatformSmoke|check_profile_compile|traceability" test tools spec
The project is easier to review when changes preserve the same separation: drivers produce typed payloads, runtime moves bounded work, tasks translate mechanics into events, and HSMs own product behavior.