Sale Prep Transition

Deer Valley sale-preparation transition Dated plan editing API (September 30, 2026)

Executive Summary

Deer Valley sale-preparation transition Dated plan editing API (September 30, 2026) Existing task display fields can now be edited by Owner through POST /api/service-requests/admin/sale-prep, action plan-task, with current revision, unique eventId, existing task id, nonempty field patch and a reason in note. Saved state.planOverrides take precedence over bundled fields in both sale-prep and command-display responses; source edits alone do not replace saved overrides.

Deer Valley sale-preparation transition

Dated plan editing API (September 30, 2026)

Existing task display fields can now be edited by Owner through POST
/api/service-requests/admin/sale-prep
, action plan-task, with current revision,
unique eventId, existing task id, nonempty field patch and a reason in note.
Saved state.planOverrides take precedence over bundled fields in both sale-prep
and command-display responses; source edits alone do not replace saved overrides.
plan-task-reset removes a task override with the same revision/reason contract.
Both actions retain before/after audit events and reject achieved-state edits
until Owner reopens the transition. They do not check off tasks or alter targets.
See the canonical README's Dashboard modification API for the exact allowlist.
Reconcile operations briefs and schedule separately; API calls are not a single
cross-document transaction. New task identities/dates still require source edits.

Implemented September 12, 2026 in the existing Services Desk. Canonical dates:
start September 12; early edge September 25; nominal target September 30;
absolute outer edge October 12. The envelope is −5 / +12 calendar days around
September 30, with exactly 30 days from start to outer edge. Chicago local days
drive every phase/countdown. October is contingency/error budget, not planned
extra working time. relative-date-standard.md remains applicable.

Components and state

- services-platform/src/sale-prep-config.json: canonical dates, 19 dated tasks,
13 target conditions, impact ordering and acceptable contingency reasons.
- src/sale-prep.js and migration 0022_sale_prep_transition.sql: authenticated
owner/operator state, optimistic revisions, retained event log, communications
audit and confirmed announcement receipt. Staff/preview guests receive 403.
- www/services/desk/sale-prep-model.js: pure calendar state machine.
- sale-prep-card.js / sale-prep.css: first card inside the Command brief
content scroller, expandable plan/checklist/feed, full-size desktop timeline
and independently readable narrow-screen timeline. Existing tools remain.
- bin/sale_prep_presence.py: bounded local tracker evidence exported through
the existing signed service-request bridge. No frames, identities or track
boxes are sent through the Worker. The existing screen mask stays in force.
- presence-attention.js: browser Web Audio and synchronized attention popup.

Readiness is the percentage of explicitly checked target conditions, not an
invented estimate of household volume removed. Tasks and target conditions are
separate: checking a dated task does not assert that the entire house is ready.
Only an owner can record SALE_READY, after all conditions are verified and a
walkthrough note is entered. The actual timestamp is generated on the server;
reopening retains it in history. Time alone never marks completion.

Priority updates and verification

The banner's Today's priority is derived by sale-prep-model.js from the
dated tasks in services-platform/src/sale-prep-config.json. It does not read
operations.brief.headline. The Owner brief, sanitized operations.staffBrief,
operating schedule, and dated sale-preparation task are separate records. When
an instruction changes a priority shared by these views, reconcile the affected
records; preserve historical entries and explicit completion states.

Owner and Operator may change total progress in Plan & updates with a percentage
and reason. The total-progress action saves a revision-bound audit event and a
SQLite schedule override, rebasing automatic increases from the new value while
retaining milestone percentages and dates. At or above the target, or after its
date, the corrected value grows exponentially toward the final target without a
replacement intermediate milestone. A zero correction after the milestone uses
a normalized exponential curve that can rise from zero. Editing after
the completion date requires schedule replanning. Command displays read the same
override; checklist completion and labor records remain independent.
The form previews the effect before saving and preserves drafts across live
refreshes. Drafts retain their original revision; stale writes require reviewing
the saved total with the explicit refresh button. A future one-time hold and the
recurring daily window survive ordinary percentage edits.
For a timed hold, supply resumeAt with the current total to total-progress.
The future baseline holds the value through that timestamp, then rises continuously
to the unchanged milestone, followed by the existing exponential final stretch.
Verify immediately before/at/after resume and at both targets. No scheduler job is needed.

For carryover, update the next date's task and schedule focus, not just its
title. Keep displaced work pending without inventing a new date. A reported
work session ending around a time can be recorded as a concluded session with
an approximate time; it does not complete the remaining task or establish exact
billable hours. Keep tomorrow's start unconfirmed unless supplied.

