We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Capstones step 1 of 5
Capstone: the upload that cannot be misused
There is nothing interesting to compute in this problem. sha256 does the only
arithmetic. The entire assessment is structural, and that is the point: this
is what it looks like when the type system, rather than a runtime check, is what
keeps an API honest.
The API
Export exactly these names:
Draft, Sealed, Upload
begin(name: str) -> Draft
add_chunk(draft: Draft, data: bytes) -> Draft
seal(draft: Draft) -> Sealed
checksum_of(sealed: Sealed) -> str
describe(upload: Upload) -> str
Draft and Sealed are your design. The grader never looks inside them; it
only ever calls the five functions. What matters is that they are two distinct
types.
checksum_of returns the hex SHA-256 of the concatenated chunk bytes — the
name is not part of the digest. describe returns "draft(<name>)" or
"sealed(<name>)".
What “cannot be misused” means
The obvious design is one class with a sealed: bool flag and a
raise RuntimeError("already sealed") in add_chunk. That design works. It
is also strictly worse, because the error arrives at runtime, in production, on
the path nobody exercised — and the caller had no way to know they were writing
a bug.
With two types, the mistake is not an error you raise. It is a program that does not type-check:
checksum_of(begin("a")) # a Draft has no checksum yet
add_chunk(seal(begin("a")), b"") # you cannot append to a sealed upload
seal(seal(begin("a"))) # you cannot seal twice
The feedback writes itself: “your API lets a caller express
checksum_of(begin('a')). That should not be possible to express.”
This is the typestate pattern. The state of the object lives in its type, so every state transition is a function with a different input and output type, and an illegal sequence is a compile-time error.
The negative fixture, and why you cannot cheat it
Those three lines live in _negative_fixtures, each with a # type: ignore
comment. They are never executed. They are graded anyway, because --strict
includes --warn-unused-ignores.
So consider the lazy solution: Draft = Sealed = dict[str, Any]. It passes
every behavioural test in this problem. And it fails the submission, because
with one type the three calls stop being errors, the three # type: ignore
comments become unused, and mypy reports three errors. A negative assertion
that cannot be satisfied by weakening the types is the only kind worth writing.
The assert_type calls above them are the positive half: they pin every return
type, so you cannot satisfy the negatives by returning Any from somewhere.
Three more properties the driver checks
add_chunk does not mutate its argument. The driver builds the whole chain
of drafts and then seals them newest first. If add_chunk appended in
place, every prefix would carry the final digest and the whole prefixes list
collapses. A frozen dataclass returning a new instance makes this free.
Instances are immutable. setattr(draft, "name", "hijacked") must raise.
frozen=True gives you FrozenInstanceError; slots=True also removes the
per-instance __dict__, so a typo’d attribute name is an error rather than a
new attribute nobody reads.
describe is total. type Upload = Draft | Sealed, matched exhaustively.
mypy proves every branch returns; adding case _: assert_never(upload) makes
the proof explicit, so that the day someone adds an Aborted state, the failure
is a type error in describe rather than a None returned into a log line.
The rule for the implementation
No isinstance, no assert, and no raise TypeError in add_chunk or
checksum_of. If you find yourself reaching for one, the types are not doing
their job and you have rebuilt the boolean-flag design with extra steps.
describe is the one place a runtime discrimination is legitimate — that is
what the union is for.
One aside worth keeping: draft_type is not sealed_type in the driver goes
through two object locals on purpose. Written directly as Draft is not Sealed, --strict-equality rejects it as a non-overlapping identity check —
which is the type checker telling you, unprompted, that your two states are
provably distinct.
Loading visualization…
Stuck?
Python reference solution
Sign in to attempt this problem and reveal the reference solution.