Files
ThothII/docs/superpowers/plans/2026-08-10-p6-evidence-materialization.md
T

109 KiB
Raw Blame History

P6 Commit-Addressed Evidence Materialization Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Materialize one workspace's filesystem Evidence tree safely from its pinned Git commit, run the existing P2 preprocessing workflow against that immutable tree, reclaim only content that no authoritative pin retains, and close the P2–P6 two-installation acceptance gates.

Architecture: P6 extends P5's fixed Git-child protocol and selected-image ProtectedWorkspaceFs; it does not add a checkout, archive extractor, second operator, second lock, or second path model. A repository-owned boundary holds the repository lock, resolves and inventories the exact Git tree, then takes the P2 writer lock in repository → writer order to publish or verify a dirfd-anchored immutable tree. It releases both locks before WorkspacePreprocessingService.execute later enters a writer-only one-element ordered callback, rereads the active revision and manifest/tree identity, acquires the unchanged P3 maintenance lease, and invokes the existing harness pipeline. Every reuse is anchored again to the Git tree OID. Registry snapshot and Evidence retention consume one authoritative set built from active commits, validated revision leases, a complete session-manifest scan, and nonterminal preprocessing runs.

Tech Stack: Node.js 22, TypeScript 5, P5 runFixedGitChild, Git fixed plumbing, P5 ProtectedWorkspaceFs, Python 3.12 internal protected-filesystem helper, renameat2(RENAME_NOREPLACE), Go 1.26.5 thothctl, Docker Compose v2, Qdrant 1.18.2, internal Ollama qwen3-embedding:0.6b, Vitest, pytest, Ruff, Bash/Node acceptance tooling.

Source: docs/prd/2026-08-09-workspace-preprocessing-prd.md D6/RF5 and docs/superpowers/specs/2026-08-10-p2-p6-workspace-preprocessing-design.md §§8–10.

Planning status: DESIGN/PLAN ONLY until P1–P5 are implemented and accepted and the reviewer explicitly authorizes P6 execution.


Scope, dependency gate, and frozen handoff

P6 begins only from a clean worktree whose PROJECT_STATE.md records automated and manual PASS for P1–P5. It consumes these exact flat P2/P3/P5 surfaces:

  • backend/src/workspace-maintenance.ts::main;
  • backend/src/workspaces/preprocessing-service.ts::WorkspacePreprocessingService.execute;
  • backend/src/workspaces/preprocessing-state.ts::{PreprocessingStateStore,runUnderOrderedWorkspaceWriterLocks,runUnderWorkspaceWriterLock,probeWorkspaceWriterLock,WorkspaceLockedChildRequest,BorrowedWorkspaceSessionReadersExclusiveLockLease};
  • backend/src/workspaces/runtime-config-lease.ts::WorkspaceRuntimeConfigLeaseFactory;
  • backend/src/workspaces/revision-layout.ts::{WorkspaceLayout,workspaceRuntimePaths,readRevisionLayoutState};
  • tools/thothctl/internal/workspaceops::{ParseWorkspaceCommand,Run};
  • P5 backend/src/workspaces/fixed-git-child.ts::runFixedGitChild;
  • P5 backend/src/workspaces/protected-workspace-fs.ts::{ProtectedWorkspaceFs,openProtectedWorkspaceFs} and harness/tht/protected_fs_helper.py;
  • P5 AnnotationSynchronizer.verifyPrepared / readRuntime and its adapter typed as exact P2 CapabilityAwareRegistryPublicationParticipant; P2 RegistryPullAddressedPlanV1, AddressedWorkspacePublicationLeaseV1, OrderedWorkspaceWriterCapabilitySet, CapabilityAwareRegistryPublicationLifecycleOwner, RegistryAddressedRequestV1, RegistryPullAddressedResultV1, and WorkspaceRegistry.publishAddressed; P3 RegistryPullPublicJobRequestV1, RegistryPullPhaseV1, RegistryPullAddressedJobRequestV1, RegistryPullParticipantStateV1, RegistryPullSynchronizerStateV1, and RegistryPullJobStateV1; P3's explicit workspace registry pull --workspace <id> --json author workflow and exact registry_pull capability; and P3's released non-activating workspace migrate dwh-cache --workspace "$WORKSPACE_ID" --json / migrate_dwh_cache transition whose fixed p3_migrate_dwh_cache/p3_prepare_dwh_cache plus p3_materialize_dwh_snapshot stages publish and strictly reverify the selected unready revision's binding-qualified physical/LSH snapshot without READY.

P6 must not rename or wrap those APIs. It must not modify any P3 production file. In particular, revision-layout.ts, runtime-config-lease.ts, runtime-renderer.ts, tht-runner.ts, and all P3 harness path/corpus/Qdrant/Memory production modules are read-only dependencies in P6. P6 may add cross-layer regression tests; a failing P3 contract stops P6, reopens P3, reruns P3's focused and clean-state acceptance, checkpoints P3, and then rebases P6. It is forbidden to commit a P3 repair as P6 work.

Exact released P2/P3 addressed registry-pull exception

P6 dependency gates must import and consume the released names verbatim. The six P3 job names have one owning module; production consumers import them only as follows (tests use the corresponding ../src/workspaces/registry-pull-job.js path):

import type {
  RegistryPullPublicJobRequestV1,
  RegistryPullPhaseV1,
  RegistryPullAddressedJobRequestV1,
  RegistryPullParticipantStateV1,
  RegistryPullSynchronizerStateV1,
  RegistryPullJobStateV1,
} from "./registry-pull-job.js";

backend/src/workspaces/registry-pull-job.ts exports all six names. It defines RegistryPullPhaseV1 = RegistryAddressedPublicationPhaseV1, RegistryPullAddressedJobRequestV1 = Extract<RegistryAddressedRequestV1, { readonly operation: "registry_pull" }>, and RegistryPullJobStateV1 = RegistryPullAddressedPublicationStateV1; these are exact aliases owned by P3, not names P6 may redeclare. RegistryPullPublicJobRequestV1, RegistryPullParticipantStateV1, and RegistryPullSynchronizerStateV1 are the other three exact P3 exports. Use both P3's existing backend/test/registry-pull-job-imports.compile.ts and P5's backend/test/p5-registry-pull-imports.compile.ts as compile-only gates. Preserve the bidirectional alias parity assertions and P5's exact field fence: it imports AddressedWorkspacePublicationLeaseV1 from registry-publication.js and BorrowedWorkspaceSessionReadersExclusiveLockLease from preprocessing-state.js, proves their exact bidirectional parity through AddressedWorkspacePublicationLeaseV1["readers"], proves all three identities are required on pull create/resume, expectedBaseCommit is required on pull create, installation/repository/remote identity is required in plan/state, and publishAddressed takes the exact addressed union. No barrel, compatibility export, optional-field overload, local alias, or second job shape is permitted. P3's production registry.publishAddressed(...) call and mismatch-before-mutation tests must already satisfy that fence; otherwise stop and reopen P3 rather than repairing or decorating the call in P6.

P6 consumes these exact P2 exports from backend/src/workspaces/registry-publication.ts and backend/src/workspaces/preprocessing-state.ts. AddressedWorkspacePublicationLeaseV1 is owned by registry-publication.ts, and its exact readers field type BorrowedWorkspaceSessionReadersExclusiveLockLease is owned by preprocessing-state.ts:

export type RegistryAddressedPublicationPhaseV1 =
  | "request_claimed" | "target_advertised" | "target_fetched" | "planned"
  | "participants_prepared" | "publication_intent_durable"
  | "target_published" | "terminal_durable";
interface RegistryAddressedPlanFieldsV1 {
  readonly schemaVersion: 1;
  readonly installationIdentitySha256: Sha256Hex;
  readonly repositoryIdentitySha256: Sha256Hex;
  readonly remoteRefIdentitySha256: Sha256Hex;
  readonly jobArtifactPath: RegistryAddressedJobArtifactPathV1;
  readonly advertisedTargetCommit: Revision40;
  readonly immutableTargetRef: `refs/thoth/addressed-runs/${RegistryRunId32}/target`;
  readonly fetchedTargetCommit: Revision40;
  readonly targetCommit: Revision40;
  readonly targetManifestSha256: Sha256Hex;
  readonly targetWorkspaces: readonly RegistryWorkspaceManifestIdentityV1[];
  readonly changedWorkspaceIds: readonly CanonicalWorkspaceId[];
  readonly changedSetSha256: Sha256Hex;
}
export interface RegistryPullAddressedPlanV1 extends RegistryAddressedPlanFieldsV1 {
  readonly operation: "registry_pull";
  readonly changedSetRule: "symmetric_base_target_workspace_difference";
  readonly baseCommit: Revision40;
  readonly baseManifestSha256: Sha256Hex;
  readonly baseWorkspaces: readonly RegistryWorkspaceManifestIdentityV1[];
}
export type RegistryAddressedPlanV1 =
  | RegistryBootstrapAddressedPlanV1 | RegistryPullAddressedPlanV1;
interface RegistryAddressedPublicationStateFieldsV1 {
  readonly schemaVersion: 1;
  readonly runId: RegistryRunId32;
  readonly requestSha256: Sha256Hex;
  readonly jobArtifactPath: RegistryAddressedJobArtifactPathV1;
  readonly phase: RegistryAddressedPublicationPhaseV1;
  readonly installationIdentitySha256: Sha256Hex;
  readonly repositoryIdentitySha256: Sha256Hex;
  readonly remoteRefIdentitySha256: Sha256Hex;
  readonly advertisedTargetCommit: Revision40 | null;
  readonly immutableTargetRef: `refs/thoth/addressed-runs/${RegistryRunId32}/target` | null;
  readonly fetchedTargetCommit: Revision40 | null;
  readonly targetCommit: Revision40 | null;
  readonly targetManifestSha256: Sha256Hex | null;
  readonly targetWorkspaces: readonly RegistryWorkspaceManifestIdentityV1[] | null;
  readonly changedWorkspaceIds: readonly CanonicalWorkspaceId[] | null;
  readonly planSha256: Sha256Hex | null;
  readonly changedSetSha256: Sha256Hex | null;
  readonly participantsSha256: Sha256Hex | null;
  readonly synchronizersSha256: Sha256Hex | null;
  readonly publicationIntentSha256: Sha256Hex | null;
  readonly publishedActiveStateSha256: Sha256Hex | null;
  readonly terminalResultSha256: Sha256Hex | null;
  readonly priorStateSha256: Sha256Hex | null;
}
export interface RegistryPullAddressedPublicationStateV1
  extends RegistryAddressedPublicationStateFieldsV1 {
  readonly operation: "registry_pull";
  readonly baseCommit: Revision40;
  readonly baseManifestSha256: Sha256Hex;
  readonly baseWorkspaces: readonly RegistryWorkspaceManifestIdentityV1[];
  readonly changedSetRule: "symmetric_base_target_workspace_difference" | null;
}
export interface AddressedWorkspacePublicationLeaseV1
  extends BorrowedOrderedWorkspaceWriterLeaseV1 {
  readonly quiescence: BorrowedWorkspaceMaintenanceQuiescenceLease;
  readonly readers: BorrowedWorkspaceSessionReadersExclusiveLockLease;
}
export interface CapabilityAwareRegistryPublicationParticipant<T> {
  readonly participantId: string;
  prepare(plan: RegistryAddressedPlanV1,
    workspace: AddressedWorkspacePublicationLeaseV1): Promise<T>;
  reconcile(plan: RegistryAddressedPlanV1,
    workspace: AddressedWorkspacePublicationLeaseV1, prepared: T,
    phase: RegistryAddressedPublicationPhaseV1): Promise<void>;
}
export class CapabilityAwareRegistryPublicationLifecycleOwner {
  run<T>(input: {
    readonly plan: RegistryAddressedPlanV1;
    readonly capabilities: OrderedWorkspaceWriterCapabilitySet;
    readonly participants: readonly CapabilityAwareRegistryPublicationParticipant<unknown>[];
    readonly synchronizers: readonly CapabilityAwareRegistryPublicationSynchronizer[];
    readonly action: () => Promise<T>;
  }): Promise<T>;
}
export type RegistryAddressedRequestV1 =
  | {
      readonly mode: "create";
      readonly operation: "registry_bootstrap";
      readonly runId: RegistryRunId32;
      readonly requestSha256: Sha256Hex;
      readonly installationIdentitySha256: Sha256Hex;
      readonly repositoryIdentitySha256: Sha256Hex;
      readonly expectedBaseCommit: null;
      readonly remoteRefIdentitySha256: Sha256Hex;
    }
  | {
      readonly mode: "resume";
      readonly operation: "registry_bootstrap";
      readonly runId: RegistryRunId32;
      readonly requestSha256: Sha256Hex;
      readonly installationIdentitySha256: Sha256Hex;
      readonly repositoryIdentitySha256: Sha256Hex;
      readonly remoteRefIdentitySha256: Sha256Hex;
    }
  | {
      readonly mode: "create";
      readonly operation: "registry_pull";
      readonly runId: RegistryRunId32;
      readonly requestSha256: Sha256Hex;
      readonly installationIdentitySha256: Sha256Hex;
      readonly repositoryIdentitySha256: Sha256Hex;
      readonly expectedBaseCommit: Revision40;
      readonly remoteRefIdentitySha256: Sha256Hex;
    }
  | {
      readonly mode: "resume";
      readonly operation: "registry_pull";
      readonly runId: RegistryRunId32;
      readonly requestSha256: Sha256Hex;
      readonly installationIdentitySha256: Sha256Hex;
      readonly repositoryIdentitySha256: Sha256Hex;
      readonly remoteRefIdentitySha256: Sha256Hex;
    };
