# pqdx/2.0.0 Patching Guide

**PQDX-GDE-0001** — Non-normative guidance
Companion to the pqdx/2.0.0 schema specification (PQDX-STD-0001)

---

## What This Document Covers

State files are append-mostly documents. Most updates add new entries
to the end of a section. Some updates modify existing entries (status
changes, field additions). No updates delete entries or reorder arrays.

This guide describes how to apply changes — "patches" — to a
`{project}.state.yaml` file. Three actors patch state files, and each
needs different guidance:

1. **Humans** editing in a text editor
2. **AI tools** producing session close-out patches
3. **Automated tools** applying changes programmatically

The rules are the same for all three. The guidance differs in format
and workflow.

---

## Core Rules (All Actors)

These rules are non-negotiable. Every patch, regardless of who or what
produces it, must follow them.

### Rule 1: Append, Never Reorder

All sequences in a state file are chronologically ordered — oldest
first, newest last. New entries go at the end of their section. Existing
entries are never moved. The array index of an entry is part of its
identity in version control diffs; reordering creates phantom changes
that obscure real edits.

### Rule 2: Never Delete

No entry is ever removed from a state file. Resolved concerns stay.
Completed actions stay. Superseded decisions stay. Cancelled milestones
stay. The state file is an audit trail — deletion destroys history.

If an entry was created in error, supersede it (decisions), resolve it
with a note (concerns), or cancel it (actions, queued items). The
governance record shows what happened, including mistakes.

### Rule 3: Respect Immutability

Sessions are immutable after creation (per ILD-56). Once a session entry
is written, these fields MUST NOT change: `id`, `date`, `title`,
`purpose`, `mode`, `participants`, `decisions_made`, `state_changes`,
`next_action`, `notes`, `duration`, `planned_duration`. The `produced`
array is the sole exception —
artifacts discovered or completed after the session may be appended
with a note indicating the amendment session.

### Rule 4: Respect Terminal States

An entity in a terminal state MUST NOT transition to any other state.
Terminal states by entity type:

| Entity Type | Terminal States |
|-------------|----------------|
| Milestones | `complete`, `cancelled` |
| Milestone Items | `done`, `deferred`, `dropped`, `subsumed` |
| DR | `superseded`, `rejected`, `withdrawn` |
| OQ | `resolved` |
| ILD | `superseded` |
| Concerns | `resolved` |
| Actions | `done`, `cancelled` |
| Queued Items | `complete`, `cancelled` |

If a terminal entity needs "reopening," the correct action is to create
a new entity that references the old one, not to change the old one's
status.

### Rule 5: Inline Arrays Stay Inline

The `decisions_made`, `depends_on`, `blocked_by`, and `blocks` fields
use inline YAML array syntax. The array MUST be on the same line as
the key. The pqdc-cc parser breaks silently on split arrays.

```yaml
# CORRECT
decisions_made: [DR-001, ILD-4, ILD-5]

# WRONG — parser breaks
decisions_made:
  - DR-001
  - ILD-4
  - ILD-5
```

### Rule 6: Indentation Is 2 Spaces

All indentation in a conforming state file uses 2-space indentation.
No tabs. No 4-space. Mixing indentation within a file creates YAML
parse errors that are difficult to diagnose.

### Rule 7: Flow-Style for Milestone Items

Milestone items use flow-style YAML for scannability:

```yaml
- { id: v11-1, text: "Semantic search implementation", status: open, gate: true }
```

Field order is normative: `id`, `text`, `status`, then optional fields
(`gate`, `notes`, `visibility`). Items with extended notes MAY use
block-style, but flow-style is the default.

### Rule 8: Session Participants Use Flow-Style

```yaml
participants:
  - { name: sarah, type: human, role: authority }
  - { name: claude, type: collaborator, role: driver }
```

### Rule 9: Provenance Is Always Present

Every entity created in a patch MUST include the `session` field. For
session close-out patches, this is the current session ID. For
dashboard-created entities, this is `"dashboard"`. For migrated
entities, this is `"migrated:{source}"`. Provenance is non-negotiable.

### Rule 10: Projection Files Are Read-Only

