@traits-dev/core 0.3.0 → 0.4.0
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-CFhdB_nQ.d.cts → index-Ct4kuPk7.d.cts} +11 -4
- package/dist/{index-CFhdB_nQ.d.ts → index-Ct4kuPk7.d.ts} +11 -4
- package/dist/index.cjs +271 -71
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +271 -71
- package/dist/internal.cjs +272 -72
- package/dist/internal.d.cts +2 -2
- package/dist/internal.d.ts +2 -2
- package/dist/internal.js +272 -72
- package/package.json +1 -1
package/dist/internal.js
CHANGED
|
@@ -48,6 +48,37 @@ function isClaudeModel(model) {
|
|
|
48
48
|
function isGptModel(model) {
|
|
49
49
|
return /gpt/i.test(String(model ?? ""));
|
|
50
50
|
}
|
|
51
|
+
function isLockedRule(value) {
|
|
52
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
53
|
+
const candidate = value;
|
|
54
|
+
if (typeof candidate.rule !== "string" || candidate.rule.trim().length === 0) return false;
|
|
55
|
+
if (candidate.locked != null && typeof candidate.locked !== "boolean") return false;
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
function ruleConstraintText(entry) {
|
|
59
|
+
if (typeof entry === "string") {
|
|
60
|
+
const text = entry.trim();
|
|
61
|
+
return text.length > 0 ? text : null;
|
|
62
|
+
}
|
|
63
|
+
if (isLockedRule(entry)) {
|
|
64
|
+
return entry.rule.trim();
|
|
65
|
+
}
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
function normalizeRuleConstraints(value) {
|
|
69
|
+
const out = [];
|
|
70
|
+
for (const entry of asArray(value)) {
|
|
71
|
+
if (typeof entry === "string") {
|
|
72
|
+
const text = entry.trim();
|
|
73
|
+
if (!text) continue;
|
|
74
|
+
out.push({ rule: text, locked: false });
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
if (!isLockedRule(entry)) continue;
|
|
78
|
+
out.push({ rule: entry.rule.trim(), locked: Boolean(entry.locked) });
|
|
79
|
+
}
|
|
80
|
+
return out;
|
|
81
|
+
}
|
|
51
82
|
|
|
52
83
|
// src/profile/merge.ts
|
|
53
84
|
var PASS_THROUGH_FIELDS = /* @__PURE__ */ new Set([
|
|
@@ -63,17 +94,6 @@ var PASS_THROUGH_FIELDS = /* @__PURE__ */ new Set([
|
|
|
63
94
|
"behavioral_rules_remove",
|
|
64
95
|
"context_adaptations_remove"
|
|
65
96
|
]);
|
|
66
|
-
function dedupExact(items) {
|
|
67
|
-
const seen = /* @__PURE__ */ new Set();
|
|
68
|
-
const out = [];
|
|
69
|
-
for (const item of items) {
|
|
70
|
-
const key = String(item);
|
|
71
|
-
if (seen.has(key)) continue;
|
|
72
|
-
seen.add(key);
|
|
73
|
-
out.push(item);
|
|
74
|
-
}
|
|
75
|
-
return out;
|
|
76
|
-
}
|
|
77
97
|
function dedupCaseInsensitive(items) {
|
|
78
98
|
const seen = /* @__PURE__ */ new Set();
|
|
79
99
|
const out = [];
|
|
@@ -116,8 +136,29 @@ function mergeVocabulary(parentVocab = {}, childVocab = {}) {
|
|
|
116
136
|
if (mergedForbidden.length) merged.forbidden_terms = mergedForbidden;
|
|
117
137
|
return merged;
|
|
118
138
|
}
|
|
139
|
+
function mergeRuleConstraints(parentRules = [], childRules = [], options) {
|
|
140
|
+
const out = [];
|
|
141
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
142
|
+
const combined = [
|
|
143
|
+
...normalizeRuleConstraints(parentRules),
|
|
144
|
+
...normalizeRuleConstraints(childRules)
|
|
145
|
+
];
|
|
146
|
+
for (const entry of combined) {
|
|
147
|
+
const key = options.caseInsensitive ? entry.rule.toLowerCase() : entry.rule;
|
|
148
|
+
const existingIndex = byKey.get(key);
|
|
149
|
+
if (existingIndex == null) {
|
|
150
|
+
byKey.set(key, out.length);
|
|
151
|
+
out.push({ rule: entry.rule, locked: entry.locked });
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
out[existingIndex].locked = out[existingIndex].locked || entry.locked;
|
|
155
|
+
}
|
|
156
|
+
return out.map(
|
|
157
|
+
(entry) => entry.locked ? { rule: entry.rule, locked: true } : entry.rule
|
|
158
|
+
);
|
|
159
|
+
}
|
|
119
160
|
function mergeBehavioralRules(parentRules = [], childRules = []) {
|
|
120
|
-
return
|
|
161
|
+
return mergeRuleConstraints(parentRules, childRules, { caseInsensitive: false });
|
|
121
162
|
}
|
|
122
163
|
function mergeContextAdaptations(parentAdaptations = [], childAdaptations = []) {
|
|
123
164
|
const base = asArray(parentAdaptations).map((item) => clone(item));
|
|
@@ -149,13 +190,13 @@ function mergeCapabilities(parentCapabilities, childCapabilities) {
|
|
|
149
190
|
...asArray(parentCapabilities.tools),
|
|
150
191
|
...asArray(childCapabilities.tools)
|
|
151
192
|
]);
|
|
152
|
-
const mergedConstraints = dedupCaseInsensitive([
|
|
153
|
-
...asArray(parentCapabilities.constraints),
|
|
154
|
-
...asArray(childCapabilities.constraints)
|
|
155
|
-
]);
|
|
156
193
|
return {
|
|
157
194
|
tools: mergedTools,
|
|
158
|
-
constraints:
|
|
195
|
+
constraints: mergeRuleConstraints(
|
|
196
|
+
parentCapabilities.constraints,
|
|
197
|
+
childCapabilities.constraints,
|
|
198
|
+
{ caseInsensitive: true }
|
|
199
|
+
),
|
|
159
200
|
handoff: {
|
|
160
201
|
trigger: childCapabilities.handoff?.trigger ?? parentCapabilities.handoff?.trigger ?? "",
|
|
161
202
|
action: childCapabilities.handoff?.action ?? parentCapabilities.handoff?.action ?? ""
|
|
@@ -178,9 +219,15 @@ function applyExplicitRemovals(childProfile, mergedProfile) {
|
|
|
178
219
|
);
|
|
179
220
|
const childAdaptationRemovals = asArray(childProfile.context_adaptations_remove);
|
|
180
221
|
if (childBehavioralRemovals.length) {
|
|
181
|
-
mergedProfile.behavioral_rules = asArray(
|
|
182
|
-
|
|
183
|
-
)
|
|
222
|
+
mergedProfile.behavioral_rules = asArray(
|
|
223
|
+
mergedProfile.behavioral_rules
|
|
224
|
+
).filter((ruleEntry) => {
|
|
225
|
+
const ruleText = ruleConstraintText(ruleEntry);
|
|
226
|
+
if (!ruleText) return false;
|
|
227
|
+
if (!childBehavioralRemovals.includes(ruleText)) return true;
|
|
228
|
+
if (typeof ruleEntry === "object" && ruleEntry.locked === true) return true;
|
|
229
|
+
return false;
|
|
230
|
+
});
|
|
184
231
|
}
|
|
185
232
|
if (childForbiddenRemovals.length) {
|
|
186
233
|
const nextForbidden = removeCaseInsensitive(
|
|
@@ -235,37 +282,89 @@ function mergeProfiles(parentProfile, childProfile) {
|
|
|
235
282
|
}
|
|
236
283
|
|
|
237
284
|
// src/profile/extends.ts
|
|
285
|
+
function normalizeExtendsTargets(value) {
|
|
286
|
+
if (value == null) return [];
|
|
287
|
+
if (typeof value === "string") {
|
|
288
|
+
return value.trim().length > 0 ? [value] : null;
|
|
289
|
+
}
|
|
290
|
+
if (!Array.isArray(value) || value.length === 0) return null;
|
|
291
|
+
const targets = [];
|
|
292
|
+
for (const item of value) {
|
|
293
|
+
if (typeof item !== "string" || item.trim().length === 0) {
|
|
294
|
+
return null;
|
|
295
|
+
}
|
|
296
|
+
targets.push(item);
|
|
297
|
+
}
|
|
298
|
+
return targets;
|
|
299
|
+
}
|
|
238
300
|
function resolveExtends(profilePath, options = {}) {
|
|
239
301
|
const diagnostics = { warnings: [], errors: [] };
|
|
240
302
|
const childProfile = loadProfileFile(profilePath);
|
|
241
|
-
|
|
303
|
+
const extendsTargets = normalizeExtendsTargets(childProfile?.extends);
|
|
304
|
+
if (!extendsTargets || extendsTargets.length === 0) {
|
|
242
305
|
return {
|
|
243
306
|
profile: childProfile,
|
|
244
307
|
parentPath: null,
|
|
308
|
+
parentPaths: [],
|
|
309
|
+
parentProfile: null,
|
|
245
310
|
diagnostics
|
|
246
311
|
};
|
|
247
312
|
}
|
|
248
|
-
const
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
313
|
+
const parentPaths = [];
|
|
314
|
+
let mergedParent = null;
|
|
315
|
+
for (const extendsName of extendsTargets) {
|
|
316
|
+
const parentPath = resolveParentPath(profilePath, extendsName, options);
|
|
317
|
+
if (!parentPath) {
|
|
318
|
+
diagnostics.errors.push({
|
|
319
|
+
code: "E_RESOLVE_EXTENDS",
|
|
320
|
+
severity: "error",
|
|
321
|
+
message: `Unable to resolve parent profile "${extendsName}".`
|
|
322
|
+
});
|
|
323
|
+
return {
|
|
324
|
+
profile: childProfile,
|
|
325
|
+
parentPath: parentPaths[0] ?? null,
|
|
326
|
+
parentPaths,
|
|
327
|
+
parentProfile: null,
|
|
328
|
+
diagnostics
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
const parentProfile2 = loadProfileFile(parentPath);
|
|
332
|
+
if (parentProfile2?.extends) {
|
|
333
|
+
diagnostics.errors.push({
|
|
334
|
+
code: "E_EXTENDS_CHAIN",
|
|
335
|
+
severity: "error",
|
|
336
|
+
message: "extends chains are not supported in MVP."
|
|
337
|
+
});
|
|
338
|
+
return {
|
|
339
|
+
profile: childProfile,
|
|
340
|
+
parentPath: parentPaths[0] ?? parentPath,
|
|
341
|
+
parentPaths: [...parentPaths, parentPath],
|
|
342
|
+
parentProfile: null,
|
|
343
|
+
diagnostics
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
parentPaths.push(parentPath);
|
|
347
|
+
mergedParent = mergedParent ? mergeProfiles(mergedParent, parentProfile2) : parentProfile2;
|
|
348
|
+
}
|
|
349
|
+
const parentProfile = mergedParent;
|
|
350
|
+
if (!parentProfile) {
|
|
351
|
+
return {
|
|
352
|
+
profile: childProfile,
|
|
353
|
+
parentPath: null,
|
|
354
|
+
parentPaths: [],
|
|
355
|
+
parentProfile: null,
|
|
356
|
+
diagnostics
|
|
357
|
+
};
|
|
265
358
|
}
|
|
266
359
|
const merged = mergeProfiles(parentProfile, childProfile);
|
|
267
360
|
delete merged.extends;
|
|
268
|
-
return {
|
|
361
|
+
return {
|
|
362
|
+
profile: merged,
|
|
363
|
+
parentPath: parentPaths[0] ?? null,
|
|
364
|
+
parentPaths,
|
|
365
|
+
parentProfile,
|
|
366
|
+
diagnostics
|
|
367
|
+
};
|
|
269
368
|
}
|
|
270
369
|
|
|
271
370
|
// src/profile/normalize.ts
|
|
@@ -310,7 +409,7 @@ function resolveActiveContext(profile, context = {}) {
|
|
|
310
409
|
|
|
311
410
|
// src/validator/overspec.ts
|
|
312
411
|
function computeConstraintCount(profile) {
|
|
313
|
-
const behavioralRules =
|
|
412
|
+
const behavioralRules = normalizeRuleConstraints(profile?.behavioral_rules).length;
|
|
314
413
|
const preferredTerms = asArray(profile?.vocabulary?.preferred_terms).length;
|
|
315
414
|
const forbiddenTerms = asArray(profile?.vocabulary?.forbidden_terms).length;
|
|
316
415
|
const contextAdaptations = asArray(profile?.context_adaptations).length;
|
|
@@ -352,7 +451,7 @@ function checkOverspec(profile) {
|
|
|
352
451
|
|
|
353
452
|
// src/validator/schema.ts
|
|
354
453
|
var HUMOR_STYLES = ["none", "dry", "subtle-wit", "playful"];
|
|
355
|
-
var SUPPORTED_SCHEMAS = /* @__PURE__ */ new Set(["v1.4", "v1.5"]);
|
|
454
|
+
var SUPPORTED_SCHEMAS = /* @__PURE__ */ new Set(["v1.4", "v1.5", "v1.6"]);
|
|
356
455
|
var TOP_LEVEL_KEYS = /* @__PURE__ */ new Set([
|
|
357
456
|
"schema",
|
|
358
457
|
"meta",
|
|
@@ -384,6 +483,9 @@ function isString(value) {
|
|
|
384
483
|
function isStringArray(value) {
|
|
385
484
|
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
386
485
|
}
|
|
486
|
+
function isNonEmptyStringArray(value) {
|
|
487
|
+
return Array.isArray(value) && value.length > 0 && value.every((item) => typeof item === "string" && item.trim().length > 0);
|
|
488
|
+
}
|
|
387
489
|
function pushDiagnostic(target, code, message, location) {
|
|
388
490
|
target.push({
|
|
389
491
|
code,
|
|
@@ -409,6 +511,66 @@ function validateScalarField(parent, key, location, diagnostics) {
|
|
|
409
511
|
);
|
|
410
512
|
}
|
|
411
513
|
}
|
|
514
|
+
function validateRuleConstraintArray(value, field, diagnostics, options) {
|
|
515
|
+
if (!Array.isArray(value)) {
|
|
516
|
+
pushDiagnostic(
|
|
517
|
+
diagnostics,
|
|
518
|
+
"V001",
|
|
519
|
+
`Expected "${field}" to be an array`,
|
|
520
|
+
field
|
|
521
|
+
);
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
524
|
+
value.forEach((entry, idx) => {
|
|
525
|
+
const location = `${field}[${idx}]`;
|
|
526
|
+
if (typeof entry === "string") return;
|
|
527
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
|
|
528
|
+
pushDiagnostic(
|
|
529
|
+
diagnostics,
|
|
530
|
+
"V001",
|
|
531
|
+
`Expected "${location}" to be a string or { rule, locked? } object`,
|
|
532
|
+
location
|
|
533
|
+
);
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
536
|
+
if (!options.allowObjects) {
|
|
537
|
+
pushDiagnostic(
|
|
538
|
+
diagnostics,
|
|
539
|
+
"V001",
|
|
540
|
+
`Object rule entries in "${field}" require schema version "v1.6"`,
|
|
541
|
+
location
|
|
542
|
+
);
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
545
|
+
const ruleObject = entry;
|
|
546
|
+
for (const key of Object.keys(ruleObject)) {
|
|
547
|
+
if (key !== "rule" && key !== "locked") {
|
|
548
|
+
pushDiagnostic(
|
|
549
|
+
diagnostics,
|
|
550
|
+
"V001",
|
|
551
|
+
`Unknown key "${key}" in ${location}`,
|
|
552
|
+
`${location}.${key}`
|
|
553
|
+
);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
if (!isString(ruleObject.rule)) {
|
|
557
|
+
pushDiagnostic(
|
|
558
|
+
diagnostics,
|
|
559
|
+
"V001",
|
|
560
|
+
`Expected "${location}.rule" to be a non-empty string`,
|
|
561
|
+
`${location}.rule`
|
|
562
|
+
);
|
|
563
|
+
}
|
|
564
|
+
if (ruleObject.locked != null && typeof ruleObject.locked !== "boolean") {
|
|
565
|
+
pushDiagnostic(
|
|
566
|
+
diagnostics,
|
|
567
|
+
"V001",
|
|
568
|
+
`Expected "${location}.locked" to be a boolean`,
|
|
569
|
+
`${location}.locked`
|
|
570
|
+
);
|
|
571
|
+
}
|
|
572
|
+
});
|
|
573
|
+
}
|
|
412
574
|
function validateDimensionValue(value, dimension, location, dimensionsDiagnostics, rangeDiagnostics) {
|
|
413
575
|
if (typeof value === "string") {
|
|
414
576
|
if (!LEVEL_INDEX.has(value)) {
|
|
@@ -559,13 +721,25 @@ function validateSchema(profile) {
|
|
|
559
721
|
"schema"
|
|
560
722
|
);
|
|
561
723
|
}
|
|
562
|
-
if (profile.extends != null
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
724
|
+
if (profile.extends != null) {
|
|
725
|
+
const isStringExtends = isString(profile.extends);
|
|
726
|
+
const isArrayExtends = isNonEmptyStringArray(profile.extends);
|
|
727
|
+
if (!isStringExtends && !isArrayExtends) {
|
|
728
|
+
pushDiagnostic(
|
|
729
|
+
structureDiagnostics,
|
|
730
|
+
"V001",
|
|
731
|
+
`Expected "extends" to be a non-empty string or non-empty array of non-empty strings`,
|
|
732
|
+
"extends"
|
|
733
|
+
);
|
|
734
|
+
}
|
|
735
|
+
if (Array.isArray(profile.extends) && profile.schema !== "v1.6") {
|
|
736
|
+
pushDiagnostic(
|
|
737
|
+
structureDiagnostics,
|
|
738
|
+
"V001",
|
|
739
|
+
`Array "extends" requires schema version "v1.6"`,
|
|
740
|
+
"extends"
|
|
741
|
+
);
|
|
742
|
+
}
|
|
569
743
|
}
|
|
570
744
|
if (!isObject(profile.meta)) {
|
|
571
745
|
pushDiagnostic(structureDiagnostics, "V001", `Missing required "meta" section`, "meta");
|
|
@@ -691,20 +865,17 @@ function validateSchema(profile) {
|
|
|
691
865
|
}
|
|
692
866
|
}
|
|
693
867
|
}
|
|
694
|
-
if (profile.behavioral_rules != null
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
`Expected "behavioral_rules" to be an array of strings`,
|
|
699
|
-
"behavioral_rules"
|
|
700
|
-
);
|
|
868
|
+
if (profile.behavioral_rules != null) {
|
|
869
|
+
validateRuleConstraintArray(profile.behavioral_rules, "behavioral_rules", structureDiagnostics, {
|
|
870
|
+
allowObjects: profile.schema === "v1.6"
|
|
871
|
+
});
|
|
701
872
|
}
|
|
702
873
|
if (profile.capabilities != null) {
|
|
703
|
-
if (profile.schema !== "v1.5") {
|
|
874
|
+
if (profile.schema !== "v1.5" && profile.schema !== "v1.6") {
|
|
704
875
|
pushDiagnostic(
|
|
705
876
|
structureDiagnostics,
|
|
706
877
|
"V001",
|
|
707
|
-
`The "capabilities" section requires schema version "v1.5"`,
|
|
878
|
+
`The "capabilities" section requires schema version "v1.5" or "v1.6"`,
|
|
708
879
|
"capabilities"
|
|
709
880
|
);
|
|
710
881
|
}
|
|
@@ -734,13 +905,20 @@ function validateSchema(profile) {
|
|
|
734
905
|
"capabilities.tools"
|
|
735
906
|
);
|
|
736
907
|
}
|
|
737
|
-
if (
|
|
908
|
+
if (profile.capabilities.constraints == null) {
|
|
738
909
|
pushDiagnostic(
|
|
739
910
|
structureDiagnostics,
|
|
740
911
|
"V001",
|
|
741
|
-
`Expected "capabilities.constraints" to be an array
|
|
912
|
+
`Expected "capabilities.constraints" to be an array`,
|
|
742
913
|
"capabilities.constraints"
|
|
743
914
|
);
|
|
915
|
+
} else {
|
|
916
|
+
validateRuleConstraintArray(
|
|
917
|
+
profile.capabilities.constraints,
|
|
918
|
+
"capabilities.constraints",
|
|
919
|
+
structureDiagnostics,
|
|
920
|
+
{ allowObjects: profile.schema === "v1.6" }
|
|
921
|
+
);
|
|
744
922
|
}
|
|
745
923
|
if (!isObject(profile.capabilities.handoff)) {
|
|
746
924
|
pushDiagnostic(
|
|
@@ -1037,7 +1215,9 @@ function collectS001Candidates(profile) {
|
|
|
1037
1215
|
text: normalizeText(profile.identity.backstory)
|
|
1038
1216
|
});
|
|
1039
1217
|
}
|
|
1040
|
-
asArray(profile?.behavioral_rules).forEach((
|
|
1218
|
+
asArray(profile?.behavioral_rules).forEach((ruleEntry, idx) => {
|
|
1219
|
+
const rule = ruleConstraintText(ruleEntry);
|
|
1220
|
+
if (!rule) return;
|
|
1041
1221
|
candidates.push({
|
|
1042
1222
|
location: `behavioral_rules[${idx}]`,
|
|
1043
1223
|
text: normalizeText(rule)
|
|
@@ -1057,7 +1237,9 @@ function collectS001Candidates(profile) {
|
|
|
1057
1237
|
}
|
|
1058
1238
|
function collectS005Candidates(profile) {
|
|
1059
1239
|
const candidates = [];
|
|
1060
|
-
asArray(profile?.behavioral_rules).forEach((
|
|
1240
|
+
asArray(profile?.behavioral_rules).forEach((ruleEntry, idx) => {
|
|
1241
|
+
const rule = ruleConstraintText(ruleEntry);
|
|
1242
|
+
if (!rule) return;
|
|
1061
1243
|
candidates.push({
|
|
1062
1244
|
location: `behavioral_rules[${idx}]`,
|
|
1063
1245
|
text: normalizeText(rule)
|
|
@@ -1089,7 +1271,9 @@ function collectS005Candidates(profile) {
|
|
|
1089
1271
|
}
|
|
1090
1272
|
function collectS008Candidates(profile) {
|
|
1091
1273
|
const candidates = [];
|
|
1092
|
-
asArray(profile?.behavioral_rules).forEach((
|
|
1274
|
+
asArray(profile?.behavioral_rules).forEach((ruleEntry, idx) => {
|
|
1275
|
+
const rule = ruleConstraintText(ruleEntry);
|
|
1276
|
+
if (!rule) return;
|
|
1093
1277
|
candidates.push({
|
|
1094
1278
|
location: `behavioral_rules[${idx}]`,
|
|
1095
1279
|
text: normalizeText(rule)
|
|
@@ -1233,6 +1417,19 @@ function checkS006(parentProfile, childProfile, mergedProfile) {
|
|
|
1233
1417
|
severity: "warning",
|
|
1234
1418
|
message: "Explicit behavioral_rules_remove detected. Behavioral rules are safety-relevant."
|
|
1235
1419
|
});
|
|
1420
|
+
const lockedParentRules = new Set(
|
|
1421
|
+
normalizeRuleConstraints(parentProfile.behavioral_rules).filter((rule) => rule.locked).map((rule) => rule.rule)
|
|
1422
|
+
);
|
|
1423
|
+
const lockedRemovals = childBehavioralRemovals.filter(
|
|
1424
|
+
(rule) => lockedParentRules.has(rule)
|
|
1425
|
+
);
|
|
1426
|
+
if (lockedRemovals.length > 0) {
|
|
1427
|
+
diagnostics.push({
|
|
1428
|
+
code: "S006",
|
|
1429
|
+
severity: "error",
|
|
1430
|
+
message: `behavioral_rules_remove attempted to remove locked inherited rules: ${lockedRemovals.join("; ")}`
|
|
1431
|
+
});
|
|
1432
|
+
}
|
|
1236
1433
|
}
|
|
1237
1434
|
if (childForbiddenRemovals.length) {
|
|
1238
1435
|
diagnostics.push({
|
|
@@ -1241,9 +1438,9 @@ function checkS006(parentProfile, childProfile, mergedProfile) {
|
|
|
1241
1438
|
message: "Explicit vocabulary.forbidden_terms_remove detected. Forbidden terms are safety-relevant."
|
|
1242
1439
|
});
|
|
1243
1440
|
}
|
|
1244
|
-
const parentBehavioralCount =
|
|
1441
|
+
const parentBehavioralCount = normalizeRuleConstraints(parentProfile.behavioral_rules).length;
|
|
1245
1442
|
const parentForbiddenCount = asArray(parentProfile?.vocabulary?.forbidden_terms).length;
|
|
1246
|
-
const mergedBehavioralCount =
|
|
1443
|
+
const mergedBehavioralCount = normalizeRuleConstraints(mergedProfile.behavioral_rules).length;
|
|
1247
1444
|
const mergedForbiddenCount = asArray(mergedProfile?.vocabulary?.forbidden_terms).length;
|
|
1248
1445
|
if (mergedBehavioralCount < parentBehavioralCount || mergedForbiddenCount < parentForbiddenCount) {
|
|
1249
1446
|
diagnostics.push({
|
|
@@ -1413,11 +1610,10 @@ function validateProfile(profilePath, options = {}) {
|
|
|
1413
1610
|
(diagnostic) => normalizeDiagnosticSeverity(diagnostic, "error")
|
|
1414
1611
|
);
|
|
1415
1612
|
let s006Diagnostics = [];
|
|
1416
|
-
if (resolvedErrors.length === 0 && resolved.
|
|
1613
|
+
if (resolvedErrors.length === 0 && resolved.parentProfile) {
|
|
1417
1614
|
try {
|
|
1418
1615
|
const childProfile = loadProfileFile(profilePath);
|
|
1419
|
-
|
|
1420
|
-
s006Diagnostics = checkS006(parentProfile, childProfile, resolved.profile);
|
|
1616
|
+
s006Diagnostics = checkS006(resolved.parentProfile, childProfile, resolved.profile);
|
|
1421
1617
|
} catch (error) {
|
|
1422
1618
|
s006Diagnostics = [
|
|
1423
1619
|
{
|
|
@@ -1779,7 +1975,7 @@ function renderPersonalityText(profile, model, contextResolution, compileOptions
|
|
|
1779
1975
|
lines.push(`Protected refusal terms (always available): ${PROTECTED_REFUSAL_TERMS.join("; ")}`);
|
|
1780
1976
|
lines.push("");
|
|
1781
1977
|
lines.push("[BEHAVIORAL RULES]");
|
|
1782
|
-
const rules =
|
|
1978
|
+
const rules = normalizeRuleConstraints(profile.behavioral_rules).map((entry) => entry.rule);
|
|
1783
1979
|
if (rules.length === 0) {
|
|
1784
1980
|
lines.push("- (none)");
|
|
1785
1981
|
} else {
|
|
@@ -1787,10 +1983,12 @@ function renderPersonalityText(profile, model, contextResolution, compileOptions
|
|
|
1787
1983
|
lines.push(`- ${rule}`);
|
|
1788
1984
|
}
|
|
1789
1985
|
}
|
|
1790
|
-
if (profile.schema === "v1.5" && profile.capabilities) {
|
|
1986
|
+
if ((profile.schema === "v1.5" || profile.schema === "v1.6") && profile.capabilities) {
|
|
1791
1987
|
const capabilities = profile.capabilities;
|
|
1792
1988
|
const tools = asArray(capabilities.tools);
|
|
1793
|
-
const constraints =
|
|
1989
|
+
const constraints = normalizeRuleConstraints(capabilities.constraints).map(
|
|
1990
|
+
(entry) => entry.rule
|
|
1991
|
+
);
|
|
1794
1992
|
lines.push("");
|
|
1795
1993
|
lines.push("[CAPABILITY BOUNDARIES]");
|
|
1796
1994
|
lines.push(
|
|
@@ -2084,7 +2282,7 @@ function evaluateTier1Response(profile, responseText, options = {}) {
|
|
|
2084
2282
|
forbidden_matched: forbiddenMatches,
|
|
2085
2283
|
pass: forbiddenMatches === 0
|
|
2086
2284
|
};
|
|
2087
|
-
const behavioralRules =
|
|
2285
|
+
const behavioralRules = normalizeRuleConstraints(profile?.behavioral_rules);
|
|
2088
2286
|
const structureCheck = {
|
|
2089
2287
|
behavioral_rule_count: behavioralRules.length,
|
|
2090
2288
|
response_non_empty: response.trim().length > 0,
|
|
@@ -2766,7 +2964,9 @@ function buildJudgeUserPrompt(profile, sample) {
|
|
|
2766
2964
|
const targets = collectVoiceTargets(profile);
|
|
2767
2965
|
const preferredTerms = asArray(profile?.vocabulary?.preferred_terms);
|
|
2768
2966
|
const forbiddenTerms = asArray(profile?.vocabulary?.forbidden_terms);
|
|
2769
|
-
const behavioralRules =
|
|
2967
|
+
const behavioralRules = normalizeRuleConstraints(profile?.behavioral_rules).map(
|
|
2968
|
+
(entry) => entry.rule
|
|
2969
|
+
);
|
|
2770
2970
|
return [
|
|
2771
2971
|
`Profile: ${profile?.meta?.name ?? "unknown"}`,
|
|
2772
2972
|
`Role: ${profile?.identity?.role ?? "assistant"}`,
|