For a people-centered daily goal, name the primary collaborators and starting
location explicitly, while keeping visitors who are not participating in the
work out of the primary collaborator list. Preserve reported origins and family
relationships as context rather than turning them into staff roles. Use neutral
decision-making language and retain Lee's agency.

src/sale-prep-staffing.json separately supplies the staffing summary. A current
staffing or payment line beside an old priority is evidence of partial propagation,
not proof that the entire display is cached. Card approval, bank settlement,
invoice allocation, signatures, labor hours and completed work remain separate facts.

A dated task may carry an owner-reported startTime, dailyCompletionPercent,
staffingSummary and paymentSummary. Daily completion is shown as progress on
that date's focus and must remain distinct from overall verified sale readiness.
The compact staffing/payment block should summarize the current work session and
the authoritative payment facts once, while leaving scenario comparisons and
labor-record detail behind Plan & updates.

An approximate owner estimate uses dailyCompletionStatus: "approximate owner estimate"
alongside dailyCompletionPercent; the progress label explicitly identifies the
estimate. Mirror the value and status on the dated operating schedule. Keep
verified criteria and labor records independent. Household records now use the
local SQLite authority described at the top of the platform README; reload
sol37-household-local.service after bundled configuration changes and verify
the public forwarding route as well as local storage.

Run scripts/verify_command_brief_scroll.cjs from services-platform to check
the visible priority as well as scrolling and navigation. Check the dated plan
detail and the corresponding brief, not only the API revision or presence of
the card. Both dashboard stylesheets must retain two rows: navigation and the
scrolling content area. On phones the document scrolls normally.

For the September 24 priority/payment reconciliation, also run
node scripts/verify_priority_payments.cjs. It checks the actual banner, staff
brief, dated schedule, two distinct receipt identities, payment total, source
details, private-care exclusion and responsive views. Original payment receipts
and invoice-issued notices must be reconciled by invoice/payment identity;
an invoice notice and its paid notice are one payment, not two. A private
communications change must reach the sanitized dated plan as well as the Owner
brief. Preserve screenshot draft state when a later sent message is found.

For a reported TV discrepancy, inspect a fresh frame from that receiver's actual
feed. The Record Player source is ~/.local/state/sol37/services-desk-record-player-hls/;
the general fleet uses services-desk-screen-fleet-hls/. Prefer the running
HTTP playlist. Local playlists can contain absolute URL paths: for a local
read-only capture, resolve the newest listed segment's basename inside its HLS
directory, then decode a frame. Check segment freshness and the visible text.
Keep captures private because they may contain household data and access QR codes.
Stream-source verification establishes what is being transmitted; distinguish
it from physical receiver or speaker verification. Do not restart or broadcast
merely to inspect a working feed.

After a fleet restart, a stored buffering label can precede actual playback.
Triple-check a disputed receiver with a fresh playlist mtime, the current stream
frame, and several direct Cast status samples. Treat it as playing only when the
receiver owns the fleet HLS content and current_time advances. Home Assistant
may retain an old unavailable entity while direct Cast status is current;
report that disagreement rather than selecting the convenient state.

Presence cue and display capabilities

The nominal countdown displays calendar days beside total remaining hours.
Hours round up to the next whole hour and count to midnight after September 30
in America/Chicago. They update without a page reload, including hourly and
local-midnight boundaries. Calendar days and exact elapsed hours are separate
measures; the hourly calculation handles daylight-saving offsets.

The timeline is an elapsed-time display, independent of verified readiness.
Its arrow advances by one local-clock minute and the past portion is grey;
future clearing, target-band and contingency colors remain visible. The NOW
label shows the local minute and briefly pulses on a tick because one minute
is only about 0.03 display pixels on the month-long scale. September 30 remains
the nominal marker and October 12 the outer bound. Mobile has an equivalent
elapsed-time strip.

Enabled native browsers play one brief clock tick per minute. Record Player
draws the arrow/grey segment and encodes the tick on the same A/V frame clock;
it does not wait for the periodic dashboard screenshot to move the arrow.
Minute ticks obey the existing sound enable state, but neither use nor reset
the 10-second presence cooldown. Reloads and missed minutes do not replay ticks.
Living Room's existing video-only feed remains silent. Its sampled visual
refresh follows the fleet capture cadence rather than the Record Player clock.
Run node scripts/verify_minute_timeline.cjs to verify minute stepping, grey
growth, one audio event, no catch-up and unchanged presence cooldown.

The current usable camera is SOL Vision overlooking the Record Player area.
Two fresh samples of repeated scored body evidence trigger person presence.
Fallback motion requires a persistent, localized moving track and meaningful
travel; tiny noise, stationary changes, broad-frame light changes and the
existing masked TV region do not qualify. The cue boundary also rejects tracks
overlapping the screen polygon and its five-pixel margin by more than 35%.
The Record Player checks the lightweight camera snapshot about every 150 ms;
qualifying motion needs at least three tracker hits, 12 pixels of travel and
two fresh qualifying samples. No facial recognition is used.

