feat: VersionGuard — newer-major sidecars are read-only with one warning (F5 SLICE-2, INV-16/PUC-4, #14)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Ben Stull
2026-06-10 23:01:30 -07:00
parent ef4ec8dbe9
commit ef7d9403c0
5 changed files with 66 additions and 6 deletions
+5
View File
@@ -15,6 +15,7 @@ import { emptyArtifact, type Artifact, type Fingerprint, type Proposal, type Pro
import { resolve, shift, type OffsetRange } from "./anchorer";
import { addProposal, proposalBody, removeProposal } from "./proposalModel";
import type { AttributionController } from "./attributionController";
import type { VersionGuard } from "./versionGuard";
/** Test-facing snapshot of what is currently rendered for a document. */
export interface RenderedProposal {
@@ -53,6 +54,7 @@ export class ProposalController implements vscode.Disposable {
private readonly store: CoauthorStore,
private readonly attribution: AttributionController,
private readonly rootDir: string,
private readonly guard: VersionGuard,
) {
// No commentingRangeProvider: humans never open proposal threads by hand —
// proposals are born of machine turns only (INV-12 keeps decisions human).
@@ -103,6 +105,7 @@ export class ProposalController implements vscode.Disposable {
opts?: { turnId?: string; instruction?: string },
): Promise<string | undefined> {
if (!this.isTracked(document)) return undefined;
if (this.guard.isReadOnly(this.docPathOf(document.uri))) return undefined;
const docPath = this.docPathOf(document.uri);
let proposalId: string | undefined;
this.store.update(docPath, (a) => {
@@ -137,6 +140,7 @@ export class ProposalController implements vscode.Disposable {
}
private async accept(state: DocState, proposal: Proposal): Promise<boolean> {
if (this.guard.isReadOnly(state.docPath)) return false;
const document = this.openDoc(state);
if (!document) return false;
// INV-11: the fingerprint-guard — exact re-resolve at decision time.
@@ -167,6 +171,7 @@ export class ProposalController implements vscode.Disposable {
}
private reject(state: DocState, proposal: Proposal): void {
if (this.guard.isReadOnly(state.docPath)) return;
this.store.update(state.docPath, (a) => removeProposal(a, proposal.id));
const document = this.openDoc(state);
if (document) this.renderAll(document);