> ## Documentation Index
> Fetch the complete documentation index at: https://prism.ntecdev.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Spec 096 v0 2 wrap pre flight uncommitted ratified artifacts

# SPEC-096 v0.2 — Wrap-Time Pre-Flight Check for Uncommitted Ratified Artifacts

**Status:** draft v0.2 — Texi architecture review approved\_with\_required\_revisions on v0.1 (then SPEC-095 — renumbered to SPEC-096 due to collision with Porsche's machine\_id contract). All eight required revisions and three nits applied as v0.2.
**Author:** Donna
**Reviewer (architecture):** Texi
**Reviewer (governance):** Candi
**Origin:** Two ratify-vs-commit gap recurrences on 2026-05-08 (postmortems `2bc9bda2` and `07e9345e`).

## Summary

Prevent the recurring class of gap where a Prism entity is **ratified** in TriGraph (spec status=approved, ADR committed, fragment published, nav added) but the **disk artifacts** that the ratification implies are left uncommitted on local working trees. Peer machines pull stale state on `git pull`, even though Prism reports the artifact as live.

Mechanism: a **pre-flight check** at `prism_checkpoint` and `prism_wrap` time that warns or refuses to close the unit-of-work when the working tree contains uncommitted modifications under a watched path AND the session shows publication/ratification language referencing that path or the artifact ID it implements.

Operationalizes Element 4 ("Wrap-time handoff gate") of `method.wall-break.persistence` for the specific case of artifact-on-disk vs artifact-in-Prism drift.

## Background — observed gap

| Time (UTC)       | Artifact                              | Ratified at                  | Disk landed at       | Gap                                                |
| ---------------- | ------------------------------------- | ---------------------------- | -------------------- | -------------------------------------------------- |
| 2026-05-08T03:02 | SPEC-094 v0.3 (BIOS auto-memory rule) | spec entity status=approved  | PR #216 merged 04:19 | \~75 min, peer machines stale                      |
| 2026-05-08T14:21 | method.wall-break.persistence v0.2    | docs.json nav added by Candi | PR #222 merged 14:35 | \~14 min, peer caught nav-without-file via Desiree |

Both occurrences share the same shape: author edits, author or peer marks ratified in Prism, author wraps without committing, peer or downstream session fetches main and finds the gap.

## Watched paths (v0.2 scope, per Texi nit + answer)

The check fires when a working-tree-modified, staged, untracked, renamed, or deleted file matches any of these path families. Per Texi guidance, **enforcement priority** is split into two tiers:

**Tier 1 — high-risk paths (eligible for enforcement at v0.3):**

* `CLAUDE.md`, `AGENTS.md`, `templates/CLAUDE.md`, `templates/AGENTS.md` (BIOS replicas + SORs)
* `docs/method-fragments/*.md`, `docs/method-fragments/*.mdx` (open pattern, not only `method.*`)
* `docs/specs/spec-*.md`, `docs/specs/spec-*.mdx`
* `docs/adrs/adr-*.md`, `docs/adrs/adr-*.mdx` (corrected from v0.1 `docs/decisions`)

**Tier 2 — advisory-only:**

* `docs/case-studies/*.mdx`
* `docs/docs.json` when the diff adds a navigation entry pointing into a Tier 1 path (see §docs.json detection)

**Deferred (with explicit risk callout):**

* `templates/PRISM.md` — risk acknowledged. PRISM.md sync is already gated by ADR-16 pre-flight; this SPEC defers to that path until v0.3.
* SPEC-080 governance / method-domain paths.

## Trigger semantics — clarified

The pre-flight fires when ALL of:

* (a) `prism_wrap` or `prism_checkpoint` is being requested AND
* (b) at least one watched path is dirty in the working tree (any of: staged, unstaged-modified, untracked, renamed, deleted) AND
* (c) the session shows publication/ratification language referencing the watched artifact: at least one of —
  * a session\_delta / journal entry / postmortem / signal payload from this `session_id` containing the literal path, OR
  * a session-emitted reference to a spec\_id (e.g. `SPEC-096`) or fragment\_id (e.g. `method.wall-break.persistence`) that matches the dirty file's identifier, OR
  * the **attempted wrap/checkpoint payload** (`summary`, `decisions`, `next_actions`, `tags`) containing publish-language tokens — `publish`, `ratified`, `approved`, `merged`, `nav added`, `landed`, `shipped` — adjacent to a watched path or watched artifact ID.

Per Texi: condition (c) does not require proving formal ratification status against TriGraph. The detector matches *intent-language* in session artifacts; this captures the failure mode at close time.

## Behavior — clarified by mode

A new env flag (per Texi nit, replacing v0.1's boolean):

```
PRISM_WRAP_PREFLIGHT_MODE = off | advisory | enforce
```

Default: `advisory`. Existing boolean `PRISM_WRAP_PREFLIGHT_ENFORCED` is removed in favor of this trinary.

### Mode: off

The check does not run. Verbs proceed as today. Reserved for emergency disable; should not be the default for any tier or environment.

### Mode: advisory (default v0.2)

The verb proceeds (delta is written, registration is unaffected) and returns a `warnings: [...]` list naming the uncommitted paths and the matched references. Matches the existing `storeSessionDelta` warning pattern. Response shape:

```json theme={null}
{
  "ok": true,
  "registration_status": "active",
  "delta": { ... },
  "warnings": [
    {
      "kind": "uncommitted_ratified_artifact",
      "uncommitted_paths": ["docs/specs/spec-096-v0-2-wrap-pre-flight-uncommitted-ratified-artifacts.md"],
      "matched_references": [{"path": "docs/specs/spec-096-v0-2-...", "evidence_kind": "next_actions_publish_token", "evidence_excerpt": "publish via Candi"}],
      "branch": "donna/wrap-preflight-v0-2",
      "ahead_by": 0,
      "behind_by": 0,
      "remediation": "git add <paths> && git commit && git push, or pass force=true with reason."
    }
  ]
}
```

### Mode: enforce (v0.3-eligible)

For **Tier 1 paths only**, the verb refuses:

```json theme={null}
{
  "ok": false,
  "error": "uncommitted_ratified_artifact",
  "stage": "wrap_preflight",
  "uncommitted_paths": [ ... ],
  "matched_references": [ ... ],
  "remediation": "...",
  "registration_status": "active"
}
```

**Critical lifecycle invariant (per Texi revision 1):** the preflight runs **before** deregistration / heartbeat shutdown / coordination stream stop / signal subscriber close. If preflight refuses, **the session remains fully active**: registration, heartbeat, signal subscription continue. The agent sees the error and either commits + retries the wrap, or passes `force=true` with reason.

Tier 2 paths (case-studies, docs.json nav-only) stay advisory even in enforce mode at v0.3.

### Checkpoint semantics (per Texi revision 2)

`prism_checkpoint` operates identically in advisory and enforce modes — same response shape — but **never deregisters**. Checkpoint refusal in enforce mode means the session keeps going; the agent is expected to commit and retry the checkpoint. This is consistent with checkpoint's "I'm saving my game" semantics: if save fails because state is incoherent, you don't quit the game.

### Force-override (per Texi revision 1, audited)

The agent or operator may pass `force=true` with a non-empty `force_reason` string. The verb proceeds, AND records a `wrap_preflight_force` audit event in TriGraph attributable to `session_id`. Audit event fields:

* `session_id`, `agent_identity`, `force_reason` (string, ≥10 chars)
* `uncommitted_paths`, `matched_references` (as collected at force time)
* `wrap_or_checkpoint` (which verb was forced)

Audit events become queryable via `prism_runtime_diagnostics` and feed the v0.3 promotion telemetry decision.

## Backend (per Texi revision 4 & 6)

New helper in `backend/app/services/wrap_preflight.py`:

```python theme={null}
def detect_uncommitted_ratified_artifacts(
    *,
    session_id: UUID,
    project_id: UUID,
    git_state: GitState,                 # parsed from shim
    attempted_wrap_payload: dict | None, # summary/decisions/next_actions/tags
    mode: PreflightMode,                 # off | advisory | enforce
) -> PreflightOutcome: ...
```

`GitState` has fields:

* `dirty_paths: list[DirtyPathEntry]` (path, status\_code, repo-relative POSIX)
* `branch: str | None`
* `head_sha: str | None`
* `ahead_by: int | None`, `behind_by: int | None` (None if not cheaply available)
* `git_root: str | None` (None if project\_dir not in a repo)
* `docs_json_diff: str | None` (bounded ≤16KB; populated only if `docs/docs.json` is dirty)

Backend is responsible for: matching dirty\_paths against watched-path families; matching attempted\_wrap\_payload + recent session artifacts for evidence; emitting warnings or refusal; recording force audit events.

`detect_uncommitted_ratified_artifacts` is called from the prism\_wrap and prism\_checkpoint handlers immediately after memory-hygiene trigger computation, **before any deregistration or stream-shutdown logic**.

## MCP-node shim changes (per Texi revision 4 & 6)

`mcp-node/src/verbs/lifecycle.ts` `prism_checkpoint` and `prism_wrap`:

1. Resolve `project_dir` from cached project context.
2. Run `git status --porcelain=v1 -z` with `cwd: project_dir`, `shell: false`. Parse `\0`-separated entries; capture status\_code (X+Y from porcelain v1), original path, and rename target if applicable. Normalize all paths to repo-relative POSIX (forward slashes).
3. Run `git rev-parse --abbrev-ref HEAD` and `git rev-parse HEAD` for branch / head\_sha. Run `git rev-list --left-right --count @{upstream}...HEAD` for ahead/behind; on error (e.g. detached HEAD or no upstream), set both to null without failing.
4. If `docs/docs.json` appears in dirty\_paths, run `git diff -- docs/docs.json` capped at 16KB and pass as `docs_json_diff`. Backend parses the diff for navigation-entry additions referencing watched-path families.
5. Forward the assembled `git_state` plus the attempted wrap/checkpoint payload to the backend call.
6. Surface the backend's response (warnings or refusal) to the model with the remediation hint.

If `git` is missing or `project_dir` is not in a repo: shim sends `git_state.git_root = null`; backend skips the check and returns a `preflight_skipped` advisory (so calls don't silently degrade without telemetry).

## Phasing

* **v0.1 (renumbered+retired):** initial proposal, blocked by Texi review.
* **v0.2 (this SPEC):** all required revisions + nits applied. Default mode = `advisory`. Watched paths split into Tier 1 / Tier 2. Lifecycle ordering invariant explicit. Force-override audited.
* **v0.3:** flip default mode to `enforce` for Tier 1 paths after — (a) Phase 0 advisory telemetry observation period of at least 7 days of active multi-agent sessions, AND (b) Texi + Candi review of telemetry, AND (c) MCP-node + backend test coverage in place. **No automatic flip** (per Texi nit 3).
* **v0.4 (open):** add `templates/PRISM.md` and SPEC-080 governance paths to Tier 1.

## Tests (per Texi revision 8)

**MCP-node unit tests** in `mcp-node/tests/wrap_preflight_shim.test.ts`:

* Porcelain v1 -z parsing: modified, untracked, staged, renamed, deleted, mixed
* Path normalization (Windows-style → POSIX, nested directories, with spaces)
* ahead/behind null cases (detached HEAD, no upstream, unreachable remote)
* docs.json diff capture (≤16KB cap, JSON-valid diff, malformed JSON)
* Project\_dir not-in-repo graceful degradation

**Backend unit tests** in `backend/tests/test_wrap_preflight.py`:

* Path-family matching: BIOS / fragment / spec / ADR (both .md and .mdx) / case-study / docs.json nav-add
* Reference matching: exact path, spec\_id, fragment\_id, publish-language tokens in summary/decisions/next\_actions/tags
* Mode behavior: off skips entirely, advisory returns warnings + ok=true, enforce on Tier 1 returns ok=false, enforce on Tier 2 returns warnings
* Force-override: audit event emitted with required fields; missing force\_reason rejects with validation error
* Lifecycle invariant: enforce-refusal does NOT trigger deregistration / heartbeat stop (state-machine assertions)

## Out of scope (v0.2)

* Generic "any uncommitted file" check (too noisy on this monorepo)
* CI-side enforcement (downstream — once wrap-time enforcement proves the pattern)
* Auto-commit on wrap (unsafe — agents must not author commits without operator review)
* Cross-fleet drift detection in `prism_start` — separate SPEC if pursued

## Risk

* **Adoption: R2** — methodology change affecting every wrap and checkpoint. Backward-compat preserved via advisory default + force-override.
* **Lifecycle invariant** is the highest-risk surface: a buggy preflight that accidentally deregistered a session would strand agents. Test coverage on the lifecycle-state-machine assertion is mandatory before flag flip.

## Implementation cautions (per Texi second-pass)

Captured here so they survive the spec → implementation handoff:

1. **Don't let advisory mode become noisy generic dirty-worktree lint.** The path-family AND intent-language match together are the product boundary; either alone is too broad.
2. **Force-override must be durable through partial failures.** If TriGraph audit-event write is unavailable when the operator invokes force, return a clear mode-appropriate warning or error rather than silently force-closing without an audit trail.
3. **Keep remote/ahead-behind best-effort only.** No network remote fetch on wrap or checkpoint in v0.2 — keep the verb local-only.
4. **Lifecycle test assertions** must explicitly verify no calls to `releaseController` / `stopHeartbeat` / `stopStream` / `shutdownDaemon` occur on enforced refusal. The state-machine test is the load-bearing safety surface.

## Cross-references

* `method.wall-break.persistence` v0.2 — Element 4 (Wrap-time handoff gate)
* SPEC-094 v0.3 — first observed instance; postmortem `2bc9bda2`
* ADR-16 — `PRISM.md` sync pre-flight; force=True semantics modeled on this
* Postmortem `07e9345e` (2026-05-08 second occurrence) — action item satisfied here
* SPEC-063 — postmortem closed-form template
* SPEC-095 (Porsche, machine\_id launch contract) — distinct workstream; this SPEC renumbered to avoid collision
* Texi v0.1 review (signal `b1eb74a4`) — eight required revisions all applied as v0.2

## Acceptance criteria for v0.2 → v0.3 promotion

1. Phase 0 advisory mode shipped + observed for at least 7 days of active multi-agent sessions.
2. Telemetry shows the warning fired at least 3 times on independent sessions, OR shows zero fires. **Zero fires does not auto-flip the flag** — Texi + Candi review the telemetry and explicitly elect to flip.
3. Texi architecture review of v0.2 → v0.3 deltas (lifecycle invariant test coverage, Tier 1 enforcement scope, audit event surface).
4. Candi governance ratification of v0.3.
5. MCP-node test coverage per §Tests (porcelain parsing, path normalization, ahead/behind, docs.json diff, graceful degradation).
6. Backend test coverage per §Tests (path-family matching, reference matching, mode behavior, force-override audit, lifecycle invariant).
