@dailephd/my-dev-kit-lab 0.4.3 → 0.4.4
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/README.md +6 -1
- package/dist/src/evaluation/stageContextExpectations/producerReadinessExpectationTypes.js +8 -0
- package/dist/src/evaluation/stageContextExpectations/validation.js +200 -0
- package/dist/src/evaluation/stageContextMetrics/calculateAllocationMetrics.js +96 -0
- package/dist/src/evaluation/stageContextMetrics/calculateCriticalityMetrics.js +112 -0
- package/dist/src/evaluation/stageContextMetrics/calculateOwnerMetrics.js +84 -0
- package/dist/src/evaluation/stageContextMetrics/calculateReadinessAgreement.js +152 -0
- package/dist/src/evaluation/stageContextMetrics/calculateSupplementalRawAgreement.js +50 -0
- package/dist/src/evaluation/stageContextMetrics/calculateTruncationClassification.js +44 -0
- package/dist/src/evaluation/stageContextMetrics/evaluateProducerReadinessBridge.js +127 -0
- package/dist/src/evaluation/stageContextMetrics/index.js +7 -0
- package/dist/src/evaluation/stageContextMetrics/producerReadinessMetricTypes.js +1 -0
- package/dist/src/evaluation/stageContextSelectors/contextArtifactConsistency.js +10 -0
- package/dist/src/evaluation/upstreamArtifacts/index.js +6 -0
- package/dist/src/evaluation/upstreamArtifacts/orchestratorContextReadinessResultV1.js +247 -0
- package/dist/src/evaluation/upstreamArtifacts/readImplementationContextPacketV1.js +9 -0
- package/dist/src/evaluation/upstreamArtifacts/readImplementationContextRetrievalReportV1.js +9 -0
- package/dist/src/evaluation/upstreamArtifacts/readSupplementalContextTextFile.js +29 -0
- package/dist/src/evaluation/upstreamArtifacts/readTestContextPacketV1.js +9 -0
- package/dist/src/evaluation/upstreamArtifacts/readTestContextRetrievalReportV1.js +9 -0
- package/dist/src/evaluation/upstreamArtifacts/supplementalContextTextParser.js +75 -0
- package/dist/src/evaluation/upstreamArtifacts/supplementalContextTypes.js +152 -0
- package/dist/src/evaluation/upstreamArtifacts/validateMyDevKitRetrievalAuditRecordV1.js +9 -2
- package/dist/src/evaluation/upstreamArtifacts/validateSupplementalContextDocumentV1.js +148 -0
- package/dist/src/experiments/plugins/contextStrategyComparison/loadV043StrategyArtifacts.js +117 -6
- package/dist/src/experiments/plugins/contextStrategyComparison/runV043StageContextStrategyWithAssurance.js +18 -3
- package/dist/src/experiments/plugins/contextStrategyComparison/validateV043StrategyInput.js +17 -3
- package/dist/src/report/experiments/buildContextStrategyComparisonV043Report.js +122 -0
- package/dist/src/report/experiments/renderContextStrategyComparisonV043Html.js +62 -0
- package/dist/src/report/experiments/renderPluginExperimentReportText.js +67 -0
- package/docs/METRICS.md +28 -0
- package/package.json +2 -2
- package/tests/fixtures/upstream-artifacts/my-dev-kit-1.10.2-identity/PROVENANCE.txt +13 -0
- package/tests/fixtures/upstream-artifacts/my-dev-kit-1.10.2-identity/context-capsule.json +7874 -0
- package/tests/fixtures/upstream-artifacts/my-dev-kit-1.10.2-identity/retrieval-audit-record.json +1076 -0
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { makeFailure } from "./runtimeValidation.js";
|
|
2
|
+
import { parseSupplementalContextText } from "./supplementalContextTextParser.js";
|
|
3
|
+
import { REQUIRED_METADATA_BY_DOCUMENT_KIND, REQUIRED_SECTIONS_BY_DOCUMENT_KIND, ROLE_BY_DOCUMENT_KIND, SUPPLEMENTAL_CONTEXT_PACKET_SCHEMA_SUPPORTED_MAJOR, SUPPLEMENTAL_CONTEXT_RETRIEVAL_REPORT_SCHEMA_SUPPORTED_MAJOR, VALID_ADEQUACY_VALUES, VALID_DECLARED_STATUSES, VALID_FRESHNESS_VALUES, VALID_REPOSITORY_SCOPES, VALID_TRUNCATION_VALUES, isRetrievalReportKind, isTestKind } from "./supplementalContextTypes.js";
|
|
4
|
+
const SCHEMA_VERSION_PATTERN = /^[0-9]+\.[0-9]+\.[0-9]+$/;
|
|
5
|
+
function supportedMajorForKind(kind) {
|
|
6
|
+
return isRetrievalReportKind(kind)
|
|
7
|
+
? SUPPLEMENTAL_CONTEXT_RETRIEVAL_REPORT_SCHEMA_SUPPORTED_MAJOR
|
|
8
|
+
: SUPPLEMENTAL_CONTEXT_PACKET_SCHEMA_SUPPORTED_MAJOR;
|
|
9
|
+
}
|
|
10
|
+
function literalOrFail(artifactKind, sourcePath, fieldPath, value, allowed, errorCode = "INVALID_LITERAL_VALUE") {
|
|
11
|
+
if (allowed.includes(value))
|
|
12
|
+
return value;
|
|
13
|
+
return {
|
|
14
|
+
ok: false,
|
|
15
|
+
artifactKind,
|
|
16
|
+
sourcePath,
|
|
17
|
+
code: errorCode,
|
|
18
|
+
message: `Field "${fieldPath}" in artifact "${artifactKind}" at "${sourcePath}" has invalid value "${value}". Expected one of: ${allowed.join(", ")}.`,
|
|
19
|
+
fieldPath,
|
|
20
|
+
expected: allowed.join(" | "),
|
|
21
|
+
actual: value
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
function isFailure(value) {
|
|
25
|
+
return typeof value === "object" && value !== null && value.ok === false;
|
|
26
|
+
}
|
|
27
|
+
export function validateSupplementalContextDocumentV1(artifactKind, expectedKind, text, sourcePath) {
|
|
28
|
+
const parsed = parseSupplementalContextText(text);
|
|
29
|
+
if (parsed.metadataOrder.length === 0 && parsed.sectionOrder.length === 0) {
|
|
30
|
+
return makeFailure(artifactKind, sourcePath, "MALFORMED_TEXT_ARTIFACT", `Artifact "${artifactKind}" at "${sourcePath}" contains no recognizable "Key: value" metadata or "## Heading" sections.`);
|
|
31
|
+
}
|
|
32
|
+
// Kind/role identity is checked before general required-metadata/section completeness so
|
|
33
|
+
// that feeding one document kind's file to another kind's reader is reported as a
|
|
34
|
+
// structural identity mismatch (DOCUMENT_KIND_MISMATCH / DOCUMENT_ROLE_MISMATCH) rather
|
|
35
|
+
// than as an incidental "missing metadata key" from the wrong kind's required-field list.
|
|
36
|
+
const declaredKind = parsed.metadata["Document kind"];
|
|
37
|
+
if (declaredKind !== expectedKind) {
|
|
38
|
+
return makeFailure(artifactKind, sourcePath, "DOCUMENT_KIND_MISMATCH", `Artifact "${artifactKind}" at "${sourcePath}" declares document kind "${declaredKind}" but this reader expects "${expectedKind}".`, "Document kind", expectedKind, declaredKind);
|
|
39
|
+
}
|
|
40
|
+
const expectedRole = ROLE_BY_DOCUMENT_KIND[expectedKind];
|
|
41
|
+
const declaredRole = parsed.metadata["Role"];
|
|
42
|
+
if (declaredRole !== expectedRole) {
|
|
43
|
+
return makeFailure(artifactKind, sourcePath, "DOCUMENT_ROLE_MISMATCH", `Artifact "${artifactKind}" at "${sourcePath}" declares role "${declaredRole}" but this reader expects "${expectedRole}".`, "Role", expectedRole, declaredRole);
|
|
44
|
+
}
|
|
45
|
+
const requiredMetadata = REQUIRED_METADATA_BY_DOCUMENT_KIND[expectedKind];
|
|
46
|
+
for (const key of requiredMetadata) {
|
|
47
|
+
if (!(key in parsed.metadata)) {
|
|
48
|
+
return makeFailure(artifactKind, sourcePath, "MISSING_REQUIRED_METADATA", `Artifact "${artifactKind}" at "${sourcePath}" is missing required metadata key "${key}".`, key, "present");
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const requiredSections = REQUIRED_SECTIONS_BY_DOCUMENT_KIND[expectedKind];
|
|
52
|
+
for (const heading of requiredSections) {
|
|
53
|
+
if (!(heading in parsed.sections)) {
|
|
54
|
+
return makeFailure(artifactKind, sourcePath, "MISSING_REQUIRED_SECTION", `Artifact "${artifactKind}" at "${sourcePath}" is missing required section heading "## ${heading}".`, heading, "present");
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
const schemaVersion = parsed.metadata["Schema version"];
|
|
58
|
+
if (!SCHEMA_VERSION_PATTERN.test(schemaVersion)) {
|
|
59
|
+
return makeFailure(artifactKind, sourcePath, "INVALID_SCHEMA_VERSION", `Artifact "${artifactKind}" at "${sourcePath}" has an invalid "Schema version": expected strict "x.y.z" semantic version.`, "Schema version", "^[0-9]+\\.[0-9]+\\.[0-9]+$", schemaVersion);
|
|
60
|
+
}
|
|
61
|
+
const major = Number(schemaVersion.split(".")[0]);
|
|
62
|
+
const supportedMajor = supportedMajorForKind(expectedKind);
|
|
63
|
+
if (major !== supportedMajor) {
|
|
64
|
+
return makeFailure(artifactKind, sourcePath, "UNSUPPORTED_SCHEMA_MAJOR", `Artifact "${artifactKind}" at "${sourcePath}" has unsupported schema major "${major}" in "Schema version": supported major is ${supportedMajor}.`, "Schema version", String(supportedMajor), schemaVersion);
|
|
65
|
+
}
|
|
66
|
+
const status = literalOrFail(artifactKind, sourcePath, "Status", parsed.metadata["Status"], VALID_DECLARED_STATUSES, "UNKNOWN_STATUS_VALUE");
|
|
67
|
+
if (isFailure(status))
|
|
68
|
+
return status;
|
|
69
|
+
const repositoryScope = literalOrFail(artifactKind, sourcePath, "Repository scope", parsed.metadata["Repository scope"], VALID_REPOSITORY_SCOPES);
|
|
70
|
+
if (isFailure(repositoryScope))
|
|
71
|
+
return repositoryScope;
|
|
72
|
+
const freshness = literalOrFail(artifactKind, sourcePath, "Freshness", parsed.metadata["Freshness"], VALID_FRESHNESS_VALUES);
|
|
73
|
+
if (isFailure(freshness))
|
|
74
|
+
return freshness;
|
|
75
|
+
const adequacy = literalOrFail(artifactKind, sourcePath, "Adequacy", parsed.metadata["Adequacy"], VALID_ADEQUACY_VALUES);
|
|
76
|
+
if (isFailure(adequacy))
|
|
77
|
+
return adequacy;
|
|
78
|
+
const requiredEvidenceTruncated = literalOrFail(artifactKind, sourcePath, "Required evidence truncated", parsed.metadata["Required evidence truncated"], VALID_TRUNCATION_VALUES);
|
|
79
|
+
if (isFailure(requiredEvidenceTruncated))
|
|
80
|
+
return requiredEvidenceTruncated;
|
|
81
|
+
const indexIdentity = parsed.metadata["Index identity"];
|
|
82
|
+
if (!indexIdentity || indexIdentity.length === 0) {
|
|
83
|
+
return makeFailure(artifactKind, sourcePath, "MISSING_CANONICAL_IDENTITY", `Artifact "${artifactKind}" at "${sourcePath}" is missing a non-empty canonical "Index identity".`, "Index identity", "present");
|
|
84
|
+
}
|
|
85
|
+
const document = {
|
|
86
|
+
documentKind: expectedKind,
|
|
87
|
+
role: expectedRole,
|
|
88
|
+
schemaVersion,
|
|
89
|
+
schemaMajor: 1,
|
|
90
|
+
status,
|
|
91
|
+
repositoryScope,
|
|
92
|
+
freshness,
|
|
93
|
+
adequacy,
|
|
94
|
+
requiredEvidenceTruncated,
|
|
95
|
+
contextCapsuleSchemaVersion: parsed.metadata["Context capsule schema version"],
|
|
96
|
+
retrievalAuditSchemaVersion: parsed.metadata["Retrieval audit schema version"],
|
|
97
|
+
toolName: parsed.metadata["Tool name"],
|
|
98
|
+
toolVersion: parsed.metadata["Tool version"],
|
|
99
|
+
indexIdentity,
|
|
100
|
+
rawMetadata: parsed.metadata,
|
|
101
|
+
sections: parsed.sections,
|
|
102
|
+
sectionOrder: parsed.sectionOrder,
|
|
103
|
+
metadataOrder: parsed.metadataOrder,
|
|
104
|
+
warnings: []
|
|
105
|
+
};
|
|
106
|
+
if (isRetrievalReportKind(expectedKind)) {
|
|
107
|
+
document.requestSchemaVersion = parsed.metadata["Request schema version"];
|
|
108
|
+
const fullFileFallbackUsed = literalOrFail(artifactKind, sourcePath, "Full-file fallback used", parsed.metadata["Full-file fallback used"], VALID_TRUNCATION_VALUES);
|
|
109
|
+
if (isFailure(fullFileFallbackUsed))
|
|
110
|
+
return fullFileFallbackUsed;
|
|
111
|
+
document.fullFileFallbackUsed = fullFileFallbackUsed;
|
|
112
|
+
const determinismChecked = literalOrFail(artifactKind, sourcePath, "Determinism checked", parsed.metadata["Determinism checked"], VALID_TRUNCATION_VALUES);
|
|
113
|
+
if (isFailure(determinismChecked))
|
|
114
|
+
return determinismChecked;
|
|
115
|
+
document.determinismChecked = determinismChecked;
|
|
116
|
+
}
|
|
117
|
+
if (isTestKind(expectedKind)) {
|
|
118
|
+
const responsibilityMappingsTruncated = literalOrFail(artifactKind, sourcePath, "Responsibility mappings truncated", parsed.metadata["Responsibility mappings truncated"], VALID_TRUNCATION_VALUES);
|
|
119
|
+
if (isFailure(responsibilityMappingsTruncated))
|
|
120
|
+
return responsibilityMappingsTruncated;
|
|
121
|
+
document.responsibilityMappingsTruncated = responsibilityMappingsTruncated;
|
|
122
|
+
// Preserved verbatim, never validated against the orchestrator's internal
|
|
123
|
+
// CriticalResponsibilityMappingState enum -- see supplementalContextTypes.ts.
|
|
124
|
+
const criticalStatus = parsed.metadata["Critical responsibility mapping status"];
|
|
125
|
+
if (!criticalStatus || criticalStatus.length === 0) {
|
|
126
|
+
return makeFailure(artifactKind, sourcePath, "MISSING_REQUIRED_METADATA", `Artifact "${artifactKind}" at "${sourcePath}" is missing a non-empty "Critical responsibility mapping status".`, "Critical responsibility mapping status", "present");
|
|
127
|
+
}
|
|
128
|
+
document.criticalResponsibilityMappingStatus = criticalStatus;
|
|
129
|
+
}
|
|
130
|
+
if (parsed.duplicateMetadataKeys.length > 0) {
|
|
131
|
+
document.warnings.push(`Duplicate metadata keys were ignored (first occurrence kept): ${parsed.duplicateMetadataKeys.join(", ")}.`);
|
|
132
|
+
}
|
|
133
|
+
if (parsed.duplicateSectionHeadings.length > 0) {
|
|
134
|
+
document.warnings.push(`Duplicate section headings were ignored (first occurrence kept): ${parsed.duplicateSectionHeadings.join(", ")}.`);
|
|
135
|
+
}
|
|
136
|
+
if (parsed.emptyMetadataKeys.length > 0) {
|
|
137
|
+
document.warnings.push(`Metadata keys had empty values: ${parsed.emptyMetadataKeys.join(", ")}.`);
|
|
138
|
+
}
|
|
139
|
+
return {
|
|
140
|
+
ok: true,
|
|
141
|
+
artifactKind,
|
|
142
|
+
sourcePath,
|
|
143
|
+
schemaVersion,
|
|
144
|
+
schemaMajor: 1,
|
|
145
|
+
artifact: document,
|
|
146
|
+
rawArtifact: { metadata: parsed.metadata, sections: parsed.sections }
|
|
147
|
+
};
|
|
148
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { readMyDevKitContextCapsuleV1, readMyDevKitRetrievalAuditRecordV1, readOrchestratorWorkflowInstructionPacketV1 } from "../../../evaluation/upstreamArtifacts/index.js";
|
|
1
|
+
import { readImplementationContextPacketV1, readImplementationContextRetrievalReportV1, readMyDevKitContextCapsuleV1, readMyDevKitRetrievalAuditRecordV1, readOrchestratorWorkflowInstructionPacketV1, readTestContextPacketV1, readTestContextRetrievalReportV1, validateOrchestratorContextReadinessResultV1 } from "../../../evaluation/upstreamArtifacts/index.js";
|
|
2
2
|
import { checkMyDevKitContextArtifactConsistency } from "../../../evaluation/stageContextSelectors/index.js";
|
|
3
3
|
import { readStageContextExpectationFixtureV1 } from "../../../evaluation/stageContextExpectations/index.js";
|
|
4
4
|
import { readV043FullWorkflowLibraryFixture } from "./readV043FullWorkflowLibraryFixture.js";
|
|
@@ -265,20 +265,131 @@ export async function loadV043StrategyArtifacts(input) {
|
|
|
265
265
|
details: packetResult
|
|
266
266
|
});
|
|
267
267
|
}
|
|
268
|
+
// v0.4.4 Batch 3: producer-readiness bridge inputs -- every one is optional (section
|
|
269
|
+
// 11.2). Only a supplied-but-unreadable source becomes a load failure; an absent
|
|
270
|
+
// source is simply left off the payload for the bridge evaluator to treat as
|
|
271
|
+
// unavailable/not-applicable.
|
|
272
|
+
let implementationContextPacketSourcePath;
|
|
273
|
+
let implementationContextPacket;
|
|
274
|
+
if (input.implementationContextPacketPath !== undefined) {
|
|
275
|
+
const result = await readImplementationContextPacketV1(input.implementationContextPacketPath);
|
|
276
|
+
if (!result.ok) {
|
|
277
|
+
issues.push({
|
|
278
|
+
code: "IMPLEMENTATION_CONTEXT_PACKET_READ_FAILED",
|
|
279
|
+
fieldPath: "implementationContextPacket",
|
|
280
|
+
message: `Failed to read implementation-context packet: ${result.message}`,
|
|
281
|
+
sourcePath: result.sourcePath,
|
|
282
|
+
details: result
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
else {
|
|
286
|
+
implementationContextPacketSourcePath = result.sourcePath;
|
|
287
|
+
implementationContextPacket = result.artifact;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
let implementationContextRetrievalReportSourcePath;
|
|
291
|
+
let implementationContextRetrievalReport;
|
|
292
|
+
if (input.implementationContextRetrievalReportPath !== undefined) {
|
|
293
|
+
const result = await readImplementationContextRetrievalReportV1(input.implementationContextRetrievalReportPath);
|
|
294
|
+
if (!result.ok) {
|
|
295
|
+
issues.push({
|
|
296
|
+
code: "IMPLEMENTATION_CONTEXT_RETRIEVAL_REPORT_READ_FAILED",
|
|
297
|
+
fieldPath: "implementationContextRetrievalReport",
|
|
298
|
+
message: `Failed to read implementation-context retrieval report: ${result.message}`,
|
|
299
|
+
sourcePath: result.sourcePath,
|
|
300
|
+
details: result
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
else {
|
|
304
|
+
implementationContextRetrievalReportSourcePath = result.sourcePath;
|
|
305
|
+
implementationContextRetrievalReport = result.artifact;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
let testContextPacketSourcePath;
|
|
309
|
+
let testContextPacket;
|
|
310
|
+
if (input.testContextPacketPath !== undefined) {
|
|
311
|
+
const result = await readTestContextPacketV1(input.testContextPacketPath);
|
|
312
|
+
if (!result.ok) {
|
|
313
|
+
issues.push({
|
|
314
|
+
code: "TEST_CONTEXT_PACKET_READ_FAILED",
|
|
315
|
+
fieldPath: "testContextPacket",
|
|
316
|
+
message: `Failed to read test-context packet: ${result.message}`,
|
|
317
|
+
sourcePath: result.sourcePath,
|
|
318
|
+
details: result
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
else {
|
|
322
|
+
testContextPacketSourcePath = result.sourcePath;
|
|
323
|
+
testContextPacket = result.artifact;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
let testContextRetrievalReportSourcePath;
|
|
327
|
+
let testContextRetrievalReport;
|
|
328
|
+
if (input.testContextRetrievalReportPath !== undefined) {
|
|
329
|
+
const result = await readTestContextRetrievalReportV1(input.testContextRetrievalReportPath);
|
|
330
|
+
if (!result.ok) {
|
|
331
|
+
issues.push({
|
|
332
|
+
code: "TEST_CONTEXT_RETRIEVAL_REPORT_READ_FAILED",
|
|
333
|
+
fieldPath: "testContextRetrievalReport",
|
|
334
|
+
message: `Failed to read test-context retrieval report: ${result.message}`,
|
|
335
|
+
sourcePath: result.sourcePath,
|
|
336
|
+
details: result
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
else {
|
|
340
|
+
testContextRetrievalReportSourcePath = result.sourcePath;
|
|
341
|
+
testContextRetrievalReport = result.artifact;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
let readiness;
|
|
345
|
+
if (input.readiness !== undefined) {
|
|
346
|
+
const readinessResult = validateOrchestratorContextReadinessResultV1(input.readiness, "programmatic-input:readiness");
|
|
347
|
+
if (!readinessResult.ok) {
|
|
348
|
+
issues.push({
|
|
349
|
+
code: "READINESS_INPUT_INVALID",
|
|
350
|
+
fieldPath: "readiness",
|
|
351
|
+
message: `Readiness programmatic input is invalid: ${readinessResult.message}`,
|
|
352
|
+
details: readinessResult
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
else {
|
|
356
|
+
readiness = readinessResult.artifact;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
268
359
|
if (issues.length > 0 || !expectationsResult.ok || !packetResult.ok || pairs.length !== input.contextArtifacts.length) {
|
|
269
360
|
return buildFailureResult(input, issues);
|
|
270
361
|
}
|
|
362
|
+
const payload = {
|
|
363
|
+
contextArtifacts: pairs,
|
|
364
|
+
workflowInstructionPacketSourcePath: packetResult.sourcePath,
|
|
365
|
+
workflowInstructionPacket: packetResult.artifact
|
|
366
|
+
};
|
|
367
|
+
if (implementationContextPacket !== undefined) {
|
|
368
|
+
payload.implementationContextPacketSourcePath = implementationContextPacketSourcePath;
|
|
369
|
+
payload.implementationContextPacket = implementationContextPacket;
|
|
370
|
+
}
|
|
371
|
+
if (implementationContextRetrievalReport !== undefined) {
|
|
372
|
+
payload.implementationContextRetrievalReportSourcePath = implementationContextRetrievalReportSourcePath;
|
|
373
|
+
payload.implementationContextRetrievalReport = implementationContextRetrievalReport;
|
|
374
|
+
}
|
|
375
|
+
if (testContextPacket !== undefined) {
|
|
376
|
+
payload.testContextPacketSourcePath = testContextPacketSourcePath;
|
|
377
|
+
payload.testContextPacket = testContextPacket;
|
|
378
|
+
}
|
|
379
|
+
if (testContextRetrievalReport !== undefined) {
|
|
380
|
+
payload.testContextRetrievalReportSourcePath = testContextRetrievalReportSourcePath;
|
|
381
|
+
payload.testContextRetrievalReport = testContextRetrievalReport;
|
|
382
|
+
}
|
|
383
|
+
if (readiness !== undefined) {
|
|
384
|
+
payload.readiness = readiness;
|
|
385
|
+
}
|
|
271
386
|
return {
|
|
272
387
|
status: "completed",
|
|
273
388
|
strategyId: input.strategyId,
|
|
274
389
|
input,
|
|
275
390
|
expectationsSourcePath: expectationsResult.sourcePath,
|
|
276
391
|
expectations: expectationsResult.fixture,
|
|
277
|
-
payload
|
|
278
|
-
contextArtifacts: pairs,
|
|
279
|
-
workflowInstructionPacketSourcePath: packetResult.sourcePath,
|
|
280
|
-
workflowInstructionPacket: packetResult.artifact
|
|
281
|
-
},
|
|
392
|
+
payload,
|
|
282
393
|
warnings: []
|
|
283
394
|
};
|
|
284
395
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { captureTargetSnapshot, compareTargetSnapshots } from "../../../evaluation/targetImmutability/index.js";
|
|
2
2
|
import { calculateStageContextDeterminism } from "../../../evaluation/stageContextDeterminism/index.js";
|
|
3
|
-
import { evaluateV043StageContextExecution } from "../../../evaluation/stageContextMetrics/index.js";
|
|
3
|
+
import { buildProducerReadinessBridgeInputFromCombinedPayload, evaluateProducerReadinessBridge, evaluateV043StageContextExecution } from "../../../evaluation/stageContextMetrics/index.js";
|
|
4
4
|
import { executeV043StageContextStrategy } from "./executeV043StageContextStrategy.js";
|
|
5
5
|
async function buildRunWithTargetImmutability(targetConfig, input) {
|
|
6
6
|
if (targetConfig === undefined) {
|
|
@@ -57,6 +57,15 @@ async function buildRunWithTargetImmutability(targetConfig, input) {
|
|
|
57
57
|
targetImmutability: { availability: "available", comparison, reason: null }
|
|
58
58
|
};
|
|
59
59
|
}
|
|
60
|
+
// v0.4.4 Batch 3: computed exactly once per run, immediately after execution, from the
|
|
61
|
+
// already-loaded execution payload -- never rereads artifacts, never reruns the strategy.
|
|
62
|
+
function buildProducerReadinessBridgeResult(execution) {
|
|
63
|
+
if (execution.status !== "completed" || execution.strategyId !== "combined-bounded-stage-context")
|
|
64
|
+
return null;
|
|
65
|
+
const payload = execution.payload;
|
|
66
|
+
const bridgeInput = buildProducerReadinessBridgeInputFromCombinedPayload(payload, execution.expectations);
|
|
67
|
+
return evaluateProducerReadinessBridge(bridgeInput);
|
|
68
|
+
}
|
|
60
69
|
function computeStatus(issues, config) {
|
|
61
70
|
if (issues.length > 0)
|
|
62
71
|
return "failed";
|
|
@@ -69,23 +78,27 @@ export async function runV043StageContextStrategyWithAssurance(input, config) {
|
|
|
69
78
|
const issues = [];
|
|
70
79
|
let primaryExecution;
|
|
71
80
|
let primaryEvaluation;
|
|
81
|
+
let primaryProducerReadinessBridge = null;
|
|
72
82
|
try {
|
|
73
83
|
for (let runNumber = 1; runNumber <= config.repeatCount; runNumber += 1) {
|
|
74
84
|
const { execution, targetImmutability } = await buildRunWithTargetImmutability(config.targetImmutability, input);
|
|
75
85
|
const evaluation = evaluateV043StageContextExecution(execution, { targetImmutability });
|
|
86
|
+
const producerReadinessBridge = buildProducerReadinessBridgeResult(execution);
|
|
76
87
|
if (runNumber === 1) {
|
|
77
88
|
primaryExecution = execution;
|
|
78
89
|
primaryEvaluation = evaluation;
|
|
90
|
+
primaryProducerReadinessBridge = producerReadinessBridge;
|
|
79
91
|
}
|
|
80
92
|
runRecords.push({
|
|
81
93
|
runNumber,
|
|
82
94
|
executionStatus: execution.status,
|
|
83
95
|
evaluationStatus: evaluation.status,
|
|
84
|
-
targetImmutability
|
|
96
|
+
targetImmutability,
|
|
97
|
+
producerReadinessBridge
|
|
85
98
|
});
|
|
86
99
|
determinismCandidates.push({
|
|
87
100
|
runNumber,
|
|
88
|
-
value: { execution, evaluation, targetImmutability }
|
|
101
|
+
value: { execution, evaluation, targetImmutability, producerReadinessBridge }
|
|
89
102
|
});
|
|
90
103
|
if (execution.status !== "completed") {
|
|
91
104
|
issues.push({
|
|
@@ -147,6 +160,7 @@ export async function runV043StageContextStrategyWithAssurance(input, config) {
|
|
|
147
160
|
repeatCount: config.repeatCount,
|
|
148
161
|
primaryExecution: primaryExecution,
|
|
149
162
|
primaryEvaluation: primaryEvaluation,
|
|
163
|
+
primaryProducerReadinessBridge,
|
|
150
164
|
runRecords,
|
|
151
165
|
determinism,
|
|
152
166
|
issues
|
|
@@ -175,6 +189,7 @@ export async function runV043StageContextStrategyWithAssurance(input, config) {
|
|
|
175
189
|
repeatCount: config.repeatCount,
|
|
176
190
|
primaryExecution: fallbackExecution,
|
|
177
191
|
primaryEvaluation: fallbackEvaluation,
|
|
192
|
+
primaryProducerReadinessBridge,
|
|
178
193
|
runRecords,
|
|
179
194
|
determinism: fallbackDeterminism,
|
|
180
195
|
issues: [
|
|
@@ -45,7 +45,12 @@ const STRATEGY_CONTRACTS = {
|
|
|
45
45
|
fields: [
|
|
46
46
|
{ name: "expectationsPath", kind: "required-path" },
|
|
47
47
|
{ name: "contextArtifacts", kind: "required-array" },
|
|
48
|
-
{ name: "workflowInstructionPacketPath", kind: "required-path" }
|
|
48
|
+
{ name: "workflowInstructionPacketPath", kind: "required-path" },
|
|
49
|
+
{ name: "implementationContextPacketPath", kind: "optional-path" },
|
|
50
|
+
{ name: "implementationContextRetrievalReportPath", kind: "optional-path" },
|
|
51
|
+
{ name: "testContextPacketPath", kind: "optional-path" },
|
|
52
|
+
{ name: "testContextRetrievalReportPath", kind: "optional-path" },
|
|
53
|
+
{ name: "readiness", kind: "optional-object" }
|
|
49
54
|
]
|
|
50
55
|
}
|
|
51
56
|
};
|
|
@@ -93,7 +98,7 @@ export function validateV043StrategyInput(value) {
|
|
|
93
98
|
}
|
|
94
99
|
}
|
|
95
100
|
for (const field of contract.fields) {
|
|
96
|
-
if (field.kind !== "optional-path" && root[field.name] === undefined) {
|
|
101
|
+
if (field.kind !== "optional-path" && field.kind !== "optional-object" && root[field.name] === undefined) {
|
|
97
102
|
issues.push({
|
|
98
103
|
code: "MISSING_REQUIRED_FIELD",
|
|
99
104
|
fieldPath: field.name,
|
|
@@ -114,6 +119,15 @@ export function validateV043StrategyInput(value) {
|
|
|
114
119
|
});
|
|
115
120
|
}
|
|
116
121
|
}
|
|
122
|
+
else if (field.kind === "optional-object") {
|
|
123
|
+
if (!isPlainObject(fieldValue)) {
|
|
124
|
+
issues.push({
|
|
125
|
+
code: "INVALID_FIELD_TYPE",
|
|
126
|
+
fieldPath: field.name,
|
|
127
|
+
message: `"${field.name}" must be an object.`
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
}
|
|
117
131
|
else if (typeof fieldValue !== "string") {
|
|
118
132
|
issues.push({
|
|
119
133
|
code: "INVALID_FIELD_TYPE",
|
|
@@ -123,7 +137,7 @@ export function validateV043StrategyInput(value) {
|
|
|
123
137
|
}
|
|
124
138
|
}
|
|
125
139
|
for (const field of contract.fields) {
|
|
126
|
-
if (field.kind === "required-array")
|
|
140
|
+
if (field.kind === "required-array" || field.kind === "optional-object")
|
|
127
141
|
continue;
|
|
128
142
|
const fieldValue = root[field.name];
|
|
129
143
|
if (typeof fieldValue === "string" && fieldValue.length === 0) {
|
|
@@ -69,12 +69,134 @@ function buildSummary(executions, evaluations, assurances) {
|
|
|
69
69
|
};
|
|
70
70
|
}
|
|
71
71
|
function buildStrategyReport(execution, evaluation, assurance) {
|
|
72
|
+
const producerReadinessBridge = buildProducerReadinessBridgeReport(assurance.primaryProducerReadinessBridge);
|
|
72
73
|
return {
|
|
73
74
|
strategyId: assurance.strategyId,
|
|
74
75
|
artifacts: buildArtifactMetadata(execution),
|
|
75
76
|
execution: buildExecutionSummary(execution),
|
|
76
77
|
evaluation: buildEvaluationSummary(evaluation),
|
|
77
78
|
assurance: buildAssuranceSummary(assurance),
|
|
79
|
+
...(producerReadinessBridge ? { producerReadinessBridge } : {}),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
function buildAllocationFactList(source) {
|
|
83
|
+
return boundItems(source.map((f) => ({ groupId: f.groupId, value: f.value, unit: f.unit, availability: f.availability, reason: f.reason })));
|
|
84
|
+
}
|
|
85
|
+
function buildOwnerEvaluation(owner) {
|
|
86
|
+
return {
|
|
87
|
+
selectedOwnerEvidence: boundItems(owner.selectedOwnerEvidence.map((e) => ({ ownerId: e.ownerId, sourceArtifact: e.sourceArtifact, sourceInstance: e.sourceInstance }))),
|
|
88
|
+
expectedOwnerPresent: buildRatioMetric(owner.expectedOwnerPresent),
|
|
89
|
+
forbiddenOwnerPresent: buildRatioMetric(owner.forbiddenOwnerPresent),
|
|
90
|
+
falsePositiveCount: buildCountMetric(owner.falsePositiveCount),
|
|
91
|
+
falseNegativeCount: buildCountMetric(owner.falseNegativeCount),
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
function buildTruncationList(source) {
|
|
95
|
+
return boundItems(source.map((t) => ({
|
|
96
|
+
groupId: t.groupId,
|
|
97
|
+
cause: t.cause,
|
|
98
|
+
availability: t.availability,
|
|
99
|
+
reason: t.reason,
|
|
100
|
+
omittedRequiredEvidenceIds: t.omittedRequiredEvidenceIds,
|
|
101
|
+
limit: t.limit,
|
|
102
|
+
used: t.used,
|
|
103
|
+
available: t.available,
|
|
104
|
+
})));
|
|
105
|
+
}
|
|
106
|
+
function buildSupplementalRawAgreement(agreement) {
|
|
107
|
+
if (agreement === null)
|
|
108
|
+
return null;
|
|
109
|
+
return {
|
|
110
|
+
fields: agreement.fields.map((f) => ({ field: f.field, rawValue: f.rawValue, supplementalValue: f.supplementalValue, agreement: f.agreement, availability: f.availability, reason: f.reason })),
|
|
111
|
+
contradictions: agreement.contradictions.map((f) => ({ field: f.field, rawValue: f.rawValue, supplementalValue: f.supplementalValue, agreement: f.agreement, availability: f.availability, reason: f.reason })),
|
|
112
|
+
upstreamProducerParityPreserved: agreement.upstreamProducerParityPreserved,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
function buildProducerReadinessSide(side) {
|
|
116
|
+
return {
|
|
117
|
+
role: side.role,
|
|
118
|
+
ownerEvaluation: buildOwnerEvaluation(side.ownerEvaluation),
|
|
119
|
+
allocationEvaluation: {
|
|
120
|
+
requiredGroupCapacity: buildAllocationFactList(side.allocationEvaluation.requiredGroupCapacity),
|
|
121
|
+
usedReservation: buildAllocationFactList(side.allocationEvaluation.usedReservation),
|
|
122
|
+
borrowedCapacity: buildAllocationFactList(side.allocationEvaluation.borrowedCapacity),
|
|
123
|
+
unusedCapacity: buildAllocationFactList(side.allocationEvaluation.unusedCapacity),
|
|
124
|
+
},
|
|
125
|
+
truncationEvaluation: buildTruncationList(side.truncationEvaluation),
|
|
126
|
+
packetAgreement: buildSupplementalRawAgreement(side.packetAgreement),
|
|
127
|
+
reportAgreement: buildSupplementalRawAgreement(side.reportAgreement),
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
function buildReadinessAgreement(agreement) {
|
|
131
|
+
return {
|
|
132
|
+
structuralAgreement: agreement.structuralAgreement.map((s) => ({
|
|
133
|
+
field: s.field,
|
|
134
|
+
expectedValue: s.expectedValue,
|
|
135
|
+
observedValue: s.observedValue,
|
|
136
|
+
agreement: s.agreement,
|
|
137
|
+
availability: s.availability,
|
|
138
|
+
reason: s.reason,
|
|
139
|
+
})),
|
|
140
|
+
decisionAgreement: {
|
|
141
|
+
availability: agreement.decisionAgreement.availability,
|
|
142
|
+
observedDecision: agreement.decisionAgreement.observedDecision,
|
|
143
|
+
allowedDecisions: agreement.decisionAgreement.allowedDecisions,
|
|
144
|
+
decisionAgreement: agreement.decisionAgreement.decisionAgreement,
|
|
145
|
+
observedIssueCodes: agreement.decisionAgreement.observedIssueCodes,
|
|
146
|
+
expectedIssueCodes: agreement.decisionAgreement.expectedIssueCodes,
|
|
147
|
+
issueCodesAgreement: agreement.decisionAgreement.issueCodesAgreement,
|
|
148
|
+
observedPrimaryIssueCode: agreement.decisionAgreement.observedPrimaryIssueCode,
|
|
149
|
+
expectedPrimaryIssueCode: agreement.decisionAgreement.expectedPrimaryIssueCode,
|
|
150
|
+
primaryIssueAgreement: agreement.decisionAgreement.primaryIssueAgreement,
|
|
151
|
+
reason: agreement.decisionAgreement.reason,
|
|
152
|
+
},
|
|
153
|
+
invalidReady: {
|
|
154
|
+
availability: agreement.invalidReady.availability,
|
|
155
|
+
invalidReady: agreement.invalidReady.invalidReady,
|
|
156
|
+
missingExpectedIssueCodes: agreement.invalidReady.missingExpectedIssueCodes,
|
|
157
|
+
reason: agreement.invalidReady.reason,
|
|
158
|
+
},
|
|
159
|
+
validBlocked: {
|
|
160
|
+
availability: agreement.validBlocked.availability,
|
|
161
|
+
validBlocked: agreement.validBlocked.validBlocked,
|
|
162
|
+
validRefreshRequired: agreement.validBlocked.validRefreshRequired,
|
|
163
|
+
observedDecision: agreement.validBlocked.observedDecision,
|
|
164
|
+
reason: agreement.validBlocked.reason,
|
|
165
|
+
},
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
function buildCriticalityEvaluation(criticality) {
|
|
169
|
+
return {
|
|
170
|
+
overlayAgreement: boundItems(criticality.overlayAgreement.map((e) => ({
|
|
171
|
+
responsibilityId: e.responsibilityId,
|
|
172
|
+
expectedCriticality: e.expectedCriticality,
|
|
173
|
+
observedCriticality: e.observedCriticality,
|
|
174
|
+
agreement: e.agreement,
|
|
175
|
+
availability: e.availability,
|
|
176
|
+
reason: e.reason,
|
|
177
|
+
}))),
|
|
178
|
+
missingCriticalityResponsibilityIds: criticality.missingCriticalityResponsibilityIds,
|
|
179
|
+
conflictingCriticalityResponsibilityIds: criticality.conflictingCriticalityResponsibilityIds,
|
|
180
|
+
unexpectedCriticalityResponsibilityIds: criticality.unexpectedCriticalityResponsibilityIds,
|
|
181
|
+
mappedCriticalCompleteness: buildRatioMetric(criticality.mappedCriticalCompleteness),
|
|
182
|
+
fullyMappedCriticalIds: criticality.fullyMappedCriticalIds,
|
|
183
|
+
partiallyMappedCriticalIds: criticality.partiallyMappedCriticalIds,
|
|
184
|
+
unmappedCriticalIds: criticality.unmappedCriticalIds,
|
|
185
|
+
missingMappingEvidenceCriticalIds: criticality.missingMappingEvidenceCriticalIds,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
function buildProducerReadinessBridgeReport(bridge) {
|
|
189
|
+
if (bridge === null || bridge === undefined)
|
|
190
|
+
return null;
|
|
191
|
+
return {
|
|
192
|
+
status: bridge.status,
|
|
193
|
+
reason: bridge.reason,
|
|
194
|
+
implementation: bridge.implementation ? buildProducerReadinessSide(bridge.implementation) : null,
|
|
195
|
+
testImplementation: bridge.testImplementation ? buildProducerReadinessSide(bridge.testImplementation) : null,
|
|
196
|
+
readinessAgreement: bridge.readinessAgreement ? buildReadinessAgreement(bridge.readinessAgreement) : null,
|
|
197
|
+
criticalityEvaluation: bridge.criticalityEvaluation ? buildCriticalityEvaluation(bridge.criticalityEvaluation) : null,
|
|
198
|
+
warnings: boundItems(bridge.warnings),
|
|
199
|
+
evidenceReferences: boundItems(bridge.evidenceReferences),
|
|
78
200
|
};
|
|
79
201
|
}
|
|
80
202
|
function buildExecutionSummary(execution) {
|
|
@@ -187,8 +187,70 @@ function renderStrategySection(strategy, index) {
|
|
|
187
187
|
${table(["Status", "Repeat Count"], [[strategy.assurance.status, String(strategy.assurance.repeatCount)]])}
|
|
188
188
|
${boundedListNote(strategy.assurance.issues)}
|
|
189
189
|
${table(["Code", "Run", "Field Path", "Message"], assuranceIssueRows)}
|
|
190
|
+
${strategy.producerReadinessBridge ? renderProducerReadinessBridgeSection(strategy.producerReadinessBridge) : ""}
|
|
190
191
|
</section>`;
|
|
191
192
|
}
|
|
193
|
+
function renderProducerReadinessSideSection(label, side) {
|
|
194
|
+
if (side === null) {
|
|
195
|
+
return `<h4>${escapeHtml(label)}</h4><p class="muted">Not supplied.</p>`;
|
|
196
|
+
}
|
|
197
|
+
const truncationRows = side.truncationEvaluation.items.map((t) => [
|
|
198
|
+
t.groupId,
|
|
199
|
+
t.cause,
|
|
200
|
+
t.availability,
|
|
201
|
+
String(t.limit),
|
|
202
|
+
String(t.used),
|
|
203
|
+
t.reason ?? "",
|
|
204
|
+
]);
|
|
205
|
+
function agreementSection(agreementLabel, agreement) {
|
|
206
|
+
if (agreement === null) {
|
|
207
|
+
return `<h5>${escapeHtml(agreementLabel)}</h5><p class="muted">Unavailable.</p>`;
|
|
208
|
+
}
|
|
209
|
+
const rows = agreement.contradictions.map((c) => [c.field, String(c.rawValue), String(c.supplementalValue), c.availability]);
|
|
210
|
+
return `<h5>${escapeHtml(agreementLabel)}</h5>
|
|
211
|
+
<p>Producer parity preserved: ${escapeHtml(agreement.upstreamProducerParityPreserved)}</p>
|
|
212
|
+
${table(["Field", "Raw Value", "Supplemental Value", "Availability"], rows)}`;
|
|
213
|
+
}
|
|
214
|
+
return `<h4>${escapeHtml(label)}</h4>
|
|
215
|
+
${renderRatioMetricTable("Expected Owner Present", side.ownerEvaluation.expectedOwnerPresent)}
|
|
216
|
+
${renderRatioMetricTable("Forbidden Owner Present", side.ownerEvaluation.forbiddenOwnerPresent)}
|
|
217
|
+
${renderCountMetricTable("Owner False Positives", side.ownerEvaluation.falsePositiveCount)}
|
|
218
|
+
${renderCountMetricTable("Owner False Negatives", side.ownerEvaluation.falseNegativeCount)}
|
|
219
|
+
<h5>Truncation Classification</h5>
|
|
220
|
+
${boundedListNote(side.truncationEvaluation)}
|
|
221
|
+
${table(["Group", "Cause", "Availability", "Limit", "Used", "Reason"], truncationRows)}
|
|
222
|
+
${agreementSection("Packet/Raw Agreement", side.packetAgreement)}
|
|
223
|
+
${agreementSection("Report/Raw Agreement", side.reportAgreement)}`;
|
|
224
|
+
}
|
|
225
|
+
function renderProducerReadinessBridgeSection(bridge) {
|
|
226
|
+
if (bridge.status === "not-applicable") {
|
|
227
|
+
return `<h4>Producer-Readiness Bridge</h4><p class="muted">${escapeHtml(bridge.reason)}</p>`;
|
|
228
|
+
}
|
|
229
|
+
const criticality = bridge.criticalityEvaluation;
|
|
230
|
+
return `<h4>Producer-Readiness Bridge</h4>
|
|
231
|
+
${renderProducerReadinessSideSection("Implementation", bridge.implementation)}
|
|
232
|
+
${renderProducerReadinessSideSection("Test", bridge.testImplementation)}
|
|
233
|
+
<h5>Readiness Agreement</h5>
|
|
234
|
+
${bridge.readinessAgreement === null
|
|
235
|
+
? `<p class="muted">Not supplied.</p>`
|
|
236
|
+
: table(["Observed Decision", "Decision Agreement", "Issue-Code Agreement", "Invalid Ready", "Valid Blocked", "Valid Refresh-Required"], [
|
|
237
|
+
[
|
|
238
|
+
String(bridge.readinessAgreement.decisionAgreement.observedDecision),
|
|
239
|
+
String(bridge.readinessAgreement.decisionAgreement.decisionAgreement),
|
|
240
|
+
String(bridge.readinessAgreement.decisionAgreement.issueCodesAgreement),
|
|
241
|
+
String(bridge.readinessAgreement.invalidReady.invalidReady),
|
|
242
|
+
String(bridge.readinessAgreement.validBlocked.validBlocked),
|
|
243
|
+
String(bridge.readinessAgreement.validBlocked.validRefreshRequired),
|
|
244
|
+
],
|
|
245
|
+
])}
|
|
246
|
+
<h5>Criticality Evaluation</h5>
|
|
247
|
+
${criticality === null
|
|
248
|
+
? `<p class="muted">Not supplied.</p>`
|
|
249
|
+
: `${renderRatioMetricTable("Mapped Critical Completeness", criticality.mappedCriticalCompleteness)}
|
|
250
|
+
<p>Partially mapped (excluded from complete): ${escapeHtml(criticality.partiallyMappedCriticalIds.join(", ") || "none")}</p>
|
|
251
|
+
<p>Unmapped: ${escapeHtml(criticality.unmappedCriticalIds.join(", ") || "none")}</p>
|
|
252
|
+
<p>Conflicting: ${escapeHtml(criticality.conflictingCriticalityResponsibilityIds.join(", ") || "none")}</p>`}`;
|
|
253
|
+
}
|
|
192
254
|
export function renderContextStrategyComparisonV043Html(report) {
|
|
193
255
|
if (report === null) {
|
|
194
256
|
return `<section><h2>V0.4.3 Stage-Context Evidence</h2><p>Not applicable to this plugin.</p></section>`;
|