export class WorkspaceRegistry {
  publishAddressed(request: RegistryAddressedRequestV1): Promise<RegistryAddressedResultV1>;
}

This is a field-for-field dependency quotation, not a compatibility sketch. The validated installation/registry boundary derives the production installationIdentitySha256, repositoryIdentitySha256, and remoteRefIdentitySha256 and supplies all three on every create and resume member; pull create additionally supplies the exact expectedBaseCommit. The same three identities are required, non-optional fields in the immutable plan and durable state, and same-ID resume revalidates them before network or state mutation. P6 adds no optional identity, partial request, constructor decoration, compatibility overload, or second publishAddressed signature.

The production registry constructor, not the request, owns the single VerifiedWorkspaceLockRootLeaseFactory, CapabilityAwareRegistryPublicationLifecycleOwner, participant list, and synchronizer list. The sole pull order is repository lock → durable request_claimed → durable target advertisement → exact-OID fetch to the create-only immutable ref → target_fetched → immutable pull plan → planned → acquire/provision every complete changed root → one runUnderOrderedWorkspaceWriterLocks callback → lexical quiescence/readers → participant prepare → participants_prepared → durable publication intent → active-state publication/reconciliation → target_published → terminal_durable → callback settlement invalidates and reverse-closes readers/quiescence/writers/roots → repository lock release. The artifact is exactly addressed-publication-jobs/<run-id>.json.

P6 preserves P5's annotation participant and calls only WorkspaceRegistry.publishAddressed with the registry_pull member. The annotation participant is called by the existing lifecycle owner with the same callback-scoped AddressedWorkspacePublicationLeaseV1; it consumes that lease's opaque rootLease and writerCapability, never reacquires either, and never calls a repository method. The complete ordered capability set stays live through active-pointer sibling write, file fsync, rename, parent fsync, byte verification, and terminal durability. Same-ID recovery uses only RegistryPullJobStateV1: it never refetches/reselects after the durable target pin, accepts only the recorded all-base/all-target/mixed identities, reconciles a lost publication acknowledgement without a second rename, and refuses a third identity or target drift. A new process may reacquire the recorded complete set once; no live attempt permits nested acquisition or participant reentry.

Freeze these declarations exactly once in the plan and implement them in backend/src/workspaces/types.ts during Task 2:

export interface RepositoryMaterializationInput {
  workspaceId: string;
  expectedRevision: string;
  signal: AbortSignal;
  deadline: number;                    // caller-owned monotonic absolute deadline
}

export interface PreparedEvidenceMaterialization {
  workspaceId: string;                 // active identity revalidated under writer
  workspaceRevision: string;           // exact expected/active 40-hex revision
  descriptorBlob: string;              // exact active descriptor blob identity
  descriptorSnapshotPath: string;      // exact protected snapshot reopened under writer
  contentRoot: string;                 // verified immutable revisions/<commit>/content root
  sourceRoot: string;                  // verified filesystem Evidence root rendered to the harness
  sourceTreeOid: string;               // Git anchor re-resolved during materialize/reuse
  manifestSha256: string;              // exact validated materialization.json bytes
  entryCount: number;
  totalBytes: number;
}

PreparedEvidenceMaterialization is a closed identity handoff, not a bag of optional metadata: Task 7 must re-read active.json plus descriptorSnapshotPath, require workspace/revision/descriptor equality, and fully revalidate contentRoot, manifestSha256, sourceTreeOid, and sourceRoot before rendering or child spawn. Counts must also equal the revalidated manifest. Do not redefine either type in evidence-git-tree.ts, registry.ts, evidence-materializer.ts, or the service; import and use these names throughout.

Global lock order is exactly:

repository lock → workspace writer lock → existing harness stage lock

A repository-owned materialization callback may enter the writer lock while it holds repository. Once the repository lock has been released, the preprocessing service may enter the writer lock alone and call only lock-free verification and the fixed harness child. No repository method, Git child, registry pull, or materializing ensure is available inside that later writer callback. Tests must reject the opposite edge structurally and with both deterministic race directions.

P6 does not add a GUI/API maintenance endpoint, SSH runtime transport, PSD content, LLM/Pi preprocessing, archive extraction, checkout/worktree reads, arbitrary Git argv, user-selected destinations, or long-term policy GC. P4's released session-admission route is used only by the aggregate test to prove self-heal, with exact failed-admission cleanup described below.

Fixed published layout

For workspace alpha and exact commit <40hex>:

