@ngockhoale/ukit 3.0.8 → 3.0.10
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 +18 -1
- package/manifests/documentation.yaml +11 -0
- package/package.json +1 -1
- package/scripts/audit/decision-coverage.mjs +29 -2
- package/scripts/bench/data-foundation.mjs +52 -3
- package/scripts/bench/decision-runtime-baseline.mjs +427 -0
- package/scripts/bench/decision-runtime-metrics.mjs +67 -0
- package/scripts/bench/decision-runtime-variant.mjs +626 -0
- package/scripts/bench/memory-ablation.mjs +495 -0
- package/scripts/bench/memory-baseline.mjs +596 -0
- package/scripts/bench/memory-bench.mjs +661 -0
- package/scripts/bench/memory-canary.mjs +321 -0
- package/scripts/bench/memory-corpus.mjs +354 -0
- package/scripts/bench/memory-gate.mjs +389 -0
- package/scripts/bench/memory-metrics.mjs +179 -0
- package/scripts/bench/parallel-agents.mjs +33 -11
- package/scripts/bench/recorder-overhead.mjs +204 -0
- package/scripts/bench/sqlite-spike.mjs +451 -0
- package/scripts/measure-decision-gateway.mjs +306 -0
- package/scripts/perf/audit-perf.mjs +35 -17
- package/src/bug/triageBug.js +4 -3
- package/src/cli/commands/memory.js +357 -63
- package/src/context/detectProjectContext.js +11 -1
- package/src/core/agentRuntime/adapters.js +254 -0
- package/src/core/agentRuntime/artifacts.js +192 -0
- package/src/core/agentRuntime/completionGate.js +176 -0
- package/src/core/agentRuntime/context.js +149 -0
- package/src/core/agentRuntime/contract.js +247 -0
- package/src/core/agentRuntime/diagnostics.js +244 -0
- package/src/core/agentRuntime/evaluation.js +163 -0
- package/src/core/agentRuntime/eventStore.js +404 -0
- package/src/core/agentRuntime/liveness.js +60 -0
- package/src/core/agentRuntime/planCompiler.js +322 -0
- package/src/core/agentRuntime/promotion.js +53 -0
- package/src/core/agentRuntime/qualityComparison.js +112 -0
- package/src/core/agentRuntime/recovery.js +266 -0
- package/src/core/agentRuntime/resourcePolicy.js +78 -0
- package/src/core/agentRuntime/runtimeSupport.js +237 -0
- package/src/core/agentRuntime/supervisor.js +565 -0
- package/src/core/agentRuntime/vmEngine.js +621 -0
- package/src/core/codeintel/analogy.js +3 -2
- package/src/core/experiments/dynamicWorkflow.js +17 -2
- package/src/core/fileOps.js +21 -3
- package/src/core/memory/deltaOverlays.js +75 -30
- package/src/core/memory/learningCandidates.js +93 -48
- package/src/core/memory/memoryFlags.js +83 -0
- package/src/core/memory/memoryFreshness.js +190 -0
- package/src/core/memory/memoryHit.js +144 -0
- package/src/core/memory/migrate.js +69 -189
- package/src/core/memory/migrateMapping.js +232 -0
- package/src/core/memory/mutateMemory.js +323 -0
- package/src/core/memory/policy.js +96 -0
- package/src/core/memory/projectIdentity.js +266 -0
- package/src/core/memory/recordIndex.js +178 -0
- package/src/core/memory/recordStore.js +133 -20
- package/src/core/memory/records.js +144 -6
- package/src/core/memory/retrieval.js +259 -125
- package/src/core/memory/store.js +16 -5
- package/src/core/memory/storeBackup.js +226 -0
- package/src/core/memory/storeV2.js +63 -26
- package/src/core/memory/storeV2Loader.js +30 -12
- package/src/core/memory/userMemory.js +38 -20
- package/src/core/memory/writeClassification.js +161 -0
- package/src/core/memory/writeGuard.js +129 -0
- package/src/core/observability/adapters/hookTelemetryAdapter.js +90 -0
- package/src/core/observability/analytics/cohorts.js +148 -0
- package/src/core/observability/analytics/storeDigest.js +163 -0
- package/src/core/observability/evaluation/experimentPlan.js +95 -0
- package/src/core/observability/evaluation/findings.js +99 -0
- package/src/core/observability/evaluation/optimizationKnowledge.js +10 -1
- package/src/core/observability/evaluation/perturbation.js +273 -0
- package/src/core/observability/evaluation/replay.js +7 -1
- package/src/core/observability/evaluation/scorecard.js +23 -3
- package/src/core/observability/rollout.js +11 -7
- package/src/core/observability/schema/compatibility.js +135 -0
- package/src/core/observability/schema/registry.js +99 -0
- package/src/core/observability/schema/validate.js +7 -0
- package/src/core/observability/support/import.js +53 -9
- package/src/core/observability/support/paths.js +13 -3
- package/src/core/observability/support/projector.js +148 -12
- package/src/core/output/index.js +12 -2
- package/src/core/runtimeConfig.js +83 -0
- package/src/core/runtimePaths.js +3 -0
- package/src/core/sensitiveValueScanner.js +40 -0
- package/src/core/token/index.js +40 -3
- package/src/decision/client.js +37 -13
- package/src/decision/protocol.js +1 -1
- package/src/decision/registry.js +5 -3
- package/src/decision/runtimeDecide.js +242 -0
- package/src/decision/runtimeFilter.js +150 -0
- package/src/decision/runtimeScheduler.js +239 -0
- package/src/index/buildIndex.js +13 -12
- package/src/index/queryIndex.js +35 -14
- package/src/index/relatedTests.js +50 -8
- package/src/index/resolveContext.js +9 -4
- package/src/manifest/selectItems.js +7 -3
- package/src/render/instructionRenderer.js +17 -5
- package/template_project/.claude/ukit/index/lib/index-core.mjs +94 -39
- package/template_project/.claude/ukit/index/route-task.mjs +121 -19
- package/template_project/.claude/ukit/index/unic-decision.mjs +28 -13
- package/template_project/.claude/ukit/runtime/memory-flags.mjs +51 -0
- package/template_project/.claude/ukit/runtime/memory-freshness.mjs +155 -0
- package/template_project/.claude/ukit/runtime/memory-policy.mjs +286 -0
- package/template_project/.claude/ukit/runtime/output-compression.mjs +3 -0
- package/template_project/.claude/ukit/runtime/reinject-context.mjs +145 -14
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
// Registry compatibility — SPEC W1 G1-FR02, §4.
|
|
2
|
+
//
|
|
3
|
+
// checkRegistryCompatibility(prev, next, options?) → { ok, violations }
|
|
4
|
+
// validateEnvelopeSchemaVersion(record) → { ok } | { ok, reason }
|
|
5
|
+
//
|
|
6
|
+
// Enforces the frozen registry lifecycle rules mechanically, not by
|
|
7
|
+
// convention: entries are append-only. New keys and new OPTIONAL metadata
|
|
8
|
+
// fields are additive and tolerated; mutating an existing entry's meaning
|
|
9
|
+
// (definition, unit, privacy floor) in place, removing a key, resurrecting
|
|
10
|
+
// a deprecated entry, or re-dating an entry's introduced_version is a
|
|
11
|
+
// breaking change that requires a new SCHEMA_VERSION major — never an
|
|
12
|
+
// in-place edit.
|
|
13
|
+
//
|
|
14
|
+
// Violation codes are a static closed set:
|
|
15
|
+
// meaning_changed — definition/unit/privacy_class mutated in place
|
|
16
|
+
// removed — a key present in prev is absent in next
|
|
17
|
+
// deprecated_resurrected — a deprecated entry lost its deprecation marker
|
|
18
|
+
// major_required — introduced_version was rewritten on an
|
|
19
|
+
// existing entry (re-dating history) or the
|
|
20
|
+
// registry's own version marker regressed
|
|
21
|
+
|
|
22
|
+
import { SCHEMA_VERSION } from './constants.js';
|
|
23
|
+
|
|
24
|
+
export const COMPATIBILITY_VIOLATION_CODES = Object.freeze([
|
|
25
|
+
'meaning_changed',
|
|
26
|
+
'removed',
|
|
27
|
+
'deprecated_resurrected',
|
|
28
|
+
'major_required',
|
|
29
|
+
]);
|
|
30
|
+
|
|
31
|
+
// Fields whose mutation changes what an entry MEANS. New optional fields
|
|
32
|
+
// not in this list (e.g. additional hints) are additive and tolerated.
|
|
33
|
+
const MEANING_FIELDS = ['definition', 'unit', 'privacy_class'];
|
|
34
|
+
|
|
35
|
+
function isPlainObject(value) {
|
|
36
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function isDeprecated(entry) {
|
|
40
|
+
return entry.deprecated !== undefined && entry.deprecated !== null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Compare two registry snapshots ({ key: entry }) under the additive-only
|
|
45
|
+
* lifecycle contract. `prev`/`next` may be SEMANTIC_REGISTRY, REASON_CODES,
|
|
46
|
+
* or any same-shaped registry map.
|
|
47
|
+
*
|
|
48
|
+
* @param {object} prev — earlier registry snapshot.
|
|
49
|
+
* @param {object} next — candidate registry snapshot.
|
|
50
|
+
* @param {{ schemaVersion?: number }} [options] — the schema major the next
|
|
51
|
+
* snapshot ships under. Breaking changes are only legitimate when this is
|
|
52
|
+
* a NEW major (> SCHEMA_VERSION); defaults to the current SCHEMA_VERSION,
|
|
53
|
+
* so callers must opt into a major bump explicitly.
|
|
54
|
+
* @returns {{ ok: boolean, violations: Array<{ code: string, key: string }> }}
|
|
55
|
+
*/
|
|
56
|
+
export function checkRegistryCompatibility(prev, next, options = {}) {
|
|
57
|
+
const violations = [];
|
|
58
|
+
const prevRegistry = isPlainObject(prev) ? prev : {};
|
|
59
|
+
const nextRegistry = isPlainObject(next) ? next : {};
|
|
60
|
+
const schemaVersion = options.schemaVersion === undefined ? SCHEMA_VERSION : options.schemaVersion;
|
|
61
|
+
|
|
62
|
+
// A declared major bump opens a new registry lineage — breaking changes
|
|
63
|
+
// are legitimate there and there is nothing to enforce on this contract.
|
|
64
|
+
if (Number.isInteger(schemaVersion) && schemaVersion > SCHEMA_VERSION) {
|
|
65
|
+
return { ok: true, violations: [] };
|
|
66
|
+
}
|
|
67
|
+
// A regressed major marker is always invalid.
|
|
68
|
+
if (schemaVersion !== SCHEMA_VERSION) {
|
|
69
|
+
return { ok: false, violations: [{ code: 'major_required', key: 'schema_version' }] };
|
|
70
|
+
}
|
|
71
|
+
// Same-major snapshot pair: enforce the additive-only lifecycle rules.
|
|
72
|
+
|
|
73
|
+
for (const key of Object.keys(prevRegistry)) {
|
|
74
|
+
const before = prevRegistry[key];
|
|
75
|
+
const after = nextRegistry[key];
|
|
76
|
+
|
|
77
|
+
if (after === undefined) {
|
|
78
|
+
violations.push({ code: 'removed', key });
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
if (!isPlainObject(before) || !isPlainObject(after)) {
|
|
82
|
+
violations.push({ code: 'meaning_changed', key });
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Re-dating an existing entry's introduced_version rewrites registry
|
|
87
|
+
// history — always a major boundary, never an in-place edit.
|
|
88
|
+
if (before.introduced_version !== after.introduced_version) {
|
|
89
|
+
violations.push({ code: 'major_required', key });
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (isDeprecated(before) && !isDeprecated(after)) {
|
|
93
|
+
violations.push({ code: 'deprecated_resurrected', key });
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
for (const field of MEANING_FIELDS) {
|
|
97
|
+
if (before[field] === undefined && after[field] === undefined) continue;
|
|
98
|
+
// A missing field on either side is not a meaning change — older
|
|
99
|
+
// snapshots simply did not carry the metadata.
|
|
100
|
+
if (before[field] === undefined || after[field] === undefined) continue;
|
|
101
|
+
if (before[field] !== after[field]) {
|
|
102
|
+
violations.push({ code: 'meaning_changed', key });
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return { ok: violations.length === 0, violations };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Gate a semantic envelope on its declared schema major. Tolerates any
|
|
113
|
+
* unknown optional fields — version compatibility is judged ONLY on
|
|
114
|
+
* `schema_version` (DF-FR03 additive-only tolerance lives upstream in
|
|
115
|
+
* validateSemanticRecord).
|
|
116
|
+
*
|
|
117
|
+
* @param {object} record — candidate semantic envelope.
|
|
118
|
+
* @returns {{ ok: true } | { ok: false, reason: string }}
|
|
119
|
+
*/
|
|
120
|
+
export function validateEnvelopeSchemaVersion(record) {
|
|
121
|
+
if (!isPlainObject(record)) {
|
|
122
|
+
return { ok: false, reason: 'record must be a plain object' };
|
|
123
|
+
}
|
|
124
|
+
const version = record.schema_version;
|
|
125
|
+
if (!Number.isInteger(version)) {
|
|
126
|
+
return { ok: false, reason: `schema_version must be an integer (got ${JSON.stringify(version)})` };
|
|
127
|
+
}
|
|
128
|
+
if (version !== SCHEMA_VERSION) {
|
|
129
|
+
return {
|
|
130
|
+
ok: false,
|
|
131
|
+
reason: `unsupported schema_version ${version} — this major requires schema ${version} support; supported: ${SCHEMA_VERSION}`,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
return { ok: true };
|
|
135
|
+
}
|
|
@@ -18,111 +18,147 @@ const V1 = '1';
|
|
|
18
18
|
export const SEMANTIC_REGISTRY = Object.freeze({
|
|
19
19
|
'execution.started': {
|
|
20
20
|
definition: 'An execution unit (task/episode) began. Span root for the execution.',
|
|
21
|
+
unit: 'event',
|
|
21
22
|
privacy_class: 'internal',
|
|
22
23
|
retention_hint: 'retain',
|
|
23
24
|
introduced_version: V1,
|
|
25
|
+
deprecated: null,
|
|
24
26
|
},
|
|
25
27
|
'execution.completed': {
|
|
26
28
|
definition: 'An execution unit finished successfully. Payload carries outcome and duration.',
|
|
29
|
+
unit: 'ms',
|
|
27
30
|
privacy_class: 'internal',
|
|
28
31
|
retention_hint: 'retain',
|
|
29
32
|
introduced_version: V1,
|
|
33
|
+
deprecated: null,
|
|
30
34
|
},
|
|
31
35
|
'execution.failed': {
|
|
32
36
|
definition: 'An execution unit terminated with failure. Payload carries error_code.',
|
|
37
|
+
unit: 'ms',
|
|
33
38
|
privacy_class: 'internal',
|
|
34
39
|
retention_hint: 'retain',
|
|
35
40
|
introduced_version: V1,
|
|
41
|
+
deprecated: null,
|
|
36
42
|
},
|
|
37
43
|
'execution.blocked': {
|
|
38
44
|
definition: 'An execution unit stopped on an unmet precondition or gate.',
|
|
45
|
+
unit: 'ms',
|
|
39
46
|
privacy_class: 'internal',
|
|
40
47
|
retention_hint: 'retain',
|
|
41
48
|
introduced_version: V1,
|
|
49
|
+
deprecated: null,
|
|
42
50
|
},
|
|
43
51
|
'tool.started': {
|
|
44
52
|
definition: 'A tool invocation began. Payload may name the tool; arguments stay redacted.',
|
|
53
|
+
unit: 'event',
|
|
45
54
|
privacy_class: 'sensitive',
|
|
46
55
|
retention_hint: 'retain',
|
|
47
56
|
introduced_version: V1,
|
|
57
|
+
deprecated: null,
|
|
48
58
|
},
|
|
49
59
|
'tool.completed': {
|
|
50
60
|
definition: 'A tool invocation finished. Payload carries duration; output stays redacted.',
|
|
61
|
+
unit: 'ms',
|
|
51
62
|
privacy_class: 'sensitive',
|
|
52
63
|
retention_hint: 'retain',
|
|
53
64
|
introduced_version: V1,
|
|
65
|
+
deprecated: null,
|
|
54
66
|
},
|
|
55
67
|
'tool.failed': {
|
|
56
68
|
definition: 'A tool invocation failed. Payload carries error_code; stderr stays redacted.',
|
|
69
|
+
unit: 'ms',
|
|
57
70
|
privacy_class: 'sensitive',
|
|
58
71
|
retention_hint: 'retain',
|
|
59
72
|
introduced_version: V1,
|
|
73
|
+
deprecated: null,
|
|
60
74
|
},
|
|
61
75
|
'model.started': {
|
|
62
76
|
definition: 'A model attempt began. Payload carries attempt_index.',
|
|
77
|
+
unit: 'event',
|
|
63
78
|
privacy_class: 'internal',
|
|
64
79
|
retention_hint: 'retain',
|
|
65
80
|
introduced_version: V1,
|
|
81
|
+
deprecated: null,
|
|
66
82
|
},
|
|
67
83
|
'model.completed': {
|
|
68
84
|
definition: 'A model attempt finished. Payload carries duration and typed resource usage.',
|
|
85
|
+
unit: 'ms',
|
|
69
86
|
privacy_class: 'internal',
|
|
70
87
|
retention_hint: 'retain',
|
|
71
88
|
introduced_version: V1,
|
|
89
|
+
deprecated: null,
|
|
72
90
|
},
|
|
73
91
|
'model.failed': {
|
|
74
92
|
definition: 'A model attempt failed. Payload carries error_code and typed resource usage.',
|
|
93
|
+
unit: 'ms',
|
|
75
94
|
privacy_class: 'internal',
|
|
76
95
|
retention_hint: 'retain',
|
|
77
96
|
introduced_version: V1,
|
|
97
|
+
deprecated: null,
|
|
78
98
|
},
|
|
79
99
|
'cache.hit': {
|
|
80
100
|
definition: 'A cache lookup resolved from cache. Payload identifies the cache seam, not content.',
|
|
101
|
+
unit: 'event',
|
|
81
102
|
privacy_class: 'internal',
|
|
82
103
|
retention_hint: 'aggregate',
|
|
83
104
|
introduced_version: V1,
|
|
105
|
+
deprecated: null,
|
|
84
106
|
},
|
|
85
107
|
'cache.miss': {
|
|
86
108
|
definition: 'A cache lookup did not resolve from cache.',
|
|
109
|
+
unit: 'event',
|
|
87
110
|
privacy_class: 'internal',
|
|
88
111
|
retention_hint: 'aggregate',
|
|
89
112
|
introduced_version: V1,
|
|
113
|
+
deprecated: null,
|
|
90
114
|
},
|
|
91
115
|
'verification.completed': {
|
|
92
116
|
definition: 'A verification step ran to completion. Payload carries the verdict, not raw output.',
|
|
117
|
+
unit: 'event',
|
|
93
118
|
privacy_class: 'internal',
|
|
94
119
|
retention_hint: 'retain',
|
|
95
120
|
introduced_version: V1,
|
|
121
|
+
deprecated: null,
|
|
96
122
|
},
|
|
97
123
|
'context.item.injected': {
|
|
98
124
|
definition: 'A context item (e.g. user correction) entered the episode. Kind only, never content.',
|
|
125
|
+
unit: 'event',
|
|
99
126
|
privacy_class: 'sensitive',
|
|
100
127
|
retention_hint: 'retain',
|
|
101
128
|
introduced_version: V1,
|
|
129
|
+
deprecated: null,
|
|
102
130
|
},
|
|
103
131
|
'decision.made': {
|
|
104
132
|
definition: 'An optimization or routing decision was recorded with experiment provenance.',
|
|
133
|
+
unit: 'event',
|
|
105
134
|
privacy_class: 'internal',
|
|
106
135
|
retention_hint: 'retain',
|
|
107
136
|
introduced_version: V1,
|
|
137
|
+
deprecated: null,
|
|
108
138
|
},
|
|
109
139
|
'telemetry.dropped': {
|
|
110
140
|
definition: 'Telemetry records were dropped or sampled. Payload carries reason_code and count.',
|
|
141
|
+
unit: 'records',
|
|
111
142
|
privacy_class: 'internal',
|
|
112
143
|
retention_hint: 'retain',
|
|
113
144
|
introduced_version: V1,
|
|
145
|
+
deprecated: null,
|
|
114
146
|
},
|
|
115
147
|
'telemetry.recovered': {
|
|
116
148
|
definition: 'Previously incomplete telemetry recovered (e.g. partial tail repaired).',
|
|
149
|
+
unit: 'records',
|
|
117
150
|
privacy_class: 'internal',
|
|
118
151
|
retention_hint: 'retain',
|
|
119
152
|
introduced_version: V1,
|
|
153
|
+
deprecated: null,
|
|
120
154
|
},
|
|
121
155
|
'telemetry.redacted': {
|
|
122
156
|
definition: 'A record or field was redacted before persistence. Payload carries the field path.',
|
|
157
|
+
unit: 'fields',
|
|
123
158
|
privacy_class: 'internal',
|
|
124
159
|
retention_hint: 'retain',
|
|
125
160
|
introduced_version: V1,
|
|
161
|
+
deprecated: null,
|
|
126
162
|
},
|
|
127
163
|
});
|
|
128
164
|
|
|
@@ -131,87 +167,150 @@ export const SEMANTIC_REGISTRY = Object.freeze({
|
|
|
131
167
|
export const REASON_CODES = Object.freeze({
|
|
132
168
|
TOOL_TIMEOUT: {
|
|
133
169
|
definition: 'A tool or model call exceeded its deadline.',
|
|
170
|
+
unit: null,
|
|
134
171
|
introduced_version: V1,
|
|
172
|
+
deprecated: null,
|
|
173
|
+
privacy_class: 'internal',
|
|
135
174
|
},
|
|
136
175
|
AUTH_FAILED: {
|
|
137
176
|
definition: 'Authentication or authorization was rejected by the provider.',
|
|
177
|
+
unit: null,
|
|
138
178
|
introduced_version: V1,
|
|
179
|
+
deprecated: null,
|
|
180
|
+
privacy_class: 'internal',
|
|
139
181
|
},
|
|
140
182
|
RATE_LIMITED: {
|
|
141
183
|
definition: 'The provider or host throttled the request.',
|
|
184
|
+
unit: null,
|
|
142
185
|
introduced_version: V1,
|
|
186
|
+
deprecated: null,
|
|
187
|
+
privacy_class: 'internal',
|
|
143
188
|
},
|
|
144
189
|
MODEL_ERROR: {
|
|
145
190
|
definition: 'The model endpoint returned an error response.',
|
|
191
|
+
unit: null,
|
|
146
192
|
introduced_version: V1,
|
|
193
|
+
deprecated: null,
|
|
194
|
+
privacy_class: 'internal',
|
|
147
195
|
},
|
|
148
196
|
NETWORK_ERROR: {
|
|
149
197
|
definition: 'A transport-level failure prevented the call.',
|
|
198
|
+
unit: null,
|
|
150
199
|
introduced_version: V1,
|
|
200
|
+
deprecated: null,
|
|
201
|
+
privacy_class: 'internal',
|
|
151
202
|
},
|
|
152
203
|
QUEUE_OVERFLOW: {
|
|
153
204
|
definition: 'The emit queue exceeded its bound; records were dropped.',
|
|
205
|
+
unit: null,
|
|
154
206
|
introduced_version: V1,
|
|
207
|
+
deprecated: null,
|
|
208
|
+
privacy_class: 'internal',
|
|
155
209
|
},
|
|
156
210
|
SAMPLED_OUT: {
|
|
157
211
|
definition: 'The record was excluded by the configured sampling policy.',
|
|
212
|
+
unit: null,
|
|
158
213
|
introduced_version: V1,
|
|
214
|
+
deprecated: null,
|
|
215
|
+
privacy_class: 'internal',
|
|
159
216
|
},
|
|
160
217
|
HOST_BLIND: {
|
|
161
218
|
definition: 'The host does not expose the field; value stays explicitly unknown.',
|
|
219
|
+
unit: null,
|
|
162
220
|
introduced_version: V1,
|
|
221
|
+
deprecated: null,
|
|
222
|
+
privacy_class: 'internal',
|
|
163
223
|
},
|
|
164
224
|
FLUSH_FAILED: {
|
|
165
225
|
definition: 'A recorder flush could not persist its batch; user task unaffected.',
|
|
226
|
+
unit: null,
|
|
166
227
|
introduced_version: V1,
|
|
228
|
+
deprecated: null,
|
|
229
|
+
privacy_class: 'internal',
|
|
167
230
|
},
|
|
168
231
|
DISK_FULL: {
|
|
169
232
|
definition: 'The storage volume rejected a write for lack of space.',
|
|
233
|
+
unit: null,
|
|
170
234
|
introduced_version: V1,
|
|
235
|
+
deprecated: null,
|
|
236
|
+
privacy_class: 'internal',
|
|
171
237
|
},
|
|
172
238
|
PERMISSION_DENIED: {
|
|
173
239
|
definition: 'The filesystem or OS denied the operation.',
|
|
240
|
+
unit: null,
|
|
174
241
|
introduced_version: V1,
|
|
242
|
+
deprecated: null,
|
|
243
|
+
privacy_class: 'internal',
|
|
175
244
|
},
|
|
176
245
|
SCHEMA_MISMATCH: {
|
|
177
246
|
definition: 'A record or bundle failed schema validation.',
|
|
247
|
+
unit: null,
|
|
178
248
|
introduced_version: V1,
|
|
249
|
+
deprecated: null,
|
|
250
|
+
privacy_class: 'internal',
|
|
179
251
|
},
|
|
180
252
|
CHECKSUM_MISMATCH: {
|
|
181
253
|
definition: 'A segment or bundle digest did not match its manifest.',
|
|
254
|
+
unit: null,
|
|
182
255
|
introduced_version: V1,
|
|
256
|
+
deprecated: null,
|
|
257
|
+
privacy_class: 'internal',
|
|
183
258
|
},
|
|
184
259
|
PARTIAL_TAIL: {
|
|
185
260
|
definition: 'A segment ended mid-record; the tail was truncated on recovery.',
|
|
261
|
+
unit: null,
|
|
186
262
|
introduced_version: V1,
|
|
263
|
+
deprecated: null,
|
|
264
|
+
privacy_class: 'internal',
|
|
187
265
|
},
|
|
188
266
|
ROTATED: {
|
|
189
267
|
definition: 'A segment was rotated out by retention or cap policy.',
|
|
268
|
+
unit: null,
|
|
190
269
|
introduced_version: V1,
|
|
270
|
+
deprecated: null,
|
|
271
|
+
privacy_class: 'internal',
|
|
191
272
|
},
|
|
192
273
|
REDACTED_FIELD: {
|
|
193
274
|
definition: 'A payload field was removed by the privacy gate before persistence.',
|
|
275
|
+
unit: null,
|
|
194
276
|
introduced_version: V1,
|
|
277
|
+
deprecated: null,
|
|
278
|
+
privacy_class: 'internal',
|
|
195
279
|
},
|
|
196
280
|
USER_CANCELLED: {
|
|
197
281
|
definition: 'The user aborted the operation.',
|
|
282
|
+
unit: null,
|
|
198
283
|
introduced_version: V1,
|
|
284
|
+
deprecated: null,
|
|
285
|
+
privacy_class: 'internal',
|
|
199
286
|
},
|
|
200
287
|
GATE_BLOCKED: {
|
|
201
288
|
definition: 'A completion or security gate stopped the execution.',
|
|
289
|
+
unit: null,
|
|
202
290
|
introduced_version: V1,
|
|
291
|
+
deprecated: null,
|
|
292
|
+
privacy_class: 'internal',
|
|
203
293
|
},
|
|
204
294
|
WAITING_ON_TOOL: {
|
|
205
295
|
definition: 'The span is waiting on a tool result.',
|
|
296
|
+
unit: null,
|
|
206
297
|
introduced_version: V1,
|
|
298
|
+
deprecated: null,
|
|
299
|
+
privacy_class: 'internal',
|
|
207
300
|
},
|
|
208
301
|
WAITING_ON_MODEL: {
|
|
209
302
|
definition: 'The span is waiting on a model response.',
|
|
303
|
+
unit: null,
|
|
210
304
|
introduced_version: V1,
|
|
305
|
+
deprecated: null,
|
|
306
|
+
privacy_class: 'internal',
|
|
211
307
|
},
|
|
212
308
|
WAITING_ON_USER: {
|
|
213
309
|
definition: 'The span is waiting on user input.',
|
|
310
|
+
unit: null,
|
|
214
311
|
introduced_version: V1,
|
|
312
|
+
deprecated: null,
|
|
313
|
+
privacy_class: 'internal',
|
|
215
314
|
},
|
|
216
315
|
});
|
|
217
316
|
|
|
@@ -112,6 +112,13 @@ function validatePayload(payload, errors) {
|
|
|
112
112
|
if (payload.telemetry_complete !== undefined && typeof payload.telemetry_complete !== 'boolean') {
|
|
113
113
|
errors.push('payload.telemetry_complete must be a boolean');
|
|
114
114
|
}
|
|
115
|
+
// Duration is always milliseconds — a unit-suffixed string or non-finite
|
|
116
|
+
// number is malformed, never a unit conversion request.
|
|
117
|
+
if (payload.duration_ms !== undefined && payload.duration_ms !== null) {
|
|
118
|
+
if (typeof payload.duration_ms !== 'number' || !Number.isFinite(payload.duration_ms) || payload.duration_ms < 0) {
|
|
119
|
+
errors.push('payload.duration_ms must be a non-negative finite number (unit is always milliseconds)');
|
|
120
|
+
}
|
|
121
|
+
}
|
|
115
122
|
}
|
|
116
123
|
|
|
117
124
|
export function validateSemanticRecord(record, context) {
|
|
@@ -11,12 +11,16 @@
|
|
|
11
11
|
*
|
|
12
12
|
* Validation order (fail-fast, first reason wins):
|
|
13
13
|
* input shape → container parse (zip EOCD/CD/local headers, or dir
|
|
14
|
-
* listing) →
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
14
|
+
* listing) → one-folder unwrap (zip only: if every member shares
|
|
15
|
+
* exactly one `Prefix/…` root — an OS "Compress folder" bundle — the
|
|
16
|
+
* prefix is stripped; prefix-only dir entries are ignored; mixed-root
|
|
17
|
+
* archives keep raw names and fail below) → symlink check (unix mode
|
|
18
|
+
* in CD external attrs, `symlink_entry`) → per-entry name safety
|
|
19
|
+
* (flat namespace, no traversal, no absolute/drive/backslash) →
|
|
20
|
+
* compression method allowlist → entry/total size caps → zip-bomb
|
|
21
|
+
* ratio → CRC32 + declared-size integrity → manifest presence →
|
|
22
|
+
* manifest format/checksum/member verification → file-type allowlist
|
|
23
|
+
* (.md/.json/.jsonl) → records.jsonl line parsing.
|
|
20
24
|
*
|
|
21
25
|
* Defense invariants:
|
|
22
26
|
* - Nothing is ever executed, sourced, or written outside the input
|
|
@@ -134,15 +138,55 @@ function unzip(buf) {
|
|
|
134
138
|
const nameLen = buf.readUInt16LE(p + 28);
|
|
135
139
|
const extraLen = buf.readUInt16LE(p + 30);
|
|
136
140
|
const commentLen = buf.readUInt16LE(p + 32);
|
|
141
|
+
// upper 16 bits of external attrs carry the unix mode (zip spec §4.4.15)
|
|
142
|
+
const externalAttrs = buf.readUInt32LE(p + 38);
|
|
137
143
|
const localOffset = buf.readUInt32LE(p + 42);
|
|
138
144
|
const name = buf.subarray(p + 46, p + 46 + nameLen).toString('utf8');
|
|
139
|
-
central.push({ name, method, crc, compressedSize, uncompressedSize, localOffset });
|
|
145
|
+
central.push({ name, method, crc, compressedSize, uncompressedSize, externalAttrs, localOffset });
|
|
140
146
|
p += 46 + nameLen + extraLen + commentLen;
|
|
141
147
|
}
|
|
142
148
|
|
|
149
|
+
// One-folder unwrap (G4-FR09): a real OS "Compress folder → .zip" prefixes
|
|
150
|
+
// every member with `FolderName/`. When every member shares exactly one
|
|
151
|
+
// top-level directory prefix, strip it so the flat-name safety rules and
|
|
152
|
+
// the manifest map apply to the unwrapped names. Prefix-only dir entries
|
|
153
|
+
// (`FolderName/`) carry no payload and are ignored. Mixed-root archives
|
|
154
|
+
// (no shared single prefix) keep their raw names and fail the flat-name
|
|
155
|
+
// checks below — we never guess which subtree is the bundle.
|
|
156
|
+
const prefixes = new Set();
|
|
157
|
+
let sharedPrefix = true;
|
|
158
|
+
for (const e of central) {
|
|
159
|
+
const i = e.name.indexOf('/');
|
|
160
|
+
if (i <= 0) {
|
|
161
|
+
sharedPrefix = false; // root-level or leading-slash member → no unwrap
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
prefixes.add(e.name.slice(0, i + 1));
|
|
165
|
+
}
|
|
166
|
+
const members = [];
|
|
167
|
+
if (sharedPrefix && prefixes.size === 1) {
|
|
168
|
+
const prefix = [...prefixes][0];
|
|
169
|
+
for (const e of central) {
|
|
170
|
+
if (e.name !== prefix) {
|
|
171
|
+
e.name = e.name.slice(prefix.length);
|
|
172
|
+
members.push(e);
|
|
173
|
+
}
|
|
174
|
+
// e.name === prefix → prefix-only dir entry, ignored
|
|
175
|
+
}
|
|
176
|
+
} else {
|
|
177
|
+
members.push(...central);
|
|
178
|
+
}
|
|
179
|
+
|
|
143
180
|
// Metadata-level bounds before touching payloads.
|
|
181
|
+
const UNIX_S_IFMT = 0o170000;
|
|
182
|
+
const UNIX_S_IFLNK = 0o120000;
|
|
144
183
|
let totalUncompressed = 0;
|
|
145
|
-
for (const e of
|
|
184
|
+
for (const e of members) {
|
|
185
|
+
// symlink members are rejected on central-directory metadata alone —
|
|
186
|
+
// the payload is never touched (G4-FR10).
|
|
187
|
+
if (((e.externalAttrs >>> 16) & UNIX_S_IFMT) === UNIX_S_IFLNK) {
|
|
188
|
+
return fail('symlink_entry', { name: e.name });
|
|
189
|
+
}
|
|
146
190
|
if (!isSafeEntryName(e.name)) {
|
|
147
191
|
return fail('unsafe_entry_name', { name: e.name });
|
|
148
192
|
}
|
|
@@ -170,7 +214,7 @@ function unzip(buf) {
|
|
|
170
214
|
}
|
|
171
215
|
|
|
172
216
|
// Extraction: local header → data slice → inflate → CRC + size check.
|
|
173
|
-
for (const e of
|
|
217
|
+
for (const e of members) {
|
|
174
218
|
const off = e.localOffset;
|
|
175
219
|
if (off + 30 > buf.length || buf.readUInt32LE(off) !== LOCAL_SIG) {
|
|
176
220
|
return fail('corrupt_local_header', { name: e.name });
|
|
@@ -30,12 +30,22 @@ import { ioReason } from '../segments/internal.js';
|
|
|
30
30
|
|
|
31
31
|
export const SUPPORT_DIR_NAME = 'UKit Support';
|
|
32
32
|
|
|
33
|
+
/**
|
|
34
|
+
* Path operations follow the INJECTED platform, never the host: an
|
|
35
|
+
* observability record replayed on a different OS must still produce the
|
|
36
|
+
* target platform's separators and join semantics.
|
|
37
|
+
*/
|
|
38
|
+
function pathFor(platform) {
|
|
39
|
+
return platform === 'win32' ? path.win32 : path.posix;
|
|
40
|
+
}
|
|
41
|
+
|
|
33
42
|
function candidates({ homeDir, env, platform }) {
|
|
34
43
|
const list = [];
|
|
44
|
+
const p = pathFor(platform);
|
|
35
45
|
if (platform === 'win32') {
|
|
36
46
|
const profile = env.USERPROFILE || homeDir;
|
|
37
47
|
if (typeof profile === 'string' && profile.length > 0) {
|
|
38
|
-
list.push(
|
|
48
|
+
list.push(p.join(profile, 'Documents'));
|
|
39
49
|
}
|
|
40
50
|
return list;
|
|
41
51
|
}
|
|
@@ -44,7 +54,7 @@ function candidates({ homeDir, env, platform }) {
|
|
|
44
54
|
if (typeof xdg === 'string' && xdg.length > 0) list.push(xdg);
|
|
45
55
|
}
|
|
46
56
|
if (typeof homeDir === 'string' && homeDir.length > 0) {
|
|
47
|
-
list.push(
|
|
57
|
+
list.push(p.join(homeDir, 'Documents'));
|
|
48
58
|
}
|
|
49
59
|
return list;
|
|
50
60
|
}
|
|
@@ -88,7 +98,7 @@ export async function resolveSupportDir(opts = {}) {
|
|
|
88
98
|
reason = err && err.code === 'EACCES' ? 'documents_not_writable' : ioReason(err);
|
|
89
99
|
continue;
|
|
90
100
|
}
|
|
91
|
-
return { ok: true, dir:
|
|
101
|
+
return { ok: true, dir: pathFor(platform).join(documents, SUPPORT_DIR_NAME) };
|
|
92
102
|
}
|
|
93
103
|
return { ok: false, reason };
|
|
94
104
|
}
|