Files carrying the top-level `projection: public` key are generated
public projections, not canonical state. Patches MUST NOT be applied
to projection files: `apply_patch` and any other canonical-mutation
operation MUST refuse when the target file carries `projection:
public`, citing PQDX-STD-0001b §11.17 rule 1. Edits to a projection
file would be lost on the next regeneration of the projection from
canonical, and the divergence would silently corrupt the public
record.

Patch the canonical (`{project}.state.yaml`) instead. The projection
(`{project}.public-state.yaml`) is regenerated mechanically by the
projection algorithm specified in PQDX-STD-0001b §10.6; tools
generating the public projection MUST follow that algorithm and
SHOULD provide a CI guard that fails when committed projection
output diverges from regenerated output (per §10.6.8).

---

## For Humans

### The Manual Patching Workflow

1. Open `{project}.state.yaml` in your text editor
2. Navigate to the section you need to update
3. For new entries: scroll to the end of the section, add your entry
4. For status changes: find the entry by ID, change the status value
5. Save the file
6. If using pqdc-cc with watch mode, the dashboard updates in ~2 seconds

### Adding a New Entry

Find the section. Go to the last entry. Add your new entry after it
with the same indentation. Example — adding an action:

```yaml
  # ... existing actions above ...

  - id: A-9
    text: "Review the search quality audit results"
    session: S012
    assignee: david
    status: open
    priority: high
    due: "2026-05-01"
```

### Changing a Status

Find the entry by searching for its ID. Change the status value. Add
any fields required by the transition:

```yaml
  # Before
  - id: DEF-1
    type: defect
    severity: medium
    text: "Search ranking degrades for articles with code blocks"
    status: monitoring
    session: S009

  # After — resolved, with required fields
  - id: DEF-1
    type: defect
    severity: medium
    text: "Search ranking degrades for articles with code blocks"
    status: resolved
    session: S009
    resolution: "Implemented content-type-aware embedding with code block weighting"
    closed_in: S012
```

### Adding Fields to Existing Entries

Optional fields can be added to any existing entry at any time. Add
them at the end of the entry's field block, maintaining indentation:

```yaml
  # Before
  - id: A-6
    text: "Write contributor onboarding guide"
    session: S010
    status: open

  # After — assignee and priority added
  - id: A-6
    text: "Write contributor onboarding guide"
    session: S010
    status: open
    assignee: mia
    priority: low
```

### Common Mistakes

**Mistake: Editing session fields.** Session entries are immutable.
If you got a date wrong, add a note to the session or record the
correction in the next session's `state_changes`.

**Mistake: Reordering entries.** It's tempting to group related entries
together. Don't. Chronological order is the rule. Use search (Ctrl+F)
to find related entries.

**Mistake: Deleting resolved entries.** A resolved concern or done
action is not clutter — it's history. Leave it.

**Mistake: Forgetting `session` on new entries.** Every new entity
needs provenance. If you're editing the file directly (not during a
session close-out), use `session: "direct"` or `session: "dashboard"`.

---

## For AI Tools

AI tools produce session close-out patches as fenced YAML code blocks
in conversation. The human (or Claude Code) applies the patch to the
state file. The patch format is designed for copy-paste application.

### Patch Block Format

A session close-out patch is a fenced YAML code block with section
labels indicating where each block goes and what operation to perform:

````
```yaml
# sessions — append
  - id: S012
    date: "2026-04-05"
    title: "Search quality audit and v1.1 gate review"
    purpose: evaluation
    mode: live-text
    participants:
      - { name: david, type: human, role: authority }
      - { name: claude, type: collaborator, role: driver }
    produced:
      - description: "Search quality audit report"
        purpose: analysis
        format: prose
        location: "session://S012"
    decisions_made: [ILD-9]
    state_changes:
      - "ILD-9 locked — search quality meets v1.1 gate threshold"
      - "DEF-1 resolved — content-aware embedding deployed"
      - "A-8 done — search audit complete"
    next_action: "Resolve v11-1 gate item and prepare v1.1 release."

# decisions — append
  - id: ILD-9
    title: "Search quality meets v1.1 gate: P95 relevance score above 0.82"
    status: locked
    session: S012
    blast_radius: medium

# concerns — update DEF-1
    status: resolved
    resolution: "Implemented content-type-aware embedding with code block weighting"
    closed_in: S012

# actions — update A-8
    status: done
    resolution: "Audit complete. 94/100 queries returned relevant top-3 results."
    closed_in: S012

# milestones — update v1.1 item v11-1
    - { id: v11-1, text: "Semantic search using embedding-based similarity scoring", status: done, gate: true }
```
````

