Skip to content

What a case is

A case is the unit of practice in Fito: a repository a learner forks, works in, and hands back as a pull request. This page describes what one is made of, before you build any of it.

Read it if you are about to author a case. It covers the shape of the artefact and the words the authoring commands use. It does not cover the commands themselves — each of those has its own page — and it does not cover playing a case, which is Open and work a case.

A case lives in its own git repository, under your account or your organisation. Fito never hosts it. What Fito stores is the https URL, the version, and the knowledge the case claims to teach.

Three kinds of ref live in that repository, and they have different lifetimes:

Ref What it is Lifetime
main The development branch. Where you edit Yours, always
vX.Y.Z A branch and a tag, cut at publish Frozen forever
case/solution-vX.Y.Z The exemplar solution for that version Frozen forever

A learner clones the frozen vX.Y.Z, so what they receive never moves under them. When you correct a case you cut new frozen refs beside the old ones; you never rewrite one.

The case’s identity is a UUID the backend assigns at registration. It is not written anywhere in the repository, and it is the <id> in the case’s /cases/<id> URL.

The source in that repository exists in two states, and you build both.

State Where it lives What it is
Starter The frozen vX.Y.Z branch What the learner receives
Solution case/solution-vX.Y.Z The worked example, for the evaluator

What each state contains follows from the case type. A diagnostic starter carries the defect and its solution repairs it. An implementation starter carries the gap and its solution fills it. A tutorial may use the same code in both, and still names a solution branch.

.case/ is the same on both. It describes the case, not a branch, so the folder on the solution branch must match the one the learner receives. The publish step checks this and reconciles the two before it pushes, so a divergence is caught rather than shipped — but it is easier to never create one.

That is also why starter_state/known_state.yaml carries no branch field: one file describing every ref could never name a branch correctly.

Every case declares one type, and the type binds what the case must contain. The structural validator enforces the binding.

Type What the learner does What the type requires
diagnostic Finds and repairs a defect starter_state/known_state.yaml carries failing_tests or known_errors
implementation Builds a feature task/deliverables.yaml includes a code_change entry
tutorial Follows guided steps through a codebase task/steps.md is present, and failing_tests must not appear

.case/ is the contract. It sits beside the project’s own source, and it describes the case on every ref.

Fourteen files are always required:

File What it carries
manifest.yaml Title, type, version, status, duration, language, tags, authors, and how a solution is submitted
artifacts.yaml The project files that matter, each with a role
learning_objectives.yaml What the case develops
prerequisites.yaml What the learner must already hold
rubric.yaml The criteria the work is scored against
solution.yaml The exemplar branch and who may see it
situation/situation.yaml The list of narrative documents
situation/01_intro.md The situation the learner lands in
task/objective.md The single goal statement
task/deliverables.yaml The expected outputs
starter_state/description.md The starting condition, in prose
starter_state/known_state.yaml The starting condition, machine-readable
solution/notes.md The intended approach — for the evaluator, never the learner
solution/notes-template.md The blank scaffold of those sections

Four more are conditional or optional:

  • task/steps.md — required for a tutorial, and only then.
  • quiz.yaml — optional. A case may carry questions alongside its rubric.
  • task/constraints.md — optional. Rules the learner must work within.
  • Further situation documents — a ticket, an email, a log — each listed in situation/situation.yaml with its format.
  • acceptable_solutions in solution.yaml — further branches that also solve the case, each with an id and an optional description.

solution.yaml’s visibility decides when a learner may see the exemplar: never, post_completion, or always. Generating a case proposes post_completion unless you choose otherwise.

Four of those files name knowledge components, each with a Bloom level:

  • learning_objectives.yaml — what the case develops. At least one entry, and at least one of them carries weight: primary.
  • prerequisites.yaml — what the learner should already hold. Each entry is mandatory: true or false.
  • rubric.yaml — every criterion is anchored to one component, and every learning objective is anchored by at least one criterion.
  • quiz.yaml — each question names one or more.

A reference is written whole, in the form <type>:<technology-slug>.<component-slug>, for example concept:javalin.handler. The three types are concept, execution-fact and usage-scenario.

Together these make the case’s reference set: the deduplicated pairs of component and Bloom level. It is what the backend resolves when you publish, and it is what freezes.

That rule is why choosing references is the slowest part of designing a case, and why it happens before anything is written.

A rubric criterion or a quiz question met at a score of 2 or 3 records its own Bloom level as the proficiency for the component it names, and a case cover’s prerequisite badges read those same recorded proficiencies. So a criterion aimed at a prerequisite lets the case award the prerequisite it asked the learner to arrive with, and the next case that needs that component reads You have this on the strength of a case that demanded it.

Three rules hold, and they are enforced in different places:

  • Every learning objective is scored by at least one rubric criterion. The validator rejects a tree that breaks this, so you find out before you publish. A quiz question does not satisfy it, because the quiz is optional.
  • Every learning objective is reachable at its own level. At least one of those criteria declares the objective’s Bloom level or a higher one, because a criterion records no more than the level it declares. An APPLICATION criterion covers a COMPREHENSION objective, so one component can be graded at two depths. The validator rejects a tree that breaks this too.
  • Every criterion and every question names a learning objective. Here the validator accepts what it could reject — a criterion aimed at a prerequisite, or at a component the case references nowhere else. /fito-case-design compares them for you instead, and reports each miss under Open questions in the proposal for you to settle.

The third is a report rather than a refusal because the structural check also runs when a learner opens a case, and a case already published that way has to stay openable.

Everything under solution/ is for the evaluator. The learner is briefed from the situation, the task, the constraints, the deliverables, the artifacts and the learning objectives — never from the solution notes.

Two files reach further than they look:

  • learning_objectives.yaml seeds the public catalogue page, which anyone can read before enrolling.
  • prerequisites.yaml and the task files are read to the learner in their briefing.

So the prose in those files carries one rule.

Descriptions name the topic, never the move

Section titled “Descriptions name the topic, never the move”

A description may say what is broken and what the learner will be able to do. It must not say the move that repairs it.

The test is a single question:

Could a learner who read only this description, and nothing else from the case, apply the fix?

If yes, rewrite it.

  • ✗ “Trace a failing request through the ordered handler chain, identify the before-handler that short-circuits it before routing, and apply an idiomatic fix.” — this hands over the diagnosis.
  • ✓ “Trace a failing request through the ordered handler chain and repair the composition that breaks it.” — the same objective, with the diagnosis left to the learner.

tutorial cases are exempt. Their steps are the method, so there is nothing to withhold. The rubric is exempt too: a scoring level cannot describe an exemplary answer without naming what that answer does, and the rubric is never read to a learner.

Nothing checks this rule. It is carried by the authoring commands and by your review.