Browser audio requires enabling the sound toggle on that browser. One successful
sound starts a 10-second localStorage cooldown, shared across tabs with Web Locks
where available. A new presence episode requires 10 seconds of quiet first;
continued presence cannot retrigger. Reconnects preserve the cooldown.
Quiet-episode eligibility also persists across reconnects. Stale/unavailable
camera samples never count as proven quiet.

The floating attention popup was removed on September 13 at David’s request.
Each eligible chime is paired with one full-screen pale-teal flash, fading from
25% opacity to transparent over 0.6 seconds, without repeated pulses. Browser
flash begins only when audio starts; Record Player encodes flash/audio in the
same frame. Both use attention-timing.json: 10-second cooldown and rearming,
with QR quiet time independently fixed at 600 seconds. Existing saved sound
deadlines migrate from their original event time, without restarting the clock.
Activity cannot extend the fixed sound cooldown or repeatedly trigger while
presence continues. After cooldown and a new quiet-qualified arrival, flash
and sound occur together. No persistent visual overlay remains.
The primary objective card always stays visible.
Record Player also restores only the guest QR floater, refreshed from the
existing scoped guest credential. Meaningful room presence hides it immediately;
it reappears after 600 seconds without activity. Further activity restarts that
QR timer, which persists across reconnects. Camera-screen motion remains masked.
If the camera becomes unavailable while QR is hidden, the quiet timer pauses
conservatively until fresh samples resume; camera failure alone does not hide an
already visible QR. This rolling QR timer is distinct from the fixed sound
cooldown; the QR uses qrQuietSeconds: 600, not the sound’s 10-second setting.
The Vision floater stays hidden. QR reappearance does not play a separate sound.
The QR follows a reflected lower-left path at 24 pixels/second horizontally and
10 vertically. Record Player HLS publishes completed files by atomic rename,
retains ten unreferenced segments for delayed requests, and waits for four
complete two-second segments before starting the receiver. Keep the continuous
encoder running between ordinary dashboard/QR image updates.

Logged cue events contain a
timestamp, device, person/motion trigger, played/suppressed flags, and an explicit
test flag for synthetic self-tests. No per-frame event log is written.

The Mac native dashboard supports Web Audio; a labeled live self-test verified
engine playback, synchronized popup appearance and persisted cooldown. This is
not a listener confirmation of physical audibility. Record Player now receives
a dedicated H.264/AAC variant managed by the same fleet and HTTP server, from
bin/sale_prep_record_player.py. A shared 5 Hz clock places popup reveal one
frame before the brief chime. Living Room retains the video-only stream; no
separate room camera was found for it. No other screen inherits this audio.

Record Player cooldown and enabled state persist in the private fleet state
directory as services-desk-record-player-attention.json. Useful events go to
the matching -events.jsonl; audioEncoded establishes A/V generation, not
physical audibility. Dashboard event publication preserves that distinction.
The owner confirmed the labeled diagnostic chime was audible on September 12.
Its 0.15 peak oscillator gain and brief envelope now also drive the automatic
Record Player cue. The Cast/Android source volume was set to 0.60; Samsung
speaker volume was read as 25 and unmuted. These are independent device scales.
The -status.json file is a replace-in-place health snapshot, not a frame log.
An explicit local -command.json command {id, createdAt, action: "fade-test"}
now arms one real camera arrival to trigger sound/flash without a popup preview.
It permits that test arrival to bypass the current cooldown. Never issue this
test automatically. Enable/disable uses
action: "enable", enabled: true|false. Native browsers retain their own toggle.

The attention bridge reads the existing lightweight camera-caption endpoint
over loopback, avoiding slow whole-dashboard probes. The tracker excludes its
adaptive screen polygon before motion extraction and filters overlapping body
detections. Its tracked/calibrated fallback continues masking when confidence
is low. Neither screen animations nor identities should become attention cues.

The camera tracker also uses a scene reference configured privately in
~/.local/state/sol-vision-camera/attention-reference.json. Its verified screen
polygon anchors adaptive detection; candidates with less than 60% overlap are
rejected. The reference polygon remains included in the motion/person mask.
Motion must differ from the reference after compensating for global exposure,
as well as passing the existing shadow-aware background subtraction.

After 60 seconds with no motion or body detections, it atomically replaces one
private attention-reference-current.png and advances the reference timestamp.
Large unexplained changes affecting over 8% of the non-screen area pause refresh
to avoid learning an undetected stationary person into the background. It never
archives each frame or lets TV animation advance the quiet timer. Inspect live
scene_reference_active, scene_reference_updated_at,
scene_reference_quiet_since, and scene_reference_refresh_error in the local
camera snapshot. Recheck the physical screen polygon after camera movement.

