@ngockhoale/ukit 3.0.6 → 3.0.7

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.
Files changed (39) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/package.json +1 -1
  3. package/scripts/bench/data-foundation.mjs +562 -0
  4. package/src/core/observability/adapters/common.js +75 -0
  5. package/src/core/observability/adapters/contextAdapter.js +55 -0
  6. package/src/core/observability/adapters/decisionAdapter.js +61 -0
  7. package/src/core/observability/adapters/routeAdapter.js +135 -0
  8. package/src/core/observability/analytics/digest.js +186 -0
  9. package/src/core/observability/analytics/fingerprints.js +126 -0
  10. package/src/core/observability/analytics/opportunities.js +329 -0
  11. package/src/core/observability/analytics/rebuild.js +56 -0
  12. package/src/core/observability/analytics/summary.js +298 -0
  13. package/src/core/observability/emit/config.js +29 -0
  14. package/src/core/observability/emit/recorder.js +297 -0
  15. package/src/core/observability/evaluation/aiPacket.js +230 -0
  16. package/src/core/observability/evaluation/optimizationKnowledge.js +172 -0
  17. package/src/core/observability/evaluation/replay.js +143 -0
  18. package/src/core/observability/evaluation/scorecard.js +445 -0
  19. package/src/core/observability/privacy/allowlist.js +185 -0
  20. package/src/core/observability/privacy/redaction.js +113 -0
  21. package/src/core/observability/privacy/sanitizeForSupport.js +133 -0
  22. package/src/core/observability/privacy/sanitizeObserved.js +134 -0
  23. package/src/core/observability/rollout.js +155 -0
  24. package/src/core/observability/schema/constants.js +66 -0
  25. package/src/core/observability/schema/registry.js +223 -0
  26. package/src/core/observability/schema/validate.js +227 -0
  27. package/src/core/observability/segments/internal.js +241 -0
  28. package/src/core/observability/segments/readSegments.js +215 -0
  29. package/src/core/observability/segments/recovery.js +123 -0
  30. package/src/core/observability/segments/retention.js +381 -0
  31. package/src/core/observability/support/import.js +402 -0
  32. package/src/core/observability/support/manifest.js +135 -0
  33. package/src/core/observability/support/paths.js +94 -0
  34. package/src/core/observability/support/projector.js +483 -0
  35. package/src/core/observability/support/renderer.js +130 -0
  36. package/src/core/observability/support/retention.js +155 -0
  37. package/template_project/.omp/RULES.md +6 -6
  38. package/template_project/.omp/config.yml +6 -0
  39. package/template_project/instructions/overlays/omp-rules.md +6 -6
