@principles/core 1.275.1 → 1.275.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/runtime-v2/config/__tests__/pd-config-sparse-bootstrap.test.d.ts +2 -0
- package/dist/runtime-v2/config/__tests__/pd-config-sparse-bootstrap.test.d.ts.map +1 -0
- package/dist/runtime-v2/config/__tests__/pd-config-sparse-bootstrap.test.js +143 -0
- package/dist/runtime-v2/config/__tests__/pd-config-sparse-bootstrap.test.js.map +1 -0
- package/dist/runtime-v2/feature-flags/__tests__/installer-config-parity.test.js +44 -27
- package/dist/runtime-v2/feature-flags/__tests__/installer-config-parity.test.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pd-config-sparse-bootstrap.test.d.ts","sourceRoot":"","sources":["../../../../src/runtime-v2/config/__tests__/pd-config-sparse-bootstrap.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PRI-645 — sparse bootstrap config contracts.
|
|
3
|
+
*
|
|
4
|
+
* Fresh configs (installer template, `pd runtime init`) now carry
|
|
5
|
+
* `features: {}`; these tests lock the invariants that make that safe:
|
|
6
|
+
*
|
|
7
|
+
* 1. Effective parity — a sparse config resolves to EXACTLY the same
|
|
8
|
+
* effective flag map as the old dense default snapshot did.
|
|
9
|
+
* 2. Category semantics — absence follows registry defaults for
|
|
10
|
+
* core/quiet-on/quiet-off/gone flags, and gone stays un-resurrectable.
|
|
11
|
+
* 3. Registry authority — when the registry default flips, a sparse config
|
|
12
|
+
* follows the new default while an old-style default snapshot would pin
|
|
13
|
+
* the stale value (the hazard PRI-645 removes).
|
|
14
|
+
* 4. Observation-window compatibility — the PRI-638 legacy
|
|
15
|
+
* diagnostician_split_pipeline=false cutover and the PRI-609 aliases keep
|
|
16
|
+
* working on existing configs regardless of bootstrap sparsification.
|
|
17
|
+
*/
|
|
18
|
+
import { describe, expect, it } from 'vitest';
|
|
19
|
+
import { computeEffectivePdConfig } from '../pd-config-effective.js';
|
|
20
|
+
import { getDefaultPdConfig } from '../pd-config-defaults.js';
|
|
21
|
+
import { DEFAULT_FEATURE_FLAGS, FEATURE_FLAG_ALIASES, computeEffectiveFlags, } from '../../feature-flags/feature-flag-contract.js';
|
|
22
|
+
function sparseConfig() {
|
|
23
|
+
return { ...getDefaultPdConfig(), features: {} };
|
|
24
|
+
}
|
|
25
|
+
/** Build a config whose features section is exactly the given overrides
|
|
26
|
+
* (typed parameter gives the literal contextual typing — a bare spread
|
|
27
|
+
* widens `category` to string). */
|
|
28
|
+
function withFeatures(features) {
|
|
29
|
+
return { ...getDefaultPdConfig(), features };
|
|
30
|
+
}
|
|
31
|
+
describe('PRI-645 effective parity — sparse vs dense bootstrap', () => {
|
|
32
|
+
it('features: {} resolves to exactly the registry defaults for ALL registered flags', () => {
|
|
33
|
+
const sparseEffective = computeEffectivePdConfig(sparseConfig());
|
|
34
|
+
// The pre-PRI-645 bootstrap shape: a full registry snapshot in raw config.
|
|
35
|
+
const denseEffective = computeEffectivePdConfig(getDefaultPdConfig());
|
|
36
|
+
expect(Object.keys(sparseEffective.config.features)).toHaveLength(DEFAULT_FEATURE_FLAGS.length);
|
|
37
|
+
for (const flag of DEFAULT_FEATURE_FLAGS) {
|
|
38
|
+
const effective = sparseEffective.config.features[flag.id];
|
|
39
|
+
expect(effective, `flag ${flag.id} missing from sparse effective map`).toBeDefined();
|
|
40
|
+
expect(effective).toEqual({ category: flag.category, enabled: flag.enabled });
|
|
41
|
+
// BEFORE == AFTER: identical to the dense-snapshot effective value.
|
|
42
|
+
expect(effective).toEqual(denseEffective.config.features[flag.id]);
|
|
43
|
+
}
|
|
44
|
+
// No flag may be reported as changed-from-default on a fresh sparse config.
|
|
45
|
+
expect(sparseEffective.featuresChangedFromDefault).toEqual([]);
|
|
46
|
+
});
|
|
47
|
+
it('registry defaults are the values sparse configs resolve to (registry snapshot, not a fixture)', () => {
|
|
48
|
+
// The expected map is read from the canonical registry — never a
|
|
49
|
+
// hand-maintained third copy of defaults (PRI-645 §21 anti-pattern).
|
|
50
|
+
const effective = computeEffectivePdConfig(sparseConfig());
|
|
51
|
+
const registryById = new Map(DEFAULT_FEATURE_FLAGS.map(f => [f.id, f]));
|
|
52
|
+
for (const [id, entry] of Object.entries(effective.config.features)) {
|
|
53
|
+
const def = registryById.get(id);
|
|
54
|
+
expect(def, `effective flag ${id} not registered`).toBeDefined();
|
|
55
|
+
expect(entry.category).toBe(def?.category);
|
|
56
|
+
expect(entry.enabled).toBe(def?.enabled);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
describe('PRI-645 category semantics under config absence', () => {
|
|
61
|
+
it('core flag absence → registry default ON (prompt)', () => {
|
|
62
|
+
const effective = computeEffectivePdConfig(sparseConfig());
|
|
63
|
+
expect(effective.config.features.prompt).toEqual({ category: 'core', enabled: true });
|
|
64
|
+
});
|
|
65
|
+
it('quiet ON-default flag absence → registry default ON (feedback_channel)', () => {
|
|
66
|
+
const effective = computeEffectivePdConfig(sparseConfig());
|
|
67
|
+
const def = DEFAULT_FEATURE_FLAGS.find(f => f.id === 'feedback_channel');
|
|
68
|
+
expect(def?.enabled).toBe(true);
|
|
69
|
+
expect(effective.config.features.feedback_channel).toEqual({ category: 'quiet', enabled: true });
|
|
70
|
+
});
|
|
71
|
+
it('quiet OFF-default flag absence → registry default OFF (gfi)', () => {
|
|
72
|
+
const effective = computeEffectivePdConfig(sparseConfig());
|
|
73
|
+
const def = DEFAULT_FEATURE_FLAGS.find(f => f.id === 'gfi');
|
|
74
|
+
expect(def?.enabled).toBe(false);
|
|
75
|
+
expect(effective.config.features.gfi).toEqual({ category: 'quiet', enabled: false });
|
|
76
|
+
});
|
|
77
|
+
it('gone flag absence → gone behavior, and config cannot resurrect it', () => {
|
|
78
|
+
const absent = computeEffectivePdConfig(sparseConfig());
|
|
79
|
+
expect(absent.config.features.nocturnal).toEqual({ category: 'gone', enabled: false });
|
|
80
|
+
// Explicit resurrection attempt is refused observably — with or without
|
|
81
|
+
// a sparse bootstrap, gone semantics live in the resolver, never in
|
|
82
|
+
// config presence.
|
|
83
|
+
const resurrect = computeEffectivePdConfig(withFeatures({
|
|
84
|
+
nocturnal: { category: 'gone', enabled: true },
|
|
85
|
+
}));
|
|
86
|
+
expect(resurrect.config.features.nocturnal).toEqual({ category: 'gone', enabled: false });
|
|
87
|
+
expect(resurrect.warnings.some(w => w.includes("gone flag cannot be re-enabled"))).toBe(true);
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
describe('PRI-645 registry authority — sparse config does not pin old defaults', () => {
|
|
91
|
+
it('a registry default flip reaches sparse configs but not old default snapshots', () => {
|
|
92
|
+
// Pure merge seam: computeEffectiveFlags takes the registry as a
|
|
93
|
+
// parameter, so a graduation flip is simulated without rewriting the
|
|
94
|
+
// production constant (no brittle source rewriting).
|
|
95
|
+
const flippedRegistry = DEFAULT_FEATURE_FLAGS.map(f => f.id === 'gfi' ? { ...f, enabled: !f.enabled } : f);
|
|
96
|
+
const gfiDefaultBefore = DEFAULT_FEATURE_FLAGS.find(f => f.id === 'gfi')?.enabled;
|
|
97
|
+
const gfiDefaultAfter = flippedRegistry.find(f => f.id === 'gfi')?.enabled;
|
|
98
|
+
expect(gfiDefaultAfter).toBe(!gfiDefaultBefore);
|
|
99
|
+
// Sparse fresh config follows the NEW registry default.
|
|
100
|
+
const sparse = computeEffectiveFlags({}, flippedRegistry, 'sparse-test');
|
|
101
|
+
expect(sparse.flags.gfi?.enabled).toBe(gfiDefaultAfter);
|
|
102
|
+
// The pre-PRI-645 bootstrap shape (a materialized default snapshot)
|
|
103
|
+
// would keep serving the OLD value after the flip — frozen default.
|
|
104
|
+
const oldSnapshot = computeEffectiveFlags({ gfi: { category: 'quiet', enabled: gfiDefaultBefore ?? false } }, flippedRegistry, 'dense-test');
|
|
105
|
+
expect(oldSnapshot.flags.gfi?.enabled).toBe(gfiDefaultBefore);
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
describe('PRI-645 existing-config compatibility preserved', () => {
|
|
109
|
+
it('PRI-638 legacy diagnostician_split_pipeline=false still folds Diagnostician off', () => {
|
|
110
|
+
const config = withFeatures({
|
|
111
|
+
diagnostician_split_pipeline: { category: 'quiet', enabled: false },
|
|
112
|
+
});
|
|
113
|
+
const effective = computeEffectivePdConfig(config);
|
|
114
|
+
expect(effective.config.internalAgents.agents.diagnostician?.enabled).toBe(false);
|
|
115
|
+
expect(effective.warnings.some(w => w.includes('PRI-638 cutover'))).toBe(true);
|
|
116
|
+
});
|
|
117
|
+
it('PRI-609 snake_case aliases still normalize onto canonical IDs', () => {
|
|
118
|
+
expect(Object.keys(FEATURE_FLAG_ALIASES).length).toBeGreaterThan(0);
|
|
119
|
+
for (const [alias, canonical] of Object.entries(FEATURE_FLAG_ALIASES)) {
|
|
120
|
+
const config = withFeatures({
|
|
121
|
+
[alias]: { category: 'quiet', enabled: false },
|
|
122
|
+
});
|
|
123
|
+
const effective = computeEffectivePdConfig(config);
|
|
124
|
+
// Alias key controls the canonical flag's effective value.
|
|
125
|
+
expect(effective.config.features[canonical]?.enabled).toBe(false);
|
|
126
|
+
// The alias key itself never becomes a capability entry.
|
|
127
|
+
expect(Object.hasOwn(effective.config.features, alias)).toBe(false);
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
it('explicit owner overrides keep their source on a sparse-config workspace', () => {
|
|
131
|
+
const config = withFeatures({
|
|
132
|
+
intent_engineering: { category: 'quiet', enabled: true, source: 'owner' },
|
|
133
|
+
});
|
|
134
|
+
const effective = computeEffectivePdConfig(config);
|
|
135
|
+
expect(effective.config.features.intent_engineering).toEqual({
|
|
136
|
+
category: 'quiet',
|
|
137
|
+
enabled: true,
|
|
138
|
+
source: 'owner',
|
|
139
|
+
});
|
|
140
|
+
expect(effective.featuresChangedFromDefault).toContain('intent_engineering');
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
//# sourceMappingURL=pd-config-sparse-bootstrap.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pd-config-sparse-bootstrap.test.js","sourceRoot":"","sources":["../../../../src/runtime-v2/config/__tests__/pd-config-sparse-bootstrap.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,13 +2,15 @@ import { describe, expect, it } from 'vitest';
|
|
|
2
2
|
import * as fs from 'node:fs';
|
|
3
3
|
import * as path from 'node:path';
|
|
4
4
|
import { DEFAULT_FEATURE_FLAGS } from '../feature-flag-contract.js';
|
|
5
|
-
// PRI-
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
|
|
5
|
+
// PRI-645: the installer's hardcoded .pd/config.yaml template
|
|
6
|
+
// (create-principles-disciple/src/mvp-config.ts) must NOT duplicate registry
|
|
7
|
+
// defaults. The old PRI-574 contract kept a full default snapshot "aligned"
|
|
8
|
+
// with the registry; the converged contract is that the duplicate must not
|
|
9
|
+
// exist at all — config records intent, the registry owns defaults.
|
|
10
|
+
//
|
|
11
|
+
// The installer package deliberately has no dependency on @principles/core
|
|
12
|
+
// (independent installability), so this contract lives on the core side and
|
|
13
|
+
// parses the installer SOURCE instead of importing it.
|
|
12
14
|
const REGISTRY_BY_ID = new Map(DEFAULT_FEATURE_FLAGS.map(f => [f.id, f]));
|
|
13
15
|
function findInstallerSource() {
|
|
14
16
|
let dir = process.cwd();
|
|
@@ -28,7 +30,6 @@ function parseInstallerFeatures(source) {
|
|
|
28
30
|
// Entries look like:
|
|
29
31
|
// prompt: { category: 'core', enabled: true, source: 'system' },
|
|
30
32
|
// 'host.codex': { category: 'core', enabled: true, source: 'system' },
|
|
31
|
-
// (PRI-637: entries carry a system provenance label — tolerated, not required.)
|
|
32
33
|
const entryPattern = /(^|\n)\s{6}'?([a-z0-9_.]+)'?:\s*\{\s*category:\s*'([a-z_]+)',\s*enabled:\s*(true|false),?\s*(?:source:\s*'[a-z_]+',?\s*)?\}/g;
|
|
33
34
|
const entries = [];
|
|
34
35
|
for (const match of source.matchAll(entryPattern)) {
|
|
@@ -39,31 +40,47 @@ function parseInstallerFeatures(source) {
|
|
|
39
40
|
}
|
|
40
41
|
entries.push({ id, category, enabled: enabledRaw === 'true' });
|
|
41
42
|
}
|
|
42
|
-
if (entries.length === 0) {
|
|
43
|
-
throw new Error('parsed zero installer feature entries — mvp-config.ts template shape changed; update this parser');
|
|
44
|
-
}
|
|
45
43
|
return entries;
|
|
46
44
|
}
|
|
47
|
-
describe('PRI-
|
|
45
|
+
describe('PRI-645 installer bootstrap config ↔ registry sparse-override contract', () => {
|
|
48
46
|
const source = fs.readFileSync(findInstallerSource(), 'utf8');
|
|
49
47
|
const entries = parseInstallerFeatures(source);
|
|
50
|
-
it(
|
|
51
|
-
|
|
52
|
-
|
|
48
|
+
it("declares an empty features map in the fresh template (no snapshot of registry defaults)", () => {
|
|
49
|
+
// Template-shape guard: the literal `features: {}` must be present in
|
|
50
|
+
// generateConfigYamlContent. If this fails, the template shape changed —
|
|
51
|
+
// update this contract deliberately (an intentional bootstrap override is
|
|
52
|
+
// legal but must satisfy the contracts below).
|
|
53
|
+
expect(source).toContain('features: {},');
|
|
53
54
|
});
|
|
54
|
-
it('
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
55
|
+
it('materializes zero default-equivalent registry entries (duplicate defaults must not exist)', () => {
|
|
56
|
+
// Contract A: any entry the installer writes whose category AND enabled
|
|
57
|
+
// equal the registry default is a forbidden default snapshot — it would
|
|
58
|
+
// freeze today's defaults against future registry flips.
|
|
59
|
+
const snapshots = entries
|
|
60
|
+
.filter(e => {
|
|
61
|
+
const def = REGISTRY_BY_ID.get(e.id);
|
|
62
|
+
return def !== undefined && def.category === e.category && def.enabled === e.enabled;
|
|
63
|
+
})
|
|
64
|
+
.map(e => e.id);
|
|
65
|
+
expect(snapshots, `installer materializes registry-default-equivalent entries: ${snapshots.join(', ')} — remove them; defaults belong to the registry`).toEqual([]);
|
|
66
|
+
});
|
|
67
|
+
it('every bootstrap entry exists in the runtime registry (no orphans)', () => {
|
|
68
|
+
const orphans = entries.filter(e => !REGISTRY_BY_ID.has(e.id)).map(e => e.id);
|
|
69
|
+
expect(orphans, `installer writes flags unknown to the registry: ${orphans.join(', ')}`).toEqual([]);
|
|
60
70
|
});
|
|
61
|
-
it('
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
71
|
+
it('every bootstrap entry differs from the registry default (intentional override only)', () => {
|
|
72
|
+
// Contract C: a bootstrap entry is legal only as a DELIBERATE deviation.
|
|
73
|
+
// There is no allowlist: if an entry equals the default, Contract A
|
|
74
|
+
// already fails it. This test keeps the intent explicit for future edits.
|
|
75
|
+
const equivalences = entries
|
|
76
|
+
.filter(e => {
|
|
77
|
+
const def = REGISTRY_BY_ID.get(e.id);
|
|
78
|
+
return def !== undefined && def.category === e.category && def.enabled === e.enabled;
|
|
79
|
+
})
|
|
80
|
+
.map(e => e.id);
|
|
81
|
+
expect(equivalences).toEqual([]);
|
|
82
|
+
// Current expected steady state: zero bootstrap overrides at all.
|
|
83
|
+
expect(entries, 'if adding a bootstrap override, document the product/safety reason in mvp-config.ts and update this expectation').toEqual([]);
|
|
67
84
|
});
|
|
68
85
|
});
|
|
69
86
|
//# sourceMappingURL=installer-config-parity.test.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"installer-config-parity.test.js","sourceRoot":"","sources":["../../../../src/runtime-v2/feature-flags/__tests__/installer-config-parity.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,qBAAqB,EAAE,MAAM,6BAA6B,CAAC;AAEpE,
|
|
1
|
+
{"version":3,"file":"installer-config-parity.test.js","sourceRoot":"","sources":["../../../../src/runtime-v2/feature-flags/__tests__/installer-config-parity.test.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@principles/core",
|
|
3
|
-
"version": "1.275.
|
|
3
|
+
"version": "1.275.2",
|
|
4
4
|
"description": "Pure-logic core of Principles Disciple, an AI Agent Governance System - pain signal capture and principle proposal (no I/O)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|