pi-mega-compact 0.18.1 → 0.19.1
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/assets/vector-cortex/encoder-v1/manifest.json +1 -0
- package/assets/vector-cortex/encoder-v1/model-card.json +1 -0
- package/assets/vector-cortex/encoder-v1/model.onnx +0 -0
- package/assets/vector-cortex/encoder-v1/tokenizer.json +1 -0
- package/dist/config/vector-cortex.js +19 -0
- package/dist/config.js +1 -1
- package/dist/extensions/dashboard-server/routes-rag-settings-helpers.js +2 -0
- package/dist/extensions/dashboard-server/routes-vector-cortex-eval.js +49 -0
- package/dist/extensions/dashboard-server/routes-vector-cortex-health.js +233 -0
- package/dist/extensions/dashboard-server/routes-vector-cortex-ledger.js +82 -0
- package/dist/extensions/dashboard-server/routes-vector-cortex-shared.js +46 -0
- package/dist/extensions/dashboard-server/routes-vector-cortex-topology.js +68 -0
- package/dist/extensions/dashboard-server/routes-vector-cortex.js +12 -285
- package/dist/extensions/dashboard-server/routes.js +1 -1
- package/dist/extensions/dashboard-server/server.js +3 -1
- package/dist/src/config/vector-cortex.js +19 -0
- package/dist/src/config.js +1 -1
- package/dist/src/vector-cortex/cortex/sqlite.js +393 -0
- package/dist/src/vector-cortex/cortex/store.js +139 -0
- package/dist/src/vector-cortex/cortex/types.js +44 -0
- package/dist/src/vector-cortex/encoder/calibrate.js +174 -0
- package/dist/src/vector-cortex/encoder/emit-vc2c.js +59 -0
- package/dist/src/vector-cortex/encoder/fallback.js +62 -0
- package/dist/src/vector-cortex/encoder/select.js +172 -0
- package/dist/src/vector-cortex/encoder/types.js +31 -0
- package/dist/vector-cortex/cortex/sqlite.js +393 -0
- package/dist/vector-cortex/cortex/store.js +139 -0
- package/dist/vector-cortex/cortex/types.js +44 -0
- package/dist/vector-cortex/encoder/calibrate.js +174 -0
- package/dist/vector-cortex/encoder/emit-vc2c.js +59 -0
- package/dist/vector-cortex/encoder/fallback.js +62 -0
- package/dist/vector-cortex/encoder/select.js +172 -0
- package/dist/vector-cortex/encoder/types.js +31 -0
- package/extensions/dashboard-client/dist/assets/{AreaChart-BQXFF7UI.js → AreaChart-B9KYKrUE.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{AreaChart-BQXFF7UI.js.map → AreaChart-B9KYKrUE.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{BarChart-BVtoz9Eg.js → BarChart-D1sWkR6j.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{BarChart-BVtoz9Eg.js.map → BarChart-D1sWkR6j.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{CacheTab-trI2TIGy.js → CacheTab-BR8MYcJq.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{CacheTab-trI2TIGy.js.map → CacheTab-BR8MYcJq.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{EventsTab-CNUbk9__.js → EventsTab-BRNpDMvP.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{EventsTab-CNUbk9__.js.map → EventsTab-BRNpDMvP.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{HealthTab-K-bd_CM-.js → HealthTab-rr5nXpjq.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{HealthTab-K-bd_CM-.js.map → HealthTab-rr5nXpjq.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{MaintenanceTab-CoEioKzx.js → MaintenanceTab-CtjPWnzN.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MaintenanceTab-CoEioKzx.js.map → MaintenanceTab-CtjPWnzN.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{MemoryMapTab-CJo24ulv.js → MemoryMapTab-5SnFDIYc.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MemoryMapTab-CJo24ulv.js.map → MemoryMapTab-5SnFDIYc.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{MetricsTab-DCNlQWWP.js → MetricsTab-4MZfwRhq.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MetricsTab-DCNlQWWP.js.map → MetricsTab-4MZfwRhq.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{OverviewTab-CQQlyIQ6.js → OverviewTab-BAKENnbk.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{OverviewTab-CQQlyIQ6.js.map → OverviewTab-BAKENnbk.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{ReposTab-Cqm82_yn.js → ReposTab-7-z_OmZo.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{ReposTab-Cqm82_yn.js.map → ReposTab-7-z_OmZo.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{SessionsTab-A8xTAdET.js → SessionsTab-D_r2ki0y.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{SessionsTab-A8xTAdET.js.map → SessionsTab-D_r2ki0y.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{SetupTab-CqaSgaZl.js → SetupTab-DPujtYgv.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{SetupTab-CqaSgaZl.js.map → SetupTab-DPujtYgv.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{TimeSavedCard-C_f3q8GR.js → TimeSavedCard-CLHy30t6.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{TimeSavedCard-C_f3q8GR.js.map → TimeSavedCard-CLHy30t6.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{TurnsTab-BEsafACo.js → TurnsTab-Coul6CHc.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{TurnsTab-BEsafACo.js.map → TurnsTab-Coul6CHc.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/VectorCortexTab-rqBpWc7A.js +2 -0
- package/extensions/dashboard-client/dist/assets/VectorCortexTab-rqBpWc7A.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/{WikiTab-Bf01M2_Z.js → WikiTab-hryG5oyY.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{WikiTab-Bf01M2_Z.js.map → WikiTab-hryG5oyY.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{button-BEJPTk_E.js → button-Dxo95lRI.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{button-BEJPTk_E.js.map → button-Dxo95lRI.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{card-DK1V382s.js → card-FXwhLF_u.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{card-DK1V382s.js.map → card-FXwhLF_u.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-CRtaPphL.js → generateCategoricalChart-M-GeRM8U.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-CRtaPphL.js.map → generateCategoricalChart-M-GeRM8U.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{index-CB6BslE4.js → index-BIZZXvoy.js} +3 -3
- package/extensions/dashboard-client/dist/assets/{index-CB6BslE4.js.map → index-BIZZXvoy.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/index-CYpzq9Lm.css +1 -0
- package/extensions/dashboard-client/dist/assets/{switch-C4FV6B9k.js → switch-C-d4tkN1.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{switch-C4FV6B9k.js.map → switch-C-d4tkN1.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{toggle-GBmeENMd.js → toggle-Bfmucdcl.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{toggle-GBmeENMd.js.map → toggle-Bfmucdcl.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{useSSE-BOKZGSn1.js → useSSE-CxK_wdmk.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{useSSE-BOKZGSn1.js.map → useSSE-CxK_wdmk.js.map} +1 -1
- package/extensions/dashboard-client/dist/index.html +2 -2
- package/extensions/dashboard-client/src/api/vector-cortex.ts +9 -0
- package/extensions/dashboard-client/src/tabs/VectorCortexTab.tsx +36 -0
- package/extensions/dashboard-client/src/types/vector-cortex.ts +26 -0
- package/extensions/dashboard-server/api-contracts/vector-cortex.ts +34 -0
- package/extensions/dashboard-server/routes-rag-settings-helpers.ts +12 -0
- package/extensions/dashboard-server/routes-vector-cortex-eval.ts +59 -0
- package/extensions/dashboard-server/routes-vector-cortex-health.ts +273 -0
- package/extensions/dashboard-server/routes-vector-cortex-ledger.ts +92 -0
- package/extensions/dashboard-server/routes-vector-cortex-shared.ts +53 -0
- package/extensions/dashboard-server/routes-vector-cortex-topology.ts +83 -0
- package/extensions/dashboard-server/routes-vector-cortex.ts +12 -324
- package/extensions/dashboard-server/routes.ts +1 -0
- package/extensions/dashboard-server/server.ts +2 -0
- package/package.json +2 -1
- package/src/config/vector-cortex.ts +21 -0
- package/src/config.ts +2 -0
- package/src/vector-cortex/cortex/sqlite.ts +489 -0
- package/src/vector-cortex/cortex/store.ts +202 -0
- package/src/vector-cortex/cortex/types.ts +194 -0
- package/src/vector-cortex/encoder/calibrate.ts +225 -0
- package/src/vector-cortex/encoder/emit-vc2c.ts +70 -0
- package/src/vector-cortex/encoder/fallback.ts +99 -0
- package/src/vector-cortex/encoder/select.ts +237 -0
- package/src/vector-cortex/encoder/types.ts +116 -0
- package/extensions/dashboard-client/dist/assets/VectorCortexTab-DD1CMK80.js +0 -2
- package/extensions/dashboard-client/dist/assets/VectorCortexTab-DD1CMK80.js.map +0 -1
- package/extensions/dashboard-client/dist/assets/index-CfvziyBi.css +0 -1
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vector-cortex/cortex/types.ts — capability-gated cortex store contract (VC3A).
|
|
3
|
+
*
|
|
4
|
+
* Owns `CortexReader` / `CortexWriter` / `CortexAdmin` and `CortexRecordV1`
|
|
5
|
+
* (the immutable derived record). Consumes only reviewer-accepted predecessor
|
|
6
|
+
* contracts and [common contracts](../../CONTRACTS.md §Store and migration
|
|
7
|
+
* contracts), which are NORMATIVE here: the derived frontier cannot exceed the
|
|
8
|
+
* contiguous durable authority high-water and cannot advance during
|
|
9
|
+
* authority/spool outage.
|
|
10
|
+
*
|
|
11
|
+
* A CortexRecordV1 is a DERIVED record keyed by
|
|
12
|
+
* `(sourceHighWater, algorithmVersion, id)`. The same `id` at a different
|
|
13
|
+
* `algorithmVersion` (or a different `sourceHighWater`) is a DISTINCT record —
|
|
14
|
+
* never collapsed (CTX-KEY-002). Records are immutable: re-appending an exact
|
|
15
|
+
* `(sourceHighWater, algorithmVersion, id)` with the SAME `payloadDigest` is an
|
|
16
|
+
* idempotent acknowledge; the same key with a DIFFERENT digest is a conflict.
|
|
17
|
+
*
|
|
18
|
+
* Capability gating mirrors the host `asReader/asWriter/asAdmin` pattern and the
|
|
19
|
+
* VC1B ledger: the writer exposes append only, the reader exposes query only,
|
|
20
|
+
* and the admin alone can rebuild / switch generations. No callbacks or event
|
|
21
|
+
* emitters flow from the store; writes are non-fatal (failures log and never
|
|
22
|
+
* break the agent loop).
|
|
23
|
+
*
|
|
24
|
+
* Pure type/schema definitions + small pure predicates. No storage, no console,
|
|
25
|
+
* no network, no side effects (PREVENT-PI-004 / PREVENT-011).
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* A derived cortex record — byte authority is `payloadBytes`; `payloadDigest` is
|
|
30
|
+
* the authoritative `sha256:<hex>` over those bytes. `sourceHighWater` is the
|
|
31
|
+
* durable authority high-water the record was derived AT (the derived frontier
|
|
32
|
+
* cannot exceed it); `algorithmVersion` is the producing algorithm revision.
|
|
33
|
+
*/
|
|
34
|
+
export interface CortexRecordV1 {
|
|
35
|
+
readonly schema: "cortex-record-v1";
|
|
36
|
+
/** Durable contiguous authority high-water this record derives from. */
|
|
37
|
+
readonly sourceHighWater: bigint;
|
|
38
|
+
/** Producing algorithm revision (records at distinct versions stay distinct). */
|
|
39
|
+
readonly algorithmVersion: number;
|
|
40
|
+
/** Stable record identity within the derived store. */
|
|
41
|
+
readonly id: string;
|
|
42
|
+
/** Record kind (semantic / dependency / contradiction / synthetic...). */
|
|
43
|
+
readonly kind: string;
|
|
44
|
+
/** Authoritative `sha256:<hex>` of `payloadBytes` (immutability anchor). */
|
|
45
|
+
readonly payloadDigest: string;
|
|
46
|
+
/** Immutable derived payload bytes (byte authority for the record). */
|
|
47
|
+
readonly payloadBytes: Uint8Array;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* A derived GENEration — a named, digest-pinned front over the sorted accepted
|
|
52
|
+
* records. `rootDigest` is ONE deterministic digest over the canonical sorted
|
|
53
|
+
* key list (order-independent, CTX-REBUILD-003); `sourceHighWater` is the derived
|
|
54
|
+
* frontier (max sourceHighWater across its records).
|
|
55
|
+
*/
|
|
56
|
+
export interface CortexGenerationV1 {
|
|
57
|
+
readonly schema: "cortex-generation-v1";
|
|
58
|
+
readonly id: string;
|
|
59
|
+
readonly sourceHighWater: bigint;
|
|
60
|
+
readonly recordCount: number;
|
|
61
|
+
readonly rootDigest: string;
|
|
62
|
+
/** Monotonic rebuild ordinal (never regresses for an accepted generation). */
|
|
63
|
+
readonly ordinal: bigint;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Failure codes the writer surfaces on a rejected / failed append. */
|
|
67
|
+
export type CortexAppendCode =
|
|
68
|
+
/** Storage failure (e.g. SQLITE_FULL). Non-fatal; host continues in mode C. */
|
|
69
|
+
| "CTX_APPEND_FAILED"
|
|
70
|
+
/** Same (sourceHighWater, algorithmVersion, id) but a different payloadDigest. */
|
|
71
|
+
| "CTX_KEY_CONFLICT";
|
|
72
|
+
|
|
73
|
+
/** Result of a single append attempt. */
|
|
74
|
+
export type CortexAppendResult =
|
|
75
|
+
| { ok: true; record: CortexRecordV1 }
|
|
76
|
+
| { ok: false; code: CortexAppendCode; rejected: CortexRecordV1 };
|
|
77
|
+
|
|
78
|
+
/** Failure codes the admin surfaces on a rejected rebuild. */
|
|
79
|
+
export type CortexRebuildCode =
|
|
80
|
+
/** A record's payloadDigest does not match its payloadBytes (authority corrupt). */
|
|
81
|
+
| "CTX_PAYLOAD_DIGEST_MISMATCH"
|
|
82
|
+
/** A record exceeds the caller's declared generation source high-water. */
|
|
83
|
+
| "CTX_HIGH_WATER_EXCEEDED"
|
|
84
|
+
/** The generation INSERT/activate write did not persist (storage failure, e.g. SQLITE_FULL). */
|
|
85
|
+
| "CTX_REBUILD_FAILED";
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Reader capability: query-only. The dashboard's reader-only GET
|
|
89
|
+
* `GET /api/vector-cortex/topology` is built on exactly this surface and nothing
|
|
90
|
+
* more — it can read the generation/topology summary and records but never
|
|
91
|
+
* append, rebuild, or switch generations.
|
|
92
|
+
*/
|
|
93
|
+
export interface CortexReader {
|
|
94
|
+
readonly kind: "CortexReader";
|
|
95
|
+
/** Count of accepted derived records. */
|
|
96
|
+
recordCount(): number;
|
|
97
|
+
/** Accepted records in ascending `(sourceHighWater, algorithmVersion, id)`. */
|
|
98
|
+
readRecords(): readonly CortexRecordV1[];
|
|
99
|
+
/** The single record at a composite key, or undefined. */
|
|
100
|
+
readRecord(sourceHighWater: bigint, algorithmVersion: number, id: string): CortexRecordV1 | undefined;
|
|
101
|
+
/** The latest (active) generation, or undefined when none rebuilt yet. */
|
|
102
|
+
latestGeneration(): CortexGenerationV1 | undefined;
|
|
103
|
+
/** Reader-only topology summary (the dashboard GET payload). */
|
|
104
|
+
topologySummary(): CortexTopologySummary;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Writer capability: append-only. Can add immutable derived records — but cannot
|
|
109
|
+
* query arbitrary history, rebuild, or touch generations. Enforces the composite
|
|
110
|
+
* key immutability on every append.
|
|
111
|
+
*/
|
|
112
|
+
export interface CortexWriter {
|
|
113
|
+
readonly kind: "CortexWriter";
|
|
114
|
+
/** Append one derived record; idempotent ack on exact key+digest, conflict otherwise. */
|
|
115
|
+
append(input: CortexAppendInput): CortexAppendResult;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Admin capability: maintenance + generation control ONLY. The sole surface with
|
|
120
|
+
* rebuild / switch-generations capability — never exposed to ingestion or the
|
|
121
|
+
* dashboard reader.
|
|
122
|
+
*/
|
|
123
|
+
export interface CortexAdmin {
|
|
124
|
+
readonly kind: "CortexAdmin";
|
|
125
|
+
/**
|
|
126
|
+
* Deterministically rebuild a generation from ALL accepted records: sorts keys,
|
|
127
|
+
* verifies each payload digest, computes ONE root digest, writes + activates a
|
|
128
|
+
* new generation. Idempotent with respect to unchanged accepted inputs.
|
|
129
|
+
*
|
|
130
|
+
* `authorityHighWater` (optional) is the contiguous durable authority high-water
|
|
131
|
+
* the rebuild must not outrun. When supplied and the derived frontier (max
|
|
132
|
+
* record sourceHighWater) exceeds it, the rebuild is rejected with
|
|
133
|
+
* `CTX_HIGH_WATER_EXCEEDED` and NO generation is written — the derived frontier
|
|
134
|
+
* cannot exceed the durable authority high-water (normative in CONTRACTS.md).
|
|
135
|
+
* Omit it to rebuild without an authority bound.
|
|
136
|
+
*/
|
|
137
|
+
rebuild(authorityHighWater?: bigint):
|
|
138
|
+
| { ok: true; generation: CortexGenerationV1 }
|
|
139
|
+
| { ok: false; code: CortexRebuildCode };
|
|
140
|
+
/** Switch the active generation pointer without deleting evidence. */
|
|
141
|
+
switchGeneration(generationId: string): { ok: boolean; code?: string };
|
|
142
|
+
/** List every generation id in ascending ordinal order (evidence retained). */
|
|
143
|
+
listGenerations(): readonly CortexGenerationV1[];
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** Input shape of a single append (record fields minus the readonly full type). */
|
|
147
|
+
export interface CortexAppendInput {
|
|
148
|
+
readonly sourceHighWater: bigint;
|
|
149
|
+
readonly algorithmVersion: number;
|
|
150
|
+
readonly id: string;
|
|
151
|
+
readonly kind: string;
|
|
152
|
+
readonly payloadDigest?: string;
|
|
153
|
+
readonly payloadBytes: Uint8Array;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Reader-only topology summary — the exact payload of the dashboard
|
|
158
|
+
* `GET /api/vector-cortex/topology` reader-only GET. Aggregate only: root digest
|
|
159
|
+
* prefix, record count, derived frontier. Never exposes writer/admin surfaces or
|
|
160
|
+
* raw record payloads.
|
|
161
|
+
*/
|
|
162
|
+
export interface CortexTopologySummary {
|
|
163
|
+
/** Whether the VC3A cortex-store flag is enabled. */
|
|
164
|
+
readonly enabled: boolean;
|
|
165
|
+
/** Active generation id, or null when no generation rebuilt yet. */
|
|
166
|
+
readonly generationId: string | null;
|
|
167
|
+
/** Active generation root digest (or null). */
|
|
168
|
+
readonly rootDigest: string | null;
|
|
169
|
+
/** Derived frontier (active generation sourceHighWater, or "0"). */
|
|
170
|
+
readonly sourceHighWater: string;
|
|
171
|
+
/** Accepted derived record count. */
|
|
172
|
+
readonly recordCount: number;
|
|
173
|
+
/** Monotonic rebuild ordinal (or null). */
|
|
174
|
+
readonly ordinal: string | null;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Registered CTX conformance ID range (CTX-001..010). The acceptance test reads
|
|
179
|
+
* these rows from the v2 manifest and asserts each returns its manifest bytes or
|
|
180
|
+
* exactly its listed failure code. The three NAMED assertions
|
|
181
|
+
* (CTX-CAP-001 / CTX-KEY-002 / CTX-REBUILD-003) live in the acceptance test.
|
|
182
|
+
*/
|
|
183
|
+
export const CTX_IDS = [
|
|
184
|
+
"CTX-001",
|
|
185
|
+
"CTX-002",
|
|
186
|
+
"CTX-003",
|
|
187
|
+
"CTX-004",
|
|
188
|
+
"CTX-005",
|
|
189
|
+
"CTX-006",
|
|
190
|
+
"CTX-007",
|
|
191
|
+
"CTX-008",
|
|
192
|
+
"CTX-009",
|
|
193
|
+
"CTX-010",
|
|
194
|
+
] as const;
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vector-cortex/encoder/calibrate.ts — VC2C calibration fit (task 2).
|
|
3
|
+
*
|
|
4
|
+
* Fits a `CalibrationV1` using ONLY the calibration split. Held-out
|
|
5
|
+
* (test/eval) labels are STRICTLY PROHIBITED from the fit inputs: the fit
|
|
6
|
+
* function rejects (ENC_QUALIFICATION_HELD_OUT_IN_FIT) any example whose
|
|
7
|
+
* `itemId` appears in the caller's held-out set. Ties in example score are
|
|
8
|
+
* broken deterministically by item ID (stable score/id ties), never by arrival
|
|
9
|
+
* order, so the fit is invariant to row order.
|
|
10
|
+
*
|
|
11
|
+
* The calibration split assignment is grouped by repository+session
|
|
12
|
+
* (EVALUATION.md §corpus): every label-bearing item carries a `repository` +
|
|
13
|
+
* `session` group, and the split digest is the canonical SHA-256 over the
|
|
14
|
+
* sorted group list. A single repo/session group NEVER crosses split boundaries
|
|
15
|
+
* (the caller seeds a group wholly into the calibration split or not at all).
|
|
16
|
+
*
|
|
17
|
+
* The fit itself is a deterministic seeded per-head temperature + threshold
|
|
18
|
+
* calibration over the calibration-only examples (real isotonic/Platt weights
|
|
19
|
+
* land with trained weights; the contract, split isolation, held-out
|
|
20
|
+
* prohibition, stable ties, and frozen-temperature/threshold surface are all
|
|
21
|
+
* normative here). Pi-agnostic, zero network (PREVENT-PI-004), no `any`
|
|
22
|
+
* (PREVENT-011).
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { createHash } from "node:crypto";
|
|
26
|
+
import {
|
|
27
|
+
ENCODER_HEAD_ORDER,
|
|
28
|
+
ENCODER_SEED,
|
|
29
|
+
ENC_QUALIFICATION_FAIL,
|
|
30
|
+
type CalibrationV1,
|
|
31
|
+
type EncoderHeadName,
|
|
32
|
+
} from "./types.js";
|
|
33
|
+
|
|
34
|
+
/** A scored, label-bearing calibration item. `heldOutIds` forbid review labels. */
|
|
35
|
+
export interface CalibrationExample {
|
|
36
|
+
readonly itemId: string;
|
|
37
|
+
readonly head: EncoderHeadName;
|
|
38
|
+
readonly score: number;
|
|
39
|
+
/** Binary ground-truth label (0/1) against which the raw score is calibrated. */
|
|
40
|
+
readonly label: 0 | 1;
|
|
41
|
+
/** Repository group (EVALUATION.md §corpus) — the split unit. */
|
|
42
|
+
readonly repository: string;
|
|
43
|
+
/** Session group (within the repository). */
|
|
44
|
+
readonly session: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface CalibrationFitOptions {
|
|
48
|
+
/** Seed of the deterministic fit (defaults to ENCODER_SEED). */
|
|
49
|
+
readonly seed?: number;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export type CalibrationFitResult =
|
|
53
|
+
| { ok: true; calibration: CalibrationV1 }
|
|
54
|
+
| { ok: false; code: string; reason: string };
|
|
55
|
+
|
|
56
|
+
/** Canonical digests of a sorted stable representation (order-invariant). */
|
|
57
|
+
function digestStrings(values: readonly string[]): string {
|
|
58
|
+
const sorted = [...values].sort();
|
|
59
|
+
return createHash("sha256").update(sorted.join("\n")).digest("hex");
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Compute the calibration split digest for a set of (repository, session) groups
|
|
64
|
+
* present in the fit. The group list is canonicalized (sorted, deduped) so the
|
|
65
|
+
* digest is invariant to row order within the input. A caller that seeds WHOLE
|
|
66
|
+
* groups into the calibration split guarantees no group crosses a boundary.
|
|
67
|
+
*/
|
|
68
|
+
/**
|
|
69
|
+
* Render one (repository, session) group as an injective canonical string.
|
|
70
|
+
* Each field is length-prefixed (`<len>:<value>`), so two distinct pairs can
|
|
71
|
+
* never collide to the same rendering — e.g. `{r:"a", s:"b::c"}` renders as
|
|
72
|
+
* `1:a3:b::c` while `{r:"a::b", s:"c"}` renders as `4:a::b1:c`. A plain
|
|
73
|
+
* `repository::session` join would conflate those when identifiers happen to
|
|
74
|
+
* contain "::"; length-prefixing makes the split digest sound for arbitrary
|
|
75
|
+
* repository/session identifiers.
|
|
76
|
+
*/
|
|
77
|
+
function renderGroup(g: { repository: string; session: string }): string {
|
|
78
|
+
return `${g.repository.length}:${g.repository}${g.session.length}:${g.session}`;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function calibrationSplitDigest(groups: readonly { repository: string; session: string }[]): string {
|
|
82
|
+
const rendered = new Set<string>();
|
|
83
|
+
for (const g of groups) rendered.add(renderGroup(g));
|
|
84
|
+
return digestStrings([...rendered]);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** A deterministic 32-bit LCG step (matches the heads/runtime projectors). */
|
|
88
|
+
function nextState(state: number): number {
|
|
89
|
+
return (state * 1664525 + 1013904223) >>> 0;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Deterministic per-head temperature in a stable, healthy range (e.g. 0.8..1.5).
|
|
94
|
+
*
|
|
95
|
+
* The caller-supplied `seed` is mixed into the LCG state through independent
|
|
96
|
+
* steps so it ALWAYS affects the temperature — even when it equals ENCODER_SEED.
|
|
97
|
+
* (A naive `ENCODER_SEED ^ head.length ^ seed` cancels the two seed terms when
|
|
98
|
+
* `seed === ENCODER_SEED`, leaving a pure function of the head — the default
|
|
99
|
+
* path would make the seed option inert. Here the seed seeds the PRNG first,
|
|
100
|
+
* then the head name is folded in, so both vary the fit independently.) The fit
|
|
101
|
+
* stays deterministic for a fixed (seed, head) across processes.
|
|
102
|
+
*/
|
|
103
|
+
function fitTemperature(head: EncoderHeadName, seed: number): number {
|
|
104
|
+
let state = (seed >>> 0) ^ 0x9e3779b9;
|
|
105
|
+
state = nextState(state);
|
|
106
|
+
state = (state ^ (head.length >>> 0)) >>> 0;
|
|
107
|
+
state = nextState(state);
|
|
108
|
+
state = (state ^ 0x85ebca6b) >>> 0;
|
|
109
|
+
state = nextState(state);
|
|
110
|
+
const r = (state / 4294967296) % 1;
|
|
111
|
+
return 0.8 + r * 0.7; // 0.8 .. 1.5
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Deterministic per-head decision threshold derived from the calibration
|
|
116
|
+
* distribution. For a head with BOTH classes present the threshold is the
|
|
117
|
+
* midpoint between the highest-scoring negative (label 0) and the lowest-scoring
|
|
118
|
+
* positive (label 1) — a true between-class balance point that NEVER lands on a
|
|
119
|
+
* negative example's own score (code-review Q03): a future inference at the
|
|
120
|
+
* highest calibration negative is still classified negative, and one at the
|
|
121
|
+
* lowest calibration positive is still classified positive. Degenerate heads with
|
|
122
|
+
* a single class fall back conservatively (no positives -> just above the top
|
|
123
|
+
* observed score; no negatives -> just below the lowest observed positive) and an
|
|
124
|
+
* empty head defaults to 0.5. Scoring is order-invariant: ties in score resolve by
|
|
125
|
+
* item ID bytewise (stable score/id ties), never by arrival order, so the fit is
|
|
126
|
+
* invariant to row order. This frozen threshold is a normative placeholder (real
|
|
127
|
+
* trained weights land later).
|
|
128
|
+
*/
|
|
129
|
+
function fitThreshold(
|
|
130
|
+
head: EncoderHeadName,
|
|
131
|
+
examples: readonly CalibrationExample[],
|
|
132
|
+
): number {
|
|
133
|
+
const headEx = examples
|
|
134
|
+
.filter((e) => e.head === head)
|
|
135
|
+
.slice()
|
|
136
|
+
.sort((a, b) => (a.score - b.score) || (a.itemId < b.itemId ? -1 : a.itemId > b.itemId ? 1 : 0));
|
|
137
|
+
if (headEx.length === 0) return 0.5;
|
|
138
|
+
let highestNeg = -Infinity;
|
|
139
|
+
let lowestPos = Infinity;
|
|
140
|
+
for (const e of headEx) {
|
|
141
|
+
if (e.label === 0) highestNeg = Math.max(highestNeg, e.score);
|
|
142
|
+
else lowestPos = Math.min(lowestPos, e.score);
|
|
143
|
+
}
|
|
144
|
+
if (lowestPos === Infinity) {
|
|
145
|
+
// Only negatives observed — no positive class to balance against. Set the
|
|
146
|
+
// threshold just above the top observed score so no observed negative is
|
|
147
|
+
// re-admitted (a genuine future positive must exceed all calibration negatives).
|
|
148
|
+
return Math.max(0.5, highestNeg + 0.05);
|
|
149
|
+
}
|
|
150
|
+
if (highestNeg === -Infinity) {
|
|
151
|
+
// Only positives observed — set the threshold just below the lowest observed
|
|
152
|
+
// positive so every observed positive is admitted.
|
|
153
|
+
return Math.max(0, lowestPos - 0.05);
|
|
154
|
+
}
|
|
155
|
+
// Both classes present: the midpoint strictly between the highest negative and
|
|
156
|
+
// the lowest positive is the between-class balance point (Q03).
|
|
157
|
+
return (highestNeg + lowestPos) / 2;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Fit `CalibrationV1` over the calibration split only (task 2).
|
|
162
|
+
*
|
|
163
|
+
* - rejects any item whose `itemId` is in `heldOutIds` (held-out labels are
|
|
164
|
+
* prohibited from fit inputs).
|
|
165
|
+
* - treats `groups` as the calibration split units; the emitted split digest
|
|
166
|
+
* covers only the groups ACTUALLY present in the fit examples (plus the
|
|
167
|
+
* declared `groups`, when supplied — see below).
|
|
168
|
+
* - stable score/id ties (never arrival order).
|
|
169
|
+
*
|
|
170
|
+
* Held-out labels are prohibited by construction: the caller passes the full set
|
|
171
|
+
* of held-out item IDs, and the fit fails loudly if any calibration input is
|
|
172
|
+
* actually a held-out item — the fit can never silently learn from review labels.
|
|
173
|
+
*/
|
|
174
|
+
export function fitCalibration(
|
|
175
|
+
examples: readonly CalibrationExample[],
|
|
176
|
+
options: CalibrationFitOptions & {
|
|
177
|
+
/** Item IDs that belong to the held-out (test/eval) split. Any calibration
|
|
178
|
+
* input whose id appears here is a fit violation. */
|
|
179
|
+
readonly heldOutIds?: readonly string[];
|
|
180
|
+
/** Declared calibration groups (repository+session). When supplied, the split
|
|
181
|
+
* digest covers these; otherwise it covers the groups actually in `examples`.
|
|
182
|
+
* Whole groups never cross a split boundary when the caller seeds a group
|
|
183
|
+
* into exactly one split. */
|
|
184
|
+
readonly groups?: readonly { repository: string; session: string }[];
|
|
185
|
+
} = {},
|
|
186
|
+
): CalibrationFitResult {
|
|
187
|
+
const seed = options.seed ?? ENCODER_SEED;
|
|
188
|
+
const heldOut = new Set(options.heldOutIds ?? []);
|
|
189
|
+
|
|
190
|
+
for (const e of examples) {
|
|
191
|
+
if (heldOut.has(e.itemId)) {
|
|
192
|
+
return {
|
|
193
|
+
ok: false,
|
|
194
|
+
code: ENC_QUALIFICATION_FAIL.HELD_OUT_IN_FIT,
|
|
195
|
+
reason: `held-out item ${e.itemId} leaked into calibration fit`,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Stable order by (score, itemId) — arrival order never affects the digest or
|
|
201
|
+
// the fit; ties resolve by item ID bytewise (stable score/id ties).
|
|
202
|
+
const stable = examples
|
|
203
|
+
.slice()
|
|
204
|
+
.sort((a, b) => (a.score - b.score) || (a.itemId < b.itemId ? -1 : a.itemId > b.itemId ? 1 : 0));
|
|
205
|
+
|
|
206
|
+
const usedGroups = options.groups ?? stable.map((e) => ({ repository: e.repository, session: e.session }));
|
|
207
|
+
const splitDigest = calibrationSplitDigest(usedGroups);
|
|
208
|
+
const temperatures: Record<EncoderHeadName, number> = {} as Record<EncoderHeadName, number>;
|
|
209
|
+
const thresholds: Record<EncoderHeadName, number> = {} as Record<EncoderHeadName, number>;
|
|
210
|
+
for (const head of ENCODER_HEAD_ORDER) {
|
|
211
|
+
temperatures[head] = fitTemperature(head, seed);
|
|
212
|
+
thresholds[head] = fitThreshold(head, stable);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const calibration: CalibrationV1 = {
|
|
216
|
+
schema: "calibration-v1",
|
|
217
|
+
headOrder: [...ENCODER_HEAD_ORDER],
|
|
218
|
+
calibrationSplitDigest: splitDigest,
|
|
219
|
+
fittedOnCalibrationOnly: true,
|
|
220
|
+
temperatures,
|
|
221
|
+
thresholds,
|
|
222
|
+
seed,
|
|
223
|
+
};
|
|
224
|
+
return { ok: true, calibration };
|
|
225
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vector-cortex/encoder/emit-vc2c.ts — VC2C observability seam.
|
|
3
|
+
*
|
|
4
|
+
* Owns the two VC2C events (task 5), gated on `MEGACOMPACT_VC2C` so the
|
|
5
|
+
* flag-OFF path emits zero events (mode C parity, byte-identical predecessor):
|
|
6
|
+
*
|
|
7
|
+
* vector_cortex_encoder_qualification_passed — a QualifiedEncoderV1 produced (A)
|
|
8
|
+
* vector_cortex_encoder_qualification_demoted — qualification failed; all of A demoted
|
|
9
|
+
*
|
|
10
|
+
* Every event is a JSON line with `ts` + `event` (ENGINEERING_PRACTICES §8); the
|
|
11
|
+
* `ts` is the numeric epoch-ms timestamp the `Logger` injects (LogEntry.ts is
|
|
12
|
+
* `number`), so these events are consistent with the rest of the log stream
|
|
13
|
+
* (code-review Q04 — no ISO-string ts override). The emitters are non-fatal
|
|
14
|
+
* (never break the agent loop). Pi-agnostic, zero network (PREVENT-PI-004), no
|
|
15
|
+
* `any` (PREVENT-011).
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { VC2C_ENABLED } from "../../config/vector-cortex.js";
|
|
19
|
+
import { Logger } from "../../log.js";
|
|
20
|
+
|
|
21
|
+
export type EncoderEmit = (event: string, fields: Record<string, unknown>) => void;
|
|
22
|
+
|
|
23
|
+
/** The two-event VC2C reporter surface (qualification passed / demoted). */
|
|
24
|
+
export interface EncoderQualificationReporter {
|
|
25
|
+
readonly qualificationPassed: (fields: Record<string, unknown>) => void;
|
|
26
|
+
readonly qualificationDemoted: (fields: Record<string, unknown>) => void;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* The default emitter: routes both VC2C events into the append-only structured
|
|
31
|
+
* logger (`src/log.ts`) as JSON lines with `event` (the logger injects the
|
|
32
|
+
* numeric `ts`). Supplying `emit:` to `createEncoderQualificationReporter`
|
|
33
|
+
* replaces this with a caller-provided sink (used by tests and downstream
|
|
34
|
+
* consumers). Making the default a REAL producer means a caller that just
|
|
35
|
+
* invokes the producer seam (`selectQualifiedEncoder`) without injecting an
|
|
36
|
+
* emitter still yields structured telemetry instead of silently dropping every
|
|
37
|
+
* event (task 5). Best-effort: the logger swallows all I/O errors.
|
|
38
|
+
*/
|
|
39
|
+
function defaultEmitFor(): EncoderEmit {
|
|
40
|
+
const logger = new Logger();
|
|
41
|
+
return (event, fields) => {
|
|
42
|
+
logger.info(event, fields);
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Flag-gated emit, defaulting to a real logger-backed sink. The returned
|
|
48
|
+
* reporter is itself flag-gated (`VC2C_ENABLED`), so wiring it into a producer
|
|
49
|
+
* seam yields zero emissions when `MEGACOMPACT_VC2C=0` (byte-identical to the
|
|
50
|
+
* predecessor). Pass an explicit `emit` to route elsewhere (tests, downstream
|
|
51
|
+
* consumers); omit it to emit real structured log lines. The `ts` is set by the
|
|
52
|
+
* underlying sink (numeric epoch-ms from `Logger`), not overridden here (Q04).
|
|
53
|
+
*/
|
|
54
|
+
export function createEncoderQualificationReporter(
|
|
55
|
+
emit?: EncoderEmit,
|
|
56
|
+
): EncoderQualificationReporter {
|
|
57
|
+
const sink = emit ?? defaultEmitFor();
|
|
58
|
+
const fire = (event: string, fields: Record<string, unknown>): void => {
|
|
59
|
+
if (!VC2C_ENABLED()) return;
|
|
60
|
+
try {
|
|
61
|
+
sink(event, fields);
|
|
62
|
+
} catch {
|
|
63
|
+
/* non-fatal observability */
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
return {
|
|
67
|
+
qualificationPassed: (fields) => fire("vector_cortex_encoder_qualification_passed", fields),
|
|
68
|
+
qualificationDemoted: (fields) => fire("vector_cortex_encoder_qualification_demoted", fields),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vector-cortex/encoder/fallback.ts — VC2C qualification fallback selection.
|
|
3
|
+
*
|
|
4
|
+
* After a qualification demotion (qualify/select fails to produce mode A), this
|
|
5
|
+
* seam selects the mode-B/C fallback and routes the encode request to the
|
|
6
|
+
* independently initialized VC2B producers (trigram B / lexical C). It is the
|
|
7
|
+
* breaker-recovery handoff for the encoder triad:
|
|
8
|
+
*
|
|
9
|
+
* A = fully qualified learned asset (QualifiedEncoderV1).
|
|
10
|
+
* B = asset-free trigram, forced by ANY one failed qualification threshold
|
|
11
|
+
* (or a qualification-manifest digest mismatch).
|
|
12
|
+
* C = token/phrase lexical, forced when A is absent AND B itself errors
|
|
13
|
+
* (injected B error), or when the caller forces C.
|
|
14
|
+
*
|
|
15
|
+
* Breaker recovery follows TRIAD_RESILIENCE: a demotion may probe/promote, never
|
|
16
|
+
* directly re-qualify A; the fallback only ever SELECTS B or C locally, never a
|
|
17
|
+
* remote fetch (PREVENT-PI-004). C states its loss of old semantic context.
|
|
18
|
+
*
|
|
19
|
+
* Pi-agnostic, zero network (PREVENT-PI-004), no `any` (PREVENT-011).
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { createEncoderHeadsReporter, type EncoderHeadsReporter } from "./emit-vc2b.js";
|
|
23
|
+
import { embedTrigram512, selectTrigramBFallback, ENCODER_TRIGRAM_WIDTH } from "./trigram.js";
|
|
24
|
+
import { embedLexical, selectLexicalC, ENCODER_LEXICAL_WIDTH, ENCODER_LEXICAL_LIMITATION } from "./lexical.js";
|
|
25
|
+
|
|
26
|
+
export type QualificationFallbackVerdict =
|
|
27
|
+
| {
|
|
28
|
+
readonly ok: true;
|
|
29
|
+
readonly mode: "B";
|
|
30
|
+
readonly vector: Float32Array;
|
|
31
|
+
readonly width: number;
|
|
32
|
+
/** The qualification failure code that forced B (never null on this path). */
|
|
33
|
+
readonly code: string;
|
|
34
|
+
readonly limitation: null;
|
|
35
|
+
}
|
|
36
|
+
| {
|
|
37
|
+
readonly ok: true;
|
|
38
|
+
readonly mode: "C";
|
|
39
|
+
readonly vector: Float32Array;
|
|
40
|
+
readonly width: number;
|
|
41
|
+
/** The qualification or B failure code that forced C. */
|
|
42
|
+
readonly code: string;
|
|
43
|
+
readonly limitation: string;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export interface FallbackSelectionOptions {
|
|
47
|
+
readonly reporter?: EncoderHeadsReporter;
|
|
48
|
+
/** Simulates a B initializer failure (forces C despite B being available). */
|
|
49
|
+
readonly injectBError?: boolean;
|
|
50
|
+
/** Force lexical C directly (skips the B attempt). */
|
|
51
|
+
readonly forceC?: boolean;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Deterministic text derived from an int token sequence (mirrors router.ts). */
|
|
55
|
+
function textFromTokens(tokens: readonly number[]): string {
|
|
56
|
+
return tokens.join("-");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Select the B/C fallback for a failed qualification and produce the encode
|
|
61
|
+
* vector. When `injectBError` or `forceC` is set, selection lands on lexical C
|
|
62
|
+
* (absent A + injected B error -> C); otherwise a qualification THRESHOLD or
|
|
63
|
+
* DIGEST mismatch selects trigram B. Never returns without an explicit verdict.
|
|
64
|
+
*/
|
|
65
|
+
export function selectQualificationFallback(
|
|
66
|
+
qualificationCode: string,
|
|
67
|
+
tokens: readonly number[],
|
|
68
|
+
options: FallbackSelectionOptions = {},
|
|
69
|
+
): QualificationFallbackVerdict {
|
|
70
|
+
const reporter = options.reporter ?? createEncoderHeadsReporter();
|
|
71
|
+
const tokensText = textFromTokens(tokens);
|
|
72
|
+
|
|
73
|
+
// A absent + injected/forced B error -> C (lexical) with the documented
|
|
74
|
+
// semantic-context limitation.
|
|
75
|
+
if (options.forceC || options.injectBError) {
|
|
76
|
+
selectLexicalC({ reporter });
|
|
77
|
+
const vector = embedLexical(tokensText);
|
|
78
|
+
return {
|
|
79
|
+
ok: true,
|
|
80
|
+
mode: "C",
|
|
81
|
+
vector,
|
|
82
|
+
width: ENCODER_LEXICAL_WIDTH,
|
|
83
|
+
code: options.injectBError ? "ENC_B_INJECTED_ERROR" : qualificationCode,
|
|
84
|
+
limitation: ENCODER_LEXICAL_LIMITATION,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// A qualification demotion selects the independently initialized trigram B.
|
|
89
|
+
selectTrigramBFallback({ reporter });
|
|
90
|
+
const vector = embedTrigram512(tokensText);
|
|
91
|
+
return {
|
|
92
|
+
ok: true,
|
|
93
|
+
mode: "B",
|
|
94
|
+
vector,
|
|
95
|
+
width: ENCODER_TRIGRAM_WIDTH,
|
|
96
|
+
code: qualificationCode,
|
|
97
|
+
limitation: null,
|
|
98
|
+
};
|
|
99
|
+
}
|