# What v1 means

v1 is a promise about applications, not a count of features, and it has two conditions. One is positive and
one is negative.

> **Positive: an application built on v1 does not need a major refactor for the foreseeable future. Later
> releases add functionality to the abstractions v1 already has.**
>
> **Negative: there is no such thing as legacy in v1. Nothing stays in the code for the sake of backwards
> compatibility.**

They work together. The first makes v1's shape permanent, so whatever is in it is there for good. The
second is why the shape has to be clean when it is frozen: v1 is the last release where a breaking cleanup
is free. Anything kept "for now" at v1 stays forever.

So v1 is reached when the abstractions stop moving, not when the list of capabilities is long. A capability
that is missing at v1 is fine if the abstraction it will attach to is already there. An abstraction that is
still being drawn is not fine, however much works on top of it. [status.html](status.html) grades where each
capability *lives*; this page grades whether the shape it lives in is *final*. The two are different
questions, and only the second decides the version number.

[TODO.md](TODO.md) is organised by this page: what blocks v1, what can land after it, what is upstream.

## What counts as breaking

A change is breaking if an application that compiled, passed and behaved correctly on v1.x would not on
v1.x+1 **without the application's author touching it**. That is wider than a removed method:

- **A new compile error is a breaking change.** The processor's whole job is to move failures leftward, so
  every new check is a way to break a build that was accepted yesterday. A check may land after v1 only if
  it rejects something that was already a bug. Anything else lands before v1, or behind an opt-in.
- **A different resolution is a breaking change.** Which provider satisfies a parameter, which phase a part
  is built in, what a generated wiring is named, what a `null` provider does: an application depends on
  these without ever writing them.
- **A different lifecycle order is a breaking change.** `Phase` and `FrameStage` are contracts. Adding a
  phase between two existing ones moves every part built around it.
- **The generated project is application code.** Once `vexel-desktop` has produced a project, the
  template is a promise about what that project keeps compiling against. A change to the builder's public
  API is also a change `mainframe` compiles against.
- **The wire protocol is an interface.** A script that drove the application on 1.0 must still drive it.
  New verbs are additive. A verb's replies are not.

Additive means: a new method with a default, a new annotation attribute with a default, a new starter, a
new verb, a new `Shell` accessor, a new module. It never means a new obligation on existing code.

## The contract surface

