Thin integration harness for RailToolKit workflows: versioned use cases, assets, and CI — not a shared domain-types package.
Re-exports APIs needed by active use cases (TrainRuns for UC-001):
using RailToolKit| Piece | Role |
|---|---|
usecases/ |
Catalogue of integration workflows + assets + per-case test.jl |
RailToolKit.jl |
Optional convenience re-exports; no domain logic |
test/catalogue.jl |
Frontmatter and section validation |
test/runtests.jl |
Catalogue gates + discovery of active UC-*/test.jl |
| CI | Pkg.test() runs catalogue checks and every status: active case |
Contracts grow from active use cases. Do not introduce a RailCore-style abstract
type layer here — see AGENTS.md for agent scope and conventions.
Use cases are intent first, implementation when active.
- Draft: describe the workflow in domain language — goals, actors, data needs, decidable outcomes — without requiring a specific package or API.
- Active: bind a concrete implementation (
packages:,assets/, APIs,test.jl) so CI can prove those outcomes.
Propose early; integrate and test later. The same UC-NNN-short-name/ directory
carries the case from proposal through binding.
propose (draft) → work in parallel → bind & prove (active)
│ │ │
intent narrative build pkgs, packages: + assets/
implementation- resolve Q-n, test.jl + APIs
agnostic choose binding status: active → CI
status |
Narrative | Catalogue in CI | test.jl |
Runs in CI | Harness Project.toml |
|---|---|---|---|---|---|
| draft | intent / domain | yes (structure) | optional | no | not required yet |
| active | intent + bound implementation | yes (strict) | required | yes | required for listed Julia pkgs |
| deprecated | retained for lineage | yes | — | no | remove when unused |
Draft — land the contract before code exists:
- Copy the scaffold from
usecases/template/(TEMPLATE.md→usecase.md, plustest.jl; mkdirassets/) withstatus: draft - Fill Goal, Scope, Main scenario, Artifacts, and Open questions (
Q-n) in domain terms — e.g. blocking until a package is chosen or published packages:may list candidates or stay TBD- If no decidable Expected outcomes (
OUT-n) exist yet, stay draft
In between — months of other work are normal: packages ship elsewhere, schemas settle, assets are collected. The draft case stays the stable intent target.
Active — when an implementation can prove the outcomes:
- Bind
packages:and add Julia deps to rootProject.toml - Add
assets/(inputs); put regression expected values intest.jl - Write
test.jlimplementing Verification (TEST-n/OUT-n) - Name concrete entrypoints in the main scenario (code sketch optional but preferred)
- Set
status: active, updateupdated, remove template HTML comments - CI discovers and runs the case automatically — nothing is hardcoded
You can run test.jl locally before activation; CI ignores it until status: active.
Each case has a stable id (UC-001, …) and a workflow version
(integer). Reference a specific revision as UC-001@2. Optional lineage
in frontmatter records how versions relate:
| Change | What to do | lineage |
|---|---|---|
| Update | Bump version in the same or a new directory; deprecate the old one |
supersedes: [UC-001@1] |
| Split | New ids (e.g. UC-003, UC-004) from one parent |
split_from: UC-001@2 |
| Merge | New id combining workflows | merged_from: [UC-001@1, UC-002@1] |
| Retire | Set status: deprecated |
superseded_by: UC-001@2 |
CI allows at most one status: active directory per id. Each (id, version)
pair must be unique across the catalogue. Lineage refs must point at existing
cases when declared.
Optional frontmatter for navigation and binding order — not CI prerequisites.
| Field | Meaning |
|---|---|
related: |
Peer cases (shared assets, same tutorial source, complementary scope) |
builds_on: |
Read/bind these first; this case extends their intent |
Refs use UC-NNN@M (same as lineage). Catalogue validation checks format
and warns on missing refs; it does not block activation or run cases in
dependency order.
Each status: active case stays self-contained: own assets/ and
test.jl that prove its OUT-n locally. Do not import another case's test or
require another case to be active first — re-compute or vendor inputs in the
dependent case instead.
Each usecase.md follows usecases/template/TEMPLATE.md:
- Intent (always): Goal, Scope, Actors, Preconditions, Main scenario, Expected outcomes
- Artifacts / testability: formats and decidable
OUT-n; concrete paths when active - Implementation binding (active):
packages:,assets/, APIs in the scenario,test.jl - Context: Assumptions, Reproducibility, Open questions, References
ID prefixes within a case: ACT-n, PRE-n, STEP-n, A-n, OUT-n, EXC-n, EX-n, TEST-n, Q-n.
Layout: one directory per case — usecases/UC-NNN-short-name/ with usecase.md,
assets/, and test.jl (stub from the template; required to pass CI when active).
Keep cases under usecases/, not under src/ or test/.
Rules:
- Declare
template_version: 1andversion: 1(seeCHANGELOG.md). - Keep the narrative implementation-agnostic until binding;
status: activerequires a concrete binding, decidable outcomes,test.jl, and passing CI. - Do not use a
layersfrontmatter field — model layers as their own use case. - Non-Julia data contracts (e.g.
schema) userole: datainpackages:and are not Pkg deps.
mkdir -p usecases/UC-NNN-short-name/assets
cp usecases/template/TEMPLATE.md usecases/UC-NNN-short-name/usecase.md
cp usecases/template/test.jl usecases/UC-NNN-short-name/test.jl
# edit frontmatter + sections; fill assets/ and test.jl when binding for activeThen open a PR. Template snapshots: usecases/template/versions/v1.md.
julia> using Pkg; Pkg.activate("."); Pkg.instantiate()
julia> using Pkg; Pkg.test()First active case: usecases/UC-001-running-time-minimal/.
POI / block sections: usecases/UC-002-block-section-pois/.
Single case:
julia --project=. -e 'include("usecases/UC-001-running-time-minimal/test.jl")'- TrainRuns.jl — running-time calculation (UC-001)
- Data contracts: railtoolkit/schema (validated by TrainRuns on load)
Copyright (c) 2026, Martin Scheidt (ISC License)
see LICENSE file
