Paula Patterns — Feasibility & Whack-a-Field Review: WP/Op Schema Model
Scope reviewed: wp-op-schema-model.md idea note against the code as-is on clean main. Read-only.
Headline: The idea diagnoses a real pain (whole-file hash churn) but mis-locates the fix. Two of the three things it proposes to build already exist in this codebase. The residual problem is a narrow boundary eviction, not a new store. Framed as written, the maximal version would create a fourth parallel WP authority — the exact leak this lens exists to catch.
1. Authoritative-vs-derived split — partially realistic, with one hard trap
The idea treats "structured authoritative, markdown derived (one-way)" as a novel bet. It is already the shipped pattern for tasks.md:
core/wps_manifest.py:170generate_tasks_md_from_manifest()renderstasks.mdfrom thewps.yamlPydantic manifest, stamped"_Generated by finalize-tasks from wps.yaml. Do not edit directly._"(wps_manifest.py:184). Sowps.yaml(structured) →tasks.md(derived, one-way) is proven and live.
The trap is conflating two different markdown surfaces:
| Surface | Authoring reality | One-way-derivable? |
|---|---|---|
tasks.md (index/rollup) |
machine-generated already | ✅ yes — already is |
tasks/WP##.md body (the prompt) |
the prompt body IS the agent's instructions; human- and agent-edited | ❌ no |
The WP body is not decoration — it is load-bearing content consumed as prose:
- Dashboard renders the body verbatim as
prompt_markdown(dashboard/scanner.py:976), and pulls the card title from a body regex^#\s+Work Package Prompt:in preference to frontmatter title (scanner.py:895-901). - Subtask progress is counted from
- [ ] T###checkboxes in the body or tasks.md, not frontmatter (scanner.py:950-961). - Agents read the WP body as their work order.
Verdict on Q1: "structured authoritative → derived index markdown" is realistic and already done for tasks.md. "Derived one-way WP body" is not realistic — the body is authored, not projected. The metadata half can be authoritative-structured; the prose half must stay round-trippable. Any spec that says "markdown becomes a derived view" without splitting index from prompt body is undercut on contact.
2. Whack-a-field census — the model the idea asks for is ~70% already built
The note's premise ("no single model, scattered defensive parsing") is stale. There are today three structured WP representations plus the body, and the read side is already largely consolidated:
Structured authorities that already exist:
WPMetadata(status/wp_metadata.py:183) — frozen Pydantic v2,extra="forbid", ~40 typed fields covering identity, deps, branch contract, execution context, workflow, review, descriptive. This is the "code-owned logical model" the idea proposes to create.WorkPackageEntry/WpsManifest(core/wps_manifest.py:15,61) — Pydantic planning manifest, the authoritative source fortasks.md.FrontmatterManager.WP_FIELD_ORDER(frontmatter.py:49) — the write-path field list + validator.
Read-path is already unified: 23 modules consume the typed read_wp_frontmatter → WPMetadata (dashboard, dependency_graph, requirement_mapping, ownership, merge, workflow_executor…). Only 9 use the raw-dict path, and those are non-WP or migration-only: task_metadata_validation.py (explicitly MIGRATION-ONLY, task_metadata_validation.py:82,181), the generic template/* renderers, doc_analysis/gap_analysis.py. So "scattered defensive parsing" of WP structure is mostly already retired.
Where the naive model would leak (my core competency):
The real duplication is three independent field-name lists for one object: WP_FIELD_ORDER (frontmatter.py:49), WorkPackageEntry fields (wps_manifest.py:16-26), and WPMetadata fields (wp_metadata.py:197-282). They already drift — the codebase carries a comment tax keeping shell_pid_created_at in lockstep by hand across frontmatter.py:26 and wp_metadata.py:252-257 because Pydantic attr names can't import the shared constant. Dropping a new "single source of truth" WPRecord next to these three would make it four — a layer on top, not a consolidation. That is the whack-a-field failure mode, and the idea as written walks straight into it.
Verdict on Q2: a single model does NOT need inventing; it needs electing. Pick WPMetadata (already the read authority, already extra="forbid") as the canonical shape and make WP_FIELD_ORDER and WorkPackageEntry derive from it, or the whole exercise adds a layer instead of removing one.
3. Bookkeeping vs semantic boundary — the store already exists; the fix is eviction, not invention
The idea asks (Q3) whether the append-only event log already owns the mutable half. It does, and the migration is already ~90% done:
frontmatter.py:47-48,284-285explicitly states mutable status fields (lane,review_status,reviewed_by,review_feedback) are "managed exclusively via the canonical event log and are NOT written here."- The only code that reads/writes
lanein frontmatter is stampedMIGRATION-ONLY(task_metadata_validation.py:82,181). status.events.jsonlis the sole lane authority (per CLAUDE.md status-model section;status/store.py,status/emit.py).
So the correct boundary is already drawn for lane/review. What still lives in the WP file and churns the hash is the residual runtime/bookkeeping tail the earlier migration didn't finish evicting:
shell_pid,shell_pid_created_at,base_branch,base_commit,planning_base_branch,created_at,history(frontmatter.py:60-68), written at claim/implement time viawrite_shell_pid_claim_to_file(frontmatter.py:393) andadd_history_entry(frontmatter.py:176).- Subtask completion checkboxes flipping in the body/
tasks.md.
Why that churns the hash: dossier/hasher.py:14 hash_file() and sync/body_upload.py:116 compute a whole-file SHA-256 over WP files matching ^tasks/WP\d+.*\.md$ (body_upload.py:52). Any byte change — a shell_pid claim write, a history append, a checkbox flip — moves the whole-file hash → dossier "parity drift" review noise (.contextive/dossier.yml:37) + sync re-upload + the content_hash_mismatch guard (body_upload.py:121).
Verdict on Q3: the idea's "invent a new structured store for bookkeeping" is the wrong fix — the store (event log) already exists. The right boundary is: finish the eviction that lane/review started — move the residual mutable/runtime fields out of the hashed WP file, and/or exclude them from the content hash. No new authority required.
4. Smallest de-risking slice + ordering
Maximal version (as the note frames it) — over-engineered: "rewrite WP + Op + markdown pipeline, new authoritative store, markdown derived." This (a) builds a model that ~70% exists, (b) risks a 4th field authority, (c) breaks WP-body authoring, and (d) collides head-on with in-flight work (see risks). Do not scope this.
Recommended minimal first slice (de-risks the actual pain, ~1 mission):
Evict the residual mutable/runtime fields from the WP file's hashed surface, reusing the existing stores. Continue the lane/review precedent: move
shell_pid/shell_pid_created_at/base_*/created_at/historyinto the event log or a status sidecar, and stop whole-file-hashing the prompt — hash the semantic frontmatter + body only. One migration. No new model. No markdown-derived rewrite.
This alone kills the hash-churn (the one concrete, recurring pain) and touches nothing that authors read.
Tidy-first ordering (enabler sequence):
- Tidy (pure debt, no behaviour): unify the three field-name lists — make
WP_FIELD_ORDERandWorkPackageEntryderive fromWPMetadata(or a shared field registry). Kills the existing hand-maintained drift tax before anything is added. This is the true enabler; skipping it guarantees the 4th-authority leak. - Boundary fix (the slice above): evict residual mutable fields → event log/sidecar; narrow the content hash. Delivers the hash-churn win.
- Only if still wanted, later: promote
wps.yamlto authoritative semantic store and generate the WP body scaffold from it — extending thetasks.mdgeneration that already works (wps_manifest.py:170) — while keeping the body round-trippable for human/agent edits. - Op record (decouple entirely):
OpStartedEventis already Pydantic (invocation/record.py:39). Adding optional structuredintent/scopefields is a small, independent change — do NOT bundle it with WP work; coupling them doubles the blast radius for no shared code.
Verdict: Feasible-with-caveats — but the idea over-scopes; the real fix is small
The pain is real and the direction is sound, but the framing ("build a single Pydantic model, make markdown derived") describes work that is 60–70% already shipped (WPMetadata, wps.yaml→tasks.md, event-log ownership of lane/review). The genuine residual is a narrow boundary eviction + field-list unification, not a new store or a pipeline rewrite. Scope it that way and it's a clean, low-risk mission; scope it as written and it manufactures a new parallel authority.
Top 3 risks:
- New parallel authority (the whack-a-field leak). Adding a
WPRecord"single source of truth" next to the existingWPMetadata+WorkPackageEntry+WP_FIELD_ORDERmakes four field lists, not one. Mandatory precondition: unify the existing three (frontmatter.py:49,wps_manifest.py:16,wp_metadata.py:197) first, electingWPMetadataas canonical. - Markdown round-trip breakage. The WP body is authored instructions consumed as prose (
scanner.py:895,976; agents read it as their work order). "One-way derived markdown" is safe for thetasks.mdindex (already done) but breaks the prompt body. A spec that doesn't split index from body is undercut. - In-flight collision / sequencing. The exact surfaces this touches are under active change: coord-authority status work (#2160) also writes the
shell_pidclaim into frontmatter (frontmatter.py:393), plus infra-logic separation (#2173) and mission-type→doctrine. Evicting frontmatter fields races writers being restructured elsewhere — land the tidy-first field unification before those diverge further.