Six surfaces, and v1 freezes each. The state of each is in [status.html](status.html#v1-ledger).

| Surface | What is promised |
| --- | --- |
| **The annotations** (`-api`) | Every annotation, attribute and default; and the **processor semantics** behind them, which are the larger half: type matching, phase inference, `@Default` and back-off, generated names, the checks that exist |
| **`Shell` and the phases** | Every accessor, the phase each is legal in, the order parts are built in, and what a wiring may do at each |
| **The component model** | How a component declares its lane, its channels and its loss class; what may cross a lane; what a wedged component does to the rest. `Lanes` and `Placement` are the threads and mailboxes; `@Component(lane)`, `@Subscribe` and `@Publishes` are the declaration. **Ruled to freeze at v1, multithreaded by default on a shared default lane that is not the main thread**, not to defer |
| **The build shape** | Module coordinates, starters, the BOM, the reachability metadata an application inherits, and what `new vexel-desktop` writes |
| **The launch surface** | `Launch`, `FRAMEWORK_KEYS`, the reserved keys, `--automation` and `--profile`, exit behaviour, what is reported when a capability is dropped |
| **The wire protocol** | The automation line protocol: framing, `ok` and `err`, the verbs and their replies |

**A seventh surface is not the framework's, and bounds all six.** `Shell` hands over `Gui`, `GuiApp`,
`KronoGui`, `TitleBar`, `Settings`, `Atchung`, `ComputeQueue` and `Icon`, and its `Windows`, `Window` and `Dialogs`
hand over `Gui`, `TitleBar` and `Modal` in turn, so an application holds `vexelray-gui`'s types directly. v1 cannot be more stable than they are. Either those
repos freeze what `Shell` exposes, or `Shell` stops exposing it. That is a decision, not a task, and it is
in [TODO.md](TODO.md).

## When an abstraction is frozen

An abstraction is ready to freeze when all five hold. Anything short of that is *experimental*, however
well it works.

1. **It has more than one witness.** The framework's own method, *one component is not a census*, applies
   to the framework itself. The three ported applications were deleted on purpose; what validates the abstractions
   now is the template, the three built witnesses (W1 with several windows, W2 component-heavy, W3 long-running)
   and the applications generated from it, most of them one window and its dialogs. Freezing something drawn from
   one shape freezes that shape's accidents, which is what the witnesses are for.
2. **Its semantics are decided in writing.** Not "works", but "in this case it does this, and here is why"
   in [architecture.md](architecture.md). The open ones (exact-type matching, a `null` provider) are cheap
   today and a breaking change afterwards.
3. **Its extension points are named.** For each thing v1 leaves out, the abstraction it will attach to
   exists already. That is the extension test, below.
4. **Nothing in it is legacy** (see below), so that the freeze does not preserve a compatibility shim.
5. **No entry under *Blocks v1* in TODO.md names it.**

## The liveness guarantee

The point of multithreaded-by-default is that **there is never another frozen GUI.** Stated as a contract v1
freezes: *whatever a component does, including looping forever, the window keeps responding, the user can
always close the application, and the application can end itself.* Three parts, each with what would break it:

- **The main thread never waits on a component.** A send from the main thread to a lane must not block. A
  channel that blocks a full publisher (`Backpressure.BLOCK`) is exactly how a wedged lane freezes a window:
  TODO already records a real freeze from it, a full 65,536-slot mailbox blocking a tree's node setters for
  good with nothing thrown. So code on the main thread may only send on a channel that coalesces, drops or
  fails. The processor checks it for declared sends (T4.7); a send nobody declared is not seen.
- **Closing always ends the process.** A close request is the user's explicit intent, so it is honoured within
  a bound even if a lane will not drain. Shutdown once joined each lane for about a second, which for *n*
  wedged lanes was *n* seconds and then nothing. The contract is a total bound after which the process halts, and it is built: `Disposer.beforeClose` arms it, 10 s by default (`LivenessPolicy`).
  A close gate that waits on a component would break it, and so would any gate run on a wedged lane.
- **The application can end itself.** The watchdog runs on a thread that belongs to no lane, so a wedged main
  thread cannot stop it, and an explicit `halt` reaches `Runtime.halt` from that thread. What supervision does
  by default is a policy the application can override and is not itself part of the contract (below).

What is not promised: that a wedged component's own work continues, or that a frozen main thread is *fixed*.
`Stalls` names it, and the exit path is the way out. An application that blocks its own main thread has broken
the first rule from the other side, and the guarantee is honest only about the framework's share.
A wedged main thread leaves the window itself unresponsive, so there the way out is the watchdog ending the
process once the stall threshold passes: guaranteed, and delayed rather than immediate.

### The policy is the application's, and its default is not part of the contract

What happens when a lane wedges is a **policy the application can override**. The frozen parts are the
guarantee above and the seam that takes the policy; **the default itself is deliberately not contractual**, so
it can improve without a break, provided every default upholds all three parts. Today the default is to report
and then exit. The target is a fallback on the main thread that offers the user a *restart* of the GUI.

The seam is built for that target now, because it is the frozen part. An application supplies a policy the way
it supplies a look or an input backend, by a provider handed back to `Shell`. The policy is told what happened
(which lane, which components, for how long, and whether it was a lane, a component or the main thread) and
acts by calling methods on a context object it is handed (`exit`, later `restart`, later others). It does not
return a choice from an enum, so a new action is a new method and not a change for every existing policy.

## The extension test

The bar is "future releases add to the abstractions we have". So for every capability v1 leaves out, name
the seam it will use. If there isn't one, the seam is a v1 item and the capability is not.

| Left out of v1 | The seam it attaches to | Does the seam exist? |
| --- | --- | --- |
| Diagnostics, `--profile` | A starter plus `@ConditionalOnType`; `atchung-probe` | Yes. The module's *name* is undecided, and it is Maven coordinates |
| Framework SDF vocabulary | A `@Provides` and a placed component | Only once the component model is final |
| elektro-Q, cross-process actors | The channel declaration | **Partly.** `@Subscribe` and `@Publishes` exist on a component; a `@Subscribe` off a component, which this needs, is not built |
| Simulation vocabulary | `FrameStage.APP`, and a placed component | Partly. `APP` is an empty slot by design |
| Reactive automation, macros, extensions | The channel declaration | **Partly.** The same seam, and the same missing half |
| A second automation socket | `Driver.open(shell, gui, controls, offset)` | Yes; additive |
| Supervision and recovery | The application-supplied policy and the context it acts through | Yes: `LivenessPolicy`, handed back like the look, acting through a context with `exit` and `interrupt`. Actions (`restart`) and the default are additive |
| A GUI fallback that offers a restart | The same policy seam, plus the main thread, which is not a lane | Yes, if the seam ships; covers a wedged lane and never a wedged main thread |
| A BOM, more starters | `@Default` and `@VexelApp` naming | One starter exists; a second is the test of the shape |
| Linux and macOS native builds | The per-OS backend modules and the metadata each jar carries, and a facade in front of every native extension | Only if metadata lives in the backend jars. Metadata in an app-local file would make each new OS an edit to every app |

Two rows pass only in part, and on the same seam: a `@Subscribe` that is not on a component. That is why it sits ahead of everything else.

## What must not remain

**Legacy is anything kept only because something used to depend on it, or because removing it would break
someone.** A feature that earns its place on its own terms is not legacy, however old. The test is: *would we
write this, in this form, if we were starting today with the v1 abstractions?* If the honest answer is "no,
but removing it breaks a caller", it is a cleanup to do before v1, when a breaking change costs nothing and
the callers are the author's own.

What that rules out, concretely:

- **Compatibility shims.** A prior constructor, `Config` form or overload retained so old callers still
  compile. The engine refactor kept every prior constructor deliberately, which was right for a stack that
  had to keep building, and is exactly what v1 does not carry.
- **Two ways to do one thing.** A superseded seam left beside its replacement, or a redundant call the
  framework still makes. One name per idea.
- **Reserved names nothing honours.** A flag, key or accessor that parses and does nothing. It is either
  built by v1 or deleted, since a frozen no-op is a promise nobody can keep.
- **Test seams in the public surface.** An overload that exists so a test can reach it, on a type an
  application also sees.
- **Dead API.** A public member with no caller outside its own tests. Frozen, it is a permanent cost and a
  permanent question for the next reader.
- **Prose that describes a past.** A doc, Javadoc or comment that still says how something used to work, or
  qualifies a rule with *for now*, *until*, or *deliberately not yet*. A stale description is a second
  spec, and it will disagree with the first.
- **A deprecation.** `@Deprecated` and anything like it. v1 does not ship with a way out already open; it
  removes the thing.

The rule binds the framework and every sibling whose types an application holds through `Shell`, because
the freeze covers them (see the seventh surface above). Deleting something is a breaking change, and before v1
it is allowed. After v1 it is not, so this is the moment.

Greppable markers to start from, none of them conclusive: `legacy`, `@Deprecated`, `kept for`, `backward`,
`for now`, `until`, `deliberately not`, `reserved`. A hit is a question, not a finding.

## How to get there

In this order, because each step decides what the next bakes in:

1. **Finish the component model.** The channel declaration, the colour rule's copier and capture halves,
   the message-graph checks, supervision, and the completion path for work that outlasts a frame. It
   reshapes how every component is written, and it is where new compile errors come from.
2. **Decide the semantics nobody has argued.** Exact-type matching, `null` providers, which defaults are
   replaceable, whether a new `Phase` is ever additive, and how a new processor check is allowed to land.
3. **Settle the boundary with the siblings.** What `Shell` exposes of `vexelray-gui`, and the upstream bus
   that admits one tree. Then the composition root: `GuiApp` and `VexelEngine`.
4. **Add witnesses, and spike the extension test.** Two or three generated applications with deliberately different shapes: several
   windows, component-heavy, long-running with nothing to settle. Each is expected to find something. What
   they find is written up, as the ports were.
5. **The build shape.** A second starter, the BOM, and aggregated reachability metadata, once the
   abstractions they package have stopped moving.
6. **Sweep legacy.** Go through the framework and the sibling types `Shell` exposes, and delete what the section
   above rules out. Do it before the freeze pass, because the pass would otherwise freeze it, and after the
   component model, because the model is what makes some current seams redundant.
7. **Freeze.** Walk the whole public surface. Anything that would be renamed or restructured today is
   fixed first. Anything not yet frozen is marked. Then cut 1.0.

Until then releases are 0.x, and 0.x says what it means: the shape may still move.

## How future-proofness is measured

It can only be *confirmed* in retrospect: a later release either added to the abstractions or it did not. What
can be measured beforehand is failure, and the method is to try to cause it:

- **Spike each row of the extension test.** Write a throwaway version of the anticipated feature (a second
  window, a Linux backend, `@Subscribe`, supervision) against the API as it stands, and record how many
  existing signatures it forces to change. The target is zero, and the count goes in the entry. A pass proves
  only that one known future fits; a failure is proof the shape is wrong, and it is found while a breaking
  change is still free.
- **Witnesses are out-of-sample tests.** An application shaped differently from the one the API was drawn from
  finds what the first could not.
- **Read the surface's own history.** Days since an app-facing removal, and whether behaviour changed under an
  unchanged signature. Retrospective, and real.
- **Prefer a small surface.** Unshipped API cannot break. A ratio of exposed to used is worth reading.
  Prefer concepts with one obvious home for a future feature.

What none of it reaches is the future nobody imagined. v1 is a bet, and no-legacy means a lost bet is a v2
that breaks cleanly, so the way to lower the stake is to ship less and shape it well.

## How the surface is marked

The version is `0.1.0-SNAPSHOT`, and each public type in `-api`, `-core`, `-shell` and `-automation` carries
`@Stability(FROZEN)` or `@Stability(EXPERIMENTAL)`, so a reader can tell without asking. It is `CLASS`
retention and nothing reads it: the processor does not check it, because a check would be a new compile
error. Every type is `EXPERIMENTAL` today. The freeze pass is what changes one to `FROZEN`, and only when
the five conditions above hold. The wire protocol is graded ready in [status.html](status.html#v1-ledger),
but it is a text protocol and not a type; `Driver` and `AutomationStarter` are its Java side and stay
`EXPERIMENTAL` until the pass. A new public type is added with a mark. A public type with none has not been
looked at, which is a different thing from either.

## What v1 is not

Not feature-complete. Rendering vocabulary, the game-engine half, cross-process actors and the diagnostics
module can all arrive after v1, each behind a seam the extension test names. It is also not a claim that
the design is right, only that we will stop changing it under people.
