Phase 1 Data Model: Safe Sync Daemon Orphan Cleanup

Mission: sync-daemon-orphan-cleanup-01KWC2A3

These are in-process domain objects and on-wire/structured payloads — there is no relational schema. Field names are normative for the contracts in contracts/.

Entity: DaemonIdentityRecord

The per-candidate identity produced by the scan for each inspected listener in the sync range. Satisfies FR-001.

FieldTypeSourceNotes
daemon_family"sync"constant for this engineDD-02; hard family tag (C-001/C-002)
pid`int \None`listener owner via psutil/lsof
portintrange scan [9400,9450)in-range invariant (NFR-001)
protocol_version`int \None`/api/health
package_version`str \None`/api/health
singleton_scope_id`str \None`cmdline daemon-root marker
daemon_root`str \None`resolved runtime root
queue_db_path`str \None`owner record
auth_scope`str \None`owner record
server_url`str \None`owner record
owner_presentboolowner record existedreporting only — not kill authority (FR-003)
identity_sourceenumhow identity was provenhealth_self_report \
executable_summary`str \None`_process_executable_scopes
spawn_shape_okbool_cmdline_has_daemon_spawn_signatureproduction spawn-shape present
self_report_matches_listenerboolhealth pid/port vs actualrequired for safe_auto (D-01)
is_recorded_singletonboolstate file pid/portlive daemon ⇒ never cleaned
cleanup_classCleanupClassclassifier verdictsee below
skip_reason`SkipReason \None`classifier

Invariants

  • port[9400, 9450) for every record the sync engine emits (out-of-range ⇒ never scanned/recorded).
  • cleanup_class == safe_autois_recorded_singleton is False and singleton_scope_id matches the foreground scope and self_report_matches_listener is True and spawn_shape_ok is True.
  • skip_reason is Nonecleanup_class == safe_auto.
  • owner_present never influences cleanup_class (FR-003).

Value object: CleanupClass (enum)

ValueMeaningActed on by
safe_autoProvably-ours, same-scope, responsive, not the singletonstartup auto-clean and --reset
operator_requiredLooks like SK sync but ambiguous (pre-marker, cross-root, missing pid, pid/port mismatch, or wedged)--reset with --force/confirmation (D-02); never startup
never_touchNot identifiable as SK sync / dashboard / third-party / out-of-rangenothing, ever (C-004)

Value object: SkipReason (enum)

is_recorded_singleton · pre_marker (no daemon-root marker) · cross_root (marker ≠ foreground scope) · missing_pid · pid_port_mismatch · unresponsive (wedged, D-01) · not_spec_kitty · out_of_range · dashboard_family · third_party.

Classification decision table (normative)

Evaluated top-to-bottom; first match wins.

#Conditioncleanup_classskip_reason
1port ∉ [9400,9450)never_touchout_of_range
2not identifiable as SK sync (no spawn-signature and no SK self-report)never_touchnot_spec_kitty / third_party
3is_recorded_singleton(excluded from cleanup; reported as live)is_recorded_singleton
4pid is Noneoperator_requiredmissing_pid
5no daemon-root markeroperator_requiredpre_marker
6marker ≠ foreground scopeoperator_requiredcross_root
7no live health self-report (wedged) — D-01operator_requiredunresponsive
8health pid/port ≠ listeneroperator_requiredpid_port_mismatch
9else (marker==scope, responsive, spawn-shape ok, not singleton; version/exe mismatch allowed as stale evidence — FR-008)safe_auto

> Note on FR-008: a non-matching package_version/executable_summary does not appear as a skip condition. Once rows 1–8 pass, version/executable mismatch is treated as stale-version evidence and the daemon is safe_auto.

Entity: ResetResult

Structured outcome of auth doctor --reset. Satisfies FR-005.

FieldTypePer-entry fields
sweptlistpid, port, package_version, protocol_version, cleanup_path (http_shutdown\
skippedlistpid (nullable), port, skip_reason, cleanup_class
failedlistpid (nullable), port, failure_reason
  • operator_required entries appear in skipped (with cleanup_class=operator_required) unless --force/confirmation was given, in which case successful ones move to swept and survivors to failed.
  • never_touch candidates are never listed in swept/failed; they may appear in the read-only auth doctor scan view but are out of --reset scope.

State transitions: sync daemon self-retirement (FR-010/FR-011)

        spawn
          │
          ▼
   ┌─────────────┐  becomes superseded (state-file pid/port ≠ self)
   │  ACTIVE     │──────────────┐
   │ (singleton) │              ▼
   └─────────────┘        ┌───────────────┐  no sync work in flight
          │ idle ≥        │  SUPERSEDED   │──────────────────────► RETIRED (exit)
          │ SYNC_DAEMON_  └───────────────┘
          │ IDLE_RETIRE-
          │ MENT_SECONDS
          ▼ (no auth / no work)
       RETIRED (exit)
  • SUPERSEDED → RETIRED is prompt (no full idle wait) once sync.is_running is false and the queue is drained.
  • ACTIVE → RETIRED (general idle) only after SYNC_DAEMON_IDLE_RETIREMENT_SECONDS of no auth/no work; constant is patchable in tests (FR-011).
  • A daemon with active sync work in flight never retires (FR-010 guard).

Externally visible surfaces (no new network events)

This mission changes local CLI output and loopback /api/health shape only — no SaaS/protocol changes (C-007). The /api/health payload gains daemon_family (and the existing redacted owner block is unchanged); see contracts/health-payload.md.