Phase 1 Quickstart: Using Mission Dossier

Date: 2026-02-21 | Feature: 042-local-mission-dossier-authority-parity-export

For Curators: Inspect Artifact Completeness

Scenario 1: Open Dashboard & View Dossier

# Terminal 1: Start dashboard server
spec-kitty dashboard --open

# Browser: http://localhost:8000
# → Click "Dossier" tab
# → See overview panel with:
#   - Feature: 042-local-mission-dossier
#   - Status: Complete ✅
#   - Artifacts: 15 total, 10 required (all present)
#   - Parity Hash: abc456def789...

Scenario 2: Filter Artifacts by Type

Dashboard → Dossier → Filter
  [Class] = "output"
  → Shows: plan.md, tasks.md, tasks/WP01.md, tasks/WP02.md, ...

Dashboard → Dossier → Filter
  [Class] = "evidence"
  → Shows: research.md, data-model.md, (contracts/* if present)

Scenario 3: View Artifact Detail

Dashboard → Dossier → Click "spec.md"
  → Artifact Panel opens:
    - Path: spec.md
    - Class: input
    - Hash: a1b2c3d4e5f6...
    - Size: 8.5 KB
    - Content:
      ```
      # Feature Specification: Local Mission Dossier Authority...
      [full text, syntax-highlighted markdown]
      ```

Scenario 4: Detect Missing Required Artifact

# Feature missing plan.md (manually deleted for testing)
spec-kitty dashboard

# Dashboard shows:
#   Status: Incomplete ⚠️
#   Missing: 1 required artifact
#   - plan.md (output, BLOCKING)
#
# → Look at Events section:
#   - MissionDossierArtifactMissing event emitted
#   - reason_code: "not_found"
#   - blocking: true

For Developers: API Integration

Programmatic Artifact Indexing

from specify_cli.dossier import (
    MissionDossier,
    Indexer,
    ManifestRegistry,
)
from pathlib import Path

# 1. Load manifest
manifest = ManifestRegistry.load_manifest("software-dev")

# 2. Index artifacts
feature_dir = Path("/path/to/feature/042-local-mission-dossier")
dossier = Indexer.index_feature(
    feature_dir=feature_dir,
    mission_type="software-dev",
    mission_slug="software-dev",
    feature_slug="042-local-mission-dossier",
)

# 3. Check completeness
print(f"Completeness: {dossier.completeness_status}")
print(f"Missing: {len(dossier.get_missing_required_artifacts())}")

# 4. Iterate artifacts
for artifact in dossier.artifacts:
    print(f"- {artifact.artifact_key}: {artifact.relative_path}")

Emit Dossier Events

from specify_cli.dossier import (
    emit_indexed_event,
    emit_missing_event,
    emit_snapshot_computed_event,
)

# Emit indexed event
await emit_indexed_event(
    feature_slug="042-local-mission-dossier",
    artifact_key="input.spec.main",
    artifact_class="input",
    content_hash_sha256="a1b2c3d4...",
    size_bytes=8500,
    relative_path="spec.md",
)

# Emit missing event (if required artifact not found)
await emit_missing_event(
    feature_slug="042-local-mission-dossier",
    artifact_key="output.plan.main",
    reason_code="not_found",
    blocking=True,
)

# Emit snapshot computed event (after all artifacts indexed)
snapshot = compute_snapshot(dossier)
await emit_snapshot_computed_event(snapshot)

Query API Endpoints

import httpx

client = httpx.Client(base_url="http://localhost:8000")

# 1. Get dossier overview
resp = client.get("/api/dossier/overview?feature=042-local-mission-dossier")
overview = resp.json()
# {
#   "feature_slug": "042-local-mission-dossier",
#   "completeness_status": "complete",
#   "parity_hash_sha256": "abc456def789...",
#   "artifact_counts": {...}
# }

# 2. List artifacts with filtering
resp = client.get(
    "/api/dossier/artifacts",
    params={
        "feature": "042-local-mission-dossier",
        "class": "output",
        "step_id": "planning",
    }
)
artifacts = resp.json()
# [
#   {"artifact_key": "output.plan.main", "relative_path": "plan.md", ...},
#   {"artifact_key": "output.tasks.main", "relative_path": "tasks.md", ...},
#   ...
# ]

# 3. Get artifact detail with full text
resp = client.get(
    "/api/dossier/artifacts/input.spec.main",
    params={"feature": "042-local-mission-dossier"}
)
detail = resp.json()
# {
#   "artifact_key": "input.spec.main",
#   "content": "# Feature Specification: ...",
#   "media_type_hint": "markdown",
#   ...
# }

# 4. Export snapshot for SaaS import
resp = client.get(
    "/api/dossier/snapshots/export",
    params={"feature": "042-local-mission-dossier"}
)
snapshot_json = resp.json()
# Full MissionDossierSnapshot, ready for SaaS backend

For SaaS: Consuming Dossier Events

Event Stream Example

[
  {
    "event_type": "mission_dossier_artifact_indexed",
    "feature_slug": "042-local-mission-dossier",
    "payload": {
      "artifact_key": "input.spec.main",
      "artifact_class": "input",
      "relative_path": "spec.md",
      "content_hash_sha256": "a1b2c3d4e5f6...",
      "size_bytes": 8500,
      "required_status": "required"
    },
    "timestamp": "2026-02-21T10:00:00Z"
  },
  {
    "event_type": "mission_dossier_artifact_indexed",
    "feature_slug": "042-local-mission-dossier",
    "payload": {
      "artifact_key": "output.plan.main",
      "artifact_class": "output",
      "relative_path": "plan.md",
      "content_hash_sha256": "b2c3d4e5f6g7...",
      "size_bytes": 6200,
      "required_status": "required"
    },
    "timestamp": "2026-02-21T10:00:01Z"
  },
  {
    "event_type": "mission_dossier_snapshot_computed",
    "feature_slug": "042-local-mission-dossier",
    "payload": {
      "parity_hash_sha256": "xyz789...",
      "artifact_counts": {
        "total": 15,
        "required": 10,
        "required_present": 10,
        "required_missing": 0,
        "optional": 5,
        "optional_present": 3
      },
      "completeness_status": "complete"
    },
    "timestamp": "2026-02-21T10:00:05Z"
  }
]

SaaS Parity Check

# SaaS receives events
artifacts_from_events = {}
for event in event_stream:
    if event["event_type"] == "mission_dossier_artifact_indexed":
        payload = event["payload"]
        artifacts_from_events[payload["artifact_key"]] = payload

# Receive snapshot
snapshot_event = [e for e in event_stream if e["event_type"] == "mission_dossier_snapshot_computed"][0]
local_parity_hash = snapshot_event["payload"]["parity_hash_sha256"]

# Verify parity locally (recompute hash from indexed events)
hashes = sorted([a["content_hash_sha256"] for a in artifacts_from_events.values()])
computed_parity = hashlib.sha256("".join(hashes).encode()).hexdigest()

assert computed_parity == local_parity_hash, "Parity mismatch!"
# ✅ Parity verified

Expected Artifact Manifests (Reference)

Software-Dev Mission

File: src/specify_cli/missions/software-dev/expected-artifacts.yaml

Uses states from mission.yaml: discover → specify → plan → implement → review → done

schema_version: "1.0"
mission_type: "software-dev"
manifest_version: "1"

required_always:
  - artifact_key: "input.spec.main"
    artifact_class: "input"
    path_pattern: "spec.md"

required_by_step:
  discover: {}    # No additional requirements

  specify: {}     # Spec already in required_always

  plan:           # Adding plan phase
    - artifact_key: "output.plan.main"
      artifact_class: "output"
      path_pattern: "plan.md"

  implement:      # Adding implementation artifacts
    - artifact_key: "output.tasks.main"
      artifact_class: "output"
      path_pattern: "tasks.md"
    - artifact_key: "output.tasks.per_wp"
      artifact_class: "output"
      path_pattern: "tasks/*.md"

  review: {}      # Review phase (no new requirements, previous steps must be complete)

optional_always:
  - artifact_key: "evidence.research"
    artifact_class: "evidence"
    path_pattern: "research.md"
  - artifact_key: "evidence.data_model"
    artifact_class: "evidence"
    path_pattern: "data-model.md"
  - artifact_key: "evidence.contracts"
    artifact_class: "evidence"
    path_pattern: "contracts/*"

Research Mission

File: src/specify_cli/missions/research/expected-artifacts.yaml

Uses states from mission.yaml: scoping → methodology → gathering → synthesis → output → done

schema_version: "1.0"
mission_type: "research"
manifest_version: "1"

required_always:
  - artifact_key: "input.spec.main"
    artifact_class: "input"
    path_pattern: "spec.md"

required_by_step:
  scoping: {}     # Spec in required_always

  methodology:
    - artifact_key: "output.plan.main"
      artifact_class: "output"
      path_pattern: "plan.md"

  gathering: {}   # No new hard requirements

  synthesis:
    - artifact_key: "output.findings.main"
      artifact_class: "output"
      path_pattern: "findings.md"

  output: {}

optional_always:
  - artifact_key: "evidence.sources"
    artifact_class: "evidence"
    path_pattern: "sources.csv"

Documentation Mission

File: src/specify_cli/missions/documentation/expected-artifacts.yaml

schema_version: "1.0"
mission_type: "documentation"
manifest_version: "1"

required_always:
  - artifact_key: "input.spec.main"
    artifact_class: "input"
    path_pattern: "spec.md"

required_by_step:
  planning: {}

  authoring:
    - artifact_key: "output.docs.main"
      artifact_class: "output"
      path_pattern: "docs/*.md"

optional_always:
  - artifact_key: "evidence.generated_api_docs"
    artifact_class: "evidence"
    path_pattern: "api-docs/*"

Testing Determinism

Verify Parity Hash Reproducibility

from specify_cli.dossier import Indexer, compute_snapshot
from pathlib import Path

feature_dir = Path("/path/to/feature/042-local-mission-dossier")

# Scan 1
dossier1 = Indexer.index_feature(feature_dir, "software-dev", "software-dev", "042-local-mission-dossier")
snapshot1 = compute_snapshot(dossier1)
hash1 = snapshot1.parity_hash_sha256

# Scan 2 (no artifact changes)
dossier2 = Indexer.index_feature(feature_dir, "software-dev", "software-dev", "042-local-mission-dossier")
snapshot2 = compute_snapshot(dossier2)
hash2 = snapshot2.parity_hash_sha256

assert hash1 == hash2, f"Hashes differ: {hash1} vs {hash2}"
# ✅ Determinism verified

Common Workflows

Workflow 1: After Planning Complete

# User creates spec, plan, tasks
cd /path/to/feature/042-local-mission-dossier

# Open dashboard
spec-kitty dashboard

# → Dossier tab shows:
#   - All required artifacts present ✅
#   - Completeness: complete
#   - Parity hash computed and stable

Workflow 2: Detect Artifact Corruption

# User modifies spec.md (introduces typo)
echo "corrupted" >> spec.md

# Dashboard auto-detects
# → Parity hash changed
# → If baseline cached, MissionDossierParityDriftDetected emitted
# → Review changes in dashboard detail view

Workflow 3: Export for SaaS Import

curl -s http://localhost:8000/api/dossier/snapshots/export?feature=042-local-mission-dossier \
  | jq . > snapshot.json

# Ship snapshot.json to SaaS backend
curl -X POST https://saas.example.com/api/import-dossier \
  -H "Content-Type: application/json" \
  -d @snapshot.json

# SaaS verifies parity, displays artifact catalog in dashboard

Troubleshooting

"Missing required artifact" event

Cause: Feature missing required artifact (e.g., plan.md)

Fix:

# Check what's missing
spec-kitty dashboard → Dossier → Filter [Required Only]

# Regenerate missing artifact
spec-kitty plan  # Recreates plan.md

# Re-scan
# (Dashboard auto-rescans on file change)

"Parity hash mismatch"

Cause: Artifact content changed (expected after edits)

Fix:

# Re-capture parity baseline
curl -s http://localhost:8000/api/dossier/snapshots/export?feature=042-local-mission-dossier \
  > /path/to/.kittify/dossiers/042-local-mission-dossier/parity-baseline.json

# Or manually edit the file with new hash from current snapshot

"Encoding error on artifact"

Cause: Artifact file not valid UTF-8

Fix:

# Check file encoding
file -i artifact.md
# artifact.md: text/plain; charset=utf-16

# Convert to UTF-8
iconv -f UTF-16 -t UTF-8 artifact.md > artifact-fixed.md
mv artifact-fixed.md artifact.md

# Re-scan

Next Steps

  • WP01-WP05: Implement core indexing, events, snapshot computation
  • WP06-WP08: Build dashboard API and UI, local drift detection
  • WP09-WP10: Comprehensive testing, edge case hardening