@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
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ my-dev-kit-lab is the experiment, audit, and evidence companion for [my-dev-kit]
|
|
|
4
4
|
|
|
5
5
|
my-dev-kit provides local repository indexing and graph-guided retrieval. my-dev-kit-lab supplies the controlled benchmarks, agent adapters, metrics, security checks, and reports needed to evaluate when that retrieval is useful. Results are evidence for a specific target and configuration; they do not guarantee token savings or security.
|
|
6
6
|
|
|
7
|
-
The latest published release is v0.4.
|
|
7
|
+
The latest published release is v0.4.4 (producer-readiness bridge). See [docs/CURRENT_STATE.md](docs/CURRENT_STATE.md).
|
|
8
8
|
|
|
9
9
|
## Current capabilities
|
|
10
10
|
|
|
@@ -14,6 +14,7 @@ The latest published release is v0.4.3 (stage-specific bounded-context and workf
|
|
|
14
14
|
- **Validate Android projects:** run nineteen static checks by default, with Gradle operations, external tools, and network access available only through explicit opt-in flags.
|
|
15
15
|
- **Review evidence:** generate JSON and HTML reports, SVG plots, optional screenshots, visualization demos, and a static gallery.
|
|
16
16
|
- **Evaluate stage-context strategies:** compare the two legacy strategies against six additional bounded stage-context strategies — `architecture-context-only`, `architecture-plus-implementation-refresh`, `architecture-plus-implementation-and-test-refresh`, `full-workflow-library`, `bounded-workflow-instruction-packet`, and `combined-bounded-stage-context` — selected through programmatic configuration, not CLI flags. Each strategy's evidence is reported through bounded `report.json`, `report.html`, and `report.txt` output with an explicit `available`/`unavailable`/`not-applicable` metric-availability model and no composite score, grade, ranking, or winning strategy.
|
|
17
|
+
- **Evaluate the producer-readiness bridge (v0.4.4):** optionally extend `combined-bounded-stage-context` with the frozen my-dev-kit-orchestrator supplemental implementation/test-context packet and retrieval-report documents plus an observed readiness result, all supplied programmatically (there is no CLI flag), to measure owner, allocation, truncation-cause, supplemental/raw agreement, readiness-agreement, and criticality-overlay evidence without reimplementing upstream owner-selection, allocation, producer-parity, or readiness policy.
|
|
17
18
|
|
|
18
19
|
## Quickstart
|
|
19
20
|
|
|
@@ -34,9 +35,12 @@ npm run build
|
|
|
34
35
|
### Verify the installation
|
|
35
36
|
|
|
36
37
|
```bash
|
|
38
|
+
npm run test
|
|
37
39
|
npm run verify
|
|
38
40
|
```
|
|
39
41
|
|
|
42
|
+
`npm test` runs the complete test suite; `npm run verify` runs the remaining non-test verification gates. Running both is complete validation and does not execute the suite twice.
|
|
43
|
+
|
|
40
44
|
### Run the fake-agent final demo (deterministic, no external CLIs required)
|
|
41
45
|
|
|
42
46
|
```bash
|
|
@@ -162,6 +166,7 @@ See [docs/METRICS.md](docs/METRICS.md) for full metric definitions.
|
|
|
162
166
|
- Provider telemetry dashboards, semantic LLM judging, and cloud API billing integration are not yet implemented
|
|
163
167
|
- The six new stage-context strategies have no CLI flags yet, are configured programmatically, and do not yet include plots, screenshots, or gallery integration
|
|
164
168
|
- The published upstream artifacts the stage-context strategies read do not expose considered-but-unselected reads or unnecessary-read evidence; those metrics report `unavailable` rather than zero
|
|
169
|
+
- The v0.4.4 producer-readiness bridge is released. All bridge inputs are programmatic — there is no CLI flag. The coordinated upstream releases my-dev-kit@1.10.3 and orchestrator@1.2.2 were verified published before lab publication.
|
|
165
170
|
|
|
166
171
|
---
|
|
167
172
|
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// v0.4.4 Batch 2: additive, optional expectation facets covering the producer/readiness
|
|
2
|
+
// evidence Batch 1 preserves (selected owners, allocation/truncation evidence on the raw
|
|
3
|
+
// my-dev-kit artifacts, orchestrator readiness, and responsibility criticality). These are
|
|
4
|
+
// a separate axis from StageContextExpectationItemV1/expectedEvidence -- they are the
|
|
5
|
+
// deterministic lab oracle for Batch 2 metrics, not evidence-match expectations. Fully
|
|
6
|
+
// optional so every existing v0.4.3 StageContextExpectationFixtureV1 remains valid and
|
|
7
|
+
// unchanged (schema major stays 1).
|
|
8
|
+
export {};
|
|
@@ -199,6 +199,11 @@ export function validateStageContextExpectationFixtureV1(value, sourcePath) {
|
|
|
199
199
|
const statesFailure = validateExpectedStates(root.expectedStates, sourcePath);
|
|
200
200
|
if (statesFailure)
|
|
201
201
|
return statesFailure;
|
|
202
|
+
if (root.producerReadinessExpectations !== undefined) {
|
|
203
|
+
const producerReadinessFailure = validateProducerReadinessExpectations(root.producerReadinessExpectations, sourcePath);
|
|
204
|
+
if (producerReadinessFailure)
|
|
205
|
+
return producerReadinessFailure;
|
|
206
|
+
}
|
|
202
207
|
return {
|
|
203
208
|
ok: true,
|
|
204
209
|
sourcePath,
|
|
@@ -566,3 +571,198 @@ function validateExpectedStates(statesValue, sourcePath) {
|
|
|
566
571
|
return immutabilityFailure;
|
|
567
572
|
return null;
|
|
568
573
|
}
|
|
574
|
+
// v0.4.4 Batch 2: additive, optional producer/readiness expectation validation. Structural
|
|
575
|
+
// classification only, mirroring the existing expectation-item validation conventions.
|
|
576
|
+
const OWNER_INCLUSION_VALUES = ["required", "allowed", "forbidden"];
|
|
577
|
+
const READINESS_KIND_VALUES = ["implementation", "test"];
|
|
578
|
+
const READINESS_DECISION_VALUES = ["not-required", "ready", "refresh-required"];
|
|
579
|
+
const CRITICALITY_VALUES = ["critical", "noncritical"];
|
|
580
|
+
const PRODUCER_READINESS_SOURCE_ARTIFACT_VALUES = ["context-capsule", "retrieval-audit-record"];
|
|
581
|
+
function requireNonEmptyExpectationId(item, itemPath, sourcePath) {
|
|
582
|
+
return requireNonEmptyString(item, "expectationId", `${itemPath}.expectationId`, sourcePath);
|
|
583
|
+
}
|
|
584
|
+
function validateOwnerExpectation(itemValue, itemPath, sourcePath) {
|
|
585
|
+
if (!isPlainObject(itemValue)) {
|
|
586
|
+
return failure(sourcePath, "INVALID_FIELD_TYPE", `${itemPath} must be an object.`, { fieldPath: itemPath });
|
|
587
|
+
}
|
|
588
|
+
const idFailure = requireNonEmptyExpectationId(itemValue, itemPath, sourcePath);
|
|
589
|
+
if (idFailure)
|
|
590
|
+
return idFailure;
|
|
591
|
+
if (!OWNER_INCLUSION_VALUES.includes(itemValue.inclusion)) {
|
|
592
|
+
return failure(sourcePath, "INVALID_LITERAL_VALUE", `${itemPath}.inclusion is not valid.`, {
|
|
593
|
+
fieldPath: `${itemPath}.inclusion`,
|
|
594
|
+
actual: itemValue.inclusion
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
if (!PRODUCER_READINESS_SOURCE_ARTIFACT_VALUES.includes(itemValue.sourceArtifact)) {
|
|
598
|
+
return failure(sourcePath, "INVALID_LITERAL_VALUE", `${itemPath}.sourceArtifact is not valid.`, {
|
|
599
|
+
fieldPath: `${itemPath}.sourceArtifact`,
|
|
600
|
+
actual: itemValue.sourceArtifact
|
|
601
|
+
});
|
|
602
|
+
}
|
|
603
|
+
const ownerIdFailure = requireNonEmptyString(itemValue, "ownerId", `${itemPath}.ownerId`, sourcePath);
|
|
604
|
+
if (ownerIdFailure)
|
|
605
|
+
return ownerIdFailure;
|
|
606
|
+
if (itemValue.notes === undefined || !isStringArray(itemValue.notes)) {
|
|
607
|
+
return failure(sourcePath, "INVALID_FIELD_TYPE", `${itemPath}.notes must be an array of strings.`, {
|
|
608
|
+
fieldPath: `${itemPath}.notes`
|
|
609
|
+
});
|
|
610
|
+
}
|
|
611
|
+
return null;
|
|
612
|
+
}
|
|
613
|
+
function validateAllocationExpectation(itemValue, itemPath, sourcePath) {
|
|
614
|
+
if (!isPlainObject(itemValue)) {
|
|
615
|
+
return failure(sourcePath, "INVALID_FIELD_TYPE", `${itemPath} must be an object.`, { fieldPath: itemPath });
|
|
616
|
+
}
|
|
617
|
+
const idFailure = requireNonEmptyExpectationId(itemValue, itemPath, sourcePath);
|
|
618
|
+
if (idFailure)
|
|
619
|
+
return idFailure;
|
|
620
|
+
const groupIdFailure = requireNonEmptyString(itemValue, "groupId", `${itemPath}.groupId`, sourcePath);
|
|
621
|
+
if (groupIdFailure)
|
|
622
|
+
return groupIdFailure;
|
|
623
|
+
if (itemValue.expectedCapacity !== undefined && itemValue.expectedCapacity !== null && !isNonNegativeInteger(itemValue.expectedCapacity)) {
|
|
624
|
+
return failure(sourcePath, "INVALID_FIELD_TYPE", `${itemPath}.expectedCapacity must be a nonnegative integer or null.`, {
|
|
625
|
+
fieldPath: `${itemPath}.expectedCapacity`
|
|
626
|
+
});
|
|
627
|
+
}
|
|
628
|
+
if (itemValue.expectedUsedReservation !== undefined &&
|
|
629
|
+
itemValue.expectedUsedReservation !== null &&
|
|
630
|
+
!isNonNegativeInteger(itemValue.expectedUsedReservation)) {
|
|
631
|
+
return failure(sourcePath, "INVALID_FIELD_TYPE", `${itemPath}.expectedUsedReservation must be a nonnegative integer or null.`, {
|
|
632
|
+
fieldPath: `${itemPath}.expectedUsedReservation`
|
|
633
|
+
});
|
|
634
|
+
}
|
|
635
|
+
if (itemValue.expectedRequiredEvidenceIds !== undefined && !isStringArray(itemValue.expectedRequiredEvidenceIds)) {
|
|
636
|
+
return failure(sourcePath, "INVALID_FIELD_TYPE", `${itemPath}.expectedRequiredEvidenceIds must be an array of strings.`, {
|
|
637
|
+
fieldPath: `${itemPath}.expectedRequiredEvidenceIds`
|
|
638
|
+
});
|
|
639
|
+
}
|
|
640
|
+
if (itemValue.expectedOmitted !== undefined && typeof itemValue.expectedOmitted !== "boolean") {
|
|
641
|
+
return failure(sourcePath, "INVALID_FIELD_TYPE", `${itemPath}.expectedOmitted must be a boolean.`, {
|
|
642
|
+
fieldPath: `${itemPath}.expectedOmitted`
|
|
643
|
+
});
|
|
644
|
+
}
|
|
645
|
+
if (itemValue.notes === undefined || !isStringArray(itemValue.notes)) {
|
|
646
|
+
return failure(sourcePath, "INVALID_FIELD_TYPE", `${itemPath}.notes must be an array of strings.`, {
|
|
647
|
+
fieldPath: `${itemPath}.notes`
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
return null;
|
|
651
|
+
}
|
|
652
|
+
function validateReadinessExpectation(itemValue, itemPath, sourcePath) {
|
|
653
|
+
if (!isPlainObject(itemValue)) {
|
|
654
|
+
return failure(sourcePath, "INVALID_FIELD_TYPE", `${itemPath} must be an object.`, { fieldPath: itemPath });
|
|
655
|
+
}
|
|
656
|
+
const idFailure = requireNonEmptyExpectationId(itemValue, itemPath, sourcePath);
|
|
657
|
+
if (idFailure)
|
|
658
|
+
return idFailure;
|
|
659
|
+
if (!READINESS_KIND_VALUES.includes(itemValue.kind)) {
|
|
660
|
+
return failure(sourcePath, "INVALID_LITERAL_VALUE", `${itemPath}.kind is not valid.`, {
|
|
661
|
+
fieldPath: `${itemPath}.kind`,
|
|
662
|
+
actual: itemValue.kind
|
|
663
|
+
});
|
|
664
|
+
}
|
|
665
|
+
if (itemValue.allowedDecisions !== undefined) {
|
|
666
|
+
if (!isStringArray(itemValue.allowedDecisions) || itemValue.allowedDecisions.some((d) => !READINESS_DECISION_VALUES.includes(d))) {
|
|
667
|
+
return failure(sourcePath, "INVALID_LITERAL_VALUE", `${itemPath}.allowedDecisions contains an invalid decision value.`, {
|
|
668
|
+
fieldPath: `${itemPath}.allowedDecisions`
|
|
669
|
+
});
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
if (itemValue.expectedClassification !== undefined && typeof itemValue.expectedClassification !== "string") {
|
|
673
|
+
return failure(sourcePath, "INVALID_FIELD_TYPE", `${itemPath}.expectedClassification must be a string.`, {
|
|
674
|
+
fieldPath: `${itemPath}.expectedClassification`
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
if (itemValue.expectedIssueCodes !== undefined && !isStringArray(itemValue.expectedIssueCodes)) {
|
|
678
|
+
return failure(sourcePath, "INVALID_FIELD_TYPE", `${itemPath}.expectedIssueCodes must be an array of strings.`, {
|
|
679
|
+
fieldPath: `${itemPath}.expectedIssueCodes`
|
|
680
|
+
});
|
|
681
|
+
}
|
|
682
|
+
if (itemValue.expectedPrimaryIssueCode !== undefined && typeof itemValue.expectedPrimaryIssueCode !== "string") {
|
|
683
|
+
return failure(sourcePath, "INVALID_FIELD_TYPE", `${itemPath}.expectedPrimaryIssueCode must be a string.`, {
|
|
684
|
+
fieldPath: `${itemPath}.expectedPrimaryIssueCode`
|
|
685
|
+
});
|
|
686
|
+
}
|
|
687
|
+
if (itemValue.notApplicable !== undefined && typeof itemValue.notApplicable !== "boolean") {
|
|
688
|
+
return failure(sourcePath, "INVALID_FIELD_TYPE", `${itemPath}.notApplicable must be a boolean.`, {
|
|
689
|
+
fieldPath: `${itemPath}.notApplicable`
|
|
690
|
+
});
|
|
691
|
+
}
|
|
692
|
+
if (itemValue.notes === undefined || !isStringArray(itemValue.notes)) {
|
|
693
|
+
return failure(sourcePath, "INVALID_FIELD_TYPE", `${itemPath}.notes must be an array of strings.`, {
|
|
694
|
+
fieldPath: `${itemPath}.notes`
|
|
695
|
+
});
|
|
696
|
+
}
|
|
697
|
+
return null;
|
|
698
|
+
}
|
|
699
|
+
function validateCriticalityExpectation(itemValue, itemPath, sourcePath) {
|
|
700
|
+
if (!isPlainObject(itemValue)) {
|
|
701
|
+
return failure(sourcePath, "INVALID_FIELD_TYPE", `${itemPath} must be an object.`, { fieldPath: itemPath });
|
|
702
|
+
}
|
|
703
|
+
const idFailure = requireNonEmptyExpectationId(itemValue, itemPath, sourcePath);
|
|
704
|
+
if (idFailure)
|
|
705
|
+
return idFailure;
|
|
706
|
+
const responsibilityIdFailure = requireNonEmptyString(itemValue, "responsibilityId", `${itemPath}.responsibilityId`, sourcePath);
|
|
707
|
+
if (responsibilityIdFailure)
|
|
708
|
+
return responsibilityIdFailure;
|
|
709
|
+
if (!CRITICALITY_VALUES.includes(itemValue.expectedCriticality)) {
|
|
710
|
+
return failure(sourcePath, "INVALID_LITERAL_VALUE", `${itemPath}.expectedCriticality is not valid.`, {
|
|
711
|
+
fieldPath: `${itemPath}.expectedCriticality`,
|
|
712
|
+
actual: itemValue.expectedCriticality
|
|
713
|
+
});
|
|
714
|
+
}
|
|
715
|
+
if (typeof itemValue.applicable !== "boolean") {
|
|
716
|
+
return failure(sourcePath, "INVALID_FIELD_TYPE", `${itemPath}.applicable must be a boolean.`, {
|
|
717
|
+
fieldPath: `${itemPath}.applicable`
|
|
718
|
+
});
|
|
719
|
+
}
|
|
720
|
+
if (typeof itemValue.requiresFullMapping !== "boolean") {
|
|
721
|
+
return failure(sourcePath, "INVALID_FIELD_TYPE", `${itemPath}.requiresFullMapping must be a boolean.`, {
|
|
722
|
+
fieldPath: `${itemPath}.requiresFullMapping`
|
|
723
|
+
});
|
|
724
|
+
}
|
|
725
|
+
if (itemValue.notes === undefined || !isStringArray(itemValue.notes)) {
|
|
726
|
+
return failure(sourcePath, "INVALID_FIELD_TYPE", `${itemPath}.notes must be an array of strings.`, {
|
|
727
|
+
fieldPath: `${itemPath}.notes`
|
|
728
|
+
});
|
|
729
|
+
}
|
|
730
|
+
return null;
|
|
731
|
+
}
|
|
732
|
+
function validateProducerReadinessExpectations(value, sourcePath) {
|
|
733
|
+
const fieldPath = "producerReadinessExpectations";
|
|
734
|
+
if (!isPlainObject(value)) {
|
|
735
|
+
return failure(sourcePath, "INVALID_FIELD_TYPE", `${fieldPath} must be an object.`, { fieldPath });
|
|
736
|
+
}
|
|
737
|
+
const lists = [
|
|
738
|
+
["ownerExpectations", value.ownerExpectations, validateOwnerExpectation],
|
|
739
|
+
["allocationExpectations", value.allocationExpectations, validateAllocationExpectation],
|
|
740
|
+
["readinessExpectations", value.readinessExpectations, validateReadinessExpectation],
|
|
741
|
+
["criticalityExpectations", value.criticalityExpectations, validateCriticalityExpectation]
|
|
742
|
+
];
|
|
743
|
+
const seenIds = new Set();
|
|
744
|
+
for (const [key, listValue, validateItem] of lists) {
|
|
745
|
+
if (listValue === undefined)
|
|
746
|
+
continue;
|
|
747
|
+
if (!Array.isArray(listValue)) {
|
|
748
|
+
return failure(sourcePath, "INVALID_FIELD_TYPE", `${fieldPath}.${key} must be an array.`, {
|
|
749
|
+
fieldPath: `${fieldPath}.${key}`
|
|
750
|
+
});
|
|
751
|
+
}
|
|
752
|
+
for (let index = 0; index < listValue.length; index += 1) {
|
|
753
|
+
const itemPath = `${fieldPath}.${key}[${index}]`;
|
|
754
|
+
const itemFailure = validateItem(listValue[index], itemPath, sourcePath);
|
|
755
|
+
if (itemFailure)
|
|
756
|
+
return itemFailure;
|
|
757
|
+
const id = listValue[index].expectationId;
|
|
758
|
+
if (seenIds.has(id)) {
|
|
759
|
+
return failure(sourcePath, "DUPLICATE_PRODUCER_READINESS_EXPECTATION_ID", `${itemPath}.expectationId "${id}" is duplicated.`, {
|
|
760
|
+
fieldPath: `${itemPath}.expectationId`,
|
|
761
|
+
actual: id
|
|
762
|
+
});
|
|
763
|
+
}
|
|
764
|
+
seenIds.add(id);
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
return null;
|
|
768
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
const NO_BORROW_EVIDENCE_REASON = "The frozen my-dev-kit artifacts do not expose cross-group capacity borrowing evidence.";
|
|
2
|
+
export function calculateAllocationFactsForGroups(evidenceGroups, sourceArtifact, sourceInstance) {
|
|
3
|
+
const requiredGroupCapacity = [];
|
|
4
|
+
const usedReservation = [];
|
|
5
|
+
const borrowedCapacity = [];
|
|
6
|
+
const unusedCapacity = [];
|
|
7
|
+
for (const group of evidenceGroups) {
|
|
8
|
+
requiredGroupCapacity.push({
|
|
9
|
+
groupId: group.id,
|
|
10
|
+
sourceArtifact,
|
|
11
|
+
sourceInstance,
|
|
12
|
+
value: group.limit,
|
|
13
|
+
unit: "items",
|
|
14
|
+
availability: group.limit === null ? "unavailable" : "available",
|
|
15
|
+
reason: group.limit === null ? "No hard limit is declared for this evidence group." : null
|
|
16
|
+
});
|
|
17
|
+
usedReservation.push({
|
|
18
|
+
groupId: group.id,
|
|
19
|
+
sourceArtifact,
|
|
20
|
+
sourceInstance,
|
|
21
|
+
value: group.usedCount,
|
|
22
|
+
unit: "items",
|
|
23
|
+
availability: "available",
|
|
24
|
+
reason: null
|
|
25
|
+
});
|
|
26
|
+
borrowedCapacity.push({
|
|
27
|
+
groupId: group.id,
|
|
28
|
+
sourceArtifact,
|
|
29
|
+
sourceInstance,
|
|
30
|
+
value: null,
|
|
31
|
+
unit: "items",
|
|
32
|
+
availability: "unavailable",
|
|
33
|
+
reason: NO_BORROW_EVIDENCE_REASON
|
|
34
|
+
});
|
|
35
|
+
if (group.limit === null) {
|
|
36
|
+
unusedCapacity.push({
|
|
37
|
+
groupId: group.id,
|
|
38
|
+
sourceArtifact,
|
|
39
|
+
sourceInstance,
|
|
40
|
+
value: null,
|
|
41
|
+
unit: "items",
|
|
42
|
+
availability: "unavailable",
|
|
43
|
+
reason: "No hard limit is declared for this evidence group, so unused capacity cannot be computed."
|
|
44
|
+
});
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
const unused = group.limit - group.usedCount;
|
|
48
|
+
if (unused < 0) {
|
|
49
|
+
unusedCapacity.push({
|
|
50
|
+
groupId: group.id,
|
|
51
|
+
sourceArtifact,
|
|
52
|
+
sourceInstance,
|
|
53
|
+
value: null,
|
|
54
|
+
unit: "items",
|
|
55
|
+
availability: "unavailable",
|
|
56
|
+
reason: `Computed unused capacity was negative (used ${group.usedCount} exceeds limit ${group.limit}); treated as a data inconsistency rather than a valid value.`
|
|
57
|
+
});
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
unusedCapacity.push({
|
|
61
|
+
groupId: group.id,
|
|
62
|
+
sourceArtifact,
|
|
63
|
+
sourceInstance,
|
|
64
|
+
value: unused,
|
|
65
|
+
unit: "items",
|
|
66
|
+
availability: "available",
|
|
67
|
+
reason: null
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
return { requiredGroupCapacity, usedReservation, borrowedCapacity, unusedCapacity };
|
|
71
|
+
}
|
|
72
|
+
export function calculateRequiredEvidenceOmitted(matches, sourceArtifactTruncated) {
|
|
73
|
+
const missing = matches.filter((m) => m.inclusion === "required" && m.outcome === "missing");
|
|
74
|
+
if (missing.length === 0) {
|
|
75
|
+
return {
|
|
76
|
+
metric: { availability: "not-applicable", count: null, evidenceKeys: [], reason: "No required expectation is missing from observed evidence." },
|
|
77
|
+
entries: []
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
const entries = missing.map((m) => ({
|
|
81
|
+
evidenceKey: m.targetKey,
|
|
82
|
+
groupId: null,
|
|
83
|
+
truncationReported: sourceArtifactTruncated[m.sourceArtifact] ?? false,
|
|
84
|
+
sourceArtifact: m.sourceArtifact
|
|
85
|
+
}));
|
|
86
|
+
return {
|
|
87
|
+
metric: { availability: "available", count: entries.length, evidenceKeys: entries.map((e) => e.evidenceKey), reason: null },
|
|
88
|
+
entries
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
// Not currently consumed by the calculators above, but kept available for callers that
|
|
92
|
+
// want to cross-check allocation expectations directly (bounded, deterministic; the lab
|
|
93
|
+
// still never recalculates the producer's allocation decision).
|
|
94
|
+
export function findAllocationExpectationForGroup(expectations, groupId) {
|
|
95
|
+
return (expectations ?? []).find((e) => e.groupId === groupId);
|
|
96
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
function findMapping(mappings, responsibilityId) {
|
|
2
|
+
return mappings?.find((m) => m.responsibilityId === responsibilityId);
|
|
3
|
+
}
|
|
4
|
+
export function calculateCriticalityMetrics(mappings, expectations) {
|
|
5
|
+
const list = expectations ?? [];
|
|
6
|
+
const overlayAgreement = [];
|
|
7
|
+
const missingCriticalityResponsibilityIds = [];
|
|
8
|
+
const conflictingCriticalityResponsibilityIds = [];
|
|
9
|
+
for (const e of list) {
|
|
10
|
+
if (!e.applicable) {
|
|
11
|
+
overlayAgreement.push({
|
|
12
|
+
responsibilityId: e.responsibilityId,
|
|
13
|
+
expectedCriticality: e.expectedCriticality,
|
|
14
|
+
observedCriticality: findMapping(mappings, e.responsibilityId)?.criticality ?? null,
|
|
15
|
+
agreement: null,
|
|
16
|
+
availability: "not-applicable",
|
|
17
|
+
reason: "Expectation is marked not applicable."
|
|
18
|
+
});
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
if (mappings === undefined) {
|
|
22
|
+
overlayAgreement.push({
|
|
23
|
+
responsibilityId: e.responsibilityId,
|
|
24
|
+
expectedCriticality: e.expectedCriticality,
|
|
25
|
+
observedCriticality: null,
|
|
26
|
+
agreement: null,
|
|
27
|
+
availability: "unavailable",
|
|
28
|
+
reason: "No raw responsibility-mapping evidence was supplied."
|
|
29
|
+
});
|
|
30
|
+
missingCriticalityResponsibilityIds.push(e.responsibilityId);
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
const mapping = findMapping(mappings, e.responsibilityId);
|
|
34
|
+
if (mapping === undefined) {
|
|
35
|
+
overlayAgreement.push({
|
|
36
|
+
responsibilityId: e.responsibilityId,
|
|
37
|
+
expectedCriticality: e.expectedCriticality,
|
|
38
|
+
observedCriticality: null,
|
|
39
|
+
agreement: null,
|
|
40
|
+
availability: "unavailable",
|
|
41
|
+
reason: "No raw mapping entry exists for this responsibility ID."
|
|
42
|
+
});
|
|
43
|
+
missingCriticalityResponsibilityIds.push(e.responsibilityId);
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
const agreement = mapping.criticality === e.expectedCriticality;
|
|
47
|
+
overlayAgreement.push({
|
|
48
|
+
responsibilityId: e.responsibilityId,
|
|
49
|
+
expectedCriticality: e.expectedCriticality,
|
|
50
|
+
observedCriticality: mapping.criticality,
|
|
51
|
+
agreement,
|
|
52
|
+
availability: "available",
|
|
53
|
+
reason: null
|
|
54
|
+
});
|
|
55
|
+
if (!agreement)
|
|
56
|
+
conflictingCriticalityResponsibilityIds.push(e.responsibilityId);
|
|
57
|
+
}
|
|
58
|
+
const unexpectedCriticalityResponsibilityIds = list.length === 0 || mappings === undefined
|
|
59
|
+
? []
|
|
60
|
+
: mappings.filter((m) => !list.some((e) => e.responsibilityId === m.responsibilityId)).map((m) => m.responsibilityId);
|
|
61
|
+
const fullyMappedCriticalIds = [];
|
|
62
|
+
const partiallyMappedCriticalIds = [];
|
|
63
|
+
const unmappedCriticalIds = [];
|
|
64
|
+
const missingMappingEvidenceCriticalIds = [];
|
|
65
|
+
for (const e of list) {
|
|
66
|
+
if (!e.applicable || !e.requiresFullMapping || e.expectedCriticality !== "critical")
|
|
67
|
+
continue;
|
|
68
|
+
const mapping = findMapping(mappings, e.responsibilityId);
|
|
69
|
+
if (mapping === undefined) {
|
|
70
|
+
missingMappingEvidenceCriticalIds.push(e.responsibilityId);
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
if (mapping.mappingStatus === "mapped")
|
|
74
|
+
fullyMappedCriticalIds.push(e.responsibilityId);
|
|
75
|
+
else if (mapping.mappingStatus === "partially-mapped")
|
|
76
|
+
partiallyMappedCriticalIds.push(e.responsibilityId);
|
|
77
|
+
else if (mapping.mappingStatus === "unmapped")
|
|
78
|
+
unmappedCriticalIds.push(e.responsibilityId);
|
|
79
|
+
// mapping.mappingStatus === "not-applicable": excluded from the denominator entirely.
|
|
80
|
+
}
|
|
81
|
+
const denominator = fullyMappedCriticalIds.length + partiallyMappedCriticalIds.length + unmappedCriticalIds.length;
|
|
82
|
+
const mappedCriticalCompleteness = denominator === 0
|
|
83
|
+
? {
|
|
84
|
+
availability: "not-applicable",
|
|
85
|
+
numerator: null,
|
|
86
|
+
denominator: null,
|
|
87
|
+
rate: null,
|
|
88
|
+
matchedExpectationIds: [],
|
|
89
|
+
missingExpectationIds: [],
|
|
90
|
+
reason: "No applicable critical responsibility requiring full mapping has resolvable mapping status."
|
|
91
|
+
}
|
|
92
|
+
: {
|
|
93
|
+
availability: "available",
|
|
94
|
+
numerator: fullyMappedCriticalIds.length,
|
|
95
|
+
denominator,
|
|
96
|
+
rate: fullyMappedCriticalIds.length / denominator,
|
|
97
|
+
matchedExpectationIds: fullyMappedCriticalIds,
|
|
98
|
+
missingExpectationIds: [...partiallyMappedCriticalIds, ...unmappedCriticalIds],
|
|
99
|
+
reason: null
|
|
100
|
+
};
|
|
101
|
+
return {
|
|
102
|
+
overlayAgreement,
|
|
103
|
+
missingCriticalityResponsibilityIds,
|
|
104
|
+
conflictingCriticalityResponsibilityIds,
|
|
105
|
+
unexpectedCriticalityResponsibilityIds,
|
|
106
|
+
mappedCriticalCompleteness,
|
|
107
|
+
fullyMappedCriticalIds,
|
|
108
|
+
partiallyMappedCriticalIds,
|
|
109
|
+
unmappedCriticalIds,
|
|
110
|
+
missingMappingEvidenceCriticalIds
|
|
111
|
+
};
|
|
112
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
export function collectSelectedOwnerEvidence(selectedOwners, sourceArtifact, sourceInstance) {
|
|
2
|
+
return selectedOwners.map((item) => ({
|
|
3
|
+
ownerId: item.id,
|
|
4
|
+
sourceArtifact,
|
|
5
|
+
sourceInstance,
|
|
6
|
+
itemKind: item.itemKind,
|
|
7
|
+
path: item.path ?? null,
|
|
8
|
+
symbolId: item.symbolId ?? null,
|
|
9
|
+
sourceFieldPath: `${sourceInstance}.selectedOwners`
|
|
10
|
+
}));
|
|
11
|
+
}
|
|
12
|
+
function notApplicableRatio(reason) {
|
|
13
|
+
return { availability: "not-applicable", numerator: null, denominator: null, rate: null, matchedExpectationIds: [], missingExpectationIds: [], reason };
|
|
14
|
+
}
|
|
15
|
+
function unavailableRatio(reason) {
|
|
16
|
+
return { availability: "unavailable", numerator: null, denominator: null, rate: null, matchedExpectationIds: [], missingExpectationIds: [], reason };
|
|
17
|
+
}
|
|
18
|
+
function notApplicableCount(reason) {
|
|
19
|
+
return { availability: "not-applicable", count: null, evidenceKeys: [], reason };
|
|
20
|
+
}
|
|
21
|
+
function unavailableCount(reason) {
|
|
22
|
+
return { availability: "unavailable", count: null, evidenceKeys: [], reason };
|
|
23
|
+
}
|
|
24
|
+
export function calculateOwnerMetrics(selectedOwnerEvidence, ownerExpectations) {
|
|
25
|
+
const evidence = selectedOwnerEvidence ?? [];
|
|
26
|
+
const evidenceUnavailable = selectedOwnerEvidence === undefined;
|
|
27
|
+
const unavailableReason = "Producer selected-owner evidence was not supplied to this metric.";
|
|
28
|
+
const requiredIds = new Set((ownerExpectations ?? []).filter((e) => e.inclusion === "required").map((e) => e.ownerId));
|
|
29
|
+
const allowedIds = new Set((ownerExpectations ?? []).filter((e) => e.inclusion === "allowed").map((e) => e.ownerId));
|
|
30
|
+
const forbiddenIds = new Set((ownerExpectations ?? []).filter((e) => e.inclusion === "forbidden").map((e) => e.ownerId));
|
|
31
|
+
const hasClosedSet = requiredIds.size > 0 || allowedIds.size > 0;
|
|
32
|
+
const observedIds = new Set(evidence.map((e) => e.ownerId));
|
|
33
|
+
const expectedOwnerPresent = evidenceUnavailable
|
|
34
|
+
? unavailableRatio(unavailableReason)
|
|
35
|
+
: requiredIds.size === 0
|
|
36
|
+
? notApplicableRatio("The expectation fixture contains no required owner expectations.")
|
|
37
|
+
: (() => {
|
|
38
|
+
const matched = [...requiredIds].filter((id) => observedIds.has(id));
|
|
39
|
+
const missing = [...requiredIds].filter((id) => !observedIds.has(id));
|
|
40
|
+
return {
|
|
41
|
+
availability: "available",
|
|
42
|
+
numerator: matched.length,
|
|
43
|
+
denominator: requiredIds.size,
|
|
44
|
+
rate: matched.length / requiredIds.size,
|
|
45
|
+
matchedExpectationIds: matched,
|
|
46
|
+
missingExpectationIds: missing,
|
|
47
|
+
reason: null
|
|
48
|
+
};
|
|
49
|
+
})();
|
|
50
|
+
const forbiddenOwnerPresent = evidenceUnavailable
|
|
51
|
+
? unavailableRatio(unavailableReason)
|
|
52
|
+
: forbiddenIds.size === 0
|
|
53
|
+
? notApplicableRatio("The expectation fixture contains no forbidden owner expectations.")
|
|
54
|
+
: (() => {
|
|
55
|
+
const present = [...forbiddenIds].filter((id) => observedIds.has(id));
|
|
56
|
+
const absent = [...forbiddenIds].filter((id) => !observedIds.has(id));
|
|
57
|
+
return {
|
|
58
|
+
availability: "available",
|
|
59
|
+
numerator: present.length,
|
|
60
|
+
denominator: forbiddenIds.size,
|
|
61
|
+
rate: present.length / forbiddenIds.size,
|
|
62
|
+
matchedExpectationIds: present,
|
|
63
|
+
missingExpectationIds: absent,
|
|
64
|
+
reason: null
|
|
65
|
+
};
|
|
66
|
+
})();
|
|
67
|
+
const falsePositiveCount = evidenceUnavailable
|
|
68
|
+
? unavailableCount(unavailableReason)
|
|
69
|
+
: !hasClosedSet && forbiddenIds.size === 0
|
|
70
|
+
? notApplicableCount("The expectation fixture defines neither a closed owner set nor forbidden owners.")
|
|
71
|
+
: (() => {
|
|
72
|
+
const falsePositives = [...observedIds].filter((id) => forbiddenIds.has(id) || (hasClosedSet && !requiredIds.has(id) && !allowedIds.has(id)));
|
|
73
|
+
return { availability: "available", count: falsePositives.length, evidenceKeys: falsePositives, reason: null };
|
|
74
|
+
})();
|
|
75
|
+
const falseNegativeCount = evidenceUnavailable
|
|
76
|
+
? unavailableCount(unavailableReason)
|
|
77
|
+
: requiredIds.size === 0
|
|
78
|
+
? notApplicableCount("The expectation fixture contains no required owner expectations.")
|
|
79
|
+
: (() => {
|
|
80
|
+
const falseNegatives = [...requiredIds].filter((id) => !observedIds.has(id));
|
|
81
|
+
return { availability: "available", count: falseNegatives.length, evidenceKeys: falseNegatives, reason: null };
|
|
82
|
+
})();
|
|
83
|
+
return { selectedOwnerEvidence: [...evidence], expectedOwnerPresent, forbiddenOwnerPresent, falsePositiveCount, falseNegativeCount };
|
|
84
|
+
}
|