37 tasks · 24 done · 6 files · 2026-09-20

Advisory Contract — Staleness Surfacing and the Last Unmapped Answers

Code complete. Two answers are mapped and waiting on one rebuild to reach Mira, and approving a contract now refreshes the export file that every Claude Code session reads.

2Answers to fold in
24/37Done
2Unmapped ids
2Bugs found
The gist

Section H of the elicitation instrument already asks who produces the first version, when words stop being enough, and what should arrive rough. It shipped on 6 September 2026, along with the contract field, the draft mapping, the prompt injection and the render. This plan does not re-add any of it.

What is missing is any signal that it exists. The Advisory Contract governing every AI surface today is the one approved on 22 July — before section H, and before the mapping fix meant to carry the re-igniter answer to Mira. A builder fix is inert until a draft is rebuilt, and nothing says so.

The problem

The requested work is built

Every question in the note maps to a shipped question, near-verbatim: h1 who produces the first version and whether that changes by stage; h2 when words stop being enough and what has to be seen or played with; h3 what arrives finished versus rough. AdvisoryContract.collaborationProtocol holds them, ContractDraftBuilder quotes them verbatim, DossierContextBuilder.compactContext() injects them ahead of the prohibitions, and the Dossier renders them as a card. The prior plan is .claude/plans/dossier-collaboration-protocol.md, implementation complete, manual testing entirely unchecked.

What the stored data says

Read out of dossier.json, last written 24 July 2026:

31 answers stored, ids a1–g2. g3, h1, h2 and h3 are all unanswered. The live contract was approved 2026-07-22 and has no collaborationProtocol key at all. The b4 re-igniter line is not in objectiveFunction — the mapping fix landed 6 September, but a mapping only takes effect when a draft is rebuilt, and no rebuild has happened. Your suspicion in the note is right at the data level even though the code was corrected: it still has not reached Mira.

Live gap

Two answers still fall on the floor

ContractDraftBuilder's header promises that "everything else in the instrument reaches a field." That is false for two ids, neither listed as deliberately unmapped.

g3 — "Is there anything about how you work that every AI keeps getting wrong?" — is the catch-all your note names as the question that was supposed to catch all of this. It is unmapped. Had it been answered in July, the answer would have gone nowhere.

a1 — the count of live endeavors and whether it is deliberate — is not merely recall. Whether six concurrent projects are a choice or a drift governs whether launch-risk advice may suggest consolidation, the most-guarded territory in the contract.

Approach

Rebuild-and-diff rather than version counters

Half the detector exists: DossierStore.openQuestionIds already computes unanswered and unsure-skipped questions, and is referenced by no view in the project. Rendering it is the cheap half.

The expensive half is the b4 case, where the question is answered and the mapping changed underneath it. A stored instrument version and a builder-revision int would work and would drift. Asking buildDraft(from:existing:) what it would add is a pure function of current code plus stored answers, with no bookkeeping to keep in sync — and it reuses the same signature comparison, so detection and assembly cannot disagree.

A coverage test, not a comment

The deliberately-unmapped list is prose in a header comment, which is exactly how a1 and g3 slipped out of it. Making it a Set<String> and asserting round-trip coverage from synthetic answers turns the promise into something that fails a build when it stops being true.

Decisions made for you

Judgment call

This plan is smaller than what was asked for

The note asks for three questions to be added. They exist. Writing them again would create a second plan for work already checked off, so this plan fixes the reason you could not tell. Reversible by saying so — if the shipped wording is wrong, rewording is a different and much smaller task.

Judgment call

g3 routed to the collaboration protocol

"What every AI keeps getting wrong" is explicitly about how you work, which puts it beside h1–h3 rather than in the framing rules. It could equally be about delivery, in which case it lands oddly. Cheaper than a field of its own, and visible on the review screen where you can move the text before approving.

Judgment call

Staleness stays in-view and does not reach the Workbench

A background-activity row would catch you without opening the profile. It would also re-raise itself, which is the framing-rule violation the drift-alert design already forbids. Chose the quiet failure over the noisy one. Reversible with one WorkbenchStore.register call.

The work

Phase 1 — Close the last two silent drops