### Section Label Convention

Each block in the patch starts with a comment label:

| Label | Operation | Target |
|-------|-----------|--------|
| `# {section} — append` | Add entry at end of section | Section array |
| `# {section} — update {id}` | Modify fields on existing entry | Specific entry by ID |
| `# milestones — update {ms-id} item {item-id}` | Replace a milestone item line | Specific item |

### What AI Tools MUST Do

1. **Track all changes during the session.** Every decision made,
   status changed, concern raised, action created — accumulated for
   the close-out patch.
2. **Include the session entry.** Every close-out patch includes the
   new session record as its first block.
3. **Use exact IDs as find targets.** When updating an existing entry,
   the section label includes the entity ID so the applier can search
   for it.
4. **Produce the patch LAST.** The YAML patch is always the final
   deliverable of a session close-out, after any next-session prompt
   or other artifacts. This ensures the patch captures everything.
5. **Separate appends from updates.** New entries (append) and
   modifications to existing entries (update) are labeled differently
   so the applier knows which operation to perform.

### What AI Tools MUST NOT Do

1. **Never include the full state file.** The patch contains only
   new and changed entries, not a complete file replacement.
2. **Never reorder existing entries.** Even if the AI thinks a
   different order would be cleaner.
3. **Never modify immutable session fields.** If a past session has
   an error, note it in the current session, don't patch the old one.
4. **Never omit the session ID from the patch.** The session entry
   itself must be in the patch so the applier can verify the session
   ID is correct and sequential.
5. **Never produce a patch mid-session.** The patch captures the
   complete session outcome. Producing it early means changes after
   the patch are lost.

### The Two-Message Close-Out Flow

Session close-out follows a two-message sequence:

**Message 1:** Summary, session rename suggestion, next-action
recommendation, and optionally a next-session prompt. The human
reviews and may request changes.

**Message 2:** The YAML patch block. Produced after Message 1 is
finalized. This ensures the patch includes any next-session prompt
that was produced in Message 1 as a produced artifact.

Never combine these into one message. The human needs to review the
summary before the patch is finalized.

---

## For Automated Tools

Automated tools (Claude Code, pqdc-cc export, pqtf, custom scripts)
apply patches programmatically. These tools need precise rules for
parsing state files, locating entries, and writing changes.

### Reading the State File

Parse the file as YAML. The schema version is at `schema_version`.
Validate against pqdx/2.0.0 rules (or use the reference validator
`validateState()` from pqdc-cc). The ten entity-bearing sections are
arrays at fixed top-level keys.

### Locating Entries

Every entity has a unique ID within its section. To find an entry:

```
decisions[].id === "DR-001"
concerns[].id === "DEF-1"
actions[].id === "A-8"
sessions[].id === "S012"
queued[].id === "QI-1"
notes[].id === "N-1"
milestones[].id === "v1.1"
milestones[].items[].id === "v11-1"  (nested within parent milestone)
```

### Appending Entries

New entries are appended to the end of the relevant section array.
The entry must conform to the schema for that entity type — required
fields present, values in valid sets, provenance included.

### Updating Entries

To update an existing entry, locate it by ID, then modify the specific
fields. Rules:

1. **Only modify the fields that changed.** Don't rewrite the entire
   entry.
2. **Never change `id`, `session`, or `origin`** on any entry.
3. **Never change immutable session fields** (everything except
   `produced`).
4. **Add `closed_in` when transitioning to terminal status.**
5. **Add `resolution` when resolving a concern or completing an
   action.**
6. **Respect transition guards.** Don't set `status: complete` on a
   milestone that has `open` items.

### Serialization

When writing the modified state file back to disk:

1. Preserve the original file's comment structure as much as possible.
   Comments are not part of the YAML data model — round-tripping
   through a parser destroys them. Tools that need to preserve
   comments should use a comment-aware YAML library or text-based
   patching.
2. Use 2-space indentation.
3. Use flow-style for milestone items and participants.
4. Inline arrays (`decisions_made`, `depends_on`, `blocked_by`,
   `blocks`) stay on the same line as the key.
5. Multi-line strings use YAML folded scalar (`>`).

### The pqdc-cc Reference Implementation

pqdc-cc's patch pipeline serves as the reference:

- `diffState(baseline, current)` computes the difference between the
  file as loaded and the current editor state.
- `generatePatchYaml(diff)` produces a structured YAML patch from
  the diff.
- The Export function writes the complete state back to disk via
  browser download.
- The Watch mode detects external file changes and offers merge or
  keep-mine resolution.

Tools implementing their own patch pipeline should produce output
that `validateState()` accepts without errors or warnings.

### Idempotency

A well-formed patch applied twice should produce the same result as
applying it once. This means:

- Append operations should check whether the entry already exists
  (by ID) before appending.
- Update operations should be safe to re-apply if the field already
  has the target value.
- Tools SHOULD NOT error if a patch targets an entry that's already
  in the desired state.

---

## Appendix A: Migration from Pre-S027 State Files

Session S027 of the pqdx project introduced several naming changes,
new fields, and structural updates. Existing state files created before
these changes need a one-time migration. This appendix describes the
migration procedure.

### Scope

This migration applies to all `{project}.state.yaml` files created
under pqdx/2.0.0 before the S027 changes were adopted. The
`schema_version` does NOT change — this is an intra-version migration,
not a version bump. The schema is pqdx/2.0.0 before and after.

### Migration Steps

Apply these changes in order. Each step is independent — partial
migration is valid (the file remains parseable at every step).

#### Step 1: Rename ID- to ILD- (Decisions)

Find all decision entries with `id` matching `^ID-\d+$` and rename
the prefix to `ILD-`.

Also find and replace all references to `ID-N` patterns in:
- `decisions_made` arrays in session entries
- `state_changes` strings in session entries
- `depends_on` arrays on any entity
- `notes` fields (freeform text — use care with text replacement)
- `blocks` arrays on concern entries

```
Before: id: ILD-47
After:  id: ILD-47

Before: decisions_made: [DR-001, ILD-4, ILD-5]
After:  decisions_made: [DR-001, ILD-4, ILD-5]
```

**Caution with text fields:** `state_changes` and `notes` contain
freeform prose that may mention `ILD-47` in context. Replace these
references too, but review each match — the string `ID` appears in
English prose ("the ID field", "user ID") and must not be blindly
replaced. Target the pattern `ID-\d+` specifically.

#### Step 2: Rename DS-Q to QI- (Queued Items)

Find all queued entries with `id` matching `^DS-Q\d+$` and rename
to `QI-N` (where N is the same number).

Also find and replace references in `depends_on`, `state_changes`,
and `notes` fields.

```
Before: id: DS-Q1
After:  id: QI-1

Before: depends_on: [DS-Q2]
After:  depends_on: [QI-2]
```

#### Step 3: Rename source to session (Actions)

Find all action entries. Rename the `source` field to `session`.
Values do not change — they are already session IDs.

```
Before:
  - id: A-1
    text: "Complete the search pipeline"
    source: S009

After:
  - id: A-1
    text: "Complete the search pipeline"
    session: S009
```

#### Step 4: Rename source to session (Notes)

Find all note entries. Rename the `source` field to `session`.
Values do not change.

```
Before:
  - date: "2026-03-01"
    source: S005
    text: "Memory profiling revealed..."

After:
  - id: N-1
    date: "2026-03-01"
    session: S005
    text: "Memory profiling revealed..."
```

Note: this step combines with Step 6 (adding `id` to notes). Apply
both together.

#### Step 5: Rename resolved_in to closed_in (Concerns)

Find all concern entries with a `resolved_in` field. Rename to
`closed_in`. Values do not change.

```
Before: resolved_in: S006
After:  closed_in: S006
```

