Skip to content

T-Y1JN-add-sdlc-reconcile-reporter

Status: closed/done · Impact: high · Complexity: large

Ship the sdlc reconcile command that builds a structured report comparing every authoritative source of lease-protocol state (task frontmatter, active lease refs, archive refs, control-plane ref, branches, PRs) and surfaces the 14 anomaly categories specified in the ADR’s Reconcile section. Read-only by design — no --fix modes in this slice. This is the verification surface that operators use to confirm the cutover migration worked, and the ongoing health check for distributed lease state. Without reconcile, the only way to spot drift is by manually git ls-remote-ing and cross-referencing PRs.

LocationRole today
plugin/scripts/sdlc_lease.pyTop-level CLI dispatcher. No reconcile subcommand exists.
plugin/scripts/lease_cli/Per-subcommand handlers. No reconcile.py.
plugin/lib/lease/Library exposes primitives, schemas, runtime helpers, cache, control-plane validator, migration helpers (after the migrate task lands). No reconcile detector library.
docs/planning/decisions/github-ref-leases/protocol.md (section “Reconcile”, lines 1033–1084)Canonical 14-category checklist that this task implements.
docs/planning/decisions/github-ref-leases/protocol.md (section “Local Runtime Index for UI”, lines 572–621)Defines the local-mirror semantics that reconcile’s FETCH-NAMESPACE refresh relies on.

Operators currently have no automated way to spot the anomalies the ADR enumerates: expired leases, abandoned PRs, mismatched footers, post-mortem-after-closing violations, namespace conflicts, etc. Each requires manual inspection.

