@mnemom/agent-alignment-protocol 0.6.0 → 0.6.2
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/dist/index.d.mts +18 -1
- package/dist/index.d.ts +18 -1
- package/dist/index.js +46 -19
- package/dist/index.mjs +46 -19
- package/package.json +2 -2
- package/src/schemas/alignment-card.ts +18 -1
- package/src/verification/api.ts +67 -28
package/dist/index.d.mts
CHANGED
|
@@ -40,7 +40,24 @@ interface ValueDefinition {
|
|
|
40
40
|
}
|
|
41
41
|
/** Value declarations (SPEC Section 4.4). */
|
|
42
42
|
interface Values {
|
|
43
|
-
/**
|
|
43
|
+
/**
|
|
44
|
+
* Behavioral and ethical values the agent applies in its decision-making.
|
|
45
|
+
*
|
|
46
|
+
* This field is actively monitored by AIP at runtime: every value listed here
|
|
47
|
+
* is expected to appear in AP-Trace `values_applied` fields when it influences
|
|
48
|
+
* a decision. Declaring a value the agent never applies produces verification
|
|
49
|
+
* warnings and degrades trust scoring.
|
|
50
|
+
*
|
|
51
|
+
* **Include**: Ethical and behavioral commitments — e.g. `transparency`,
|
|
52
|
+
* `honesty`, `accuracy`, `safety`, `accountability`, `helpfulness`,
|
|
53
|
+
* `deliberation_before_action`. These describe HOW the agent reasons.
|
|
54
|
+
*
|
|
55
|
+
* **Do not include**: Role capabilities, operational principles, or job-function
|
|
56
|
+
* descriptors — e.g. `fiduciary_precision`, `organizational_clarity`. These
|
|
57
|
+
* describe WHAT the agent is in its role and belong in `extensions.clpi.role`
|
|
58
|
+
* or other `extensions` metadata. Capability names (e.g. `read_documents`)
|
|
59
|
+
* belong in `autonomy_envelope.bounded_actions`.
|
|
60
|
+
*/
|
|
44
61
|
declared: string[];
|
|
45
62
|
/** Definitions for non-standard values */
|
|
46
63
|
definitions?: Record<string, ValueDefinition> | null;
|
package/dist/index.d.ts
CHANGED
|
@@ -40,7 +40,24 @@ interface ValueDefinition {
|
|
|
40
40
|
}
|
|
41
41
|
/** Value declarations (SPEC Section 4.4). */
|
|
42
42
|
interface Values {
|
|
43
|
-
/**
|
|
43
|
+
/**
|
|
44
|
+
* Behavioral and ethical values the agent applies in its decision-making.
|
|
45
|
+
*
|
|
46
|
+
* This field is actively monitored by AIP at runtime: every value listed here
|
|
47
|
+
* is expected to appear in AP-Trace `values_applied` fields when it influences
|
|
48
|
+
* a decision. Declaring a value the agent never applies produces verification
|
|
49
|
+
* warnings and degrades trust scoring.
|
|
50
|
+
*
|
|
51
|
+
* **Include**: Ethical and behavioral commitments — e.g. `transparency`,
|
|
52
|
+
* `honesty`, `accuracy`, `safety`, `accountability`, `helpfulness`,
|
|
53
|
+
* `deliberation_before_action`. These describe HOW the agent reasons.
|
|
54
|
+
*
|
|
55
|
+
* **Do not include**: Role capabilities, operational principles, or job-function
|
|
56
|
+
* descriptors — e.g. `fiduciary_precision`, `organizational_clarity`. These
|
|
57
|
+
* describe WHAT the agent is in its role and belong in `extensions.clpi.role`
|
|
58
|
+
* or other `extensions` metadata. Capability names (e.g. `read_documents`)
|
|
59
|
+
* belong in `autonomy_envelope.bounded_actions`.
|
|
60
|
+
*/
|
|
44
61
|
declared: string[];
|
|
45
62
|
/** Definitions for non-standard values */
|
|
46
63
|
definitions?: Record<string, ValueDefinition> | null;
|
package/dist/index.js
CHANGED
|
@@ -747,6 +747,13 @@ function hasRoleKeyword(agentId) {
|
|
|
747
747
|
const lower = agentId.toLowerCase();
|
|
748
748
|
return ROLE_KEYWORDS.some((kw) => lower.includes(kw));
|
|
749
749
|
}
|
|
750
|
+
function getClpiRole(card) {
|
|
751
|
+
const ext = card.extensions;
|
|
752
|
+
if (!ext) return null;
|
|
753
|
+
const clpi = ext["clpi"];
|
|
754
|
+
if (!clpi || typeof clpi["role"] !== "string") return null;
|
|
755
|
+
return clpi["role"] || null;
|
|
756
|
+
}
|
|
750
757
|
function analyzeFaultLines(coherenceResult, cards, options) {
|
|
751
758
|
const reputationScores = options?.reputationScores;
|
|
752
759
|
const agentBoundedActions = /* @__PURE__ */ new Map();
|
|
@@ -757,6 +764,10 @@ function analyzeFaultLines(coherenceResult, cards, options) {
|
|
|
757
764
|
for (const { agentId, card } of cards) {
|
|
758
765
|
agentConflictMap.set(agentId, new Set(card.values.conflicts_with ?? []));
|
|
759
766
|
}
|
|
767
|
+
const agentRoleMap = /* @__PURE__ */ new Map();
|
|
768
|
+
for (const { agentId, card } of cards) {
|
|
769
|
+
agentRoleMap.set(agentId, getClpiRole(card));
|
|
770
|
+
}
|
|
760
771
|
const faultLines = [];
|
|
761
772
|
for (const divergence of coherenceResult.divergence_report) {
|
|
762
773
|
const {
|
|
@@ -789,6 +800,13 @@ function analyzeFaultLines(coherenceResult, cards, options) {
|
|
|
789
800
|
})()) {
|
|
790
801
|
classification = "priority_mismatch";
|
|
791
802
|
} else if (agents_declaring.length >= 1 && agents_missing.length >= 1 && (() => {
|
|
803
|
+
const declaringRoles = new Set(agents_declaring.map((id2) => agentRoleMap.get(id2) ?? null).filter(Boolean));
|
|
804
|
+
const missingRoles = new Set(agents_missing.map((id2) => agentRoleMap.get(id2) ?? null).filter(Boolean));
|
|
805
|
+
if (declaringRoles.size > 0) {
|
|
806
|
+
const declaringRoleArr = [...declaringRoles];
|
|
807
|
+
const isRoleExclusive = declaringRoleArr.every((role) => !missingRoles.has(role));
|
|
808
|
+
if (isRoleExclusive) return true;
|
|
809
|
+
}
|
|
792
810
|
const allInvolved = [...agents_declaring, ...agents_missing];
|
|
793
811
|
return allInvolved.some((id2) => hasRoleKeyword(id2));
|
|
794
812
|
})()) {
|
|
@@ -816,23 +834,29 @@ function analyzeFaultLines(coherenceResult, cards, options) {
|
|
|
816
834
|
coordinationOverlap = count > 0 ? total / count : 0.5;
|
|
817
835
|
}
|
|
818
836
|
}
|
|
819
|
-
let impactScore
|
|
820
|
-
if (reputationScores && involvedAgents.length > 0) {
|
|
821
|
-
const repValues = involvedAgents.map((id2) => (reputationScores[id2] ?? 500) / 1e3).map((r) => Math.max(1e-3, r));
|
|
822
|
-
const logSum = repValues.reduce((sum, r) => sum + Math.log(r), 0);
|
|
823
|
-
const geoMean = Math.exp(logSum / repValues.length);
|
|
824
|
-
impactScore *= geoMean;
|
|
825
|
-
}
|
|
826
|
-
impactScore = Math.min(1, Math.max(0, impactScore));
|
|
837
|
+
let impactScore;
|
|
827
838
|
let severity;
|
|
828
|
-
if (
|
|
829
|
-
|
|
830
|
-
} else if (impactScore >= 0.4) {
|
|
831
|
-
severity = "high";
|
|
832
|
-
} else if (impactScore >= 0.2) {
|
|
833
|
-
severity = "medium";
|
|
834
|
-
} else {
|
|
839
|
+
if (classification === "complementary") {
|
|
840
|
+
impactScore = 0;
|
|
835
841
|
severity = "low";
|
|
842
|
+
} else {
|
|
843
|
+
impactScore = impact_on_fleet_score * coordinationOverlap;
|
|
844
|
+
if (reputationScores && involvedAgents.length > 0) {
|
|
845
|
+
const repValues = involvedAgents.map((id2) => (reputationScores[id2] ?? 500) / 1e3).map((r) => Math.max(1e-3, r));
|
|
846
|
+
const logSum = repValues.reduce((sum, r) => sum + Math.log(r), 0);
|
|
847
|
+
const geoMean = Math.exp(logSum / repValues.length);
|
|
848
|
+
impactScore *= geoMean;
|
|
849
|
+
}
|
|
850
|
+
impactScore = Math.min(1, Math.max(0, impactScore));
|
|
851
|
+
if (impactScore >= 0.7) {
|
|
852
|
+
severity = "critical";
|
|
853
|
+
} else if (impactScore >= 0.4) {
|
|
854
|
+
severity = "high";
|
|
855
|
+
} else if (impactScore >= 0.2) {
|
|
856
|
+
severity = "medium";
|
|
857
|
+
} else {
|
|
858
|
+
severity = "low";
|
|
859
|
+
}
|
|
836
860
|
}
|
|
837
861
|
let resolutionHint;
|
|
838
862
|
switch (classification) {
|
|
@@ -883,12 +907,15 @@ function analyzeFaultLines(coherenceResult, cards, options) {
|
|
|
883
907
|
return b.impact_score - a.impact_score;
|
|
884
908
|
});
|
|
885
909
|
const alignments = [];
|
|
910
|
+
const actionableFaultLines = faultLines.filter(
|
|
911
|
+
(fl) => fl.classification === "resolvable" || fl.classification === "incompatible"
|
|
912
|
+
);
|
|
886
913
|
const grouped = /* @__PURE__ */ new Map();
|
|
887
914
|
const groupAssignment = /* @__PURE__ */ new Map();
|
|
888
915
|
let nextGroupId = 0;
|
|
889
|
-
for (let i = 0; i <
|
|
890
|
-
for (let j = i + 1; j <
|
|
891
|
-
const sim = jaccardSimilarity(
|
|
916
|
+
for (let i = 0; i < actionableFaultLines.length; i++) {
|
|
917
|
+
for (let j = i + 1; j < actionableFaultLines.length; j++) {
|
|
918
|
+
const sim = jaccardSimilarity(actionableFaultLines[i].agents_missing, actionableFaultLines[j].agents_missing);
|
|
892
919
|
if (sim > 0.6) {
|
|
893
920
|
const gi = groupAssignment.get(i);
|
|
894
921
|
const gj = groupAssignment.get(j);
|
|
@@ -921,7 +948,7 @@ function analyzeFaultLines(coherenceResult, cards, options) {
|
|
|
921
948
|
for (const [, members] of grouped) {
|
|
922
949
|
if (members.length < 2) continue;
|
|
923
950
|
const unique = [...new Set(members)];
|
|
924
|
-
const groupFaultLines = unique.map((i) =>
|
|
951
|
+
const groupFaultLines = unique.map((i) => actionableFaultLines[i]);
|
|
925
952
|
const minorityAgents = [
|
|
926
953
|
...new Set(groupFaultLines.flatMap((fl) => fl.agents_missing))
|
|
927
954
|
];
|
package/dist/index.mjs
CHANGED
|
@@ -690,6 +690,13 @@ function hasRoleKeyword(agentId) {
|
|
|
690
690
|
const lower = agentId.toLowerCase();
|
|
691
691
|
return ROLE_KEYWORDS.some((kw) => lower.includes(kw));
|
|
692
692
|
}
|
|
693
|
+
function getClpiRole(card) {
|
|
694
|
+
const ext = card.extensions;
|
|
695
|
+
if (!ext) return null;
|
|
696
|
+
const clpi = ext["clpi"];
|
|
697
|
+
if (!clpi || typeof clpi["role"] !== "string") return null;
|
|
698
|
+
return clpi["role"] || null;
|
|
699
|
+
}
|
|
693
700
|
function analyzeFaultLines(coherenceResult, cards, options) {
|
|
694
701
|
const reputationScores = options?.reputationScores;
|
|
695
702
|
const agentBoundedActions = /* @__PURE__ */ new Map();
|
|
@@ -700,6 +707,10 @@ function analyzeFaultLines(coherenceResult, cards, options) {
|
|
|
700
707
|
for (const { agentId, card } of cards) {
|
|
701
708
|
agentConflictMap.set(agentId, new Set(card.values.conflicts_with ?? []));
|
|
702
709
|
}
|
|
710
|
+
const agentRoleMap = /* @__PURE__ */ new Map();
|
|
711
|
+
for (const { agentId, card } of cards) {
|
|
712
|
+
agentRoleMap.set(agentId, getClpiRole(card));
|
|
713
|
+
}
|
|
703
714
|
const faultLines = [];
|
|
704
715
|
for (const divergence of coherenceResult.divergence_report) {
|
|
705
716
|
const {
|
|
@@ -732,6 +743,13 @@ function analyzeFaultLines(coherenceResult, cards, options) {
|
|
|
732
743
|
})()) {
|
|
733
744
|
classification = "priority_mismatch";
|
|
734
745
|
} else if (agents_declaring.length >= 1 && agents_missing.length >= 1 && (() => {
|
|
746
|
+
const declaringRoles = new Set(agents_declaring.map((id2) => agentRoleMap.get(id2) ?? null).filter(Boolean));
|
|
747
|
+
const missingRoles = new Set(agents_missing.map((id2) => agentRoleMap.get(id2) ?? null).filter(Boolean));
|
|
748
|
+
if (declaringRoles.size > 0) {
|
|
749
|
+
const declaringRoleArr = [...declaringRoles];
|
|
750
|
+
const isRoleExclusive = declaringRoleArr.every((role) => !missingRoles.has(role));
|
|
751
|
+
if (isRoleExclusive) return true;
|
|
752
|
+
}
|
|
735
753
|
const allInvolved = [...agents_declaring, ...agents_missing];
|
|
736
754
|
return allInvolved.some((id2) => hasRoleKeyword(id2));
|
|
737
755
|
})()) {
|
|
@@ -759,23 +777,29 @@ function analyzeFaultLines(coherenceResult, cards, options) {
|
|
|
759
777
|
coordinationOverlap = count > 0 ? total / count : 0.5;
|
|
760
778
|
}
|
|
761
779
|
}
|
|
762
|
-
let impactScore
|
|
763
|
-
if (reputationScores && involvedAgents.length > 0) {
|
|
764
|
-
const repValues = involvedAgents.map((id2) => (reputationScores[id2] ?? 500) / 1e3).map((r) => Math.max(1e-3, r));
|
|
765
|
-
const logSum = repValues.reduce((sum, r) => sum + Math.log(r), 0);
|
|
766
|
-
const geoMean = Math.exp(logSum / repValues.length);
|
|
767
|
-
impactScore *= geoMean;
|
|
768
|
-
}
|
|
769
|
-
impactScore = Math.min(1, Math.max(0, impactScore));
|
|
780
|
+
let impactScore;
|
|
770
781
|
let severity;
|
|
771
|
-
if (
|
|
772
|
-
|
|
773
|
-
} else if (impactScore >= 0.4) {
|
|
774
|
-
severity = "high";
|
|
775
|
-
} else if (impactScore >= 0.2) {
|
|
776
|
-
severity = "medium";
|
|
777
|
-
} else {
|
|
782
|
+
if (classification === "complementary") {
|
|
783
|
+
impactScore = 0;
|
|
778
784
|
severity = "low";
|
|
785
|
+
} else {
|
|
786
|
+
impactScore = impact_on_fleet_score * coordinationOverlap;
|
|
787
|
+
if (reputationScores && involvedAgents.length > 0) {
|
|
788
|
+
const repValues = involvedAgents.map((id2) => (reputationScores[id2] ?? 500) / 1e3).map((r) => Math.max(1e-3, r));
|
|
789
|
+
const logSum = repValues.reduce((sum, r) => sum + Math.log(r), 0);
|
|
790
|
+
const geoMean = Math.exp(logSum / repValues.length);
|
|
791
|
+
impactScore *= geoMean;
|
|
792
|
+
}
|
|
793
|
+
impactScore = Math.min(1, Math.max(0, impactScore));
|
|
794
|
+
if (impactScore >= 0.7) {
|
|
795
|
+
severity = "critical";
|
|
796
|
+
} else if (impactScore >= 0.4) {
|
|
797
|
+
severity = "high";
|
|
798
|
+
} else if (impactScore >= 0.2) {
|
|
799
|
+
severity = "medium";
|
|
800
|
+
} else {
|
|
801
|
+
severity = "low";
|
|
802
|
+
}
|
|
779
803
|
}
|
|
780
804
|
let resolutionHint;
|
|
781
805
|
switch (classification) {
|
|
@@ -826,12 +850,15 @@ function analyzeFaultLines(coherenceResult, cards, options) {
|
|
|
826
850
|
return b.impact_score - a.impact_score;
|
|
827
851
|
});
|
|
828
852
|
const alignments = [];
|
|
853
|
+
const actionableFaultLines = faultLines.filter(
|
|
854
|
+
(fl) => fl.classification === "resolvable" || fl.classification === "incompatible"
|
|
855
|
+
);
|
|
829
856
|
const grouped = /* @__PURE__ */ new Map();
|
|
830
857
|
const groupAssignment = /* @__PURE__ */ new Map();
|
|
831
858
|
let nextGroupId = 0;
|
|
832
|
-
for (let i = 0; i <
|
|
833
|
-
for (let j = i + 1; j <
|
|
834
|
-
const sim = jaccardSimilarity(
|
|
859
|
+
for (let i = 0; i < actionableFaultLines.length; i++) {
|
|
860
|
+
for (let j = i + 1; j < actionableFaultLines.length; j++) {
|
|
861
|
+
const sim = jaccardSimilarity(actionableFaultLines[i].agents_missing, actionableFaultLines[j].agents_missing);
|
|
835
862
|
if (sim > 0.6) {
|
|
836
863
|
const gi = groupAssignment.get(i);
|
|
837
864
|
const gj = groupAssignment.get(j);
|
|
@@ -864,7 +891,7 @@ function analyzeFaultLines(coherenceResult, cards, options) {
|
|
|
864
891
|
for (const [, members] of grouped) {
|
|
865
892
|
if (members.length < 2) continue;
|
|
866
893
|
const unique = [...new Set(members)];
|
|
867
|
-
const groupFaultLines = unique.map((i) =>
|
|
894
|
+
const groupFaultLines = unique.map((i) => actionableFaultLines[i]);
|
|
868
895
|
const minorityAgents = [
|
|
869
896
|
...new Set(groupFaultLines.flatMap((fl) => fl.agents_missing))
|
|
870
897
|
];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mnemom/agent-alignment-protocol",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.2",
|
|
4
4
|
"description": "Agent Alignment Protocol (AAP) - Verification and drift detection for AI agents",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -54,4 +54,4 @@
|
|
|
54
54
|
"engines": {
|
|
55
55
|
"node": ">=18.0.0"
|
|
56
56
|
}
|
|
57
|
-
}
|
|
57
|
+
}
|
|
@@ -49,7 +49,24 @@ export interface ValueDefinition {
|
|
|
49
49
|
|
|
50
50
|
/** Value declarations (SPEC Section 4.4). */
|
|
51
51
|
export interface Values {
|
|
52
|
-
/**
|
|
52
|
+
/**
|
|
53
|
+
* Behavioral and ethical values the agent applies in its decision-making.
|
|
54
|
+
*
|
|
55
|
+
* This field is actively monitored by AIP at runtime: every value listed here
|
|
56
|
+
* is expected to appear in AP-Trace `values_applied` fields when it influences
|
|
57
|
+
* a decision. Declaring a value the agent never applies produces verification
|
|
58
|
+
* warnings and degrades trust scoring.
|
|
59
|
+
*
|
|
60
|
+
* **Include**: Ethical and behavioral commitments — e.g. `transparency`,
|
|
61
|
+
* `honesty`, `accuracy`, `safety`, `accountability`, `helpfulness`,
|
|
62
|
+
* `deliberation_before_action`. These describe HOW the agent reasons.
|
|
63
|
+
*
|
|
64
|
+
* **Do not include**: Role capabilities, operational principles, or job-function
|
|
65
|
+
* descriptors — e.g. `fiduciary_precision`, `organizational_clarity`. These
|
|
66
|
+
* describe WHAT the agent is in its role and belong in `extensions.clpi.role`
|
|
67
|
+
* or other `extensions` metadata. Capability names (e.g. `read_documents`)
|
|
68
|
+
* belong in `autonomy_envelope.bounded_actions`.
|
|
69
|
+
*/
|
|
53
70
|
declared: string[];
|
|
54
71
|
/** Definitions for non-standard values */
|
|
55
72
|
definitions?: Record<string, ValueDefinition> | null;
|
package/src/verification/api.ts
CHANGED
|
@@ -788,6 +788,15 @@ function hasRoleKeyword(agentId: string): boolean {
|
|
|
788
788
|
return ROLE_KEYWORDS.some(kw => lower.includes(kw));
|
|
789
789
|
}
|
|
790
790
|
|
|
791
|
+
/** Extract the CLPI role from a card's extensions, if present. */
|
|
792
|
+
function getClpiRole(card: AlignmentCard): string | null {
|
|
793
|
+
const ext = (card as unknown as { extensions?: Record<string, unknown> }).extensions;
|
|
794
|
+
if (!ext) return null;
|
|
795
|
+
const clpi = ext['clpi'] as Record<string, unknown> | undefined;
|
|
796
|
+
if (!clpi || typeof clpi['role'] !== 'string') return null;
|
|
797
|
+
return clpi['role'] || null;
|
|
798
|
+
}
|
|
799
|
+
|
|
791
800
|
/**
|
|
792
801
|
* Analyze fault lines in a fleet based on a FleetCoherenceResult.
|
|
793
802
|
*
|
|
@@ -815,6 +824,12 @@ export function analyzeFaultLines(
|
|
|
815
824
|
agentConflictMap.set(agentId, new Set(card.values.conflicts_with ?? []));
|
|
816
825
|
}
|
|
817
826
|
|
|
827
|
+
// Build lookup: agentId → CLPI role (from extensions.clpi.role)
|
|
828
|
+
const agentRoleMap = new Map<string, string | null>();
|
|
829
|
+
for (const { agentId, card } of cards) {
|
|
830
|
+
agentRoleMap.set(agentId, getClpiRole(card));
|
|
831
|
+
}
|
|
832
|
+
|
|
818
833
|
const faultLines: FaultLine[] = [];
|
|
819
834
|
|
|
820
835
|
for (const divergence of coherenceResult.divergence_report) {
|
|
@@ -863,7 +878,18 @@ export function analyzeFaultLines(
|
|
|
863
878
|
agents_declaring.length >= 1 &&
|
|
864
879
|
agents_missing.length >= 1 &&
|
|
865
880
|
(() => {
|
|
866
|
-
// complementary:
|
|
881
|
+
// complementary: the declaring agents all share a CLPI role that the missing
|
|
882
|
+
// agents do NOT share — indicating intentional role specialization, not a gap.
|
|
883
|
+
// Primary check: extensions.clpi.role (authoritative)
|
|
884
|
+
const declaringRoles = new Set(agents_declaring.map(id => agentRoleMap.get(id) ?? null).filter(Boolean));
|
|
885
|
+
const missingRoles = new Set(agents_missing.map(id => agentRoleMap.get(id) ?? null).filter(Boolean));
|
|
886
|
+
if (declaringRoles.size > 0) {
|
|
887
|
+
// All declaring agents share a role that none of the missing agents have
|
|
888
|
+
const declaringRoleArr = [...declaringRoles];
|
|
889
|
+
const isRoleExclusive = declaringRoleArr.every(role => !missingRoles.has(role));
|
|
890
|
+
if (isRoleExclusive) return true;
|
|
891
|
+
}
|
|
892
|
+
// Fallback: agent ID contains a role keyword (original heuristic)
|
|
867
893
|
const allInvolved = [...agents_declaring, ...agents_missing];
|
|
868
894
|
return allInvolved.some(id => hasRoleKeyword(id));
|
|
869
895
|
})()
|
|
@@ -897,30 +923,38 @@ export function analyzeFaultLines(
|
|
|
897
923
|
}
|
|
898
924
|
|
|
899
925
|
// --- impact_score ---
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
// Reputation weighting: multiply by geometric mean of reputation/1000
|
|
903
|
-
if (reputationScores && involvedAgents.length > 0) {
|
|
904
|
-
const repValues = involvedAgents
|
|
905
|
-
.map(id => (reputationScores[id] ?? 500) / 1000)
|
|
906
|
-
.map(r => Math.max(0.001, r)); // avoid log(0)
|
|
907
|
-
const logSum = repValues.reduce((sum, r) => sum + Math.log(r), 0);
|
|
908
|
-
const geoMean = Math.exp(logSum / repValues.length);
|
|
909
|
-
impactScore *= geoMean;
|
|
910
|
-
}
|
|
911
|
-
|
|
912
|
-
impactScore = Math.min(1, Math.max(0, impactScore));
|
|
913
|
-
|
|
914
|
-
// --- Severity ---
|
|
926
|
+
// Complementary faults are intentional — they carry zero risk by definition.
|
|
927
|
+
let impactScore: number;
|
|
915
928
|
let severity: Severity;
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
severity = 'high';
|
|
920
|
-
} else if (impactScore >= 0.2) {
|
|
921
|
-
severity = 'medium';
|
|
922
|
-
} else {
|
|
929
|
+
|
|
930
|
+
if (classification === 'complementary') {
|
|
931
|
+
impactScore = 0;
|
|
923
932
|
severity = 'low';
|
|
933
|
+
} else {
|
|
934
|
+
impactScore = impact_on_fleet_score * coordinationOverlap;
|
|
935
|
+
|
|
936
|
+
// Reputation weighting: multiply by geometric mean of reputation/1000
|
|
937
|
+
if (reputationScores && involvedAgents.length > 0) {
|
|
938
|
+
const repValues = involvedAgents
|
|
939
|
+
.map(id => (reputationScores[id] ?? 500) / 1000)
|
|
940
|
+
.map(r => Math.max(0.001, r)); // avoid log(0)
|
|
941
|
+
const logSum = repValues.reduce((sum, r) => sum + Math.log(r), 0);
|
|
942
|
+
const geoMean = Math.exp(logSum / repValues.length);
|
|
943
|
+
impactScore *= geoMean;
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
impactScore = Math.min(1, Math.max(0, impactScore));
|
|
947
|
+
|
|
948
|
+
// --- Severity ---
|
|
949
|
+
if (impactScore >= 0.7) {
|
|
950
|
+
severity = 'critical';
|
|
951
|
+
} else if (impactScore >= 0.4) {
|
|
952
|
+
severity = 'high';
|
|
953
|
+
} else if (impactScore >= 0.2) {
|
|
954
|
+
severity = 'medium';
|
|
955
|
+
} else {
|
|
956
|
+
severity = 'low';
|
|
957
|
+
}
|
|
924
958
|
}
|
|
925
959
|
|
|
926
960
|
// --- Resolution hint ---
|
|
@@ -981,17 +1015,22 @@ export function analyzeFaultLines(
|
|
|
981
1015
|
});
|
|
982
1016
|
|
|
983
1017
|
// --- Fault line alignment detection ---
|
|
1018
|
+
// Only consider resolvable and incompatible faults — complementary faults are intentional
|
|
1019
|
+
// role specialization and should NEVER trigger a structural fault line alert.
|
|
984
1020
|
const alignments: FaultLineAlignment[] = [];
|
|
1021
|
+
const actionableFaultLines = faultLines.filter(
|
|
1022
|
+
fl => fl.classification === 'resolvable' || fl.classification === 'incompatible'
|
|
1023
|
+
);
|
|
985
1024
|
|
|
986
|
-
// For each pair of fault lines, compute Jaccard of agents_missing sets
|
|
1025
|
+
// For each pair of actionable fault lines, compute Jaccard of agents_missing sets
|
|
987
1026
|
// Group fault lines with similarity > 0.6
|
|
988
1027
|
const grouped = new Map<number, number[]>(); // groupId → faultLine indices
|
|
989
1028
|
const groupAssignment = new Map<number, number>(); // faultLine index → groupId
|
|
990
1029
|
let nextGroupId = 0;
|
|
991
1030
|
|
|
992
|
-
for (let i = 0; i <
|
|
993
|
-
for (let j = i + 1; j <
|
|
994
|
-
const sim = jaccardSimilarity(
|
|
1031
|
+
for (let i = 0; i < actionableFaultLines.length; i++) {
|
|
1032
|
+
for (let j = i + 1; j < actionableFaultLines.length; j++) {
|
|
1033
|
+
const sim = jaccardSimilarity(actionableFaultLines[i].agents_missing, actionableFaultLines[j].agents_missing);
|
|
995
1034
|
if (sim > 0.6) {
|
|
996
1035
|
// Find or create groups for i and j
|
|
997
1036
|
const gi = groupAssignment.get(i);
|
|
@@ -1028,7 +1067,7 @@ export function analyzeFaultLines(
|
|
|
1028
1067
|
for (const [, members] of grouped) {
|
|
1029
1068
|
if (members.length < 2) continue;
|
|
1030
1069
|
const unique = [...new Set(members)];
|
|
1031
|
-
const groupFaultLines = unique.map(i =>
|
|
1070
|
+
const groupFaultLines = unique.map(i => actionableFaultLines[i]);
|
|
1032
1071
|
|
|
1033
1072
|
const minorityAgents = [
|
|
1034
1073
|
...new Set(groupFaultLines.flatMap(fl => fl.agents_missing)),
|