@@ -0,0 +1,155 @@
1
+ /**
2
+ * retention.js (TASK-009, SPEC §5 DF-FR10) — support-view cap/age pruning.
3
+ *
4
+ * pruneSupportView(dir, policy?) → { ok: true, removed: string[], bytes }
5
+ * | { ok: false, reason }
6
+ *
7
+ * This is the retention owner for the user-facing `UKit Support` folder —
8
+ * deliberately SEPARATE from segments/retention.js, which governs the
9
+ * private canonical store. The two stores have different caps, different
10
+ * audiences, and different deletion safety rules.
11
+ *
12
+ * Ownership rule (the safety boundary): a file inside the support dir is
13
+ * prunable only when it is a regular file AND
14
+ * - listed in the current manifest.json `files` map, or
15
+ * - matches the digest naming convention `digest-<16 hex>.md`.
16
+ * Symlinks, directories, foreign-named files, and — when no manifest
17
+ * exists — static-named files are never touched. User-owned contents
18
+ * survive pruning unconditionally.
19
+ *
20
+ * Eviction order under the byte cap mirrors the projector's priority:
21
+ * digests first (oldest mtime first), then records.jsonl, then SUMMARY.md,
22
+ * README.md, and manifest.json last — core self-description outlives
23
+ * derived content.
24
+ */
25
+
26
+ import fs from 'node:fs';
27
+ import path from 'node:path';
28
+
29
+ import { ioReason } from '../segments/internal.js';
30
+
31
+ export const STATIC_OWNED_NAMES = new Set([
32
+ 'README.md',
33
+ 'SUMMARY.md',
34
+ 'manifest.json',
35
+ 'records.jsonl',
36
+ ]);
37
+
38
+ export const DIGEST_NAME_RE = /^digest-[0-9a-f]{16}\.md$/;
39
+
40
+ /** Names this module may ever treat as owned. */
41
+ export function isOwnedName(name) {
42
+ return STATIC_OWNED_NAMES.has(name) || DIGEST_NAME_RE.test(name);
43
+ }
44
+
45
+ export const DEFAULT_SUPPORT_POLICY = Object.freeze({
46
+ maxBytes: 8 * 1024 * 1024,
47
+ maxAgeMs: 30 * 24 * 60 * 60 * 1000,
48
+ });
49
+
50
+ function isPlainObject(value) {
51
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
52
+ }
53
+
54
+ async function readManifestFiles(dir) {
55
+ try {
56
+ const raw = await fs.promises.readFile(path.join(dir, 'manifest.json'), 'utf8');
57
+ const manifest = JSON.parse(raw);
58
+ if (isPlainObject(manifest) && isPlainObject(manifest.files)) {
59
+ return new Set(Object.keys(manifest.files));
60
+ }
61
+ } catch {
62
+ // missing/corrupt manifest → only convention-named digests stay owned
63
+ }
64
+ return null;
65
+ }
66
+
67
+ /** Eviction priority: lower number = evicted sooner. */
68
+ function evictionRank(name) {
69
+ if (DIGEST_NAME_RE.test(name)) return 0;
70
+ if (name === 'records.jsonl') return 1;
71
+ if (name === 'SUMMARY.md') return 2;
72
+ if (name === 'README.md') return 3;
73
+ return 4; // manifest.json and anything else owned
74
+ }
75
+
76
+ /**
77
+ * @param {string} dir resolved support directory
78
+ * @param {{ maxBytes?: number, maxAgeMs?: number, now?: number }} [policy]
79
+ */
80
+ export async function pruneSupportView(dir, policy = {}) {
81
+ const maxBytes = Number.isFinite(policy.maxBytes) ? policy.maxBytes : DEFAULT_SUPPORT_POLICY.maxBytes;
82
+ const maxAgeMs = Number.isFinite(policy.maxAgeMs) ? policy.maxAgeMs : DEFAULT_SUPPORT_POLICY.maxAgeMs;
83
+ const now = Number.isFinite(policy.now) ? policy.now : Date.now();
84
+
85
+ let dirStat;
86
+ try {
87
+ dirStat = await fs.promises.lstat(dir);
88
+ } catch (err) {
89
+ if (err && err.code === 'ENOENT') return { ok: true, removed: [], bytes: 0 };
90
+ return { ok: false, reason: ioReason(err) };
91
+ }
92
+ if (dirStat.isSymbolicLink() || !dirStat.isDirectory()) {
93
+ return { ok: false, reason: 'support_path_not_directory' };
94
+ }
95
+
96
+ const manifestOwned = await readManifestFiles(dir);
97
+
98
+ let entries;
99
+ try {
100
+ entries = await fs.promises.readdir(dir);
101
+ } catch (err) {
102
+ return { ok: false, reason: ioReason(err) };
103
+ }
104
+
105
+ // Snapshot owned regular files with stats. Never follow symlinks.
106
+ const owned = [];
107
+ for (const name of entries) {
108
+ const ownedByManifest = manifestOwned !== null && manifestOwned.has(name);
109
+ const ownedByConvention = DIGEST_NAME_RE.test(name);
110
+ if (!ownedByManifest && !ownedByConvention) continue;
111
+ let st;
112
+ try {
113
+ st = await fs.promises.lstat(path.join(dir, name));
114
+ } catch {
115
+ continue;
116
+ }
117
+ if (!st.isFile()) continue; // symlink/dir at an owned name → not ours to touch
118
+ owned.push({ name, size: st.size, mtimeMs: st.mtimeMs });
119
+ }
120
+
121
+ const removed = [];
122
+ const remove = async (entry) => {
123
+ try {
124
+ await fs.promises.rm(path.join(dir, entry.name), { force: true });
125
+ removed.push(entry.name);
126
+ return true;
127
+ } catch {
128
+ return false;
129
+ }
130
+ };
131
+
132
+ // Age pass: owned files past the age cap go first.
133
+ const survivors = [];
134
+ for (const entry of owned) {
135
+ if (now - entry.mtimeMs > maxAgeMs) {
136
+ await remove(entry);
137
+ } else {
138
+ survivors.push(entry);
139
+ }
140
+ }
141
+
142
+ // Cap pass: evict lowest-priority owned content until under maxBytes.
143
+ let total = survivors.reduce((sum, e) => sum + e.size, 0);
144
+ if (total > maxBytes) {
145
+ const order = survivors
146
+ .slice()
147
+ .sort((a, b) => evictionRank(a.name) - evictionRank(b.name) || a.mtimeMs - b.mtimeMs);
148
+ for (const entry of order) {
149
+ if (total <= maxBytes) break;
150
+ if (await remove(entry)) total -= entry.size;
151
+ }
152
+ }
153
+
154
+ return { ok: true, removed, bytes: Math.max(0, total) };
155
+ }
@@ -48,9 +48,8 @@ Your own model does not change mid-turn. A tier only applies when work is handed
48
48
  - Images: `@vision` only. `@default`/`@slow` cannot see images on the UNIC gateway and must never