9 tasks · done Lands independently; nothing else depends on it.
  • Map g3 into collaborationProtocol in ContractDraftBuilder, quoted verbatim under the label "What AI tools keep getting wrong about how I work"
  • Map a1 into objectiveFunction as a concurrency stance line combining the count and the deliberate/accident/managing select, so advice knows whether the portfolio shape is a choice
  • Correct ContractDraftBuilder's header comment — its deliberately-unmapped list currently omits both ids while the prose claims full coverage
  • Add a deliberatelyUnmapped: Set<String> constant on ContractDraftBuilder so the list is code rather than a comment that can go stale
  • Add a sentinel round-trip coverage test: build a draft from synthetic answers carrying a unique marker per question, assert every marker reaches the contract unless its id is in deliberatelyUnmapped
  • Handle the formats whose text the builder never reads (.rank, .allocate, .countThenSelect) in that test with a format-appropriate payload marker rather than exempting them
  • Run the coverage test through the standalone swiftc harness in the scratchpad, not xcodebuild test
  • Install the harness at scripts/harnesses/contract-coverage/ with a scripts/verify-contract-coverage.sh runner, matching the instruction-manager harness convention
  • Fix ContractElicitationView rendering an empty review step: with every question answered the view opens at questions.count, and draft was only ever assigned by onChange(of: questionIndex), which never fires

Phase 2 — Make contract staleness visible

8 tasks · done The systemic fix. Half of it is already written and unrendered.
  • Add ContractDraftBuilder.pendingAdditions(from:existing:) -> [String], returning the lines a merge would add to the stored contract, reusing the existing signature/mergeText comparison so detection and assembly cannot disagree
  • Add a contractStaleness computed property to DossierStore returning the open question ids and the pending additions
  • Render a staleness advisory in DeveloperDossierView's Advisory Contract card when either list is non-empty, above the contract fields
  • State both facts plainly and separately — how many questions are unanswered, and how many stored answers are not yet reflected in the approved contract
  • Change the "Revisit the interview" button label to name the work when staleness exists (e.g. "Answer 4 open questions")
  • Keep delivery deterministic and templated — no AI call on this path, per the Dossier's drift-alert invariant
  • Keep it in-view only: do not register it with WorkbenchStore and do not raise a notification, so it cannot become a recurring nag
  • Confirm the advisory reads as neutral information rather than a prompt to catch up, against the contract's own framing rules

Phase 3 — Optional: structure the see-versus-play priority

3 tasks Recommended skip. h2 prose already carries the ordering.
  • Only if wanted: add h4 with a .rank format over user-agnostic forms — a picture or mockup, something clickable, a working slice, more wordsoptional
  • Only if wanted: word it so "more words" ranking first is an equally natural answer, per the instrument's fairness ruleoptional
  • Only if wanted: map h4 into collaborationProtocol beneath the h2 line and bump instrumentVersion to 1.6optional

Phase 4 — Export propagation (follow-up from the first checkin)

3 tasks · done Raised as an out-of-scope concern on the 21:49 checkin, then investigated.
  • Confirm whether a contract rebuild marks stored projection records stale — currentContentHash() hashes fullMarkdown(), which carries both the objective function and the collaboration protocol, so the rows already self-report and offer "Regenerate & Copy"
  • Call DossierClaudeCodeSync.writeIfEnabled() and DossierSharedExport.writeIfEnabled() from DossierStore.approveContract(), since both were reachable only from the Generate & Copy button in Exports
  • Survey the other deterministic builders for the same silent-drop shape and decide whether the coverage harness generalizes — it does not; ContractDraftBuilder is the only service that reads stored answers by hardcoded id

Verification

4 tasks · done Compile check only — runtime behavior is yours to confirm.
  • Build with xcodebuild -scheme "Code Raven" -destination 'platform=macOS' build
  • Confirm the product actually relinked with scripts/verify-build-product.sh
  • Confirm the coverage test fails when a mapping is deliberately removed, so the guard is real rather than vacuous
  • Confirm no existing stored answer or contract fails to decode after the changes

Manual Testing