Ship sdlc reconcile as a top-level subcommand that:

  1. Refreshes the local mirror via batch FETCH-NAMESPACE refs/sdlc/* and reports the count of pruned-stale-locals.
  2. Loads five sources of truth in parallel: task frontmatter (all of docs/planning/tasks/*.md), active task leases (refs/sdlc/tasks/*), active operation leases (refs/sdlc/ops/*), archive refs (refs/sdlc/archive/*), the control-plane ref, open + merged PRs (via gh pr list), and local task branches (refs/heads/task/*, refs/heads/feat/* legacy).
  3. Runs 14 anomaly detectors independently — each is a small pure function over the loaded state returning a list of AnomalyRecord.
  4. Emits the report in two modes: human-readable plaintext (default) grouped by category with counts and per-row detail; structured JSON (--json) for machine consumers (CI, dashboards, future --fix plumbing).
  5. Exit code reflects severity: 0 (clean), 1 (warnings present), 2 (critical anomalies present). Critical set: namespace-conflict, duplicate-or-malformed-payloads, lifecycle-lease-missing-task-file.
  6. Scopable via --task <id> to scope the report to one task’s frontmatter+lease+PR+branch consistency.
  1. Add plugin/lib/lease/reconcile.py as the detector library. Defines:

    • AnomalyRecord dataclass:

      {category: str, severity: Literal["info","warning","critical"], task_id: str|None, ref: str|None, description: str, evidence: dict}
    • ReconcileState dataclass holding all loaded sources of truth (task_frontmatter, task_leases, op_leases, archive_refs, control_plane, prs_open, prs_merged, branches).

    • load_reconcile_state(authority, project_root, cwd=None) — runs the batch FETCH-NAMESPACE, loads everything else, returns ReconcileState. Records the prune count.

    • 14 detector functions, one per anomaly category, each (state: ReconcileState) -> list[AnomalyRecord].

  2. Implement the 14 detectors (one per ADR bullet):

    • detect_expired_task_leasesexpires_at + grace < now for active task leases.
    • detect_abandoned_awaiting_reviewawaiting-review leases with last_phase_transition >30 days ago and no PR activity in that window.
    • detect_stale_or_incompatible_client_versions — leases whose sdlc_version differs from the control-plane’s sdlc_version by major segment.
    • detect_lifecycle_lease_missing_task_filerefs/sdlc/tasks/<id> exists but docs/planning/tasks/<id>.md doesn’t.
    • detect_op_leases_with_results_not_applied — operation leases at phase done with result_ref set but the result ref never consumed (heuristic: archive ref absent).
    • detect_tasks_in_progress_without_lease — task frontmatter status: in-progress with no refs/sdlc/tasks/<id> ref.
    • detect_leases_with_merged_pr_but_no_close_out — open task lease whose linked PR is merged but archive ref absent.
    • detect_task_branches_without_leaserefs/heads/task/<id> or refs/heads/feat/<id> exists with no corresponding lease ref.
    • detect_duplicate_or_malformed_lease_payloads — lease ref whose blob fails Pydantic validation, or two refs collide on task_id.
    • detect_pr_footer_missing_or_mismatched — PR body lacks the footer, OR footer’s lease_id doesn’t match any active or archived lease for that task.
    • detect_post_mortem_edited_after_closing — task file’s “Post-mortem” section modified after the lease transitioned to closing (compare commit timestamp of task file change vs lease’s phase-transition commit).
    • detect_clock_skew_from_heartbeat_pattern — sample the last 30 active leases’ commit timestamps vs embedded expires_at - TTL; flag persistent one-directional skew >TTL/4.
    • detect_heartbeat_rate_above_floor — leases whose commit cadence exceeds the configured floor (heartbeat-rate denial-of-service signal).
    • detect_namespace_conflict_ref_present — a literal refs/sdlc ref exists (blocks creation of children) — uses the slice 1 namespace-conflict guard library.
  3. Build plugin/scripts/lease_cli/reconcile.py as the CLI handler. Calls load_reconcile_state, runs each detector, aggregates AnomalyRecords into the report shape, emits per the format flag.

  4. Wire reconcile into sdlc_lease.py top-level dispatch as sdlc reconcile [--task <id>] [--json] [--include <category>...] [--exclude <category>...].

  5. Output format — plaintext default: top-level summary block (overall counts by severity + prune count); per-category sections with header ### <category> — <count> records (<severity>), each record formatted as <task_id-or-ref>: <description>. Footer with exit code rationale.

  6. Output format — JSON (--json):

    {summary: {total: N, by_severity: {critical: N, warning: N, info: N}, by_category: {...}, prune_count: N}, anomalies: [AnomalyRecord, ...], metadata: {authority, sdlc_version, timestamp_utc}}
  7. Add --task <id> scope filter to narrow detectors to records matching that task_id; if a detector is global (e.g., namespace-conflict), it still runs and reports.

  8. Tests in plugin/lib/lease/tests/test_reconcile.py — each of the 14 detectors gets a known-positive (anomaly present) and known-negative (anomaly absent) test against the local-bare-repo authority fixture. CLI integration tests in plugin/scripts/lease_cli/tests/test_reconcile.py cover plaintext output, JSON output, exit codes, --task scoping.

  9. Document the subcommand in plugin/scripts/lease_cli/README.md: signature, exit codes, category list with definitions and severity, worked examples for both output modes.

LocationKindChange
plugin/lib/lease/reconcile.pynewAnomalyRecord, ReconcileState, load_reconcile_state, 14 detector functions.
plugin/lib/lease/__init__.pymodifyRe-export reconcile types and load_reconcile_state.
plugin/scripts/lease_cli/reconcile.pynewCLI handler — runs detectors, emits report in plaintext or JSON.
plugin/scripts/sdlc_lease.pymodifyAdd reconcile to top-level argparse dispatch + module docstring.
plugin/lib/lease/tests/test_reconcile.pynewPer-detector unit tests with positive + negative fixtures.
plugin/scripts/lease_cli/tests/test_reconcile.pynewCLI integration tests covering output formats, exit codes, --task filter.
plugin/scripts/lease_cli/README.mdmodifyDocument the reconcile subcommand, category list, exit codes.
  • AC-1: sdlc reconcile begins each run with a batch FETCH-NAMESPACE refs/sdlc/* and reports the count of pruned-stale-locals in the summary block. Verified by injecting a stale local ref before run and asserting it’s pruned + counted.

  • AC-2: Each of the 14 anomaly categories has a dedicated detector function with at least one known-positive test (anomaly correctly detected) and one known-negative test (clean state produces no anomaly).

  • AC-3: Plaintext output (default) groups anomalies by category with counts and per-row detail lines. Verified against fixture-generated state with multiple anomaly types.

  • AC-4: sdlc reconcile --json emits valid JSON matching the documented schema ({summary, anomalies, metadata}). Verified by parsing the output and asserting structure.

  • AC-5: Exit code is 0 (no anomalies), 1 (warnings), or 2 (critical anomalies present). Critical set: namespace-conflict, duplicate-or-malformed-payloads, lifecycle-lease-missing-task-file.

  • AC-6: Reconcile is read-only — no refs, no PR bodies, no files are mutated. Verified by running against a known-good fixture, recording git ls-remote + gh pr list state before/after, and asserting identity.

  • AC-7: The clock-skew detector samples lease-commit timestamps across the last 30 active leases (or all if fewer) and flags only persistent one-directional skew >TTL/4. Verified by fixture with simulated clock-skew commit timestamps.

  • AC-8: sdlc reconcile --task <id> scopes per-task detectors to that one task; global detectors (namespace-conflict, control-plane absent) still run and report. Verified by comparing scoped vs unscoped runs against the same state.

  • AC-9: --include and --exclude flags filter detectors by category name; mutually-exclusive flags surface a clear argparse error.

  • AC-10: All tests pass:

    uv run --with pytest --with pyyaml --with pydantic python -m pytest plugin/lib/lease/tests/test_reconcile.py plugin/scripts/lease_cli/tests/test_reconcile.py

    Quality checks pass: plugin/scripts/run_quality_checks.py --config sdlc.yaml.

  • AC-11: Reconcile against a freshly-migrated repo (post sdlc lease migrate) reports zero critical anomalies — verifies that the migrate task’s output is reconcile-clean by construction.

  • AC-12: Reconcile output is deterministic — given identical state, two consecutive runs produce byte-identical reports (modulo the timestamp_utc metadata field). Tested by snapshot comparison.

  • --fix modes — slice 3 reconcile ships as read-only only. Safe cleanups (e.g., archive merged-but-not-closed leases) defer to a later slice.
  • New anomaly categories beyond the ADR’s 14. Future categories belong in dedicated follow-up tasks (and likely a sub-document under docs/planning/decisions/github-ref-leases/).
  • A reconcile web UI / Grafana dashboard — JSON output exists so consumers can build that separately.
  • Cross-repo reconcile — single-repo scope only. Multi-repo coordination belongs in the operation-leases slice (slice 4).
  • This task closes E0002 Rollout Plan item 8 (“Add sdlc reconcile reporting against the checklist in Reconcile section”). Defined as a stub in the epic; this task is the concrete spec.
  • The ADR’s Reconcile section (protocol.md lines 1033–1084) enumerates the 14 categories verbatim; this task does not invent new categories. Future categories belong in dedicated follow-up tasks per the ADR’s “Likely expansion areas” note (README.md#expanding-this-design).
  • Decision to ship read-only (no --fix) follows the ADR’s framing: “Reconcile does not mutate by default. […] Later, reconcile may gain explicit --fix modes for safe cleanups.” Adding --fix here would require operator-confirmation UX work that’s out of slice 3’s scope.

Bullet: The baseline showed 198 pre-existing findings vs Task B’s 0. Most are from the slice 4a schema-bump fixture churn; some may be from the dashboard merge. Worth surfacing in reconcile or in /sdlc:entities-migrate —include-closed to clear closed-task drift. Keywords searched: entities-migrate, include-closed, pre-existing, schema-bump, closed-task, dashboard, surfacing, reconcile Excluded: 2026-05-27-task-close-out-verifies-prs-against-merged-pr Top candidates (score / status / headline):

  • 39 / planning/draft / 2026-05-25-add-sdlc-reconcile-reporter — Add sdlc reconcile — read-only multi-source anomaly reporter
  • 35 / closed/done / 2026-05-19-implement-entities-migrate — Implement /sdlc:entities-migrate to apply mechanical schema-drift fixes
  • 32 / closed/done / 2026-05-21-run-quality-checks-isolates-pre-existing-drift — run_quality_checks.py only fails on drift the current branch introduced
  • 28 / closed/done / 2026-05-22-restructure-task-touchpoints-as-a-table-with-symbol-dir-glob — Restructure task touchpoints as a table with symbol/dir/glob citation grammar
  • 24 / closed/done / 2026-05-19-schema-bump-tasks-handle-missing-version — Schema-bump task template enumerates the missing-schema_version case explicitly Decision: LINKED-EXISTING 2026-05-25-add-sdlc-reconcile-reporter Originating task: 2026-05-27-task-close-out-verifies-prs-against-merged-pr

T-FFHN-github-ref-leases-coordination


← Back to Tasks