Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions docs/BUILD_LOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@

This is a concise chronological record of material implementation work and runtime evidence. It is not a substitute for Git history; it captures intent, verification, and blockers that a commit alone may not explain.

## 2026-07-31 — R4 Experience Compiler and Phase-A Judge accepted

- Added deterministic seam selection over fully supported R2 evidence and compiled the real R1/R2 fixture path into separate internal and participant recovery manifests. The participant projection is exact-schema and omits source revisions, run identity, setup, judge internals, hidden repair, production paths, and controller handles.
- Added a Phase-A judge that reopens the project-scoped snapshot and immutable command registry, verifies the participant Git boundary, computes the candidate diff, rejects protected or out-of-scope edits, and evaluates only an exact catalog-owned known repair in a separate clean evaluation twin. No-op and unrelated candidates never reach command execution.
- Added project-scoped command-evidence persistence with canonical metadata, bounded base64 payloads, hash/size verification, and tamper rejection. Reveal and abandonment remain explicit outcomes without executable readiness evidence.
- Seven R4 tests pass locally on Windows, including three byte-identical judge replays and evidence-store reopening/tamper cases. The complete extension suite passes 57/57; `npm run check`, production build, and VSIX packaging pass. Protected PR #14 run `30670322126` passed the required Linux, Windows, contract, web, and policy checks, so R4 acceptance is complete.

Evidence: `extension/src/experience/`, `extension/src/judge/`, `extension/src/twin/{commands,snapshot}.ts`, `extension/test/experience-judge.test.ts`, local command/package output on 2026-07-31, and protected GitHub Actions run `30670322126` on PR #14.

## 2026-07-31 — R3 Takeover Twin local candidate

- Implemented a controller-owned `SnapshotStore` that starts from the verified fixture target, applies the catalog mutation, creates a sanitized standalone one-commit participant repository, and re-verifies tree hashes, commit identity, ownership, remotes, reflogs, and Git alternates before materialization.
Expand Down
16 changes: 9 additions & 7 deletions docs/PROJECT_STATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Last updated: 2026-07-31

## Current branch milestone — R3 Takeover Twin complete
## Current branch milestone — R4 Experience Compiler and Judge accepted

Branch `codex/shadow-cockpit-rnd` resets the product R&D thesis around **Dual-Control Development**.

Expand All @@ -21,7 +21,10 @@ Branch `codex/shadow-cockpit-rnd` resets the product R&D thesis around **Dual-Co
- Six R2 tests cover the real cache-key fixture, cross-run determinism and golden IDs, file rename, multi-file changes, added/deleted functions, class boundaries, Git hunk parsing, unsupported syntax/language, missing checks, unsafe paths, and revision/run drift. The full local Windows extension suite passes 43/43 with TypeScript, build, and VSIX packaging. Protected PR #12 run `30666648522` passed `extension`, `extension-windows`, `contract`, `web`, and `jules-rnd-policy`, so R2 acceptance is complete.
- R3 now implements the fixture-only sanitized snapshot store, standalone one-commit participant repository, opaque Twin Manager, immutable command-registry snapshots, extension-owned trusted catalog, and `TrustedFixtureRunner`. The runner accepts only the pinned manifest, exact declared state/tree, catalog command, hash-verified standalone Node runtime, and opaque twin handle; non-fixture execution remains explicitly unsupported.
- Seven R3 tests cover production file/index/HEAD/ref/remote/worktree invariants, hidden-answer and source-history absence, cross-project ownership, exact-state execution, tamper/unknown-command rejection, concurrent execution IDs, single-run cancellation, immutable command snapshots, Windows paths with spaces, descendant-process termination, and exact cleanup. The full local Windows extension suite passes 50/50; build and VSIX packaging pass, and the runtime fixture assets are present in the VSIX. Protected PR #13 run `30668675359` passed `extension`, `extension-windows`, `contract`, `web`, and `jules-rnd-policy`, so R3 acceptance is complete.
- The Experience Compiler, Evidence Judge, Control Pulse runtime, readiness ledger, and v0.3 cockpit do not exist yet. R3 proves only the closed reviewed-fixture boundary; it does not execute arbitrary participant or workspace code.
- R4 now compiles one fully attributed, bounded, test-backed seam from real R1 replay plus R2 change evidence into separate internal and participant recovery manifests. The participant projection excludes source revisions, run identity, judge internals, setup, hidden repair, production paths, and controller handles.
- The Phase-A Evidence Judge reopens the immutable snapshot and command registry, verifies the standalone Git boundary, computes the participant diff, rejects protected or out-of-scope changes, and evaluates only the exact catalog-owned known repair in a clean evaluation twin. No-op and unrelated candidates fail without execution; reveal and abandonment produce no executable readiness evidence.
- Seven R4 tests cover deterministic compilation, strict projection/schema rejection, unsupported and fixture-drift rejection, protected-path integrity, no-op/unrelated non-execution, three identical clean replays, reveal/abandon honesty, and project-scoped persistent command evidence with tamper detection. The full local Windows extension suite passes 57/57; `npm run check`, production build, and VSIX packaging pass. Protected PR #14 run `30670322126` passed `extension`, `extension-windows`, `contract`, `web`, and `jules-rnd-policy`, so R4 acceptance is complete.
- The Control Pulse runtime, readiness ledger, and v0.3 cockpit do not exist yet. R4 remains a closed reviewed-fixture mechanism and does not execute arbitrary participant or workspace code.
- No skill-retention or speed metric has been measured. Values in the PRD are predeclared R&D targets.
- A new implementation audit found five R0 ambiguities: candidate-diff identity, pre-store fixture blobs, runtime identity, check IDs, and Git object format. The normative contract closes them with structured diffs, catalog-owned blobs, standalone Node `v22.17.0`, declared test IDs, and SHA-1 Git initialization; R0a/R0b now implement and verify that complete substrate.
- A guarded Jules dispatcher and PR policy are defined as a finite R0→R4 queue. They create at most one session after a successful preflight, stop after merged R4, remain inert unless dispatch is explicitly enabled, and keep plan approval on by default. Merges remain manual because the current project tests are not an independent immutable verifier. Full scheduled continuation still requires the dispatcher workflow to be reviewed into the default branch.
Expand Down Expand Up @@ -139,11 +142,10 @@ No external input blocks the repository-owned fixture R0–R4.5 mechanism in `do

## Next ordered actions

1. Integrate R4: one compiled recovery episode and deterministic Evidence Judge.
2. Pass R4.5: one bounded, catalog-only Explain-to-Break Pulse with replay/error fail-closed tests.
3. Run the 30-patch recovery-plus-probe technical corpus audit before expanding the product surface.
4. Add the local readiness ledger and minimal cockpit only after the vertical slice is reliable.
5. Run the preregistered delayed-transfer pilot before making any skill-retention claim.
1. Pass R4.5: one bounded, catalog-only Explain-to-Break Pulse with replay/error fail-closed tests.
2. Run the 30-patch recovery-plus-probe technical corpus audit before expanding the product surface.
3. Add the local readiness ledger and minimal cockpit only after the vertical slice is reliable.
4. Run the preregistered delayed-transfer pilot before making any skill-retention claim.

## Recent milestone commits

Expand Down
2 changes: 2 additions & 0 deletions docs/v0.3/CONTRACTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -840,6 +840,8 @@ interface JudgeResult {
}
```