49
49
  guess at their contents — hand every image to `ukit-vision-analyst` first.
50
50
 
51
- Doing everything inline is exactly what makes UKit look like it only has one model. Keep direct
52
- execution for trivial/simple work; delegate when the tier differs or when a noisy lane would other-
53
- wise flood this context.
51
+ Doing everything inline is what makes UKit look like it only has one model. Keep direct execution
52
+ for trivial/simple work; delegate when the tier differs or a noisy lane would flood this context.
54
53
 
55
54
  ## 7. Safe Patch
56
55
 
@@ -73,6 +72,7 @@ last thing you emit is one short line naming what is unfinished and what you nee
73
72
  a status — the user cannot distinguish it from a crash.
74
73
 
75
74
  > Maintainer note, not a per-turn rule: `modelRoles` in `.omp/config.yml` ships UNIC gateway names.
76
- > On a non-UNIC provider, edit only the three cost tiers — `smol`, `default`, `slow` — never `vision`,
77
- > which stays `unic-vision` (capability lane, not a cost tier). Old keys `lite`/`code`/`smart` remain
78
- > as compat aliases — edit each alias alongside its tier (smol↔lite, default↔code, slow↔smart).
75
+ > On a non-UNIC provider, edit only the cost tiers `smol`/`default`/`slow` — never `vision`
76
+ > (`unic-vision`) or `decision` (`unic-decision`, the LOCAL JEV/Lava typed-decision model —
77
+ > `docs/pstack/UNIC_DECISION_GUIDE.md`); both are capability lanes. Aliases `lite`/`code`/`smart`
78
+ > map smol↔lite, default↔code, slow↔smart — edit each alongside its tier.
@@ -13,6 +13,12 @@ modelRoles:
13
13
  default: unic-code
14
14
  slow: unic-smart
15
15
  vision: unic-vision
16
+ # `decision` is a capability lane like `vision`, not a cost tier: it binds to
17
+ # the LOCAL JEV/Lava typed-decision model `unic-decision` (bounded classify/
18
+ # select/rank/score calls — see docs/pstack/UNIC_DECISION_GUIDE.md). It is NOT
19
+ # a handoff tier, so tier maps (handoff-model-guard.sh tierOf) never see it.
20
+ # On a non-UNIC provider leave it unset — there is no cost-tier alias for it.
21
+ decision: unic-decision
16
22
  # Back-compat aliases — INDEPENDENT keys, not references. A maintainer overriding a
17
23
  # cost tier above must edit the matching alias too (smol↔lite, default↔code,
18
24
  # slow↔smart), or the alias keeps resolving to the old unic-* value.
@@ -45,9 +45,8 @@ Your own model does not change mid-turn. A tier only applies when work is handed
45
45
  - Images: `@vision` only. `@default`/`@slow` cannot see images on the UNIC gateway and must never
46
46
  guess at their contents — hand every image to `ukit-vision-analyst` first.
47
47
 
48
- Doing everything inline is exactly what makes UKit look like it only has one model. Keep direct
49
- execution for trivial/simple work; delegate when the tier differs or when a noisy lane would other-
50
- wise flood this context.
48
+ Doing everything inline is what makes UKit look like it only has one model. Keep direct execution
49
+ for trivial/simple work; delegate when the tier differs or a noisy lane would flood this context.
51
50
 
52
51
  ## 7. Safe Patch
53
52
 
@@ -70,6 +69,7 @@ last thing you emit is one short line naming what is unfinished and what you nee
70
69
  a status — the user cannot distinguish it from a crash.
71
70
 
72
71
  > Maintainer note, not a per-turn rule: `modelRoles` in `.omp/config.yml` ships UNIC gateway names.
73
- > On a non-UNIC provider, edit only the three cost tiers — `smol`, `default`, `slow` — never `vision`,
74
- > which stays `unic-vision` (capability lane, not a cost tier). Old keys `lite`/`code`/`smart` remain
75
- > as compat aliases — edit each alias alongside its tier (smol↔lite, default↔code, slow↔smart).
72
+ > On a non-UNIC provider, edit only the cost tiers `smol`/`default`/`slow` — never `vision`
73
+ > (`unic-vision`) or `decision` (`unic-decision`, the LOCAL JEV/Lava typed-decision model —
74
+ > `docs/pstack/UNIC_DECISION_GUIDE.md`); both are capability lanes. Aliases `lite`/`code`/`smart`
75
+ > map smol↔lite, default↔code, slow↔smart — edit each alongside its tier.