Disciplines · Proposals

YSD-19106 — Whether a mobile review companion and an offline note queue are justified

Phase 5 defers two capabilities behind one condition: build them only if learner research shows they solve a demonstrated need, and only then go beyond the policy-safe core.

8sections4 minread

On this page
  • Status: approved (2026-08-15)
  • Priority: EXT — non-blocking for the core, blocking for YSD-19107/19108
  • Decision owner: @GreyChimp
  • Drafted: 2026-08-15 by Claude Code (Opus 5)
  • Approval: ruled by @GreyChimp on 2026-08-15; outcome recorded in decision-log.json; review by 2027-08-15

Question#

Phase 5 defers two capabilities behind one condition: build them only if learner research shows they solve a demonstrated need, and only then go beyond the policy-safe core. Does the need exist, and are they to be built for the first release?

What the gate says today#

capabilityNeedVerdict over the recorded research (src/product/deferred-capability-research.ts) returns, for both:

capability verdict gate standing construction
mobile-review-companion unproven decided-after-build partially-built
offline-note-queue unproven decided-after-build built

Three facts in that table matter more than the ruling.

unproven is not not-justified. The gate's vocabulary separates "the evidence shows the core does the job" from "nothing demonstrates it yet, which is an absence rather than a refusal". The programme holds no learner research about either capability, so only the second is available, and this decision was recorded against the verdict the evidence earns rather than the stronger one it would be convenient to write. The distinction is asserted in deferred-capability-research.spec.ts, in both directions.

The absence is structural, not a backlog item. planesThatCanDemonstrateAwayFromWorkspaceNeed returns exactly one plane — consented-research. The need at issue is for work done away from the workspace, and every other observation plane can only see somebody who is in it: a learner who wanted to review on the train and therefore did not review emits no session, files no ticket and appears in no funnel. Product analytics cannot close the gap either, because YSD-17072 permits metrics to carry interaction-shape and record-shape and nothing else, so analytics can establish that work stopped and never why. Consented research needs a panel, and YSD-18002's recruitment has not happened.

Both gates fired after the build. The offline note queue shipped under YSD-15094 and YSD-17034; mobile-companion is one of the four rendering surfaces of the deep-link contract shipped under YSD-15092. The decision this gate exists to make was, for both, partly made by construction. That is recorded rather than smoothed over: gatesDecidedAfterBuild names them and assertGateStillOpen refuses to pretend the gate can authorize what has already shipped.

Recommendation#

Do not build beyond the policy-safe core in the first release, for both capabilities, and record the verdict as unproven rather than not-justified.

Ruling#

Approved as recommended by @GreyChimp on 2026-08-15: do not build beyond the policy-safe core in the first release, for both capabilities.

For the companion, the core already opens the web workspace in a phone browser under the same rights enforcement, and a companion would move rights-restricted material and a learner's private notes onto a personal device to serve a need nobody has observed. For the queue, the ruling governs what comes next; it does not retroactively authorize what is already here.

Recorded in code as DEFERRED_CAPABILITY_RULINGS, with the verdict and the construction state as they stood on the day of the ruling, so rulingStanding reports superseded-by-evidence the moment research justifies either one and construction-moved if the tree is built past the ruling. A ruling that cannot go stale is a sentence; this one reports its own supersession.

Options considered#

  • Ship the companion anyway — rejected: it crosses four policy surfaces to serve a need with no observation behind it, and "we already mint deep links for it" is a fact about a link contract, not about a learner.
  • Record not-justified — rejected: that verdict asserts the core does the job, and no evidence says so. Writing it would launder an absence into a finding, which is the specific failure this gate is built to catch.
  • Recruit for consented research first — deferred, not rejected: it is the only plane that could settle the question, and it waits on YSD-18002. The ruling's review date is what brings it back.

Consequences#

  • YSD-19107's obligation does not arise. It is conditional on a justified verdict; companionObligationArises returns false with the verdict and the reason attached, so the absence is a computed answer rather than an assumed one.
  • Two crossings on a shipped capability remain unresolved and are now visible. The offline note queue crosses learner-notes-on-personal-device and lost-device-revocation, and neither has a decision from the owner of those surfaces. The tree holds controls that bear on them — an offline bundle expires locally, a covering grant lapses, accepted records are lease-gated — and they are recorded in NEAREST_CONTROLS as controls, each with the sentence saying why it is not a permission. A time bound limits how long a lost device stays useful; it does not revoke anything on demand. These are rights and device-privacy decisions that no amount of demonstrated need would settle, and they are owed regardless of this ruling.
  • The first release ships the policy-safe core: the web workspace in a phone browser, portable study packages, and deep links.

Machine-enforced outcome#

The gate is asked about the programme rather than about a fixture: its inputs come from a recorded register instead of an array literal at each call site, and the owner's ruling is held against the live verdict and the live construction state every time it is read.

Control in force#

assertGateStillOpen throws for both capabilities, and that stays true after this ruling — approval decides what to build next, not that the gate was consulted in time. assertDeferredCapabilitiesJustified still throws, because neither verdict is justified and this decision does not make one so.