$npx -y skills add OpenCairn/OpenCairn --skill park --agent claude-code
Installs into .claude/skills of the current project.
Are you the author of Park?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/opencairn-park)
---
name: park
description: Capture session with bookkeeping — quality gate, session log, project-doc update, open-loop routing, and a close-out audit that repeats until clean.
---
# Park - Session Capture
**Scoped rule loading:** `_shared-rules-planning.md` before project/task routing; `_shared-rules-reviewer.md` before attribution or reviewer preparation; `_shared-rules-content.md` before verbatim or structural operations. Read the applicable numbered sections at that point, from the same directory as the core `_shared-rules.md`; do not preload unrelated supplements.
Capture a work session: proportional quality gate, session log, project-doc update, reference-graph propagation, open-loop routing, and a close-out audit that repeats until clean.
**Args:** `$park --quick` requests the fail-closed quick path below. Quick mode activates only when the complete argument string is exactly `--quick`; bare `$park` and every other argument string, including `--quick foo`, run the full protocol. Never infer quick mode from a quiet-looking session.
**On the full path, despatch propagation at Step 4b only for a checked nonempty identifier enumeration; recheck and collect at Step 8.** Checked-empty skips only this agent, not the quality gates, backfill, mechanical verification or independent Step 9 close-out reviewer; it is not quick mode. When despatched, it starts after park-time moves are known, but before the main quality pass, and runs in the background across Step 4c and Steps 5 and 7. Park's cost is dominated by model turns, not by its scripts, so a blocking sub-agent is dead wall clock. **The overlap is not conflict-free:** the agent writes planning and hub files that Steps 4c, 5 and 7 may also touch. `locked-edit.sh` prevents lost updates, not stale-preimage conflicts — an exit 2/3 on either side means re-read and recompute (§5), and that is the expected cost of the overlap, not a malfunction.
**Concurrent parks are safe.** All shared-file writes go through the locking scripts (`write-session.sh`, `update-session-section.sh`, `backfill-files-updated.sh`, `locked-edit.sh`, `write-tickler.sh` — `_shared-rules.md` §5; exit 2/3 = a parallel writer changed the region: re-read and recompute, don't loop-retry; lock timeouts are §5 Failure mode B — kill the hung script, never fall back to a raw write). After each `locked-edit.sh` call, apply §5's anchored padded-line residue check and region-scoped malformed-fragment check; classify documentation hits before repairing. `$goodnight` uses the same machinery; parking before goodnight keeps its daily report coherent.
## Steps
### 0. Setup
**Delegation-unavailable checkpoint (full path only).** Check the actual harness tools and governing session instructions before requiring a propagation or audit seat. Standing skill authorisation permits the required delegation unless an explicit higher-priority restriction forbids it. A busy slot, observation timeout or still-running seat is not unavailability: collect or continue that existing seat. If a required native operation (`collaboration.spawn_agent` for a new seat, or same-seat continuation for recovery) is absent, explicitly prohibited or returns a terminal unsupported error, retain the exact cause. Do not inline the independent audit, invent a clean report or change models.
Use Steps 2(a–c) and 3 to persist the attributable inventory, existing work product and session narrative. Before Step 3, perform Step 4c’s proportional quality inspection and §19 SOURCE check on that work; this checkpoint waives only Step 4b’s delegation-before-quality ordering. Record any unfinished quality/propagation/routing gates and the actual delegation reason in Pickup Context. If the failure happens later, reconcile the work already landed into that same assigned entry. Defer new structural/routing work and Steps 2(d), 4–9 that have not run. This explicit checkpoint branch permits only Step 10's monitor of the work actually performed, Step 11's transcript export and measurement close-out with outcome `blocked`; their outputs do not discharge review. When no audit began, report `✗ Audit: not run (delegation unavailable — <verified reason>)`; when an existing audit cannot finish, report `✗ Audit: not clean — <verified reason>`. Report `✗ Park: incomplete` and the actual saved log/export results. Offer to resume the saved checkpoint in a harness with independent review. An alternative completion policy requires explicit user authorisation; do not treat this branch as Park completion or use Step 12's success message.
**Full-run measurements:** keep a count of completed independent audit reports, rounds of fixes after those reports, and distinct confirmed audit findings. Before every completion or blocked return (including quick mode), record the observed counts as described under **Measurement close-out** below. Missing evidence stays unknown; human review time requires the user's report. Passive transcript collection measures the full request, including the final response.
Run `"${VAULT_PATH:?VAULT_PATH not set}/.claude/scripts/resolve-vault.sh"`, then `"$VAULT_PATH/.claude/scripts/check-archive-layout.sh" --enforce "$VAULT_PATH"`; abort on either error (usual causes: `VAULT_PATH` unset, or a pending archive migration). Resolve `CODEX_ROOT="${CODEX_HOME:-$HOME/.codex}"`, read `$CODEX_ROOT/skills/_shared-rules.md`, and apply it throughout. `{VAULT}` below = the resolved vault path: the part after `VAULT_PATH=` in the resolver's output (§1). Set `PARK_REVIEW="$CODEX_ROOT/skills/park/scripts/park-review.py"` and `PARK_ARTIFACT="{VAULT}/.claude/scripts/park-artifact.py"`; abort if either is absent.
**Startup bundle (normal first tool call).** Codex has no UserPromptSubmit hook here. If the installed helper exists, include `python3 "{VAULT}/.claude/scripts/park-preflight.py" --manual --session-id "<actual parent session id>"` in setup's first tool call, after resolving the vault; otherwise run the normal setup/ledger reads. Inspect actual command statuses and source hashes before reusing checked observations. This is a normal tool call, not pre-first-model delivery. Its task candidates never replace fresh Step 7 literal/semantic dedup, attribution, propagation, verification or independent audit.
Get date and time from bash — `date +"%Y-%m-%d"` and `LC_TIME=C date +"%I:%M%p" | tr '[:upper:]' '[:lower:]'` (`LC_TIME=C` guards `%p`, which expands empty under many locales).
Derive and display today's session-log path mechanically:
```bash
TODAY="$(date +%F)"
SESSION_DIR="{VAULT}/06 Archive/OpenCairn/Session Logs"
SESSION_LOG="$SESSION_DIR/$TODAY.md"
[ "$(dirname "$SESSION_LOG")" = "$SESSION_DIR" ] || { echo "ERROR: session log escaped current-log directory: $SESSION_LOG" >&2; exit 1; }
printf 'Session log path: %s\n' "$SESSION_LOG"
```
**Path invariant:** a new entry for today goes directly in `Session Logs/YYYY-MM-DD.md`, never `Session Logs/YYYY/YYYY-MM-DD.md`. Year subfolders contain archived logs; an archived path loaded by `$pickup` must not be carried into this park. Shell variables do not persist between tool calls, so re-derive and assert `SESSION_LOG` inside every later call that writes or edits today's log, or use the exact path printed above. A later-date continuation creates a new entry here, retaining a link to the earlier session.
**Parboil draft.** A mid-session snapshot may already hold this park's expensive half — session narrative, identifier enumeration, open-loop list — derived while the context was still cheap:
```bash
SID="$(. "{VAULT}/.claude/scripts/lib-session.sh"; _session_id)"
S="${CLAUDE_CONFIG_DIR:-$HOME/.claude}/.session-state/${SID:-none}"
[ -f "$S.parboil.md" ] && { echo "LEDGER NOW: $(wc -l < "$S.tsv" 2>/dev/null || echo 0)"; cat "$S.parboil.md"; } || echo "no parboil draft"
```
Compare the draft's `SNAPSHOT-LEDGER-LINES: K` against `LEDGER NOW`. K is the exact `wc -l < "$S.tsv"` line count captured by the hook, not a count of bullets, files, or sections in the draft. **An equal count does not mean nothing changed** — the ledger records file writes only, so decisions taken, messages sent, bookings made and shell-mediated work all move the session on without moving the number. Equal count licenses adopting the *Files* sections, not the narrative: re-check Summary, Key Insights, Next Steps and the enumeration against what happened after the snapshot regardless. Higher → adopt as the base, and **read the delta paths** before extending the enumeration; a draft enumeration carried forward unread becomes the Step 6 agent's grep list, so an error there is a silent reference-graph miss, not a cosmetic one.
Either way the draft is a cheap artefact, not authority: anything in it that a file you read this session contradicts loses. Bare and escalated parks run Steps 2, 4–5 and 8–11 in full; an eligible explicit quick park follows the checked branch below. A parboil trigger that fires after this park has begun is satisfied by the active park; do not write or refresh a shadow snapshot mid-park.
**Full-path evidence receipts.** Do not create evidence or pre-state receipts before the explicit quick gate. When entering the full path (bare Park or a quick escalation), begin a fresh run once before any capture, classification or preparation:
```bash
python3 "$PARK_REVIEW" begin-run
```
This retains earlier runs under the real harness session ID, including same-session re-parks and merged entries. Keep the active generation throughout remediation and re-audit; do not begin another on a retry. Prior clean file audits remain reusable only while their hashes match. Then capture source text for Step 9 using §16's source-size and per-claim coverage rules instead of reconstructing it later:
```bash
python3 "$PARK_REVIEW" capture --kind prestate --label "<what this establishes>" --source "<path/tool>" <<'EOF'
<excerpt>
EOF
python3 "$PARK_REVIEW" capture --kind evidence --label "<source name>" --source "<URL/path/message>" --provenance primary <<'EOF'
<excerpt>
EOF
```
For each decision-bearing command, retain its literal invocation, working directory, exit status and discriminating stdout/stderr before cleanup or brief assembly. State which observation separates pass from fail; include empty/error output when it controls a conclusion. A prose success summary, recreated command or bare exit 0 is not the evidence. Preserve the raw result in the full-path evidence/pre-state handoff, with an exact path and digest for any companion file the reviewer must inspect. Capture that raw result with `capture --kind evidence` or `--kind prestate` as appropriate.
Use `secondary` or `unverified` where §16 requires it. Add receipts as evidence arrives; all excerpts for the same `--source` are retained in capture order. A later excerpt does not replace an earlier one. On a bare or escalated full path, follow §16's source-union, source-size and per-claim coverage rules: capture short sources whole and the supporting passages from longer sources. Step 9 then assembles the brief from receipts; it never reconstructs it from the transcript. An eligible quick path creates only its verifier receipt.
### 1. Merge-continuation check
If this session directly continues a just-parked session dated today (a `$pickup` loaded it and the work finishes its loose end, or this same conversation already parked today and has worked on since), `--quick` is ineligible: print `↪ --quick escalated to full park: merge-continuation`, then update the existing entry via `update-session-section.sh <log> N <section> [--replace]` (append to Summary / Files sections; `--replace` for Next Steps and Pickup Context), and run Steps 2 and 4–11 against the merged session's N. **Reconcile the whole merged entry**, including surviving Summary, Key Insights, Next Steps and Pickup Context; confirm inherited artefact classifications still fit. A continuation on a later calendar date creates a new entry with a continuation link. **Escape hatch:** if the addendum would exceed ~2× the target's current summary or touch >3 files unrelated to its topic, start a new session instead — a session titled X that hides hours of Y is invisible to topic search. Completion: `✓ Merged into Session N — [what was added]`. Otherwise proceed normally.
### 2. Inventory and change classification
(a) **Enumerate every file the session created or edited** — vault and non-vault. Recall reliably under-reports the non-vault half (early config edits, tooling side-effects, hook-written files), so derive it mechanically:
```bash
"{VAULT}/.claude/scripts/session-ledger.sh" --read [-m MINUTES]
"{VAULT}/.claude/scripts/park-files.sh" "{VAULT}" [-m MINUTES] [repo ...]
```
Under this harness no Write/Edit hook exists: `locked-edit.sh`, `locked-ingress.sh` and the session-log writers (`write-session.sh`, `update-session-section.sh`, `backfill-files-updated.sh`) self-ledger writes, including Obsidian move collateral performed by `locked-edit.sh`. Other raw shell, script and MCP mutations can bypass those recorders, so the mtime/ctime and git candidates remain the backstop. The ledger records the session ID; its `agents` value is `?` when the shell writer has no agent identity. That value does not identify `main` or a sub-agent. Reconcile it with the session's actual tool/action evidence, and retain the explicit parent-session prefix requirement for dispatched agents. Two distinct failure observables, both meaning "fall back to park-files.sh alone and say so": `NOTE: no ledger` (no self-ledger write has landed yet), and `ERROR: no session id` (no harness session id resolvable — see lib-session.sh) with **exit 1** — that exit 1 is expected here and is not the invocation error Step 3 warns about. A sub-agent's writes ledger under **this** session's id only when its brief carries the `env OPENCAIRN_SESSION_ID=<this session's id>` prefix on every `locked-edit.sh` call (mandatory in every despatch block below — a sub-agent otherwise gets its own thread id and its edits land in a separate ledger). With the prefix, the Step 4b propagation agent's edits appear inline in your own list. `# CONCURRENT-SESSION` lines are other sessions — or a sub-agent despatched without the prefix; account for your own sub-agents before excluding per §20. That listing looks back 240 minutes: for a longer session pass `-m` with minutes over-covering the whole session, since `-m` also drops this session's own rows older than it. The `# ledger begins` line is the coverage window — earlier writes are not in it, which is one more reason park-files.sh stays.
The parent-session ledger key and writer identity are separate. Keep the parent `OPENCAIRN_SESSION_ID` prefix on dispatched writes. Self-ledgered writes use explicit `OPENCAIRN_AGENT_ID` when supplied, otherwise `codex:<the actual writer CODEX_THREAD_ID>`; unidentified shell writers remain `?`. Do not infer main/child identity from time or convert legacy unknown rows to main. For a harness without a writer thread ID, pass `OPENCAIRN_AGENT_ID` only when the actual agent handle is known.
Reconcile park-files.sh's candidate lines (sync-receipt, config/runtime mtimes or ctimes, repo status, `[vault]` body files, `[git-diff]` working-tree changes, `[git-history]` recent vault commits and transient surfaces) against the ledger and the session's actual tool/action evidence. Git rows contain the repo root, status and repo-relative path; rename rows contain both the original and destination paths. Resolve each against that repo root, use deletion and rename rows to recover removed or moved paths, and preserve intermediate history paths that a later auto-save removed. These are candidates, not attribution: a time-window match does not make another session's or already-parked work yours. Add each attributable path you were about to omit, and exclude other sessions' work per §20.
#### Explicit `--quick` gate
Run this gate only when the complete argument string is exactly `--quick`, after inventory and before classification, full reads, persistence, routing, evidence capture, or agent despatch. Supply every repository touched during the session to `park-files.sh`.
1. Review the session's actual tool calls and shell commands. Display one row per filesystem-mutating action as `tool/action | resolved path | ledger/park-files/repo coverage`. Every action must resolve to an inventoried path; print `none` only when there were no such actions. Any attributed creation, update, or deletion fails the gate, as does a mutation outside inventory coverage or an action whose target or ownership is uncertain.
2. Check the at-risk surfaces before Step 4: the inventory's `[transient]` hits, each recent `~/.claude/plans` file, and assistant-authored drafts that exist only in the conversation. Any candidate fails the gate.
3. Display checked results for external mutations (messages sent, bookings, purchases, pushes, remote edits); decisions, project-state changes, changed identifiers, or factual corrections requiring durable reconciliation; and resumable next steps or open loops. Read-only queries are non-mutating. Any positive or uncertain category fails the gate.
4. Display the evidence before branching: attributed-file count and inventory sources; mutation rows; transient and plan-file counts; conversation-only draft result; and world-state, project/identifier, and open-loop results. Nil must be a checked result, never a default.
If any condition fails, print `↪ --quick escalated to full park: [reasons]`, perform the deferred evidence/pre-state capture, and continue at Step 2(b), without rerunning setup or inventory. Otherwise follow the quick success path immediately; do not enter the remaining full-path steps.
#### Quick success path
Begin a fresh generation once, now that eligibility passed, before writing the log or creating its verifier receipt:
```bash
python3 "$PARK_REVIEW" begin-run
```
Keep this generation if a late mutation or verifier failure escalates the quick run; do not call `begin-run` again.
1. Apply Step 3's metadata and locked `write-session.sh --auto-number` mechanics once. Write a sparse entry containing every normal heading, including `Files Created`, `Files Updated`, and `Files Deleted`, with bare `None` under each Files heading; `None` under Key Insights; `None — work completed` under Next Steps and `**For next session:**`; and the normal `**Project:**` line.
2. Immediately rerun the same ledger, `park-files.sh`, and local-mutation census. This second inventory—not `park-verify.sh`—detects late files. Permit only the exact assigned session-log mutation. Any new or ambiguously attributed non-bookkeeping delta prints an escalation reason and retains the assigned N: never rerun Step 3; perform the deferred evidence/pre-state capture; apply Step 2(b)'s proportional classification and inspection to every late created or updated artefact, while recording late deleted paths from ledger, pre-state, or tool evidence without reading or classifying them; replace every affected `Summary`, `Key Insights / Decisions`, `Next Steps / Open Loops`, `Files Created`, `Files Updated`, `Files Deleted`, and `Pickup Context` section via `update-session-section.sh --replace`; finish Step 2(b–d); then resume the full path at Step 4.
3. With the second inventory still nil, re-derive `SESSION_LOG` per Step 0, then run the verifier through `python3 "$PARK_REVIEW" run-verifier -- "{VAULT}/.claude/scripts/park-verify.sh" "{VAULT}" "$SESSION_LOG" N` with no `--ident` or `--touched` arguments, preserving the verifier receipt. The verifier does not discover omitted files. Repair and rerun only failures confined to the newly assigned session block. A failure involving another file, a pre-existing entry, or any correction outside that block escalates before remediation: retain N, perform the deferred evidence/pre-state capture, convert every affected sparse section through the same `update-session-section.sh --replace` contract in item 2, and resume the full path at Step 4. Triage any unexpected REVIEW rather than treating it as file discovery.
4. Run the Step 10 skill monitor against the quick execution, then perform Step 11 transcript export. These outputs and the verifier receipt are Park-owned bookkeeping and do not invalidate eligibility. Branch past propagation capture, `build`, reviewer despatch, and `record-audit`; do not manufacture propagation or audit receipts. Skip project-doc mutation, reference-graph propagation and Step 8 collection, open-loop routing, semantic file review/classification, and the bounded audit because their inputs were checked nil.
5. Finish with the quick completion contract below and stop:
```text
✓ Quick eligibility: PASS — 0 attributed files; no at-risk work; no changed world/project/identifier state; no open loops
✓ Session N saved: 06 Archive/OpenCairn/Session Logs/YYYY-MM-DD.md
✓ Quality check: no attributed files
✓ park-verify: PASS
✓ Reference graph: skipped (--quick; no changed identifiers or world state)
✓ Open loops: none (quick eligibility)
✓ Audit: skipped (--quick)
✓ Skill monitor: [no gaps | N logged]
✓ Transcript exported: N sessions
Quick parked.
[Short session summary.]
```
Keep every attributed lexical path spelling, including symlink aliases, in the Files record and preparation input. Preparation deduplicates content review by resolved physical identity while preserving aliases for attribution in the manifest. A missing or dangling retained path fails preparation; it is not silently treated as a deletion.
Older session logs touched only by `add-forward-link.sh` may use its exact producer proof automatically through preparation. The helper requires a complete forward-link-only receipt chain for all logged operations ending at current bytes, with source/target session context checked. Explicit classification takes precedence. If any proof or chain is missing, changed or includes other writes, retain normal coverage; do not exempt a file merely because it is a session log or the command reported success.
(b) **Classify each attributed file before reading it again:**
First import link-healing receipts from any locked Obsidian move in this session:
```bash
python3 "$PARK_REVIEW" import-move-heals --vault "{VAULT}"
```
This validates each receipt's exact link-target-only delta and current hash, then pre-classifies healed files as mechanical, including files under `06 Archive/`. Inherited lint is accepted only when its fingerprint is unchanged. Mixed or later-edited files remain subject to ordinary classification; the move destination itself does too unless its only content delta was link healing. Treat an import failure as a quality-gate failure and never repair unrelated archive prose or lint.
- `semantic` — session-authored, substantively rewritten, or otherwise meaning-bearing. A bounded file uses `python3 "$PARK_REVIEW" classify --vault "{VAULT}" --path "<file>" --semantic --reason "<created|substantive>"` and receives one full coherence read at Step 4c. For a large artefact where that is disproportionate, add `--targeted --inspection-target "<page/section/line range or outcome-specific check>"` (repeat as needed); Step 4c reviews only that delta-plus-context coverage.
- `reference` — a local manual, textbook, paper, dataset or other source copied/downloaded but not authored this session. Register it with `python3 "$PARK_REVIEW" classify --vault "{VAULT}" --path "<file>" --reference --reason "<why retained>"`, adding `--inspection-target "<passage or claim actually used>"` for each source-dependent claim. The shared `park-artifact.py` helper creates a hash-bound source snapshot, metadata and text index in the cross-session/cross-lane `.park-artifacts` cache; extraction is not a whole-source read attestation. A reference with no source-dependent session claim needs identity/metadata verification only.
- `mechanical` — only literal locator/token substitutions, with no other prose or state change in the same locked-edit payload. Register every exact pair and destination in one call: `python3 "$PARK_REVIEW" classify --vault "{VAULT}" --path "<file>" --mechanical --replace "<OLD>" "<NEW>" --target "<resolved target>"` (repeat `--replace` / `--target` as needed). For substitutions that change no locator, use `--no-locator-target` instead of `--target`; never pass both. The helper requires the current session's locked-edit receipts, proves their hash chain and exact declared delta, then checks old-locator absence, new-locator presence, target/anchor existence, separator integrity and lint. A missing receipt, undeclared delta or failed check means semantic; choose bounded-full or targeted coverage by the artefact rules above. If and only if lint predates an otherwise exact mechanical delta, rerun that classification with `--allow-inherited-lint`; the receipt records the exception for the verifier's exact-path gate.
- `nonlocal` — an exact remote artefact, or a local root-only/secret-bearing file that must not be copied into a review brief. Use sanitised identity/configuration checks as evidence, never secret contents. Register it with `--nonlocal --reason "<surface represented by evidence>"`; the reviewer sees it only through evidence receipts. Deleted files stay in `Files Deleted` and need no read classification.
Display the inventory with its classification and declared coverage. Do not perform the quality read here; Step 4b's enumeration check and any required despatch must precede the expensive quality pass.
(c) **Earlier audit reuse:** if a clean independent reviewer already full-read any attributed file in this or an earlier session and returned its SHA-256, pipe that report once into `python3 "$PARK_REVIEW" record-audit --reviewer "<seat>" --vault "{VAULT}" --file "<file>" ...`. The helper records only hashes explicitly attested in a clean report. Step 9 reuses the receipt only while the current file hash remains identical.
(d) **Pre-log propagation pass:** Step 3 has not written the log yet, so stale living content is reconciled here in editing mode rather than recalled later in record-keeping mode. Build the initial Step 6 enumeration now: every `old → new` pair plus, for each named-entity claim, the entity's canonical identifiers. Run each search yourself in Step 6's exact shape (working directory `{VAULT}`, one Step 6 live-text search per identifier with Step 6's exclusion globs), reconcile every stale living hit per §12 through `locked-edit.sh`, and report `Pre-log propagation: I identifiers, H hits, U updated`. For a substantive reframe (scope, timeline, audience, constraint, underlying premise), also list the affected project's sibling artefacts (files in its folder, files its hub links, live planning lines that reference it) and read each one's framing for a premise the reframe invalidated: framing only, not the Step 4c read; report `Sibling framing: S reviewed, U updated`. This is the initial enumeration Step 4b despatches; the seat's sweep and Step 8's final delta remain the backstop, not the primary catch. **New-entity check** (same pass, the complementary failure class: duplication rather than staleness): list every proper noun the session introduced as a new vault concept (person, project, organisation, product, venue, event, place) and run one `rg --hidden --no-ignore -F --type md -i -g '!**/.git/**' -- '<name>' .` call per name from `{VAULT}` across the whole vault, `06 Archive/` included, since a prior mention under another framing is exactly what this catches. Name each existing home found and merge or cross-link rather than leaving a parallel entry. Report `New entities: E searched` with the hit count per name, zeros included.
### 3. Write the session log
**Metadata:** topic (descriptive weeks later — "Wezterm config fix", not "Terminal stuff"); project link per §2 (shipped one-shot work → area hub) and §3. Display: `Session: [Topic] | Project: [[03 Projects/Name]] (or "None")`.
**Body format is a parse contract** — `$pickup`, `pickup-scan.sh`, and `$weekly-review` key on these exact headings:
```markdown
### Summary
[2-4 sentence narrative — outcomes and decisions]
### Key Insights / Decisions
- ...
### Next Steps / Open Loops
- [specific enough to resume without re-reading the conversation; plain bullets, never checkboxes — session logs are records, not task trackers]
### Files Created
- path - purpose [one path per line; never brace-expand or comma-group; bare `None` if empty; same for Updated]
### Files Updated
- path - what changed and why [one path per line; never brace-expand or comma-group; include load-bearing external paths]
### Files Deleted
- path - why removed [one path per line; never brace-expand or comma-group; omit the section entirely if none]
### Pickup Context
**For next session:** [one immediately-actionable sentence]
**Continues:** [[06 Archive/OpenCairn/Session Logs/YYYY-MM-DD]] (Session X - Topic) [only if continuing]
**Project:** [exact link from the metadata line]
```
Use full vault-relative paths inside the vault, and absolute or `~`-prefixed paths outside the vault. `park-verify.sh` parses these rows: each begins with one complete concrete path, never a glob, range, prose stand-in or `~/x, y, z` abbreviation. A file that predates the session goes under Updated, a moved one under Updated at its new path.
**Write it:**
```bash
TODAY="$(date +%F)"
SESSION_DIR="{VAULT}/06 Archive/OpenCairn/Session Logs"
SESSION_LOG="$SESSION_DIR/$TODAY.md"
[ "$(dirname "$SESSION_LOG")" = "$SESSION_DIR" ] || { echo "ERROR: session log escaped current-log directory: $SESSION_LOG" >&2; exit 1; }
cat << 'EOF' | "{VAULT}/.claude/scripts/write-session.sh" "$SESSION_LOG" --auto-number "TOPIC" "HH:MMam/pm"
[body — NO `## Session N` heading; the script assigns N inside the file lock and rejects a supplied heading]
EOF
```
The script handles locking, file/header creation, and atomic numbering — parallel parks can't collide, which is why N is never pre-computed. Its stdout ends `Session number assigned: N`: **that N is canonical for every later step** — display it. On a `Lock timeout` stderr message retry once; any other exit-1 means the invocation is wrong — fix it, don't retry.
**Continuation link** (only if continuing previous work): add the back-link to the original session via `"{VAULT}/.claude/scripts/add-forward-link.sh" --continued-in <session-file> <orig-N> <new-N> "<topic>" [<target-date>.md]`. Continuation links are the only session-to-session links (chronology is already file order), and they are file-level — never `#Session N` heading anchors (§13). Script failure logs a warning, doesn't fail the park.
### 4. At-risk work product
Three checks:
- **Conversation-only drafts** Claude composed (emails, messages, analysis, plans) exist only as text output — write each to its semantic vault home. Drafts the user authored/pasted themselves are not at-risk.
- **Transient surfaces** (Scratchpad, Inbox captures, daily notes) are cleared on a cadence — `park-files.sh`'s `[transient]` lines list this-session candidates mechanically (memory-gating is the failure this check exists to prevent). Read each hit; tasks correctly filed in a configured map’s retained task sections are already at their task home and stay there. Move other durable this-session work product to its semantic home and update every reference to the old location. **Exception:** `$reply` draft sections (headings starting `**Reply to `) need explicit per-draft user confirmation before removal (§11). Pre-existing cross-session content is the user's working buffer — leave it (`$weekly-hygiene`'s job).
- **Claude-internal files** live outside the vault, so `park-files.sh`'s vault-only transient scan cannot see them: run `find ~/.claude/plans -type f -mmin -<session minutes>` and Read every hit individually. A sub-agent's output (`*-agent-*.md`) shares its parent plan's name prefix but is a separate document with its own migration status — "the plan was migrated" is not a verdict on it. Migrate standalone reference material to its semantic vault home; leave spent execution plans (`$weekly-hygiene` owns their cleanup).
Output: `✓ No at-risk work product to persist` or `🔧 Persisted N item(s): [paths]`.
### 4b. Check enumeration; despatch propagation if nonempty (background)
**Do this here — after Step 4, before the main quality pass and Step 5 — not at Step 6.** Actually check every Step 6 identifier class against the session's work and tool/world-state effects, extending the Step 2(d) enumeration with everything Steps 3–4 changed (including moves). Walk the session log's Files rows one file at a time and name the identifiers each change produced before adding what memory supplies; recall alone drops moves and bulk edits. Display and retain the exact enumeration and category-check results. Checked nonempty → despatch the Step 6 agent now via `collaboration.spawn_agent` (background). Checked-empty → record `Step 4b: checked empty; no propagation agent despatched` and continue without a seat. Unknown/incomplete attribution or any unchecked category is not nil: resolve it before taking either branch; if it cannot be resolved, report the blocker, not a completed Park. File count and a quiet-looking session are never evidence of nil. Before despatch, trace every `new` value in the enumeration per §19, including those the Step 3 log introduced: the agent propagates whatever it is handed. Step 6 defines the agent's work and prompt; Step 8 rechecks even when no seat was created.
Keep the exact initial enumeration, including a checked-empty one. Step 8 recomputes it after Steps 4c, 5–7: an existing seat receives the complete final enumeration for a last hit-set comparison; if identifiers first appear then, create the seat then. The initial sweep buys overlap; the final delta sweep closes the read race.
Not earlier: Step 4 **moves files**, and moves are one of Step 6's identifier classes (full old-path forms). Despatching before Step 4 would put every park-time relocation outside the vault-wide sweep, leaving Step 4's own hand-picked reference update as the only net — which is exactly the candidate-list approach §12 forbids. Not later: delaying until after Step 4c throws away the quality-pass overlap.
If you reach Step 5 without a seat or a recorded checked-empty result, perform Step 4b before continuing; do not catch-up spawn for a checked-empty enumeration.
### 4c. Proportional quality gate (whether or not propagation runs)
- **Bounded semantic files:** read each in full once. Mid-session direction changes can leave stale residue outside the edited span.
- **Large semantic artefacts:** inspect the session delta, enough surrounding structure to judge coherence, and the declared outcome-specific pages/sections/renders/tests. State exact coverage; do not claim a whole-file read.
- **Local reference artefacts:** verify the helper's source hash and metadata, then inspect only declared passages supporting session claims. Never read a whole manual/textbook merely because it was touched. A complete extraction is an index, not evidence every page was read; `empty` or `unsupported` text status proves no text coverage, so use targeted rendering/OCR only when a declared target needs it.
- **Across semantic coverage:** fix broken syntax/links/paths, stale interim state, prose that contradicts a table, heading or list in the same doc, a `Last updated:` stamp older than the session's edit to its doc, redundancy, typos and locale spelling (if `.claude/scripts/locale-lint.sh` exists, run it over attributed prose paths from the ledger's Write/Edit, `locked-edit` and `locked-ingress` rows — never the mtime backstop's — and fold accepted candidates in, always skipping candidates inside URLs, quoted spans, or capitalised runs; record candidate, applied and skipped counts, including zero), draft-era filename residue after a terminal status change (rename via link-healing move, never raw `mv`), and hook collateral on verbatim external text (§14). A durable new doc must be linked from a durable parent. Don't auto-revert changes you didn't make — surface them.
- **Wikilinks resolve through the structural index, not by eye.** Probe `obsidian version` first; a CLI that is absent or not responding means this check did not run, and the output says so (§24). When it runs: `obsidian unresolved` once, inspected for targets or basenames this session introduced, then `obsidian links` (argument form per `obsidian help links`, §24) on every edited file that added or changed a wikilink, confirming each new link resolves at file level. An empty result straight after edits can be a reindexing blank, not a pass (§24): re-query once settled. Heading anchors are outside the index: open each target and match the heading text exactly. An eyeball pass is the fallback for anchors only, never the file-level check.
- **Mechanical-only files:** do not full-read. Use the Step 2 receipt result; inspect only its exact replacement and changed span if judgement is needed. Re-run `classify --mechanical` after any later edit; it fails if the receipt chain or current hash changed.
- **All attributed files:** run `_shared-rules.md` §19's SOURCE check; its required output line is part of this gate.
- **File-creation code:** regression-test both mode branches: a missing target receives the caller's umask-derived mode, while replacement of an existing target preserves that target's mode.
- **Superseded queues:** compare the captured pre-state with the retained canonical source before deleting or relabelling the queue. Preserve execution-critical detail; a surviving source label is not evidence that the detail survived.
- **Status narrowing:** bind claims such as "already in place" or "complete" to the exact surface checked and capture the supporting receipt. Count-only or subset evidence cannot discharge a wider status.
- **Override completeness:** when an override suspends a protocol or routine for a stated interval, enumerate every imperative in that interval from the linked SSOT and mark each suspended or retained. Disabling one named mechanism does not implicitly disable adjacent instructions.
- **Hot-capture nudge:** if substantive insights surfaced but weren't routed in the moment, name the habit gap in one line — don't cold-read the transcript to enumerate them. Omit if none.
Output: `✓ Quality check: B bounded semantic files full-read; T large/reference artefacts target-inspected; M mechanical files receipt-verified`, `🔧 Quality check: fixed N issues — [file: fix]`, or — when (a)'s checked inventory is empty — `✓ Quality check: no attributed files (writes deferred to Steps 3–7)`. The empty case is a result, not a skip: planning and research sessions can legitimately make their first writes during Park, and the later writes still pass their step contracts, Step 8 verification, and Step 9 audit.
**Coverage receipt:** after inspection, display `Files read in full: [exact paths | none]`, `Targeted coverage: [path: pages/sections/ranges/checks | none]` and `Mechanical substitute: [exact receipt paths/checks | none]`. Keep this tied to what was actually read or verified, not the intended inventory class; an omitted or truncated read remains incomplete until the stated substitute is performed.
**Coherence after status changes:** compare the changed span with its governing heading, table labels, adjacent conditions and parent scope/provenance statement. Check each clause before ticking a compound task. Reconcile surviving tasks with the settled decision, preserving any still-open clause. For claimed deduplication, inspect the retained counterpart as evidence even when it was not edited. For reciprocal links, verify both endpoints only when reciprocity was intended.
**Before saving durable prose:** replace reader-relative dates and unstable list counts/back-references with durable wording. Run the same check on the final session record and later routed items; a completed heading does not prove a scheduled event occurred.
### 5. Project doc update
If the session materially changed a project's state, update that project's doc in `03 Projects/` — rewrite its existing current-state/action content to match reality, preserving the document's structure and bumping any `Last updated:` stamp, via `locked-edit.sh` (§5). No material change, no edit. If the doc has a `## Session History` section, read it and add `- [[06 Archive/OpenCairn/Session Logs/YYYY-MM-DD]] (Session N) — one-line gloss` at whichever end holds its newest row, via `locked-edit.sh --replace` anchored on that row (not `--append` — the section may not be last; skip if this N is already there from a merge). A section that only points at another doc gets the row in that doc instead. No such section → don't create one.
### 6. Reference-graph propagation
Perform the enumeration at Step 4b and again at Step 8. The **Propagate** paragraph below applies only to a checked nonempty enumeration: despatch at Step 4b, or at Step 8 if identifiers first appear then. Checked-empty skips that paragraph's agent work, never the main session's out-of-vault responsibilities.
**Enumerate (main session):** walk each changed file and tool/world-state effect through the anchor classes below. Mark each class applicable or checked-none; for every applicable class name a literal descriptive anchor from the session source or an existing live record, alongside any `old → new` values. The classes describe what sibling records say, so a deletion or state change still has searchable anchors when its old token is absent.
| Anchor class | Carry into the enumeration |
|---|---|
| Subject / container | Canonical entity identifiers and the decision, set or record that owns the claim; a new option needs the existing decision/container anchor, not just the new member. |
| Value / locator | Old/new facts, status and numeric values with their constrained subject; full old paths, filename forms and old `[[path#heading` prefixes. For a changed This Week day heading also carry its exact old text and repoint prose locators as $goodnight Step 10 does. |
| State / premise | A descriptive phrase for the action or assumption the change discharges, negates, defers or invalidates, including an acted-on entity's prior state after a message, booking or cancellation. A pasted planning item contributes a distinctive run of its literal text. |
| Dependency / cause | An anchor for the obligation or cause/configuration record whose instruction now needs changing, including a deferred action's trigger or re-enable condition. |
| Structure / membership | The owning heading, field/list/set name or route whose framing changes; check governing predicates and diagrams, not only the changed member's token. |
If an anchor search does not reach an expected live record, inspect that record's actual wording and revise the anchor; do not call an unsearched premise current. Step 2's sibling-framing pass covers related documents that share no literal phrase. Commits pushed this session also enter the enumeration for the hub record required by §17. Display the exact enumeration and class checks. Nil is a positive claim: print `✓ Reference graph: No identifier values changed` only after checking every class and the session's world-state effects.
**Propagate (sub-agent — standing authorisation; running it inline instead is the failure):** despatch a background sub-agent via `collaboration.spawn_agent`, on the session's own model — do not downgrade this seat. §12 triage decides whether each hit is a stale cross-reference, a live locator, a historical record, or unrelated; getting that wrong silently corrupts the reference graph. Despatch at Step 4b, then run Step 4c and Steps 5 and 7 while it works; collect its consolidated report at Step 8 before the backfill. Its prompt is self-contained, embedding verbatim: the enumeration (copied, not retyped — count must match), the resolved vault path, and instructions to set the search command's working directory to `{VAULT}` and run a separate `rg --hidden --no-ignore -n -F -i -g '!**/06 Archive/**' -g '!**/07 System/.Provenance/**' -g '!**/.git/**' -g '!**/.obsidian*/**' -g '!**/.trash/**' -g '!**/.Trash*/**' -g '!**/.stversions/**' -g '!**/node_modules/**' -g '!**/__pycache__/**' -g '!**/.cache/**' -g '!*.lock' -- '<identifier>' .` for each identifier, the bare value and never a keyword-conjoined pattern (`-F` because identifiers are literal values: a needle beginning `**` or containing `(`, `.` or `$` silently changes meaning or fails to parse as a regex, and a co-occurrence pattern misses the prose reference carrying the value alone); the resulting live-vault hit-set is the scope, with no hand-picked candidate lists and with already-updated docs re-grepped for other instances. `07 System/.Provenance/**` is a frozen evidence snapshot, not a live reference surface: exclude it from triage, while still validating any snapshot file explicitly attributed to this session. The agent reads §12 rather than recalling it, triages every hit, runs structural link-integrity after moves, bumps co-located `Last updated:` stamps on docs it edits, and retains its per-identifier hit-list for Step 8's comparison; its report gives `identifiers received N`, then per identifier `hits H → updated U, left L`. Embed the executed-search receipt requirement too: for each identifier retain its exact needle, command, working directory, exit status and complete stdout/stderr, or an immutable receipt path; report failed searches distinctly. Planning/hub writes use `locked-edit.sh`, every call prefixed `env OPENCAIRN_SESSION_ID=<id>` with this session's id (resolve it through `lib-session.sh` first: `SID="$(. "{VAULT}/.claude/scripts/lib-session.sh"; _session_id)"`; require a nonempty result and embed that value in the brief) so its edits and receipts land under this session.
**Edited banner/block locators:** embed this check in the propagation brief: after changing a banner or bounded reference block, inspect that whole block and verify every locator it contains, including unchanged neighbours. Resolve file and heading targets against the actual destination; report each as valid, repaired or unverified. Identifier searches alone do not cover a neighbouring locator with different wording.
**Search boundary:** this is the whole live text surface, including ignored scripts/config and hidden authored files; do not restrict it to Markdown. The explicit globs omit archive/evidence, repository internals, editor backups/state, trash, dependency/cache trees and lock files. Ripgrep's binary detection remains enabled and symlinks are not followed: binary content and symlink targets are outside this text claim; use a format-aware check or a separately scoped target search when relevant. Link integrity uses the structural query. Keep exclusions in every pre-log, seat and delta search and name this boundary in negative conclusions. Non-vault consumers remain the main session's separate responsibility below; do not paste private config contents into public findings.
**Executed-search receipt:** for every enumerated identifier, retain the exact needle, command, working directory, exit status and complete stdout/stderr (or an immutable receipt path). A received-count or prose hit total is not proof the search ran. Exit 0 means hits; exit 1 means checked-no-match; any other status is a failed search, never nil. In Step 8 compare the final enumerated needles with these executed rows by value, not count: display `Propagation coverage: enumerated E; executed X; missing [needles]; failed [needles]`. Run and collect every missing or failed search before accepting propagation; preserve initial and delta receipts. Step 10 logs any mismatch that required recovery, even if later fixed.
**Out-of-vault facts** (skill/command files asserting things about each other) stay in the main session — the sub-agent has no skill-edit authority: grep `~/.codex/skills`, `~/.claude/commands` and repo command dirs yourself, propagate mechanical fixes, and log non-mechanical skill changes at Step 10.
Output: `✓ Reference graph: N files updated for [identifier]` (+ file list). The short nil line above is an enumeration result, not the completion message: use Step 8/12's checked-final nil wording only when the final enumeration is checked-empty and no propagation seat was despatched. If a seat existed, report its actual result instead; never claim it was not despatched.
### 7. Route open loops
Route every open loop to exactly one canonical target — no per-item prompting.
**Already in This Week.md:** keep the existing task there; update any changed scope in place. Do not recapture or offload it through the routing rules below. Shared §9 owns carry-forward.
**Configured task-home map takes precedence for new open loops:** locate it through vault navigation/Autopilot and follow Quick capture, including its undated fallback. Honour actual dates and execution days the user chose; being actionable does not by itself select work for this week. Keep retained tasks as checkboxes. Whimsy is only for ideas the user has released from obligation, never merely low-priority tasks. Use the fallback table below only when no map is configured.
1. **Explicit future date** → the matching dated section in This Week.md if it exists (use `locked-edit.sh --replace`); otherwise Tickler: `"{VAULT}/.claude/scripts/write-tickler.sh" "{VAULT}/01 Now/Tickler.md" "YYYY-MM-DD" "- [ ] text → [[06 Archive/OpenCairn/Session Logs/YYYY-MM-DD]] (Session N - Topic)"`
2. **No date, actionable this week** → This Week.md day section (tomorrow's; today's if parking before noon) via `locked-edit.sh --replace` on the day section — never `--append`, which lands outside any section. Format: `- [ ] text → [[project/area doc]]`. Trigger-contingent loops ("next time X runs, check Y") are not day-bound — use rule 3, or the Tickler at +10 days if no project doc exists.
3. **No date, has a project** → that project doc's existing task/action section (prefer `## Next Actions`, then `## Open Loops`, then another clearly equivalent section). If none exists, route to the Tickler at +10 days instead of creating a section.
4. **Undated, low-priority, no project home** → Whimsy: append a plain line (no checkbox) to `{VAULT}/04 Areas/Whimsy/_notes.md`.
**§18 applies:** an item carrying a deadline/expiry/window token MUST land on a dated surface — for this skill the disallowed sinks are undated project/area task homes, general capture inboxes and Whimsy. Derive the date (`date -d`) if it isn't written as one.
**Dedup before writing:** search both a distinctive task phrase and its stable subject across This Week.md, Tickler.md, and the candidate project/area/capture destination. Read candidate hits for semantic overlap; an intact duplicate may be skipped. Preserve source triggers and safety conditions through a link to their canonical home; do not invent a stronger condition or copy volatile decision prose into another home. Follow the destination's own entry rules. If the session resolved only part of the existing item, rewrite that item's surviving scope under `locked-edit.sh`; do not skip the whole item merely because the grep hit.
Output: `✓ Routed: [item] → [target]` per item. A zero-routing claim cites an observable (the dedup grep hit, or the session log's "None — work completed").
### 8. Backfill + mechanical verification
**Finalise Step 6 first.** Recompute and display the complete identifier enumeration after all intervening work, including Steps 4c, 5–7, using Step 4b's category checks and unknown/incomplete rule. Branch on whether a propagation seat exists:
- **No seat, checked-empty final enumeration:** neither spawn, follow up nor wait. The primary session records `Checked final enumeration: no identifier values changed. No propagation agent dispatched.`, with its category-check results, and captures that explicit checked-nil receipt below before backfill. It is not an agent report.
- **No seat, nonempty final enumeration:** create the Step 6 background seat via `collaboration.spawn_agent` with the complete final enumeration and normal self-contained brief, including the primary session ID write prefix. Collect its report before backfill; there is no initial hit-set to compare.
- **Existing seat:** use `collaboration.followup_task` on the original propagation seat with that final enumeration and instruct it to re-run every original and newly added identifier search in the exact Step 6 shape, compare each final hit-set with its initial one, triage every new or changed hit, and return one consolidated report. Do this even when the enumeration is unchanged: the main pass may have changed a hit after the seat's first read. The final re-grep is mechanical; only hit-set deltas need fresh semantic triage. Never discard an existing seat or its report on a later nil claim.
For either branch with a seat, collect its report with `collaboration.wait_agent` before backfill — its edits belong in the backfill, and park-verify's `--touched` list is incomplete without them. wait_agent's return is the *only* signal it has finished; never substitute a proxy that cannot tell "running" from "finished" (a transcript's file size, an elapsed-time guess, a scratch file appearing). Improvising one has already reported completion mid-run.
When a seat reported, check that its `identifiers received` count equals the despatched enumeration and that each H = U + L matches its hit-list before relaying any figure, then inspect the report for unresolved or gated consequences before backfill. Route each through Step 7. If that routing changes the final enumeration, send one delta-only `collaboration.followup_task` to the same propagation seat and collect its consolidated addendum before capture; a report that surfaces a new open loop invalidates any earlier "none" result. Apply the same consequence/delta check to each addendum; further identifier changes from propagation or later Park edits require rechecking Step 8 before completion.
Immediately capture the returned report and any consolidated addendum verbatim, or, only in the no-seat checked-empty branch, the primary session's explicit checked-nil receipt and category-check results. Never fabricate an agent report or supply empty input. Step 9 consumes this receipt mechanically and still despatches its independent reviewer:
```bash
python3 "$PARK_REVIEW" capture --kind propagation --label "Step 6 propagation" <<'EOF'
<agent report and addendum verbatim OR primary session's explicit checked-nil receipt and category-check results>
EOF
```
Add every propagation-touched file to the attribution list. Classify new locator-only files through the locked-edit receipts; apply Step 2(b)'s proportional semantic/reference inspection to every other new artefact. This closes the overlap without repeating mechanical checks or promoting imported references into full reads.
**Backfill:** park-time edits (Steps 5–7: project docs, This Week, Tickler) postdate the Step 3 log write — pipe them as `- path - what changed` lines through `"{VAULT}/.claude/scripts/backfill-files-updated.sh" <log> N`. The script skips paths already in Files Created/Updated, keeps their existing descriptions, prints each incoming row it did not write, and ends with `<N> added, <M> skipped`. Exit 3 means it wrote nothing because every row was skipped: a result to read, not a failure to retry. Extend a skipped description in its existing section via `update-session-section.sh --replace`; a created file stays in Files Created after later edits. Also reconcile inline closures: a Next Steps item that park itself closed comes out of `### Next Steps / Open Loops` (`--replace`, preserving the other lines).
Reconcile the Files lists with a fresh `session-ledger.sh --read`, the Step 2 inventory (including its non-ledger backstop), and the collected propagation/park-time edits; backfill any attributed omissions before verification. The ledger alone is not complete. Reconcile the complete path set independently from attributed commit/merge ranges and tool calls, including parent-skill, shell and connector writes; do not infer ownership from a commit time window. Include renamed/deleted paths in the Files record with their actual status. Build `--touched` from the reconciled Files-list paths and deduplicate by the same resolved path (expand `~` and resolve vault-relative spellings), passing each path once. Keep distinct installed/source paths separate even when their basenames or contents match; repeated writes to one path are not separate files.
**Batch the ready mechanical work:** after the reconciliation above, prefer `python3 "$PARK_REVIEW" prepare --vault "{VAULT}" --session-log "<session log>" --number N` with the JSON handoff described in [references/prepare.md](references/prepare.md). It batches supplied classifications/receipts, derives the complete deduplicated `--touched` list from the log, runs the receipt-wrapped verifier and builds Step 9's brief, with stage timings. It stops on FAIL, untriaged REVIEW or changed inputs. All preceding quality, propagation and routing gates still apply; this is not a quick-path shortcut.
**Standalone verification** (when preparation is not ready to batch): run the verifier through the receipt wrapper and resolve every FAIL, re-running until clean:
```bash
python3 "$PARK_REVIEW" run-verifier -- \
"{VAULT}/.claude/scripts/park-verify.sh" "{VAULT}" "<session log>" N \
--ident "<distinctive substring per item/identifier the session completed>" ... \
--touched "<each file the session+park created or edited>" ...
```
**`--touched` must be the log's own Files-list paths**, all of them — they are already enumerated by this point, so pass that list rather than a subset you retype. The verifier can only speak for the paths it was handed: a narrower list still prints `PASS backfill`, and a `REVIEW backfill` naming paths the log lists but the run never saw means the list was short, not that the log is wrong. `--ident` values must be distinctive strings — a bare number under ~4 digits matches digit runs inside phone numbers, order IDs and amounts, and a common word or a doc's name matches every item that mentions or links it, burying real hits in noise. Paths may be absolute, `~`-prefixed, or relative to `{VAULT}`.
It checks the deterministic layer: session numbering, required sections, Project line (compare its printed line against Step 3's metadata — fix the log if they differ), stranded locked-edit separator lines, list-join/blank-line lint on touched files, unchecked planning-doc items matching each ident, that each touched path exists (`FAIL touched`: mistyped or truncated), and Files-list coverage of touched paths. Judgement stays with you: each closure `REVIEW` hit is an item park itself wrote at Step 7 (expected: ignore the hit, keep the item), an item the session genuinely completed — flip `[ ]`→`[x]` via `locked-edit.sh --replace` with a `→ [[log]] (Session N)` backlink (Tickler flips only after confirming it's the very item resolved — a false flip silently kills deferred work) — or an adjacent still-open item: surface it in Pickup Context, don't action it. Passing edited-file basenames as extra `--ident` args catches adjacent open items mechanically. Once an adjacent-open hit has been recorded in Pickup Context, the final rerun may omit only that discovery-only basename while retaining every core completion/value identifier; record the triage rather than accepting an unexplained `REVIEW` forever.
**One accepted-FAIL class:** a lint hit in content the session never wrote, where the file's classification receipt proves every session delta was an exact declared locator/token substitution, carries pre/post lint fingerprints proving the edit introduced no lint, and records `--allow-inherited-lint`. "Fixing" inherited lint means reformatting the user's own notes, which a standing instruction forbids. Anything the session introduced or any file lacking that mechanical proof is not in this class.
When this is the verifier's **only** failure, rerun through `run-verifier` with one `--accept-inherited-lint "<path>"` before `--` for every affected file. The wrapper accepts only paths pre-approved by those mechanical receipts, an exact path-set match over the complete multi-line lint block, exactly one `FAIL lint`, zero `REVIEW` lines, and `RESULT: FAIL (1 fail, 0 review)`; any additional or differently scoped issue remains a failing receipt.
Output: the script's `RESULT:` line plus what you fixed.
**Final evidence check:** re-read the session entry as written and apply §19 to it and all Park-owned writes, including backfill descriptions; print §19's `Value check:` line again for this pass. Reconcile completion claims with surviving open items before assembling the review. If audit remediation or later edits change verified inputs, reconcile attribution and rerun Step 8 before reporting its PASS.
### 9. Delta-aware audit until clean (fresh read-only sub-agent — standing authorisation)
**Script probes:** embed this boundary in the reviewer brief: the reviewer may run a command only after reading its argument handling and establishing that the invocation is read-only. Otherwise copy its required inputs into a disposable scratch directory outside the vault and installed config, redirect all output/state there, and probe that fixture. `--help` is not evidence of a read-only command. If isolation cannot be established, inspect the source and report the execution check unverified; never weaken host policy or bypass a missing sandbox.
Run the independent close-out audit, remediate confirmed findings, and **re-audit until a full pass finds no further findings**. Keep the delta-aware scope and read-only reviewer; the main session owns remediation. This does not invoke `$audit` or its panel. **Steps 10–11 and the Park completion message must wait for the final clean report and accepted audit receipt.** The Setup delegation-unavailable checkpoint is the explicit incomplete-bookkeeping exception; it never permits a Park completion claim.
Use the brief and SHA-256 printed by a successful Step 8 `prepare` invocation when its inputs are unchanged. Otherwise generate the brief from the post-backfill session log, raw session ledger, locked-edit/classification receipts, propagation receipt, latest verifier receipt, evidence receipts and hash-matching prior audit receipts:
```bash
python3 "$PARK_REVIEW" build --vault "{VAULT}" --session-log "<session log>" --number N
```
The command prints the §16 evidence count and review-mode counts, writes `review-brief.md`, and uses immutable digest-addressed source/review copies under this session's `.session-state` directory. It full-reads only bounded semantic groups; large semantic and local reference artefacts carry hash-bound metadata plus declared inspection targets. It fails closed if a propagation/verifier receipt is absent, an artefact receipt is stale/corrupt, or a mechanical receipt no longer matches the live file. Give the filesystem-capable reviewer the generated brief's absolute path and printed SHA-256, instructing it to read the file and verify the digest before reviewing. Do not reconstruct the brief in the spawn message.
Despatch exactly one reviewer with this shape (replace `yyyymmdd` and `n` with the current date and assigned session number; both substitutions use digits only):
```text
collaboration.spawn_agent({
task_name: "park_audit_yyyymmdd_n",
fork_turns: "none",
message: "<brief absolute path + SHA-256 + read-and-verify instruction + read-only contract>"
})
```
Resolve the task name against `collaboration.list_agents` before spawning. On a collision, append `_addendum_1` and increment the suffix until unused; if the spawn itself reports a collision, recompute and retry. A failed spawn creates no reviewer, so this still yields exactly one fresh seat. Carry the resolved task name into collection and the audit receipt.
Omit both `model` and `reasoning_effort`: the reviewer must inherit the active park seat's model and reasoning effort exactly. Do not resolve, copy or hard-code their current names or values; omission keeps the match intact when the user changes either setting. `fork_turns: "none"` keeps the context fresh and does not change that inheritance. The propagation seat's anti-downgrade clause in Step 6 is unaffected.
The generated brief is delta-aware: bounded semantic files are full-read once per pass from immutable review copies; large semantic and local reference artefacts are checked only at their declared targets after the source hash and any review-copy hash are verified; mechanical-only files use exact locked-edit receipts and changed spans; files covered by an earlier clean audit receipt are not reread while their SHA-256 is unchanged. Complete PDF extraction is an index, never a whole-book coverage claim. Attestations bind full-read snapshots to original paths. The brief embeds the Layers 1–5 checklist for each pass, §16 evidence, §23 attestation table, strict read-only scope and compact report contract. These are generated invariants, not fields to retype.
Collect with `collaboration.wait_agent` in intervals no longer than 60 seconds until the report arrives. Do not infer completion from elapsed time or another proxy, and do not re-despatch merely because the seat is still running. If the same seat has explicitly said its review work is complete but remains running through repeated no-progress waits, send one report-only message. If it still does not yield, interrupt that seat and use `collaboration.followup_task` on the same task name with a no-tools, report-only instruction to return its existing evidence. Do not spawn a fresh audit before this same-seat recovery.
If recovery fails because the reviewer hits a model/context wall or the same-seat continuation is unavailable, reconcile the fresh session ledger and any landed edits against attribution/backfill, preserve the current brief and evidence, and report `✗ Audit: not clean — <reason>`. Do not switch the reviewer to an arbitrary named model or treat an incomplete report as clean. A terminally unavailable supported continuation may use only the Setup incomplete-checkpoint bookkeeping exception for Steps 10–11; a model/context wall alone does not permit it. A different-model fallback requires existing user authorisation and must preserve the independent review contract.
For each completed report, re-run `session-ledger.sh --read` and reconcile any row attributed to the reviewer against the read-only contract. An unexpected reviewer write is not cured by a clean final file: inspect the target for lost concurrent content, replay any warranted vault delta through `locked-edit.sh`, and add the path to attribution/backfill. Then sanity-check each finding in the live file.
**Remediate and repeat:** the main seat fixes confirmed, attributable findings using the normal locked write mechanics, backfills newly touched paths, refreshes affected classification/evidence receipts, re-reads the changed portions and reruns Step 8. Obtain the updated brief from `prepare`, or rebuild it explicitly after standalone verification, then use `collaboration.followup_task` to send the same independent reviewer the new brief path and SHA-256, with instructions to verify its digest and run the full scoped checklist again. Collect and assess that report by the same contract. Repeat after every fix round; there is no one-round cap. An incomplete report or missing attestation also requires a follow-up before claiming clean.
If a finding requires unavailable evidence, a user decision or action beyond the authorised scope, report the concrete blocker and retain it as an open loop; do not label the audit clean or Park complete. For a disputed finding, send the reviewer the counter-evidence; unresolved disagreement is a blocker, not grounds for silently accepting or endlessly repeating the finding.
On a clean report, pipe it into `python3 "$PARK_REVIEW" record-audit --reviewer "<resolved task name>" --from-brief`. The helper refuses a receipt unless the report says clean and attests every snapshot SHA-256 against its original path. It then checks the live originals: unchanged files remain eligible for later audit reuse; a changed file is accepted only when post-snapshot locked-edit receipts from other sessions form a complete hash chain and every session-owned locator remains intact. Traceable disjoint edits by another session therefore do not invalidate this park, but that file's snapshot receipt is not reused as a clean audit of its newer live bytes. A post-snapshot edit by this session requires a rebuilt brief and new review; an untraceable change or missing owned locator fails closed as possible overlap. The helper persists `pending`, completes its final live-byte check, then atomically promotes the receipt to `clean`, so interruption cannot strand a reusable receipt before finalisation. Do not create a clean receipt after findings/remediation without a new independent full read.
Output: `✓ Audit: clean pass` or `🔧 Audit: N findings fixed and re-audited clean — see [paths]`. If blocked: `✗ Audit: not clean — [blocker]`; do not proceed to the completion message.
### 10. Skill monitor
Per §8: on the full path, review this park execution including the audit — an audit catch that a documented step should have made is a skill gap, the highest-signal kind. On the quick path, review the checked quick execution without inventing an audit input. On the explicit delegation-unavailable checkpoint, monitor only the work actually performed; no audit input exists. Log observations per `_skill-monitor.md`, else `✓ Skill monitor: No gaps detected`.
### 11. Export transcript (last, so it captures the audit)
```bash
python3 "{VAULT}/.claude/scripts/export-session-transcripts.py" "{VAULT}" --days 7 --all-projects
```
`--all-projects` is cwd-independent and merges multi-project days (a single-project export hashes an incomplete day; do NOT use `--fallback-any-project`). `--days 7` because the cutoff is a rolling window from now — `--days 1` truncates boundary days. Report the count. The exporter covers Claude Code sessions and Codex rollouts alike (Codex sessions appear as `codex-<id>` sections).
### Measurement close-out (full, quick and blocked paths)
Batch this with the last existing tool call before the final response; it does not replace any audit gate:
```bash
python3 "{VAULT}/.claude/scripts/park-metrics.py" record --harness codex --session-id "$CODEX_THREAD_ID" --outcome <completed|blocked|aborted> --review-rounds <completed reports> --correction-rounds <fix rounds> --confirmed-findings <distinct confirmed findings>
```
Use the actual harness transcript session ID (resolve via `lib-session.sh` when needed), not a synthetic bookkeeping suffix. Count independent audit reports only, including the clean pass; exclude propagation and report-only recovery. Count each accepted finding once. Omit any unknown count flag; do not turn missing data into zero. Quick success has zero audit counts only after its checks pass. A recording error is reported but does not block parking. The scheduled collector measures through the final response. Do not ask for human review time routinely; record it only when supplied by the user. Contract and report commands → `{VAULT}/.claude/scripts/park-metrics-guide.md`.
### 12. Full-path completion message
```
✓ Quality check: N files, [no issues | M fixed] (or “no attributed files” when Step 2(a)'s checked inventory was empty)
✓ Session N saved: 06 Archive/OpenCairn/Session Logs/YYYY-MM-DD.md
✓ At-risk work product: [none | persisted N]
✓ Project doc: [updated [[Name]] | no material change]
✓ Reference graph: [N files updated | No identifier values changed — checked final enumeration; no propagation agent despatched]
✓ Open loops routed: N (This Week: X, Tickler: Y, Project: Z, Whimsy: W)
✓ park-verify: PASS
✓ Audit: [clean pass | N findings fixed and re-audited clean]
✓ Skill monitor: [no gaps | N logged]
✓ Transcript exported: N sessions
Parked.
[Roughly 50 words summarising the preceding session.]
```
Work done after this message is unparked: a further `$park` in the same conversation takes Step 1's merge path.