#### Step 6: Add id to Notes

Notes previously had no `id` field. Assign IDs sequentially based on
array position:

```
First note:  id: N-1
Second note: id: N-2
Third note:  id: N-3
...
```

#### Step 7: Rename completed_date to completed (Milestones)

Find all milestone entries with a `completed_date` field. Rename to
`completed`. Values do not change.

```
Before: completed_date: "2026-03-05"
After:  completed: "2026-03-05"
```

#### Step 8: Add New Optional Fields (Non-Breaking)

These fields are optional. Adding them improves the file but is not
required for conformance. Apply as governance practice allows:

- **`closed_in`** on actions with `status: done` or `cancelled`:
  identify the session that closed each action and add
  `closed_in: SNNN`.
- **`closed_in`** on milestones with `status: complete` or `cancelled`:
  identify the completing session.
- **`closed_in`** on queued items with `status: complete` or
  `cancelled`: identify the completing session.
- **`session`** on queued items (newly MUST): identify the session
  that created each queued item.
- **`priority`** on actions and queued items: add if known.
- **`visibility`** on entities with non-default visibility: add
  only where an explicit override is needed.
- **`origin`** on actions and notes where the provenance source
  differs from the session.

### Automated Migration Script

For projects with Claude Code or similar tooling, the mechanical
steps (1–7) can be scripted. The following pseudocode covers the
find-replace operations:

```
For each .state.yaml file:

  1. Replace /\bID-(\d+)\b/ with ILD-$1
     — in id fields, decisions_made arrays, state_changes, depends_on,
       blocks, notes text
     — EXCLUDE: matches inside "session ID", "entity ID", etc.
       (use word-boundary matching on the pattern ID-\d+)

  2. Replace /\bDS-Q(\d+)\b/ with QI-$1
     — in id fields, depends_on, state_changes, notes text

  3. In actions[]: rename field "source" → "session"

  4. In notes[]: rename field "source" → "session"

  5. In concerns[]: rename field "resolved_in" → "closed_in"

  6. In notes[]: add id: N-{index+1} as first field of each entry

  7. In milestones[]: rename field "completed_date" → "completed"

  Validate result with validateState()
```

### Verification

After migration, the file should:

1. Parse as valid YAML
2. Pass `validateState()` with zero errors
3. Produce zero warnings for renamed fields (no `source` on actions,
   no `resolved_in` on concerns, no `ID-` prefixes, no `DS-Q`
   prefixes, no `completed_date` on milestones)
4. Have all notes entries carrying an `id` field

### Migration Order Across Projects

If you manage multiple state files, migrate in dependency order:
files that are referenced by cross-project references
(`{project}:{entity-id}`) should be migrated first, so that
references in downstream files can be updated to the new prefixes.

In practice, most cross-project references target DRs, concerns, and
actions — which do not change prefix. The ILD and QI renames affect
entity types that are rarely referenced cross-project. But verify
before assuming.

---

## Appendix B: Patch Validation Checklist

Before applying any patch, verify:

- [ ] Every new entry has a `session` field (provenance)
- [ ] Every new entry has a unique `id` within its section
- [ ] New IDs follow the correct pattern for their type
- [ ] Status values are in the valid set for the entity type
- [ ] Status transitions are valid (not violating terminal states)
- [ ] `closed_in` is present on entities transitioning to terminal
- [ ] `resolution` is present on concerns being resolved
- [ ] `resolution` is present on actions being marked done
- [ ] Inline arrays are on the same line as their key
- [ ] Milestone items use flow-style
- [ ] Session participants use flow-style
- [ ] Indentation is 2 spaces throughout
- [ ] No entries were deleted or reordered

---

## Appendix C: Post-2.0.0 Additive Changes

The pqdx schema is at `pqdx/2.0.0` and stays there pending external
release (per ILD-237). All ecosystem projects are internal until
pqdx-rs is fully validated and externally released, so version-literal
discipline is deferred — no minor bumps for internal additive work.

Additive content layered onto `pqdx/2.0.0` since the original lock:

### DR-009 — Trust boundary suite (S036/S037)