Communications and announcement

scripts/sale_prep_communications.py makes a bounded, read-only 72-hour audit of
the configured Solmail index, live Mac Messages, household Discord through
Masterbot, and owner dashboard APIs. Run a fresh Solmail sync before the audit;
index reads alone do not prove IMAP freshness. Raw audit candidates remain in
the private mode-0700 SOL evidence directory and are manually screened before
publish_sale_prep_audit.py posts source-qualified summaries. Date-only sources
remain date-only. No appointment/financial/legal outcome is inferred from silence.
Keep the audit term set aligned with the day's named collaborators and visitors;
otherwise a fresh channel check can still omit the relevant conversation. A
presence text supports “reported present,” not arrival time, consent, work start,
or participation. Reuse an existing invoice follow-up instead of creating a
duplicate from an automated mail-watch notification.

The September 12 audit published seller-disclosure, hauling/provider conflict,
receipt/scope, tax-account reconciliation and September 17 schedule-conflict
follow-ups. No new confirmed buyer terms or inspection appointment were found.
Lee's separate phone is not part of the reliable connected feed and was not
claimed checked. Care notification email is covered through Solmail; this audit
does not inspect logged-in Care pages.

scripts/sale_prep_announcement.py posted the explicitly authorized announcement
through the existing Masterbot identity to Deer Valley / public-share-board.
It deduplicates/reconciles uncertain sends and retrieves the exact posted content
before recording success. Delivery receipt lives in the private dashboard state.

Verification

Private announcement confirmations

services-platform/scripts/sale_prep_reactions.py polls only the existing
share-board announcement for the two user-confirmed household accounts.
Initial private requests use --request; scheduled checks omit that flag.
The thread heartbeat checks every five minutes while retaining date audits
once per half-day. Each observed thumbs-up queues an independent Masterbot DM
acknowledgment and canonical-self-chat BlueBubbles notification. Removal and
re-addition do not duplicate acknowledgments. Ambiguous sends are reconciled
through Discord readback or Mac Messages receipts, never blindly resent.
The private ledger is ~/.local/state/sol37/sale-prep-confirmations/state.json.
Completion requires both Discord acknowledgments and BlueBubbles delivery;
then the heartbeat returns to the twice-daily date audit. Polling records
the detection time, not an invented exact reaction time.

September 12 outreach: Thomas's DM was verified. Stef's DM was rejected by
Discord HTTP 403 / code 50278: "Cannot send messages to this user due to having
no mutual guilds", despite a successful guild-member readback. Do not claim
it was delivered. Monitoring includes both accounts; private delivery to Stef
remains unresolved. Test with python3 -m unittest discover -s test -p
test_sale_prep_reactions.py
(no external sends).

From services-platform: npm test, python3 test/test_sale_prep_presence.py,
node scripts/verify_sale_prep.cjs, node scripts/verify_presence_cue.cjs, and
node scripts/audit_relative_dates.cjs. UI probes are read-only. The Mac self-test
script plays one real test cue and starts cooldown; do not run it repeatedly.

Owner/operator access and guest/unauthenticated denial were checked live. The
server rejected an attempted SALE_READY transition with unverified conditions.
Desktop/TV-size, tablet and phone renderings were inspected. The optical camera
confirms screen content, but its oblique low-resolution view cannot establish
across-room text legibility. Use a human distance check before claiming that.

Private evidence: /mnt/sol-data/sol-stack/sale-prep-20260912/. Do not expose its
mail/message snapshots, protected screenshots or D1 exports through public links.

Total progress schedule — September 27, 2026

The owner requested that the main progress label become “verified total” and
use 37% as the total baseline. The adjacent note identifies automatic increases
as scheduled progress, not new walkthrough evidence. Configuration lives in
services-platform/src/sale-prep-config.json under totalProgressSchedule.
The saved owner baseline rises to 73% linearly through October 1, 10 PM Chicago,
then 73 → 100% exponentially through October 31, midnight Chicago. Both curves
count only 5–10 PM Central active hours; every 10 PM–5 PM interval is flat.
The final active window ends October 30 at 10 PM, so 100% is held from then
through the midnight Halloween target. Display values
are floored; the existing render timer and tab-resume listeners update them.
The October milestone assumes the arrived container is filled and most clutter
cleared, barring emergencies. An explicit pausedAt timestamp freezes the curve;
clear it to catch up or revise the dates to replan. Restart the local household
service after configuration changes. Tasks, checklist verification, and the
recorded sale-ready transition remain independent.