`JudgeResult.resultHash` is `canonicalHash("judge-result", result without resultHash)`.

The judge establishes only the declared behavior under the declared oracle. It is not proof of total correctness or human mastery.

## 11. Readiness evidence versus verified readiness
Expand Down
157 changes: 157 additions & 0 deletions extension/src/experience/compile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
import { randomUUID } from "node:crypto";
import { assertBoundedText, assertToken } from "../agent/types";
import type { ExtractionResult } from "../change/evidence";
import { canonicalJson, compareUtf8 } from "../rnd/canonical";
import { assertEvidenceRef, type EvidenceRef } from "../recorder/events";
import { BuiltinFixtureCatalog } from "../twin/catalog";
import { FixtureCommandRegistry } from "../twin/commands";
import { FixtureSnapshotStore } from "../twin/snapshot";
import type { FixtureBlobRef } from "../twin/fixture-contract";
import {
assertInternalExperience,
assertParticipantExperience,
type CompiledExperience,
type InternalExperience,
type ParticipantExperience,
} from "./types";
import { selectRecoverySeam } from "./select";

export interface CompileExperienceInput {
extraction: ExtractionResult;
fixtureId: string;
manifestHash: string;
}

export class FixtureExperienceCompiler {
constructor(
private readonly catalog: BuiltinFixtureCatalog,
private readonly snapshots: FixtureSnapshotStore,
private readonly registry: FixtureCommandRegistry,
private readonly options: { id?: () => string } = {},
) {}

async compile(input: CompileExperienceInput): Promise<CompiledExperience> {
const record = await this.catalog.open(input.fixtureId, input.manifestHash);
if (!record) throw new Error("Experience fixture is not in the trusted catalog");
const selected = selectRecoverySeam(input.extraction);
const seam = selected.seam;
if (
seam.projectId !== input.extraction.projectId ||
seam.sourceRunId !== input.extraction.sourceRunId ||
seam.baseRevision !== input.extraction.baseRevision ||
seam.targetRevision !== input.extraction.targetRevision ||
seam.baseRevision !== record.manifest.baseRevision ||
seam.targetRevision !== record.manifest.targetRevision
) {
throw new Error("Selected seam identity does not match the trusted fixture");
}
if (!record.manifest.changedSymbols.some(({ path, symbol }) => path === seam.unit.path && symbol === seam.unit.symbol)) {
throw new Error("Selected seam is not declared by the trusted fixture");
}
if (seam.linkedChecks.some((id) => !record.manifest.targetChecks.includes(id))) {
throw new Error("Selected seam references a check outside the trusted fixture target");
}

const mutation = evidenceFromFixture(record.manifest.mutation.changeRef, "controller");
const target = record.manifest.states.find((state) => state.id === "target")!;
const snapshot = await this.snapshots.create({
projectId: seam.projectId,
sourceRevision: seam.targetRevision,
mutationId: record.manifest.mutation.id,
mutation,
allowedFiles: target.files.map((file) => file.path),
});
const commandIds = seam.linkedChecks.map((checkId) => {
const check = record.manifest.checks.find((item) => item.id === checkId);
if (!check) throw new Error(`Trusted fixture check is missing: ${checkId}`);
return check.commandId;
}).sort(compareUtf8);
const registry = await this.registry.freeze(seam.projectId, commandIds);
const editable = [...record.manifest.mutation.editablePaths];
const id = (this.options.id ?? (() => randomUUID().replaceAll("-", "")))();
assertToken(id, "experienceId");
const symbols = [`${seam.unit.path}#${seam.unit.symbol}`];
const concepts = ["tested-boundary-recovery"];
const task = `Recover the declared behavior at ${seam.unit.symbol}. Make ${seam.linkedChecks.join(", ")} pass without changing protected files.`;
assertBoundedText(task, 4096, "experience task");
const hiddenAnswer = evidenceFromFixture(record.manifest.knownRepair.changeRef, "controller");
const internal: InternalExperience = {
schemaVersion: 1,
id,
sourceRunId: seam.sourceRunId,
projectId: seam.projectId,
sourceBaseRevision: seam.baseRevision,
sourceTargetRevision: seam.targetRevision,
snapshotId: snapshot.id,
snapshotTreeHash: snapshot.treeHash,
commandRegistryHash: registry.sha256,
scope: { symbols, tests: [...seam.linkedChecks], concepts },
kind: "recover",
task,
setup: [
{ kind: "materialize-snapshot", snapshotId: snapshot.id },
...commandIds.map((commandId) => ({ kind: "run-approved-command" as const, commandId })),
],
judge: {
timeoutMs: 10_000,
commandRegistryHash: registry.sha256,
editablePaths: editable,
protectedFiles: snapshot.files
.filter((file) => !editable.includes(file.path))
.map(({ path, sha256 }) => ({ path, sha256 })),
checks: seam.linkedChecks.map((checkId) => ({
id: checkId,
kind: "approved-command" as const,
commandId: record.manifest.checks.find((item) => item.id === checkId)!.commandId,
expectedExitCode: 0,
})),
},
hiddenAnswer,
budgetMinutes: seam.factors.estimatedAttentionMinutes,
};
assertInternalExperience(internal);
const participant = projectParticipant(internal);
return {
internal,
participant,
snapshot,
registry,
selectionReasons: [...selected.reasons, "fixture-supported"].sort(compareUtf8),
};
}
}

