mirror of
https://github.com/morluto/rea.git
synced 2026-10-26 17:35:11 +00:00
fix(metadata): stop schema changes churning generated skill evidence (#1075)
This commit is contained in:
1 parent
e2f03786f4
commit
d52f66d62a
11 files changed
+98
-18
No files matched your search
@@ -76,6 +76,15 @@ the same configuration. Pre-commit formats and lints staged files; pre-push runs
|
||||
`check:fast`.
|
||||
`docs:check` checks committed generated metadata. `docs:generate` regenerates
|
||||
those files, and the docs CI lane checks them.
|
||||
The committed product catalog contains documented facts and their provider
|
||||
identity, rather than full runtime schema hashes. The managed skill contains
|
||||
instructions and inventory metadata; doctor compares its installed files with
|
||||
the canonical bundle. Schema-only fixes should not change these files or the
|
||||
skill commitment in the conformance manifest. Runtime schema identity remains
|
||||
available through doctor and `binary_session`. If a branch conflicts only in
|
||||
these generated files, merge the source changes first, then run
|
||||
`npm run docs:generate` to regenerate from the merged sources; keep hand-written
|
||||
skill instructions from both branches.
|
||||
Real-provider execution remains uncached; deterministic builds use Turbo.
|
||||
|
||||
Local `npm test` runs every deterministic Vitest project without coverage or
|
||||
|
||||
@@ -713,9 +713,6 @@
|
||||
"mcp_prompts": 6
|
||||
},
|
||||
"digests": {
|
||||
"tools_sha256": "0ac2316e6bd007d273c60f69e132341fc06a3dcb6879408ecfdd3271f30a8230",
|
||||
"prompts_sha256": "ef2de8814390b340623c32001610aa2709d90bed51537b6e562ab3abf144ab4d",
|
||||
"combined_sha256": "20cc1432b29f11c20206cbaeacafab927dcfd68c81a82cf198ad058f54f06eac",
|
||||
"providers_sha256": "954b499c76514890062d2b6c936d73cb3a9c45412d522a081125a325e81cdec5"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"manifest_id": "ecm_bee7251323bddbe561003a45ddee6c43eb9e37839983dc89e438dca6d9e5fdec",
|
||||
"manifest_id": "ecm_0abe486e63d1ebdeec6487dfd19a5bd92d47a90d84e60df96926fa66602552ea",
|
||||
"verifier": {
|
||||
"id": "rea-managed-conformance",
|
||||
"version": "1"
|
||||
@@ -13,7 +13,7 @@
|
||||
"skill_digests": [
|
||||
{
|
||||
"skill_id": "reverse-engineer-anything",
|
||||
"sha256": "62f197bbd05ca97ff21cc69f45ccf776c152004f3c7f1bd589dfea0b8202db57"
|
||||
"sha256": "0e6130b292e3cd3fe1da214b2b3ca5eba8661c81a5bf64679b7ef29828393f29"
|
||||
}
|
||||
],
|
||||
"claims": [
|
||||
|
||||
@@ -19,13 +19,12 @@ const withCount = current.replace(
|
||||
/^\s{2}tool_count:\s*\d+\s*$/mu,
|
||||
` tool_count: ${String(CATALOG_IDENTITY.counts.mcp_tools)}`,
|
||||
);
|
||||
const digestLine = ` catalog_digest: "${CATALOG_IDENTITY.digests.combined_sha256}"`;
|
||||
const source = /^\s{2}catalog_digest:/mu.test(withCount)
|
||||
? withCount.replace(
|
||||
/^\s{2}catalog_digest:\s*"[a-f0-9]{64}"\s*$/mu,
|
||||
digestLine,
|
||||
)
|
||||
: withCount.replace(/^(\s{2}tool_count:\s*\d+\s*)$/mu, `$1\n${digestLine}`);
|
||||
// Skill identity follows the installed instruction bundle. Runtime schema identity
|
||||
// is still reported independently by doctor and binary_session.
|
||||
const source = withCount.replace(
|
||||
/^ {2}catalog_digest:[^\r\n]*(?:\r?\n|$)/mu,
|
||||
"",
|
||||
);
|
||||
|
||||
await ensureGeneratedFile({
|
||||
path,
|
||||
|
||||
@@ -58,7 +58,7 @@ export const createProductCatalog = async (root) => {
|
||||
runtime_catalog: {
|
||||
counts: sources.catalogIdentity.CATALOG_IDENTITY.counts,
|
||||
digests: {
|
||||
...sources.catalogIdentity.CATALOG_IDENTITY.digests,
|
||||
// Full runtime schema digests belong to server identity, not this facts projection.
|
||||
providers_sha256: providerCatalogDigest(providers),
|
||||
},
|
||||
},
|
||||
|
||||
@@ -4,7 +4,6 @@ description: Reverse engineer native, managed, Electron/JavaScript, packaged, fi
|
||||
metadata:
|
||||
version: "33"
|
||||
tool_count: 139
|
||||
catalog_digest: "20cc1432b29f11c20206cbaeacafab927dcfd68c81a82cf198ad058f54f06eac"
|
||||
---
|
||||
|
||||
# REA
|
||||
|
||||
@@ -26,7 +26,8 @@ export const createDoctorHostFixture = (
|
||||
Promise.resolve({
|
||||
version: PRODUCT_IDENTITY.skillVersion,
|
||||
toolCount: CATALOG_IDENTITY.counts.mcp_tools,
|
||||
catalogDigest: CATALOG_IDENTITY.digests.combined_sha256,
|
||||
catalogDigest: null,
|
||||
canonical: true,
|
||||
}),
|
||||
...overrides,
|
||||
});
|
||||
@@ -177,6 +177,7 @@ describe("doctor installation identity", () => {
|
||||
version: "10",
|
||||
toolCount: null,
|
||||
catalogDigest: null,
|
||||
canonical: false,
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
readLinuxDistribution,
|
||||
type LinuxDistribution,
|
||||
} from "./LinuxHopper.js";
|
||||
import { canonicalSkillNeedsInstall } from "./SetupSkill.js";
|
||||
import { CATALOG_IDENTITY } from "../catalogIdentity.js";
|
||||
import { PRODUCT_IDENTITY, SDK_IDENTITY } from "../identity.js";
|
||||
import {
|
||||
@@ -95,11 +96,13 @@ export interface DoctorHost {
|
||||
ilspyCmdVersion?(path: string): Promise<string | undefined>;
|
||||
}
|
||||
|
||||
/** Parsed identity committed by an installed REA skill. */
|
||||
/** Observed skill metadata and comparison with the packaged instruction bundle. */
|
||||
interface InstalledSkillIdentity {
|
||||
readonly version: string | null;
|
||||
readonly toolCount: number | null;
|
||||
readonly catalogDigest: string | null;
|
||||
/** Whether all managed instruction and reference files match this package. */
|
||||
readonly canonical: boolean;
|
||||
}
|
||||
|
||||
/** Structured result returned by the read-only doctor workflow. */
|
||||
@@ -139,6 +142,7 @@ interface DoctorIdentity {
|
||||
readonly skill: {
|
||||
readonly installed_version: string | null;
|
||||
readonly installed_tool_count: number | null;
|
||||
/** Legacy catalog digest observed in older bundles; current bundles omit it. */
|
||||
readonly installed_catalog_digest: string | null;
|
||||
readonly state: "aligned" | "stale" | "missing";
|
||||
readonly remediation: string | null;
|
||||
@@ -253,7 +257,7 @@ const skillIdentityAligned = (
|
||||
): boolean =>
|
||||
identity?.version === PRODUCT_IDENTITY.skillVersion &&
|
||||
identity.toolCount === CATALOG_IDENTITY.counts.mcp_tools &&
|
||||
identity.catalogDigest === CATALOG_IDENTITY.digests.combined_sha256;
|
||||
identity.canonical;
|
||||
|
||||
const skillIdentityCheck = (
|
||||
identity: InstalledSkillIdentity | undefined,
|
||||
@@ -507,6 +511,7 @@ const installedSkillIdentity = async (
|
||||
const content = await readInstalledSkill(home);
|
||||
const countText = /^\s{2}tool_count:\s*(\d+)\s*$/mu.exec(content)?.[1];
|
||||
return {
|
||||
canonical: !(await canonicalSkillNeedsInstall(home)),
|
||||
version: /^\s{2}version:\s*"([^"]+)"\s*$/mu.exec(content)?.[1] ?? null,
|
||||
toolCount:
|
||||
countText === undefined ? null : Number.parseInt(countText, 10),
|
||||
|
||||
@@ -195,6 +195,11 @@ describe("canonical product catalog", () => {
|
||||
"project_managed_application_graph",
|
||||
"verify_managed_native_boundaries",
|
||||
]);
|
||||
// Runtime schema commitments must not fan out into checked-in documentation.
|
||||
// Provider commitments cover the facts actually present in this projection.
|
||||
expect(Object.keys(catalog.runtime_catalog.digests)).toEqual([
|
||||
"providers_sha256",
|
||||
]);
|
||||
expect(catalog.runtime_catalog.digests.providers_sha256).toBe(
|
||||
providerCatalogDigest(catalog.providers),
|
||||
);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
||||
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
||||
import { dirname, join } from "node:path";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
||||
@@ -7,6 +7,11 @@ import {
|
||||
canonicalSkillNeedsInstall,
|
||||
installCanonicalSkill,
|
||||
} from "../../../src/application/SetupSkill.js";
|
||||
import {
|
||||
runDoctor,
|
||||
systemDoctorHost,
|
||||
} from "../../../src/application/Doctor.js";
|
||||
import { createDoctorHostFixture } from "../../../src/application/Doctor.fixture.js";
|
||||
import { TOOL_CONTRACTS } from "../../../src/contracts/toolContracts.js";
|
||||
import { PRODUCT_IDENTITY } from "../../../src/identity.js";
|
||||
import { skillReferenceIssues } from "../../../scripts/lib/docs-facts.mjs";
|
||||
@@ -98,3 +103,62 @@ describe("canonical skill transaction", () => {
|
||||
expect(await skillReferenceIssues(dirname(destination))).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
it("doctor verifies installed instructions and references even when metadata is current", async () => {
|
||||
const home = await createTestTempDirectory("rea-skill-doctor-");
|
||||
const destination = join(
|
||||
home,
|
||||
".agents/skills/reverse-engineer-anything/SKILL.md",
|
||||
);
|
||||
const reference = join(
|
||||
dirname(destination),
|
||||
"references/evidence-workflows.md",
|
||||
);
|
||||
const system = systemDoctorHost({
|
||||
environment: { HOME: home, USERPROFILE: home },
|
||||
});
|
||||
const host = createDoctorHostFixture({
|
||||
installedSkillIdentity: () =>
|
||||
system.installedSkillIdentity?.() ?? Promise.resolve(undefined),
|
||||
});
|
||||
expect((await runDoctor(undefined, host)).identity?.skill.state).toBe(
|
||||
"missing",
|
||||
);
|
||||
expect(await installCanonicalSkill(home)).toBe("installed");
|
||||
expect((await runDoctor(undefined, host)).identity?.skill).toMatchObject({
|
||||
state: "aligned",
|
||||
installed_catalog_digest: null,
|
||||
});
|
||||
const legacyDigest = "0".repeat(64);
|
||||
await writeFile(
|
||||
destination,
|
||||
(await readFile(destination, "utf8")).replace(
|
||||
` tool_count: ${String(TOOL_CONTRACTS.length)}`,
|
||||
` tool_count: ${String(TOOL_CONTRACTS.length)}\n catalog_digest: "${legacyDigest}"`,
|
||||
),
|
||||
);
|
||||
expect((await runDoctor(undefined, host)).identity?.skill).toMatchObject({
|
||||
state: "stale",
|
||||
installed_catalog_digest: legacyDigest,
|
||||
});
|
||||
expect(await installCanonicalSkill(home)).toBe("installed");
|
||||
for (const path of [destination, reference]) {
|
||||
const canonical = await readFile(path, "utf8");
|
||||
await writeFile(path, `${canonical}\nLocally changed instructions.\n`);
|
||||
expect((await runDoctor(undefined, host)).identity?.skill.state).toBe(
|
||||
"stale",
|
||||
);
|
||||
expect(await installCanonicalSkill(home)).toBe("installed");
|
||||
expect((await runDoctor(undefined, host)).identity?.skill.state).toBe(
|
||||
"aligned",
|
||||
);
|
||||
}
|
||||
await rm(reference);
|
||||
expect((await runDoctor(undefined, host)).identity?.skill.state).toBe(
|
||||
"stale",
|
||||
);
|
||||
expect(await installCanonicalSkill(home)).toBe("installed");
|
||||
expect((await runDoctor(undefined, host)).identity?.skill.state).toBe(
|
||||
"aligned",
|
||||
);
|
||||
});
|
||||
Reference in new issue
Block a user