Migrating existing learner answers

The words learners have already written are the one thing here that cannot be regenerated. Every authored word is in git and a GitSync restores it; a learner's answer exists in exactly one place. So the migration onto data-bound exercises is staged: this page is the analysis and the plan. Nothing described here writes or deletes anything yet.

What is actually there

Before the authored/learner split, a writable exercise was one node in a per-learner copy of the course, holding both halves at once:

// {viewer}/AgenticPrimer/01-TheMagicWish/Exercise/ThreeWishesBook
{ "$type": "WishBookContent",
  "title":   "Your first wish",          // authored
  "intro":   "Write your answers here.", // authored
  "prompts": ["…", "…", "…"],            // authored
  "answer1": "a robot that tidies my room",   // the learner's
  "answer2": "…", "answer3": "…"              // …up to answer6
}

WishBook is a course-local NodeType (AgenticPrimer/WishBook, AgenticPrimerDe/WishBook), not an Edu/ one — so the shape lives in the course repos, and only the central definitions are visible from outside a learner's space. Measured on memex.meshweaver.cloud, 2026-09-02: six WishBook nodes, three in AgenticPrimer and three in AgenticPrimerDe. An install of a course copies its whole subtree (AdvancedBusinessRules: 126 nodes), so every learner of those courses holds their own copy of each of those six.

🚨 The finding that decides the design: nobody can survey this from outside

A legacy copy lives in the learner's own partition. A global admin holds no data access anywhere on any mesh — Admin/_Access gates platform actions and grants zero read on any partition's content — so there is no vantage point from which an operator can count the copies, read the answers, verify a harvest, or check afterwards that nothing was lost. Attempting the survey as system would mean reading every learner's private work to plan a migration, which is not a thing to do casually and is not necessary.

So the migration is not a sweep. It is the learner's own act, on their own render turn. When a learner opens an exercise that has moved to the new shape, the page harvests their copy into their sheet, in their own circuit, under their own identity. No cross-partition read, no admin pass, no list of learners to work through, and no window in which somebody else's work is in flight.

That also means the honest answer to "how many learners are affected?" is: not knowable from outside, by design. What is knowable is which courses carry the legacy shape (the six nodes above), and that is the unit the rollout is planned in anyway.

The plan, as code

AnswerHarvest.Plan(viewerHome, copyPath, copyContent, existingSheet) returns a HarvestPlan — what would move, what would not, and why. It is pure: content in, plan out, no hub and no clock, so every rule below is a unit test rather than something a reviewer has to take on trust. Holding a plan has no effect on anything.

Three properties make it safe to run, and to run again:

  1. Additive — nothing is deleted. The plan contains writes and nothing else; the legacy copy is never touched. The inverse of the whole migration is therefore "delete the sheet", with the original still sitting where it always was. Retiring the old copies is a separate decision, taken once somebody has confirmed the new side reads correctly — never a step of the harvest.
  2. The sheet always wins. An answer the sheet already holds is left alone. The sheet is the NEW store, so what is on it is at least as recent as the copy; overwriting would destroy an answer the learner wrote after their course moved. HarvestPlan.MergeInto restates the same rule where the write is shaped, so a caller cannot turn a plan into an overwrite by accident.
  3. Authored text is never carried. title, intro and prompts are the author's words. Copying them into the learner's partition would recreate exactly the mixture the new model exists to end, and leave a second copy of the prompts to go stale at the next authoring change.

Two smaller rules follow the same instinct: a blank answer is not work (carrying it would make an untouched question look answered), and a member the legacy shape does not know is reported rather than dropped — a course whose books differ must show up as a line to read, not as a learner who apparently never wrote anything.

The key convention — no mapping table

A legacy answer is stored under answerN; a workbook field's key is the pointer answers/{key} on the sheet. So a WishBook converted to an Edu/Workbook keeps answer1…answerN as its field keys, and the harvest is a rename-free copy. There is no per-exercise mapping to author, to get wrong, or to keep in step with the questions.

A course that would rather rename its keys may — but its learners' answers then land under the old ones, and it must move them itself. Describe(plan) names every key it carries, so that is visible rather than assumed.

What execution will look like

Its own change, after a course actually adopts the new shape:

What is deliberately NOT planned here

Reconnecting…
The server was updated. Reloading the page to pick up the latest version.