export function serializeParticipantExperience(value: ParticipantExperience): string {
assertParticipantExperience(value);
return canonicalJson(value);
}

function projectParticipant(value: InternalExperience): ParticipantExperience {
return {
schemaVersion: 1,
id: value.id,
snapshotId: value.snapshotId,
snapshotTreeHash: value.snapshotTreeHash,
scope: { symbols: [...value.scope.symbols], concepts: [...value.scope.concepts] },
kind: "recover",
task: value.task,
visibleChecks: [...value.scope.tests],
budgetMinutes: value.budgetMinutes,
};
}

function evidenceFromFixture(ref: FixtureBlobRef, visibility: EvidenceRef["visibility"]): EvidenceRef {
const value: EvidenceRef = {
id: ref.id,
kind: "diff",
sha256: ref.sha256,
storedBytes: ref.storedBytes,
originalBytes: ref.storedBytes,
truncated: false,
redactions: [],
mediaType: ref.mediaType,
visibility,
};
assertEvidenceRef(value, "diff");
return value;
}
33 changes: 33 additions & 0 deletions extension/src/experience/select.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { compareUtf8 } from "../rnd/canonical";
import type { CandidateSeam, ExtractionResult } from "../change/evidence";

export interface SeamSelection {
seam: CandidateSeam;
reasons: string[];
}

export function selectRecoverySeam(result: ExtractionResult): SeamSelection {
if (result.status !== "supported") {
throw new Error("Recovery compilation requires fully supported change evidence");
}
const eligible = result.seams.filter((seam) =>
seam.linkedChecks.length > 0 &&
seam.unit.attribution.status === "attributed" &&
seam.factors.evidenceGap === 0 &&
seam.factors.estimatedAttentionMinutes > 0 &&
seam.factors.estimatedAttentionMinutes <= 10,
);
if (eligible.length === 0) throw new Error("No attributed, bounded, test-backed seam is eligible");

eligible.sort((left, right) =>
left.factors.estimatedAttentionMinutes - right.factors.estimatedAttentionMinutes ||
right.linkedChecks.length - left.linkedChecks.length ||
compareUtf8(left.unit.path, right.unit.path) ||
compareUtf8(left.unit.symbol, right.unit.symbol) ||
compareUtf8(left.id, right.id),
);
return {
seam: structuredClone(eligible[0]!),
reasons: ["attributed-change", "bounded-attention", "test-backed"],
};
}
Loading
Loading