@gmickel/gno 2.1.0 → 2.1.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/README.md +1 -1
- package/browser-extension/artifacts/{gno-browser-clipper-v2.1.0.zip → gno-browser-clipper-v2.1.1.zip} +0 -0
- package/browser-extension/artifacts/gno-browser-clipper-v2.1.1.zip.sha256 +1 -0
- package/browser-extension/dist/manifest.json +1 -1
- package/package.json +1 -1
- package/spec/cli.md +8 -0
- package/src/embed/backlog.ts +1 -0
- package/src/store/sqlite/adapter.ts +7 -2
- package/src/store/vector/status.ts +196 -0
- package/src/store/vector/variants.ts +11 -0
- package/browser-extension/artifacts/gno-browser-clipper-v2.1.0.zip.sha256 +0 -1
package/README.md
CHANGED
|
@@ -117,7 +117,7 @@ gno daemon --detach # headless indexing + resident MCP gateway
|
|
|
117
117
|
|
|
118
118
|
<!-- public-truth:current-version -->
|
|
119
119
|
|
|
120
|
-
> Current source version: **v2.1.
|
|
120
|
+
> Current source version: **v2.1.1**. See [CHANGELOG.md](./CHANGELOG.md).
|
|
121
121
|
|
|
122
122
|
<!-- /public-truth -->
|
|
123
123
|
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
bf5fc1ea3dd699217e270114051b19e88b239265b3a33e395f5b5ba753bc9e47 gno-browser-clipper-v2.1.1.zip
|
package/package.json
CHANGED
package/spec/cli.md
CHANGED
|
@@ -224,6 +224,14 @@ setup and emits `setup-profile-result@1.0`.
|
|
|
224
224
|
|
|
225
225
|
Display index status and health information.
|
|
226
226
|
|
|
227
|
+
Embedding backlog follows the last verified partition for the selected model
|
|
228
|
+
when exact-input storage is authoritative, counting pending document/chunk
|
|
229
|
+
owners. Per-collection chunk totals remain deduplicated by canonical chunk;
|
|
230
|
+
embedded counts require matching current inputs for every active owner within
|
|
231
|
+
that collection. Status reads persisted identity and coverage without loading
|
|
232
|
+
models. Legacy storage remains the fallback before variant authority; ambiguous
|
|
233
|
+
older partition selection is conservative until a normal embed records it.
|
|
234
|
+
|
|
227
235
|
**Synopsis:**
|
|
228
236
|
|
|
229
237
|
```bash
|
package/src/embed/backlog.ts
CHANGED
|
@@ -144,6 +144,7 @@ import { getSchemaVersion, migrations, runMigrations } from "../migrations";
|
|
|
144
144
|
import { err, ok } from "../types";
|
|
145
145
|
import { getStoredEmbeddingFingerprint } from "../vector/freshness";
|
|
146
146
|
import { modelTableName } from "../vector/sqlite-vec";
|
|
147
|
+
import { getVariantStatus } from "../vector/status";
|
|
147
148
|
import {
|
|
148
149
|
deleteSavedCapsuleRegistration as deleteStoredSavedCapsuleRegistration,
|
|
149
150
|
getSavedCapsuleRegistration as getStoredSavedCapsuleRegistration,
|
|
@@ -5697,6 +5698,8 @@ export class SqliteAdapter implements StorePort, SqliteDbProvider {
|
|
|
5697
5698
|
options?.embedFingerprint ??
|
|
5698
5699
|
(embedModel ? getStoredEmbeddingFingerprint(db, embedModel) : null);
|
|
5699
5700
|
|
|
5701
|
+
const variantStatus = getVariantStatus(db, options);
|
|
5702
|
+
|
|
5700
5703
|
// Get version
|
|
5701
5704
|
const versionRow = db
|
|
5702
5705
|
.query<{ value: string }, []>(
|
|
@@ -5873,12 +5876,14 @@ export class SqliteAdapter implements StorePort, SqliteDbProvider {
|
|
|
5873
5876
|
errorDocuments: s.errored,
|
|
5874
5877
|
chunkedDocuments: s.chunked,
|
|
5875
5878
|
totalChunks: s.chunk_count,
|
|
5876
|
-
embeddedChunks:
|
|
5879
|
+
embeddedChunks: variantStatus
|
|
5880
|
+
? (variantStatus.embeddedByCollection.get(s.name) ?? 0)
|
|
5881
|
+
: s.embedded_count,
|
|
5877
5882
|
})),
|
|
5878
5883
|
totalDocuments: totalsRow?.total ?? 0,
|
|
5879
5884
|
activeDocuments: totalsRow?.active ?? 0,
|
|
5880
5885
|
totalChunks: chunkCount,
|
|
5881
|
-
embeddingBacklog: backlogRow?.count ?? 0,
|
|
5886
|
+
embeddingBacklog: variantStatus?.backlog ?? backlogRow?.count ?? 0,
|
|
5882
5887
|
recentErrors,
|
|
5883
5888
|
lastUpdatedAt: lastUpdatedRow?.last_updated ?? null,
|
|
5884
5889
|
healthy,
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/** Read-only coverage of persisted variant authority; never initializes models. */
|
|
2
|
+
import type { Database } from "bun:sqlite";
|
|
3
|
+
|
|
4
|
+
import { getEmbeddingFingerprint } from "../../embed/fingerprint";
|
|
5
|
+
import { formatDocForEmbedding } from "../../pipeline/contextual";
|
|
6
|
+
import {
|
|
7
|
+
embeddingInputHash,
|
|
8
|
+
SELECTED_VECTOR_PARTITION_PREFIX,
|
|
9
|
+
} from "./variants";
|
|
10
|
+
|
|
11
|
+
interface Partition {
|
|
12
|
+
partition_id: string;
|
|
13
|
+
version: number;
|
|
14
|
+
model: string;
|
|
15
|
+
fingerprint: string;
|
|
16
|
+
dimensions: number;
|
|
17
|
+
state: string;
|
|
18
|
+
activated_epoch: number | null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface OwnerCoverage {
|
|
22
|
+
document_id: number;
|
|
23
|
+
partition_id: string | null;
|
|
24
|
+
collection: string;
|
|
25
|
+
mirror_hash: string;
|
|
26
|
+
seq: number;
|
|
27
|
+
text: string;
|
|
28
|
+
title: string | null;
|
|
29
|
+
input_hash: string | null;
|
|
30
|
+
embedding_bytes: number | null;
|
|
31
|
+
legacy_embedded: number;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Null retains legacy counts until a verified selection or activation exists. */
|
|
35
|
+
export function getVariantStatus(
|
|
36
|
+
db: Database,
|
|
37
|
+
options?: { embedModel?: string; embedFingerprint?: string }
|
|
38
|
+
): { backlog: number; embeddedByCollection: Map<string, number> } | null {
|
|
39
|
+
return db.transaction(() => {
|
|
40
|
+
if (
|
|
41
|
+
!db
|
|
42
|
+
.query(
|
|
43
|
+
"SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'vector_partitions'"
|
|
44
|
+
)
|
|
45
|
+
.get()
|
|
46
|
+
)
|
|
47
|
+
return null;
|
|
48
|
+
const partitions = db
|
|
49
|
+
.query<Partition, [string | null, string | null]>(`
|
|
50
|
+
SELECT partition_id, version, model, fingerprint, dimensions, state, activated_epoch
|
|
51
|
+
FROM vector_partitions WHERE (? IS NULL OR model = ?)
|
|
52
|
+
`)
|
|
53
|
+
.all(options?.embedModel ?? null, options?.embedModel ?? null);
|
|
54
|
+
const selections = db
|
|
55
|
+
.query<{ key: string; value: string }, [string]>(
|
|
56
|
+
"SELECT key, value FROM schema_meta WHERE key GLOB ?"
|
|
57
|
+
)
|
|
58
|
+
.all(`${SELECTED_VECTOR_PARTITION_PREFIX}*`);
|
|
59
|
+
const selected = new Map(
|
|
60
|
+
selections.map((row) => [
|
|
61
|
+
row.key.slice(SELECTED_VECTOR_PARTITION_PREFIX.length),
|
|
62
|
+
row.value,
|
|
63
|
+
])
|
|
64
|
+
);
|
|
65
|
+
const models = new Set(partitions.map((p) => p.model));
|
|
66
|
+
if (options?.embedModel) models.add(options.embedModel);
|
|
67
|
+
else for (const model of selected.keys()) models.add(model);
|
|
68
|
+
const authoritativeModels = new Set<string>();
|
|
69
|
+
const usablePartitions = new Map<string, Partition>();
|
|
70
|
+
for (const model of models) {
|
|
71
|
+
const candidates = partitions.filter((p) => p.model === model);
|
|
72
|
+
const selection = selected.get(model);
|
|
73
|
+
const activated = candidates.some(
|
|
74
|
+
(p) => p.state === "active" && p.activated_epoch !== null
|
|
75
|
+
);
|
|
76
|
+
if (selection === undefined && !activated) continue;
|
|
77
|
+
authoritativeModels.add(model);
|
|
78
|
+
// Resolve one persisted identity per model, never combine alternative
|
|
79
|
+
// partitions of the same model. Unscoped status may accept any model.
|
|
80
|
+
// Stale epochs do not revoke owners whose current inputs still match.
|
|
81
|
+
const partition =
|
|
82
|
+
selection === undefined
|
|
83
|
+
? candidates.length === 1
|
|
84
|
+
? candidates[0]
|
|
85
|
+
: undefined
|
|
86
|
+
: candidates.find((p) => p.partition_id === selection);
|
|
87
|
+
if (
|
|
88
|
+
partition &&
|
|
89
|
+
partition.version === 1 &&
|
|
90
|
+
partition.partition_id ===
|
|
91
|
+
embeddingInputHash(
|
|
92
|
+
JSON.stringify([
|
|
93
|
+
partition.model,
|
|
94
|
+
partition.fingerprint,
|
|
95
|
+
partition.dimensions,
|
|
96
|
+
])
|
|
97
|
+
) &&
|
|
98
|
+
(options?.embedFingerprint === undefined ||
|
|
99
|
+
options.embedFingerprint ===
|
|
100
|
+
getEmbeddingFingerprint({
|
|
101
|
+
modelUri: partition.model,
|
|
102
|
+
dimensions: partition.dimensions,
|
|
103
|
+
}))
|
|
104
|
+
)
|
|
105
|
+
usablePartitions.set(partition.partition_id, partition);
|
|
106
|
+
}
|
|
107
|
+
if (authoritativeModels.size === 0) return null;
|
|
108
|
+
// Unscoped legacy coverage remains valid only for models that have never
|
|
109
|
+
// selected or activated verified authority; legacy rows cannot repair it.
|
|
110
|
+
const statement = db.prepare<
|
|
111
|
+
OwnerCoverage,
|
|
112
|
+
[string | null, string, string]
|
|
113
|
+
>(`
|
|
114
|
+
WITH legacy_vectors AS (
|
|
115
|
+
SELECT mirror_hash, seq, MAX(embedded_at) AS embedded_at
|
|
116
|
+
FROM content_vectors
|
|
117
|
+
WHERE ? IS NULL AND model NOT IN (SELECT value FROM json_each(?))
|
|
118
|
+
GROUP BY mirror_hash, seq
|
|
119
|
+
)
|
|
120
|
+
SELECT d.id AS document_id, o.partition_id, d.collection, d.mirror_hash, c.seq, c.text, d.title,
|
|
121
|
+
v.input_hash, length(v.embedding) AS embedding_bytes,
|
|
122
|
+
CASE WHEN lv.embedded_at >= c.created_at THEN 1 ELSE 0 END AS legacy_embedded
|
|
123
|
+
FROM documents d
|
|
124
|
+
JOIN content_chunks c ON c.mirror_hash = d.mirror_hash
|
|
125
|
+
LEFT JOIN vector_owners o ON o.document_id = d.id AND o.seq = c.seq
|
|
126
|
+
AND o.mirror_hash = d.mirror_hash
|
|
127
|
+
AND o.partition_id IN (SELECT value FROM json_each(?))
|
|
128
|
+
LEFT JOIN vector_variants v ON v.variant_id = o.variant_id
|
|
129
|
+
AND v.partition_id = o.partition_id
|
|
130
|
+
LEFT JOIN legacy_vectors lv ON lv.mirror_hash = c.mirror_hash AND lv.seq = c.seq
|
|
131
|
+
WHERE d.active = 1
|
|
132
|
+
`);
|
|
133
|
+
const owners = new Map<
|
|
134
|
+
string,
|
|
135
|
+
{ collection: string; chunk: string; embedded: boolean }
|
|
136
|
+
>();
|
|
137
|
+
try {
|
|
138
|
+
for (const row of statement.iterate(
|
|
139
|
+
options?.embedModel ?? null,
|
|
140
|
+
JSON.stringify([...authoritativeModels]),
|
|
141
|
+
JSON.stringify([...usablePartitions.keys()])
|
|
142
|
+
)) {
|
|
143
|
+
const partition = row.partition_id
|
|
144
|
+
? usablePartitions.get(row.partition_id)
|
|
145
|
+
: undefined;
|
|
146
|
+
const embedded = Boolean(
|
|
147
|
+
row.legacy_embedded ||
|
|
148
|
+
(partition &&
|
|
149
|
+
row.embedding_bytes ===
|
|
150
|
+
partition.dimensions * Float32Array.BYTES_PER_ELEMENT &&
|
|
151
|
+
row.input_hash ===
|
|
152
|
+
embeddingInputHash(
|
|
153
|
+
formatDocForEmbedding(
|
|
154
|
+
row.text,
|
|
155
|
+
row.title ?? undefined,
|
|
156
|
+
partition.model
|
|
157
|
+
)
|
|
158
|
+
))
|
|
159
|
+
);
|
|
160
|
+
const key = `${row.document_id}:${row.seq}`;
|
|
161
|
+
const previous = owners.get(key);
|
|
162
|
+
if (previous) previous.embedded ||= embedded;
|
|
163
|
+
else
|
|
164
|
+
owners.set(key, {
|
|
165
|
+
collection: row.collection,
|
|
166
|
+
chunk: JSON.stringify([row.mirror_hash, row.seq]),
|
|
167
|
+
embedded,
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
} finally {
|
|
171
|
+
statement.finalize();
|
|
172
|
+
}
|
|
173
|
+
const collections = new Map<string, Map<string, boolean>>();
|
|
174
|
+
let backlog = 0;
|
|
175
|
+
for (const owner of owners.values()) {
|
|
176
|
+
if (!owner.embedded) backlog++;
|
|
177
|
+
let chunks = collections.get(owner.collection);
|
|
178
|
+
if (!chunks) {
|
|
179
|
+
chunks = new Map();
|
|
180
|
+
collections.set(owner.collection, chunks);
|
|
181
|
+
}
|
|
182
|
+
// Distinct collection chunks are ready only when every active owner is.
|
|
183
|
+
chunks.set(
|
|
184
|
+
owner.chunk,
|
|
185
|
+
(chunks.get(owner.chunk) ?? true) && owner.embedded
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
const embeddedByCollection = new Map<string, number>();
|
|
189
|
+
for (const [collection, chunks] of collections) {
|
|
190
|
+
let count = 0;
|
|
191
|
+
for (const embedded of chunks.values()) if (embedded) count++;
|
|
192
|
+
embeddedByCollection.set(collection, count);
|
|
193
|
+
}
|
|
194
|
+
return { backlog, embeddedByCollection };
|
|
195
|
+
})();
|
|
196
|
+
}
|
|
@@ -6,6 +6,8 @@ import type { VectorOwnerInput, VectorVariantIdentity } from "./types";
|
|
|
6
6
|
import { formatDocForEmbedding } from "../../pipeline/contextual";
|
|
7
7
|
import { decodeEmbedding, encodeEmbedding } from "./sqlite-vec";
|
|
8
8
|
|
|
9
|
+
export const SELECTED_VECTOR_PARTITION_PREFIX = "vector_selected_partition:";
|
|
10
|
+
|
|
9
11
|
export function embeddingInputHash(input: string): string {
|
|
10
12
|
return new Bun.CryptoHasher("sha256").update(input).digest("hex");
|
|
11
13
|
}
|
|
@@ -94,6 +96,15 @@ export class VectorVariantStore {
|
|
|
94
96
|
}).immediate();
|
|
95
97
|
}
|
|
96
98
|
|
|
99
|
+
/** Persist only after embedding has resolved the actual runtime identity. */
|
|
100
|
+
selectForEmbedding(): void {
|
|
101
|
+
this.db.run(
|
|
102
|
+
`INSERT INTO schema_meta (key, value) VALUES (?, ?)
|
|
103
|
+
ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = datetime('now')`,
|
|
104
|
+
[SELECTED_VECTOR_PARTITION_PREFIX + this.identity.model, this.partitionId]
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
97
108
|
epoch(): number {
|
|
98
109
|
return this.db
|
|
99
110
|
.query<{ epoch: number }, []>(
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
8dd833b01ebc0a0cb5747de65a9cd60b06cafcf645c2a74d697c918fe7c642d7 gno-browser-clipper-v2.1.0.zip
|