mirror of
https://github.com/mvschwarz/openrig.git
synced 2026-11-07 05:56:21 -10:00
Scaffolded mission manifests (and mission.yaml files without an explicit
metadata block) omit the top-level metadata key. Previously,
readMissionReadiness called mapping(doc.metadata, 'mission metadata')
unconditionally, throwing a JudgmentError ('mission metadata: expected a
mapping') when doc.metadata was undefined. This error was caught at the
top level of readMissionReadiness, causing it to abort composition slice
resolution, leave slices empty ([]), and set state to 'unknown'.
Safely default metadata to {} when doc.metadata is nullish, while
retaining strict mapping validation when doc.metadata is specified. Also
add unit test coverage for missions omitting metadata as well as
malformed metadata negative control.
Fixes #72.
This commit is contained in:
1 parent
5fead45078
commit
4fcf2910bb
2 files changed
+33
-1
No files matched your search
@@ -211,6 +211,15 @@ export interface JudgeInput {
|
||||
subject?: Judgment["subject"]; expectedEvidence?: Evidence[]; reason: string; expectedRevision: string;
|
||||
expectedPrevious: string | null; operationId?: string; replace?: boolean;
|
||||
}
|
||||
/**
|
||||
* Records a verifiable proof judgment for a slice item into the judgments ledger.
|
||||
*
|
||||
* @param missionsRoot - Workspace root path for resolving scopes.
|
||||
* @param input - Judgment details including scope, item, verdict, and evidence.
|
||||
* @param actor - Identifier of the judging actor authorized under the proof policy.
|
||||
* @param provenance - Attestation provenance string recorded in the ledger.
|
||||
* @returns Recorded judgment receipt, current slice readiness, and replay status.
|
||||
*/
|
||||
export function recordJudgment(missionsRoot: string, input: JudgeInput, actor: string, provenance: string): { judgment: Judgment; readiness: ScopeReadiness; replayed: boolean } {
|
||||
const dir = resolveProofScope(missionsRoot, input.scope), root = workspaceOf(dir, proofFs);
|
||||
if (path.basename(path.dirname(dir)) !== "slices") throw new JudgmentError("slice_required", "Item judgments belong to a slice; higher outcome judgments remain workflow decisions");
|
||||
@@ -264,13 +273,20 @@ export function recordJudgment(missionsRoot: string, input: JudgeInput, actor: s
|
||||
return { judgment, readiness: readSliceReadiness(dir), replayed: false };
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluates readiness for a mission by reading its composition and resolving member slices.
|
||||
*
|
||||
* @param missionDir - Path to the mission directory containing mission.yaml.
|
||||
* @param readPolicy - Reader function for determining applicable proof policy.
|
||||
* @returns Evaluated mission readiness, including revision, state, slices, and any issues.
|
||||
*/
|
||||
export function readMissionReadiness(missionDir: string, readPolicy: ProofPolicyRead = policyOf): MissionReadiness {
|
||||
const issues: string[] = [], slices: MissionReadiness["slices"] = [];
|
||||
let historicalStatus: string | null = null;
|
||||
try {
|
||||
const doc = manifest(proofFs, path.join(missionDir, "mission.yaml"));
|
||||
if (!doc) return { revision: hash("legacy"), state: "legacy", slices, issues, historicalStatus };
|
||||
const metadata = mapping(doc.metadata, "mission metadata");
|
||||
const metadata = doc.metadata != null ? mapping(doc.metadata, "mission metadata") : {};
|
||||
historicalStatus = typeof metadata.status === "string" ? metadata.status : null;
|
||||
const members = validateMissionComposition(doc, path.join(missionDir, "mission.yaml")).filter(m => m.active);
|
||||
for (const member of members) {
|
||||
|
||||
@@ -205,6 +205,22 @@ describe("retained authority negative controls", () => {
|
||||
f.write(join(f.beta, "proof/judgments/00000001.md"), fs.readFileSync(join(f.alpha, "proof/judgments/00000001.md"), "utf8"));
|
||||
expect(readSliceReadiness(f.beta).items[0]!.state).toBe("unknown");
|
||||
});
|
||||
/**
|
||||
* Verifies that mission readiness resolves composition slices when metadata is omitted (#72),
|
||||
* while rejecting malformed metadata values.
|
||||
*/
|
||||
it("tolerates mission without metadata and resolves composition slices (#72)", () => {
|
||||
const f = fixture();
|
||||
const members = ["01-alpha", "02-beta"].map((s, i) => ({ ref: `slices/${s}/slice.yaml`, order: i + 1, active: true }));
|
||||
f.write(join(f.mission, "mission.yaml"), { kind: "mission", composition: { slices: members } });
|
||||
const readiness = readMissionReadiness(f.mission);
|
||||
expect(readiness.issues).toEqual([]);
|
||||
expect(readiness.slices.map(s => s.id)).toEqual(["01-alpha", "02-beta"]);
|
||||
expect(readiness.historicalStatus).toBeNull();
|
||||
f.write(join(f.mission, "mission.yaml"), { kind: "mission", metadata: "not-a-mapping", composition: { slices: members } });
|
||||
const invalid = readMissionReadiness(f.mission);
|
||||
expect(invalid.issues).toContain("mission metadata: expected a mapping");
|
||||
});
|
||||
it("pushes changed source truth, keeps sibling basis, and ignores unchanged source bytes", async () => {
|
||||
const f = fixture(); f.judge(); const sibling = readSliceReadiness(f.beta).revision;
|
||||
const events: Array<{ type: string; revision: string }> = []; let invalidated = 0;
|
||||
|
||||
Reference in new issue
Block a user