mirror of
https://github.com/mvschwarz/openrig.git
synced 2026-11-09 03:39:51 -10:00
fix(slice-05): v2 inspect provenance test + camelCase contract consistency (guard B1 repair on 6b81d3b6) (OPR.0.3.2.5)
Repairs guard BLOCKING-CONCERN B1 on qitem-20260518194024-2fbe1454 /6b81d3b6: the v2 inspect provenance projection line at routes/bundles.ts was claimed but not regression-covered — the existing v2 inspect test sent no provenance in its create request, so removing the projection still passed 16/16. Discriminator verified as dead w.r.t. tests. Also resolves the API-contract inconsistency guard flagged: v1 inspect surfaces camelCase provenance via the normalizer (from82d39715), but v2 inspect was returning raw snake_case. One /api/bundles/inspect endpoint should return one shape regardless of schema version. Fixes: - Export normalizeProvenanceBlock from domain/bundle-types.ts so it can be shared between the v1 normalizer pipeline and the v2 inspect projection. - Route v2 inspect projection now wraps rawParsed.provenance through normalizeProvenanceBlock, returning normalized camelCase (matches v1 contract). Field stays optional; undefined when absent. - New regression test in test/bundle-routes.test.ts: pod-aware (v2) create with provenance -> inspect, asserts response.manifest.provenance contains camelCase keys (sourceHost/authorSession/cliVersion/notes/ daemonVersion) and explicitly asserts snake_case keys are NOT present (negative discriminator on the contract). Verified: - 81/81 daemon bundle suite green post-fix (was 80; +1 new v2 round-trip) - Discriminator confirmed: removing the v2 projection line makes the new test fail (1/17 fail in bundle-routes); restoring it returns 17/17 PASS - 12/12 CLI bundle suite unchanged (no CLI files touched in this fix) - scope-and-versioning sections 1 and 3 coherence re-audit clean - audit-every-layer unchanged: helpers still function-level reads only Diff: 3 files, +86 / -6 lines. No CLI files modified. Branch chain since main:82d39715(CLEAR) ->3b7432e4(CLEAR) ->6b81d3b6(BLOCKING-CONCERN B1; this commit repairs) -> new tip. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
This commit is contained in:
1 parent
6b81d3b6d5
commit
77bed48ba6
3 files changed
+86
-6
No files matched your search
@@ -67,8 +67,14 @@ function provenanceToYamlRecord(p: BundleProvenance): Record<string, unknown> {
|
||||
return out;
|
||||
}
|
||||
|
||||
/** Normalize raw provenance to typed BundleProvenance. Returns undefined when absent or empty. */
|
||||
function normalizeProvenanceBlock(raw: unknown): BundleProvenance | undefined {
|
||||
/**
|
||||
* Normalize raw snake_case provenance (as parsed from YAML or received from
|
||||
* a request body) to typed camelCase BundleProvenance. Returns undefined when
|
||||
* absent or empty. Exported so both the v1 normalizer pipeline and the v2
|
||||
* inspect-route projection produce identical camelCase shapes — the
|
||||
* /api/bundles/inspect contract is one shape regardless of schema version.
|
||||
*/
|
||||
export function normalizeProvenanceBlock(raw: unknown): BundleProvenance | undefined {
|
||||
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return undefined;
|
||||
const p = raw as Record<string, unknown>;
|
||||
const result: BundleProvenance = {};
|
||||
|
||||
@@ -14,7 +14,7 @@ import { LegacyRigSpecCodec } from "../domain/rigspec-codec.js";
|
||||
import { LegacyRigSpecSchema } from "../domain/rigspec-schema.js";
|
||||
import { RigSpecCodec } from "../domain/rigspec-codec.js";
|
||||
import { RigSpecSchema } from "../domain/rigspec-schema.js";
|
||||
import { parseLegacyBundleManifest as parseBundleManifest, normalizeLegacyBundleManifest as normalizeBundleManifest, serializePodBundleManifest, parsePodBundleManifest, validatePodBundleManifest } from "../domain/bundle-types.js";
|
||||
import { parseLegacyBundleManifest as parseBundleManifest, normalizeLegacyBundleManifest as normalizeBundleManifest, serializePodBundleManifest, parsePodBundleManifest, validatePodBundleManifest, normalizeProvenanceBlock } from "../domain/bundle-types.js";
|
||||
import type { PodBundleManifest, BundleProvenance } from "../domain/bundle-types.js";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
@@ -286,9 +286,11 @@ bundleRoutes.post("/inspect", async (c) => {
|
||||
algorithm: integritySection.algorithm ?? "sha256",
|
||||
files: integritySection.files ?? {},
|
||||
} : undefined,
|
||||
// Item 1 / slice-05: surface provenance raw from manifest so JSON-path
|
||||
// consumers (UI inspector, jq pipelines) see it. Field is optional.
|
||||
provenance: rawParsed["provenance"] as Record<string, unknown> | undefined,
|
||||
// Item 1 / slice-05: surface provenance in normalized camelCase so the
|
||||
// /api/bundles/inspect contract is one shape regardless of v1 vs v2
|
||||
// (v1 path normalizes through normalizeLegacyBundleManifest below).
|
||||
// Field is optional; undefined when bundle has no provenance.
|
||||
provenance: normalizeProvenanceBlock(rawParsed["provenance"]),
|
||||
};
|
||||
const integrityCompat = integritySection ? {
|
||||
schemaVersion: 2,
|
||||
|
||||
@@ -487,6 +487,78 @@ describe("Bundle API routes", () => {
|
||||
expect(body.digestValid).toBe(true);
|
||||
});
|
||||
|
||||
// Item 1 / slice-05 / guard B1 repair: pod-aware (v2) create -> inspect provenance round-trip.
|
||||
// Asserts the inspect response surfaces provenance in normalized camelCase,
|
||||
// matching the v1 contract. Discriminator: removing the v2 inspect projection
|
||||
// line in routes/bundles.ts must make this test fail.
|
||||
it("POST /api/bundles/inspect with v2 bundle surfaces provenance in camelCase (create -> inspect round-trip)", async () => {
|
||||
const agentsDir = path.join(tmpDir, "agents", "impl");
|
||||
fs.mkdirSync(agentsDir, { recursive: true });
|
||||
fs.writeFileSync(path.join(agentsDir, "agent.yaml"), [
|
||||
'name: impl-agent',
|
||||
'version: "1.0.0"',
|
||||
'resources:',
|
||||
' skills: []',
|
||||
'profiles:',
|
||||
' default:',
|
||||
' uses:',
|
||||
' skills: []',
|
||||
].join("\n"));
|
||||
|
||||
const specPath = path.join(tmpDir, "rig.yaml");
|
||||
fs.writeFileSync(specPath, [
|
||||
'version: "0.2"',
|
||||
'name: v2-prov-test',
|
||||
'pods:',
|
||||
' - id: dev',
|
||||
' label: Dev',
|
||||
' members:',
|
||||
' - id: impl',
|
||||
' agent_ref: "local:agents/impl"',
|
||||
' profile: default',
|
||||
' runtime: claude-code',
|
||||
' cwd: .',
|
||||
' edges: []',
|
||||
'edges: []',
|
||||
].join("\n"));
|
||||
|
||||
const bundlePath = path.join(tmpDir, "v2-prov.rigbundle");
|
||||
const createRes = await app.request("/api/bundles/create", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
specPath, bundleName: "v2-prov", bundleVersion: "0.1.0", outputPath: bundlePath,
|
||||
provenance: {
|
||||
sourceHost: "v2-route-test-host",
|
||||
authorSession: "velocity-driver@openrig-velocity",
|
||||
cliVersion: "0.3.2",
|
||||
notes: "v2 route round-trip fixture",
|
||||
},
|
||||
}),
|
||||
});
|
||||
expect(createRes.status).toBe(201);
|
||||
|
||||
const inspectRes = await app.request("/api/bundles/inspect", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ bundlePath }),
|
||||
});
|
||||
expect(inspectRes.status).toBe(200);
|
||||
const inspectBody = await inspectRes.json();
|
||||
expect(inspectBody.manifest.schemaVersion).toBe(2);
|
||||
// The contract: provenance returned in normalized camelCase (matches v1)
|
||||
expect(inspectBody.manifest.provenance).toBeDefined();
|
||||
expect(inspectBody.manifest.provenance.sourceHost).toBe("v2-route-test-host");
|
||||
expect(inspectBody.manifest.provenance.authorSession).toBe("velocity-driver@openrig-velocity");
|
||||
expect(inspectBody.manifest.provenance.cliVersion).toBe("0.3.2");
|
||||
expect(inspectBody.manifest.provenance.notes).toBe("v2 route round-trip fixture");
|
||||
expect(typeof inspectBody.manifest.provenance.daemonVersion).toBe("string");
|
||||
expect(inspectBody.manifest.provenance.daemonVersion.length).toBeGreaterThan(0);
|
||||
// Negative — snake_case keys must NOT be present (camelCase contract)
|
||||
expect(inspectBody.manifest.provenance.source_host).toBeUndefined();
|
||||
expect(inspectBody.manifest.provenance.author_session).toBeUndefined();
|
||||
});
|
||||
|
||||
// T11: Install concurrency lock
|
||||
it("concurrent bundle install returns 409", async () => {
|
||||
// Acquire lock manually
|
||||
|
||||
Reference in new issue
Block a user