@dailephd/my-dev-kit-lab 0.4.4 → 0.4.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +468 -0
- package/README.md +3 -1
- package/dist/scripts/report-context-integrity-smoke.js +70 -0
- package/dist/src/evaluation/ecosystemFixtures/index.js +3 -0
- package/dist/src/evaluation/ecosystemFixtures/loadContextIntegrityFixture.js +102 -0
- package/dist/src/evaluation/ecosystemFixtures/manifestTypes.js +1 -0
- package/dist/src/evaluation/ecosystemFixtures/validateManifest.js +82 -0
- package/dist/src/evaluation/ecosystemFixtures/verifyFixtureHashes.js +45 -0
- package/dist/src/evaluation/stageContextMetrics/calculateAllocationMetrics.js +169 -0
- package/dist/src/evaluation/stageContextMetrics/calculateConditionCoverageMetrics.js +74 -0
- package/dist/src/evaluation/stageContextMetrics/calculateProducerConditionAgreement.js +113 -0
- package/dist/src/evaluation/stageContextMetrics/calculateReadinessAgreement.js +60 -0
- package/dist/src/evaluation/stageContextMetrics/calculateRunIntegrityAgreement.js +309 -0
- package/dist/src/evaluation/stageContextMetrics/calculateSupplementalRawAgreement.js +12 -2
- package/dist/src/evaluation/stageContextMetrics/calculateTruncationClassification.js +100 -0
- package/dist/src/evaluation/stageContextMetrics/evaluateProducerReadinessBridge.js +76 -5
- package/dist/src/evaluation/stageContextMetrics/index.js +4 -0
- package/dist/src/evaluation/stageContextMetrics/runIntegrityAgreementTypes.js +1 -0
- package/dist/src/evaluation/stageContextSelectors/contextArtifactConsistency.js +7 -0
- package/dist/src/evaluation/stageContextSelectors/index.js +1 -0
- package/dist/src/evaluation/stageContextSelectors/orchestratorRunIntegritySelectors.js +64 -0
- package/dist/src/evaluation/upstreamArtifacts/index.js +2 -0
- package/dist/src/evaluation/upstreamArtifacts/orchestratorRunIntegrityV1.js +33 -0
- package/dist/src/evaluation/upstreamArtifacts/readOrchestratorRunIntegrityV1.js +9 -0
- package/dist/src/evaluation/upstreamArtifacts/validateMyDevKitContextCapsuleV1.js +59 -2
- package/dist/src/evaluation/upstreamArtifacts/validateMyDevKitRetrievalAuditRecordV1.js +6 -1
- package/dist/src/evaluation/upstreamArtifacts/validateOrchestratorRunIntegrityV1.js +177 -0
- package/dist/src/report/experiments/buildContextIntegrityReport.js +204 -0
- package/dist/src/report/experiments/contextIntegrityReportModel.js +1 -0
- package/dist/src/report/experiments/index.js +5 -0
- package/dist/src/report/experiments/renderContextIntegrityHtml.js +98 -0
- package/dist/src/report/experiments/renderContextIntegrityJsonReport.js +3 -0
- package/dist/src/report/experiments/renderContextIntegrityText.js +111 -0
- package/dist/src/securityValidation/packageChecks/requiredPackageContents.js +33 -0
- package/dist/src/securityValidation/packageChecks/runPackageChecks.js +11 -5
- package/docs/METRICS.md +83 -0
- package/package.json +4 -2
- package/tests/fixtures/ecosystem/context-integrity/v0.4.5/corrected-replay/derived/run-integrity-evidence.json +99 -0
- package/tests/fixtures/ecosystem/context-integrity/v0.4.5/corrected-replay/producer/implementation-audit.json +1585 -0
- package/tests/fixtures/ecosystem/context-integrity/v0.4.5/corrected-replay/producer/implementation-capsule.json +11455 -0
- package/tests/fixtures/ecosystem/context-integrity/v0.4.5/failed-run/00-request.txt +1 -0
- package/tests/fixtures/ecosystem/context-integrity/v0.4.5/failed-run/artifacts/architecture-context-packet.txt +82 -0
- package/tests/fixtures/ecosystem/context-integrity/v0.4.5/failed-run/artifacts/behavior-model.txt +104 -0
- package/tests/fixtures/ecosystem/context-integrity/v0.4.5/failed-run/artifacts/final-report.txt +41 -0
- package/tests/fixtures/ecosystem/context-integrity/v0.4.5/failed-run/artifacts/implementation-context-packet.txt +114 -0
- package/tests/fixtures/ecosystem/context-integrity/v0.4.5/failed-run/artifacts/implementation-report.txt +92 -0
- package/tests/fixtures/ecosystem/context-integrity/v0.4.5/failed-run/artifacts/judge-report.txt +34 -0
- package/tests/fixtures/ecosystem/context-integrity/v0.4.5/failed-run/artifacts/pseudocode-packet.txt +244 -0
- package/tests/fixtures/ecosystem/context-integrity/v0.4.5/failed-run/artifacts/request-brief.txt +40 -0
- package/tests/fixtures/ecosystem/context-integrity/v0.4.5/failed-run/artifacts/test-context-packet.txt +93 -0
- package/tests/fixtures/ecosystem/context-integrity/v0.4.5/failed-run/artifacts/test-implementation-report.txt +69 -0
- package/tests/fixtures/ecosystem/context-integrity/v0.4.5/failed-run/artifacts/test-strategy-packet.txt +354 -0
- package/tests/fixtures/ecosystem/context-integrity/v0.4.5/failed-run/artifacts/verification-report.txt +85 -0
- package/tests/fixtures/ecosystem/context-integrity/v0.4.5/failed-run/derived/run-integrity-evidence.json +98 -0
- package/tests/fixtures/ecosystem/context-integrity/v0.4.5/failed-run/producer/architecture-audit.json +1169 -0
- package/tests/fixtures/ecosystem/context-integrity/v0.4.5/failed-run/producer/architecture-request.json +29 -0
- package/tests/fixtures/ecosystem/context-integrity/v0.4.5/failed-run/producer/implementation-audit.json +1494 -0
- package/tests/fixtures/ecosystem/context-integrity/v0.4.5/failed-run/producer/implementation-capsule.json +11364 -0
- package/tests/fixtures/ecosystem/context-integrity/v0.4.5/failed-run/producer/implementation-request.json +33 -0
- package/tests/fixtures/ecosystem/context-integrity/v0.4.5/failed-run/producer/test-request.json +74 -0
- package/tests/fixtures/ecosystem/context-integrity/v0.4.5/failed-run/reports/architecture-context-retrieval-report.txt +58 -0
- package/tests/fixtures/ecosystem/context-integrity/v0.4.5/failed-run/reports/implementation-context-retrieval-report.txt +75 -0
- package/tests/fixtures/ecosystem/context-integrity/v0.4.5/failed-run/reports/test-context-retrieval-report.txt +84 -0
- package/tests/fixtures/ecosystem/context-integrity/v0.4.5/failed-run/run.json +62 -0
- package/tests/fixtures/ecosystem/context-integrity/v0.4.5/failed-run/tool-versions/my-dev-kit-version.txt +1 -0
- package/tests/fixtures/ecosystem/context-integrity/v0.4.5/failed-run/tool-versions/orchestrator-version.txt +1 -0
- package/tests/fixtures/ecosystem/context-integrity/v0.4.5/manifests/corrected-replay-manifest.json +19 -0
- package/tests/fixtures/ecosystem/context-integrity/v0.4.5/manifests/failed-run-manifest.json +56 -0
- package/tests/fixtures/upstream-artifacts/my-dev-kit/1.10.4/PROVENANCE.txt +18 -0
- package/tests/fixtures/upstream-artifacts/my-dev-kit/1.10.4/context-capsule/complete-v1.0.0.json +556 -0
- package/tests/fixtures/upstream-artifacts/my-dev-kit/1.10.4/retrieval-audit-record/complete-v1.0.0.json +238 -0
- package/tests/fixtures/upstream-artifacts/my-dev-kit-orchestrator/1.2.3/run-integrity/PROVENANCE.txt +31 -0
- package/tests/fixtures/upstream-artifacts/my-dev-kit-orchestrator/1.2.3/run-integrity/ready-pass-final-pass.json +132 -0
- package/tests/fixtures/upstream-artifacts/my-dev-kit-orchestrator/1.2.3/run-integrity/refresh-required-need-context.json +146 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// v0.4.5 Batch 4: bounded end-to-end fixture loader (section 22). Loads a frozen
|
|
2
|
+
// context-integrity ecosystem fixture (failed-run or corrected-replay) through the existing
|
|
3
|
+
// Batch 1 producer readers and Batch 3 run-integrity reader, verifies hashes, and rejects
|
|
4
|
+
// path traversal. It never recalculates metrics itself -- evaluation happens through the
|
|
5
|
+
// existing evaluateProducerReadinessBridge, called by the test/consumer, not by this loader.
|
|
6
|
+
import { readFile } from "node:fs/promises";
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
import { readMyDevKitContextCapsuleV1 } from "../upstreamArtifacts/readMyDevKitContextCapsuleV1.js";
|
|
9
|
+
import { readMyDevKitRetrievalAuditRecordV1 } from "../upstreamArtifacts/readMyDevKitRetrievalAuditRecordV1.js";
|
|
10
|
+
import { readOrchestratorRunIntegrityEvidenceV1 } from "../upstreamArtifacts/readOrchestratorRunIntegrityV1.js";
|
|
11
|
+
import { isSafeFixtureRelativePath, validateEcosystemFixtureManifest } from "./validateManifest.js";
|
|
12
|
+
import { verifyFixtureHashes } from "./verifyFixtureHashes.js";
|
|
13
|
+
function findEntry(manifest, role) {
|
|
14
|
+
return manifest.artifacts.find((a) => a.role === role && a.evaluationUse);
|
|
15
|
+
}
|
|
16
|
+
export async function loadContextIntegrityFixture(fixtureRoot, manifestPath) {
|
|
17
|
+
const issues = [];
|
|
18
|
+
let manifestRaw;
|
|
19
|
+
try {
|
|
20
|
+
manifestRaw = JSON.parse(await readFile(manifestPath, "utf8"));
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
return {
|
|
24
|
+
ok: false,
|
|
25
|
+
manifest: null,
|
|
26
|
+
hashVerification: null,
|
|
27
|
+
implementationCapsule: null,
|
|
28
|
+
implementationAudit: null,
|
|
29
|
+
runIntegrityEvidence: null,
|
|
30
|
+
issues: [`Manifest at "${manifestPath}" could not be read or parsed: ${error.message}`]
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
const manifestResult = validateEcosystemFixtureManifest(manifestRaw);
|
|
34
|
+
if (!manifestResult.ok) {
|
|
35
|
+
return {
|
|
36
|
+
ok: false,
|
|
37
|
+
manifest: null,
|
|
38
|
+
hashVerification: null,
|
|
39
|
+
implementationCapsule: null,
|
|
40
|
+
implementationAudit: null,
|
|
41
|
+
runIntegrityEvidence: null,
|
|
42
|
+
issues: manifestResult.issues.map((i) => `[${i.code}] ${i.fieldPath}: ${i.message}`)
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
const manifest = manifestResult.manifest;
|
|
46
|
+
const hashVerification = await verifyFixtureHashes(manifest, fixtureRoot);
|
|
47
|
+
if (!hashVerification.ok) {
|
|
48
|
+
issues.push(...hashVerification.issues.map((i) => `[${i.code}] ${i.fixtureRelativePath}: ${i.message}`));
|
|
49
|
+
}
|
|
50
|
+
function resolveSafe(relativePath) {
|
|
51
|
+
if (!isSafeFixtureRelativePath(relativePath)) {
|
|
52
|
+
issues.push(`Refused to resolve unsafe fixture-relative path "${relativePath}".`);
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
return path.resolve(fixtureRoot, relativePath);
|
|
56
|
+
}
|
|
57
|
+
let implementationCapsule = null;
|
|
58
|
+
const capsuleEntry = findEntry(manifest, "context-capsule");
|
|
59
|
+
if (capsuleEntry) {
|
|
60
|
+
const resolved = resolveSafe(capsuleEntry.fixtureRelativePath);
|
|
61
|
+
if (resolved) {
|
|
62
|
+
const result = await readMyDevKitContextCapsuleV1(resolved);
|
|
63
|
+
if (result.ok)
|
|
64
|
+
implementationCapsule = result.artifact;
|
|
65
|
+
else
|
|
66
|
+
issues.push(`context-capsule reader failed for "${capsuleEntry.fixtureRelativePath}": [${result.code}] ${result.message}`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
let implementationAudit = null;
|
|
70
|
+
const auditEntry = findEntry(manifest, "retrieval-audit-record");
|
|
71
|
+
if (auditEntry) {
|
|
72
|
+
const resolved = resolveSafe(auditEntry.fixtureRelativePath);
|
|
73
|
+
if (resolved) {
|
|
74
|
+
const result = await readMyDevKitRetrievalAuditRecordV1(resolved);
|
|
75
|
+
if (result.ok)
|
|
76
|
+
implementationAudit = result.artifact;
|
|
77
|
+
else
|
|
78
|
+
issues.push(`retrieval-audit-record reader failed for "${auditEntry.fixtureRelativePath}": [${result.code}] ${result.message}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
let runIntegrityEvidence = null;
|
|
82
|
+
const runIntegrityEntry = findEntry(manifest, "run-integrity-evidence");
|
|
83
|
+
if (runIntegrityEntry) {
|
|
84
|
+
const resolved = resolveSafe(runIntegrityEntry.fixtureRelativePath);
|
|
85
|
+
if (resolved) {
|
|
86
|
+
const result = await readOrchestratorRunIntegrityEvidenceV1(resolved);
|
|
87
|
+
if (result.ok)
|
|
88
|
+
runIntegrityEvidence = result.artifact;
|
|
89
|
+
else
|
|
90
|
+
issues.push(`run-integrity-evidence reader failed for "${runIntegrityEntry.fixtureRelativePath}": [${result.code}] ${result.message}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
ok: issues.length === 0,
|
|
95
|
+
manifest,
|
|
96
|
+
hashVerification,
|
|
97
|
+
implementationCapsule,
|
|
98
|
+
implementationAudit,
|
|
99
|
+
runIntegrityEvidence,
|
|
100
|
+
issues
|
|
101
|
+
};
|
|
102
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
function isPlainObject(value) {
|
|
2
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3
|
+
}
|
|
4
|
+
// Rejects any fixture-relative path that is absolute, empty, or escapes the fixture root
|
|
5
|
+
// via ".." segments (section 22 "rejects path traversal"). Windows and POSIX separators are
|
|
6
|
+
// both checked since fixture manifests are authored on Windows but tests may run on either.
|
|
7
|
+
export function isSafeFixtureRelativePath(candidate) {
|
|
8
|
+
if (candidate.length === 0)
|
|
9
|
+
return false;
|
|
10
|
+
if (candidate.startsWith("/") || candidate.startsWith("\\"))
|
|
11
|
+
return false;
|
|
12
|
+
if (/^[a-zA-Z]:[\\/]/.test(candidate))
|
|
13
|
+
return false;
|
|
14
|
+
const segments = candidate.split(/[\\/]/);
|
|
15
|
+
return segments.every((segment) => segment !== "..") && segments.length > 0;
|
|
16
|
+
}
|
|
17
|
+
export function validateEcosystemFixtureManifest(value) {
|
|
18
|
+
const issues = [];
|
|
19
|
+
if (!isPlainObject(value)) {
|
|
20
|
+
return { ok: false, issues: [{ code: "NON_OBJECT_MANIFEST", fieldPath: "", message: "Manifest must be a plain JSON object." }] };
|
|
21
|
+
}
|
|
22
|
+
const schemaVersion = value["manifestSchemaVersion"];
|
|
23
|
+
if (typeof schemaVersion !== "string" || !/^1\./.test(schemaVersion)) {
|
|
24
|
+
return {
|
|
25
|
+
ok: false,
|
|
26
|
+
issues: [
|
|
27
|
+
{
|
|
28
|
+
code: "UNSUPPORTED_MANIFEST_SCHEMA_MAJOR",
|
|
29
|
+
fieldPath: "manifestSchemaVersion",
|
|
30
|
+
message: `Manifest schema version "${String(schemaVersion)}" is unsupported; expected major version 1.`
|
|
31
|
+
}
|
|
32
|
+
]
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
const requiredStringFields = ["fixtureId", "description", "sourceEvidenceRoot", "generatedAt"];
|
|
36
|
+
for (const field of requiredStringFields) {
|
|
37
|
+
if (typeof value[field] !== "string" || value[field].length === 0) {
|
|
38
|
+
issues.push({ code: "MISSING_REQUIRED_FIELD", fieldPath: field, message: `Manifest is missing required non-empty string field "${field}".` });
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
const artifacts = value["artifacts"];
|
|
42
|
+
if (!Array.isArray(artifacts)) {
|
|
43
|
+
issues.push({ code: "INVALID_FIELD_TYPE", fieldPath: "artifacts", message: 'Manifest field "artifacts" must be an array.' });
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
const seenPaths = new Set();
|
|
47
|
+
artifacts.forEach((entry, index) => {
|
|
48
|
+
const fieldPath = `artifacts[${index}]`;
|
|
49
|
+
if (!isPlainObject(entry)) {
|
|
50
|
+
issues.push({ code: "INVALID_FIELD_TYPE", fieldPath, message: "Each artifact entry must be an object." });
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const relPath = entry["fixtureRelativePath"];
|
|
54
|
+
if (typeof relPath !== "string" || relPath.length === 0) {
|
|
55
|
+
issues.push({ code: "MISSING_REQUIRED_FIELD", fieldPath: `${fieldPath}.fixtureRelativePath`, message: "Missing fixtureRelativePath." });
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
if (!isSafeFixtureRelativePath(relPath)) {
|
|
59
|
+
issues.push({
|
|
60
|
+
code: "PATH_ESCAPES_FIXTURE_ROOT",
|
|
61
|
+
fieldPath: `${fieldPath}.fixtureRelativePath`,
|
|
62
|
+
message: `fixtureRelativePath "${relPath}" is absolute or escapes the fixture root.`
|
|
63
|
+
});
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
if (seenPaths.has(relPath)) {
|
|
67
|
+
issues.push({ code: "DUPLICATE_ARTIFACT_PATH", fieldPath: `${fieldPath}.fixtureRelativePath`, message: `Duplicate fixtureRelativePath "${relPath}".` });
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
seenPaths.add(relPath);
|
|
71
|
+
for (const hashField of ["originalSha256", "copiedSha256"]) {
|
|
72
|
+
const v = entry[hashField];
|
|
73
|
+
if (typeof v !== "string" || !/^[0-9a-f]{64}$/i.test(v)) {
|
|
74
|
+
issues.push({ code: "INVALID_FIELD_TYPE", fieldPath: `${fieldPath}.${hashField}`, message: `${hashField} must be a 64-hex-character SHA-256 digest.` });
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
if (issues.length > 0)
|
|
80
|
+
return { ok: false, issues };
|
|
81
|
+
return { ok: true, manifest: value };
|
|
82
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { isSafeFixtureRelativePath } from "./validateManifest.js";
|
|
5
|
+
async function sha256OfFile(absolutePath) {
|
|
6
|
+
const contents = await readFile(absolutePath);
|
|
7
|
+
return createHash("sha256").update(contents).digest("hex");
|
|
8
|
+
}
|
|
9
|
+
// Section 21: reads the manifest, hashes each tracked fixture file, and reports missing
|
|
10
|
+
// files, hash mismatches, and path-traversal attempts with deterministic (manifest) order.
|
|
11
|
+
// Never rewrites the manifest or the fixture; a mismatch is reported, not repaired.
|
|
12
|
+
export async function verifyFixtureHashes(manifest, fixtureRoot) {
|
|
13
|
+
const issues = [];
|
|
14
|
+
let checkedCount = 0;
|
|
15
|
+
for (const artifact of manifest.artifacts) {
|
|
16
|
+
const relPath = artifact.fixtureRelativePath;
|
|
17
|
+
if (!isSafeFixtureRelativePath(relPath)) {
|
|
18
|
+
issues.push({ code: "PATH_ESCAPES_FIXTURE_ROOT", fixtureRelativePath: relPath, message: `Manifest entry references an unsafe path: "${relPath}".` });
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
const absolutePath = path.resolve(fixtureRoot, relPath);
|
|
22
|
+
let actualSha256;
|
|
23
|
+
try {
|
|
24
|
+
actualSha256 = await sha256OfFile(absolutePath);
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
const code = error.code;
|
|
28
|
+
issues.push({
|
|
29
|
+
code: code === "ENOENT" ? "MISSING_FILE" : "UNREADABLE_FILE",
|
|
30
|
+
fixtureRelativePath: relPath,
|
|
31
|
+
message: code === "ENOENT" ? `Fixture file is missing: "${relPath}".` : `Fixture file could not be read: "${relPath}" (${error.message}).`
|
|
32
|
+
});
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
checkedCount += 1;
|
|
36
|
+
if (actualSha256 !== artifact.copiedSha256) {
|
|
37
|
+
issues.push({
|
|
38
|
+
code: "HASH_MISMATCH",
|
|
39
|
+
fixtureRelativePath: relPath,
|
|
40
|
+
message: `Fixture file "${relPath}" hash does not match the manifest's recorded copiedSha256 (expected ${artifact.copiedSha256}, got ${actualSha256}).`
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return { ok: issues.length === 0, checkedCount, issues };
|
|
45
|
+
}
|
|
@@ -94,3 +94,172 @@ export function calculateRequiredEvidenceOmitted(matches, sourceArtifactTruncate
|
|
|
94
94
|
export function findAllocationExpectationForGroup(expectations, groupId) {
|
|
95
95
|
return (expectations ?? []).find((e) => e.groupId === groupId);
|
|
96
96
|
}
|
|
97
|
+
// v0.4.5 Batch 2: per-group/aggregate allocation and spillover evidence, read directly from
|
|
98
|
+
// ContextCapsule.groupTruncation (the Batch 1 mirror of the v1.10.3/v1.10.4 required-first
|
|
99
|
+
// allocator diagnostics). A group entry that lacks every additive allocation field is
|
|
100
|
+
// legacy/unavailable, never fabricated as zero. RetrievalAuditRecord has no groupTruncation
|
|
101
|
+
// field, so this evidence is context-capsule-only (sourceArtifact is always fixed).
|
|
102
|
+
function hasAnyAllocationField(entry) {
|
|
103
|
+
return (entry.required !== undefined ||
|
|
104
|
+
entry.reservation !== undefined ||
|
|
105
|
+
entry.initiallySelectedCount !== undefined ||
|
|
106
|
+
entry.unusedReservationContributed !== undefined ||
|
|
107
|
+
entry.borrowedCapacity !== undefined ||
|
|
108
|
+
entry.requiredOmittedCount !== undefined ||
|
|
109
|
+
entry.optionalOmittedCount !== undefined ||
|
|
110
|
+
entry.adequacyAffected !== undefined ||
|
|
111
|
+
entry.governingHardBound !== undefined ||
|
|
112
|
+
entry.aggregateCapacityUsed !== undefined ||
|
|
113
|
+
entry.aggregateCapacityRemaining !== undefined);
|
|
114
|
+
}
|
|
115
|
+
function calculatePerGroupAllocation(entries, sourceInstance) {
|
|
116
|
+
return entries.map((entry) => {
|
|
117
|
+
const available = hasAnyAllocationField(entry);
|
|
118
|
+
return {
|
|
119
|
+
groupId: entry.groupId,
|
|
120
|
+
sourceArtifact: "context-capsule",
|
|
121
|
+
sourceInstance,
|
|
122
|
+
availability: available ? "available" : "unavailable",
|
|
123
|
+
required: entry.required ?? null,
|
|
124
|
+
reservation: entry.reservation ?? null,
|
|
125
|
+
initiallySelectedCount: entry.initiallySelectedCount ?? null,
|
|
126
|
+
unusedReservationContributed: entry.unusedReservationContributed ?? null,
|
|
127
|
+
borrowedCapacity: entry.borrowedCapacity ?? null,
|
|
128
|
+
governingHardBound: entry.governingHardBound ?? null,
|
|
129
|
+
requiredOmittedCount: entry.requiredOmittedCount ?? null,
|
|
130
|
+
optionalOmittedCount: entry.optionalOmittedCount ?? null,
|
|
131
|
+
droppedCount: entry.droppedCount,
|
|
132
|
+
droppedEvidenceIds: entry.droppedEvidenceIds ?? [],
|
|
133
|
+
adequacyAffected: entry.adequacyAffected ?? null,
|
|
134
|
+
aggregateCapacityUsed: entry.aggregateCapacityUsed ?? null,
|
|
135
|
+
aggregateCapacityRemaining: entry.aggregateCapacityRemaining ?? null,
|
|
136
|
+
reason: available ? null : `Group "${entry.groupId}" exposes no v1.10.3/v1.10.4 allocation diagnostics (legacy schema-major-1 evidence).`
|
|
137
|
+
};
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
function sumIfComplete(entries, field) {
|
|
141
|
+
if (entries.length === 0)
|
|
142
|
+
return null;
|
|
143
|
+
let total = 0;
|
|
144
|
+
for (const entry of entries) {
|
|
145
|
+
const value = entry[field];
|
|
146
|
+
if (typeof value !== "number")
|
|
147
|
+
return null;
|
|
148
|
+
total += value;
|
|
149
|
+
}
|
|
150
|
+
return total;
|
|
151
|
+
}
|
|
152
|
+
function calculateAggregateAllocation(entries) {
|
|
153
|
+
if (entries.length === 0) {
|
|
154
|
+
return {
|
|
155
|
+
availability: "not-applicable",
|
|
156
|
+
groupCount: 0,
|
|
157
|
+
totalReservation: null,
|
|
158
|
+
totalInitiallySelected: null,
|
|
159
|
+
totalUnusedReservationContributed: null,
|
|
160
|
+
totalBorrowedCapacity: null,
|
|
161
|
+
totalRequiredOmitted: null,
|
|
162
|
+
totalOptionalOmitted: null,
|
|
163
|
+
totalDropped: null,
|
|
164
|
+
groupsContributingUnusedReservation: [],
|
|
165
|
+
groupsBorrowingCapacity: [],
|
|
166
|
+
groupsWithRequiredOmission: [],
|
|
167
|
+
groupsWithOptionalOnlyOmission: [],
|
|
168
|
+
groupsWithAdequacyAffected: [],
|
|
169
|
+
partial: false,
|
|
170
|
+
reason: "No evidence groups were supplied."
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
const anyAllocationEvidence = entries.some(hasAnyAllocationField);
|
|
174
|
+
if (!anyAllocationEvidence) {
|
|
175
|
+
return {
|
|
176
|
+
availability: "unavailable",
|
|
177
|
+
groupCount: entries.length,
|
|
178
|
+
totalReservation: null,
|
|
179
|
+
totalInitiallySelected: null,
|
|
180
|
+
totalUnusedReservationContributed: null,
|
|
181
|
+
totalBorrowedCapacity: null,
|
|
182
|
+
totalRequiredOmitted: null,
|
|
183
|
+
totalOptionalOmitted: null,
|
|
184
|
+
totalDropped: null,
|
|
185
|
+
groupsContributingUnusedReservation: [],
|
|
186
|
+
groupsBorrowingCapacity: [],
|
|
187
|
+
groupsWithRequiredOmission: [],
|
|
188
|
+
groupsWithOptionalOnlyOmission: [],
|
|
189
|
+
groupsWithAdequacyAffected: [],
|
|
190
|
+
partial: false,
|
|
191
|
+
reason: "No group exposes v1.10.3/v1.10.4 allocation diagnostics (legacy schema-major-1 evidence)."
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
const totalReservation = sumIfComplete(entries, "reservation");
|
|
195
|
+
const totalInitiallySelected = sumIfComplete(entries, "initiallySelectedCount");
|
|
196
|
+
const totalUnusedReservationContributed = sumIfComplete(entries, "unusedReservationContributed");
|
|
197
|
+
const totalBorrowedCapacity = sumIfComplete(entries, "borrowedCapacity");
|
|
198
|
+
const totalRequiredOmitted = sumIfComplete(entries, "requiredOmittedCount");
|
|
199
|
+
const totalOptionalOmitted = sumIfComplete(entries, "optionalOmittedCount");
|
|
200
|
+
const totalDropped = sumIfComplete(entries, "droppedCount");
|
|
201
|
+
const totals = [
|
|
202
|
+
totalReservation,
|
|
203
|
+
totalInitiallySelected,
|
|
204
|
+
totalUnusedReservationContributed,
|
|
205
|
+
totalBorrowedCapacity,
|
|
206
|
+
totalRequiredOmitted,
|
|
207
|
+
totalOptionalOmitted
|
|
208
|
+
];
|
|
209
|
+
const partial = totals.some((t) => t === null);
|
|
210
|
+
return {
|
|
211
|
+
availability: "available",
|
|
212
|
+
groupCount: entries.length,
|
|
213
|
+
totalReservation,
|
|
214
|
+
totalInitiallySelected,
|
|
215
|
+
totalUnusedReservationContributed,
|
|
216
|
+
totalBorrowedCapacity,
|
|
217
|
+
totalRequiredOmitted,
|
|
218
|
+
totalOptionalOmitted,
|
|
219
|
+
totalDropped,
|
|
220
|
+
groupsContributingUnusedReservation: entries.filter((e) => (e.unusedReservationContributed ?? 0) > 0).map((e) => e.groupId),
|
|
221
|
+
groupsBorrowingCapacity: entries.filter((e) => (e.borrowedCapacity ?? 0) > 0).map((e) => e.groupId),
|
|
222
|
+
groupsWithRequiredOmission: entries.filter((e) => (e.requiredOmittedCount ?? 0) > 0).map((e) => e.groupId),
|
|
223
|
+
groupsWithOptionalOnlyOmission: entries
|
|
224
|
+
.filter((e) => (e.optionalOmittedCount ?? 0) > 0 && (e.requiredOmittedCount ?? 0) === 0)
|
|
225
|
+
.map((e) => e.groupId),
|
|
226
|
+
groupsWithAdequacyAffected: entries.filter((e) => e.adequacyAffected === true).map((e) => e.groupId),
|
|
227
|
+
partial,
|
|
228
|
+
reason: partial ? "At least one group lacks the full allocation field set; incomplete totals are reported as null rather than partially summed." : null
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
function calculateSpillover(entries) {
|
|
232
|
+
const anyAllocationEvidence = entries.some(hasAnyAllocationField);
|
|
233
|
+
if (!anyAllocationEvidence) {
|
|
234
|
+
return {
|
|
235
|
+
availability: "unavailable",
|
|
236
|
+
groupsContributing: [],
|
|
237
|
+
groupsBorrowing: [],
|
|
238
|
+
totalContributed: null,
|
|
239
|
+
totalBorrowed: null,
|
|
240
|
+
contributionCoversBorrowing: null,
|
|
241
|
+
reason: "No group exposes v1.10.3/v1.10.4 allocation diagnostics (legacy schema-major-1 evidence)."
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
const groupsContributing = entries.filter((e) => (e.unusedReservationContributed ?? 0) > 0).map((e) => e.groupId);
|
|
245
|
+
const groupsBorrowing = entries.filter((e) => (e.borrowedCapacity ?? 0) > 0).map((e) => e.groupId);
|
|
246
|
+
const totalContributed = sumIfComplete(entries, "unusedReservationContributed");
|
|
247
|
+
const totalBorrowed = sumIfComplete(entries, "borrowedCapacity");
|
|
248
|
+
return {
|
|
249
|
+
availability: "available",
|
|
250
|
+
groupsContributing,
|
|
251
|
+
groupsBorrowing,
|
|
252
|
+
totalContributed,
|
|
253
|
+
totalBorrowed,
|
|
254
|
+
contributionCoversBorrowing: totalContributed !== null && totalBorrowed !== null ? totalContributed >= totalBorrowed : null,
|
|
255
|
+
reason: null
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
export function calculateGroupAllocationMetrics(groupTruncation, sourceInstance) {
|
|
259
|
+
const entries = groupTruncation ?? [];
|
|
260
|
+
return {
|
|
261
|
+
perGroup: calculatePerGroupAllocation(entries, sourceInstance),
|
|
262
|
+
aggregate: calculateAggregateAllocation(entries),
|
|
263
|
+
spillover: calculateSpillover(entries)
|
|
264
|
+
};
|
|
265
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
function unavailableCoverageMetrics(reason) {
|
|
2
|
+
const unavailableCount = { availability: "unavailable", count: null, evidenceKeys: [], reason };
|
|
3
|
+
return {
|
|
4
|
+
availability: "unavailable",
|
|
5
|
+
requiredConditionsTotal: unavailableCount,
|
|
6
|
+
requiredConditionsSatisfied: unavailableCount,
|
|
7
|
+
requiredConditionsMissing: unavailableCount,
|
|
8
|
+
requiredConditionsLost: unavailableCount,
|
|
9
|
+
optionalConditionsTotal: unavailableCount,
|
|
10
|
+
optionalConditionsSatisfied: unavailableCount,
|
|
11
|
+
optionalConditionsMissing: unavailableCount,
|
|
12
|
+
witnessEvidence: [],
|
|
13
|
+
lastWitnessLoss: unavailableCount,
|
|
14
|
+
conditionToGroupMapping: [],
|
|
15
|
+
reason
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
function countMetric(ids) {
|
|
19
|
+
return { availability: "available", count: ids.length, evidenceKeys: ids, reason: null };
|
|
20
|
+
}
|
|
21
|
+
function coverageState(condition) {
|
|
22
|
+
if (condition.conditionSatisfied)
|
|
23
|
+
return "satisfied";
|
|
24
|
+
if (condition.lostRequiredCondition)
|
|
25
|
+
return "lost-to-allocation";
|
|
26
|
+
return "missing-evidence";
|
|
27
|
+
}
|
|
28
|
+
export function calculateConditionCoverageMetrics(roleConditionCoverage, availableEvidenceGroupIds) {
|
|
29
|
+
if (roleConditionCoverage === undefined) {
|
|
30
|
+
return unavailableCoverageMetrics("No roleConditionCoverage evidence was supplied (legacy schema-major-1 artifact, or the producer did not emit condition-level diagnostics).");
|
|
31
|
+
}
|
|
32
|
+
const required = roleConditionCoverage.filter((c) => c.required);
|
|
33
|
+
const optional = roleConditionCoverage.filter((c) => !c.required);
|
|
34
|
+
const requiredSatisfied = required.filter((c) => c.conditionSatisfied);
|
|
35
|
+
const requiredLost = required.filter((c) => c.lostRequiredCondition);
|
|
36
|
+
const requiredMissing = required.filter((c) => !c.conditionSatisfied && !c.lostRequiredCondition);
|
|
37
|
+
const optionalSatisfied = optional.filter((c) => c.conditionSatisfied);
|
|
38
|
+
const optionalMissing = optional.filter((c) => !c.conditionSatisfied);
|
|
39
|
+
const witnessEvidence = roleConditionCoverage.map((c) => ({
|
|
40
|
+
conditionId: c.conditionId,
|
|
41
|
+
role: c.role,
|
|
42
|
+
required: c.required,
|
|
43
|
+
witnessPolicy: c.witnessPolicy,
|
|
44
|
+
requiredWitnessCount: c.requiredWitnessCount,
|
|
45
|
+
availableWitnessCount: c.availableWitnessCount,
|
|
46
|
+
retainedWitnessCount: c.retainedWitnessCount,
|
|
47
|
+
retainedWitnessIds: c.retainedWitnessIds,
|
|
48
|
+
adequateWitnessRemains: c.conditionSatisfied,
|
|
49
|
+
coverageState: coverageState(c),
|
|
50
|
+
lossReason: c.lossReason,
|
|
51
|
+
evidenceGroupIds: c.evidenceGroupIds
|
|
52
|
+
}));
|
|
53
|
+
const conditionToGroupMapping = roleConditionCoverage.map((c) => ({
|
|
54
|
+
conditionId: c.conditionId,
|
|
55
|
+
required: c.required,
|
|
56
|
+
evidenceGroupIds: c.evidenceGroupIds,
|
|
57
|
+
mapped: c.evidenceGroupIds.length > 0,
|
|
58
|
+
unknownGroupIds: availableEvidenceGroupIds === undefined ? [] : c.evidenceGroupIds.filter((id) => !availableEvidenceGroupIds.includes(id))
|
|
59
|
+
}));
|
|
60
|
+
return {
|
|
61
|
+
availability: "available",
|
|
62
|
+
requiredConditionsTotal: countMetric(required.map((c) => c.conditionId)),
|
|
63
|
+
requiredConditionsSatisfied: countMetric(requiredSatisfied.map((c) => c.conditionId)),
|
|
64
|
+
requiredConditionsMissing: countMetric(requiredMissing.map((c) => c.conditionId)),
|
|
65
|
+
requiredConditionsLost: countMetric(requiredLost.map((c) => c.conditionId)),
|
|
66
|
+
optionalConditionsTotal: countMetric(optional.map((c) => c.conditionId)),
|
|
67
|
+
optionalConditionsSatisfied: countMetric(optionalSatisfied.map((c) => c.conditionId)),
|
|
68
|
+
optionalConditionsMissing: countMetric(optionalMissing.map((c) => c.conditionId)),
|
|
69
|
+
witnessEvidence,
|
|
70
|
+
lastWitnessLoss: countMetric(requiredLost.map((c) => c.conditionId)),
|
|
71
|
+
conditionToGroupMapping,
|
|
72
|
+
reason: null
|
|
73
|
+
};
|
|
74
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { checkMyDevKitContextArtifactConsistency } from "../stageContextSelectors/contextArtifactConsistency.js";
|
|
2
|
+
const ADEQUATE_STATUSES = [
|
|
3
|
+
"context sufficient for implementation",
|
|
4
|
+
"context sufficient with listed assumptions"
|
|
5
|
+
];
|
|
6
|
+
const INADEQUATE_STATUSES = [
|
|
7
|
+
"context insufficient and more retrieval required",
|
|
8
|
+
"context conflict found and user or upstream stage decision required"
|
|
9
|
+
];
|
|
10
|
+
export function calculateProducerConditionAgreement(roleAdequacyStatus, conditionCoverage) {
|
|
11
|
+
if (roleAdequacyStatus === undefined) {
|
|
12
|
+
return {
|
|
13
|
+
availability: "unavailable",
|
|
14
|
+
outcome: "insufficient-evidence",
|
|
15
|
+
observedRoleAdequacyStatus: null,
|
|
16
|
+
contradictionCodes: [],
|
|
17
|
+
reason: "No roleAdequacy.status was supplied."
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
if (conditionCoverage.availability !== "available") {
|
|
21
|
+
return {
|
|
22
|
+
availability: "unavailable",
|
|
23
|
+
outcome: "unsupported-legacy-evidence",
|
|
24
|
+
observedRoleAdequacyStatus: roleAdequacyStatus,
|
|
25
|
+
contradictionCodes: [],
|
|
26
|
+
reason: conditionCoverage.reason ?? "No condition-coverage evidence was available to compare against roleAdequacy.status."
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
const requiredTotal = conditionCoverage.requiredConditionsTotal.count ?? 0;
|
|
30
|
+
const requiredSatisfied = conditionCoverage.requiredConditionsSatisfied.count ?? 0;
|
|
31
|
+
const requiredLost = conditionCoverage.requiredConditionsLost.count ?? 0;
|
|
32
|
+
const allRequiredSatisfied = requiredTotal > 0 && requiredSatisfied === requiredTotal;
|
|
33
|
+
const producerSaysAdequate = ADEQUATE_STATUSES.includes(roleAdequacyStatus);
|
|
34
|
+
const producerSaysInadequate = INADEQUATE_STATUSES.includes(roleAdequacyStatus);
|
|
35
|
+
const contradictionCodes = [];
|
|
36
|
+
if (producerSaysInadequate && allRequiredSatisfied && requiredLost === 0) {
|
|
37
|
+
contradictionCodes.push("PRODUCER_INADEQUATE_BUT_ALL_REQUIRED_CONDITIONS_RETAINED");
|
|
38
|
+
}
|
|
39
|
+
if (producerSaysAdequate && requiredLost > 0) {
|
|
40
|
+
contradictionCodes.push("PRODUCER_ADEQUATE_BUT_REQUIRED_CONDITION_LOST");
|
|
41
|
+
}
|
|
42
|
+
const outcome = contradictionCodes.length > 0 ? "contradiction" : "agreement";
|
|
43
|
+
return {
|
|
44
|
+
availability: "available",
|
|
45
|
+
outcome,
|
|
46
|
+
observedRoleAdequacyStatus: roleAdequacyStatus,
|
|
47
|
+
contradictionCodes,
|
|
48
|
+
reason: outcome === "contradiction"
|
|
49
|
+
? "Producer role-adequacy status contradicts its own explicit condition-coverage evidence; both sides are retained."
|
|
50
|
+
: null
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
export function calculateRequiredEvidenceLossAgreement(requiredEvidenceLost, conditionCoverage, requiredOmittedCount) {
|
|
54
|
+
if (requiredEvidenceLost === undefined) {
|
|
55
|
+
return {
|
|
56
|
+
availability: "unavailable",
|
|
57
|
+
outcome: "insufficient-evidence",
|
|
58
|
+
requiredEvidenceLost: null,
|
|
59
|
+
explicitConditionLossDetected: null,
|
|
60
|
+
requiredOmittedCount: requiredOmittedCount ?? null,
|
|
61
|
+
contradictionCodes: [],
|
|
62
|
+
reason: "No truncation.requiredEvidenceLost rollup was supplied."
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
if (conditionCoverage.availability !== "available") {
|
|
66
|
+
return {
|
|
67
|
+
availability: "unavailable",
|
|
68
|
+
outcome: "unsupported-legacy-evidence",
|
|
69
|
+
requiredEvidenceLost,
|
|
70
|
+
explicitConditionLossDetected: null,
|
|
71
|
+
requiredOmittedCount: requiredOmittedCount ?? null,
|
|
72
|
+
contradictionCodes: [],
|
|
73
|
+
reason: conditionCoverage.reason ?? "No condition-coverage evidence was available to compare against requiredEvidenceLost."
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
const explicitConditionLossDetected = (conditionCoverage.requiredConditionsLost.count ?? 0) > 0;
|
|
77
|
+
const contradictionCodes = [];
|
|
78
|
+
if (!requiredEvidenceLost && explicitConditionLossDetected) {
|
|
79
|
+
contradictionCodes.push("REQUIRED_EVIDENCE_LOST_FALSE_BUT_CONDITION_LOSS_DETECTED");
|
|
80
|
+
}
|
|
81
|
+
if (requiredEvidenceLost && !explicitConditionLossDetected && (requiredOmittedCount ?? 0) === 0) {
|
|
82
|
+
contradictionCodes.push("REQUIRED_EVIDENCE_LOST_TRUE_BUT_NO_CONDITION_OR_GROUP_LOSS_DETECTED");
|
|
83
|
+
}
|
|
84
|
+
const outcome = contradictionCodes.length > 0 ? "contradiction" : "agreement";
|
|
85
|
+
return {
|
|
86
|
+
availability: "available",
|
|
87
|
+
outcome,
|
|
88
|
+
requiredEvidenceLost,
|
|
89
|
+
explicitConditionLossDetected,
|
|
90
|
+
requiredOmittedCount: requiredOmittedCount ?? null,
|
|
91
|
+
contradictionCodes,
|
|
92
|
+
reason: outcome === "contradiction" ? "truncation.requiredEvidenceLost contradicts explicit condition-loss/required-omission evidence; both sides are retained." : null
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
const RELEVANT_CAPSULE_AUDIT_FIELDS = new Set(["roleConditionCoverage", "truncation", "roleAdequacy"]);
|
|
96
|
+
export function calculateCapsuleAuditConditionAgreement(capsule, audit) {
|
|
97
|
+
if (capsule === undefined || audit === undefined) {
|
|
98
|
+
const reason = capsule === undefined && audit === undefined
|
|
99
|
+
? "Neither a context capsule nor a retrieval audit record was supplied."
|
|
100
|
+
: capsule === undefined
|
|
101
|
+
? "No context capsule was supplied."
|
|
102
|
+
: "No retrieval audit record was supplied.";
|
|
103
|
+
return { availability: "unavailable", consistent: null, contradictingFieldPaths: [], reason };
|
|
104
|
+
}
|
|
105
|
+
const result = checkMyDevKitContextArtifactConsistency(capsule, audit);
|
|
106
|
+
const contradictingFieldPaths = result.issues.filter((issue) => RELEVANT_CAPSULE_AUDIT_FIELDS.has(issue.fieldPath)).map((issue) => issue.fieldPath);
|
|
107
|
+
return {
|
|
108
|
+
availability: "available",
|
|
109
|
+
consistent: contradictingFieldPaths.length === 0,
|
|
110
|
+
contradictingFieldPaths,
|
|
111
|
+
reason: null
|
|
112
|
+
};
|
|
113
|
+
}
|
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
// output (Batch 1) -- this module only compares it against an explicit fixture-declared
|
|
3
3
|
// expectation. It never calls, imports, or reimplements contextReadiness.ts.
|
|
4
4
|
import { checkSupplementalReadinessIdentityConsistency } from "../upstreamArtifacts/index.js";
|
|
5
|
+
const READINESS_ADEQUATE_STATUSES = [
|
|
6
|
+
"context sufficient for implementation",
|
|
7
|
+
"context sufficient with listed assumptions"
|
|
8
|
+
];
|
|
5
9
|
function unavailableStructural(field, reason) {
|
|
6
10
|
return { field, expectedValue: null, observedValue: null, agreement: null, availability: "unavailable", reason };
|
|
7
11
|
}
|
|
@@ -142,6 +146,62 @@ export function calculateValidBlocked(readiness, expectation) {
|
|
|
142
146
|
const valid = allowsRefreshRequired && issueCodesMatch && primaryMatches;
|
|
143
147
|
return { availability: "available", validBlocked: valid, validRefreshRequired: valid, observedDecision: readiness.decision, reason: null };
|
|
144
148
|
}
|
|
149
|
+
// v0.4.5 Batch 2 (section 18): compares producer role adequacy (as already reconciled with
|
|
150
|
+
// condition-coverage evidence by calculateProducerConditionAgreement) against the supplied
|
|
151
|
+
// orchestrator readiness decision already accepted by the v0.4.4 bridge. This never decides
|
|
152
|
+
// what readiness "should" have been -- it only reports the observed relationship.
|
|
153
|
+
export function calculateProducerReadinessRelationship(roleAdequacyStatus, producerConditionAgreement, readiness) {
|
|
154
|
+
if (roleAdequacyStatus === undefined || readiness === undefined) {
|
|
155
|
+
return {
|
|
156
|
+
availability: "unavailable",
|
|
157
|
+
outcome: "insufficient-evidence",
|
|
158
|
+
observedRoleAdequacyStatus: roleAdequacyStatus ?? null,
|
|
159
|
+
observedReadinessDecision: readiness?.decision ?? null,
|
|
160
|
+
contradictionCodes: [],
|
|
161
|
+
reason: roleAdequacyStatus === undefined && readiness === undefined
|
|
162
|
+
? "Neither roleAdequacy.status nor a readiness result was supplied."
|
|
163
|
+
: roleAdequacyStatus === undefined
|
|
164
|
+
? "No roleAdequacy.status was supplied."
|
|
165
|
+
: "No readiness result was supplied."
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
if (producerConditionAgreement.outcome === "contradiction") {
|
|
169
|
+
return {
|
|
170
|
+
availability: "available",
|
|
171
|
+
outcome: "insufficient-evidence",
|
|
172
|
+
observedRoleAdequacyStatus: roleAdequacyStatus,
|
|
173
|
+
observedReadinessDecision: readiness.decision,
|
|
174
|
+
contradictionCodes: [],
|
|
175
|
+
reason: "Producer role-adequacy already contradicts its own condition-coverage evidence; the readiness relationship cannot be attributed to either side."
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
if (producerConditionAgreement.outcome === "unsupported-legacy-evidence" || producerConditionAgreement.outcome === "insufficient-evidence") {
|
|
179
|
+
return {
|
|
180
|
+
availability: "unavailable",
|
|
181
|
+
outcome: producerConditionAgreement.outcome,
|
|
182
|
+
observedRoleAdequacyStatus: roleAdequacyStatus,
|
|
183
|
+
observedReadinessDecision: readiness.decision,
|
|
184
|
+
contradictionCodes: [],
|
|
185
|
+
reason: producerConditionAgreement.reason
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
const producerAdequate = READINESS_ADEQUATE_STATUSES.includes(roleAdequacyStatus);
|
|
189
|
+
const readinessReady = readiness.decision === "ready";
|
|
190
|
+
const contradictionCodes = [];
|
|
191
|
+
if (producerAdequate && !readinessReady)
|
|
192
|
+
contradictionCodes.push("PRODUCER_ADEQUATE_BUT_READINESS_NOT_READY");
|
|
193
|
+
if (!producerAdequate && readinessReady)
|
|
194
|
+
contradictionCodes.push("PRODUCER_INADEQUATE_BUT_READINESS_READY");
|
|
195
|
+
const outcome = contradictionCodes.length > 0 ? "contradiction" : "agreement";
|
|
196
|
+
return {
|
|
197
|
+
availability: "available",
|
|
198
|
+
outcome,
|
|
199
|
+
observedRoleAdequacyStatus: roleAdequacyStatus,
|
|
200
|
+
observedReadinessDecision: readiness.decision,
|
|
201
|
+
contradictionCodes,
|
|
202
|
+
reason: outcome === "contradiction" ? "Producer role adequacy and supplied orchestrator readiness disagree; both sides are retained without a lab-owned verdict." : null
|
|
203
|
+
};
|
|
204
|
+
}
|
|
145
205
|
export function calculateReadinessAgreementMetrics(readiness, expectation, supplementalPacket, packetSourcePath) {
|
|
146
206
|
return {
|
|
147
207
|
structuralAgreement: calculateReadinessStructuralAgreement(readiness, supplementalPacket, packetSourcePath),
|