10 tasks · yours This is the section that actually repairs Mira. No code change substitutes for it.
  • Relaunch the app — the running build predates these changes, so nothing below is visible until you doSarah
  • Open the Developer Profile and confirm the Advisory Contract card names 2 stored answers not yet reflected, listing the concurrency and g3 linesSarah
  • Press "Rebuild the contract" and confirm the review step renders rather than opening blankSarah
  • At the review step, confirm your existing contract text survives verbatim in every field you hand-editedSarah
  • Confirm the rebuilt draft adds the concurrency line to the Objective Function and the g3 line to the Collaboration Protocol — the b4, b1 and d5 lines already landed on 20 SeptemberSarah
  • Approve the revised contract and confirm the Collaboration Protocol card appears in the DossierSarah
  • Confirm the staleness advisory disappears once approvedSarah
  • Ask Mira something design-shaped and confirm she produces the first version without being asked toSarah
  • Ask Mira for something at the point where words stop being enough, and confirm she reaches for a visual before proseSarah
  • Run an expert meeting and confirm Mira's opening reflects the protocol rather than handing you a blank fieldSarah

Risks & edge cases

The core limit

This plan cannot fix the thing you actually noticed

Mira ignored your collaboration dynamic because the contract governing her prompts has no collaboration protocol in it, and no code change puts your words there. Only the Manual Testing section does. Phases 1 and 2 reduce the chance of a repeat; the repair itself is four answers and an approval.

Edge case

The staleness advisory is a nag surface by construction

It appears every time the Advisory Contract card renders until the contract is re-approved. Correct for a two-item list that resolves in five minutes; wrong if the open list is ever long and you have decided not to answer it. If that happens the answer is a notApplicable skip, which openQuestionIds already excludes — no code change needed.

Edge case

Malcolm's seed predates section H

.claude/dossier-seeds/malcolm-elicitation-2026-07-20.md has no h1–h3, so his profile will show four open questions the first time it renders. Correct behavior, and worth knowing before it looks like a bug on the second install.

Limit

The coverage test is only as honest as its markers

A question whose answer is folded into a derived phrase rather than quoted could pass the marker check while still losing your words. The test proves an answer reached a field; it does not prove it arrived intact.

Cost

Rebuild-and-diff runs on every appearance of the profile view

Deterministic string work over ~31 answers, so trivial today. If the instrument grows enough for that to matter it wants caching on dossier.updatedAt, not a version counter.

Out of scope

Re-adding the section H questions — they exist, which is the point of this plan.

Re-running the full elicitation for anyone.

Inferring the collaboration protocol from session history rather than asking.

Any rewording of existing questions a1–g3.

VentureRaven's read side of the shared export, still unbuilt.

Testing how the protocol reads at the far end of injection, which remains the untested link in the whole chain.

Final review

What this does well

It refuses to build what already exists, and it names the actual failure: a contract stale for two months with no surface saying so. The two unmapped ids were found by checking every question id against the builder rather than trusting its header comment, and g3 being unmapped is a sharp finding given your note calls that question the catch-all that should have caught this. The rebuild-and-diff detector has no version bookkeeping to drift, and half of it is already written.

Where it is weakest

The g3 field routing is a guess. The coverage test proves arrival, not fidelity. And Phase 2 surfaces staleness only on a view you have to visit — if you do not open the Developer Profile, the contract stays stale and silent exactly as it did since July. A workbench row would fix that and would violate the re-nag rule; I chose the quiet failure over the noisy one, and that choice is arguable.

What I would adjust if pushed

Drop Phase 1's a1 mapping. It is the one change that puts new interpretive material — a stance about your portfolio shape — into the contract on my reading rather than yours, and the concurrency count is already visible to the portfolio synthesizers. Keeping it because launch-risk advice currently has no way to know the shape is deliberate, which is the exact territory the candor invariant guards.

84/100

The findings are read out of code and your stored data rather than inferred: the shipped section H, the July contract with no protocol key, the missing b4 line, the two unmapped ids, the unrendered openQuestionIds. Lower than the prior plan's 88 because the deliverable is smaller than the request, and because the part that actually repairs Mira's behavior is four answers only you can write.