Publish quixos-protocol from e4075003e669be0e307ec8c0585cccb1bd08276a
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"version": 1,
|
"version": 1,
|
||||||
"sourceRepo": "https://gitea-external.egads.tutti.syntaxblitz.net/quixos/quixos",
|
"sourceRepo": "https://gitea-external.egads.tutti.syntaxblitz.net/quixos/quixos",
|
||||||
"sourceCommit": "932fe7714761fb8b5db4bd6401357cdff762a428",
|
"sourceCommit": "e4075003e669be0e307ec8c0585cccb1bd08276a",
|
||||||
"sourcePath": "quixos-protocol",
|
"sourcePath": "quixos-protocol",
|
||||||
"exportName": "quixos-protocol",
|
"exportName": "quixos-protocol",
|
||||||
"mirrorRemote": "https://gitea-external.egads.tutti.syntaxblitz.net/quixos/quixos-protocol.git"
|
"mirrorRemote": "https://gitea-external.egads.tutti.syntaxblitz.net/quixos/quixos-protocol.git"
|
||||||
|
|||||||
@@ -15,7 +15,12 @@ import {
|
|||||||
} from "./assembly.js";
|
} from "./assembly.js";
|
||||||
import { createGitCapabilityResolver } from "./git-resolver.js";
|
import { createGitCapabilityResolver } from "./git-resolver.js";
|
||||||
import { snapshotCommit, buildImmutableCandidate } from "./checked-build.js";
|
import { snapshotCommit, buildImmutableCandidate } from "./checked-build.js";
|
||||||
import { planEvolution, type WorkspaceRevision, type EvolutionReview } from "../capability-model/index.js";
|
import {
|
||||||
|
planEvolution,
|
||||||
|
reviewSemanticMajors,
|
||||||
|
type WorkspaceRevision,
|
||||||
|
type EvolutionReview,
|
||||||
|
} from "../capability-model/index.js";
|
||||||
import { withFileLock } from "./file-lock.js";
|
import { withFileLock } from "./file-lock.js";
|
||||||
const execFile = promisify(callback);
|
const execFile = promisify(callback);
|
||||||
type Source = { repository: string; commit: string };
|
type Source = { repository: string; commit: string };
|
||||||
@@ -187,6 +192,34 @@ export type UpgradeEffects = {
|
|||||||
snapshot(root: string): Promise<string>;
|
snapshot(root: string): Promise<string>;
|
||||||
publish(root: string, commit: string): Promise<void>;
|
publish(root: string, commit: string): Promise<void>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Review a successfully built immutable candidate using the same boundary as
|
||||||
|
* ordinary authoring checks. Execution storage compatibility is checked against
|
||||||
|
* the current graph at activation; the legacy evolution model is not its schema.
|
||||||
|
*/
|
||||||
|
export async function reviewUpgradeCandidate({
|
||||||
|
artifact,
|
||||||
|
baseline,
|
||||||
|
candidate,
|
||||||
|
reviews = [],
|
||||||
|
}: {
|
||||||
|
artifact: string;
|
||||||
|
baseline: WorkspaceRevision | null;
|
||||||
|
candidate: WorkspaceRevision;
|
||||||
|
reviews?: EvolutionReview[];
|
||||||
|
}) {
|
||||||
|
let execution = false;
|
||||||
|
try {
|
||||||
|
await fs.access(path.join(artifact, "execution-plan.json"));
|
||||||
|
execution = true;
|
||||||
|
} catch (error) {
|
||||||
|
if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
|
||||||
|
}
|
||||||
|
return execution
|
||||||
|
? { filename: "semantic-review.json", review: reviewSemanticMajors(baseline, candidate, reviews) }
|
||||||
|
: { filename: "evolution.json", review: planEvolution(baseline, candidate, { reviews }) };
|
||||||
|
}
|
||||||
|
|
||||||
const effects: UpgradeEffects = {
|
const effects: UpgradeEffects = {
|
||||||
async check(node, root, output, spec) {
|
async check(node, root, output, spec) {
|
||||||
if (node.kind === "workspace" && !spec.baseline && !spec.bootstrap)
|
if (node.kind === "workspace" && !spec.baseline && !spec.bootstrap)
|
||||||
@@ -206,10 +239,15 @@ const effects: UpgradeEffects = {
|
|||||||
? (JSON.parse(await fs.readFile(spec.baseline, "utf8")) as WorkspaceRevision)
|
? (JSON.parse(await fs.readFile(spec.baseline, "utf8")) as WorkspaceRevision)
|
||||||
: null;
|
: null;
|
||||||
const reviews = spec.reviews ? (JSON.parse(await fs.readFile(spec.reviews, "utf8")) as EvolutionReview[]) : [];
|
const reviews = spec.reviews ? (JSON.parse(await fs.readFile(spec.reviews, "utf8")) as EvolutionReview[]) : [];
|
||||||
const evolution = planEvolution(baseline, JSON.parse(candidate), { reviews });
|
const { filename, review } = await reviewUpgradeCandidate({
|
||||||
await fs.writeFile(path.join(output, "evolution.json"), JSON.stringify(evolution, null, 2));
|
artifact,
|
||||||
if (evolution.blockers.length)
|
baseline,
|
||||||
throw new Error(`Refactor required in ${node.directory}: ${evolution.blockers.join("; ")}`);
|
candidate: JSON.parse(candidate),
|
||||||
|
reviews,
|
||||||
|
});
|
||||||
|
await fs.writeFile(path.join(output, filename), JSON.stringify(review, null, 2));
|
||||||
|
if (review.blockers.length)
|
||||||
|
throw new Error(`Refactor required in ${node.directory}: ${review.blockers.join("; ")}`);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async snapshot(root) {
|
async snapshot(root) {
|
||||||
|
|||||||
@@ -5,9 +5,89 @@ import os from "node:os";
|
|||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { promisify } from "node:util";
|
import { promisify } from "node:util";
|
||||||
import { execFile as callback } from "node:child_process";
|
import { execFile as callback } from "node:child_process";
|
||||||
import { planPinUpgrades, applyPinUpgrades, type UpgradeEffects } from "../src/capability-language/pin-upgrades.js";
|
import {
|
||||||
|
planPinUpgrades,
|
||||||
|
applyPinUpgrades,
|
||||||
|
reviewUpgradeCandidate,
|
||||||
|
type UpgradeEffects,
|
||||||
|
} from "../src/capability-language/pin-upgrades.js";
|
||||||
|
import { capabilityId } from "../src/capability-model/index.js";
|
||||||
|
import { makeValidCapabilityWorkspace } from "./fixtures/capability-model.js";
|
||||||
const execFile = promisify(callback);
|
const execFile = promisify(callback);
|
||||||
|
|
||||||
|
test("execution pin upgrades accept unchanged shared attachments without legacy migration planning", async (context) => {
|
||||||
|
const artifact = await fs.mkdtemp(path.join(os.tmpdir(), "qx-upgrade-execution-review-"));
|
||||||
|
context.after(() => fs.rm(artifact, { recursive: true, force: true }));
|
||||||
|
await fs.writeFile(path.join(artifact, "execution-plan.json"), "{}");
|
||||||
|
const baseline = makeValidCapabilityWorkspace();
|
||||||
|
assert.ok(baseline.sharedAttachments.length);
|
||||||
|
const candidate = structuredClone(baseline);
|
||||||
|
candidate.sourceRootCommit = "f".repeat(40);
|
||||||
|
const { filename, review } = await reviewUpgradeCandidate({ artifact, baseline, candidate });
|
||||||
|
assert.equal(filename, "semantic-review.json");
|
||||||
|
assert.equal(review.checkerVersion, "quixos-semantic-review-v1");
|
||||||
|
assert.deepEqual(review.blockers, []);
|
||||||
|
assert.equal("migrationRequired" in review, false);
|
||||||
|
assert.equal("runtimeActions" in review, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("execution pin upgrades require exact semantic-major receipts and reject foreign workspaces", async (context) => {
|
||||||
|
const artifact = await fs.mkdtemp(path.join(os.tmpdir(), "qx-upgrade-semantic-review-"));
|
||||||
|
context.after(() => fs.rm(artifact, { recursive: true, force: true }));
|
||||||
|
await fs.writeFile(path.join(artifact, "execution-plan.json"), "{}");
|
||||||
|
const baseline = makeValidCapabilityWorkspace();
|
||||||
|
baseline.conformances[0]!.id = capabilityId.conformance("conformance:project:named");
|
||||||
|
const candidate = structuredClone(baseline);
|
||||||
|
candidate.conformances[0]!.semanticMajor = 2;
|
||||||
|
const { review } = await reviewUpgradeCandidate({ artifact, baseline, candidate });
|
||||||
|
assert.equal(review.reviews.length, 1);
|
||||||
|
assert.equal(review.reviews[0]!.accepted, false);
|
||||||
|
assert.match(review.blockers[0]!, /Semantic-major review required/);
|
||||||
|
const receipt = {
|
||||||
|
requirementDigest: review.reviews[0]!.requirementDigest,
|
||||||
|
decision: "accepted-unchanged" as const,
|
||||||
|
rationale: "Reviewed the consumer against the provider's unchanged label behavior",
|
||||||
|
agentId: "upgrade-fixture-agent",
|
||||||
|
};
|
||||||
|
assert.deepEqual(
|
||||||
|
(await reviewUpgradeCandidate({ artifact, baseline, candidate, reviews: [receipt] })).review.blockers,
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
candidate.packageImports[0]!.source.commit = "f".repeat(40);
|
||||||
|
assert.equal(
|
||||||
|
(await reviewUpgradeCandidate({ artifact, baseline, candidate, reviews: [receipt] })).review.reviews[0]!.accepted,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
candidate.workspaceId = capabilityId.workspace("workspace:foreign");
|
||||||
|
await assert.rejects(() => reviewUpgradeCandidate({ artifact, baseline, candidate }), /different workspace/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("nonexecution pin upgrades retain their evolution checks", async (context) => {
|
||||||
|
const artifact = await fs.mkdtemp(path.join(os.tmpdir(), "qx-upgrade-evolution-review-"));
|
||||||
|
context.after(() => fs.rm(artifact, { recursive: true, force: true }));
|
||||||
|
const baseline = makeValidCapabilityWorkspace();
|
||||||
|
const { filename, review } = await reviewUpgradeCandidate({
|
||||||
|
artifact,
|
||||||
|
baseline,
|
||||||
|
candidate: structuredClone(baseline),
|
||||||
|
});
|
||||||
|
assert.equal(filename, "evolution.json");
|
||||||
|
assert.equal(review.checkerVersion, "quixos-evolution-v1");
|
||||||
|
assert.ok(review.blockers.some((message) => message.includes("legacy workspace-shared attachments")));
|
||||||
|
assert.equal("migrationRequired" in review, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("unexpected artifact access failures do not select nonexecution checks", async (context) => {
|
||||||
|
const directory = await fs.mkdtemp(path.join(os.tmpdir(), "qx-upgrade-review-failure-"));
|
||||||
|
context.after(() => fs.rm(directory, { recursive: true, force: true }));
|
||||||
|
const artifact = path.join(directory, "not-a-directory");
|
||||||
|
await fs.writeFile(artifact, "not an artifact");
|
||||||
|
const candidate = makeValidCapabilityWorkspace();
|
||||||
|
await assert.rejects(() => reviewUpgradeCandidate({ artifact, baseline: candidate, candidate }), {
|
||||||
|
code: "ENOTDIR",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
test(
|
test(
|
||||||
"real jj snapshots and immutable Git publication propagate a changed interface into the root",
|
"real jj snapshots and immutable Git publication propagate a changed interface into the root",
|
||||||
{ skip: !process.env.QX_CHECK_GENERATOR },
|
{ skip: !process.env.QX_CHECK_GENERATOR },
|
||||||
|
|||||||
Reference in New Issue
Block a user