docs: plan P5 curated FK annotations in Git

This commit is contained in:
2026-08-13 04:50:29 +02:00
parent 3397911670
commit 60e4048d4b
@@ -0,0 +1,197 @@
# P5 — Curated FK annotations in Git — Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to apply this plan task-by-task.
**Goal:** Make the curated FK annotation file the canonical, revision-pinned human review input. The
registry validates `<workspace-id>/schema/annotations.yaml` as a regular Git blob at the same commit
as the descriptor, synchronizes it to an immutable revision-qualified runtime root on activation, and
replaces the P2 host-file FK review with an explicit operator command
`workspace schema accept --run <id> --yes`. A pinned historical runtime keeps reading its own
revision's annotations; a newer active revision writes a different directory.
**Source of truth:** PRD D5 (`docs/prd/2026-08-09-workspace-preprocessing-prd.md`) and design §7
(`docs/superpowers/specs/2026-08-10-p2-p6-workspace-preprocessing-design.md`).
**Architecture:** The backend registry and the compiled operator entrypoint share the sync logic. Git
reads use fixed plumbing (`rev-parse`, `cat-file -t`, `show`) at an exact 40-hex commit — never a
mobile checkout and never author files. The harness keeps owning the annotation *parser* (Pydantic
`Annotations`) and the physical-schema orphan check.
**Tech Stack:** TypeScript (backend registry/preprocessing/runtime rendering), Go (`thothctl`),
Python (`tht schema`), YAML. TDD throughout.
---
## Current-state findings recorded by this plan
- P3 implemented revision-scoped Qdrant schema/Evidence records and a binding-keyed DWH cache
(`.tht-dwh` at `paths.artifacts.parent`), but it did **not** repurpose `paths.artifacts`/`indexes`
into a revision root (they remain workspace-global under `/data/sessions/<id>/`).
- The harness resolves curated annotations at `paths.artifacts/mschema/annotations.yaml` and already
parses them with `Annotations.from_yaml`; `tht schema check` performs the physical orphan check.
- P2 already records FK candidates and an `FkReviewRecord`
(`{ reviewedCandidatesDigest, annotationsDigest, workspaceRevision }`) and writes host-file reviews
from `schema check --annotations --reviewed-candidates`.
## Explicit decisions frozen by this plan
1. **Canonical path** is fixed `<workspace-id>/schema/annotations.yaml` (not descriptor-configurable).
Absence is compatible and yields an empty canonical annotation set plus a warning. Symlinks,
submodules/trees at the file path, cross-namespace paths, oversized, non-UTF-8, and malformed
annotations are rejected before activation.
2. **Revision-qualified annotations root** is rendered as a new explicit path
`paths.annotations_root = /data/sessions/<id>/revisions/<commit>/artifacts`; the immutable synced
file is `<annotations_root>/mschema/annotations.yaml`. `paths.artifacts`/`indexes`/`memory`/
`sessions` remain exactly as accepted by P3 because the binding-keyed DWH cache lives at
`artifacts.parent` and must stay shared across content-only revisions. This is a deliberate,
surgical refinement of design §7's literal "artifacts and indexes select the revision root": the
revision-pinned *annotations* requirement is satisfied without destabilizing the accepted P3 cache
contract. The harness resolves annotations from `paths.annotations_root` when present and falls
back to the legacy `artifacts/mschema/annotations.yaml` for unmigrated workspaces.
3. **Bounds:** the annotation blob is ≤ 16 MiB, UTF-8, and structurally parsed (Pydantic `Annotations`)
before synchronization; the full physical orphan check still runs at review time.
4. **Sync trigger:** on registry activation (pull/validate) and before session admission or
preprocessing, each active revision's annotation blob is read with fixed Git argv, validated, and
atomically written no-follow to its revision root with restrictive mode, alongside an ownership
manifest `{ workspace, commit, blobId, contentDigest, destination }`. Re-sync is idempotent and
re-verifies the manifest.
5. **Human review primitive is `workspace schema accept --run <id> --yes`.** After commit/push/pull,
the operator reviews the current Git blob against the recorded candidate, then runs the accept
command. It parses the current blob, validates it against the physical schema via the harness
parser, and records `{ reviewedCandidatesDigest, annotationsDigest, workspaceRevision }` plus the
current Git blob id and the new revision. `--yes` is required. An empty file or `schema check`
alone is **not** evidence of human review.
6. **Continuation gate:** `preprocess run` FK review now requires an accepted review whose
`annotationsDigest` equals the *current* revision's synced blob digest and a compatible reusable
DWH binding; otherwise the run starts a new review. The P2 host-file review path
(`schema check --annotations --reviewed-candidates` writing an `FkReviewRecord`) is superseded:
`schema check` remains available as read-only validation but no longer records a review.
7. **Error/output:** reuse `annotation_invalid`, `manual_review_required`, and
`preprocessing_resume_mismatch`; JSON results gain the accepted `blobId`/`annotationsDigest`
artifact identities. No new public error code is introduced unless a gap is proven by a test.
8. **No push/curation:** the preprocessing CLI never stages, commits, or pushes curated content.
Curators work in an ordinary author clone.
## Completion contract
The phase is complete when:
1. A workspace whose Git tree contains a valid `<id>/schema/annotations.yaml` blob activates and
syncs it to exactly `/data/sessions/<id>/revisions/<commit>/artifacts/mschema/annotations.yaml`
with a verified ownership manifest; a workspace without the file activates with a warning and an
empty canonical set.
2. Symlink/tree-at-path, cross-namespace, oversized (>16 MiB), non-UTF-8, and malformed annotation
objects are refused without mutating the snapshot or runtime roots.
3. The harness resolves annotations from `paths.annotations_root` (legacy fallback preserved); a
session pinned to an older revision reads that revision's synced annotations, and a newer active
revision writes/reads a different directory.
4. `thothctl ... workspace schema accept --run <id> --yes` records the accepted candidate/current-blob
digests and the new revision; `--yes` missing, an unknown run, an empty file, a malformed blob, or
a blob not matching the recorded candidate fails closed without recording a review.
5. `preprocess run` continuation succeeds only with the exact accepted blob digest and compatible DWH
binding; the superseded host-file `schema check` path no longer records a review.
6. `docs/contracts/workspace-preprocessing-cli.md` documents `schema accept` and the annotations
lifecycle; the P5 manual walkthrough section is runnable; PROJECT_STATE.md records the result.
7. The clean-state automated process goal passes 1/1 (no retry), and backend/Go/harness focused
suites plus the existing P2–P4 gates do not regress.
---
### Task 1: Registry reads and validates the annotation blob at the exact commit
**Files:** modify `backend/src/workspaces/git-repository.ts`, `backend/src/workspaces/registry.ts`; add
tests `backend/test/registry-annotations.test.ts`.
1. Failing tests: `gitObjectType`-style read of `<id>/schema/annotations.yaml` at an exact commit
returns `blob` or absent; a `tree`/`submodule`/other type is refused; the blob id (`rev-parse`) and
bytes (`show`) match; UTF-8 and 16 MiB bounds are enforced; path grammar rejects
`workspace-docs/...` and cross-namespace paths.
2. Implement `GitWorkspaceRepository.annotationsObject(revision, id)` returning
`{ blobId, type, contents } | undefined` with fixed Git argv and bounded sanitized errors.
3. In `WorkspaceRegistry.activate`, validate every active revision's annotation object; a present-but-
invalid object fails activation closed (`workspace_invalid`), absence is a safe warning.
4. Commit: `feat: read and validate curated FK annotations at the pinned commit (P5)`.
### Task 2: Atomic revision-qualified annotations sync + ownership manifest
**Files:** add `backend/src/workspaces/annotations-sync.ts`; wire into activation and
`renderActiveWorkspaceRuntime`; tests `backend/test/annotations-sync.test.ts`.
1. Failing tests: sync writes `<dataRoot>/sessions/<id>/revisions/<commit>/artifacts/mschema/
annotations.yaml` (mode restrictive, no-follow, exclusive staging + atomic rename + fsync) and an
adjacent ownership manifest `{ workspace, commit, blobId, contentDigest, destination }`; re-sync is
idempotent and re-verifies the manifest; a tampered destination or wrong manifest fails closed;
a different revision writes a different directory.
2. Implement the sync (shared by registry activation and the operator/session runtime render).
3. Commit: `feat: atomic revision-qualified annotations sync with ownership manifest (P5)`.
### Task 3: Render `paths.annotations_root` and make the harness resolve it
**Files:** modify `backend/src/workspaces/runtime-config-lease.ts`, `harness/tht/config.py`,
`harness/tht/cli/schema_cmd.py`; tests both layers.
1. Failing tests: rendered config includes `paths.annotations_root =
/data/sessions/<id>/revisions/<commit>/artifacts` while `paths.artifacts`/`indexes`/`memory`/
`sessions` stay unchanged; `tht schema` `annotations_path` prefers `paths.annotations_root` and
falls back to the legacy `artifacts/mschema/annotations.yaml` when absent; a missing annotations
file yields an empty canonical set (not a crash).
2. Implement the render field and harness resolution with the legacy fallback.
3. Commit: `feat: revision-qualified annotations root for pinned runtimes (P5)`.
### Task 4: Operator `workspace schema accept --run <id> --yes`
**Files:** modify `backend/src/workspaces/preprocessing-service.ts`,
`backend/src/workspace-maintenance.ts`, `tools/thothctl/internal/workspaceops/operations.go`,
`tools/thothctl/cmd/thothctl/main.go`; tests `workspace-preprocessing-service.test.ts` and
`operations_test.go`.
1. Failing tests: the accept command reads the current synced Git blob, stages it, validates it with
the harness parser (structural + orphan check against the recorded candidate), and records
`{ reviewedCandidatesDigest, annotationsDigest, workspaceRevision }` plus `blobId`; missing
`--yes`, unknown run, empty/malformed blob, and non-matching candidate fail closed with no review;
the recorded review is keyed by the run id.
2. Implement `WorkspacePreprocessingService.acceptSchema`, `workspace-maintenance` dispatch
(`schema-accept`), and the `thothctl` grammar/validation/execute path.
3. Commit: `feat: operator schema accept command for curated FK review (P5)`.
### Task 5: Continuation gate on the accepted blob; supersede host-file review
**Files:** modify `backend/src/workspaces/preprocessing-service.ts` (+ tests).
1. Failing tests: `preprocess run` FK review requires an accepted review whose `annotationsDigest`
equals the current revision's synced blob digest and a compatible DWH binding; a digest mismatch
starts a new review (`manual_review_required`); the host-file `schema check --annotations
--reviewed-candidates` path validates but does not record a review.
2. Implement the gate and the supersession.
3. Commit: `feat: gate FK review on the accepted revision blob (P5)`.
### Task 6: Contract, manual walkthrough, and clean-state acceptance
**Files:** modify `docs/contracts/workspace-preprocessing-cli.md`,
`docs/testing/p2-p6-manual-verification.md` (P5 section), `PROJECT_STATE.md`; add
`scripts/p5-acceptance.sh`, `scripts/test-p5-acceptance.sh`, `backend/scripts/p5-acceptance.mjs`,
`backend/scripts/p5-acceptance.test.mjs` (pattern: P4 acceptance, owned root
`.artifacts/p5-integration/p5-<run-id>/`).
1. Update the CLI contract (new command, annotations lifecycle, exit codes, JSON fields).
2. Implement the clean-state scenario: fixture P1.1 registry + curated annotations + REST DWH +
pre-provisioned Qdrant; run `thothctl` product commands; prove activation sync + ownership
manifest, revision isolation, accept happy path, `--yes`/empty/malformed/mismatch negatives, the
continuation gate, no push of curated content, secret scan, exact cleanup.
3. Finalize the P5 manual walkthrough section and record the phase in PROJECT_STATE.md.
4. Commit: `feat: P5 curated FK annotations in Git (acceptance + docs)`.
---
## Owner checkpoint
After Task 6 the implementation stops for recap. The owner records the P5 manual acceptance
(automated PASS is never recorded as manual PASS), then authorizes P6.
## Non-goals
- No GUI/backend preprocessing endpoint, no push/stage/commit of curated content.
- No P6 filesystem Evidence materialization (the `evidence_materialization_required` stop remains).
- No real PSD migration, no SSH runtime transport, no policy-driven GC.
- No change to the accepted P1/P1.1/P2/P3/P4 contracts or retained evidence beyond the documented
P5 supersession of the host-file FK review.