50 KiB
P1.1 Workspace-Directory Git Registry 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. Use superpowers:test-driven-development for every behavior change and superpowers:verification-before-completion before any completion claim.
Goal: Replace P1's split/flat Git source layout with an authoritative root catalog and one self-contained directory per workspace, while making descriptor publication bootstrap-only and all later descriptor changes curator-owned through Git.
Architecture: thoth-workspaces.yaml becomes the strict curator-owned catalog; descriptors move to <id>/workspace.yaml, embedded Evidence moves to <id>/evidence, and generated docs remain under workspace-docs/<id>. The API may create a descriptor only when its catalog slot exists and the descriptor Git object is absent at the exact base commit; existing descriptors and curated content are read-only to the API. Internal immutable snapshot paths stay flat to preserve ThtRunner/session compatibility. P1.1 gets new automated and manual acceptance evidence; accepted P1 evidence remains historical and untouched.
Tech Stack: TypeScript 5, Zod 4, YAML, Fastify 5, Git CLI with fixed argv, React 18, Vitest, Node.js 22, Bash, Python harness tht config check.
Companion design draft: docs/superpowers/specs/2026-08-11-p1-1-workspace-directory-registry-design.md
P1.1 completion contract
P1.1 is complete only when all of the following are true:
-
The only accepted source-repository layout is:
thoth-workspaces.yaml <id>/workspace.yaml <id>/evidence/** # optional; required only for filesystem Evidence workspace-docs/<id>/{contract.env.example,README.md} -
The strict root catalog is curator-owned and authoritative for ID, name, description, and display order. Catalog-only entries are valid bootstrap slots with
configuration_required; orphan descriptors/directories and catalog/descriptor metadata mismatches invalidate the candidate atomically. -
Schema v3 remains the only descriptor schema. For filesystem Evidence the only URI is
<id>/evidence; HTTP/S3 and absent-Evidence behavior stay as delivered by P1. -
The API creates
<id>/workspace.yamlonly when no Git object exists there at the exact base commit. A present empty, malformed, symlink, submodule, tree, or valid descriptor is never replaced or deleted. Existing update/delete payloads fail with safeworkspace_curator_ownedsemantics. -
Curator-pushed descriptor/catalog/Evidence changes become active only after strict pull validation. The API never stages, writes, cleans, or pushes catalog or curated content.
-
Generated docs remain API-owned under
workspace-docs/<id>. Explicit registry synchronization may produce one deterministic docs-only commit; it must preserve catalog, descriptor, and Evidence object IDs and activate only the final validated commit. -
Internal snapshots remain
<snapshots>/<commit>/<id>.yaml; session revision pins, retention leases, runtime acquisition, export, and resume retain their current contract. -
Existing workspace UI is read-only for repository-backed descriptors. Only a catalog slot with no descriptor offers an editable bootstrap draft; stale old drafts cannot update/delete. Pull/sync, validate, installation test, export, and Evidence summary remain available.
-
A new clean-state P1.1 automated run and a separate P1.1 manual walkthrough prove the complete process. Accepted retained P1 artifacts remain immutable historical evidence and are not relabelled as P1.1. Old P1 process commands are not required to execute successfully against the superseding P1.1 repository contract.
-
No preprocessing, materialization, FK synchronization, Qdrant write, embedding, ACTIVE publication, GC, or P2–P6 plan edit is performed.
Explicit decisions frozen by this plan
- Root catalog name:
thoth-workspaces.yaml. - Workspace source directory:
<id>/. - Descriptor filename:
workspace.yaml. - Catalog schema:
schema_version: 1, orderedworkspaceslist with strict{id,name,description?}entries. - Catalog-only entries are allowed and listable as
configuration_required. - Descriptor metadata remains present for self-contained snapshots/exports and must exactly equal catalog metadata.
- "Empty" means absent Git object, not zero bytes.
- Old repository layouts are rejected; there is no dual reader or automatic remote migration.
- Internal snapshot filenames do not change.
- P2–P6 documents are inventoried but not edited until after owner manual acceptance of P1.1.
Task 1: Freeze the P1.1 design, catalog schema, and strict parser
Files:
- Add:
docs/superpowers/specs/2026-08-11-p1-1-workspace-directory-registry-design.md - Add:
backend/src/workspaces/catalog.ts - Add:
backend/test/workspaces-catalog.test.ts - Modify:
backend/src/workspaces/types.ts
Step 1: Write failing catalog parser tests
Define the exact public shape:
export interface WorkspaceCatalogEntry {
id: string;
name: string;
description?: string;
}
export interface WorkspaceCatalog {
schema_version: 1;
workspaces: WorkspaceCatalogEntry[];
}
Test:
- one and many ordered entries parse without reordering;
- optional description presence is preserved (missing is not silently converted to an empty string);
- Unicode names/descriptions and the descriptor's existing trim/nonblank semantics are preserved without adding a new length limit;
- duplicate IDs, invalid/reserved IDs (including
workspace-docs), blank names, unknown keys, duplicate YAML keys, aliases, tags, multiple documents, non-mappings, and malformed YAML are rejected withworkspace_invalid; - safe errors include a stable catalog field location but never rejected canary text;
assertCatalogMatchesDescriptor(entry, descriptor)accepts exact ID/name/optional-description equality and rejects every mismatch safely;- serialization, if exposed, is deterministic and does not become an API write path.
Step 2: Run the focused test and verify RED
cd backend
npx vitest run test/workspaces-catalog.test.ts
Expected: FAIL because catalog.ts does not exist.
Step 3: Implement the strict parser
Use yaml.parseAllDocuments with the same safe-document rules as parseWorkspaceYaml and a strict Zod schema. Keep catalog parsing separate from descriptor parsing. Export only:
export const CATALOG_PATH = "thoth-workspaces.yaml";
export function parseWorkspaceCatalogYaml(source: string): WorkspaceCatalog;
export function assertCatalogMatchesDescriptor(
entry: WorkspaceCatalogEntry,
workspace: WorkspaceDescriptor,
): void;
Do not project catalog metadata into a descriptor and do not read the filesystem from this module.
Add any new stable error code only when needed by later tasks; catalog syntax/matching errors remain workspace_invalid.
Step 4: Run tests and typecheck
cd backend
npx vitest run test/workspaces-catalog.test.ts test/workspaces-schema.test.ts
npx tsc --noEmit -p .
Expected: PASS.
Step 5: Commit
git add \
docs/superpowers/specs/2026-08-11-p1-1-workspace-directory-registry-design.md \
backend/src/workspaces/catalog.ts \
backend/src/workspaces/types.ts \
backend/test/workspaces-catalog.test.ts
git commit -m "feat: define P1.1 workspace catalog contract"
Task 2: Enforce the nested repository paths and curator/API ownership boundary
Files:
- Modify:
backend/src/workspaces/git-repository.ts - Modify:
backend/test/workspaces-git-repository.test.ts
Step 1: Write failing low-level Git tests
Create a real bare-repository fixture with:
thoth-workspaces.yaml
research/workspace.yaml
research/evidence/guide.md
workspace-docs/research/README.md
workspace-docs/research/contract.env.example
Test fixed-argv helpers that:
- read exactly
thoth-workspaces.yamlas a regular Git blob at HEAD/revision; - discover only
<id>/workspace.yamldescriptors, without treating nested Evidence files as descriptors; - reject flat
workspaces/<id>.yaml,workspace-content/**, the reservedworkspace-docsID, unlisted top-level workspace directories, traversal, alternate descriptor names, symlink descriptor, tree-at-descriptor, submodule/gitlink, and malformed IDs; - read/resolve the exact descriptor blob at
<id>/workspace.yaml; - accept only
<id>/evidenceas the filesystem Evidence root and require a Git tree at the exact revision; - keep nested symlink checks deferred to P6 while still rejecting a symlink at the declared root;
- prove catalog and
<id>/evidence/**are not API-writable/stageable paths; - allow only a create-only descriptor path and generated docs in API publication helpers;
- refuse an exclusive descriptor create if any filesystem/Git object already occupies the path;
- journal each exact API-owned path before mutation (object type/mode/blob/bytes or explicit absence);
- restore overwritten/deleted generated docs to their exact pre-operation objects and remove only absent-before files on failure, never by running a directory-wide
git clean; - cover failed bootstrap with pre-existing stale docs plus failed docs update/deletion, and preserve all curator object IDs across cleanup;
- use argv arrays only and do not invoke Git filters, hooks, shell interpolation, or helper-bearing repository state.
Step 2: Run the focused test and verify RED
cd backend
npx vitest run test/workspaces-git-repository.test.ts
Expected: old flat-path assertions fail and required helpers are missing.
Step 3: Implement narrow path helpers
Introduce named guards such as:
function workspaceDescriptorPath(id: string): string {
return `${safeWorkspaceId(id)}/workspace.yaml`;
}
function evidenceRootPath(id: string): string {
return `${safeWorkspaceId(id)}/evidence`;
}
Add read-only helpers for catalog/descriptor object type and descriptor discovery. Replace broad writeRegistryFile use for descriptors with an explicit exclusive creation primitive. Keep generated-doc writes separate and exact.
restoreFailedPublication must consume the bounded per-path journal from the current operation. It restores tracked generated docs from the prior blob/mode and deletes only paths proven absent before the operation. Remove any directory-wide git clean that can descend into curated workspace content.
Step 4: Run tests and typecheck
cd backend
npx vitest run test/workspaces-git-repository.test.ts
npx tsc --noEmit -p .
Expected: PASS.
Step 5: Commit
git add backend/src/workspaces/git-repository.ts backend/test/workspaces-git-repository.test.ts
git commit -m "refactor: enforce P1.1 registry path ownership"
Task 3: Make activation catalog-driven while preserving internal snapshots
Files:
- Modify:
backend/src/workspaces/registry.ts - Modify:
backend/test/workspace-registry.test.ts - Modify:
backend/src/workspaces/types.ts
Step 1: Write failing activation/state tests
Cover real Git candidates for:
- valid catalog plus one/many matching descriptors activates in catalog order;
- catalog-only entry activates as
configuration_requiredwithout aWorkspaceRevisionand without allowing session/read/test/export; - missing catalog, malformed catalog, duplicate catalog ID, orphan descriptor/directory, metadata mismatch, wrong descriptor path, duplicate Qdrant collection, invalid descriptor, or unsafe Evidence root leaves the prior active snapshot unchanged;
- a present empty/comments-only descriptor fails activation and is never converted into a bootstrap slot;
- removing a descriptor while leaving its catalog entry produces
configuration_required, while retained historical revisions/session pins remain readable; - removing catalog entry and its workspace directory together removes it from the active catalog but retains historical pinned snapshots;
- removing a catalog entry while leaving its workspace directory/descriptor is invalid;
- catalog order controls summaries independently from Git path order;
- offline fallback restores the last complete catalog and ready revisions;
- internal snapshot paths remain exactly
<snapshots>/<commit>/<id>.yaml; - the immutable snapshot binds a canonical catalog copy/digest plus canonical descriptor/docs, but contains no Evidence bytes;
- pre-P1.1 historical internal snapshots needed by already retained session pins remain readable, without accepting old source-repository layout for new activation.
Step 2: Run the registry suite and verify RED
cd backend
npx vitest run test/workspace-registry.test.ts
Expected: nested repository and catalog-only cases fail.
Step 3: Refactor candidate parsing from activation
Introduce an internal candidate model, for example:
interface WorkspaceCatalogRecord {
entry: WorkspaceCatalogEntry;
state: "ready" | "configuration_required";
revision?: WorkspaceRevision;
}
interface RegistryCandidate {
commit: string;
catalog: WorkspaceCatalog;
ready: Array<{
entry: WorkspaceCatalogEntry;
workspace: WorkspaceDescriptor;
descriptorPath: string;
blob: string;
}>;
missing: WorkspaceCatalogEntry[];
}
Separate:
readCandidate(commit)— read/validate Git objects without mutating active state;stageSnapshot(candidate)— write immutable local snapshot bytes;activateCandidate(candidate)— atomically publish active state only after all checks/synchronization succeed.
Keep WorkspaceRevision and internal flat snapshot filenames unchanged. Persist enough canonical catalog data in the immutable snapshot to list catalog-only entries during offline fallback. Do not force WorkspaceRevision to represent a missing descriptor.
Expose a catalog-aware method for routes, while preserving list()/retained-revision methods used by sessions:
listCatalog(): Promise<WorkspaceCatalogRecord[]>;
Step 4: Run focused cross-boundary tests
cd backend
npx vitest run \
test/workspace-registry.test.ts \
test/routes-sessions.test.ts \
test/workspace-runtime-handoff.test.ts
npx tsc --noEmit -p .
Expected: PASS; ready workspaces remain session-activatable and missing descriptors do not.
Step 5: Commit
git add backend/src/workspaces/registry.ts backend/src/workspaces/types.ts backend/test/workspace-registry.test.ts
git commit -m "feat: activate workspaces from the root catalog"
Task 4: Implement bootstrap-only descriptor publication and deterministic docs synchronization
Files:
- Modify:
backend/src/workspaces/registry.ts - Modify:
backend/src/workspaces/git-repository.ts - Modify:
backend/src/workspaces/types.ts - Modify:
backend/test/workspace-registry.test.ts
Step 1: Write failing create-only publication tests
Prove:
- catalog entry exists + descriptor absent + exact base commit + matching metadata + valid Evidence context → API creates descriptor and generated docs once;
- catalog bytes/blob and every Evidence tree/blob are unchanged by bootstrap;
- a descriptor path containing zero bytes, invalid YAML, a symlink/blob/tree/gitlink, or valid YAML is considered present and is not overwritten;
- update and delete requests return
workspace_curator_owned, perform no write/stage/commit, and preserve HEAD/object IDs; - create for an unknown catalog ID or mismatched name/description fails without mutation;
- stale base and a race in which a curator creates the descriptor first fail safely;
- failed commit/push replays the per-path journal, including stale pre-existing generated docs, and leaves curator paths untouched;
- a curator modifies catalog metadata and the descriptor together, pushes, and pull activates the exact curator bytes without reserializing the descriptor in Git;
- a content-only Evidence commit changes the active workspace commit even when descriptor/catalog blobs are unchanged;
- a curator descriptor-only commit changes the descriptor blob and active revision without any API descriptor write;
- explicit pull computes generated docs and, when stale, produces at most one docs-only follow-up commit;
- that docs-only commit changes only
workspace-docs/**, preserves catalog/descriptor/Evidence object IDs, and becomes the active revision; - startup/status activation never pushes; before explicit sync, local snapshots/exports contain freshly derived docs even if committed
workspace-docsare stale; - no-op synchronization makes no commit;
- a docs push race/rejection keeps the prior active snapshot and restores a clean checkout;
- generated docs are removed only when the catalog/descriptor state no longer owns them, never by directory-wide cleanup.
Step 2: Run the focused tests and verify RED
cd backend
npx vitest run test/workspace-registry.test.ts -t "bootstrap|curator|generated docs|content-only"
Expected: FAIL against create/update/delete publication.
Step 3: Narrow the public mutation contract
Add:
export type BootstrapWorkspaceRequest = {
action: "create";
workspace: CanonicalWorkspace;
baseCommit: string;
};
Keep legacy request parsing only long enough to return the stable refusal; do not keep update/delete implementation branches. Add workspace_curator_owned to WorkspaceErrorCode and map it to HTTP 409.
publishBootstrap must:
- pull and read a candidate without activating it;
- compare the exact requested base;
- locate the authoritative catalog slot;
- verify descriptor absence and metadata equality;
- verify contextual filesystem Evidence at that base;
- exclusively create the descriptor plus deterministic docs;
- commit/push fixed paths with fixed argv;
- read/validate the resulting candidate;
- activate only after complete success.
Refactor explicit pull to reconcile docs as defined in the design. GET/status/bootstrap paths remain read-only with respect to the remote.
Step 4: Run focused tests, typecheck, and build
cd backend
npx vitest run test/workspace-registry.test.ts test/workspaces-git-repository.test.ts
npx tsc --noEmit -p .
npm run build
Expected: PASS.
Step 5: Commit
git add \
backend/src/workspaces/registry.ts \
backend/src/workspaces/git-repository.ts \
backend/src/workspaces/types.ts \
backend/test/workspace-registry.test.ts
git commit -m "feat: make workspace publication bootstrap-only"
Task 5: Update workspace routes and machine contracts
Files:
- Modify:
backend/src/routes/workspaces.ts - Modify:
backend/test/routes-workspaces.test.ts - Modify:
backend/test/routes-sessions.test.ts
Step 1: Write failing real-route tests
Using the real local bare-repository fixture, assert:
GET /workspacesreturns root-catalog order/metadata and explicitreadyvsconfiguration_requiredstate;- summary
fileis exactly<id>/workspace.yaml; summary omitslanguagebecause a catalog-only slot has none, while ready detail/bootstrap drafts retain descriptor language; - catalog-only entries have no revision and no descriptor body;
GET /workspaces/:id, diagnostic, export, and session creation for a catalog-only entry return safeworkspace_not_activatableand never start Pi;POST /workspaces/validateremains context-free and says nothing about catalog publication eligibility;- create for one matching catalog slot succeeds once;
- second create, update, and delete produce HTTP 409
workspace_curator_ownedwith no conflict field/value payload and no mutation; - unknown slot, catalog metadata mismatch, stale base, invalid Evidence tree, and present-empty descriptor produce safe errors without canary/Git stderr;
- curator-pushed descriptor/catalog/Evidence changes are visible after pull and API bytes remain unchanged;
- import remains an untrusted draft and cannot update an existing workspace;
- export remains descriptor/docs only and never includes Evidence bytes or secrets.
Step 2: Run the focused routes and verify RED
cd backend
npx vitest run test/routes-workspaces.test.ts test/routes-sessions.test.ts
Expected: FAIL because routes expose full CRUD and cannot list missing descriptors.
Step 3: Implement the new DTOs and route semantics
Create a stable summary shape with catalog authority and explicit state. Route POST /workspaces/publish to bootstrap only. Recognize legacy update/delete payload discriminators before rejecting them as workspace_curator_owned; never pass them to a file mutation method.
Remove field-level WorkspaceConflictError serialization if it has no remaining production caller. Preserve generic stale-commit 409 behavior for bootstrap races.
Step 4: Run tests and checks
cd backend
npx vitest run \
test/routes-workspaces.test.ts \
test/routes-sessions.test.ts \
test/workspace-registry.test.ts
npx tsc --noEmit -p .
npm run build
Expected: PASS.
Step 5: Commit
git add backend/src/routes/workspaces.ts backend/test/routes-workspaces.test.ts backend/test/routes-sessions.test.ts
git commit -m "feat: expose catalog-driven bootstrap workspace API"
Task 6: Change the filesystem Evidence root without changing P1 source semantics
Files:
- Modify:
backend/src/workspaces/schema.ts - Modify:
backend/test/workspaces-schema.test.ts - Modify:
backend/test/workspaces-contracts.test.ts - Modify:
backend/test/workspaces-bindings.test.ts - Modify:
backend/test/workspace-runtime-renderer.test.ts - Modify:
backend/test/workspace-runtime-handoff.test.ts - Modify:
deploy/workspaces/example.yaml - Modify:
deploy/workspaces/psd.yaml.example
Step 1: Change tests first
Replace every positive filesystem URI with:
<id>/evidence
Negative coverage must reject:
- old
workspace-content/<id>/evidence; - flat/cross-workspace paths;
- absolute paths,
./.., doubled segments, backslashes, controls, query/fragment-like content; - roots above or below the exact canonical Evidence root.
Renderer/handoff tests must expect:
<registry-root>/snapshots/<commit>/<id>/evidence
while allowing that root not to exist until P6 materializes it. Keep HTTP/S3, local secret files, policy defaults, deterministic bytes, runtime_identity.workspace_revision, and ssh_tunnel fail-closed behavior unchanged.
Step 2: Run focused tests and verify RED
cd backend
npx vitest run \
test/workspaces-schema.test.ts \
test/workspaces-contracts.test.ts \
test/workspaces-bindings.test.ts \
test/workspace-runtime-renderer.test.ts \
test/workspace-runtime-handoff.test.ts
Expected: FAIL on the old hardcoded invariant/fixtures.
Step 3: Implement the smallest production change
Change the cross-field invariant to:
const expected = `${workspace.workspace.id}/evidence`;
The runtime renderer already joins a validated repo-relative URI to revisionContentRoot; do not add a second path mapping or materialization branch.
Step 4: Run cross-layer verification
cd backend
npx vitest run \
test/workspaces-schema.test.ts \
test/workspaces-contracts.test.ts \
test/workspaces-bindings.test.ts \
test/workspace-runtime-renderer.test.ts \
test/workspace-runtime-handoff.test.ts
npx tsc --noEmit -p .
npm run build
Then:
cd harness
.venv/bin/pytest -q tests/test_config_resources.py tests/test_registry_evidence_config.py
Expected: PASS. No harness production change should be necessary.
Step 5: Commit
git add \
backend/src/workspaces/schema.ts \
backend/test/workspaces-schema.test.ts \
backend/test/workspaces-contracts.test.ts \
backend/test/workspaces-bindings.test.ts \
backend/test/workspace-runtime-renderer.test.ts \
backend/test/workspace-runtime-handoff.test.ts \
deploy/workspaces/example.yaml \
deploy/workspaces/psd.yaml.example
git commit -m "refactor: colocate filesystem Evidence with its workspace"
Task 7: Narrow frontend API and draft persistence to bootstrap-only authoring
Files:
- Modify:
frontend/src/api/workspaces.ts - Modify:
frontend/src/api/workspaces.test.ts - Modify:
frontend/src/workspaces/drafts.ts - Modify:
frontend/src/workspaces/drafts.test.ts - Modify:
frontend/src/test/workspace-fixtures.ts - Review/test:
frontend/src/api/sessions.test.ts - Review/test:
frontend/src/shell/SteerInput.test.tsx - Review/test:
frontend/src/shell/NewSessionDialog.test.tsx
Step 1: Write failing frontend contract tests
Test:
- summary parsing accepts exact catalog metadata, direct-root descriptor path, explicit state, no summary
language, and optional revision only forready; - malformed or contradictory summary state/revision combinations are rejected;
- canonical workspace sanitization accepts only
<id>/evidencefor filesystem sources; - publish request type and client emit create only;
- backend
workspace_curator_ownedis decoded safely without conflict fields; - removed field-level conflict/update/delete payloads are rejected rather than stored;
- imported bundle becomes a bootstrap candidate only; no existing-workspace update request can be constructed;
- v1 update/deletion localStorage records are purged/ignored and never returned as actionable drafts;
- new versioned bootstrap drafts contain a catalog slot identity, base commit, and workspace body but no
baseBlobor delete intent; - session/new-session consumers still require a ready workspace revision and ignore
configuration_requiredentries.
Step 2: Run focused tests and verify RED
cd frontend
npx vitest run \
src/api/workspaces.test.ts \
src/workspaces/drafts.test.ts \
src/api/sessions.test.ts \
src/shell/SteerInput.test.tsx \
src/shell/NewSessionDialog.test.tsx
Expected: FAIL against full CRUD DTOs and old URI sanitizer.
Step 3: Implement strict client contracts
Replace PublishWorkspaceRequest with the bootstrap-only request. Add configurationState to WorkspaceSummary. Remove WorkspaceConflict, conflict-field allowlists, deletion-draft types/storage, and any serializer that can produce update/delete.
Version browser storage keys so old drafts cannot be interpreted under P1.1. On initialization, remove old known draft/delete keys only; never clear unrelated localStorage.
Step 4: Run tests and typecheck
cd frontend
npx vitest run \
src/api/workspaces.test.ts \
src/workspaces/drafts.test.ts \
src/api/sessions.test.ts \
src/shell/SteerInput.test.tsx \
src/shell/NewSessionDialog.test.tsx
npx tsc -b
Expected: PASS.
Step 5: Commit
git add \
frontend/src/api/workspaces.ts \
frontend/src/api/workspaces.test.ts \
frontend/src/workspaces/drafts.ts \
frontend/src/workspaces/drafts.test.ts \
frontend/src/test/workspace-fixtures.ts \
frontend/src/api/sessions.test.ts \
frontend/src/shell/SteerInput.test.tsx \
frontend/src/shell/NewSessionDialog.test.tsx
git commit -m "refactor: make browser workspace writes bootstrap-only"
Task 8: Make existing workspaces read-only in Workspace Management
Files:
- Modify:
frontend/src/shell/WorkspaceManager.tsx - Modify:
frontend/src/shell/WorkspaceManager.test.tsx - Modify:
frontend/src/shell/WorkspaceEditor.tsx - Modify:
frontend/src/shell/WorkspaceEditor.test.tsx - Modify or remove:
frontend/src/shell/WorkspacePublishDialog.tsx - Modify or remove:
frontend/src/shell/WorkspacePublishDialog.test.tsx - Review/test:
frontend/src/shell/AppShell.new-session.test.tsx - Review/test:
frontend/src/shell/AppShell.session-mgmt.test.tsx
Step 1: Write failing behavior tests
Prove:
- catalog order/name/description render even when no descriptor exists;
configuration_requiredentry offers a prefilled editable bootstrap form with ID/name/description locked to catalog values;- Save Draft is browser-local, Validate is explicit, and Create requires a separate confirmation;
- successful create discards the bootstrap draft and reloads as read-only;
- a ready workspace renders all descriptor fields read-only, plus Evidence summary and Git edit guidance;
- ready workspace has no Save, Publish update, Delete, Duplicate, conflict merge, or imported-update action;
- Pull/Sync, Export, Validate, and installation Test remain available where meaningful;
- stale update/delete localStorage fixtures do not make controls appear or send a request;
- import can populate only a matching unconfigured catalog slot; mismatch/existing target is refused safely;
- a curator-pushed change appears after Pull and is not written back by the browser;
- configured-only workspace selection remains enforced for new sessions.
Step 2: Run focused tests and verify RED
cd frontend
npx vitest run \
src/shell/WorkspaceManager.test.tsx \
src/shell/WorkspaceEditor.test.tsx \
src/shell/WorkspacePublishDialog.test.tsx \
src/shell/AppShell.new-session.test.tsx \
src/shell/AppShell.session-mgmt.test.tsx
Expected: FAIL because existing workspaces expose full CRUD.
Step 3: Implement two explicit UI modes
Use a discriminated prop rather than inferring editability from baseBlob:
type WorkspaceEditorMode =
| { kind: "bootstrap"; catalog: WorkspaceSummary; draft: WorkspaceBootstrapDraft }
| { kind: "read_only"; catalog: WorkspaceSummary; record: WorkspaceRecord };
Do not rely only on disabled controls; remove mutation handlers and mutation buttons entirely in read-only mode. Keep descriptive text telling curators to edit <id>/workspace.yaml, commit/push, then use Pull/Sync.
Reduce WorkspacePublishDialog to one bootstrap confirmation or fold it into the manager and delete the obsolete conflict UI/tests.
Step 4: Run frontend verification
cd frontend
npx vitest run \
src/shell/WorkspaceManager.test.tsx \
src/shell/WorkspaceEditor.test.tsx \
src/shell/WorkspacePublishDialog.test.tsx \
src/shell/AppShell.new-session.test.tsx \
src/shell/AppShell.session-mgmt.test.tsx
npx tsc -b
npm run build
If WorkspacePublishDialog is removed, omit its test from the command and prove no imports remain with git grep.
Expected: PASS.
Step 5: Commit
git add -A \
frontend/src/shell/WorkspaceManager.tsx \
frontend/src/shell/WorkspaceManager.test.tsx \
frontend/src/shell/WorkspaceEditor.tsx \
frontend/src/shell/WorkspaceEditor.test.tsx \
frontend/src/shell/WorkspacePublishDialog.tsx \
frontend/src/shell/WorkspacePublishDialog.test.tsx \
frontend/src/shell/AppShell.new-session.test.tsx \
frontend/src/shell/AppShell.session-mgmt.test.tsx
git commit -m "feat: make curator-owned workspaces read-only in the browser"
Task 9: Update active contracts, examples, operator guides, and executable doc gates
Files:
- Modify:
docs/contracts/workspace-evidence-v3.md - Modify:
docs/install/local-workspace-registry.md - Modify:
docs/install/server-workspace-registry.md - Modify:
README.md - Add:
docs/migrations/p1-to-p1-1-registry-layout.md - Modify:
scripts/verify-workspace-install-docs.sh - Modify:
scripts/test-verify-workspace-install-docs.sh - Review/modify if required:
scripts/workspace_descriptor_doc_contract.py - Review/modify if required:
scripts/test-workspace-descriptor-doc-contract.sh - Modify:
scripts/verify-schema-v3-only.sh - Modify:
scripts/test-verify-schema-v3-only.sh
Step 1: Add failing verifier mutations
The self-tests must reject docs/examples that:
- omit
thoth-workspaces.yamlor put it below a workspace; - use flat
workspaces/<id>.yamlor oldworkspace-content/<id>/evidence; - omit exact
<id>/workspace.yamlor<id>/evidencepaths; - claim catalog metadata comes from the descriptor;
- claim the API updates/deletes existing descriptors or writes catalog/Evidence;
- treat zero-byte descriptors as API-writable;
- omit catalog-only bootstrap and curator commit/push/pull flow;
- place generated docs inside a workspace directory;
- claim P1.1 materializes/preprocesses/indexes Evidence;
- omit migration ordering and explicit rejection of old layout;
- silently edit or claim completion of P2–P6.
Retain secret/path/protocol/adversarial verifier coverage from P1.
Step 2: Run self-tests and verify RED
bash scripts/test-verify-workspace-install-docs.sh
bash scripts/test-verify-schema-v3-only.sh
Expected: new mutations are not detected yet.
Step 3: Rewrite the active contract and manuals
Document the exact layout, root catalog schema, metadata equality, missing-descriptor bootstrap, create-once rule, curator ownership, docs-only API ownership, embedded/external Evidence, same-commit identity, migration cutover, and manual Git workflow.
The migration guide must require one reviewed commit that:
git mv workspaces/<id>.yaml <id>/workspace.yaml
git mv workspace-content/<id>/evidence <id>/evidence
# create/review thoth-workspaces.yaml from descriptor metadata
It must say to upgrade ThothII only after that commit is pushed and to roll back application and repository revision together. Do not add an executable auto-migrator.
Add an explicit P1.1 note to historical P1 design/plan references only if needed for navigation; do not rewrite accepted P1 history.
Step 4: Strengthen and run verifiers
bash scripts/test-verify-workspace-install-docs.sh
./scripts/verify-workspace-install-docs.sh --fixtures-only
bash scripts/test-verify-schema-v3-only.sh
./scripts/verify-schema-v3-only.sh
Expected: PASS.
Step 5: Commit
git add \
docs/contracts/workspace-evidence-v3.md \
docs/install/local-workspace-registry.md \
docs/install/server-workspace-registry.md \
docs/migrations/p1-to-p1-1-registry-layout.md \
README.md \
scripts/verify-workspace-install-docs.sh \
scripts/test-verify-workspace-install-docs.sh \
scripts/workspace_descriptor_doc_contract.py \
scripts/test-workspace-descriptor-doc-contract.sh \
scripts/verify-schema-v3-only.sh \
scripts/test-verify-schema-v3-only.sh
git commit -m "docs: define the P1.1 registry layout and curator flow"
Task 10: Update deployment fixtures and cross-platform registry smokes
Files:
- Modify:
scripts/workspace-registry-smoke.sh - Modify:
backend/test/workspace-registry-deployment.test.ts - Modify:
scripts/unified-deployment-smoke.sh - Modify:
scripts/test-windows-clone-contract.ps1 - Modify as needed:
scripts/fixtures/workspace-registry-smoke.yaml - Modify as needed:
scripts/fixtures/workspace-registry-task13.yaml - Modify as needed:
scripts/fixtures/workspace-registry-windows.yaml - Review:
.github/workflows/deployment.yml
Step 1: Write failing deterministic fixture/smoke tests
Make every seed create a root catalog and nested descriptor path. Add mutations proving:
- valid catalog + descriptor + Evidence starts;
- catalog/descriptor display metadata must change together in curator commits;
- orphan descriptor/mismatch/old layout is rejected while prior active snapshot stays usable;
- content-only Evidence update changes revision;
- API/bootstrap and curator paths remain separate;
- Windows paths with spaces preserve nested layout and LF/YAML contracts.
Step 2: Run focused deployment-contract tests and verify RED
cd backend
npx vitest run test/workspace-registry-deployment.test.ts
bash -n scripts/workspace-registry-smoke.sh scripts/unified-deployment-smoke.sh
Expected: old flat fixture assertions fail.
Step 3: Update seed/update/corruption helpers
Scripts copy standalone schema-v3 descriptor fixtures into <id>/workspace.yaml and write a matching thoth-workspaces.yaml. Evidence goes below <id>/evidence only for filesystem fixtures. Keep generated docs API-owned.
Do not edit P2–P6 preprocessing fixtures in this task unless they are directly used by the generic registry deployment smoke; record deferred preprocessing paths for the later adaptation plan.
Step 4: Run deterministic gates
cd backend
npx vitest run test/workspace-registry-deployment.test.ts
Run non-Docker contract modes provided by the scripts and the Windows PowerShell contract on its supported CI/host. Run Docker smokes only at the final verification task so each is executed once from clean state.
Step 5: Commit
git add \
scripts/workspace-registry-smoke.sh \
backend/test/workspace-registry-deployment.test.ts \
scripts/unified-deployment-smoke.sh \
scripts/test-windows-clone-contract.ps1 \
scripts/fixtures/workspace-registry-smoke.yaml \
scripts/fixtures/workspace-registry-task13.yaml \
scripts/fixtures/workspace-registry-windows.yaml \
.github/workflows/deployment.yml
git commit -m "test: migrate registry deployment fixtures to P1.1"
Task 11: Build independent automated P1.1 process acceptance
Files:
- Add:
scripts/p11-acceptance.sh - Add:
scripts/test-p11-acceptance.sh - Add:
backend/scripts/p11-acceptance.mjs - Add:
backend/scripts/p11-acceptance.test.mjs - Add:
backend/scripts/acceptance-support.mjs - Add:
backend/scripts/acceptance-support.test.mjs - Modify only to import proven-equivalent generic guards:
backend/scripts/p1-acceptance.mjs - Modify/test:
backend/scripts/p1-acceptance.test.mjs
Public command:
./scripts/p11-acceptance.sh integration --keep
Artifact root:
.artifacts/p11-integration/<run-id>/
Do not relabel, overwrite, or consume .artifacts/p1-integration/**.
Step 1: Write failing runner/lifecycle tests
Preserve P1's ownership-first, no-retry, fixed-argv, listener, secret-scan, report-hash, and confined-cleanup guards under the new P1.1 namespace. Test that P11 cleanup refuses P1/manual/sibling roots and vice versa.
Extract only genuinely namespace-agnostic ownership, report, fixed-argv, secret-scan, and cleanup guards into acceptance-support.mjs. Keep P1/P11 roots, kinds, check IDs, reports, and process semantics in their versioned runners. Run both support and P1 unit suites to prove the extraction does not weaken P1 safety; do not claim the old P1 full integration scenario remains compatible with the new application contract.
Step 2: Run the runner test and verify RED
node --test backend/scripts/acceptance-support.test.mjs backend/scripts/p1-acceptance.test.mjs
bash scripts/test-p11-acceptance.sh
Expected: P1/support regression tests stay PASS; P11 test fails because P11 tooling does not exist.
Step 3: Implement the clean-state process
The retained run must:
- create a bare remote and curator clone from zero;
- curator-push
thoth-workspaces.yamlwith filesystem/HTTP/S3 catalog slots, plus nested filesystem Evidence, but no descriptors; - start the production backend on loopback and list all slots as
configuration_required; - validate and bootstrap-create all three descriptors through real HTTP, sequentially using the current base commit;
- prove API writes only nested descriptor +
workspace-docs, never catalog/Evidence; - prove second create, update, delete, catalog mismatch, present-empty descriptor, orphan descriptor, old layout, invalid path/protocol/secret field, and missing Git tree fail without mutation/leak;
- curator-modify an existing descriptor and matching catalog metadata, push, then pull/sync and prove exact curator bytes are activated without descriptor rewrite;
- push a content-only Evidence change and prove new commit identity with unchanged descriptor blob;
- prove any docs-only follow-up commit changes only
workspace-docs/**; - inspect exact catalog/descriptor/Evidence Git objects and immutable local snapshots;
- acquire/release two production runtime configs for filesystem/HTTP/S3, compare bytes, and run real
tht config check -c <lease>in correct option order; - prove no P2 artifacts/commands, scan every non-secret-fixture byte and reachable Git blob for canaries, close listeners, and clean only owned resources.
Required stable check IDs include at least:
preflight
clean_state
ownership
catalog_bootstrap
catalog_only_listing
bootstrap_create_once
api_curator_boundary
curator_descriptor_update
content_only_revision
docs_only_reconciliation
same_revision_git_objects
snapshot_and_export
runtime_render_determinism
tht_config_check
negative_catalog_layout_cases
negative_schema_context_cases
no_p2_scope_artifacts
secret_scan
cleanup_confinement
report.md must end with:
P1.1 automated integration: PASS
P1.1 manual acceptance: PENDING
Step 4: Run runner tests
bash -n scripts/p11-acceptance.sh scripts/test-p11-acceptance.sh
bash scripts/test-p11-acceptance.sh
Expected: PASS.
Step 5: Run one fresh complete retained process
First verify no P11 runner/listener is active, then:
./scripts/p11-acceptance.sh integration --keep
Expected: exit 0, one new root, all checks PASS, no retry/attempt loop, reports hash-bound to the exact clean source/runtime graph.
On failure: retain the run, diagnose, add a regression test/fix, and execute a new full run with a new ID. Never overwrite or retry a failed run in place.
Step 6: Commit the tested P1.1 acceptance tooling
git add \
scripts/p11-acceptance.sh \
scripts/test-p11-acceptance.sh \
backend/scripts/p11-acceptance.mjs \
backend/scripts/p11-acceptance.test.mjs \
backend/scripts/acceptance-support.mjs \
backend/scripts/acceptance-support.test.mjs \
backend/scripts/p1-acceptance.mjs \
backend/scripts/p1-acceptance.test.mjs
git commit -m "test: prove the P1.1 registry process end to end"
Task 12: Build the separate P1.1 manual acceptance environment
Files:
- Add:
scripts/p11-manual-acceptance.sh - Add:
scripts/test-p11-manual-acceptance.sh - Add:
backend/scripts/p11-manual-acceptance.mjs - Add:
backend/scripts/p11-manual-acceptance.test.mjs - Add:
backend/scripts/p11-render-snapshot.mjs - Add:
backend/scripts/p11-render-snapshot.test.mjs - Add:
docs/testing/p11-manual-acceptance.md
Public lifecycle:
./scripts/p11-manual-acceptance.sh prepare
./scripts/p11-manual-acceptance.sh serve
./scripts/p11-manual-acceptance.sh stop
./scripts/p11-manual-acceptance.sh cleanup
Fixed independent root:
.artifacts/manual-acceptance/p11/
serve owns two loopback-only processes so the reviewer can exercise both real surfaces without Docker: the production Fastify backend on 127.0.0.1:8791 and a production-built frontend preview on a second fixed loopback port recorded in ownership. The lifecycle manifest binds both executable/start identities and listeners; stop and cleanup refuse partial or foreign ownership. It must never read/copy P1 or P11 automated run state.
Step 1: Write failing lifecycle/ownership tests
Port P1's hardened manual safeguards to the distinct P11 namespace while preserving P1 tests unchanged:
- prepare refuses existing/symlink/unowned roots and creates ownership before child resources;
- serve binds only the fixed backend and frontend-preview loopback ports with exact PID/start/executable/build identities;
- stop signals only the two owned process groups/listeners and fails closed on a partial identity mismatch;
- cleanup refuses live/foreign state and removes only P11 root;
- no helper writes
VERDICT.mdor marks manual PASS; - renderer accepts only owned immutable snapshots/output, writes 0600 atomically, always releases leases, and leaves deterministic bytes;
- fixture/command generation cannot accept path escapes, wrong catalog/commit, old layout, or P1 roots.
Step 2: Run lifecycle tests and verify RED
bash scripts/test-p11-manual-acceptance.sh
Expected: FAIL because tooling does not exist.
Step 3: Generate a reviewer-owned walkthrough
prepare creates a new bare remote/clone with catalog slots and nested filesystem Evidence but no descriptors, fixture secrets, requests, command scripts, and GUIDE.md. It does not call any positive API operation for the reviewer.
The guide requires the reviewer personally to:
- inspect catalog, nested workspace dirs, Evidence, ownership, and secret path bindings;
- serve the production backend plus production-built frontend preview and inspect every owned loopback listener;
- list
configuration_requiredslots; - validate and bootstrap-create descriptors once;
- inspect exact Git objects and separate generated docs;
- retry create/update/delete and verify refusal plus unchanged object IDs;
- edit existing descriptor and matching catalog metadata in the curator clone, commit/push/pull, and verify API did not rewrite curator bytes;
- make an Evidence-only commit and inspect revision identity;
- inspect live UI read-only existing workspace and editable missing-slot bootstrap behavior;
- export/import under bootstrap-only rules;
- render twice, diff, and run
tht config check; - run negative catalog/path/secret cases and a bounded secret scan;
- stop, inspect listener/PID cleanup, record
VERDICT.md, and only then cleanup when desired.
Step 4: Run tooling tests
bash -n scripts/p11-manual-acceptance.sh scripts/test-p11-manual-acceptance.sh
bash scripts/test-p11-manual-acceptance.sh
Expected: PASS.
Step 5: Commit tooling and guide
git add \
scripts/p11-manual-acceptance.sh \
scripts/test-p11-manual-acceptance.sh \
backend/scripts/p11-manual-acceptance.mjs \
backend/scripts/p11-manual-acceptance.test.mjs \
backend/scripts/p11-render-snapshot.mjs \
backend/scripts/p11-render-snapshot.test.mjs \
docs/testing/p11-manual-acceptance.md
git commit -m "test: add independent P1.1 manual acceptance"
Task 13: Final verification, retained evidence, and handoff to owner review
Files:
- Modify only after successful verification:
PROJECT_STATE.md - Do not modify: P2–P6 plans/designs in this task
Step 1: Run deterministic source gates
git diff --check
bash scripts/test-verify-workspace-install-docs.sh
./scripts/verify-workspace-install-docs.sh --fixtures-only
bash scripts/test-verify-schema-v3-only.sh
./scripts/verify-schema-v3-only.sh
bash scripts/test-p11-acceptance.sh
bash scripts/test-p11-manual-acceptance.sh
Expected: PASS.
Step 2: Run complete backend verification
cd backend
npx vitest run
npx tsc --noEmit -p .
npm run build
Expected: PASS. Record exact test counts.
Step 3: Run complete frontend verification
cd frontend
npx vitest run
npx tsc -b
npm run build
Expected: PASS. Record exact test counts.
Step 4: Run harness regression verification
cd harness
.venv/bin/pytest -q
.venv/bin/ruff check \
tht/config.py \
tht/adapters/factory.py \
tests/test_config_resources.py \
tests/test_registry_evidence_config.py
Expected: pytest PASS and touched/relevant Python files Ruff-clean. Do not claim broad pre-existing Ruff debt is fixed unless ruff check . is also green.
Step 5: Run deployment/registry smokes once from clean state
Run the repository's normal deterministic deployment gates first, then each Docker smoke exactly once with its built-in timeout/ownership cleanup:
./scripts/workspace-registry-smoke.sh
./scripts/unified-deployment-smoke.sh
Run the Windows native/clone contract in CI or an available supported Windows environment. If no Windows Docker runner is available, record the deterministic contract result and leave the manual Windows Docker gate explicitly unclaimed.
Expected: PASS with exact cleanup and no global prune.
Step 6: Run one final P1.1 automated acceptance from clean state
./scripts/p11-acceptance.sh integration --keep
Expected: PASS, new unique retained path, reports bound to the clean implementation commit/tree and compiled graph immediately before the evidence-only PROJECT_STATE update.
Step 7: Audit forbidden scope and deferred plans
Use git diff --name-only plus targeted scans to prove:
- no P2–P6 PRD/plan/design/manual-verification content was changed;
- no preprocessing/materialization/Qdrant/embedding implementation was added;
- no active runtime/doc/example still relies on flat
workspaces/<id>.yamlorworkspace-content/<id>/evidence; - any remaining old-path references are only historical P1 evidence/documents or the deliberately deferred P2–P6 sources inventoried for the later adjustment plan.
Step 8: Update project state to automated PASS/manual PENDING
Record exact source commit/tree, report paths/hashes, suite counts, smoke results, known limitations, and:
P1.1 automated integration: PASS
P1.1 manual acceptance: PENDING
Do not mark manual PASS.
Step 9: Prepare the independent manual environment and stop
./scripts/p11-manual-acceptance.sh prepare
Return the generated GUIDE.md path and lifecycle commands to the owner. Stop implementation work. Do not begin the P2–P6 adaptation plan before the owner completes and approves P1.1 manual acceptance.
Step 10: Commit final evidence metadata
git add PROJECT_STATE.md
git commit -m "docs: record P1.1 automated acceptance"
Owner checkpoint after implementation
The implementation session ends with:
P1.1 implementation: COMPLETE
P1.1 automated integration: PASS
P1.1 manual acceptance: PENDING
P2–P6 plans: UNCHANGED / ADAPTATION DEFERRED
The owner then executes docs/testing/p11-manual-acceptance.md. Only after an explicit manual PASS may a new planning-only task create the P2–P6 adaptation plan requested in steps 5–7 of the owner sequence.
Deferred P2–P6 impact inventory (do not edit during P1.1)
The later adaptation-planning step must revisit at least:
docs/prd/2026-08-09-workspace-preprocessing-prd.md— old descriptor/Evidence layout and P1/P6 rows;docs/superpowers/specs/2026-08-10-p2-p6-workspace-preprocessing-design.md— P5 annotations path and P6 Evidence materialization path;docs/superpowers/plans/2026-08-10-p2-host-workspace-preprocessing-cli.md— exact descriptor/catalog identity, active snapshot fixture, and P1 dependency assumptions;docs/testing/p2-p6-manual-verification.md— old-path examples and future manual commands.
Expected future canonical paths, subject to the separately approved adaptation plan:
<id>/schema/annotations.yaml
<id>/evidence
No P3/P4/P5/P6 implementation plan files currently exist separately; their present contract lives in the combined design/PRD and must be split or revised only in the later authorized phase.