The version of the contract@mica/sdk publishes: the exported names of each entry
point — the values an add-on can call and the types it can name, equally — the props
of every exported component, and the members of every exported string vocabulary. It
moves when that surface moves and at no other time, which is the whole reason it is worth
reading.
That "and the types" is spelled out because the sentence used to say "the exported names"
and leave it open, and the check behind it read the surface with a runtime import *,
which cannot see a type at all. Sixty-three published names on index.ts and sixty-two on
addon.ts were outside the gate in both directions until MICA-182 closed it. An add-on
that writes import type { Note } from '@mica/sdk' is broken by that name disappearing
in exactly the way one calling a deleted hook is, so this number moves for both.
And it moves for a type's shape on the same reasoning (MICA-185): the fields of a
published object type and the members of a published literal union are names an add-on
compiled against too. AppTile.fg becoming AppTile.glyph, or 'unordered' leaving
AppUpdateKind, keeps AppTile and AppUpdateKind on the entry point and breaks every
bundle that wrote the old one — and pnpm typecheck staying green says nothing about it,
because that only proves the shell still compiles against the SDK, not that a bundle
built against last week's does. Seven published types are consumed by nothing in this
tree at all, so for those it would not even fail by accident. Removing a field or
narrowing a union is therefore a bump; adding a field or widening a union is not, on the
same terms as adding an export.
publicSurface.test.ts pins its frozen baselines to this value and imports it from here
rather than declaring a copy — landing a break means bumping this and re-freezing those
baselines in the same commit, so the break is a reviewable line in a diff. It lived as a
const inside that test file until MICA-173, where nobody outside this repo could
import it; the file said as much itself.
NotMICA_VERSION below, which is a CalVer build stamp computed from git log and
moves on every push to main (eleven tags on 2026-08-27 alone) — an add-on branching on
it is branching on noise. Notpackage.json's 1.0.0 either, which is a placeholder
read by no code and would advertise a published npm package that does not exist.
Typed string rather than the literal '1' on purpose: the one call site this export
exists for is an add-on comparing it against the version it was written against, and a
literal type makes SDK_CONTRACT_VERSION === '2' a "this comparison appears
unintentional" error rather than a check.
The design system is out of contract.app.css, app-utilities.css and
app-reset.css moved into the package on MICA-172, and this number does not move when
they change. That is not an oversight: an add-on reads this to decide what it may call,
and there is no equivalent question for a utility class. Nor is there a mechanism to ask
one — an add-on's CSS is inlined from whatever vite.addon.config.ts injected at its
build, so no add-on can have compiled against one stylesheet and been handed another. A
number that moved for every colour tweak would stop meaning anything for the case it
exists to serve, which is the one thing it must not do.
So is the tooling in checks/ (MICA-312): @mica/sdk/stylelint and the pnpm check
script an add-on runs. No bundle imports either, so a stricter check can fail an author's
lint and cannot change what an installed add-on does. publicSurface.test.ts pins the
subpath, not what the config says.
Note what it does not do: nothing consults it at install or boot time. There is no
runtime compatibility gate anywhere in the phone (MICA-173), and adding one is new
manifest surface that needs deciding on its own. This is a number an add-on author can
read and act on; it is not a number the shell enforces.
The version of the contract
@mica/sdkpublishes: the exported names of each entry point — the values an add-on can call and the types it can name, equally — the props of every exported component, and the members of every exported string vocabulary. It moves when that surface moves and at no other time, which is the whole reason it is worth reading.That "and the types" is spelled out because the sentence used to say "the exported names" and leave it open, and the check behind it read the surface with a runtime
import *, which cannot see a type at all. Sixty-three published names onindex.tsand sixty-two onaddon.tswere outside the gate in both directions until MICA-182 closed it. An add-on that writesimport type { Note } from '@mica/sdk'is broken by that name disappearing in exactly the way one calling a deleted hook is, so this number moves for both.And it moves for a type's shape on the same reasoning (MICA-185): the fields of a published object type and the members of a published literal union are names an add-on compiled against too.
AppTile.fgbecomingAppTile.glyph, or'unordered'leavingAppUpdateKind, keepsAppTileandAppUpdateKindon the entry point and breaks every bundle that wrote the old one — andpnpm typecheckstaying green says nothing about it, because that only proves the shell still compiles against the SDK, not that a bundle built against last week's does. Seven published types are consumed by nothing in this tree at all, so for those it would not even fail by accident. Removing a field or narrowing a union is therefore a bump; adding a field or widening a union is not, on the same terms as adding an export.publicSurface.test.tspins its frozen baselines to this value and imports it from here rather than declaring a copy — landing a break means bumping this and re-freezing those baselines in the same commit, so the break is a reviewable line in a diff. It lived as aconstinside that test file until MICA-173, where nobody outside this repo could import it; the file said as much itself.Not
MICA_VERSIONbelow, which is a CalVer build stamp computed fromgit logand moves on every push tomain(eleven tags on 2026-08-27 alone) — an add-on branching on it is branching on noise. Notpackage.json's1.0.0either, which is a placeholder read by no code and would advertise a published npm package that does not exist.Typed
stringrather than the literal'1'on purpose: the one call site this export exists for is an add-on comparing it against the version it was written against, and a literal type makesSDK_CONTRACT_VERSION === '2'a "this comparison appears unintentional" error rather than a check.The design system is out of contract.
app.css,app-utilities.cssandapp-reset.cssmoved into the package on MICA-172, and this number does not move when they change. That is not an oversight: an add-on reads this to decide what it may call, and there is no equivalent question for a utility class. Nor is there a mechanism to ask one — an add-on's CSS is inlined from whatevervite.addon.config.tsinjected at its build, so no add-on can have compiled against one stylesheet and been handed another. A number that moved for every colour tweak would stop meaning anything for the case it exists to serve, which is the one thing it must not do.So is the tooling in
checks/(MICA-312):@mica/sdk/stylelintand thepnpm checkscript an add-on runs. No bundle imports either, so a stricter check can fail an author's lint and cannot change what an installed add-on does.publicSurface.test.tspins the subpath, not what the config says.Note what it does not do: nothing consults it at install or boot time. There is no runtime compatibility gate anywhere in the phone (MICA-173), and adding one is new manifest surface that needs deciding on its own. This is a number an add-on author can read and act on; it is not a number the shell enforces.