- §10 Trust Boundary chapter (full)
- `identity.posture` OPTIONAL field
- `visibility` OPTIONAL field on every stateful entity
- `projection` top-level key for generated public projections
- §10.5 skeleton field sets (normative)
- §10.6 projection algorithm (normative)
- §11.10–§11.12 new validation rules

Existing state files without these fields remain valid (defaults
apply per §10.3 section visibility defaults).

### DR-010 — Milestone opening provenance (S038)

- Milestone gains `session` OPTIONAL field — session that introduced
  this milestone into the project's plan
- Milestone gains `origin` OPTIONAL field — freeform, where the
  milestone originally came from when different from session
- §10.5 milestone skeleton field set gains `session`

Existing state files without milestone session/origin remain valid.

### §11.18 Validator Diagnostic Completeness — DR-011 (2026-05-14)

**Origin:** pqdx-rs:DR-003 (handed off to pqdx Standard for portable contract ratification)

**Change:** New normative rule at `pqdx-schema-spec.md §11.18`. A pqdx validator MUST emit all diagnostics surfaced by its validation rule set within a single invocation. Narrow exception for YAML 1.2 grammar-level corruption only. Structural, referential, lifecycle, visibility, and projection violations all MUST accumulate.

**Schema version:** No change — stays `pqdx/2.0.0`. Additive content; downstream detection via Signal A–E content-presence pattern established by DR-010.

**Signals produced:**
- **Signal A:** §11.18 wording in `pqdx-schema-spec.md`
- **Signal B:** §11.18 comment block in `pqdx-schema-spec.yaml` and updated §1.7 validation rules reference
- **Signal C:** Not produced (no fixture changes needed — §11.18 is a behavior rule, not a state-file-shape rule)
- **Signal D:** Not produced (no new schema fields)
- **Signal E:** This appendix entry

**Downstream propagation:**
- `pqdx-rs` detects signals; `pqdx-rs:DR-003` moves from `draft` to `accepted`; `pqdx-rs:S008+` opens for value-tree-then-validate architecture implementation.
- `pqsn-mcp` requires coordinated update via separate pqsn-project session: three `throw pqsnError` calls (DUPLICATE_ID, ENTRY_NOT_FOUND, TERMINAL_STATE) convert to `errors.push` accumulation; `apply_patch` orchestrator iterates all operations and aggregates diagnostics into a unified set. Tracked as a concern in pqsn-project's state file until resolved.
- `pqdx-conformance` receives new multi-error fixtures from `pqdx-rs:S008+`: at least 2 per validator pass category, N ≥ 3 expected diagnostics each, plus at least 1 cross-cutting fixture.
- Future pqdx implementations (`pqdx-py`, `pqdx-ts`, others) consume the conformance fixtures from inception.
- All Wave 1+ Rust ecosystem tools inherit §11.18 conformance via dependency on `pqdx-rs`.

**Migration guidance.** No migration required for any of the above.
Existing entries without the new fields remain valid. Operators MAY
backfill the fields where the introducing session is recoverable from
session state_changes, or leave existing entries as-is.

**Example (DR-010 milestone session):**

```yaml
- id: v1.0
  name: "v1.0 — Core Platform"
  status: complete
  target: "2026-03-01"
  completed: "2026-03-05"
  closed_in: S008
  session: S001    # NEW (DR-010) — introducing session
  notes: >
    First production release. Bootstrapped at S001, delivered S008.
```

**Distribution.** Distributed via `pqsn spec push` to piqued-pub,
pqsn, pqdx-rs, piqued-cc. Validators in each downstream project
update to accept the field; downstream state files MAY adopt at
their own pace. Detection signal for tools watching for spec updates
is **content-presence** (e.g., presence of `### session` subsection
under §3.1; presence of `session:` on fixture milestones), not
version-literal comparison — the version literal stays at
`pqdx/2.0.0` per ILD-237.

**At external release moment**, all accumulated additive content
since 2.0.0 will roll up into a single version bump captured in the
release-triggering DR. Until that moment, this appendix is the
content manifest.

---

*PQDX-GDE-0001 — pqdx/2.0.0 Patching Guide*
*© 2026 Our Collaborative Space, LLC — CC-BY-ND 4.0*
