@plures/praxis 1.4.4 → 2.0.3
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 +164 -1067
- package/dist/browser/chunk-IUEKGHQN.js +373 -0
- package/dist/browser/factory/index.d.ts +2 -1
- package/dist/browser/index.d.ts +7 -4
- package/dist/browser/index.js +18 -6
- package/dist/browser/integrations/svelte.d.ts +4 -3
- package/dist/browser/project/index.d.ts +2 -1
- package/dist/browser/{reactive-engine.svelte-DgVTqHLc.d.ts → reactive-engine.svelte-BwWadvAW.d.ts} +2 -1
- package/dist/browser/rule-result-DcXWe9tn.d.ts +206 -0
- package/dist/browser/{rules-i1LHpnGd.d.ts → rules-BaWMqxuG.d.ts} +2 -205
- package/dist/browser/unified/index.d.ts +239 -0
- package/dist/browser/unified/index.js +20 -0
- package/dist/node/chunk-IUEKGHQN.js +373 -0
- package/dist/node/cli/index.js +1 -1
- package/dist/node/index.cjs +377 -0
- package/dist/node/index.d.cts +4 -2
- package/dist/node/index.d.ts +4 -2
- package/dist/node/index.js +19 -7
- package/dist/node/integrations/svelte.d.cts +3 -2
- package/dist/node/integrations/svelte.d.ts +3 -2
- package/dist/node/integrations/svelte.js +2 -2
- package/dist/node/{reactive-engine.svelte-DekxqFu0.d.ts → reactive-engine.svelte-BBZLMzus.d.ts} +3 -79
- package/dist/node/{reactive-engine.svelte-Cg0Yc2Hs.d.cts → reactive-engine.svelte-Cbq_V20o.d.cts} +3 -79
- package/dist/node/rule-result-B9GMivAn.d.cts +80 -0
- package/dist/node/rule-result-Bo3sFMmN.d.ts +80 -0
- package/dist/node/unified/index.cjs +494 -0
- package/dist/node/unified/index.d.cts +240 -0
- package/dist/node/unified/index.d.ts +240 -0
- package/dist/node/unified/index.js +21 -0
- package/docs/README.md +58 -102
- package/docs/archive/1.x/CONVERSATIONS_IMPLEMENTATION.md +207 -0
- package/docs/archive/1.x/DECISION_LEDGER_IMPLEMENTATION.md +109 -0
- package/docs/archive/1.x/DECISION_LEDGER_SUMMARY.md +424 -0
- package/docs/archive/1.x/ELEVATION_SUMMARY.md +249 -0
- package/docs/archive/1.x/FEATURE_SUMMARY.md +238 -0
- package/docs/archive/1.x/GOLDEN_PATH_IMPLEMENTATION.md +280 -0
- package/docs/archive/1.x/IMPLEMENTATION.md +166 -0
- package/docs/archive/1.x/IMPLEMENTATION_COMPLETE.md +389 -0
- package/docs/archive/1.x/IMPLEMENTATION_SUMMARY.md +59 -0
- package/docs/archive/1.x/INTEGRATION_ENHANCEMENT_SUMMARY.md +238 -0
- package/docs/archive/1.x/KNO_ENG_REFACTORING_SUMMARY.md +198 -0
- package/docs/archive/1.x/MONOREPO_SUMMARY.md +158 -0
- package/docs/archive/1.x/README.md +28 -0
- package/docs/archive/1.x/SVELTE_INTEGRATION_SUMMARY.md +415 -0
- package/docs/archive/1.x/TASK_1_COMPLETE.md +235 -0
- package/docs/archive/1.x/TASK_1_SUMMARY.md +281 -0
- package/docs/archive/1.x/VERSION_0.2.0_RELEASE_NOTES.md +288 -0
- package/docs/archive/1.x/ValidationChecklist.md +7 -0
- package/package.json +13 -1
- package/src/index.browser.ts +20 -0
- package/src/index.ts +21 -0
- package/src/unified/__tests__/unified-qa.test.ts +761 -0
- package/src/unified/__tests__/unified.test.ts +396 -0
- package/src/unified/core.ts +534 -0
- package/src/unified/index.ts +32 -0
- package/src/unified/rules.ts +66 -0
- package/src/unified/types.ts +148 -0
- package/dist/node/{chunk-ZO2LU4G4.js → chunk-WFRHXZBP.js} +3 -3
- package/dist/node/{validate-5PSWJTIC.js → validate-BY7JNY7H.js} +1 -1
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
// src/unified/core.ts
|
|
2
|
+
var _idCounter = 0;
|
|
3
|
+
function nextId() {
|
|
4
|
+
return `px:${Date.now()}-${++_idCounter}`;
|
|
5
|
+
}
|
|
6
|
+
function createApp(config) {
|
|
7
|
+
const paths = /* @__PURE__ */ new Map();
|
|
8
|
+
for (const schema of config.schema) {
|
|
9
|
+
paths.set(schema.path, {
|
|
10
|
+
schema,
|
|
11
|
+
value: schema.initial,
|
|
12
|
+
subscribers: /* @__PURE__ */ new Set(),
|
|
13
|
+
lastUpdated: 0,
|
|
14
|
+
updateCount: 0
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
let facts = [];
|
|
18
|
+
const factMap = /* @__PURE__ */ new Map();
|
|
19
|
+
const timeline = [];
|
|
20
|
+
const maxTimeline = 1e4;
|
|
21
|
+
function recordTimeline(path, kind, data) {
|
|
22
|
+
const entry = {
|
|
23
|
+
id: nextId(),
|
|
24
|
+
timestamp: Date.now(),
|
|
25
|
+
path,
|
|
26
|
+
kind,
|
|
27
|
+
data
|
|
28
|
+
};
|
|
29
|
+
timeline.push(entry);
|
|
30
|
+
if (timeline.length > maxTimeline) {
|
|
31
|
+
timeline.splice(0, timeline.length - maxTimeline);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
const ruleStates = (config.rules ?? []).map((rule) => ({
|
|
35
|
+
rule,
|
|
36
|
+
lastResult: null,
|
|
37
|
+
emittedTags: /* @__PURE__ */ new Set()
|
|
38
|
+
}));
|
|
39
|
+
const constraints = config.constraints ?? [];
|
|
40
|
+
const livenessConfig = config.liveness;
|
|
41
|
+
const initTime = Date.now();
|
|
42
|
+
let livenessTimer = null;
|
|
43
|
+
if (livenessConfig) {
|
|
44
|
+
const timeout = livenessConfig.timeoutMs ?? 5e3;
|
|
45
|
+
livenessTimer = setTimeout(() => {
|
|
46
|
+
for (const expectedPath of livenessConfig.expect) {
|
|
47
|
+
const state = paths.get(expectedPath);
|
|
48
|
+
if (!state || state.updateCount === 0) {
|
|
49
|
+
const elapsed = Date.now() - initTime;
|
|
50
|
+
recordTimeline(expectedPath, "liveness", {
|
|
51
|
+
stale: true,
|
|
52
|
+
elapsed,
|
|
53
|
+
message: `Path "${expectedPath}" never updated after ${elapsed}ms`
|
|
54
|
+
});
|
|
55
|
+
livenessConfig.onStale?.(expectedPath, elapsed);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}, timeout);
|
|
59
|
+
}
|
|
60
|
+
function getPathValues(watchPaths) {
|
|
61
|
+
const values = {};
|
|
62
|
+
for (const p of watchPaths) {
|
|
63
|
+
const state = paths.get(p);
|
|
64
|
+
values[p] = state ? state.value : void 0;
|
|
65
|
+
}
|
|
66
|
+
return values;
|
|
67
|
+
}
|
|
68
|
+
function notify(path, value) {
|
|
69
|
+
const state = paths.get(path);
|
|
70
|
+
if (!state) return;
|
|
71
|
+
for (const cb of state.subscribers) {
|
|
72
|
+
try {
|
|
73
|
+
cb(value);
|
|
74
|
+
} catch (err) {
|
|
75
|
+
console.error(`[praxis] Subscriber error for "${path}":`, err);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
function checkConstraints(path, value) {
|
|
80
|
+
const violations = [];
|
|
81
|
+
for (const c of constraints) {
|
|
82
|
+
if (!c.watch.includes(path)) continue;
|
|
83
|
+
const values = getPathValues(c.watch);
|
|
84
|
+
values[path] = value;
|
|
85
|
+
try {
|
|
86
|
+
const result = c.validate(values);
|
|
87
|
+
if (result !== true) {
|
|
88
|
+
violations.push({
|
|
89
|
+
kind: "constraint-violation",
|
|
90
|
+
message: result,
|
|
91
|
+
data: { constraintId: c.id, path, description: c.description }
|
|
92
|
+
});
|
|
93
|
+
recordTimeline(path, "constraint-check", {
|
|
94
|
+
constraintId: c.id,
|
|
95
|
+
violated: true,
|
|
96
|
+
message: result
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
} catch (err) {
|
|
100
|
+
violations.push({
|
|
101
|
+
kind: "constraint-violation",
|
|
102
|
+
message: `Constraint "${c.id}" threw: ${err instanceof Error ? err.message : String(err)}`,
|
|
103
|
+
data: { constraintId: c.id, error: err }
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return violations;
|
|
108
|
+
}
|
|
109
|
+
function evaluateRules() {
|
|
110
|
+
const newFacts = [];
|
|
111
|
+
const retractions = [];
|
|
112
|
+
for (const rs of ruleStates) {
|
|
113
|
+
const values = getPathValues(rs.rule.watch);
|
|
114
|
+
try {
|
|
115
|
+
const result = rs.rule.evaluate(values, [...facts]);
|
|
116
|
+
rs.lastResult = result;
|
|
117
|
+
recordTimeline(rs.rule.watch[0] ?? "*", "rule-eval", {
|
|
118
|
+
ruleId: rs.rule.id,
|
|
119
|
+
kind: result.kind,
|
|
120
|
+
reason: result.reason
|
|
121
|
+
});
|
|
122
|
+
switch (result.kind) {
|
|
123
|
+
case "emit":
|
|
124
|
+
newFacts.push(...result.facts);
|
|
125
|
+
for (const f of result.facts) {
|
|
126
|
+
rs.emittedTags.add(f.tag);
|
|
127
|
+
}
|
|
128
|
+
break;
|
|
129
|
+
case "retract":
|
|
130
|
+
retractions.push(...result.retractTags);
|
|
131
|
+
for (const tag of result.retractTags) {
|
|
132
|
+
rs.emittedTags.delete(tag);
|
|
133
|
+
}
|
|
134
|
+
break;
|
|
135
|
+
case "noop":
|
|
136
|
+
case "skip":
|
|
137
|
+
if (rs.emittedTags.size > 0) {
|
|
138
|
+
retractions.push(...rs.emittedTags);
|
|
139
|
+
rs.emittedTags.clear();
|
|
140
|
+
}
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
} catch (err) {
|
|
144
|
+
console.error(`[praxis] Rule "${rs.rule.id}" error:`, err);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
if (retractions.length > 0) {
|
|
148
|
+
const retractSet = new Set(retractions);
|
|
149
|
+
for (const tag of retractSet) {
|
|
150
|
+
factMap.delete(tag);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
for (const f of newFacts) {
|
|
154
|
+
factMap.set(f.tag, f);
|
|
155
|
+
}
|
|
156
|
+
facts = Array.from(factMap.values());
|
|
157
|
+
}
|
|
158
|
+
function query(path, opts) {
|
|
159
|
+
let state = paths.get(path);
|
|
160
|
+
if (!state) {
|
|
161
|
+
state = {
|
|
162
|
+
schema: { path, initial: void 0 },
|
|
163
|
+
value: void 0,
|
|
164
|
+
subscribers: /* @__PURE__ */ new Set(),
|
|
165
|
+
lastUpdated: 0,
|
|
166
|
+
updateCount: 0
|
|
167
|
+
};
|
|
168
|
+
paths.set(path, state);
|
|
169
|
+
}
|
|
170
|
+
const ref = {
|
|
171
|
+
get current() {
|
|
172
|
+
const s = paths.get(path);
|
|
173
|
+
return applyQueryOpts(s?.value ?? state.schema.initial, opts);
|
|
174
|
+
},
|
|
175
|
+
subscribe(cb) {
|
|
176
|
+
const wrappedCb = (rawValue) => {
|
|
177
|
+
const processed = applyQueryOpts(rawValue, opts);
|
|
178
|
+
cb(processed);
|
|
179
|
+
};
|
|
180
|
+
const s = paths.get(path);
|
|
181
|
+
s.subscribers.add(wrappedCb);
|
|
182
|
+
try {
|
|
183
|
+
cb(ref.current);
|
|
184
|
+
} catch (err) {
|
|
185
|
+
console.error(`[praxis] query("${path}") subscriber init error:`, err);
|
|
186
|
+
}
|
|
187
|
+
return () => {
|
|
188
|
+
s.subscribers.delete(wrappedCb);
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
return ref;
|
|
193
|
+
}
|
|
194
|
+
function applyQueryOpts(value, opts) {
|
|
195
|
+
if (!opts || !Array.isArray(value)) return value;
|
|
196
|
+
let result = [...value];
|
|
197
|
+
if (opts.where) result = result.filter(opts.where);
|
|
198
|
+
if (opts.sort) result.sort(opts.sort);
|
|
199
|
+
if (opts.select) result = result.map(opts.select);
|
|
200
|
+
if (opts.limit) result = result.slice(0, opts.limit);
|
|
201
|
+
return result;
|
|
202
|
+
}
|
|
203
|
+
function mutateInternal(path, value) {
|
|
204
|
+
const violations = checkConstraints(path, value);
|
|
205
|
+
if (violations.length > 0) {
|
|
206
|
+
return { violations, emittedFacts: [] };
|
|
207
|
+
}
|
|
208
|
+
const state = paths.get(path);
|
|
209
|
+
if (!state) {
|
|
210
|
+
paths.set(path, {
|
|
211
|
+
schema: { path, initial: value },
|
|
212
|
+
value,
|
|
213
|
+
subscribers: /* @__PURE__ */ new Set(),
|
|
214
|
+
lastUpdated: Date.now(),
|
|
215
|
+
updateCount: 1
|
|
216
|
+
});
|
|
217
|
+
} else {
|
|
218
|
+
const before = state.value;
|
|
219
|
+
state.value = value;
|
|
220
|
+
state.lastUpdated = Date.now();
|
|
221
|
+
state.updateCount++;
|
|
222
|
+
recordTimeline(path, "mutation", {
|
|
223
|
+
before: summarize(before),
|
|
224
|
+
after: summarize(value)
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
notify(path, value);
|
|
228
|
+
const factsBefore = facts.length;
|
|
229
|
+
evaluateRules();
|
|
230
|
+
const emittedFacts = facts.slice(factsBefore);
|
|
231
|
+
return { violations: [], emittedFacts };
|
|
232
|
+
}
|
|
233
|
+
function mutate(path, value) {
|
|
234
|
+
const { violations, emittedFacts } = mutateInternal(path, value);
|
|
235
|
+
return {
|
|
236
|
+
accepted: violations.length === 0,
|
|
237
|
+
violations,
|
|
238
|
+
facts: emittedFacts
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
function batchMutate(fn) {
|
|
242
|
+
const allViolations = [];
|
|
243
|
+
const pendingWrites = [];
|
|
244
|
+
fn((path, value) => {
|
|
245
|
+
const violations = checkConstraints(path, value);
|
|
246
|
+
if (violations.length > 0) {
|
|
247
|
+
allViolations.push(...violations);
|
|
248
|
+
} else {
|
|
249
|
+
pendingWrites.push({ path, value });
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
if (allViolations.length > 0) {
|
|
253
|
+
return { accepted: false, violations: allViolations, facts: [] };
|
|
254
|
+
}
|
|
255
|
+
for (const { path, value } of pendingWrites) {
|
|
256
|
+
const state = paths.get(path);
|
|
257
|
+
if (state) {
|
|
258
|
+
const before = state.value;
|
|
259
|
+
state.value = value;
|
|
260
|
+
state.lastUpdated = Date.now();
|
|
261
|
+
state.updateCount++;
|
|
262
|
+
recordTimeline(path, "mutation", {
|
|
263
|
+
before: summarize(before),
|
|
264
|
+
after: summarize(value)
|
|
265
|
+
});
|
|
266
|
+
} else {
|
|
267
|
+
paths.set(path, {
|
|
268
|
+
schema: { path, initial: value },
|
|
269
|
+
value,
|
|
270
|
+
subscribers: /* @__PURE__ */ new Set(),
|
|
271
|
+
lastUpdated: Date.now(),
|
|
272
|
+
updateCount: 1
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
for (const { path, value } of pendingWrites) {
|
|
277
|
+
notify(path, value);
|
|
278
|
+
}
|
|
279
|
+
const factsBefore = facts.length;
|
|
280
|
+
evaluateRules();
|
|
281
|
+
return {
|
|
282
|
+
accepted: true,
|
|
283
|
+
violations: [],
|
|
284
|
+
facts: facts.slice(factsBefore)
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
function getLiveness() {
|
|
288
|
+
const result = {};
|
|
289
|
+
const now = Date.now();
|
|
290
|
+
const watchPaths = livenessConfig?.expect ?? [];
|
|
291
|
+
for (const p of watchPaths) {
|
|
292
|
+
const state = paths.get(p);
|
|
293
|
+
const lastUpdated = state?.lastUpdated ?? 0;
|
|
294
|
+
const elapsed = lastUpdated > 0 ? now - lastUpdated : now - initTime;
|
|
295
|
+
result[p] = {
|
|
296
|
+
stale: !state || state.updateCount === 0,
|
|
297
|
+
lastUpdated,
|
|
298
|
+
elapsed
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
return result;
|
|
302
|
+
}
|
|
303
|
+
function destroy() {
|
|
304
|
+
if (livenessTimer) clearTimeout(livenessTimer);
|
|
305
|
+
for (const state of paths.values()) {
|
|
306
|
+
state.subscribers.clear();
|
|
307
|
+
}
|
|
308
|
+
paths.clear();
|
|
309
|
+
facts = [];
|
|
310
|
+
factMap.clear();
|
|
311
|
+
timeline.length = 0;
|
|
312
|
+
}
|
|
313
|
+
return {
|
|
314
|
+
query,
|
|
315
|
+
mutate,
|
|
316
|
+
batch: batchMutate,
|
|
317
|
+
facts: () => [...facts],
|
|
318
|
+
violations: () => {
|
|
319
|
+
const allViolations = [];
|
|
320
|
+
for (const c of constraints) {
|
|
321
|
+
const values = getPathValues(c.watch);
|
|
322
|
+
try {
|
|
323
|
+
const result = c.validate(values);
|
|
324
|
+
if (result !== true) {
|
|
325
|
+
allViolations.push({
|
|
326
|
+
kind: "constraint-violation",
|
|
327
|
+
message: result,
|
|
328
|
+
data: { constraintId: c.id }
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
} catch {
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
return allViolations;
|
|
335
|
+
},
|
|
336
|
+
timeline: () => [...timeline],
|
|
337
|
+
evaluate: evaluateRules,
|
|
338
|
+
destroy,
|
|
339
|
+
liveness: getLiveness
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
function summarize(value) {
|
|
343
|
+
if (value === null || value === void 0) return value;
|
|
344
|
+
if (typeof value !== "object") return value;
|
|
345
|
+
if (Array.isArray(value)) return `[Array(${value.length})]`;
|
|
346
|
+
const keys = Object.keys(value);
|
|
347
|
+
if (keys.length > 10) return `{Object(${keys.length} keys)}`;
|
|
348
|
+
return value;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// src/unified/types.ts
|
|
352
|
+
function definePath(path, initial, opts) {
|
|
353
|
+
return { path, initial, ...opts };
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// src/unified/rules.ts
|
|
357
|
+
function defineRule(rule) {
|
|
358
|
+
return rule;
|
|
359
|
+
}
|
|
360
|
+
function defineConstraint(constraint) {
|
|
361
|
+
return constraint;
|
|
362
|
+
}
|
|
363
|
+
function defineModule(name, rules) {
|
|
364
|
+
return { name, rules };
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
export {
|
|
368
|
+
createApp,
|
|
369
|
+
definePath,
|
|
370
|
+
defineRule,
|
|
371
|
+
defineConstraint,
|
|
372
|
+
defineModule
|
|
373
|
+
};
|
package/dist/node/cli/index.js
CHANGED
|
@@ -717,7 +717,7 @@ program.command("verify <type>").description("Verify project implementation (e.g
|
|
|
717
717
|
});
|
|
718
718
|
program.command("validate").description("Validate contract coverage for rules and constraints").option("--output <format>", "Output format (console, json, sarif)", "console").option("--strict", "Exit with error if contracts are missing", false).option("--registry <path>", "Path to registry module").option("--tests", "Check for tests for each rule/constraint", true).option("--spec", "Check for specs for each rule/constraint", true).option("--emit-facts", "Emit ContractMissing facts JSON payload", false).option("--gap-output <file>", "Write contract-gap payload to file").option("--ledger <dir>", "Write logic ledger snapshots to directory").option("--author <name>", "Author name for ledger entries", "system").action(async (options) => {
|
|
719
719
|
try {
|
|
720
|
-
const { validateCommand } = await import("../validate-
|
|
720
|
+
const { validateCommand } = await import("../validate-BY7JNY7H.js");
|
|
721
721
|
await validateCommand(options);
|
|
722
722
|
} catch (error) {
|
|
723
723
|
console.error("Error validating contracts:", error);
|