Data Model — Stability & Hygiene Hardening

Mission: stability-and-hygiene-hardening-2026-04-01KQ4ARB Date: 2026-04-26

This document captures the entities and state machines this mission touches. The mission introduces no new persistent entities; it tightens semantics on existing ones.

Mission

FieldTypeNotes
mission_idULID (str, 26 chars)Canonical identity; immutable
mid8str (8 chars)First 8 of mission_id; used in branch / worktree names
mission_slugstr (kebab-case)Human handle
mission_numberint \null
friendly_namestrDisplay title
mission_typestre.g. software-dev
target_branchstrAlways set; never hardcoded main in code
pr_boundboolNew optional field; default false; gates branch strategy (FR-033)
created_atISO 8601 strImmutable

Selector resolution order: mission_idmid8mission_slug. Ambiguous handles raise MISSION_AMBIGUOUS_SELECTOR. No silent fallback.

Work Package (WP)

The 9-lane state machine is unchanged; transitions are tightened.

planned -> claimed -> in_progress -> for_review -> in_review -> approved -> done
              |             |             |             |
              v             v             v             v
            blocked      blocked      blocked      blocked
              |             |             |             |
              v             v             v             v
          canceled      canceled      canceled      canceled
FieldTypeNotes
wp_idstr (e.g. WP01)Stable within a mission
laneenumOne of 9 above
execution_modeenumworktree (default) \
depends_onlist[str]WP IDs this WP needs before it can start
lane_assignmentstr \null
workspace_pathpath \null

Tightened transitions (this mission):

reviewer claims a WP (FR-016).

allocation that could fail (FR-014). On allocation failure, emit in_progress -> blocked with reason="worktree_alloc_failed".

  • for_review -> in_review (NOT for_review -> in_progress) when a
  • planned -> in_progress event MUST be emitted before any worktree

Status Event (append-only)

Lives at kitty-specs/<slug>/status.events.jsonl. One JSON object per line, sorted keys.

FieldRequiredNotes
event_idyesULID
atyesUTC ISO 8601
actoryese.g. claude, human:rob
feature_slugyesmission slug (legacy field name)
wp_idyes
from_laneyes
to_laneyes
execution_modeyesmatches WP's execution_mode
forceyes (bool)
reasonoptionalfree text
evidenceoptionaldict
review_refoptionalstr

Events are written via emit_status_transition() against the canonical mission repo (FR-013). The resolver in workspace/root_resolver.py ensures this even when the caller's CWD is a worktree.

Mission Brief (intake artifact)

Stored at .kittify/mission-brief.md with sidecar provenance YAML at .kittify/brief-source.yaml.

FieldRequiredNotes
source_fileyesSanitized path; comment-escape rules (FR-007)
source_sha256yeshex digest of the brief content
intake_rootyesAbsolute path used for scanning; same as write root (FR-012)
size_bytesyes<= intake.max_brief_bytes (NFR-003)
read_atyesUTC ISO 8601

Writes are atomic via safe_commit (FR-010): write to <target>.tmpfsyncos.replace.

Provenance lines in any consumer of this brief MUST pass through intake.provenance.escape_for_comment().

Charter Context

A render of .kittify/charter/charter.md scoped to one action. Two modes.

ModeWhenBodySection anchorsDirective IDsTactic IDs
bootstrapFirst load per actionFullAllAllAll
compactSubsequent loads per actionCollapsedAll (FR-034 fix)All (FR-034 fix)All (FR-034 fix)

First-load tracking lives in .kittify/charter/context-state.json, keyed by action.

Sync Queue

Existing SQLite OfflineQueue; this mission tightens overflow and replay semantics.

OperationBehavior (post-mission)
append(event)Raises OfflineQueueFull if would exceed sync.queue_max_events (default 10_000) instead of silent drop (FR-027)
replay()On (tenant_id, project_id) match: idempotent. Mismatch: structured TenantMismatch or ProjectMismatch, skip event, log conflict (FR-028)
drain_to_file(path)New helper for FR-027 recovery path

Auth Transport

New module: src/specify_cli/auth/transport.py.

AuthenticatedClient
  ├── token_store: TokenStore        # singleton per process
  ├── refresh_lock: RefreshLock       # mutex
  ├── _user_facing_failure_emitted: bool  # per-invocation dedup (FR-029)
  └── methods:
      get / post / put / delete / stream / websocket_connect
      → on 401: acquire refresh_lock, refresh once, retry
      → on refresh failure: raise AuthRefreshFailed; emit ≤1 user-facing line

State machine for token refresh:

fresh ─(401)──> refreshing ─(success)──> fresh
                  │
                  ├─(failure)──> failed ──(next 401)──> refreshing (with backoff)
                  │
                  └─(in-flight 401 from sibling)──> waits on lock, returns shared result

Architectural test asserts no caller in sync/, tracker/, or websocket modules imports httpx.Client / httpx.AsyncClient directly (FR-030).

Cross-Repo Package Contract

Two PyPI packages remain external, with frozen public surfaces:

  • spec_kitty_events.* — see contracts/events-envelope.md.
  • spec_kitty_tracker.* — see contracts/tracker-public-imports.md.

A resolved-version contract test reads uv.lock and asserts the test fixtures match the resolved version's public schema. Bumping a package without regenerating the snapshot fails the contract gate.

Issue Traceability Matrix

kitty-specs/<mission>/issue-matrix.md (FR-037 / D1).

ColumnTypeNotes
repostre.g. Priivacy-ai/spec-kitty
issueintGitHub issue number
themeenummerge / intake / runtime / packages / sync / governance / e2e
verdictenumfixed \
wp_idstrThe WP that owns the verdict
evidence_refstrtest name, commit SHA, doc anchor, or follow-up issue link