@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.
- package/CHANGELOG.md +12 -0
- package/package.json +1 -1
- package/scripts/bench/data-foundation.mjs +562 -0
- package/src/core/observability/adapters/common.js +75 -0
- package/src/core/observability/adapters/contextAdapter.js +55 -0
- package/src/core/observability/adapters/decisionAdapter.js +61 -0
- package/src/core/observability/adapters/routeAdapter.js +135 -0
- package/src/core/observability/analytics/digest.js +186 -0
- package/src/core/observability/analytics/fingerprints.js +126 -0
- package/src/core/observability/analytics/opportunities.js +329 -0
- package/src/core/observability/analytics/rebuild.js +56 -0
- package/src/core/observability/analytics/summary.js +298 -0
- package/src/core/observability/emit/config.js +29 -0
- package/src/core/observability/emit/recorder.js +297 -0
- package/src/core/observability/evaluation/aiPacket.js +230 -0
- package/src/core/observability/evaluation/optimizationKnowledge.js +172 -0
- package/src/core/observability/evaluation/replay.js +143 -0
- package/src/core/observability/evaluation/scorecard.js +445 -0
- package/src/core/observability/privacy/allowlist.js +185 -0
- package/src/core/observability/privacy/redaction.js +113 -0
- package/src/core/observability/privacy/sanitizeForSupport.js +133 -0
- package/src/core/observability/privacy/sanitizeObserved.js +134 -0
- package/src/core/observability/rollout.js +155 -0
- package/src/core/observability/schema/constants.js +66 -0
- package/src/core/observability/schema/registry.js +223 -0
- package/src/core/observability/schema/validate.js +227 -0
- package/src/core/observability/segments/internal.js +241 -0
- package/src/core/observability/segments/readSegments.js +215 -0
- package/src/core/observability/segments/recovery.js +123 -0
- package/src/core/observability/segments/retention.js +381 -0
- package/src/core/observability/support/import.js +402 -0
- package/src/core/observability/support/manifest.js +135 -0
- package/src/core/observability/support/paths.js +94 -0
- package/src/core/observability/support/projector.js +483 -0
- package/src/core/observability/support/renderer.js +130 -0
- package/src/core/observability/support/retention.js +155 -0
- package/template_project/.omp/RULES.md +6 -6
- package/template_project/.omp/config.yml +6 -0
- 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
|
|
52
|
-
|
|
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
|
|
77
|
-
>
|
|
78
|
-
>
|
|
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
|
|
49
|
-
|
|
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
|
|
74
|
-
>
|
|
75
|
-
>
|
|
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.
|