/data/sessions/alpha/revisions/<40hex>/
├── artifacts/                                  # P3/P5; never P6-deleted
├── indexes/                                    # P3; never P6-deleted
├── corpus/                                     # P3; never P6-deleted
├── .content-staging-<32hex>.owner-reserved.json
├── .content-staging-<32hex>.owner-bound.json   # adjacent until publication recovery completes
└── content/                                    # one P6 immutable publication unit
    ├── materialization.json
    └── workspace-content/alpha/evidence/**

The two adjacent owner files exist only during a live/recovering publication. The reserved record is durable before staging-directory creation and binds the retained revision-directory identity, staging basename, target basename, workspace, revision, and a 128-bit nonce. Before any source file is created, the bound record is durably published and additionally binds the staging directory's device and inode. After no-replace rename, the same device/inode identifies content/; the records remain adjacent until parent fsync, full validation, and final owner-record unlink+parent fsync complete. A crash never depends on an owner file inside a directory that was just renamed.

materialization.json is canonical UTF-8 JSON plus one newline, mode 0400, with exact keys:

export interface EvidenceMaterializationManifestV1 {
  schema_version: 1;
  workspace_id: string;
  workspace_revision: string;
  source_path: string;                 // workspace-content/<id>/evidence
  source_tree_oid: string;             // exact Git tree OID resolved at this commit
  entry_count: number;
  directory_count: number;             // every created source directory, including prefix directories
  total_bytes: number;
  limits: EvidenceMaterializationLimits;
  files: Array<{
    path: string;                      // repository-relative NFC path, bytewise sorted
    mode: "100644" | "100755";
    git_oid: string;
    size: number;
    sha256: string;
  }>;
}

No timestamp, host path, endpoint, credential path/value, Git stderr, exception text, or raw content enters the manifest or public result. Public failures use evidence_materialization_unsafe; safe success fields are workspace, revision, run, source tree OID, manifest SHA-256, counts, and completed stages.

Installation-local limits and exact inode accounting

Defaults are:

{
  maxEntries: 10_000,
  maxTotalBytes: 1024 * 1024 * 1024,
  maxPathBytes: 1024,
  maxSegmentBytes: 255,
  maxManifestBytes: 16 * 1024 * 1024,
  minFreeBytesAfterPublish: 64 * 1024 * 1024,
  minFreeInodesAfterPublish: 1024,
}

directoryCount includes workspace-content, the workspace-ID directory, evidence, and every nested directory not already present in the new staging tree. Peak new inodes are exactly:

entryCount
+ directoryCount
+ 1 staging root
+ 1 materialization.json
+ 1 reserved adjacent owner record
+ 1 bound adjacent owner record

Thus preflight requires ffree >= entryCount + directoryCount + 4 + minFreeInodesAfterPublish. Publication is a rename of the staging inode, not another content inode. Byte preflight includes total blob bytes, the bounded actual manifest estimate, both bounded canonical owner records, and the configured reserve. Missing, negative, or overflowing statfs values fail closed. Tests independently count actual created peak inodes and fail if this formula undercounts.

P6 operation capability and Git-child boundary

P3 owns and releases the exact general capability literal registry_pull: it alone has registry RW plus exact validated Git transport, while P5 accept/export and every ordinary mutation are registry RO/no-Git. P6 adds one internal filesystem-materialization capability selected only for a validated filesystem descriptor during preprocess evidence or preprocess run:

  • it may mount the registry volume RW solely because the existing repository lock opens/creates locks/repository.lock;
  • it mounts sessions RW, repository/object storage locally, and no Git HTTPS/SSH credential, agent socket, home credentials, DWH secret beyond the later operation's existing needs, Pi state, Docker socket, or arbitrary host path;
  • its TypeScript dependency is a narrowed EvidenceMaterializationRepository, not the mutation-capable repository surface;
  • it exposes only the repository lock plus fixed ls-tree, rev-parse/cat-file -t, and cat-file --batch reads at an already-active 40-hex commit;
  • bootstrap, pull, fetch, checkout, reset, clean, update-ref, commit, push, config writes, registry publication, arbitrary Git argv, and shell execution are absent from the command union and fail before spawn;
  • HTTP/S3 Evidence and every unrelated mutation keep the P2 registry RO/no-Git capability.

Image/Compose/Go contract tests hash the repository state/ref/object/FETCH_HEAD files before and after materialization (excluding the owned lock file) and prove byte identity. Mutation-negative tests attempt every forbidden operation through public grammar, direct machine ingress, capability selection, and an injected Git argv seam and prove zero child/mutation. A RW mount is not permission to expose a generic write API.

Every P6 Git child reuses P5 runFixedGitChild: shell-free fixed argv; one caller-supplied monotonic absolute operation deadline shared by tree resolution, inventory, and batch streaming; bounded stdout, record, stderr, and total bytes; exact stdin writes followed by close; abort on parser error, sink error, AbortSignal, deadline, or any bound; TERM then KILL only the owned process group; always await exit and all stdio settlement. No child output reaches public errors. P6 adds stage-specific tests for hang, stdout/record/stderr flood, early exit, parser abort, cancellation, sink abort, descendant survival attempts, and awaited teardown while locks are held.


Task 1: Enforce the accepted flat prerequisites and stop on dependency drift

Files:

  • Read: PROJECT_STATE.md

  • Read: P2–P5 checkpoint reports and plans

  • Read/verify unchanged: the exact production/test files named in the frozen handoff

  • Read/verify unchanged: backend/src/workspaces/registry-publication.ts

  • Read/verify unchanged: backend/src/workspaces/registry-pull-job.ts

  • Read/verify unchanged: backend/test/registry-pull-job-imports.compile.ts

  • Read/verify unchanged: backend/test/p5-registry-pull-imports.compile.ts

  • Read/verify unchanged: backend/src/workspaces/registry.ts

  • Read/verify unchanged: backend/test/workspace-registry-addressed-publication.test.ts

  • Read/verify unchanged: backend/test/workspace-registry-addressed-process.test.ts

  • Read/verify unchanged: backend/test/workspace-maintenance.test.ts

  • Read/verify unchanged: P5 backend/test/workspace-registry.test.ts

  • No production, test, or documentation modification; missing coverage reopens P2/P3/P5

  • Step 1: verify clean accepted prerequisites.

git status --short
git log -1 --format='%H %T'
rg -n 'P[1-5].*automated.*PASS|P[1-5].*manual.*PASS' PROJECT_STATE.md
test -f backend/src/workspace-maintenance.ts
test -f backend/src/workspaces/preprocessing-service.ts
test -f backend/src/workspaces/preprocessing-state.ts
test -f backend/src/workspaces/runtime-config-lease.ts
test -f backend/src/workspaces/revision-layout.ts
test -f backend/src/workspaces/registry-publication.ts
test -f backend/src/workspaces/registry-pull-job.ts
test -f backend/test/registry-pull-job-imports.compile.ts
test -f backend/test/p5-registry-pull-imports.compile.ts
test -f backend/src/workspaces/registry.ts
test -f backend/test/workspace-registry-addressed-publication.test.ts
test -f backend/test/workspace-registry-addressed-process.test.ts
test -f backend/test/workspace-maintenance.test.ts
test -f backend/test/workspace-registry.test.ts
test -f backend/src/workspaces/fixed-git-child.ts
test -f backend/src/workspaces/protected-workspace-fs.ts
test -f harness/tht/protected_fs_helper.py
test -f tools/thothctl/internal/workspaceops/operations.go

Expected: clean status; one commit/tree; accepted P1–P5; every exact file exists. A pending manual gate or missing file stops P6.

  • Step 2: fail on superseded aliases rather than renaming around them.

Run this exact negative inventory. The split literals deliberately construct disallowed legacy names without teaching later plan steps to use them.

bad=(
  'backend/src/workspace-maintenance''/'
  'WorkspaceMaintenance''Operator'
  'tools/thothctl/internal/workspace''/'
  'workspace-effective-config''.test.ts'
  'runtime''Paths('
  'RegistryPullBa''seTargetPlanV1'
  'RegistryAddressedWo''rkspaceLeaseOwner'
  'acquireCo''mpleteSet'
  'releaseCo''mpleteSet'
  'pullAndPubl''ishAddressed'
  'registry-''pull-jobs'
  'VerifiedWo''rkspaceRoot'
  'BorrowedWorkspaceExclusive''ReaderLease'
)
for needle in "${bad[@]}"; do
  if rg -nF "$needle" backend tools/thothctl harness; then
    echo "superseded P2/P3 surface present" >&2
    exit 1
  fi
done

Expected: no matches. Any match reopens the owning P2/P3 checkpoint; P6 does not add a conditional rename or compatibility wrapper.

  • Step 3: run the exact registry-pull exception dependency matrix before P6 work.

The accepted P2/P3/P5 test files above must already execute the real one-shot and import the exact released names quoted in this plan. In particular, P5's compile-only fence must directly import AddressedWorkspacePublicationLeaseV1 from registry-publication.js and BorrowedWorkspaceSessionReadersExclusiveLockLease from preprocessing-state.js, and its bidirectional Equal assertion must pin AddressedWorkspacePublicationLeaseV1["readers"] exactly to that owner type. Coverage is a gate, not prose: if any assertion below or this corrected type fence is absent, stop P6 and reopen the owning P2/P3/P5 checkpoint rather than adding a P6 alias or production repair.

  1. Build base/target manifests whose complete symmetric difference contains at least two workspace IDs presented in reverse order, including an addition/removal. Capture the real addressed create and resume objects and prove each carries required installationIdentitySha256, repositoryIdentitySha256, and remoteRefIdentitySha256 equal to the validated production registry identities; create also carries the exact active expectedBaseCommit. Assert the immutable RegistryPullAddressedPlanV1 and persisted RegistryPullJobStateV1 retain all three identities, RegistryPullJobStateV1.changedWorkspaceIds is unique/complete/strict lexical, and changedSetSha256, planSha256, every ordered RegistryPullParticipantStateV1, and every AddressedWorkspacePublicationLeaseV1.workspaceId match exact canonical bytes/order.
  2. Assert the live trace is exactly repository.lock → durable request_claimed → durable target_advertised → exact-OID immutable-ref fetch → target_fetched → durable planned → one runUnderOrderedWorkspaceWriterLocks callback → lexical root/writer set → lexical quiescence/readers set → exact CapabilityAwareRegistryPublicationParticipant.prepare calls → participants_prepared → publication_intent_durable → active-pointer sibling write/file fsync/ atomic rename/parent fsync/target-byte verification while the entire OrderedWorkspaceWriterCapabilitySet remains held → target_published → terminal_durable → ordered callback settlement and reverse close → repository release. Hold every changed-ID writer from an independent process and prove contention persists through rename+parent fsync. Reject any participant repository call, root/reader acquisition, runUnderWorkspaceWriterLock, publishAddressed, nested/duplicate acquisition, post-settlement use, or cross-workspace capability use.
  3. With core absent, SIGKILL a real create at persisted publication_intent_durable immediately before active-pointer rename. Exact same runId + requestSha256 resume must load the recorded RegistryPullJobStateV1, observe exact base, do no fetch/ls-remote/remote resolution/target selection, reacquire the recorded complete lexical set once in the new process, and converge to target.
  4. In a fresh fixture, SIGKILL immediately after active-pointer rename+parent fsync but before the target_published rewrite, leaving durable phase publication_intent_durable and exact target active. Same-ID resume must treat all-target as lost acknowledgement, do no fetch and no second active publication rename, advance target_published → terminal_durable, owner-clear, reverse release, then release repository. Assert one fetch and one active publication rename total.
  5. Inject a third active revision/descriptor/manifest identity, changed/deleted pinned target object, target inventory/manifest/digest drift, installation identity drift, repository identity drift, remote-ref identity drift, moved local remote-tracking OID, different ID, same ID/different digest, and cross-operation resume. Each must refuse before network or state mutation and without refetch, participant reentry, or nonterminal owner clear.
(cd backend && npx vitest run   test/workspace-registry-addressed-publication.test.ts   test/workspace-registry-addressed-process.test.ts   test/workspace-registry.test.ts   test/workspace-runtime-config-lease.test.ts   test/workspace-preprocessing-state.test.ts   test/workspace-preprocessing-service.test.ts   test/workspace-maintenance.test.ts   test/workspace-effective-config-equivalence.test.ts   test/workspace-revision-layout.test.ts   test/fixed-git-child.test.ts   test/protected-workspace-fs.test.ts   test/workspace-annotations.test.ts && npx tsc --noEmit -p .)
(cd backend && npx tsc --noEmit --target ES2022 --module ES2022 \
  --moduleResolution Bundler --strict --skipLibCheck \
  test/registry-pull-job-imports.compile.ts \
  test/p5-registry-pull-imports.compile.ts)
(cd harness && .venv/bin/pytest -q   tests/test_effective_dwh_binding.py tests/test_dwh_snapshot.py   tests/test_corpus_pipeline.py tests/test_qdrant_vector_store.py   tests/test_semantic_kind_isolation.py tests/test_protected_fs_helper.py)
(cd tools/thothctl && test "$(go env GOVERSION)" = 'go1.26.5' &&   go test ./internal/workspaceops ./cmd/thothctl -run   'RegistryPull|Resume|Capability|EvidenceMaterialization|RegistryMutation' -count=1)

Expected: PASS with both same-ID SIGKILL cases, exact strict-lexical multi-workspace set/order, pre/post-publication recovery, drift/third-identity refusal, zero resume refetch/republication, and zero participant/capability reentry. Record counts and the P2/P3/P5 checkpoint hashes. No commit.


Task 2: Add bounded materialization configuration and the least-capability container path

Files:

  • Modify: backend/src/workspaces/types.ts

  • Modify: backend/src/config.ts

  • Modify: backend/test/config.test.ts

  • Modify: backend/src/workspace-maintenance.ts

  • Modify: backend/test/workspace-maintenance.test.ts

  • Modify: tools/thothctl/internal/workspaceops/operations.go

  • Modify: tools/thothctl/internal/workspaceops/operations_test.go

  • Modify: tools/thothctl/internal/config/installation.go

  • Modify: tools/thothctl/internal/config/installation_test.go

  • Modify: compose.yaml

  • Modify: deploy/compose.local.yaml

  • Modify: deploy/compose.server.yaml

  • Modify: scripts/generate-connector-secrets-override.sh

  • Modify: scripts/test-compose-secret-policy.sh

  • Modify: scripts/test-deployment-command-contract.sh

  • Modify: scripts/test-unified-compose.sh

  • Modify: deploy/env/local.env.example

  • Modify: deploy/env/server.env.example

  • Step 1 (RED): add typed limit and cross-field tests.

Add EvidenceMaterializationLimits to workspaces/types.ts and tests for the exact defaults above, every environment override, safe-integer parsing, zero/negative/fraction/overflow rejection, maxSegmentBytes <= maxPathBytes, and a manifest limit large enough for the fixed envelope. In that same named file, implement the single frozen RepositoryMaterializationInput and PreparedEvidenceMaterialization declarations from the handoff above before this task's TypeScript gate; their field schema is not repeated here. Add compile-time tests that require every field and reject optional/extra/anonymous substitute shapes. Add evidence_materialization_unsafe to the existing closed result/error union in the flat P2 service/entrypoint; preserve decoding of historical evidence_materialization_required but do not emit it after Task 7.

(cd backend && npx vitest run test/config.test.ts test/workspace-maintenance.test.ts \
  -t 'Evidence materialization|materialization unsafe')

Expected: RED because limits/code are absent.

  • Step 2 (RED): freeze operation capability selection and mutation-negative cases.

In Go/Compose tests, require:

  1. validated filesystem preprocess evidence and preprocess run select the closed internal filesystem-materialization capability and pass expected workspace/revision/source identities;
  2. HTTP/S3 and all unrelated commands retain registry RO/no-Git;
  3. P3 registry_pull remains the only capability with Git transport and general repository mutation;
  4. materialization receives registry RW, sessions RW, no Git transport secret, and the exact selected immutable image;
  5. unknown capability, raw command, extra argv, environment-selected argv, bootstrap/pull/fetch/checkout/reset/clean/update-ref/commit/push/config request, and descriptor type drift are refused before a child starts;
  6. direct calls to main cannot manufacture a capability token or repository mutator;
  7. registry refs, object inventory, checkout, active state, snapshots, and FETCH_HEAD hash identically before/after positive and negative materialization; only the exact lock file may be created/touched;
  8. container cancellation uses the existing bounded process-group cleanup and removes only its labelled one-shot container.
(cd tools/thothctl && go test ./internal/workspaceops ./internal/config ./cmd/thothctl \
  -run 'EvidenceMaterialization|Capability|RegistryMutation' -count=1)
./scripts/test-compose-secret-policy.sh
./scripts/test-deployment-command-contract.sh

Expected: RED on the new capability.

  • Step 3 (GREEN): implement the minimum capability.

Parse the seven non-secret limits into one immutable config object. Extend workspaceops.Run, not a parallel dispatcher. It may select materialization only after strict read-only inspection returns a filesystem source and exact active identity; the selected maintenance ingress repeats those expected fields and the service revalidates them. Generated Compose uses the selected image ID with pull_policy: never, fixed entrypoint, and the operation-specific mount/secret set above. Do not mount an author clone or give the narrowed repository interface a mutation method.

  • Step 4: verify.
(cd backend && npx vitest run test/config.test.ts test/workspace-maintenance.test.ts && \
  npx tsc --noEmit -p .)
(cd tools/thothctl && gofmt -w internal/workspaceops/operations.go \
  internal/workspaceops/operations_test.go internal/config/installation.go \
  internal/config/installation_test.go && go test ./... -count=1)
docker compose --env-file deploy/env/local.env.example \
  -f compose.yaml -f deploy/compose.local.yaml config --quiet
./scripts/test-compose-secret-policy.sh
./scripts/test-deployment-command-contract.sh
./scripts/test-unified-compose.sh

Expected: PASS; rendered output exposes no secret value.

  • Step 5: commit.
git add backend/src/workspaces/types.ts backend/src/config.ts backend/test/config.test.ts \
  backend/src/workspace-maintenance.ts backend/test/workspace-maintenance.test.ts \
  tools/thothctl/internal/workspaceops/operations.go \
  tools/thothctl/internal/workspaceops/operations_test.go \
  tools/thothctl/internal/config/installation.go \
  tools/thothctl/internal/config/installation_test.go \
  compose.yaml deploy/compose.local.yaml deploy/compose.server.yaml \
  scripts/generate-connector-secrets-override.sh scripts/test-compose-secret-policy.sh \
  scripts/test-deployment-command-contract.sh scripts/test-unified-compose.sh \
  deploy/env/local.env.example deploy/env/server.env.example
git commit -m 'feat: isolate filesystem materialization capability'

Task 3: Inventory the exact pinned Evidence tree with the shared bounded Git protocol

Files:

  • Create: backend/src/workspaces/evidence-git-tree.ts
  • Create: backend/test/workspace-evidence-git-tree.test.ts
  • Modify: backend/src/workspaces/git-repository.ts
  • Modify: backend/test/workspaces-git-repository.test.ts
  • Reuse unchanged: backend/src/workspaces/fixed-git-child.ts
  • Modify only to add P6 lifecycle regressions, not semantics: backend/test/fixed-git-child.test.ts

Frozen interfaces:

export interface EvidenceGitEntry {
  mode: "100644" | "100755";
  type: "blob";
  oid: string;
  size: number;
  path: string;
}
export interface EvidenceGitInventory {
  revision: string;
  sourcePath: string;
  treeOid: string;
  entries: readonly EvidenceGitEntry[];
  entryCount: number;
  directoryCount: number;
  totalBytes: number;
  estimatedManifestBytes: number;
}
export interface EvidenceMaterializationRepository {
  materializeEvidenceForActiveRevision(input: RepositoryMaterializationInput):
    Promise<PreparedEvidenceMaterialization>;
}

Import RepositoryMaterializationInput and PreparedEvidenceMaterialization from backend/src/workspaces/types.ts, where Task 2 defined them before the first implementation tsc; do not restate their fields here or create local aliases. The narrowed interface exposes no generic repository or Git call.

  • Step 1 (RED): write byte parser and real-Git tests.

Test chunk splits at every byte, multiple NUL records, empty tree, invalid UTF-8, NUL/control characters, absolute/./../double/trailing separators, backslashes, non-NFC, cross-workspace prefixes, duplicate normalized paths, path/segment/count/total/manifest overflow, unsafe/nondecimal sizes, and duplicate OID size disagreement. Real Git tests cover regular/executable blobs; symlink at every depth; gitlink; injected special/unknown modes/types; per-object limit before body read; shell/pathspec strings; exact historical commit; and unrelated valid workspace namespaces that coexist unchanged.

Tree identity is resolved on every new publication and reuse from exact <commit>:workspace-content/<id>/evidence with fixed read plumbing, required type tree, and compared to source_tree_oid. Inventory uses only:

git -c core.hooksPath=<fixed-empty-hooks> ls-tree -r -z --full-tree --long \
  <validated-40hex> -- :(literal)workspace-content/<validated-id>/evidence

The root lookup uses fixed rev-parse --verify plus fixed cat-file -t; no revision/path is accepted from arbitrary caller text.

(cd backend && npx vitest run \
  test/workspace-evidence-git-tree.test.ts \
  test/workspaces-git-repository.test.ts \
  test/fixed-git-child.test.ts -t 'Evidence|reuse anchor|deadline|abort')

Expected: RED because the reader is absent.

  • Step 2 (GREEN): implement bounded inventory.

Use runFixedGitChild for every stage with one absolute deadline. Parse MODE SP TYPE SP OID SP SIZE TAB PATH NUL as bytes; bound the unterminated record by maxPathBytes + 256; decode fatal UTF-8; require NFC, exact POSIX normalization and source prefix; accept only blob modes 100644|100755, 40-hex OIDs, and safe sizes. Sort by UTF-8 bytes, reject duplicates, calculate every prefix directory, and conservatively calculate manifest bytes before any blob body starts. Empty Evidence tree is valid. All failures map to one safe error without path/OID/stderr/remote.

  • Step 3 (RED then GREEN): stream batch objects with complete teardown.

Use one fixed cat-file --batch. Send one expected OID only when the prior blob has been consumed; close stdin after the final OID. Require exact header, size, delimiter, SHA-1 Git object identity, backpressure, and sink close. Never buffer a full blob. Add hang, stderr flood, stdout/record overflow, early EOF/exit, parser throw, sink abort, cancellation, deadline, grandchild, and held-lock teardown tests. Each asserts stdin closure, TERM→KILL of only the owned group, awaited exit/stdio, no live descendant, and lock release only after settlement.

  • Step 4: verify and commit.
(cd backend && npx vitest run \
  test/workspace-evidence-git-tree.test.ts \
  test/workspaces-git-repository.test.ts test/fixed-git-child.test.ts && \
  npx tsc --noEmit -p .)
git add backend/src/workspaces/evidence-git-tree.ts \
  backend/src/workspaces/git-repository.ts \
  backend/test/workspace-evidence-git-tree.test.ts \
  backend/test/workspaces-git-repository.test.ts backend/test/fixed-git-child.test.ts
git commit -m 'feat: inventory pinned Evidence Git trees'

Task 4: Extend P5's protected filesystem with crash-safe immutable tree publication

Files:

  • Modify: backend/src/workspaces/protected-workspace-fs.ts
  • Modify: backend/test/protected-workspace-fs.test.ts
  • Modify: backend/src/workspaces/preprocessing-state.ts (cumulatively extend the sole locked-child union/dispatcher without removing P3 or P5 members)
  • Modify: backend/test/workspace-locked-child-cumulative.compile.ts (exact compile-time base-three + P3-fourteen + P5-two + P6-two union fence)
  • Modify: backend/test/workspace-preprocessing-state.test.ts (P3 locked-child focused cumulative runtime dispatcher regression)
  • Modify: backend/test/fixtures/workspace-lock-root-worker.mjs
  • Modify: harness/tht/locked_child_stdin.py
  • Modify: harness/tests/test_locked_child_stdin.py
  • Modify: harness/tht/protected_fs_helper.py
  • Modify: harness/tests/test_protected_fs_helper.py
  • Create: backend/src/workspaces/evidence-materializer.ts
  • Create: backend/test/workspace-evidence-materializer.test.ts

P6 extends P5's same capability-bound interface and sole cumulative WorkspaceLockedChildRequest union. It retains the original three P2 members, the exact fourteen-member P3LockedChildRequest, and P5's exact AnnotationPublishLockedChildRequest kind "annotation_publish" plus AnnotationVerifyLockedChildRequest kind "annotation_verify", then adds only the two Evidence-tree members. It does not copy/redeclare a prior alias, create another filesystem abstraction, or add any path/direct-spawn fallback:

export const EVIDENCE_TREE_ENTRIES_MAX = 10_000;
export const EVIDENCE_TREE_TOTAL_BYTES_MAX = 1_073_741_824;
export const EVIDENCE_TREE_MANIFEST_BYTES_MAX = 16_777_216;
export const EVIDENCE_TREE_CHILD_STDIN_MAX = 1_107_296_256;
export interface EvidenceTreePublishLockedChildRequest {
  readonly kind: "evidence_tree_publish";
  readonly workspaceId: CanonicalWorkspaceId;
  readonly revision: Revision40;
  readonly rootIdentity: WorkspaceLockRootIdentityV1;
  readonly childRunId: string;
  readonly sourceTreeOid: Revision40;
  readonly inventory: VerifiedEvidenceTreeInventoryV1;
  readonly blobSource: BoundedEvidenceBlobSource;
  readonly limits: EvidenceMaterializationLimitsV1;
}
export interface EvidenceTreeVerifyLockedChildRequest {
  readonly kind: "evidence_tree_verify";
  readonly workspaceId: CanonicalWorkspaceId;
  readonly revision: Revision40;
  readonly rootIdentity: WorkspaceLockRootIdentityV1;
  readonly childRunId: string;
  readonly sourceTreeOid: Revision40;
  readonly inventorySha256: Sha256Hex;
  readonly manifestSha256: Sha256Hex;
  readonly limits: EvidenceMaterializationLimitsV1;
}
export type WorkspaceLockedChildRequest =
  | DwhLockedChildRequest | SchemaLockedChildRequest | EvidenceLockedChildRequest
  | P3LockedChildRequest
  | AnnotationPublishLockedChildRequest | AnnotationVerifyLockedChildRequest
  | EvidenceTreePublishLockedChildRequest | EvidenceTreeVerifyLockedChildRequest;
export interface ProtectedWorkspaceFs {
  publishImmutablePair(input: ImmutablePairPublication): Promise<ImmutablePairIdentityV1>;
  verifyImmutablePair(input: ImmutablePairVerification): Promise<ImmutablePairIdentityV1>;
  publishImmutableTree(input: ImmutableTreePublication): Promise<ImmutableTreePublicationResult>;
  verifyImmutableTree(input: ImmutableTreeVerification): Promise<ImmutableTreePublicationResult>;
}
export function openProtectedWorkspaceFs(input: {
  readonly rootLease: BorrowedVerifiedWorkspaceLockRootLease;
  readonly writerCapability: WorkspaceWriterLockCapability;
}): ProtectedWorkspaceFs;

VerifiedEvidenceTreeInventoryV1 and BoundedEvidenceBlobSource have private constructors and are created only after the fixed Git inventory/deadline/limit checks; they are semantic bounded inputs, not raw argv, stdio, callbacks, workspace roots, or destination paths. The capability dispatcher fixes the selected-image executable and exact argv -I -m tht.protected_fs_helper. It rejects more than 10,000 entries, more than 1,073,741,824 blob bytes, more than 16,777,216 manifest bytes, any inventory/path limit violation, or more than 1,107,296,256 total framed stdin bytes before/while streaming. It reuses P5's exact 16,384-byte stdout, 65,536-byte stderr, 81,920-byte combined result caps, fixed deadline, owned-group termination, and awaited exit/stdio settlement. No P6 code calls spawn, exec, or child_process for the helper; every publish and verify call is writerCapability.spawnChild(exactVariant).

The helper validates inherited writer FD 3 and retained-root FD 4 with P2's shared verifier before any workspace read or write, then derives the immutable revision content destination only from the branded workspace/revision request beneath FD 4. All opens use O_NOFOLLOW; directory creation, owner evidence, cleanup, publication, and verification are anchored openat/mkdirat/unlinkat plus renameat2(RENAME_NOREPLACE) with no-follow and retained identity rechecks. There is no ambient root, absolute-root frame, pathname reopen, Node rename, overwrite fallback, or direct helper spawn.

The P5 compile-only fence is extended rather than replaced: its separate exact fourteen-P3 and two-annotation readonly tuples remain unchanged, a separate two-Evidence-tree tuple is added, duplicates are rejected, and bidirectional Equal/Assert checks prove WorkspaceLockedChildRequest["kind"] is exactly the original three P2 kinds plus the cumulative 14 + 2 + 2 extension kinds. The runtime table likewise retains the original three, all fourteen P3, and both P5 annotation requests, adds only the two tree requests, and submits all twenty-one through one real WorkspaceWriterLockCapability. The fixture must observe one capability/root identity and one exhaustive production dispatcher; a test-side switch uses assertNever, and a second capability, P3 spawner, annotation spawner, or tree spawner is forbidden. Preserve P5's compile-file declarations and extend its expected type exactly as follows:

const p6LockedKinds = ["evidence_tree_publish", "evidence_tree_verify"] as const
  satisfies readonly WorkspaceLockedChildRequest["kind"][];
const p6CumulativeLockedKinds = [...p5CumulativeLockedKinds, ...p6LockedKinds] as const;
type P6CumulativeLockedKind = typeof p6CumulativeLockedKinds[number];
type _P6CumulativeKindsAreUnique = Assert<Unique<typeof p6CumulativeLockedKinds>>;
type _P6CumulativeUnionIsExact = Assert<
  Equal<WorkspaceLockedChildRequest["kind"], P6CumulativeLockedKind>
>;
  • Step 1 (RED): specify selected-image framed tree publication.

Use real temporary filesystems and invoke the installed helper positively only through WorkspaceWriterLockCapability.spawnChild. Test exact layout; modes (0700 directories, 0400 files); UID/nlink/type; one-byte chunks; bounded memory; actual SHA-256; deterministic manifest; and exact inode accounting. Test real ancestor replacement after inherited-FD-4 validation, revision open, staging creation, every directory/file open, every file fsync, every directory fsync, manifest fsync, owner reservation/binding, no-replace rename, revision-parent fsync, validation, owner unlink, and final fsync. The helper retains accepted dirfds and must either finish in the retained inode or fail without writing into a replacement tree. Add a real parent/child barrier after the parent acquires the writer and installs FD 3/FD 4 but before the helper validates/uses FD 4; replace the canonical workspace leaf, then release the child. Require zero bytes in the replacement tree. Separately run the real child with missing FD 3, missing FD 4, substituted/cross-root FD pairs, wrong request root identity, direct spawn, and use of the saved capability after the ordered callback settles; all must fail before read, owner-record creation, staging, or publication.

Publication uses only retained-dirfd openat/mkdirat/unlinkat and the existing renameat2(RENAME_NOREPLACE) binding. There is no Node rename, overwrite-capable fallback, recursive path API, or lstat-then-normal-path operation. A competing content created immediately before rename must win with EEXIST; P6 verifies it as immutable reuse or fails closed and never overwrites it.

(cd harness && .venv/bin/pytest -q tests/test_protected_fs_helper.py \
  tests/test_locked_child_stdin.py tests/test_layout_marker_commands.py \
  tests/test_p3_internal_cli.py)
(cd backend && npx vitest run test/protected-workspace-fs.test.ts \
  test/workspace-preprocessing-state.test.ts test/workspace-evidence-materializer.test.ts \
  -t 'tree|ancestor|noreplace|inode|cumulative locked child')
(cd backend && npx tsc --noEmit --target ES2022 --module ES2022 \
  --moduleResolution Bundler --strict --skipLibCheck \
  test/workspace-locked-child-cumulative.compile.ts)

Expected: the retained P3/P5 locked-child cases remain green, while the cumulative runtime table, 14 + 2 + 2 compile-only fence, evidence_tree_publish, evidence_tree_verify, and materializer cases fail for only the missing P6 surface.

  • Step 2 (RED): freeze adjacent owner evidence and kill recovery.

Test the exact state machine:

  1. fsync reserved adjacent record before staging mkdir;
  2. create/fstat an empty staging directory;
  3. fsync bound adjacent record with staging dev/inode before creating source entries;
  4. stream/write/fsync all files; create and fsync all directories bottom-up;
  5. write/fsync manifest inside staging; fsync staging;
  6. recheck retained parent and staging identities;
  7. renameat2(..., RENAME_NOREPLACE) staging → content;
  8. fsync retained revision parent;
  9. fully validate content, including same dev/inode as bound owner;
  10. unlink both exact owner records and fsync parent.

Kill a real helper at every boundary. Recovery rules are closed: reserved/no staging removes only the matching reservation; reserved plus an empty exact-name/UID/mode staging may remove that empty directory; once bound exists, cleanup/recovery acts only when nonce/name/parent/dev/inode all match; post-rename recovery validates the same inode at content, finishes fsync/validation, and removes evidence; neither/multiple/mismatched objects fail closed without deletion. Add owner-record replacement, hardlink, symlink, foreign UID, nonce, dev/inode, and nonempty-unbound staging negatives.

  • Step 3 (GREEN): implement streaming and validation.

ProtectedWorkspaceFs.publishImmutableTree constructs the exact evidence_tree_publish semantic request and calls only writerCapability.spawnChild; verifyImmutableTree similarly uses evidence_tree_verify. The capability dispatcher alone starts the fixed selected-image python with argv -I -m tht.protected_fs_helper, owns canonical inventory/blob framing, applies the exact stdin/result/deadline caps above, and tears down/awaits the owned process group. The Python helper first validates FD 3 and FD 4, then owns all anchored tree syscalls/FDs. It computes file SHA-256, verifies exact byte counts, serializes the canonical manifest, rejects actual manifest overflow, and never echoes bytes, paths, or exceptions.

EvidenceMaterializer.validatePublished calls ProtectedWorkspaceFs.verifyImmutableTree inside the same borrowed-root/writer-capability lifetime, strictly parses the manifest, compares inventory/tree OID and limits, streams/hashes every declared local file, verifies modes/UID/nlink/size, validates every directory and absence of undeclared entries, and returns:

export interface VerifiedRevisionContent {
  workspaceId: string;
  workspaceRevision: string;
  root: string;
  sourceRoot: string;
  sourceTreeOid: string;
  manifestSha256: string;
  entryCount: number;
  totalBytes: number;
}

A suspect final root is never overwritten, repaired, or deleted.

  • Step 4: run kill, race, and focused gates.
(cd harness && .venv/bin/pytest -q tests/test_protected_fs_helper.py \
  tests/test_locked_child_stdin.py tests/test_layout_marker_commands.py \
  tests/test_p3_internal_cli.py && \
  .venv/bin/ruff check tht/protected_fs_helper.py tht/locked_child_stdin.py \
    tests/test_protected_fs_helper.py tests/test_locked_child_stdin.py \
    tests/test_layout_marker_commands.py tests/test_p3_internal_cli.py)
(cd backend && npx vitest run test/protected-workspace-fs.test.ts \
  test/workspace-preprocessing-state.test.ts test/workspace-evidence-materializer.test.ts && \
  npx tsc --noEmit --target ES2022 --module ES2022 --moduleResolution Bundler \
    --strict --skipLibCheck test/workspace-locked-child-cumulative.compile.ts && \
  npx tsc --noEmit -p .)

Expected: every kill point either recovers the owned inode or reports safe residue; no replacement-tree write, overwrite, or foreign deletion occurs. The P3 locked-child focused gates pass, the exact base-three + 14 + 2 + 2 kind fence compiles, and all twenty-one requests traverse the one exhaustive capability dispatcher.

  • Step 5: commit.
git add backend/src/workspaces/protected-workspace-fs.ts \
  backend/test/protected-workspace-fs.test.ts \
  backend/src/workspaces/preprocessing-state.ts \
  backend/test/workspace-locked-child-cumulative.compile.ts \
  backend/test/workspace-preprocessing-state.test.ts \
  backend/test/fixtures/workspace-lock-root-worker.mjs \
  backend/src/workspaces/evidence-materializer.ts \
  backend/test/workspace-evidence-materializer.test.ts \
  harness/tht/locked_child_stdin.py harness/tests/test_locked_child_stdin.py \
  harness/tht/protected_fs_helper.py harness/tests/test_protected_fs_helper.py
git commit -m 'feat: publish immutable Evidence trees safely'

Task 5: Add repository-owned materialize/reuse with Git anchoring and deadlock barriers

Files:

  • Modify: backend/src/workspaces/git-repository.ts

  • Modify: backend/src/workspaces/registry.ts

  • Modify: backend/src/workspaces/registry-factory.ts

  • Modify: backend/src/workspaces/evidence-materializer.ts

  • Modify: backend/test/workspaces-git-repository.test.ts

  • Modify: backend/test/workspace-registry.test.ts

  • Modify: backend/test/workspace-registry-factory.test.ts

  • Modify: backend/test/workspace-evidence-materializer.test.ts

  • Step 1 (RED): define the single lock-owning boundary.

Add one method to the narrowed materialization view, implemented by WorkspaceRegistry, using the Task 2 named types without an anonymous restatement:

materializeEvidenceForActiveRevision(
  input: RepositoryMaterializationInput,
): Promise<PreparedEvidenceMaterialization>;

It performs exactly:

repository acquire
→ reread operational active descriptor/snapshot and expected identity
→ fixed Git root-tree OID resolution + exact inventory
→ writer acquire while repository remains held
→ publish new tree OR validate existing tree against Git OID+inventory and stream/hash local files
→ writer release
→ repository release

The caller cannot supply a repo path, destination, source path, Git argv, writer callback, or repository mutator. The source is derived from the validated descriptor only.

Tests assert the normal trace and two races. Race A: materialization holds repository before writer while another normal writer waits; it completes. Race B: preprocessing holds a writer-only one-element ordered callback after its materialization; a pull holds repository and waits for writer; preprocessing performs no repository call, completes, then pull advances. Instrumented types fail immediately on any writer → repository edge, not merely on timeout.

  • Step 2 (RED): require a Git anchor on every reuse.

A second invocation must spawn fixed tree lookup/inventory Git reads even if local content and manifest appear valid. It compares exact root OID and every inventory entry to the manifest, then streams/hashes the local tree. Blob bodies need not be reread from Git. Git/object unavailability, tree OID mismatch, inventory mismatch, local tamper, unsigned reconstructed manifest, extra/missing file, link/type/mode/UID drift, or changed limit contract fails closed. Assert no mtime/write/blob-body read on valid reuse, but assert Git tree lookup occurred exactly once under the shared deadline.

Include an attack that replaces local files and rewrites the manifest with matching new SHA-256 values while retaining a claimed old source_tree_oid; the fixed Git inventory/OID comparison must reject it.

  • Step 3 (GREEN): implement and keep mutation APIs structurally absent.

registry-factory.ts constructs the narrowed interface only for the P6 capability. The implementation imports and uses RepositoryMaterializationInput and returns the one named PreparedEvidenceMaterialization from workspaces/types.ts; no anonymous input/result shape or duplicate declaration is permitted. Populate every prepared field only after the repository-held active descriptor/snapshot identity, Git tree anchor, manifest, immutable content root, source root, counts, and local bytes have all validated. The implementation uses P5 runFixedGitChild, P5 ProtectedWorkspaceFs, the installation-bound P2 root factory, and a one-element runUnderOrderedWorkspaceWriterLocks callback. Inside forWorkspace it passes the exact borrowed rootLease plus matching writerCapability to openProtectedWorkspaceFs; it never passes a root path or spawns directly. No exported callback allows a service to call arbitrary code under repository lock. No Git or repository object escapes in PreparedEvidenceMaterialization.

  • Step 4: run focused gates and capability mutation negatives.
(cd backend && npx vitest run \
  test/fixed-git-child.test.ts test/workspaces-git-repository.test.ts \
  test/workspace-evidence-git-tree.test.ts test/protected-workspace-fs.test.ts \
  test/workspace-evidence-materializer.test.ts test/workspace-registry.test.ts \
  test/workspace-registry-factory.test.ts -t 'material|reuse|lock order|mutation' && \
  npx tsc --noEmit -p .)
(cd tools/thothctl && go test ./internal/workspaceops -run \
  'EvidenceMaterialization|RegistryMutation' -count=1)

Expected: PASS; valid reuse has Git metadata reads and zero blob-body/local writes; every mutation attempt has zero spawned mutator.

  • Step 5: commit.
git add backend/src/workspaces/git-repository.ts backend/src/workspaces/registry.ts \
  backend/src/workspaces/registry-factory.ts backend/src/workspaces/evidence-materializer.ts \
  backend/test/workspaces-git-repository.test.ts backend/test/workspace-registry.test.ts \
  backend/test/workspace-registry-factory.test.ts \
  backend/test/workspace-evidence-materializer.test.ts
git commit -m 'feat: materialize Evidence under repository writer order'

Task 6: Reconcile snapshots and Evidence from one authoritative retention API

Files:

  • Modify: backend/src/workspaces/registry.ts
  • Modify: backend/src/workspaces/preprocessing-state.ts
  • Create: backend/src/workspaces/session-manifest-pins.ts
  • Create: backend/test/workspace-session-manifest-pins.test.ts
  • Create: backend/src/workspaces/evidence-retention.ts
  • Create: backend/test/workspace-evidence-retention.test.ts
  • Modify: backend/test/workspace-registry.test.ts
  • Modify: backend/test/workspace-preprocessing-state.test.ts
  • Modify: backend/src/routes/sessions.ts
  • Modify: backend/test/routes-sessions.test.ts

The only public retention source is:

export interface AuthoritativeRevisionRetention {
  workspaceId: string;
  revisions: readonly string[];
  reasons: Readonly<Record<string, readonly (
    "active" | "revision_lease" | "session_manifest" | "preprocessing_run"
  )[]>>;
}
export class WorkspaceRegistry {
  authoritativeRevisionRetention(workspaceId: string): Promise<AuthoritativeRevisionRetention>;
  reconcileAuthoritativeRetention(workspaceId: string): Promise<RetentionResult>;
}
  • Step 1 (RED): prove complete pin enumeration and fail-closed behavior.

Tests require the union of:

  1. the active operational commit for the workspace;
  2. every strict, persisted registry revision-lease record (creating or persisted) whose workspace/commit/token/state/file identity validates;
  3. every nonterminal/resumable session manifest pin from a complete direct scan of the workspace's canonical sessions/ root, independent of principal, HTTP pagination, RLS, or a caller-supplied list;
  4. every strict nonterminal P2/P5/P6 job revision from PreprocessingStateStore's canonical jobs/ directory.

Manifest and job scans are bounded, direct-child only where the persistence schema requires, no-follow, regular single-link, size-bounded, strict schema/identity/status, and total-count bounded. An unsafe/unknown pin file fails retention without deletion. Finalized/archived nonresumable sessions and terminal runs do not pin; tests use the exact final schemas rather than heuristic status strings.

Explicitly prove that physical snapshot/content directories, a partial /sessions?scope=mine result, a caller list, and a removed user's invisible list are never pin sources. Delete the last logical pin while leaving the physical descriptor snapshot and prove the revision becomes reclaimable.

(cd backend && npx vitest run \
  test/workspace-session-manifest-pins.test.ts \
  test/workspace-evidence-retention.test.ts \
  test/workspace-registry.test.ts test/workspace-preprocessing-state.test.ts \
  test/routes-sessions.test.ts -t 'authoritative retention|manifest pin|partial list')

Expected: RED because the authoritative API does not exist.

  • Step 2 (GREEN): implement one repository-owned reconciliation.

Under repository → writer order, compute and freeze the authoritative set once; reconcile descriptor snapshots from exactly that set first; then inspect only direct revisions/<40hex>/content targets and remove an unpinned target only if its complete manifest/owner/type/UID/nlink/tree validates as P6-owned. Never remove a revision directory, artifacts, indexes, corpus, Memory, sessions, run state, a suspect content root, another workspace, or an owned root whose pin scan was incomplete. Interrupted deletion is durable/idempotent and cannot broaden its target.

Remove the session route's retention side effect based on a list response. Routes may request authoritative reconciliation after a mutation, but cannot provide revisions/principal views. Tests prove route behavior and RLS listings remain unchanged.

  • Step 3: run retention and lock-order gates.
(cd backend && npx vitest run \
  test/workspace-session-manifest-pins.test.ts \
  test/workspace-evidence-retention.test.ts test/workspace-registry.test.ts \
  test/workspace-preprocessing-state.test.ts test/routes-sessions.test.ts && \
  npx tsc --noEmit -p .)

Expected: PASS, including active/lease/complete-manifest/nonterminal-run pins and physical-directory non-pinning.

  • Step 4: commit.
git add backend/src/workspaces/registry.ts backend/src/workspaces/preprocessing-state.ts \
  backend/src/workspaces/session-manifest-pins.ts \
  backend/src/workspaces/evidence-retention.ts backend/src/routes/sessions.ts \
  backend/test/workspace-session-manifest-pins.test.ts \
  backend/test/workspace-evidence-retention.test.ts backend/test/workspace-registry.test.ts \
  backend/test/workspace-preprocessing-state.test.ts backend/test/routes-sessions.test.ts
git commit -m 'feat: reconcile authoritative revision retention'

Task 7: Run the flat P2 service against verified filesystem Evidence without changing P3

Files:

  • Modify: backend/src/workspaces/preprocessing-service.ts

  • Modify: backend/src/workspaces/preprocessing-state.ts

  • Modify: backend/src/workspace-maintenance.ts

  • Modify: backend/src/workspaces/registry-factory.ts

  • Modify: backend/test/workspace-preprocessing-service.test.ts

  • Modify: backend/test/workspace-preprocessing-state.test.ts

  • Modify: backend/test/workspace-maintenance.test.ts

  • Modify: backend/test/workspace-runtime-config-lease.test.ts (test only)

  • Modify: tools/thothctl/internal/workspaceops/operations_test.go

  • Modify: tools/thothctl/cmd/thothctl/main_test.go

  • Read unchanged: backend/src/workspaces/runtime-config-lease.ts

  • Read unchanged: backend/src/workspaces/revision-layout.ts

  • Read unchanged: backend/src/workspaces/runtime-renderer.ts

  • Read unchanged: backend/src/tht/tht-runner.ts

  • Step 1 (RED): freeze the two-lock-phase orchestration.

For preprocess-evidence and the Evidence stage of preprocess-run, require:

repository-owned materializeEvidenceForActiveRevision(
  input: RepositoryMaterializationInput
) → PreparedEvidenceMaterialization
  (repository → writer, Git tree anchor, publish/verify, both released)
→ writer-only one-element ordered callback (`forWorkspace` supplies rootLease + writerCapability)
→ reread exact active revision and protected descriptor snapshot without repository/Git call
→ require workspaceId/workspaceRevision/descriptorBlob equal the named prepared result
→ fully revalidate contentRoot + manifestSha256 + sourceTreeOid + sourceRoot + counts/local bytes
  against that same PreparedEvidenceMaterialization
→ reread exact nonterminal run state and P5 accepted annotation identity
→ WorkspaceRuntimeConfigLeaseFactory.acquireMaintenance (unchanged P3 API)
→ require rendered filesystem root equals prepared.sourceRoot
→ fixed existing tht Evidence/full-run child
→ persist evidence_materialized before child and evidence_preprocessed only after success
→ writer release
→ repository-owned authoritative retention as a later separate operation

A pull that advances before writer reacquisition returns preprocessing_conflict; it is not retried or silently rebound. A pull waiting behind the later writer completes only after the child, because the child never asks for repository. Add deterministic traces for both directions and a compile-time fake whose repository methods throw if captured by the writer callback.

Tests cover dry-run (materialize/verify but no corpus/Qdrant publication), full publish, resume, cancel, child failure, unsafe materialization before child, prior corpus ACTIVE preservation, pristine JSON, and HTTP/S3 byte-for-byte regression. The public Go grammar is unchanged.

(cd backend && npx vitest run \
  test/workspace-preprocessing-service.test.ts \
  test/workspace-preprocessing-state.test.ts test/workspace-maintenance.test.ts \
  test/workspace-runtime-config-lease.test.ts -t 'filesystem|material|lock order')

Expected: RED because filesystem still stops at the P2 deferral.

  • Step 2 (GREEN): extend only the P2 service/state/entrypoint.

WorkspacePreprocessingService.execute owns orchestration; main only parses/encodes. Its repository dependency and local variable use the Task 2 named RepositoryMaterializationInput and PreparedEvidenceMaterialization imports directly; do not introduce another interface, Pick, anonymous signature, or inferred partial result. Reuse the P3 lease and its already-derived workspaceRuntimePaths; do not add a path helper or edit P3 production. Under the later writer lock, reopen prepared.descriptorSnapshotPath, revalidate every prepared identity field named above, and require prepared.sourceRoot to equal the unchanged lease's rendered filesystem root before invoking the existing harness argv. Preserve P2's run ID, completed-stage, dry-run, resume, stdout/stderr, and child FD 3 contracts. Preserve P5 explicit acceptance; P6 cannot skip or infer it.

  • Step 3: verify backend/Go and old-code decoding.
(cd backend && npx vitest run \
  test/workspace-preprocessing-service.test.ts test/workspace-preprocessing-state.test.ts \
  test/workspace-maintenance.test.ts test/workspace-runtime-config-lease.test.ts \
  test/workspace-revision-layout.test.ts test/workspace-runtime-renderer.test.ts \
  test/workspace-runtime-handoff.test.ts && npx tsc --noEmit -p . && npm run build)
(cd tools/thothctl && go test ./internal/workspaceops ./cmd/thothctl -count=1)

Expected: filesystem succeeds; historical result decoding still recognizes the old deferral code; no production path emits it.

  • Step 4: commit.
git add backend/src/workspaces/preprocessing-service.ts \
  backend/src/workspaces/preprocessing-state.ts backend/src/workspace-maintenance.ts \
  backend/src/workspaces/registry-factory.ts \
  backend/test/workspace-preprocessing-service.test.ts \
  backend/test/workspace-preprocessing-state.test.ts backend/test/workspace-maintenance.test.ts \
  backend/test/workspace-runtime-config-lease.test.ts \
  tools/thothctl/internal/workspaceops/operations_test.go \
  tools/thothctl/cmd/thothctl/main_test.go
git commit -m 'feat: preprocess verified filesystem Evidence'

Task 8: Add a cross-layer P3 regression test and stop rather than repairing P3

Files:

  • Create: harness/tests/test_registry_evidence_revision.py

  • Modify test only: backend/test/workspace-preprocessing-service.test.ts

  • Do not modify any P3 production file

  • Step 1: add the regression.

Build two configs for the same workspace/collection at revisions A/B with separate P3 paths.corpus. With fake embeddings and the Qdrant request recorder, prove:

  • A and B corpus ACTIVE/manifests stay in their exact roots;
  • schema/Evidence point IDs and every list/search/existing-hash/upsert/delete filter include exact revision;
  • identical document IDs/generations retrieve only their own revision;
  • filesystem resolution never crosses corpus roots;
  • Memory/solved registry, IDs, and filters remain workspace-global and do not fork;
  • service materialized source commit, runtime identity, corpus manifest, and Qdrant payload/filter revision are equal.
(cd harness && .venv/bin/pytest -q tests/test_registry_evidence_revision.py)
(cd backend && npx vitest run test/workspace-preprocessing-service.test.ts \
  -t 'materialized source corpus and Qdrant share one revision')

Expected after Tasks 1–7: PASS against finalized P3. If either exposes P3 production drift, stop P6. Amend P3 in a separate owning checkpoint, run P3 focused suites and ./scripts/p3-acceptance.sh integration --keep, record the checkpoint, rebase, and rerun Task 1. Do not add P3 production files to a P6 commit.

  • Step 2: run the P3 contract regression suite.
(cd harness && .venv/bin/pytest -q \
  tests/test_registry_evidence_revision.py tests/test_preprocess_cli.py \
  tests/test_corpus_pipeline.py tests/test_qdrant_vector_store.py \
  tests/test_semantic_kind_isolation.py tests/test_filesystem_evidence_source.py && \
  .venv/bin/ruff check tests/test_registry_evidence_revision.py)

Expected: PASS with no network.

  • Step 3: commit tests only.
git add harness/tests/test_registry_evidence_revision.py \
  backend/test/workspace-preprocessing-service.test.ts
git commit -m 'test: bind filesystem Evidence to its revision'

Task 9: Document the released Evidence path and bounded recovery model

Files:

  • Modify: docs/contracts/workspace-evidence-v3.md

  • Modify: docs/install/local-workspace-registry.md

  • Modify: docs/install/server-workspace-registry.md

  • Modify: docs/testing/p2-p6-manual-verification.md

  • Modify: deploy/workspace-registry.env.example

  • Modify: scripts/verify-workspace-install-docs.sh

  • Modify: scripts/test-verify-workspace-install-docs.sh

  • Step 1 (RED): add documentation contract checks.

Require the fixed Git source and published layout; exact thothctl ... preprocess evidence --dry-run --json, real run, and full-run commands; the inherited P5 operator order registry pull → exact workspace migrate dwh-cache --workspace "$WORKSPACE_ID" --json → strict selected-revision+binding physical/LSH snapshot verification without READY → schema accept → activation/READY; migration_required admission/resume at every pre-activation boundary; distinct exact-run-ID recovery for DWH preparation and activation; per-object vs aggregate/disk/inode limits; every symlink depth/gitlink/special/path rejection; adjacent owner recovery; every-reuse Git OID anchor; repository → writer order; operation-specific RW-lock/no-transport capability; authoritative active/lease/complete-manifest/nonterminal-run retention; revision-scoped corpus/schema/Evidence; workspace-global Memory/solved; safe inspection; and no checkout/archive/author-clone mount/generic Git mutation.

bash scripts/test-verify-workspace-install-docs.sh

Expected: RED on deferred P2 wording.

  • Step 2 (GREEN): update supported manuals.

Retain historical explanation of old P2 reports, but supported P6 execution no longer expects a deferral. Preserve the P5 install/recovery sequence in both local and server manuals: after every content pull and before accept, validate WORKSPACE_ID, run exact released DWH preparation, inspect the selected commit+binding physical/LSH snapshot while READY is absent, and prove admission remains migration_required until activation. Interrupted DWH preparation and activation each resume only with their own returned 32-hex outer run ID on the same command; never cross-use IDs or hand-edit snapshots/READY. Document Evidence recovery without instructing operators to edit owner/manifest files. Do not document P7/PSD/GUI/SSH.

  • Step 3: verify and commit.
bash scripts/test-verify-workspace-install-docs.sh
bash scripts/verify-workspace-install-docs.sh --fixtures-only
git add docs/contracts/workspace-evidence-v3.md docs/install/local-workspace-registry.md \
  docs/install/server-workspace-registry.md docs/testing/p2-p6-manual-verification.md \
  deploy/workspace-registry.env.example scripts/verify-workspace-install-docs.sh \
  scripts/test-verify-workspace-install-docs.sh
git commit -m 'docs: explain pinned Evidence materialization'

Task 10: Build the clean-state P6 process goal with full-root secret proof

Files:

  • Create: scripts/p6-acceptance.sh
  • Create: scripts/test-p6-acceptance.sh
  • Create: backend/scripts/p6-acceptance.mjs
  • Create: backend/scripts/p6-acceptance.test.mjs

Public command:

./scripts/p6-acceptance.sh integration --keep
  • Step 1 (RED): test ownership, lifecycle, mutation coverage, and reports.

The root is .artifacts/p6-integration/p6-<32hex>/. Test exclusive/no-follow ownership, clean source commit/tree, unique Compose labels, no retry, one hard monotonic deadline, bounded children, TERM→KILL group cleanup, report schema/hashes, kill points, capability mutation negatives, authoritative retention, --keep, signal/failure cleanup, and exact foreign-resource refusal. The dependency portion must also execute the exact P2/P3 multi-workspace registry-pull matrix: strict-lexical complete changedWorkspaceIds; same-ID SIGKILL immediately before active-pointer rename and immediately after rename+parent fsync; exact RegistryPullJobStateV1 phase/digest assertions; no resume refetch/republication or participant/capability reentry; full-set ownership through publication; reverse release before repository; and target-drift/third-identity refusal without mutation/owner clear. Mutation tests must fail if any required unsafe fixture, Git anchor, registry exception case, lock barrier, owner-evidence kill point, secret deletion, full-root scan, or report check is removed.

bash scripts/test-p6-acceptance.sh

Expected: RED because the runner is absent.

  • Step 2: implement the isolated production topology.

Use a real local bare remote/author clone, real selected core/maintenance image, real thothctl, production registry/materializer/service, controlled REST DWH, real Qdrant, real internal Ollama/model initialization, and fixture-only secrets. Host Node is only the bounded orchestrator/report writer; the product path requires no host Python/Node/Pi. Build once from clean HEAD. Record safe source/image/Git identities and exact ownership.

  • Step 3: execute positive P6 once.

Through production commands, inspect, dry-run, publish, retrieve, run full preprocessing, and explicitly rerun. Prove exact commit/tree/blob identity; every-reuse Git OID lookup; no blob reread/local rewrite on valid reuse; manifest/file hashes; no staging/owner residue; corpus ACTIVE; Qdrant revision payload/filter/retrieval; idempotent counts and no duplicate points; historical pinned revision retention; release of the last pin followed by authoritative content-only reclamation; and preservation of artifacts/indexes/corpus/Memory.

  • Step 4: execute each negative once, without retry.

Use independent commits/workspaces for the two registry-pull SIGKILL/resume cases, target drift/third identity, nested/root symlink, gitlink, parser-injected special mode, absolute/traversal/non-NFC/cross-workspace/duplicate path, per-file/aggregate/count/path/segment/manifest/disk/inode bounds, object header/OID drift, local manifest+file rewrite, Git unavailable on reuse, ancestor swap, target race, every owner/fsync/rename kill point, unsafe pin file, partial-user-list non-authority, foreign retention root, forbidden registry mutation, and bounded-child hang/flood/parser/cancel. The pull fixtures contain multiple reverse-presented changed IDs and use the exact P2/P3 request/job/lease-set/participant types; the pre-publication case resumes all-base without fetch, the post-rename+parent-fsync case resumes all-target without fetch or a second publication, and drift/third identity preserves nonterminal ownership. Each negative must yield a stable safe code, no harness child after unsafe materialization, no new corpus/Qdrant state, no overwrite, no foreign cleanup, and complete owned-child teardown.

Required check IDs include:

clean_source ownership capability_confinement registry_mutation_negative
registry_pull_multiworkspace_lexical_set registry_pull_same_id_prepublication
registry_pull_same_id_postpublication registry_pull_target_drift_third_identity_refusal
registry_pull_no_refetch_reentry registry_pull_publication_release_order
pinned_inventory bounded_git_children streaming_preflight inode_accounting
adjacent_owner_recovery dirfd_ancestor_races noreplace_atomic_publish
manifest_revalidation git_anchored_reuse filesystem_dry_run filesystem_publish
full_run idempotent_rerun revision_corpus revision_qdrant revision_retrieval
authoritative_retention physical_snapshot_not_pin no_partial_publish
fixture_secrets_deleted full_retained_root_secret_scan exact_cleanup
  • Step 5: delete fixture secrets before report finalization and scan all retained bytes.

On success, failure, or signal: stop listeners/children/containers; export only safe hashes/identities needed by reports; delete every fixture secret file and secret directory; assert those paths are absent; then scan every regular file under the retained run root with no directory exclusion, every reachable fixture Git blob, and every declared report artifact for all secret canaries, credential-shaped URLs, signed query values, and private endpoints. Only after that scan passes may final report.json/report.md and artifact hashes be finalized. Run the complete-root scan again including the final reports. --keep retains sanitized evidence only, never secret files or live resources.

  • Step 6: verify runner contracts and commit tooling.
bash -n scripts/p6-acceptance.sh scripts/test-p6-acceptance.sh
node --check backend/scripts/p6-acceptance.mjs
node --test backend/scripts/p6-acceptance.test.mjs
bash scripts/test-p6-acceptance.sh
git add scripts/p6-acceptance.sh scripts/test-p6-acceptance.sh \
  backend/scripts/p6-acceptance.mjs backend/scripts/p6-acceptance.test.mjs
git commit -m 'test: add P6 materialization acceptance'

Do not run the authoritative retained process until manual tooling is committed and the source is clean.


Task 11: Build the exact two-installation aggregate P2–P6 process

Files:

  • Create: scripts/p2-p6-acceptance.sh
  • Create: scripts/test-p2-p6-acceptance.sh
  • Create: backend/scripts/p2-p6-acceptance.mjs
  • Create: backend/scripts/p2-p6-acceptance.test.mjs
  • Modify: scripts/preprocess-smoke.sh (delegation/deprecation note only)

Public command:

./scripts/p2-p6-acceptance.sh integration --keep
  • Step 1 (RED): freeze two-installation ownership and exact choreography.

Use .artifacts/p2-p6-integration/p2-p6-<32hex>/, one shared bare remote/author clone, and two independent installations A/B with distinct descriptors, Compose projects, registry/sessions/Qdrant/Ollama volumes, fixture-secret roots, collections, run IDs, and reports. They may share the one immutable built image and Git remote only. Tests assert the following numbered production events occur exactly in order; no step is optional, preseeded, or replaced by a test seam.

  • Step 2: bootstrap A and B at the same base commit C0.

Start both private stacks. With the released workspace inspect --workspace <id> --json, bootstrap each empty registry against C0. Record equal C0 descriptor/tree/blob/content identities and distinct registry roots/installations. No curated annotation commit exists yet.

  • Step 3: prove P2 DWH and P3 layout/Memory/effective state in both.

For A, then B, run the released P2 DWH command and exact P3 migrations:

workspace preprocess dwh --workspace <id> --json
workspace migrate dwh-cache --workspace <id> --json
workspace migrate memory --workspace <id> --json
workspace migrate semantic-revision --workspace <id> --yes --json
workspace migrate activate-revision-layout --workspace <id> --yes --json

Prove maintenance/session effective-binding equivalence through the released inspect/runtime-snapshot evidence without starting Pi; same logical DWH binding/cache generation for equal installation inputs; revision-qualified artifacts/indexes/corpus; one workspace-global Memory registry/projection; and no mixed old/new layout.

  • Step 4: self-heal and strict-index A and B while C0 is still active and READY.

Delete/omit each exact fixture collection while the accepted C0 revision layout remains READY. For A, then B, call the released core POST /sessions admission route with valid fixture auth/question. This is the production ReadinessManager → ThtRunner.qdrantEnsure → QdrantCollectionManager.ensure path, not direct manager invocation. Configure the controlled Ollama fixture to fail at its later readiness boundary only after the collection and keyword indexes have been independently read compatible. Require the admission response to fail at that later boundary and prove cleanup: no session manifest, persisted revision lease, live Pi process, admission lease, or resumable run survives. Restore normal fixture Ollama, inspect exact collection compatibility, and run the released strict schema index in A and B at C0. No hidden session/Pi is permitted.

  • Step 5: exercise P4 guarded rebuild and deterministic interrupted recovery at READY C0, then reindex.

On A, run one fully confirmed production workspace collection rebuild; verify maintenance activation, complete session inventory, admissions/Pi quiescence, core stop, exact target deletion/recreation, neighbor preservation, core health, marker clear, and terminal state. Then arm the acceptance-owned P4 delete barrier, run another confirmed rebuild, kill only the labelled one-shot child after durable deletion, require core stopped/maintenance active/nonterminal state, release the barrier, and run the exact confirmed workspace collection recover once. Prove verified empty target and cleanup. Because rebuild/recovery intentionally discarded A's vectors, run the released strict schema index again in A before continuing. B remains independently indexed from Step 4 and is never rebuilt through A's project. Do not delete B's collection or repeat its missing-collection admission self-heal.

  • Step 6: pause one real P5 run in both before any curated commit.

Invoke workspace preprocess run --workspace <id> --json once in A and once in B. Both must stop at the real manual_review_required checkpoint with separate 32-hex run IDs and state-owned candidates. Use production workspace schema export-fks --run <id> --output <new-file> --json in both; rehash exact outputs; require equal candidate digest/count and prove no second FK suggestion occurred during export.

  • Step 7: publish one curated content-only C1, then pull, prepare its DWH snapshot, accept, and activate C1 independently in A and B.

Edit one exported candidate in the ordinary author clone, validate it, create exactly one curated content-only Git commit C1, and push once. Freeze this exact production command order, using each installation's own --installation, workspace ID, and paused run ID:

# pull C1 independently into both installations through P3-owned registry_pull
workspace registry pull --workspace <id-a> --json
workspace registry pull --workspace <id-b> --json
# bind each installation's exact ID, then prepare C1's physical/LSH snapshot without READY
WORKSPACE_ID=<id-a>
workspace migrate dwh-cache --workspace "$WORKSPACE_ID" --json
WORKSPACE_ID=<id-b>
workspace migrate dwh-cache --workspace "$WORKSPACE_ID" --json
# P5 accept remains registry RO/no-Git and writer-only one-element ordered callbacks in each installation
workspace schema accept --run <installation-a-run-id> --yes --json
workspace schema accept --run <installation-b-run-id> --yes --json
# C1 remains unready after pull/preparation/accept; publish READY independently in both
workspace migrate activate-revision-layout --workspace <id-a> --yes --json
workspace migrate activate-revision-layout --workspace <id-b> --yes --json

Each pull uses P3's exact RegistryAddressedRequestV1 / RegistryPullJobStateV1 path and registry_pull capability. Before the response-loss matrix, prove both create and resume carry the validated installation's required installation/repository/remote-ref identities, create carries its exact expected base, and the plan/state preserve all three identities. Exercise the main C1 pulls as response-loss cases without creating another curated commit: for A, arm the owned barrier at durable publication_intent_durable, SIGKILL immediately before active-pointer rename, then resume only A's returned exact 32-hex ID; for B, SIGKILL immediately after active-pointer rename+parent fsync but before target_published persistence, then resume only B's returned exact ID. A must remain all-base until resume; B must be all-target despite its still-publication_intent_durable job. In both installations prove no resume fetch/ls-remote/target reselection, no participant lock/repository reentry, complete strict-lexical changedWorkspaceIds, matching AddressedWorkspacePublicationLeaseV1 calls, full OrderedWorkspaceWriterCapabilitySet ownership through rename+parent fsync and terminal durability, reverse set release before repository release, and one active publication rename total. Record registry_pull_same_id_before_publication_a and registry_pull_same_id_after_publication_b only after exact field/digest/phase checks. Run target-drift and third-active-identity refusal in isolated copied fixtures so the one shared C1 and main A/B state remain unchanged; record registry_pull_drift_third_identity_refusal_ab only when each refusal performs no mutation/refetch/owner clear.

After its recovered pull and before its accept, each installation must execute exact released workspace migrate dwh-cache --workspace "$WORKSPACE_ID" --json with its own validated ID and outer run. For A and B separately, prove the migrate_dwh_cache operation remains registry RO/no-Git; its p3_migrate_dwh_cache/p3_prepare_dwh_cache cache stage plus p3_materialize_dwh_snapshot reuse or prepare C1's binding cache, publish C1's own revisions/C1/dwh-snapshots/<binding-key>/{artifacts,indexes} physical/LSH snapshot, and strictly reverify all identities/digests without reading or publishing READY or falling back to C0's revision snapshot. Record dwh_snapshot_c1_a and dwh_snapshot_c1_b only after those strict checks. Each accept then uses registry RO/no-Git, a writer-only one-element ordered callback, and AnnotationSynchronizer.verifyPrepared through writerCapability.spawnChild. Assert C1 session admission and preprocessing resume fail migration_required after pull, after DWH preparation, and after accept in each installation; record pre_ready_migration_required_c1_ab only after all six A/B boundary refusal pairs pass. Record separate c1_layout_activated_a and c1_layout_activated_b only after the corresponding activation proves C1's exact READY.json; only then may admission/resume succeed. After activation, assert each installation resolves the same C1 annotation blob/digest while retaining distinct state/run IDs. Rerun DWH/effective inspection and prove C1 reuses C0's DWH cache generation/binding but never its revision snapshot, while C1 artifacts/corpus/schema/Evidence roots fork from C0 and Memory/solved remain global.

  • Step 8: strict-index, resume, materialize, retrieve, and prove idempotency in A at READY C1.

Run A's released strict schema index only after c1_layout_activated_a. Resume the exact P5-accepted outer run ID through workspace preprocess run --resume; require that production resume to pass the P6 materializeEvidenceForActiveRevision boundary, materialize/index filesystem Evidence, and reach terminal success. Run revision-filtered retrieval, then one explicit idempotent materialize/full-run rerun. Prove Git tree OID/materialization/corpus/Qdrant revision equality, no duplicate points/generations, P5 accepted identity, exact DWH reuse, and authoritative retention. The rerun is an assertion, not a retry.

  • Step 9: execute the same READY-C1 chain in B with fully separate semantic state.

Only after c1_layout_activated_b, run B's strict schema index, resume its exact accepted outer run, require filesystem Evidence materialization/indexing, run revision-filtered retrieval, and run one explicit idempotent rerun through its own maintenance service, sessions root, corpus, Qdrant collection/storage, and state. Stop all A services before one B retrieval. Require equal shared Git tree/blob/content hashes and logical DWH binding where inputs match, but distinct installation IDs, registry paths, manifests, run IDs, corpus ACTIVE files, Qdrant storage/points, Memory registries, secrets, and ownership. Neither report may contain the other's canary or path.

  • Step 10: run aggregate negative/security cases and exact cleanup.

Cover incompatible Qdrant, unaccepted new annotation, revision resume mismatch, nested symlink/gitlink, aggregate/disk/inode limits, tampered local tree+manifest, Git-unavailable reuse, forbidden registry mutation, unsafe pin, stale foreign root, and cross-installation path/collection attempts. Preserve last valid state. Stop bounded children/listeners; remove only both labelled projects/resources; delete both fixture-secret trees; then perform a no-exclusion scan of the complete retained aggregate root before and after final report generation. Retain only safe hashes/identities/reports.

Required aggregate check IDs separately cover A, B, and shared choreography, including:

bootstrap_ab_base p2_dwh_ab p3_layout_memory_effective_ab
production_admission_self_heal_c0_ab failed_admission_cleanup_c0_ab strict_index_c0_ab
p4_guarded_rebuild p4_interrupted_recovery reindex_a_after_recovery
p5_paused_ab one_curated_commit registry_pull_c1_a registry_pull_c1_b
registry_pull_same_id_before_publication_a registry_pull_same_id_after_publication_b
registry_pull_drift_third_identity_refusal_ab
dwh_snapshot_c1_a dwh_snapshot_c1_b pre_ready_migration_required_c1_ab
p5_accept_c1_a p5_accept_c1_b c1_layout_activated_a c1_layout_activated_b dwh_reuse_content_fork
strict_index_c1_a resume_materialize_retrieve_idempotent_a
strict_index_c1_b resume_materialize_retrieve_idempotent_b
cross_installation_isolation fixture_secrets_deleted full_retained_root_secret_scan exact_cleanup
  • Step 11: test and commit aggregate tooling.
bash -n scripts/p2-p6-acceptance.sh scripts/test-p2-p6-acceptance.sh
node --check backend/scripts/p2-p6-acceptance.mjs
node --test backend/scripts/p2-p6-acceptance.test.mjs
bash scripts/test-p2-p6-acceptance.sh
git add scripts/p2-p6-acceptance.sh scripts/test-p2-p6-acceptance.sh \
  backend/scripts/p2-p6-acceptance.mjs backend/scripts/p2-p6-acceptance.test.mjs \
  scripts/preprocess-smoke.sh
git commit -m 'test: add aggregate P2-P6 acceptance'

Task 12: Make focused P6 and aggregate manual verification independently runnable

Files:

  • Create: scripts/p6-manual-verification.sh
  • Create: scripts/test-p6-manual-verification.sh
  • Create: backend/scripts/p6-manual-verification.mjs
  • Create: backend/scripts/p6-manual-verification.test.mjs
  • Modify: docs/testing/p2-p6-manual-verification.md
  • Modify only after a reviewer verdict: PROJECT_STATE.md

Lifecycle:

./scripts/p6-manual-verification.sh prepare
./scripts/p6-manual-verification.sh start
./scripts/p6-manual-verification.sh stop
./scripts/p6-manual-verification.sh cleanup
  • Step 1 (RED): test helper ownership and reviewer separation.

Require fresh .artifacts/manual-acceptance/p6/, exact two-project ownership, symlink/foreign refusal, bounded start/readiness, no scenario retry, stop with TERM→KILL owned groups, cleanup refusal while live, no broad Docker/Git command, fixture-secret deletion before complete-root scan, and no helper-created verdict/PASS. Generated commands use real thothctl and bounded readers; they never print Compose environments, rendered credentials, or secret files.

bash scripts/test-p6-manual-verification.sh

Expected: RED until tooling exists.

  • Step 2: implement prepare/start/stop/cleanup only.

prepare creates a new shared remote/author tree, C0, unsafe independent commits, controlled DWH/Ollama, two installations, fixture secrets, command scripts, GUIDE.md, and ownership. It does not execute reviewer checks. start starts only owned fixture services/projects and bounded readiness. stop validates and stops exact resources, exports safe identities, deletes fixture secrets, and scans the entire retained root without exclusions before declaring stopped. cleanup requires stopped state and exact nonce/labels.

  • Step 3: write the exact reviewer walkthrough.

The reviewer personally performs, in order:

  1. bootstrap A/B at C0;
  2. complete P2 DWH and every P3 layout/Memory/effective-equivalence check in both so C0 is READY;
  3. use production session admission to self-heal each missing C0 collection, fail only at later Ollama readiness, inspect complete failed-admission cleanup, and strict-index C0 in A/B;
  4. run A's guarded rebuild, interruption, exact recovery, and post-recovery reindex without repeating B's missing-collection self-heal;
  5. pause real P5 runs in both and export/re-hash both state-owned candidates;
  6. create one curated C1; run exact P3 workspace registry pull --workspace <id> independently in A/B through the quoted P2/P3 exception: SIGKILL A at publication_intent_durable immediately before active-pointer rename and resume the same ID; SIGKILL B immediately after rename+parent fsync but before target_published persistence and resume the same ID. Inspect exact RegistryPullJobStateV1 fields/phases, strict-lexical complete multi-workspace capability acquisition, no resume fetch/republication or participant reentry, full-set ownership through rename+fsync, reverse release before repository, and isolated target-drift/third-identity refusal. After each recovered pull set/validate that installation's WORKSPACE_ID and run exact released workspace migrate dwh-cache --workspace "$WORKSPACE_ID" --json; inspect and record dwh_snapshot_c1_a / dwh_snapshot_c1_b only after C1's binding-qualified physical/LSH snapshot is published and strictly reverified without READY or C0 snapshot fallback; accept each exact paused run; then run workspace migrate activate-revision-layout --workspace "$WORKSPACE_ID" --yes independently and record c1_layout_activated_a / c1_layout_activated_b;
  7. prove C1 DWH cache-generation reuse but distinct revision snapshot, revision artifact/corpus/schema/Evidence fork, global Memory/solved, and admission/resume migration_required after pull, after DWH preparation, and after accept until each C1 READY event;
  8. after A's C1 activation only, strict-index, resume the accepted run through P6 materialization, retrieve, and prove idempotency with Git OID/manifest/corpus/Qdrant equality;
  9. after B's C1 activation only, execute the same strict-index/resume/materialize/retrieve/idempotency chain and retrieve with A stopped;
  10. exercise nested symlink, gitlink, limits, tamper+manifest rewrite, Git-unavailable reuse, retention, and capability-mutation negatives;
  11. release an old pin and prove only its valid content/ is reclaimed;
  12. run generated bounded report/secret checks and stop;
  13. write VERDICT.md with reviewer, UTC time, observed hashes, each item PASS/FAIL, and separate P6 manual acceptance and aggregate P2-P6 manual acceptance decisions.

No helper writes or edits the verdict.

  • Step 4: verify and commit manual tooling/docs.
bash -n scripts/p6-manual-verification.sh scripts/test-p6-manual-verification.sh
node --test backend/scripts/p6-manual-verification.test.mjs
bash scripts/test-p6-manual-verification.sh
bash scripts/test-verify-workspace-install-docs.sh
git add scripts/p6-manual-verification.sh scripts/test-p6-manual-verification.sh \
  backend/scripts/p6-manual-verification.mjs \
  backend/scripts/p6-manual-verification.test.mjs \
  docs/testing/p2-p6-manual-verification.md
git commit -m 'test: add P6 and aggregate manual walkthrough'

Task 13: Run focused/full suites, retain final evidence, and stop at the manual gate

Files:

  • Modify after final green reports: PROJECT_STATE.md

  • Read: retained P6 and aggregate reports

  • Do not modify production in this task without a new focused RED test and owning-task repair

  • Step 1: verify a clean committed source and no superseded surface.

git diff --check
git status --short --branch
git fsck --no-reflogs --full
bad=(
  'backend/src/workspace-maintenance''/'
  'WorkspaceMaintenance''Operator'
  'tools/thothctl/internal/workspace''/'
  'workspace-effective-config''.test.ts'
  'runtime''Paths('
)
for needle in "${bad[@]}"; do ! rg -nF "$needle" backend tools/thothctl harness; done

Expected: clean source, Git fsck PASS, no superseded names.

  • Step 2: run focused P5/P6 backend and harness suites.
(cd backend && npx vitest run \
  test/fixed-git-child.test.ts test/protected-workspace-fs.test.ts \
  test/workspace-evidence-git-tree.test.ts test/workspace-evidence-materializer.test.ts \
  test/workspace-evidence-retention.test.ts test/workspace-session-manifest-pins.test.ts \
  test/workspaces-git-repository.test.ts test/workspace-registry.test.ts \
  test/workspace-registry-factory.test.ts test/workspace-preprocessing-state.test.ts \
  test/workspace-preprocessing-service.test.ts test/workspace-maintenance.test.ts \
  test/workspace-runtime-config-lease.test.ts test/workspace-revision-layout.test.ts \
  test/workspace-runtime-renderer.test.ts test/workspace-runtime-handoff.test.ts \
  test/workspace-annotations.test.ts test/routes-sessions.test.ts && \
  npx tsc --noEmit --target ES2022 --module ES2022 --moduleResolution Bundler \
    --strict --skipLibCheck test/workspace-locked-child-cumulative.compile.ts && \
  npx tsc --noEmit -p . && npm run build)
(cd harness && .venv/bin/pytest -q \
  tests/test_protected_fs_helper.py tests/test_locked_child_stdin.py \
  tests/test_layout_marker_commands.py tests/test_p3_internal_cli.py \
  tests/test_registry_evidence_revision.py tests/test_preprocess_cli.py \
  tests/test_corpus_pipeline.py tests/test_qdrant_vector_store.py \
  tests/test_semantic_kind_isolation.py tests/test_filesystem_evidence_source.py && \
  .venv/bin/ruff check tht/protected_fs_helper.py tht/locked_child_stdin.py \
  tests/test_protected_fs_helper.py tests/test_locked_child_stdin.py \
  tests/test_layout_marker_commands.py tests/test_p3_internal_cli.py \
  tests/test_registry_evidence_revision.py)
(cd tools/thothctl && test "$(go env GOVERSION)" = 'go1.26.5' && go test ./... -count=1)

Expected: PASS with exact counts recorded.

  • Step 3: run every repository full suite/gate.
(cd backend && npx vitest run && npx tsc --noEmit -p . && npm run build)
(cd harness && .venv/bin/pytest -q && .venv/bin/ruff check .)
(cd frontend && npx vitest run && npx tsc -b && npm run build)
(cd tools/thothctl && go test ./... -count=1)
./scripts/test-default-compose.sh
./scripts/test-unified-compose.sh
./scripts/test-internal-semantic-compose.sh
./scripts/test-compose-secret-policy.sh
./scripts/test-no-deployment-coupling.sh
./scripts/test-deployment-command-contract.sh
./scripts/test-thothctl-build-contract.sh
./scripts/test-p6-acceptance.sh
./scripts/test-p2-p6-acceptance.sh
./scripts/test-p6-manual-verification.sh
./scripts/test-verify-workspace-install-docs.sh
./scripts/verify-workspace-install-docs.sh --fixtures-only
git diff --check
git status --short

Expected: every command exits 0. Existing unrelated lint debt, if still accepted by project policy, must be reported with an exact baseline and touched files must remain clean; never summarize a failing required gate as PASS.

  • Step 4: run final clean P6 and aggregate integrations once each.
./scripts/p6-acceptance.sh integration --keep
./scripts/p2-p6-acceptance.sh integration --keep

Expected: new roots, no retry, same exact clean source commit/tree/image, every check PASS, fixture secrets absent, complete retained-root scans PASS, and no owned live child/listener/container/network/volume.

  • Step 5: independently verify reports and retained roots.

Use bounded report parsers to verify schemas, required check IDs exactly once, declared SHA-256 against every final artifact, source/image/Git identities, exact command order, A/B independence, and zero owned resources. Assert no fixture-secret path exists. Scan every regular file below each retained root with no excluded directory, then scan the final reports again. Do not grep secret contents into terminal history.

  • Step 6: record the automated checkpoint and commit only status docs.

PROJECT_STATE.md records exact source commit/tree, report paths and SHA-256, test/check counts, limitations, and:

P6 automated integration: PASS
P6 manual acceptance: PENDING
aggregate P2-P6 automated integration: PASS
aggregate P2-P6 manual acceptance: PENDING
git add PROJECT_STATE.md
git commit -m 'docs: record P6 automated verification checkpoint'
git status --short
  • Step 7: stop for the human gate.

Provide exact commands:

./scripts/p6-manual-verification.sh prepare
./scripts/p6-manual-verification.sh start
# reviewer follows .artifacts/manual-acceptance/p6/GUIDE.md and writes VERDICT.md
./scripts/p6-manual-verification.sh stop

Only an explicit reviewer PASS for both decisions may update the living manual and PROJECT_STATE.md; FAIL/PENDING reopens a focused owning plan. Cleanup is offered only after the verdict hashes are recorded:

./scripts/p6-manual-verification.sh cleanup

Stop. No P7/PSD/GUI/SSH work begins without new authorization.


Final acceptance checklist

  • Only the finalized flat P2/P3/P5 APIs are used; registry pull names/fields/orders match the quoted RegistryPullAddressedPlanV1, AddressedWorkspacePublicationLeaseV1, OrderedWorkspaceWriterCapabilitySet, RegistryAddressedRequestV1, exact participant interfaces, RegistryPullPhaseV1, and RegistryPullJobStateV1, with required installation/repository/remote-ref identity parity, pull-create expected-base parity, and no alias, optional overload, or replacement.
  • Registry-pull dependency/integration tests cover a complete reverse-presented multi-workspace lexical set, exact same-ID SIGKILL immediately before active-pointer rename and immediately after rename+parent fsync, no resume refetch/republication or participant/capability reentry, target drift/third identity refusal, full-set ownership through publication/terminal durability, reverse release, then repository release.
  • Repository → writer is the only nested lock order; later writer-only one-element ordered execution has no repository/Git capability.
  • The filesystem operation's registry RW mount exposes only the exact lock and fixed read Git plumbing; mutation-negative tests pass.
  • Every Git child is deadline/output/record/stderr/cancel bounded, closes stdin, TERM→KILLs only its owned group, and awaits exit/stdio.
  • The final sole WorkspaceLockedChildRequest alias remains the original three P2 members plus the unchanged fourteen-member P3LockedChildRequest, both P5 annotation members, and both P6 Evidence-tree members. A bidirectional compile-only exact-kind fence proves the cumulative 14 + 2 + 2 extension surface, and a table-driven runtime assertNever regression routes all twenty-one requests through the same WorkspaceWriterLockCapability.spawnChild; P3 locked-child focused tests remain in the P6 file, commit, and release gates.
  • P5 ProtectedWorkspaceFs and selected-image helper provide retained-dirfd tree publication with RENAME_NOREPLACE; no path-only/overwrite fallback exists. Every annotation/tree publish or verify uses that sole cumulative WorkspaceWriterLockCapability.spawnChild union, actual FD 3 + FD 4, fixed framing/result caps, and no ambient/direct spawn.
  • Adjacent reserved/bound owner evidence makes every pre/post-rename kill point explicit and deletion inode-confined.
  • Preflight counts every file, source directory, staging root, manifest, and both owner records.
  • Every reuse resolves the exact Git tree OID/inventory before streaming/hashing local bytes; Git unavailability fails closed.
  • Retention is active + validated leases + complete manifest pins + nonterminal runs; physical snapshot/content directories never self-pin.
  • Snapshot reconciliation precedes Evidence reconciliation from the same frozen authoritative set.
  • P6 adds only P3 regression tests; any P3 production defect is repaired/checkpointed under P3 before continuing.
  • Fixture secrets are deleted before finalization, and every retained-root scan has no secret-directory exclusion.
  • Aggregate order is exactly bootstrap A/B; P2/P3 C0 READY proof; one C0 admission self-heal plus strict index in A/B; A guarded rebuild/interrupted recovery plus reindex; both P5 pauses; one curated C1 commit; independent P3 workspace registry pull --workspace <id> in A/B with A same-ID pre-publication SIGKILL/resume, B same-ID post-rename+parent-fsync SIGKILL/resume, and isolated target-drift/third-identity refusal; exact workspace migrate dwh-cache --workspace "$WORKSPACE_ID" --json plus strict pre-READY C1/binding physical/LSH snapshot publication verification in A and B; both accepts; independent C1 activate-revision-layout in A/B; then C1 strict-index/resume/materialize/retrieve/idempotency in A and B. Admission/resume remain migration_required through pull, DWH preparation, and accept and succeed only after the matching activation.
  • Production self-heal traverses the released admission route and proves no failed-session manifest, lease, admission, or Pi residue.
  • Focused tests, full backend/harness/frontend/Go suites, Compose/docs gates, P6 integration, aggregate integration, and independent manual tooling are all explicit.
  • Automated statuses are PASS only from final clean retained reports; both manual decisions remain PENDING until the reviewer acts.