43 KiB
P2 Host Workspace Preprocessing CLI Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Implement P2/D2 as a native thothctl workspace interface that runs the existing preprocessing engine in a hardened one-shot container, derives its configuration from one active schema-v3 Git workspace revision plus installation-local bindings, and requires no Python, Node, Pi, or running backend HTTP service on the host.
Architecture: thothctl validates a closed command grammar, reconstructs the exact installation Compose project, resolves the selected core image to an immutable Docker image ID, and starts only the profile-gated workspace-maintenance service with --no-deps. A compiled Node entrypoint reads an already-active immutable registry snapshot, uses the same binding resolver and runtime renderer as sessions, writes a deterministic revision-owned protected harness config, and invokes fixed existing tht commands. A versioned coordinator state and one kernel-released writer lock serialize mutation, preserve outer/child resume identity, and stop at a digest-bound FK review checkpoint.
Tech Stack: Go 1.24 (thothctl), Docker Compose v2, Node.js 22, TypeScript 5, Python 3.12, Typer, Pydantic 2, Qdrant 1.18.2, the internal Ollama-compatible embedding interface, Vitest, pytest, Bash/Node acceptance tooling.
Source PRD and design: docs/prd/2026-08-09-workspace-preprocessing-prd.md D2/P2, RF1.2–RF1.4, RF2, RF3.1, RF4.1, RF5.2, RF8.5–RF8.6, RNF1–RNF9; docs/superpowers/specs/2026-08-10-p2-p6-workspace-preprocessing-design.md §§1–4, 9–10; docs/testing/p2-p6-manual-verification.md P2.
Planning status: DESIGN/PLAN ONLY. Do not change production code, start P2 implementation, or create a persistent implementation goal until the reviewer asks for plan validation and then gives explicit implementation approval.
P2 completion contract
P2 is complete only when all of the following are true:
- The only public host interface is the installed native
thothctlbinary. Docker/Compose is required, but host Python, Node, Pi,tht, and a running Fastify backend are not. - Every command consumes an already-active, validated P1.1 registry snapshot and binds the exact workspace ID, 40-hex commit, catalog blob (
thoth-workspaces.yaml), descriptor blob/digest, installation bindings, runtime roots, and selected internal semantic contract before mutation. A docs-only or content-only commit is still a distinct revision even when the descriptor blob is unchanged, because the commit is authoritative. - Operator and session configuration use the same
resolveRuntimeBindingsandrenderRuntimeConfigimplementation. P2 uses one deterministic same-revision config-source path so current schema-v1 DWH/Evidence resume works; P3 later introduces cross-revision canonical effective identity and explicit migrations. - DWH introspection+LSH, FK suggestion/check, schema indexing, and HTTP Evidence preprocessing invoke the existing harness engine through fixed argv and pristine JSON machine interfaces. No second preprocessing engine is added.
- A full run with new FK candidates stops before schema/Evidence writes. Continuation requires a reviewer-supplied annotations file and an explicit acknowledgement of the exact candidate digest;
schema checkalone is not treated as human approval. - Mutating P2 operations are safe while roots and semantic point IDs are still workspace-global: under the workspace writer lock they refuse if any resumable session is pinned to a different workspace revision. P3 removes this temporary restriction by introducing revision-scoped curated/semantic state.
- P2 never creates, repairs, deletes, or rebuilds a Qdrant collection. Schema/Evidence writes require an already-existing, exactly compatible collection and a harness
require_existingmode that cannot race into auto-create. P4 owns lifecycle reconciliation. - HTTP Evidence is operational only under installation-local egress policy. Private hosts require an exact installation allowlist; redirects are rechecked; metadata/link-local targets are always refused. S3 custom/private/insecure endpoints and ambient credentials remain fail-closed in P2 unless a later separately reviewed plan expands policy.
- Filesystem Evidence is rendered but execution stops before discovery with
evidence_materialization_requiredand no partial corpus/vector publication. P6 owns materialization and symlink/containment checks. postgres_directandrest_apirouting remain supported and are regression-tested; the clean P2 process goal uses controlled REST.ssh_tunnelreturns a stable fail-closed result until P10.- One clean-state product-path integration command passes without retry, produces retained machine/human reports and a secret scan, and proves exact cleanup. Manual P2 acceptance remains independent and PENDING.
- Work stops after the P2 handoff. No P3 work begins without a new explicit user authorization.
Truthful command status at the P2 checkpoint
| Command | P2 status | Deliberate boundary |
|---|---|---|
workspace inspect |
Operational | Reads active snapshot only; does not pull/activate Git |
workspace preprocess dwh |
Operational for REST/direct | Same-revision config identity; cross-revision reuse is P3 |
workspace schema suggest-fks |
Operational, machine-safe | Candidate export only; no automatic human acceptance |
workspace schema check |
Operational | Imports reviewed annotations and records digest-bound local P2 acknowledgement |
workspace index-schema |
Operational with compatible pre-existing collection | Collection create/repair/rebuild is P4 |
workspace preprocess evidence |
Operational for policy-allowed HTTP; filesystem deferred | Filesystem materialization is P6; S3 expansion needs separate policy review |
workspace preprocess run |
Operational with FK checkpoint | Git-canonical annotations are P5; revision-global writes use the P2 session-inventory guard |
P2 is therefore the host CLI/orchestration checkpoint, not final acceptance of the PSD filesystem path or the complete PRD chain.
Frozen host command grammar
thothctl --installation <absolute>/thothii-installation.yaml workspace inspect
--workspace <id> [--json]
thothctl ... workspace preprocess dwh
--workspace <id> [--resume <outer-run-id>] [--json]
thothctl ... workspace schema suggest-fks
--workspace <id>
[--from-sql <regular-file>]... [--assume <column=table>]...
[--output <new-file>] [--json]
thothctl ... workspace schema check
--workspace <id>
[--annotations <regular-file> --reviewed-candidates <sha256:hex>]
[--json]
thothctl ... workspace index-schema
--workspace <id> [--json]
thothctl ... workspace preprocess evidence
--workspace <id> [--dry-run] [--resume <outer-run-id>] [--json]
thothctl ... workspace preprocess run
--workspace <id> [--resume <outer-run-id>] [--json]
Rules:
--workspaceoccurs exactly once and matches[a-z][a-z0-9-]{2,62}.- All run IDs are 32 lowercase hex characters and identify outer P2 state, never a path or raw child checkpoint.
- At most 32
--from-sqlfiles, 1 MiB each and 16 MiB total.thothctlopens each as a canonical regular non-symlink/reparse-point file, rechecks identity after reading, and streams a schema-versioned request over stdin. No host directory is mounted. --assumeoccurs at most 256 times; each value is at most 256 bytes and is validated before Compose.--annotationsis a single UTF-8 YAML file, at most 16 MiB.--reviewed-candidatesis mandatory with it and must equal the persisted candidate artifact digest. The pair is invalid without both flags.--outputis created exclusively with restrictive permissions after the returned workspace/run/digest identity has been verified. Existing files, symlinks, hardlinks, and Windows reparse targets are refused.- Existing harness
suggest-fks --writeis intentionally not exposed: an automatic merge is not a human review decision. - No unknown flag, passthrough separator, environment-selected command, shell fragment, or arbitrary container entrypoint is accepted.
Public result and exit contract
The one-shot entrypoint always emits exactly one bounded schema-versioned JSON object. thothctl --json parses it strictly and re-encodes it, so Compose progress cannot contaminate stdout. Human mode renders only allowlisted fields.
interface WorkspaceOperationResult {
schemaVersion: 1;
status: "succeeded" | "unchanged" | "dry_run" | "blocked" | "failed";
code:
| "ok" | "workspace_not_found" | "workspace_not_activatable"
| "binding_missing" | "preprocessing_conflict"
| "preprocessing_resume_mismatch" | "manual_review_required"
| "evidence_materialization_required" | "effective_config_mismatch"
| "semantic_index_incompatible" | "annotation_invalid"
| "egress_policy_refused";
workspaceId: string;
workspaceRevision: string;
descriptorBlob: string;
operation: string;
runId?: string;
childRuns?: Record<string, string>;
completedStages: string[];
counts?: Record<string, number>;
artifactIdentities?: Array<{ kind: string; digest: string }>;
warnings?: string[];
}
- Exit
0:succeeded,unchanged, ordry_run. - Exit
3: expected operator checkpoint/block (manual_review_required,evidence_materialization_required, lock/revision conflict). - Exit
2: host grammar or unsafe local file error. - Exit
1: operational failure. - Stdout JSON maximum: 1 MiB. Sanitized stderr maximum: 64 KiB. Child stdout/stderr and every stage have explicit limits/timeouts.
- Never return descriptor endpoints with credentials/query strings, secret contents or paths, signed URLs, raw SQL, rendered configuration, raw child stderr, arbitrary exception text, Qdrant payload contents, or host/container environment dumps.
P2 state and identity layout
/data/sessions/<workspace-id>/preprocessing/
├── writer.lock
├── runtime-config/
│ └── <40-hex-revision>.yaml
├── runtime-config-manifests/
│ └── <40-hex-revision>.json
├── jobs/
│ └── <32-hex-outer-run-id>.json
├── fk-candidates/
│ └── <32-hex-outer-run-id>.yaml
└── fk-reviews/
└── <32-hex-outer-run-id>.json
writer.lockis a regular0600file held by a Linux kernel advisory lock for the entire outer operation. The file may persist; the kernel lock is released on crash/container death.inspectnever takes it.- Lock order is always P2 workspace writer lock → existing harness stage lock. Harness code never acquires the P2 lock, preventing inversion/deadlock.
- Every directory component is opened/validated without following symlinks. State files are
0600, written to an exclusive sibling, fsynced, renamed, and parent-fsynced. Hardlink count must be one. - The deterministic config path fixes P2 same-revision
config_sourceidentity. Its manifest binds workspace, revision, descriptor blob, config SHA-256, file identity, and the current existing harness ownership binding. Same path + different bytes returnseffective_config_mismatch; P3 introduces semantic cross-revision equivalence. - Job state binds operation, revision, catalog blob, descriptor blob, config digest, non-secret binding identity, completed stage records, child run IDs, candidate/review digests, and terminal status. Resume revalidates all fields and reconciles a child publication that completed immediately before an outer-state crash.
- Before any schema/Evidence mutation, enumerate resumable session manifests for the workspace. A different pinned revision returns
preprocessing_conflict; no write begins. This is the explicit P2 bridge until P3 revision isolation.
One-shot service security contract
workspace-maintenance is a dedicated profile service, not compose run core:
- same exact selected core image, resolved to its immutable local image ID; generated final override uses that ID and
pull_policy: never; docker compose run --rm --no-deps --no-TTY --name <owned-name> workspace-maintenance ...;- no
build, frontend, published port, Pi auth, Pi state, Pi trust initialization, Docker socket, home credential directory, or arbitrary command; - non-root
10001,read_only: true,cap_drop: [ALL],no-new-privileges:true, restrictive tmpfs, registry active snapshots read-only, sessions root writable; - only operation-required connector/Evidence secret files are mounted; AWS ambient environment is cleared;
- semantic commands require already-running healthy Qdrant/embedding services and do not start/stop them; DWH/inspect commands do not start dependencies;
- exact operation labels and container identity are recorded. Cancellation terminates the process group, verifies the owned container labels/image, removes only that container, and preserves all pre-existing services, volumes, and networks;
- fully rendered Compose is validated before launch, and post-run container/image identity is checked before accepting output.
Target file map
Native host CLI
tools/thothctl/cmd/thothctl/main.go,main_test.go: public grammar/help, dispatch, exit codes.- Create
tools/thothctl/internal/workspaceops/operations.go,operations_test.go: immutable image resolution, generated override, Compose run, cancellation cleanup, JSON validation. tools/thothctl/internal/config/installation.go,installation_test.go: maintenance service override and operation-specific binding discovery.tools/thothctl/internal/safeio/files.go, platform files/tests: bounded no-follow input and exclusive output.tools/thothctl/internal/output/sanitize.go, tests: bounded redaction.tools/thothctl/internal/pi/update.go, tests: selected image override must pin bothcoreandworkspace-maintenance.
Compose/image boundary
compose.yaml: dedicated profile-gated service with shared image identity and least privilege.deploy/compose.local.yaml,deploy/compose.server.yaml: correct registry/session storage semantics.deploy/compose.git-https.yaml,deploy/compose.git-ssh.yaml: do not attach Git credentials to P2 active-snapshot operations.scripts/generate-connector-secrets-override.sh: operation-specific maintenance secrets.- Create
docker/workspace-maintenance-entrypoint.sh; modifydocker/core.Dockerfile. - Retire/redirect fixture-only
deploy/compose.preprocess.yamlas a non-public compatibility test path; do not leave two operator commands.
Shared Node operator
- Create
backend/src/workspaces/runtime-config-lease.ts: shared snapshot read/render and deterministic protected config lease. - Modify
backend/src/tht/tht-runner.tsto delegate session/operator rendering to the shared component without changing route behavior. - Create
backend/src/workspaces/preprocessing-state.ts: state schema, durable writes, locks, resume reconciliation. - Create
backend/src/workspaces/preprocessing-service.ts: closed stage coordinator and security preflights. - Create
backend/src/workspace-maintenance.ts: compiled stdin/argv entrypoint and pristine result encoder. - Add tests:
backend/test/workspace-runtime-config-lease.test.ts,workspace-preprocessing-state.test.ts,workspace-preprocessing-service.test.ts,workspace-maintenance.test.ts.
Harness machine contracts
harness/tht/cli/preprocess_cmd.py: authoritative runtime workspace identity; require-existing collection mode.harness/tht/cli/schema_cmd.py: extracted deterministic helpers, JSON suggest/check, safe SQL staging and annotation validation.harness/tht/cli/vector_cmd.py: JSON schema-index result.harness/tht/adapters/vector/qdrant.py: explicit non-creating strict mode for P2.- Tests:
harness/tests/test_preprocess_cli.py,test_schema_fk_annotations.py,test_qdrant_cli_commands.py,test_registry_evidence_config.py,test_http_evidence_source.py, plus new focused security cases.
Docs and gates
- Create
docs/contracts/workspace-preprocessing-cli.md. - Update local/server installation manuals and
docs/testing/p2-p6-manual-verification.mdP2 only. - Create
scripts/p2-acceptance.sh,backend/scripts/p2-acceptance.mjs,backend/scripts/p2-acceptance.test.mjs. - Create
scripts/p2-manual-acceptance.sh,backend/scripts/p2-manual-acceptance.mjsonly if needed to generate the isolated walkthrough lab; automation must never create PASS. - Update
PROJECT_STATE.mdonly after implementation evidence exists.
Task 1: Freeze the native CLI, file-ingress, and result contracts
Files:
-
Modify:
tools/thothctl/cmd/thothctl/main.go -
Modify:
tools/thothctl/cmd/thothctl/main_test.go -
Modify:
tools/thothctl/internal/safeio/files.go -
Modify platform-specific safe-I/O tests
-
Create:
tools/thothctl/internal/workspaceops/operations.go -
Create:
tools/thothctl/internal/workspaceops/operations_test.go -
Create:
docs/contracts/workspace-preprocessing-cli.md -
Step 1: Write RED parser-table tests for every valid command above and for duplicate/missing/unknown flags, invalid IDs, incompatible annotation flags, option-count/size limits, and passthrough/shell attempts.
-
Step 2: Run
cd tools/thothctl && go test ./cmd/thothctl ./internal/workspaceops -run 'Workspace|workspace' -vand verify the new tests fail becauseworkspaceis unknown. -
Step 3: Add closed request types (
InspectRequest,DwhRequest,SuggestFksRequest,CheckSchemaRequest,IndexSchemaRequest,EvidenceRequest,RunRequest) and a parser that cannot represent arbitrary argv. -
Step 4: Write RED safe-I/O tests for symlinks, hardlinks, directory input, replacement during read, Windows reparse points, existing output, >1 MiB SQL, >16 MiB total, and non-UTF-8 annotation input.
-
Step 5: Implement bounded reads and exclusive restrictive output using existing platform seams; return only generic file errors.
-
Step 6: Add schema-v1 stdin request/result validation with exact field allowlists and output bounds.
-
Step 7: Run focused Go tests and
gofmt -w, thengo test ./.... -
Step 8: Commit:
feat: define P2 host workspace command contract.
Task 2: Add pristine harness JSON interfaces without changing the engine
Files:
-
Modify:
harness/tht/cli/schema_cmd.py -
Modify:
harness/tht/cli/vector_cmd.py -
Modify:
harness/tht/cli/preprocess_cmd.py -
Modify:
harness/tests/test_schema_fk_annotations.py -
Modify:
harness/tests/test_qdrant_cli_commands.py -
Modify:
harness/tests/test_preprocess_cli.py -
Step 1: Write RED tests requiring
schema suggest-fks --json,schema check --json, andvector index-schema --jsonto emit exactly one JSON object on stdout for success and failure, with no color/prose contamination. -
Step 2: Write RED deterministic FK tests for bounded staged SQL files, stable candidate ordering, candidate SHA-256, annotation import, orphan counts, and no implicit review/write.
-
Step 3: Write RED Evidence identity test showing a config named
/dev/fd/3still usesruntime_identity.workspace_id, never the config basename. -
Step 4: Run:
cd harness
.venv/bin/pytest -q \
tests/test_schema_fk_annotations.py \
tests/test_qdrant_cli_commands.py \
tests/test_preprocess_cli.py
Expected: FAIL only on the new machine-contract assertions.
- Step 5: Extract pure helpers returning typed dictionaries/models; keep existing human commands as renderers over the same helpers.
- Step 6: Implement the JSON flags and authoritative workspace identity. Catch expected exceptions and emit stable safe codes; never serialize arbitrary exception text.
- Step 7: Run the three focused files and touched Ruff:
.venv/bin/ruff check \
tht/cli/schema_cmd.py tht/cli/vector_cmd.py tht/cli/preprocess_cmd.py \
tests/test_schema_fk_annotations.py tests/test_qdrant_cli_commands.py tests/test_preprocess_cli.py
- Step 8: Commit:
feat: add P2 harness machine contracts.
Task 3: Add a non-creating semantic writer mode
Files:
-
Modify:
harness/tht/config.py -
Modify:
harness/tht/adapters/factory.py -
Modify:
harness/tht/adapters/vector/qdrant.py -
Modify:
harness/tests/test_qdrant_vector_store.py -
Modify:
harness/tests/test_qdrant_cli_commands.py -
Modify:
harness/tests/test_registry_evidence_config.py -
Step 1: Write RED tests proving operator mode refuses a missing collection without issuing create/index mutations, refuses wrong dimensions/distance/index type, and still writes to an existing compatible collection.
-
Step 2: Run the focused tests and confirm current
_ensure_collection(strict=True)incorrectly creates the collection. -
Step 3: Add an internal rendered field such as
vectors.collection_lifecycle: require_existing; it is not a descriptor option and defaults to legacy behavior for non-operator configs. -
Step 4: Thread the mode through the factory/store and perform a read-only exact collection/index preflight before any upsert.
-
Step 5: Add a race regression: delete the collection after preflight and prove the write fails rather than recreates it.
-
Step 6: Run focused pytest and touched Ruff.
-
Step 7: Commit:
fix: prevent P2 from owning Qdrant lifecycle.
Task 4: Extract the shared runtime configuration lease
Files:
-
Create:
backend/src/workspaces/runtime-config-lease.ts -
Create:
backend/test/workspace-runtime-config-lease.test.ts -
Modify:
backend/src/tht/tht-runner.ts -
Modify:
backend/test/tht-runner.test.ts -
Modify:
backend/test/workspace-runtime-handoff.test.ts -
Step 1: Write RED equivalence tests feeding the same immutable snapshot, env, roots, installation overlay, and semantic contract to the session and operator callers and requiring byte-identical YAML.
-
Step 2: Write RED identity/safety tests for snapshot replacement, wrong commit/path, symlink/hardlink, wrong workspace ID, unstable config destination, same-revision changed bytes, mode, fsync/rename failure, and cleanup.
-
Step 3: Run:
cd backend
npx vitest run \
test/workspace-runtime-config-lease.test.ts \
test/tht-runner.test.ts \
test/workspace-runtime-handoff.test.ts
- Step 4: Move snapshot validation, runtime roots, installation-overlay parsing, binding resolution, and rendering out of
ThtRunnerinto one explicit-input component. - Step 5: Preserve session behavior:
ThtRunner.acquireWorkspaceRuntimedelegates to the component and retains its current opaque FD-backed temporary lease. - Step 6: Add operator mode: deterministically publish
/data/sessions/<id>/preprocessing/runtime-config/<revision>.yamlplus a manifest, mode0400/0600, and setcollection_lifecycle: require_existing. - Step 7: Prove same-revision rerun path identity and changed config/binding refusal. Do not implement P3 semantic cross-revision canonicalization.
- Step 8: Run focused tests,
npx tsc --noEmit -p ., andnpm run build. - Step 9: Commit:
refactor: share registry runtime configuration leases.
Task 5: Build durable outer state, locking, and revision guard
Files:
-
Create:
backend/src/workspaces/preprocessing-state.ts -
Create:
backend/test/workspace-preprocessing-state.test.ts -
Modify:
docker/core.Dockerfile(install/pin the kernel lock utility only when the implementation proves it is absent) -
Step 1: Write RED state-schema tests for valid state, same-operation resume, cross-workspace/revision/operation/config mismatch, tampering, run-ID traversal, restrictive modes, atomic failure, and bounded fields.
-
Step 2: Write RED cross-process lock tests with two processes/containers: one wins, one receives
preprocessing_conflict, and SIGKILL releases the kernel lock without deleting unrelated state. -
Step 3: Write RED session-inventory tests: no sessions/current-only sessions permit mutation; a resumable different-revision manifest blocks; finalized/archived sessions follow existing resume policy.
-
Step 4: Implement the exact state layout and durable write protocol described above.
-
Step 5: Implement lock acquisition ordering and safe conflict mapping. Do not invent stale-PID deletion; the kernel owns lock lifetime.
-
Step 6: Implement active-snapshot revalidation immediately before each mutating child stage and the different-revision resumable-session guard.
-
Step 7: Add crash reconciliation tests where a child publishes DWH/corpus state but outer state has not yet advanced.
-
Step 8: Run focused Vitest, typecheck, and build.
-
Step 9: Commit:
feat: add P2 preprocessing operation state.
Task 6: Build the compiled inspect/operator boundary
Files:
-
Create:
backend/src/workspace-maintenance.ts -
Create:
backend/src/workspaces/preprocessing-service.ts -
Create:
backend/test/workspace-maintenance.test.ts -
Create:
backend/test/workspace-preprocessing-service.test.ts -
Modify:
backend/src/workspaces/types.tsonly if a separate operator-code union cannot stay private -
Step 1: Write RED entrypoint process tests for exact JSON, malformed/extra stdin, unknown command/field, stdout/stderr bounds, timeout, signal, raw exception/stderr redaction, and no Fastify listener.
-
Step 2: Write RED
inspecttests for absent/corrupt/stale active state, migration-required descriptor, missing bindings, exact commit/blob/config identities, safe capability warnings, and no URL/secret output. -
Step 3: Run focused Vitest and verify no operator exists.
-
Step 4: Implement a closed
WorkspacePreprocessingServicedependency interface: active registry reader, shared config lease, fixed child runner, state store, session inventory, semantic preflight, egress policy. -
Step 5: Implement active-snapshot-only acquisition. P2 does not pull or activate Git; clean installations receive
workspace_not_activatablewith safe instructions. -
Step 6: Implement bounded child execution with fixed executable/argv,
-cafter the subcommand, FD-backed config/input, process-group cancellation, per-stage timeout, and strict one-document child JSON parsing. -
Step 7: Implement
inspectand result encoding. -
Step 8: Run tests, typecheck, build, and verify
dist/workspace-maintenance.jsexists. -
Step 9: Commit:
feat: add P2 workspace maintenance operator.
Task 7: Implement DWH preprocessing and outer resume
Files:
-
Modify:
backend/src/workspaces/preprocessing-service.ts -
Modify:
backend/test/workspace-preprocessing-service.test.ts -
Modify:
harness/tests/test_dwh_preprocess_job.py -
Modify:
harness/tests/test_lsh_job_resume.py -
Step 1: Write RED service tests requiring fixed
preprocess dwh --steps introspect,lsh --json -c /dev/fd/N, child result validation, outer/child run IDs, completed stages, safe artifact digests, and failure mapping. -
Step 2: Add real harness regressions for deterministic same-revision config-source path, second clean-process rerun, resume after introspection, changed binding/config refusal, and ACTIVE preservation on failure.
-
Step 3: Run focused backend and harness tests.
-
Step 4: Implement
preprocess dwhunder the outer writer lock and persist state before/after every child transition. -
Step 5: Reconcile a published child run after an injected outer crash without rerunning or corrupting ACTIVE.
-
Step 6: Verify REST and direct rendered routing. SSH returns
workspace_not_activatablewith a P10 warning. -
Step 7: Run focused gates and commit:
feat: run workspace DWH preprocessing from thothctl operator.
Task 8: Implement FK candidate export and digest-bound review
Files:
-
Modify:
backend/src/workspaces/preprocessing-service.ts -
Modify:
backend/src/workspaces/preprocessing-state.ts -
Modify: relevant backend tests
-
Modify:
tools/thothctl/internal/workspaceops/operations.go -
Modify: Go tests
-
Step 1: Write RED end-to-end unit/process tests: new candidates create a bounded artifact and return
manual_review_required; schema/Evidence child calls are absent. -
Step 2: Add safe host ingress tests proving SQL and annotations travel only over stdin, are absent from Compose argv/state/logs, and staging files are removed.
-
Step 3: Add safe host egress tests for candidate export identity/digest, existing destination refusal, and sanitized JSON mode.
-
Step 4: Implement
schema suggest-fkswith candidate count/digest and optional exclusive output. -
Step 5: Implement
schema checkin two modes: read-only orphan validation; or reviewed annotation import requiring the exact candidate digest. Persist review digest + annotation digest + workspace/revision. -
Step 6: Require the review record on full-run resume. A mere zero-orphan result without reviewer digest is insufficient.
-
Step 7: Preserve the boundary: P2 updates runtime-local annotations only; it never writes Git. Output warns that P5 will supersede this local acknowledgement.
-
Step 8: Run Go/backend/harness focused gates and commit:
feat: add P2 FK review checkpoint.
Task 9: Implement schema indexing and Evidence policy boundaries
Files:
-
Modify:
backend/src/workspaces/preprocessing-service.ts -
Modify: backend service tests
-
Modify:
harness/tests/test_http_evidence_source.py -
Modify:
harness/tests/test_registry_evidence_config.py -
Modify:
harness/tests/test_semantic_kind_isolation.py -
Step 1: Write RED schema-index tests for compatible pre-existing collection, deterministic JSON counts, idempotent repeat, missing/incompatible refusal, and no collection-create request.
-
Step 2: Write RED Evidence tests for no-Evidence warning/skip, HTTP dry-run/run/resume/unchanged/mutation, authoritative workspace identity, ACTIVE preservation, and filesystem early stop before adapter/Qdrant calls.
-
Step 3: Write RED egress tests for exact private-host allowlist, DNS re-resolution, redirect to private/link-local/metadata, signed URL query redaction, and refusal of S3 ambient/custom/private/insecure modes.
-
Step 4: Implement installation-local egress-policy parsing with exact bounded hostnames and no wildcard. Descriptor flags alone never grant network access.
-
Step 5: Implement
index-schemaandpreprocess evidencewith semantic preflight and stable result mapping. -
Step 6: Revalidate active revision and session inventory immediately before each write.
-
Step 7: Run focused tests/touched Ruff/backend typecheck/build and commit:
feat: add guarded P2 semantic preprocessing.
Task 10: Implement the ordered full-run coordinator
Files:
-
Modify:
backend/src/workspaces/preprocessing-service.ts -
Modify:
backend/test/workspace-preprocessing-service.test.ts -
Modify:
backend/test/workspace-maintenance.test.ts -
Step 1: Write a RED stage-table test for exact order
dwh → fk_suggest → fk_review/check → schema_index → evidenceand for no hidden/skipped mutation. -
Step 2: Add scenarios: pre-curated/no-new-candidate completion; new-candidate block; digest-reviewed resume; no-Evidence warning; filesystem deferred block; each child failure; resume mismatch; outer crash reconciliation.
-
Step 3: Implement the coordinator as an explicit state machine, not recursive command dispatch.
-
Step 4: Persist completion after each verified child artifact and never mark a stage based only on exit code.
-
Step 5: Prove unchanged rerun creates no duplicate schema/Evidence points or generation.
-
Step 6: Run focused Vitest, typecheck, build, and commit:
feat: orchestrate the P2 preprocessing chain.
Task 11: Add the hardened maintenance service and selected-image handoff
Files:
-
Modify:
compose.yaml -
Modify:
deploy/compose.local.yaml -
Modify:
deploy/compose.server.yaml -
Modify: connector/Git override files and generators as required
-
Create:
docker/workspace-maintenance-entrypoint.sh -
Modify:
docker/core.Dockerfile -
Modify:
tools/thothctl/internal/workspaceops/operations.go -
Modify:
tools/thothctl/internal/pi/update.go -
Modify relevant Go/Bash/Compose tests
-
Step 1: Write RED Compose contract tests for the exact security contract, profile, mounts, no Pi/no port/no build, local/server storage, and absence of Git credentials on active-snapshot operations.
-
Step 2: Write RED image-precedence tests across base/profile/operator overrides/current-image override;
coreand maintenance must resolve to the same immutable ID. -
Step 3: Write RED lifecycle tests for
--no-deps, pre-existing service preservation, interruption cleanup, hostile container name/label collision, tag replacement, and output rejection on post-run image mismatch. -
Step 4: Add the dedicated service and entrypoint; the entrypoint executes only the compiled operator and never calls Pi trust setup.
-
Step 5: Generate a per-operation final override that pins immutable image ID, exact secrets, egress policy, and owned labels. Validate
docker compose configstructurally before run. -
Step 6: Extend Pi update/rollback override generation so future selected images cannot split core and maintenance.
-
Step 7: Run:
bash scripts/test-preprocess-compose-config.sh
bash scripts/test-compose-secret-policy.sh
bash scripts/test-default-compose.sh
bash scripts/test-unified-compose.sh
bash scripts/test-no-deployment-coupling.sh
cd tools/thothctl && go test ./...
- Step 8: Build the core image and invoke operator
--helpthrough the exact service without starting Pi/backend/dependencies. - Step 9: Commit:
feat: package the P2 maintenance service.
Task 12: Complete host dispatch and supported-platform build contract
Files:
-
Modify:
tools/thothctl/cmd/thothctl/main.go, tests -
Modify:
tools/thothctl/internal/workspaceops/operations.go, tests -
Modify:
scripts/build-thothctl.sh -
Modify:
scripts/test-thothctl-build-contract.sh -
Update operator docs
-
Step 1: Add RED command-to-request-to-Compose tests for all seven public commands, JSON/human output, exit mapping, secret redaction, and exact stdin.
-
Step 2: Implement dispatcher integration using only typed requests.
-
Step 3: Cross-build the existing release matrix and verify Windows input/output safety compiles. Do not claim Windows Docker behavior without a Windows Docker run.
-
Step 4: Run
go test ./..., build contract,go vet ./..., andgofmtcheck. -
Step 5: Commit:
feat: expose P2 workspace commands in thothctl.
Task 13: Build the clean-state automated P2 process goal
Files:
- Create:
scripts/p2-acceptance.sh - Create:
backend/scripts/p2-acceptance.mjs - Create:
backend/scripts/p2-acceptance.test.mjs - Update:
.gitignoreonly if the existing.artifacts/rule is insufficient
The public command is:
./scripts/p2-acceptance.sh integration --keep
- Step 1: Write RED acceptance-runner tests for ownership-first state, unique run/project/container/image names, exact cleanup,
--keep, injected failure, signal cleanup, report bounds, and no automatic retry. - Step 2: Build a clean owned topology under
.artifacts/p2-integration/p2-<run-id>/: local bare Git + author clone, active P1.1 snapshot (root catalog +<id>/workspace.yaml+<id>/evidence), installation descriptor/env, fixture-only secrets, controlled REST DWH, controlled HTTP Evidence, real compatible Qdrant, deterministic Ollama-compatible embedding fixture, selected core image, and no backend/Pi/frontend. - Step 3: Pre-provision the exact compatible Qdrant collection outside the product operation and record that setup as a P4-deferred fixture step.
- Step 4: Exercise only built
thothctlproduct commands and assert:- exact inspect revision/config identity;
- DWH introspection+LSH, clean-process rerun/resume, physical/LSH artifacts;
- FK pristine JSON, pause-before-index, candidate export, explicit digest review, resume;
- pre-curated full-run completion;
- schema index counts and unchanged rerun;
- HTTP Evidence dry-run, publish, unchanged rerun, input mutation/new generation/ACTIVE;
- no-Evidence warning/skip;
- filesystem
evidence_materialization_requiredwith no partial output; - direct renderer regression and SSH fail-closed result;
- missing workspace/binding, resume mismatch, different-revision resumable session, concurrent writer, annotation invalid, egress refusal, and semantic incompatibility;
- no collection creation, no backend listener, no Pi init, no arbitrary mount;
- exact cleanup preserving all foreign/pre-existing resources.
- Step 5: Produce bounded
report.jsonandreport.md, declare hashes for every retained owned artifact, and scan raw Git objects, names, state, reports, logs, configs, candidates, and Qdrant payloads for fixture canaries/signed queries/raw SQL. - Step 6: Run runner unit tests, then one clean integration run without retry. On failure, diagnose/fix/regress and start one new clean run; never loop blindly.
- Step 7: Commit tooling:
test: add P2 host preprocessing acceptance.
Task 14: Finalize P2 documentation and independent manual walkthrough
Files:
-
Update:
docs/testing/p2-p6-manual-verification.mdP2 section only -
Update: local/server installation manuals
-
Update:
docs/contracts/workspace-preprocessing-cli.md -
Optionally create manual lab helper files if concrete setup cannot remain concise
-
Step 1: Document prerequisites and boundaries: Docker/Compose, active registry snapshot, existing compatible collection, running semantic services for semantic commands, no host language runtimes, no backend/Pi.
-
Step 2: Fill exact P2 commands for inspect, DWH/resume, FK export/review digest, check/import, index, HTTP dry/run, full run, unchanged rerun, filesystem deferred result, secret scan, and cleanup.
-
Step 3: Explain each observed component/artifact without exposing config or secret contents.
-
Step 4: Require a new manual root and
VERDICT.mdwith reviewer, UTC time, explicit result for every P2 check, observations, and exactlyP2 manual acceptance: PASS|FAIL. Automation never writes it. -
Step 5: Add mechanical docs tests for all released commands and stable codes.
-
Step 6: Commit:
docs: add P2 preprocessing operator walkthrough.
Task 15: Run affected-layer verification and hand off the hard checkpoint
Files:
-
Modify after evidence exists:
PROJECT_STATE.md -
Step 1: Run complete affected Go gates:
cd tools/thothctl && go test ./... && go vet ./..., plus the release build contract. -
Step 2: Run complete backend gates:
cd backend && npx vitest run && npx tsc --noEmit -p . && npm run build. -
Step 3: Run focused harness tests listed in the target map, then full
.venv/bin/pytest -qif feasible. Any baseline failure must be reported exactly; touched Python files must be Ruff-clean. -
Step 4: Run all affected Compose/security contracts from Task 11 and
git diff --check. -
Step 5: Run exactly one final clean P2 integration at the final source commit:
./scripts/p2-acceptance.sh integration --keep
Expected final lines:
P2 automated integration: PASS
P2 manual acceptance: PENDING
- Step 6: Verify report hashes, declared artifacts, secret scan, closed listeners, no maintenance container, and exact cleanup/retention.
- Step 7: Update and commit only tracked project state with retained report path and truthful scope:
git add PROJECT_STATE.md
git commit -m "docs: record P2 automated acceptance"
- Step 8: Report separate statuses and STOP:
P2 automated integration: PASS — <retained report>
P2 manual acceptance: PENDING — docs/testing/p2-p6-manual-verification.md#p2
P3 authorization: PENDING — awaiting explicit user decision
Do not begin P3, mark manual PASS, or infer implementation approval from plan approval or automated evidence.
Requirement traceability
| Requirement/decision | P2 implementation/proof | Deferred truth |
|---|---|---|
| D2 / P2 / RNF7 | Native thothctl, dedicated one-shot service, existing engine |
GUI/backend endpoint excluded |
| RF1.2 / RNF1 | File-only bindings, operation-specific mounts, redaction/scan | No secret in Git/rendered output |
| RF1.3 / RNF5 | Same binding+renderer code and byte-equivalence test | P3 canonical cross-revision identity/migration |
| RF1.4 / RF2.2 | REST process goal; direct regression | SSH operational support P10 |
| RF2.1 | DWH command, JSON, outer+child resume | — |
| RF2.3 | Physical/LSH production then schema-index consumption | — |
| RF2.4 / RNF2 | Immutable engine generations, unchanged rerun, ACTIVE preservation | Cross-revision DWH reuse P3 |
| RF3.1 | JSON candidates/check, bounded SQL ingress, explicit digest review | Git-canonical review/sync P5 |
| RF3.2 / D5 | Runtime-local P2 annotations only | Repository annotations and pinned sync P5 |
| RF3.3 | No model-derived FK path added | Existing workflow invariant preserved |
| RF4.1 | Existing schema hash/upsert + JSON counts | Collection lifecycle P4 |
| RF5.2 | Policy-allowed HTTP dry/run/resume/publish | Filesystem P6; broader S3 policy separately reviewed |
| RF5.3 / D9 | Existing per-run behavior only | Long-term GC/retention P9 |
| RNF3 | Safe errors, prior ACTIVE preserved, no-Evidence warning | — |
| RNF4 | Workspace binding + P2 different-revision session guard | Revision-scoped points/roots P3 |
| RF8.5–8.6 / RNF8–9 | Clean process goal + separate walkthrough | Aggregate P2–P6 verification after P6 |
| PRD AC2 | Native release binary on local/server installation profiles | Windows Docker is a separate manual claim |
| PRD AC8 | HTTP unchanged/mutation cases | Filesystem change/GC P6/P9 |
Explicit exclusions
- No frontend/GUI or backend HTTP preprocessing endpoint.
- No host Python, Node, Pi,
tht, arbitrary shell, arbitrary entrypoint, or arbitrary host mount. - No Git pull/publish/push, active-revision transition, or authoring API in P2.
- No P3 canonical effective fingerprint, ownership migration, revision-scoped roots/points, or
.tht-dwhoperator chapter. - No P4 collection creation/index repair/rebuild/maintenance drain.
- No P5 Git-canonical FK annotation sync/acceptance.
- No P6 filesystem Evidence materialization, realpath/symlink containment, or pinned-tree retention.
- No PSD migration/re-embedding (P7), final search/session/L2 gate (P8), policy-driven long-term GC (P9), or SSH runtime (P10).
- No changed embedding model/dimensions/distance, external vector service, pgvector compatibility path, or NL→SQL workflow change.
Execution notes
- Every implementation task is RED → minimal GREEN → focused verification → commit.
- Never weaken an existing P1 security invariant to simplify P2.
- P2's session-inventory guard and deterministic same-revision config path are deliberate temporary safety mechanisms, not substitutes for P3.
- Keep automated FK mechanics distinct from human review and from the later P5 Git decision.
- A passed plan review authorizes only plan acceptance. Implementation starts only after the user's separate explicit approval.