@fangorn-network/westmarch 0.0.1-dev → 0.0.2-dev
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 +2 -2
- package/consume/apps.js +4 -2
- package/consume/corpora.js +1 -1
- package/consume/directory.js +1 -1
- package/consume/embed.js +1 -1
- package/consume/lint.js +1 -1
- package/consume/mcp-bin.js +6 -0
- package/consume/mcp.js +193 -34
- package/consume/ort-preload.js +25 -0
- package/consume/rank.js +1 -1
- package/consume/roles.js +6 -4
- package/consume/shard.js +1 -1
- package/consume/steam.js +1 -1
- package/consume/taste-doc.js +1 -1
- package/consume/taste.js +1 -1
- package/consume/tools.js +28 -5
- package/consume/ui.js +1 -1
- package/consume/view-tools.js +202 -0
- package/consume/x402.js +94 -0
- package/docs/app-to-agent.md +553 -185
- package/package.json +11 -4
- package/publish/agent-card.js +12 -2
- package/publish/cohort.js +1 -1
- package/publish/demand.js +1 -1
- package/publish/enrich.js +1 -1
- package/publish/envelope.js +1 -1
- package/publish/graph.js +1 -1
- package/publish/manifest.js +1 -1
- package/publish/reactions.js +4 -2
- package/publish/settle.js +1 -1
- package/publish/ship.js +378 -0
- package/publish/terms.js +1 -1
- package/publish/vault.js +1 -1
- package/publish/view-cli.js +39 -0
- package/publish/view.js +399 -0
- package/site/agent.js +36 -0
- package/site/dist/assets/__vite-browser-external-CKnVkMRn.js +1 -0
- package/site/dist/assets/embed.worker-CQqmvS52.js +1 -0
- package/site/dist/assets/index-DlzVyQ_9.js +6 -0
- package/site/dist/assets/ort-wasm-simd-threaded.asyncify-DMmc6YqF.wasm +0 -0
- package/site/dist/assets/preload-helper-uBIymjUX.js +1 -0
- package/site/dist/assets/roles-C9V_uDr_.js +1 -0
- package/site/dist/assets/roles-YDRjv_PY.js +1 -0
- package/site/dist/assets/rolldown-runtime-Dd_uD5pT.js +1 -0
- package/site/dist/assets/transformers.web-BgUm7F_-.js +46 -0
- package/site/dist/assets/transformers.web-u9uSoh5E.js +37 -0
- package/site/dist/index.html +20 -0
- package/site/feed.js +62 -0
package/publish/view.js
ADDED
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
// An app's committed data → a view any Fangorn reader loads: the free, local tier.
|
|
2
|
+
//
|
|
3
|
+
// westmarch-view --app my-app --namespace my-app --out site/view
|
|
4
|
+
//
|
|
5
|
+
// Reads the chain and nothing else: each publisher's commits to `namespaces`, and
|
|
6
|
+
// the app owner's schema (`fangorn.schema`, see below). Embeds only records the view
|
|
7
|
+
// does not hold yet, marks removed ones as tombstones, and writes static files:
|
|
8
|
+
//
|
|
9
|
+
// <out>/cdn/catalog
|
|
10
|
+
// <out>/cdn/domains/<domain>/manifest
|
|
11
|
+
// <out>/cdn/domains/<domain>/shards/shard-NNNN-<sha12>.ndjson.gz
|
|
12
|
+
//
|
|
13
|
+
// Stateless: the view's own files are its state, so it runs from a laptop, CI or an
|
|
14
|
+
// agent, and a second run with nothing new on chain changes nothing.
|
|
15
|
+
//
|
|
16
|
+
// THE SAME CONTRACT AS QUICKBEAM (the hosted tier), so an app moves between the two by
|
|
17
|
+
// changing one URL in its card:
|
|
18
|
+
// - URL layout, domain names (`domainFor`, byte-identical to quickbeam's
|
|
19
|
+
// `_domain_for` and the registry's `domainFor`), row shape {track_id, fields, v},
|
|
20
|
+
// manifest and catalog keys;
|
|
21
|
+
// - a record is a vertex: track_id = its CID, fields = payload + entityType (its tag);
|
|
22
|
+
// - the schema: a declared type is embedded and shipped with exactly its role map;
|
|
23
|
+
// - the embedded text (`composeText` mirrors quickbeam's `compose_document_text`).
|
|
24
|
+
// What differs is the encoder: transformers.js q8 here, fastembed fp32 there. Their
|
|
25
|
+
// vectors agree to ~0.95 cosine, close but not interchangeable, so the manifest names
|
|
26
|
+
// its `embedder` and this refuses to append to a view another encoder built.
|
|
27
|
+
//
|
|
28
|
+
// ponytail: flat records. quickbeam folds a record's graph neighbours into its text
|
|
29
|
+
// and can fuse publishers by shared identity; that is the hosted tier's job. Edges
|
|
30
|
+
// are not shipped here either (quickbeam's edges.json) until a reader needs them.
|
|
31
|
+
|
|
32
|
+
import { gunzipSync, gzipSync } from "node:zlib";
|
|
33
|
+
import { createHash } from "node:crypto";
|
|
34
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
35
|
+
import { EMBED_DIM, EMBED_MODEL, embedDocumentDirect, packVec, unpackVec } from "../consume/embed.js";
|
|
36
|
+
import { coverage as fitCoverage } from "./reactions.js";
|
|
37
|
+
|
|
38
|
+
export const SCHEMA_NAMESPACE = "fangorn.schema";
|
|
39
|
+
export const TYPE_TAG = "fangorn.type.v1";
|
|
40
|
+
export const EMBEDDER = { runtime: "transformers.js", model: EMBED_MODEL, dtype: "q8" };
|
|
41
|
+
const SINGULAR = ["identity", "title", "subtitle", "temporal", "spatial", "media"];
|
|
42
|
+
const MULTI = ["tags", "measures", "relations", "text"];
|
|
43
|
+
const ZERO = "0x0000000000000000000000000000000000000000";
|
|
44
|
+
// Refit coverage once a domain has grown this many times over what it was fit on.
|
|
45
|
+
const COVERAGE_REFIT = 2;
|
|
46
|
+
|
|
47
|
+
// ── the contract shared with quickbeam ────────────────────────────────────────
|
|
48
|
+
|
|
49
|
+
const slug = (s) => (String(s ?? "").replace(/[^A-Za-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "").toLowerCase() || "x");
|
|
50
|
+
/** quickbeam `_domain_for` / registry `domainFor`, for an app id. */
|
|
51
|
+
export const domainFor = (appId, owner, namespace) =>
|
|
52
|
+
`${slug(appId.slice(2, 10))}-${slug(owner.slice(2, 10))}-${slug(namespace)}`;
|
|
53
|
+
|
|
54
|
+
/** quickbeam `compose_document_text`, minus the `search_document: ` prefix that
|
|
55
|
+
* `embedDocumentDirect` adds itself. */
|
|
56
|
+
export function composeText(fields, rm) {
|
|
57
|
+
const tags = (rm.tags ?? []).map((t) => (typeof fields[t] === "string" ? fields[t] : "")).join(" ");
|
|
58
|
+
const rels = Object.entries(fields)
|
|
59
|
+
.filter(([k, v]) => Array.isArray(v) && v.length && k !== "entityType")
|
|
60
|
+
.map(([k, v]) => `${k}: ${v.slice(0, 20).filter((x) => x).map(String).join(", ")}`).join("; ");
|
|
61
|
+
const subtitle = fields[rm.subtitle ?? ""] ?? "";
|
|
62
|
+
const text = (rm.text ?? []).filter((t) => fields[t]).map((t) => String(fields[t])).join("; ");
|
|
63
|
+
let s = `Title: ${fields[rm.title ?? ""] ?? ""}. Tags: ${tags}`;
|
|
64
|
+
if (subtitle) s += `. Subtitle: ${subtitle}`;
|
|
65
|
+
if (text) s += `. ${text}`;
|
|
66
|
+
if (rels) s += `. ${rels}`;
|
|
67
|
+
return s.slice(0, 1000 - "search_document: ".length);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** `fangorn.schema` contents → { types: {tag: {description, role_map, presentation}}, relations }. */
|
|
71
|
+
export function parseSchema(contents) {
|
|
72
|
+
const types = {}, tagOf = {};
|
|
73
|
+
for (const v of contents?.vertices ?? []) {
|
|
74
|
+
const p = v.payload ?? {};
|
|
75
|
+
if (v.schemaId !== TYPE_TAG || typeof p.tag !== "string") continue;
|
|
76
|
+
types[p.tag] = { description: String(p.description ?? ""), role_map: p.role_map ?? {}, presentation: p.presentation ?? {} };
|
|
77
|
+
tagOf[v.cid] = p.tag;
|
|
78
|
+
}
|
|
79
|
+
const relations = (contents?.edges ?? []).filter((e) => tagOf[e.sourceCid] && tagOf[e.targetCid])
|
|
80
|
+
.map((e) => ({ rel: e.relation, from: tagOf[e.sourceCid], to: tagOf[e.targetCid] }));
|
|
81
|
+
return { types, relations };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Declared means declared: every role present, left-out ones empty. */
|
|
85
|
+
export function completeRoles(rm = {}) {
|
|
86
|
+
const out = {};
|
|
87
|
+
for (const r of SINGULAR) out[r] = rm[r] ?? null;
|
|
88
|
+
for (const r of MULTI) out[r] = rm[r] == null ? [] : [rm[r]].flat();
|
|
89
|
+
return out;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// ponytail: for a type the schema does not declare, a guess from field names and
|
|
93
|
+
// value lengths — enough to show and search something, and loud about it. quickbeam's
|
|
94
|
+
// `infer_roles` is richer; the fix either way is declaring the type.
|
|
95
|
+
export function guessRoles(rows) {
|
|
96
|
+
const keys = [...new Set(rows.flatMap((r) => Object.keys(r)))].filter((k) => k !== "entityType");
|
|
97
|
+
const find = (names) => keys.find((k) => names.includes(k.toLowerCase())) ?? null;
|
|
98
|
+
const avgLen = (k) => rows.reduce((s, r) => s + (typeof r[k] === "string" ? r[k].length : 0), 0) / (rows.length || 1);
|
|
99
|
+
return completeRoles({
|
|
100
|
+
identity: find(["id", "uid", "key", "slug"]),
|
|
101
|
+
title: find(["title", "name", "label", "headline"]),
|
|
102
|
+
text: keys.filter((k) => avgLen(k) >= 80),
|
|
103
|
+
tags: keys.filter((k) => ["tags", "category", "categories", "genre", "genres", "topics"].includes(k.toLowerCase())),
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** Merge the declared types a domain holds (most common first), like quickbeam's `domain_spec`. */
|
|
108
|
+
export function domainSpec(schema, tags) {
|
|
109
|
+
const decls = tags.map((t) => schema?.types?.[t]).filter(Boolean);
|
|
110
|
+
if (!decls.length) return null;
|
|
111
|
+
const role_map = {};
|
|
112
|
+
for (const d of decls) {
|
|
113
|
+
const c = completeRoles(d.role_map);
|
|
114
|
+
for (const r of SINGULAR) role_map[r] ??= c[r];
|
|
115
|
+
for (const r of MULTI) role_map[r] = [...new Set([...(role_map[r] ?? []), ...c[r]])];
|
|
116
|
+
}
|
|
117
|
+
const presentation = {};
|
|
118
|
+
for (const d of decls) for (const [k, v] of Object.entries(d.presentation ?? {}))
|
|
119
|
+
presentation[k] = v && typeof v === "object" && !Array.isArray(v) ? { ...presentation[k], ...v } : v;
|
|
120
|
+
return { description: decls.map((d) => d.description).filter(Boolean).join(" "), role_map, presentation };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// ── a client that only reads ──────────────────────────────────────────────────
|
|
124
|
+
|
|
125
|
+
/** An SDK client for reading commits: a throwaway key (nothing signs) and no upload
|
|
126
|
+
* credentials. Contents come through an IPFS gateway: FANGORN_IPFS_GATEWAY, else the one
|
|
127
|
+
* in ~/.fangorn/config.json (what the fangorn CLI reads through), else the SDK default.
|
|
128
|
+
* ponytail: the SDK's default is ipfs.io, which no longer serves raw blocks, so on a
|
|
129
|
+
* machine with neither setting reads fail; set FANGORN_IPFS_GATEWAY. */
|
|
130
|
+
export async function readOnlyFangorn() {
|
|
131
|
+
const [{ Fangorn, FangornConfig }, { generatePrivateKey }, { homedir }] = await Promise.all([
|
|
132
|
+
import("@fangorn-network/sdk"), import("viem/accounts"), import("node:os")]);
|
|
133
|
+
let gateway = process.env.FANGORN_IPFS_GATEWAY;
|
|
134
|
+
try { gateway ??= JSON.parse(readFileSync(`${homedir()}/.fangorn/config.json`, "utf8")).pinataGateway || undefined; } catch { /* no CLI config */ }
|
|
135
|
+
if (gateway && !/^https?:\/\//.test(gateway)) gateway = `https://${gateway}`;
|
|
136
|
+
return Fangorn.create({ privateKey: generatePrivateKey(), config: FangornConfig,
|
|
137
|
+
storage: { signedUrl: gateway ? { gateway } : {} } });
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// ── the view on disk ──────────────────────────────────────────────────────────
|
|
141
|
+
|
|
142
|
+
const readJson = (p, dflt) => (existsSync(p) ? JSON.parse(readFileSync(p, "utf8")) : dflt);
|
|
143
|
+
const writeJson = (p, o) => { writeFileSync(`${p}.tmp`, JSON.stringify(o, null, 1)); renameSync(`${p}.tmp`, p); };
|
|
144
|
+
|
|
145
|
+
/** What a domain already serves: its manifest and every row (for tombstones and coverage). */
|
|
146
|
+
function loadDomain(dir) {
|
|
147
|
+
const manifest = readJson(`${dir}/manifest`, null);
|
|
148
|
+
const rows = new Map();
|
|
149
|
+
for (const s of manifest?.shards ?? []) {
|
|
150
|
+
const text = gunzipSync(readFileSync(`${dir}/shards/${s.file}`)).toString("utf8");
|
|
151
|
+
for (const line of text.split("\n")) if (line.trim()) { const r = JSON.parse(line); rows.set(r.track_id, r); }
|
|
152
|
+
}
|
|
153
|
+
return { manifest, rows };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** Every live row's fields, across the view at `out`. */
|
|
157
|
+
export function liveFields(out) {
|
|
158
|
+
const root = `${out}/cdn/domains`;
|
|
159
|
+
if (!existsSync(root)) return [];
|
|
160
|
+
return readdirSync(root).flatMap((d) => {
|
|
161
|
+
const { manifest, rows } = loadDomain(`${root}/${d}`);
|
|
162
|
+
const dead = new Set(manifest?.tombstones ?? []);
|
|
163
|
+
return [...rows.values()].filter((r) => !dead.has(r.track_id)).map((r) => r.fields ?? {});
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Bring `out` up to date with the chain. `fangorn` is an SDK client (reads only).
|
|
169
|
+
* Returns per-domain counts of what changed.
|
|
170
|
+
*/
|
|
171
|
+
export async function publishView({ fangorn, app, namespaces, out, fromBlock = 0n, rebake = false,
|
|
172
|
+
embed = embedDocumentDirect, log = console.log }) {
|
|
173
|
+
if (!namespaces?.length) throw new Error("publishView: name the data namespace(s) to publish");
|
|
174
|
+
fangorn.setAppId(app);
|
|
175
|
+
const appId = fangorn.getAppId();
|
|
176
|
+
const owner = (await fangorn.getAppRegistry().getAppOwner()).toLowerCase();
|
|
177
|
+
let schema = { types: {}, relations: [] };
|
|
178
|
+
if (owner === ZERO) log(`app ${app} is unclaimed, so it has no schema; every type will be guessed`);
|
|
179
|
+
else {
|
|
180
|
+
try { schema = parseSchema((await fangorn.readNamespace(owner, SCHEMA_NAMESPACE)).contents); }
|
|
181
|
+
catch (e) { log(`schema: none read (${e.message}); every type will be guessed`); }
|
|
182
|
+
}
|
|
183
|
+
const declared = Object.keys(schema.types);
|
|
184
|
+
log(`schema: ${declared.length ? declared.join(", ") : "no declared types"}`);
|
|
185
|
+
|
|
186
|
+
const report = {};
|
|
187
|
+
for (const ns of namespaces.filter((n) => n !== SCHEMA_NAMESPACE)) {
|
|
188
|
+
const timelines = await fangorn.appNamespaces({ namespace: ns, fromBlock: BigInt(fromBlock) });
|
|
189
|
+
log(`${ns}: ${timelines.length} publisher(s) since block ${fromBlock}`);
|
|
190
|
+
for (const { owner: publisher } of timelines) {
|
|
191
|
+
const domain = domainFor(appId, publisher, ns);
|
|
192
|
+
report[domain] = await publishDomain({
|
|
193
|
+
fangorn, schema, publisher, ns, domain, dir: `${out}/cdn/domains/${domain}`, rebake, embed, log,
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
writeCatalog(out);
|
|
198
|
+
return report;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
async function publishDomain({ fangorn, schema, publisher, ns, domain, dir, rebake, embed, log }) {
|
|
202
|
+
const { contents } = await fangorn.readNamespace(publisher, ns);
|
|
203
|
+
const records = new Map((contents.vertices ?? []).filter((v) => v.payload && typeof v.payload === "object")
|
|
204
|
+
.map((v) => [v.cid, { ...v.payload, entityType: v.schemaId }]));
|
|
205
|
+
|
|
206
|
+
if (rebake) rmSync(dir, { recursive: true, force: true }); // old shards would stay reachable
|
|
207
|
+
const had = loadDomain(dir);
|
|
208
|
+
const made = had.manifest?.embedder ?? (had.manifest ? { runtime: "fastembed", dtype: "fp32" } : null);
|
|
209
|
+
if (made && JSON.stringify(made) !== JSON.stringify(EMBEDDER))
|
|
210
|
+
throw new Error(`${domain} was embedded by ${made.runtime} ${made.dtype}, not ${EMBEDDER.runtime} ${EMBEDDER.dtype}; ` +
|
|
211
|
+
"vectors from two encoders are not comparable. Rebake it with --rebake, or keep publishing it with the tool that made it.");
|
|
212
|
+
|
|
213
|
+
const dead = new Set(had.manifest?.tombstones ?? []);
|
|
214
|
+
const live = (id) => had.rows.has(id) && !dead.has(id);
|
|
215
|
+
const fresh = [...records.keys()].filter((id) => !live(id));
|
|
216
|
+
const gone = [...had.rows.keys()].filter((id) => live(id) && !records.has(id));
|
|
217
|
+
|
|
218
|
+
// Roles per type: declared exactly, else guessed (and said so).
|
|
219
|
+
const byType = new Map();
|
|
220
|
+
for (const id of fresh) { const t = records.get(id).entityType; byType.set(t, [...(byType.get(t) ?? []), id]); }
|
|
221
|
+
const rolesOf = new Map();
|
|
222
|
+
for (const [t, ids] of byType) {
|
|
223
|
+
if (schema.types[t]) rolesOf.set(t, completeRoles(schema.types[t].role_map));
|
|
224
|
+
else {
|
|
225
|
+
rolesOf.set(t, guessRoles(ids.slice(0, 200).map((id) => records.get(id))));
|
|
226
|
+
log(`${domain}: type ${t} is not in the schema; guessed title=${rolesOf.get(t).title} text=${rolesOf.get(t).text}`);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
let n = 0;
|
|
231
|
+
const lines = [];
|
|
232
|
+
for (const id of fresh) {
|
|
233
|
+
const fields = records.get(id);
|
|
234
|
+
const v = await embed(composeText(fields, rolesOf.get(fields.entityType)));
|
|
235
|
+
lines.push(JSON.stringify({ track_id: id, fields, v: packVec(v) }));
|
|
236
|
+
if (++n % 100 === 0) log(`${domain}: embedded ${n}/${fresh.length}`);
|
|
237
|
+
}
|
|
238
|
+
if (!fresh.length && !gone.length && had.manifest && !rebake) {
|
|
239
|
+
applySpec(dir, had.manifest, schema, had.rows, dead); // a schema change alone still lands
|
|
240
|
+
return { added: 0, removed: 0 };
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
mkdirSync(`${dir}/shards`, { recursive: true });
|
|
244
|
+
const manifest = had.manifest ?? { name: domain, description: "", count: 0, dim: EMBED_DIM, model: EMBED_MODEL,
|
|
245
|
+
distance: "Cosine", embedder: EMBEDDER, filter: { owner: [publisher], namespace: [ns] }, shards: [], tombstones: [] };
|
|
246
|
+
const writeShard = (ls) => {
|
|
247
|
+
const gz = gzipSync(`${ls.join("\n")}\n`, { level: 9 });
|
|
248
|
+
const sha256 = createHash("sha256").update(gz).digest("hex");
|
|
249
|
+
const file = `shard-${String(manifest.shards.length).padStart(4, "0")}-${sha256.slice(0, 12)}.ndjson.gz`;
|
|
250
|
+
writeFileSync(`${dir}/shards/${file}`, gz);
|
|
251
|
+
manifest.shards.push({ file, count: ls.length, bytes: gz.length, sha256 });
|
|
252
|
+
};
|
|
253
|
+
if (lines.length) {
|
|
254
|
+
writeShard(lines);
|
|
255
|
+
for (const l of lines) { const r = JSON.parse(l); had.rows.set(r.track_id, r); dead.delete(r.track_id); }
|
|
256
|
+
}
|
|
257
|
+
for (const id of gone) dead.add(id);
|
|
258
|
+
// A tombstone hides a row from readers, but its shard still serves the bytes. A record
|
|
259
|
+
// retracted for what it said (a name, an address) has to be gone, so any removal
|
|
260
|
+
// rewrites the live rows into one shard and the old files are deleted. No re-embedding:
|
|
261
|
+
// the vectors are already in the rows.
|
|
262
|
+
// ponytail: rewrites the whole domain on every removal; compact past a threshold if domains get big.
|
|
263
|
+
if (dead.size) {
|
|
264
|
+
for (const s of manifest.shards) rmSync(`${dir}/shards/${s.file}`, { force: true });
|
|
265
|
+
manifest.shards = [];
|
|
266
|
+
for (const id of dead) had.rows.delete(id);
|
|
267
|
+
dead.clear();
|
|
268
|
+
writeShard([...had.rows.values()].map((r) => JSON.stringify(r)));
|
|
269
|
+
}
|
|
270
|
+
manifest.tombstones = [...dead].sort();
|
|
271
|
+
manifest.created_at = Math.floor(Date.now() / 1000);
|
|
272
|
+
applySpec(dir, manifest, schema, had.rows, dead, rolesOf);
|
|
273
|
+
log(`${domain}: +${fresh.length} embedded, -${gone.length} removed, ${manifest.count} live`);
|
|
274
|
+
return { added: fresh.length, removed: gone.length };
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/** Counts, types, roles, description, presentation and coverage from what the domain now holds. */
|
|
278
|
+
function applySpec(dir, manifest, schema, rows, dead, guessed = new Map()) {
|
|
279
|
+
const liveRows = [...rows.values()].filter((r) => !dead.has(r.track_id));
|
|
280
|
+
const types = {};
|
|
281
|
+
for (const r of liveRows) types[r.fields?.entityType] = (types[r.fields?.entityType] ?? 0) + 1;
|
|
282
|
+
const tags = Object.entries(types).sort((a, b) => b[1] - a[1]).map(([t]) => t);
|
|
283
|
+
const before = JSON.stringify(manifest);
|
|
284
|
+
|
|
285
|
+
manifest.count = liveRows.length;
|
|
286
|
+
manifest.entity_types = tags.map((t) => ({ type: t, count: types[t] }));
|
|
287
|
+
const spec = domainSpec(schema, tags);
|
|
288
|
+
const fields = [...new Set(liveRows.slice(0, 500).flatMap((r) => Object.keys(r.fields ?? {})))].sort();
|
|
289
|
+
manifest.role_map = { ...(spec?.role_map ?? manifest.role_map ?? guessed.values().next().value ?? completeRoles()), fields };
|
|
290
|
+
if (spec?.description) manifest.description = spec.description;
|
|
291
|
+
if (spec && Object.keys(spec.presentation).length) manifest.presentation = spec.presentation;
|
|
292
|
+
|
|
293
|
+
const cov = manifest.coverage;
|
|
294
|
+
if (liveRows.length && (!cov || liveRows.length >= COVERAGE_REFIT * (cov.fit_at ?? 0))) {
|
|
295
|
+
manifest.coverage = { ...fitCoverage(liveRows.map((r) => unpackVec(r.v))), fit_at: liveRows.length };
|
|
296
|
+
}
|
|
297
|
+
if (JSON.stringify(manifest) !== before || !existsSync(`${dir}/manifest`)) {
|
|
298
|
+
mkdirSync(dir, { recursive: true });
|
|
299
|
+
writeJson(`${dir}/manifest`, manifest);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function writeCatalog(out) {
|
|
304
|
+
const root = `${out}/cdn/domains`;
|
|
305
|
+
mkdirSync(`${out}/cdn`, { recursive: true });
|
|
306
|
+
const domains = existsSync(root) ? readdirSync(root).sort().map((d) => readJson(`${root}/${d}/manifest`, null)).filter(Boolean) : [];
|
|
307
|
+
writeJson(`${out}/cdn/catalog`, {
|
|
308
|
+
generated_at: Math.floor(Date.now() / 1000), embedding_model: EMBED_MODEL,
|
|
309
|
+
domains: domains.map((m) => ({
|
|
310
|
+
name: m.name, description: m.description, count: m.count, dim: m.dim,
|
|
311
|
+
bytes: m.shards.reduce((s, x) => s + x.bytes, 0), shard_count: m.shards.length,
|
|
312
|
+
entity_types: m.entity_types.map((e) => e.type), manifest: `${m.name}/manifest.json`,
|
|
313
|
+
...(m.coverage ? { coverage: m.coverage } : {}),
|
|
314
|
+
})),
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// ── self-check: `node publish/view.js` — a fake chain, a fake embedder, the real reader ──
|
|
319
|
+
if (typeof process !== "undefined" && import.meta.url === `file://${process.argv[1]}` && process.argv[1].endsWith("/view.js")) {
|
|
320
|
+
const { createServer } = await import("node:http");
|
|
321
|
+
const { mkdtempSync } = await import("node:fs");
|
|
322
|
+
const { tmpdir } = await import("node:os");
|
|
323
|
+
const { loadShard, configure } = await import("../consume/shard.js");
|
|
324
|
+
const eq = (a, b, m) => { if (JSON.stringify(a) !== JSON.stringify(b)) throw new Error(`${m}: ${JSON.stringify(a)} != ${JSON.stringify(b)}`); };
|
|
325
|
+
const throws = async (f, re, m) => { try { await f(); } catch (e) { if (re.test(e.message)) return; throw e; } throw new Error(`must throw: ${m}`); };
|
|
326
|
+
|
|
327
|
+
// quickbeam's own outputs, pinned: the contract, not an approximation of it.
|
|
328
|
+
eq(domainFor(`0x${"7e1497af".padEnd(64, "0")}`, "0x55cf6bd2aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "media"), "7e1497af-55cf6bd2-media", "domain name = quickbeam's");
|
|
329
|
+
eq(composeText({ name: "Oak", family: "Fagaceae", description: "big", entityType: "t" },
|
|
330
|
+
completeRoles({ title: "name", tags: ["family"], text: ["description"] })),
|
|
331
|
+
"Title: Oak. Tags: Fagaceae. big", "embedded text = quickbeam's compose_document_text");
|
|
332
|
+
|
|
333
|
+
const APP = `0x${"ab".repeat(32)}`, OWNER = `0x${"11".repeat(20)}`, PUB = `0x${"22".repeat(20)}`;
|
|
334
|
+
const TAG = "shop.item.v1";
|
|
335
|
+
const chain = {
|
|
336
|
+
[`${OWNER}/${SCHEMA_NAMESPACE}`]: { vertices: [{ cid: "t1", schemaId: TYPE_TAG, payload: {
|
|
337
|
+
tag: TAG, description: "Things for a room.", role_map: { identity: "sku", title: "name", text: "about" },
|
|
338
|
+
presentation: { externalUrl: { [TAG]: "https://shop.example/{sku}" } } } }], edges: [] },
|
|
339
|
+
[`${PUB}/shop`]: { vertices: [
|
|
340
|
+
{ cid: "c1", schemaId: TAG, payload: { sku: "a1", name: "Oak table", about: "a solid oak dining table", price: 3 } },
|
|
341
|
+
{ cid: "c2", schemaId: TAG, payload: { sku: "b2", name: "Lamp", about: "brass reading lamp", price: 1 } },
|
|
342
|
+
{ cid: "c3", schemaId: "shop.note.v1", payload: { title: "Note", body: "an undeclared type, with a body long enough to count as its text field" } },
|
|
343
|
+
], edges: [] },
|
|
344
|
+
};
|
|
345
|
+
const fangorn = {
|
|
346
|
+
setAppId() {}, getAppId: () => APP,
|
|
347
|
+
getAppRegistry: () => ({ getAppOwner: async () => OWNER }),
|
|
348
|
+
appNamespaces: async ({ namespace }) => Object.keys(chain).filter((k) => k.endsWith(`/${namespace}`)).map((k) => ({ owner: k.split("/")[0] })),
|
|
349
|
+
readNamespace: async (o, ns) => { const c = chain[`${o}/${ns}`]; if (!c) throw new Error("no such namespace"); return { contents: c }; },
|
|
350
|
+
};
|
|
351
|
+
const embedded = [];
|
|
352
|
+
const embed = async (t) => { embedded.push(t); const v = new Array(EMBED_DIM).fill(0); for (const w of t.split(/\W+/)) if (w) v[w.length % EMBED_DIM] += 1; const n = Math.hypot(...v) || 1; return v.map((x) => x / n); };
|
|
353
|
+
const out = mkdtempSync(`${tmpdir()}/view-`);
|
|
354
|
+
const run = () => publishView({ fangorn, app: APP, namespaces: ["shop"], out, embed, log: () => {} });
|
|
355
|
+
|
|
356
|
+
const d = domainFor(APP, PUB, "shop");
|
|
357
|
+
eq((await run())[d], { added: 3, removed: 0 }, "first run embeds everything");
|
|
358
|
+
if (!embedded.includes("Title: Oak table. Tags: . a solid oak dining table")) throw new Error(`declared roles compose the text: ${embedded}`);
|
|
359
|
+
let m = readJson(`${out}/cdn/domains/${d}/manifest`);
|
|
360
|
+
eq([m.role_map.identity, m.role_map.title, m.role_map.text, m.description], ["sku", "name", ["about"], "Things for a room."], "the schema is applied");
|
|
361
|
+
eq(m.embedder, EMBEDDER, "the manifest names its encoder");
|
|
362
|
+
|
|
363
|
+
eq((await run())[d], { added: 0, removed: 0 }, "nothing new on chain → nothing embedded");
|
|
364
|
+
eq(embedded.length, 3, "and the embedder was not called");
|
|
365
|
+
|
|
366
|
+
chain[`${PUB}/shop`].vertices = [chain[`${PUB}/shop`].vertices[0],
|
|
367
|
+
{ cid: "c2b", schemaId: TAG, payload: { sku: "b2", name: "Lamp", about: "brass reading lamp", price: 2 } }];
|
|
368
|
+
eq((await run())[d], { added: 1, removed: 2 }, "a changed record is a new CID; removed ones are dropped");
|
|
369
|
+
m = readJson(`${out}/cdn/domains/${d}/manifest`);
|
|
370
|
+
eq([m.count, m.shards.length, m.tombstones, readdirSync(`${out}/cdn/domains/${d}/shards`).length], [2, 1, [], 1],
|
|
371
|
+
"a removal compacts: the retracted rows' bytes are no longer served anywhere");
|
|
372
|
+
if (embedded.length !== 4) throw new Error("compaction must not re-embed");
|
|
373
|
+
chain[`${PUB}/shop`].vertices.push({ cid: "c4", schemaId: TAG, payload: { sku: "d4", name: "Rug", about: "wool rug" } });
|
|
374
|
+
await run();
|
|
375
|
+
chain[`${PUB}/shop`].vertices.pop();
|
|
376
|
+
m = readJson(`${out}/cdn/domains/${d}/manifest`);
|
|
377
|
+
eq([m.count, m.shards.length], [3, 2], "an addition alone is a delta shard");
|
|
378
|
+
await run();
|
|
379
|
+
m = readJson(`${out}/cdn/domains/${d}/manifest`);
|
|
380
|
+
|
|
381
|
+
// The real reader over HTTP: digests checked, tombstones honoured, the publisher's id resolves.
|
|
382
|
+
const srv = createServer((q, s) => { try { s.end(readFileSync(`${out}${decodeURIComponent(q.url)}`)); } catch { s.statusCode = 404; s.end(); } });
|
|
383
|
+
await new Promise((ok) => srv.listen(0, ok));
|
|
384
|
+
configure({ rowText: (f) => f.about ?? "" });
|
|
385
|
+
const got = await loadShard(`http://127.0.0.1:${srv.address().port}`);
|
|
386
|
+
srv.close();
|
|
387
|
+
eq(got.map((r) => [r.id, r.price]).sort(), [["c1", 3], ["c2b", 2]], "readers see exactly the live records");
|
|
388
|
+
const { getRow } = await import("../consume/tools.js");
|
|
389
|
+
const { rolesFrom } = await import("../consume/roles.js");
|
|
390
|
+
eq(getRow(got, "b2", rolesFrom([m]))?.id, "c2b", "get by the publisher's own id, not the CID");
|
|
391
|
+
|
|
392
|
+
m.embedder = { runtime: "fastembed", dtype: "fp32" }; writeJson(`${out}/cdn/domains/${d}/manifest`, m);
|
|
393
|
+
await throws(run, /not comparable/, "appending to another encoder's view");
|
|
394
|
+
eq((await publishView({ fangorn, app: APP, namespaces: ["shop"], out, embed, rebake: true, log: () => {} }))[d],
|
|
395
|
+
{ added: 2, removed: 0 }, "--rebake starts the domain over");
|
|
396
|
+
eq(readdirSync(`${out}/cdn/domains/${d}/shards`).length, 1, "and leaves no orphaned shards");
|
|
397
|
+
rmSync(out, { recursive: true, force: true });
|
|
398
|
+
console.log("view.js self-check ok — domain names and embedded text match quickbeam's, the schema is applied, a second run embeds nothing, additions ship as delta shards, removals compact the domain, the real reader sees exactly the live records by the publisher's id, and another encoder's view is refused");
|
|
399
|
+
}
|
package/site/agent.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// The stock page's tools for agents in a browser: the same verbs as fangorn-mcp's data
|
|
2
|
+
// tools, over the rows this page loaded. `ctx` holds them; ship's card captures these.
|
|
3
|
+
import { describe, facet, getRow, search } from "../consume/tools.js";
|
|
4
|
+
|
|
5
|
+
const ok = (o) => ({ content: [{ type: "text", text: JSON.stringify(o) }] });
|
|
6
|
+
const obj = (properties, required = []) => ({ type: "object", properties, required });
|
|
7
|
+
|
|
8
|
+
export function registerAgent(ctx) {
|
|
9
|
+
const mc = document.modelContext;
|
|
10
|
+
if (!mc?.registerTool) return; // most browsers have no WebMCP yet
|
|
11
|
+
mc.registerTool({
|
|
12
|
+
name: "describe",
|
|
13
|
+
description: "What this app holds: its fields and how often each is present. Call it first.",
|
|
14
|
+
inputSchema: obj({}),
|
|
15
|
+
execute: async () => ok(describe(ctx.rows)),
|
|
16
|
+
});
|
|
17
|
+
mc.registerTool({
|
|
18
|
+
name: "search",
|
|
19
|
+
description: "Search by meaning: a question in plain words. Optional equality filters in `where`.",
|
|
20
|
+
inputSchema: obj({ query: { type: "string" }, limit: { type: "number" }, where: { type: "object" } }, ["query"]),
|
|
21
|
+
execute: async ({ query, limit = 10, where }) =>
|
|
22
|
+
ok(search(ctx.rows, query, ctx.roles, { qv: await ctx.queryVector(query), limit, where })),
|
|
23
|
+
});
|
|
24
|
+
mc.registerTool({
|
|
25
|
+
name: "get",
|
|
26
|
+
description: "One record in full, by the id search returns.",
|
|
27
|
+
inputSchema: obj({ id: { type: "string" } }, ["id"]),
|
|
28
|
+
execute: async ({ id }) => ok(getRow(ctx.rows, id, ctx.roles) ?? { error: `no record ${id}` }),
|
|
29
|
+
});
|
|
30
|
+
mc.registerTool({
|
|
31
|
+
name: "count",
|
|
32
|
+
description: "Count the distinct values of a field: the shape of the data before reading it.",
|
|
33
|
+
inputSchema: obj({ field: { type: "string" }, where: { type: "object" }, limit: { type: "number" } }, ["field"]),
|
|
34
|
+
execute: async ({ field, where, limit = 20 }) => ok(facet(ctx.rows, field, { where, limit })),
|
|
35
|
+
});
|
|
36
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{t as e}from"./rolldown-runtime-Dd_uD5pT.js";var t=e(((e,t)=>{t.exports={}}));export default t();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
let e=null;const t=()=>e??=(async()=>{let{pipeline:e}=await import(`./transformers.web-BgUm7F_-.js`);return e(`feature-extraction`,`nomic-ai/nomic-embed-text-v1.5`,{dtype:`q8`})})();function n(e,t=256){let n=e.length,r=0;for(let t=0;t<n;t++)r+=e[t];r/=n;let i=0;for(let t=0;t<n;t++)i+=(e[t]-r)**2;let a=Math.sqrt(i/n+1e-5),o=Array(Math.min(t,n));for(let t=0;t<o.length;t++)o[t]=(e[t]-r)/a;let s=0;for(let e of o)s+=e*e;if(s=Math.sqrt(s),s)for(let e=0;e<o.length;e++)o[e]/=s;return o}async function r(e){return n((await(await t())(`search_query: ${e}`,{pooling:`mean`,normalize:!1})).data)}async function i(e){return n((await(await t())(`search_document: ${e}`,{pooling:`mean`,normalize:!1})).data)}const a=()=>t().then(()=>{});function o(e){let t=new Uint8Array(e.length);for(let n=0;n<e.length;n++){let r=Math.round(Math.max(-1,Math.min(1,e[n]))*127);t[n]=r<0?r+256:r}let n=``;for(let e of t)n+=String.fromCharCode(e);return btoa(n)}function s(e){try{let t=atob(e),n=new Float32Array(t.length);for(let e=0;e<t.length;e++){let r=t.charCodeAt(e);n[e]=(r>127?r-256:r)/127}return n.length?n:null}catch{return null}}function c(e){let t=new Uint8Array(Math.ceil(e.length/8));for(let n=0;n<e.length;n++)e[n]>=0&&(t[n>>3]|=128>>(n&7));let n=``;for(let e of t)n+=String.fromCharCode(e);return btoa(n)}function l(e){try{let t=atob(e.padEnd(Math.ceil(e.length/4)*4,`=`)),n=new Float32Array(t.length*8);for(let e=0;e<n.length;e++)n[e]=t.charCodeAt(e>>3)>>7-(e&7)&1?1:-1;return n.length?n:null}catch{return null}}if(typeof process<`u`&&import.meta.url===`file://${process.argv[1]}`&&process.argv[1].endsWith(`/embed.js`)){let e=n(Array.from({length:768},(e,t)=>Math.sin(t))),t=0;for(let n of e)t+=n*n;if(Math.abs(Math.sqrt(t)-1)>1e-6)throw Error(`matryoshka must L2-normalize`);if(e.length!==256)throw Error(`matryoshka must truncate to 256`);let r=s(o(e));if(r.length!==e.length)throw Error(`pack/unpack changed the dimension`);let i=0,a=0;for(let t=0;t<e.length;t++)i+=e[t]*r[t],a+=r[t]*r[t];let u=i/Math.sqrt(a);if(u<.999)throw Error(`int8 round-trip cosine ${u} — too lossy to rank with`);let d=s(o([-1,-.5,0,.5,1]));if(d[0]>-.99||d[1]>-.49||d[1]<-.51)throw Error(`negative components corrupted: ${[...d]}`);if(s(`!!!not base64!!!`)!==null)throw Error(`malformed vector must return null, not throw`);let f=c(e);if(f.length>48)throw Error(`sign bits must stay link-sized, got ${f.length} chars`);let p=l(f);if(p.length!==e.length)throw Error(`sign round-trip changed the dimension`);for(let t=0;t<e.length;t++)if(Math.sign(p[t])!==(e[t]<0?-1:1))throw Error(`sign flipped at ${t}`);if(c(p)!==f)throw Error(`sign packing must be idempotent`);if(l(`!!!not base64!!!`)!==null)throw Error(`malformed bits must return null, not throw`);console.log(`embed.js self-check ok — matryoshka norm, int8 round-trip, negatives, malformed input, sign bits`)}const u={query:r,document:i};self.onmessage=async({data:{id:e,kind:t,text:n}})=>{try{if(t===`warm`)return await a(),self.postMessage({id:e,vec:null});self.postMessage({id:e,vec:await u[t](n)})}catch(t){self.postMessage({id:e,error:t?.message??String(t)})}};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["./transformers.web-u9uSoh5E.js","./rolldown-runtime-Dd_uD5pT.js","./preload-helper-uBIymjUX.js","./__vite-browser-external-CKnVkMRn.js","./roles-YDRjv_PY.js","./roles-C9V_uDr_.js"])))=>i.map(i=>d[i]);
|
|
2
|
+
import{i as e}from"./rolldown-runtime-Dd_uD5pT.js";import{t}from"./preload-helper-uBIymjUX.js";import{c as n,d as r,f as i,g as a,h as o,m as s,n as c,p as l,r as u,s as d}from"./roles-C9V_uDr_.js";(function(){let e=document.createElement(`link`).relList;if(e&&e.supports&&e.supports(`modulepreload`))return;for(let e of document.querySelectorAll(`link[rel="modulepreload"]`))n(e);new MutationObserver(e=>{for(let t of e)if(t.type===`childList`)for(let e of t.addedNodes)e.tagName===`LINK`&&e.rel===`modulepreload`&&n(e)}).observe(document,{childList:!0,subtree:!0});function t(e){let t={};return e.integrity&&(t.integrity=e.integrity),e.referrerPolicy&&(t.referrerPolicy=e.referrerPolicy),t.credentials=e.crossOrigin===`use-credentials`?`include`:e.crossOrigin===`anonymous`?`omit`:`same-origin`,t}function n(e){if(e.ep)return;e.ep=!0;let n=t(e);fetch(e.href,n)}})();var f=`nomic-ai/nomic-embed-text-v1.5`,p=256,m=null,ee=()=>m??=(async()=>{let{pipeline:e}=await t(async()=>{let{pipeline:e}=await import(`./transformers.web-u9uSoh5E.js`);return{pipeline:e}},__vite__mapDeps([0,1,2]),import.meta.url);return e(`feature-extraction`,f,{dtype:`q8`})})();function h(e,t=p){let n=e.length,r=0;for(let t=0;t<n;t++)r+=e[t];r/=n;let i=0;for(let t=0;t<n;t++)i+=(e[t]-r)**2;let a=Math.sqrt(i/n+1e-5),o=Array(Math.min(t,n));for(let t=0;t<o.length;t++)o[t]=(e[t]-r)/a;let s=0;for(let e of o)s+=e*e;if(s=Math.sqrt(s),s)for(let e=0;e<o.length;e++)o[e]/=s;return o}async function g(e){return h((await(await ee())(`search_query: ${e}`,{pooling:`mean`,normalize:!1})).data)}var _=()=>typeof window<`u`&&typeof Worker<`u`,v=null,te=0,y=new Map;function ne(){return v||(v=new Worker(new URL(new URL(`embed.worker-CQqmvS52.js`,import.meta.url).href,``+import.meta.url),{type:`module`}),v.onmessage=({data:e})=>{let t=y.get(e.id);t&&(y.delete(e.id),e.error?t.reject(Error(e.error)):t.resolve(e.vec))},v.onerror=e=>{for(let t of y.values())t.reject(Error(`embed worker: ${e?.message??`failed to start`}`));y.clear(),v=null},v)}var re=(e,t)=>new Promise((n,r)=>{let i=++te;y.set(i,{resolve:n,reject:r}),ne().postMessage({id:i,kind:e,text:t})}),ie=e=>_()?re(`query`,e):g(e);function ae(e){let t=new Uint8Array(e.length);for(let n=0;n<e.length;n++){let r=Math.round(Math.max(-1,Math.min(1,e[n]))*127);t[n]=r<0?r+256:r}let n=``;for(let e of t)n+=String.fromCharCode(e);return btoa(n)}function b(e){try{let t=atob(e),n=new Float32Array(t.length);for(let e=0;e<t.length;e++){let r=t.charCodeAt(e);n[e]=(r>127?r-256:r)/127}return n.length?n:null}catch{return null}}function oe(e){let t=new Uint8Array(Math.ceil(e.length/8));for(let n=0;n<e.length;n++)e[n]>=0&&(t[n>>3]|=128>>(n&7));let n=``;for(let e of t)n+=String.fromCharCode(e);return btoa(n)}function se(e){try{let t=atob(e.padEnd(Math.ceil(e.length/4)*4,`=`)),n=new Float32Array(t.length*8);for(let e=0;e<n.length;e++)n[e]=t.charCodeAt(e>>3)>>7-(e&7)&1?1:-1;return n.length?n:null}catch{return null}}if(typeof process<`u`&&import.meta.url===`file://${process.argv[1]}`&&process.argv[1].endsWith(`/embed.js`)){let e=h(Array.from({length:768},(e,t)=>Math.sin(t))),t=0;for(let n of e)t+=n*n;if(Math.abs(Math.sqrt(t)-1)>1e-6)throw Error(`matryoshka must L2-normalize`);if(e.length!==p)throw Error(`matryoshka must truncate to ${p}`);let n=b(ae(e));if(n.length!==e.length)throw Error(`pack/unpack changed the dimension`);let r=0,i=0;for(let t=0;t<e.length;t++)r+=e[t]*n[t],i+=n[t]*n[t];let a=r/Math.sqrt(i);if(a<.999)throw Error(`int8 round-trip cosine ${a} — too lossy to rank with`);let o=b(ae([-1,-.5,0,.5,1]));if(o[0]>-.99||o[1]>-.49||o[1]<-.51)throw Error(`negative components corrupted: ${[...o]}`);if(b(`!!!not base64!!!`)!==null)throw Error(`malformed vector must return null, not throw`);let s=oe(e);if(s.length>48)throw Error(`sign bits must stay link-sized, got ${s.length} chars`);let c=se(s);if(c.length!==e.length)throw Error(`sign round-trip changed the dimension`);for(let t=0;t<e.length;t++)if(Math.sign(c[t])!==(e[t]<0?-1:1))throw Error(`sign flipped at ${t}`);if(oe(c)!==s)throw Error(`sign packing must be idempotent`);if(se(`!!!not base64!!!`)!==null)throw Error(`malformed bits must return null, not throw`);console.log(`embed.js self-check ok — matryoshka norm, int8 round-trip, negatives, malformed input, sign bits`)}var x=(e,t,n)=>{let r=0;for(let n=0;n<t.length&&n<e.vector.length;n++)r+=e.vector[n]*t[n];return r/(e.norm*n)},S=e=>{let t=0;for(let n of e)t+=n*n;return Math.sqrt(t)||1};function C(e,t=1){if(e.length<3)return-1/0;let n=e.reduce((e,t)=>e+t,0)/e.length,r=Math.sqrt(e.reduce((e,t)=>e+(t-n)**2,0)/e.length);return r>1e-6?n+t*r:-1/0}function w(e,{limit:t=10,lambda:n=.7,key:r=`s`}={}){if(n>=1||e.length<=1)return e.slice(0,t);let i=e.slice(0,Math.max(t*6,30)),a=[];for(;a.length<t&&i.length;){let e=0,t=-1/0;for(let o=0;o<i.length;o++){let s=0;for(let e of a){if(!i[o].row?.vector||!e.row?.vector)continue;let t=x(i[o].row,e.row.vector,e.row.norm);t>s&&(s=t)}let c=n*i[o][r]-(1-n)*s;c>t&&(t=c,e=o)}a.push(i.splice(e,1)[0])}return a}var ce=e=>e.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`),le=e=>RegExp(`\\b${ce(e)}\\b`);function T(e,t){let n=String(e.text??``).toLowerCase();if(!n)return 0;let r=t.split(/\s+/).filter(Boolean);if(!r.length)return 0;let i=0;for(let e of r)le(e).test(n)&&i++;if(!i)return 0;let a=n.indexOf(t);return Math.min(1,.5*(i/r.length)+(le(t).test(n)?.35:0)+(a>=0?.15*(1-a/n.length):0))}function ue(e,t){let n=e[0],r=0;for(let i of e){let e=T({text:i.text},t);e>r&&(r=e,n=i)}return n}function de(e=[],t){let n=S(t);return e.map(e=>{let r=e.coverage;if(!r?.vectors?.length)return{...e,affinity:null};let i=-1;for(let e of r.vectors){let r=0,a=0;for(let n=0;n<e.length&&n<t.length;n++)r+=e[n]*t[n],a+=e[n]*e[n];let o=r/((Math.sqrt(a)||1)*n);o>i&&(i=o)}return{...e,affinity:i}}).sort((e,t)=>(t.affinity??-1/0)-(e.affinity??-1/0))}if(typeof process<`u`&&import.meta.url===`file://${process.argv[1]}`&&process.argv[1].endsWith(`/rank.js`)){if(T({text:`intoxicated education`},`cat`)!==0)throw Error(`substring matched — word boundaries lost`);if(T({text:`a cat sat`},`cat`)<=0)throw Error(`whole word must match`);let e=T({text:`cold war submarines`},`cold war`),t=T({text:`cold nights, the war ended`},`cold war`);if(!(e>t))throw Error(`phrase bonus lost: ${e} vs ${t}`);if(T({text:`what is c++`},`c++`)===void 0)throw Error(`must not throw on regex metachars`);if(Math.abs(x({vector:[1,0,0,0],norm:1},[1,0],1)-1)>1e-9)throw Error(`cosine must truncate to the query`);if(C([.5,.5,.5,.5])!==-1/0)throw Error(`no spread must admit everything, not nothing`);if(C([.4,.4])!==-1/0)throw Error(`too few rows to have a distribution`);let n=C([.4,.41,.42,.9]);if(!(.9>=n&&.42<n))throw Error(`floor did not separate the outlier: ${n}`);let r=[{text:`opening titles`},{text:`the submarine dives`}];if(ue(r,`submarine`).text!==`the submarine dives`)throw Error(`wrong passage quoted`);if(ue(r,`nothing here`).text!==`opening titles`)throw Error(`must fall back to the first passage`);{let e=(e,t)=>({vector:[e,t],norm:Math.sqrt(e*e+t*t)||1}),t=[{row:{id:`ep1`,...e(1,0)},s:.9},{row:{id:`ep2`,...e(1,.01)},s:.89},{row:{id:`ep3`,...e(1,.02)},s:.88},{row:{id:`other`,...e(0,1)},s:.7}],n=w(t,{limit:2,lambda:1}).map(e=>e.row.id);if(n.join()!==`ep1,ep2`)throw Error(`lambda 1 must be the old behaviour: ${n}`);let r=w(t,{limit:2,lambda:.7}).map(e=>e.row.id);if(r.join()!==`ep1,other`)throw Error(`MMR must not return the same episode twice: ${r}`);if(w(t,{limit:4,lambda:.5})[0].row.id!==`ep1`)throw Error(`the top hit must survive`);if(w([{row:{id:`a`},s:1}],{limit:3}).length!==1)throw Error(`vectorless rows must pass through`)}let i=de([{name:`old`,coverage:null},{name:`far`,coverage:{vectors:[[0,1]]}},{name:`near`,coverage:{vectors:[[1,0]]}}],[1,0]);if(i.map(e=>e.name).join(`,`)!==`near,far,old`)throw Error(`domains misranked: ${i.map(e=>e.name)}`);if(i[2].affinity!==null)throw Error(`an unmeasured domain must be null, not 0`);console.log(`rank.js self-check ok — word boundaries, matryoshka cosine, z-floor, MMR drops near-duplicates without moving the top hit, passage pick, domain affinity`)}var E=null,D=null,O=null,k=new Map,A=new Map,fe=new Map,pe=e=>[e.name,e.path,e.desc].filter(Boolean).join(` `),me=null,he=null;function j({resolveView:e,rowText:t,onManifests:n}={}){e&&(me=e),t&&(pe=t),n&&(he=n)}var M=()=>{k.clear(),A.clear(),fe.clear(),E=null,D=null,O=null};function ge(e){D=e?ve(e):null}var _e=()=>D,ve=e=>String(e).replace(/\/+$/,``).replace(/\/(stream|cdn)$/,``);function N(){return me?E??=Promise.resolve().then(()=>me()).then(e=>e?ve(e):null).catch(e=>{throw E=null,e}):Promise.resolve(null)}function ye(e){let t=(Array.isArray(e)?e:String(e??``).split(`,`)).map(e=>String(e).trim()).filter(Boolean);O=t.length?t:null}var be=e=>!O||O.some(t=>e===t||e.split(`/`).pop()===t||e.startsWith(t)),xe=`no quickbeam view configured — pass a view base, or wire one with configure({ resolveView })`;async function P(e,t){let n=e??D??await N();if(!n)throw Error(xe);let r=A.get(n);r||A.set(n,r=new Set),t&&r.add(t);let i=k.get(n);if(i)return i;let a=Oe(n,e=>{for(let t of r)try{t(e)}catch{}}).finally(()=>A.delete(n)).catch(e=>{throw k.delete(n),e});return k.set(n,a),a}async function F(e){let t=await fetch(e);if(!t.ok)throw Error(`shard: HTTP ${t.status} for ${e}`);return Se(new Uint8Array(await t.arrayBuffer()))}var Se=async e=>e[0]===31&&e[1]===139?await new Response(new Blob([e]).stream().pipeThrough(new DecompressionStream(`gzip`))).text():new TextDecoder().decode(e);function Ce(e,t){let n=e.fields??{},r=typeof e.v==`string`?b(e.v):Array.isArray(e.embedding)?e.embedding:typeof n.embed?.vec==`string`?b(n.embed.vec):null,i=0;if(r)for(let e of r)i+=e*e;return{id:e.track_id,owner:e.owner,...n,text:pe(n,t),vector:r,norm:Math.sqrt(i)||1}}async function we(e){let t=0;for(let n of e)t+=n.length;let n=new Uint8Array(t),r=0;for(let t of e)n.set(t,r),r+=t.length;return[...new Uint8Array(await crypto.subtle.digest(`SHA-256`,n))].map(e=>e.toString(16).padStart(2,`0`)).join(``)}async function Te(e,t,n,r){if(!t)return;if(!r){console.warn(`shard: cannot verify ${e} — the transport decoded it (Content-Encoding), and the manifest digest is over the gzipped bytes`);return}let i=await we(n);if(i!==t)throw Error(`shard: ${e} does not match the digest in its manifest (expected ${t.slice(0,12)}…, got ${i.slice(0,12)}…)`)}async function Ee(e,t,n){let r=await fetch(e);if(!r.ok)throw Error(`shard: HTTP ${r.status} for ${e}`);if(!r.body){let i=new Uint8Array(await r.arrayBuffer());await Te(e,n,[i],i[0]===31&&i[1]===139);for(let e of(await Se(i)).split(`
|
|
3
|
+
`))t(e);return}let i=r.body.getReader(),a=await i.read(),o=a.value??new Uint8Array,s=n?[]:null,c=new ReadableStream({start(e){o.length&&(s?.push(o),e.enqueue(o)),a.done&&e.close()},async pull(e){let{done:t,value:n}=await i.read();t?e.close():(s?.push(n),e.enqueue(n))},cancel(e){return i.cancel(e)}}),l=o[0]===31&&o[1]===139,u=l?c.pipeThrough(new DecompressionStream(`gzip`)):c,d=new TextDecoder,f=``;for(let r=u.getReader();;){let{done:i,value:a}=await r.read();f+=i?d.decode():d.decode(a,{stream:!0});let o=f.split(`
|
|
4
|
+
`);f=i?``:o.pop();for(let e of o)t(e);if(i){f&&t(f),await Te(e,n,s,l);return}}}var De=250;async function Oe(e,t){let n=`${e}/cdn`,{domains:r=[]}=JSON.parse(await F(`${n}/catalog`)),i=r.filter(e=>be(e.name));O&&i.length!==r.length&&console.info(`shard: pulling ${i.length} of ${r.length} domains (allowlist: ${O.join(`,`)})`),O&&!i.length&&console.warn(`shard: allowlist ${O.join(`,`)} matched none of ${r.map(e=>e.name).join(`, `)} — nothing will load`),i.length||console.warn(`shard: quickbeam view ${n} has no baked domains yet — nothing to read until the watcher bakes one.`);let a=new Map,o=new Set,s=await Promise.all(i.map(async e=>{let t=`${n}/domains/${encodeURIComponent(e.name)}`,r=JSON.parse(await F(`${t}/manifest`));for(let e of r.tombstones??[])o.add(e);return{at:t,manifest:r,shards:r.shards??[]}})),c=s.map(e=>e.manifest);fe.set(e,c);try{he?.(c,e)}catch(e){console.warn(`shard: onManifests threw, continuing with the default rowText —`,e)}let l=Date.now(),u=e=>{!t||!e&&Date.now()-l<De||(t([...a.values()]),l=Date.now())};return await Promise.all(s.map(async({at:t,shards:n})=>{for(let r of n)await Ee(`${t}/shards/${r.file}`,t=>{if(!t.trim())return;let n=Ce(JSON.parse(t),e);o.has(n.id)||a.set(n.id,n),u()},r.sha256),u(!0)})),[...a.values()]}function ke(e,t){let n=null,r=!1;return(async()=>{let i=t??D??await N();if(!i||r)return;n=new EventSource(`${i}/stream`);let a=()=>{M(),e()};n.addEventListener(`added`,a),n.addEventListener(`change`,a)})().catch(()=>{}),()=>{r=!0,n?.close()}}async function Ae(e,{url:t}={}){let n=t??D??await N();if(!n)throw Error(xe);let{domains:r=[]}=JSON.parse(await F(`${n}/cdn/catalog`));return de(r,e)}if(typeof process<`u`&&import.meta.url===`file://${process.argv[1]}`&&process.argv[1].endsWith(`/shard.js`)){let{gzipSync:n}=await t(async()=>{let{gzipSync:t}=await import(`./__vite-browser-external-CKnVkMRn.js`).then(t=>e(t.default,1));return{gzipSync:t}},__vite__mapDeps([3,1]),import.meta.url),r=console.warn,i=console.info;console.warn=console.info=()=>{};let a=(e,t,n={},r)=>({track_id:`${e}:${t}`,owner:e,...r?{embedding:r}:{},fields:{entityType:`file`,name:t.split(`/`).pop(),path:t,...n}}),o=e=>e.map(e=>JSON.stringify(e)).join(`
|
|
5
|
+
`),s=`https://registry.test/q/qb_test`,c=e=>{let t=Buffer.from(e),n=Math.max(1,Math.floor(t.length/2));return{ok:!0,headers:new Headers,arrayBuffer:async()=>t,body:new ReadableStream({start(e){e.enqueue(new Uint8Array(t.subarray(0,n))),e.enqueue(new Uint8Array(t.subarray(n))),e.close()}})}},l=async e=>[...new Uint8Array(await crypto.subtle.digest(`SHA-256`,e))].map(e=>e.toString(16).padStart(2,`0`)).join(``),u=async(e,{tombstones:t={},gzipped:r=!0,keep:i=!1,coverage:a={},digest:u=!1,corrupt:d=!1}={})=>{let f=new Map;for(let[r,i]of Object.entries(e)){let e=i.map((e,t)=>`shard-000${t}-${r}.ndjson.gz`),a=new Map(e.map((e,t)=>[e,o(i[t])])),s=u?await Promise.all(e.map(async e=>l(n(Buffer.from(d?a.get(e)+`
|
|
6
|
+
`:a.get(e)))))):e.map(()=>void 0);f.set(r,{manifest:JSON.stringify({name:r,shards:e.map((e,t)=>({file:e,...s[t]?{sha256:s[t]}:{}})),tombstones:t[r]??[]}),bodies:a})}globalThis.fetch=async e=>{let{pathname:t}=new URL(String(e),`https://app.test`);if(t.includes(`/stream/`))return{ok:!1,status:404};if(t.endsWith(`/cdn/catalog`))return c(JSON.stringify({domains:[...f.keys()].map(e=>({name:e,...a[e]?{coverage:a[e]}:{}}))}));let i=t.match(/\/cdn\/domains\/([^/]+)\/manifest$/);if(i)return f.has(i[1])?c(f.get(i[1]).manifest):{ok:!1,status:404};if(i=t.match(/\/cdn\/domains\/([^/]+)\/shards\/([^/]+)$/),i){let e=f.get(i[1])?.bodies.get(i[2]);return e===void 0?{ok:!1,status:404}:c(r?n(Buffer.from(e)):Buffer.from(e))}return{ok:!1,status:404}},i||M(),j({resolveView:()=>`${s}/stream`})},d=async(e,t={})=>u({d0:e},{...t,tombstones:{d0:t.tombstones??[]}}),f=e=>e.map(e=>e.path).sort().join(`,`);if(await d([[a(`0xa`,`a.md`)]]),f(await P())!==`a.md`)throw Error(`resolveView never reached the loader`);if(await d([[a(`0xa`,`plain.md`)]],{gzipped:!1}),f(await P())!==`plain.md`)throw Error(`an already-decoded body must still parse`);if(await d([[a(`0xa`,`signed.md`)]],{digest:!0}),f(await P())!==`signed.md`)throw Error(`a shard matching its digest must load`);await d([[a(`0xa`,`tampered.md`)]],{digest:!0,corrupt:!0});let p=null;try{await P()}catch(e){p=e}if(!p)throw Error(`a shard that does not match its digest must fail the load`);if(!/does not match the digest/.test(p.message))throw Error(`wrong error: ${p.message}`);p=null;try{await P()}catch(e){p=e}if(!p)throw Error(`a failed verification must not leave the rows in the cache`);let m=``;if(console.warn=e=>{m+=e},await d([[a(`0xa`,`unverifiable.md`)]],{digest:!0,gzipped:!1}),f(await P())!==`unverifiable.md`)throw Error(`an unverifiable body must still load`);if(!/cannot verify/.test(m))throw Error(`an unverifiable shard must say so, not pass silently`);if(console.warn=()=>{},await u({aaa:[[a(`0xa`,`a.md`,{desc:`alpha`}),a(`0xa`,`gone.md`,{desc:`retracted`})]],bbb:[[a(`0xb`,`b.md`,{desc:`beta`})]]},{tombstones:{aaa:[`0xa:gone.md`]}}),f(await P())!==`a.md,b.md`)throw Error(`domains must merge and tombstones retract`);for(let e of[`bbb`,[`bbb`],`bb`])if(await u({aaa:[[a(`0xa`,`a.md`)]],bbb:[[a(`0xb`,`b.md`)]]}),ye(e),f(await P())!==`b.md`)throw Error(`allowlist ${JSON.stringify(e)} did not narrow the load`);if(await u({aaa:[[a(`0xa`,`a.md`)]]}),ye(null),f(await P())!==`a.md`)throw Error(`a null allowlist must pull everything`);await d([[a(`0xa`,`a.md`),a(`0xa`,`gone.md`)],[a(`0xa`,`b.md`)]],{tombstones:[`0xa:gone.md`]});{let e=[],t=await P(void 0,t=>e.push(t.length));if(e.length!==2||e[0]!==1||e[1]!==2)throw Error(`must emit per shard, got ${e}`);if(t.length!==2)throw Error(`the final rows must still be whole`);if(e.some(e=>e>2))throw Error(`a tombstoned row must never surface in a partial emit`)}await d([[a(`0xa`,`a.md`)],[a(`0xa`,`b.md`)]]);{let e=[],t=[],n=P(void 0,t=>e.push(t.length)),r=P(void 0,e=>t.push(e.length));if(await Promise.all([n,r]),!e.length)throw Error(`the starting caller must still receive partials`);if(!t.length)throw Error(`a caller that joins an in-flight load must receive partials too`)}if(M(),j({resolveView:()=>`${s}/stream`}),globalThis.fetch=async()=>({ok:!1,status:500}),await P().then(()=>{throw Error(`a 500 must reject`)},()=>{}),await d([[a(`0xa`,`a.md`)]],{keep:!0}),f(await P())!==`a.md`)throw Error(`a failed load poisoned the cache — the next read must retry`);{let e=[];await u({d0:[[a(`0xa`,`a.md`,{blurb:`declared prose`})]]}),j({onManifests:t=>e.push(`manifests:${t[0]?.name}`),rowText:t=>(e.push(`rowText`),t.blurb??``)});let t=await P();if(e[0]!==`manifests:d0`)throw Error(`onManifests must fire first, got ${e}`);if(!e.includes(`rowText`))throw Error(`rowText never ran`);if(t[0].text!==`declared prose`)throw Error(`the manifest-derived rowText did not reach toRow`);if(M(),j({resolveView:()=>`${s}/stream`,onManifests:()=>{throw Error(`boom`)}}),(await P()).length!==1)throw Error(`a throwing onManifests must not fail the load`);j({onManifests:()=>{}})}if(await d([[a(`0xa`,`ep1.md`,{cues:[{text:`the submarine dives`}]})]]),(await P())[0].text.includes(`submarine`))throw Error(`the default rowText must not know about cues`);if(j({rowText:e=>e.cues?.length?e.cues.map(e=>e.text).join(` `):[e.name,e.path,e.desc].filter(Boolean).join(` `)}),M(),j({resolveView:()=>`${s}/stream`}),!(await P())[0].text.includes(`submarine`))throw Error(`a configured rowText must reach toRow`);{let e=new Set;if(j({rowText:(t,n)=>(e.add(n),t.name??``),onManifests:(t,n)=>{e.add(`m:${n}`)}}),M(),j({resolveView:()=>`${s}/stream`}),await P(),!e.has(s))throw Error(`rowText must receive its trimmed view base, saw ${[...e]}`);if(!e.has(`m:${s}`))throw Error(`onManifests must receive its view`);j({rowText:e=>[e.name,e.path,e.desc].filter(Boolean).join(` `),onManifests:()=>{}})}await d([[a(`0xa`,`v.md`,{},[3,4]),a(`0xa`,`packed.md`,{embed:{vec:`fwA=`}}),a(`0xa`,`none.md`)]]);{let e=new Map((await P()).map(e=>[e.path,e]));if(Math.abs(e.get(`v.md`).norm-5)>1e-9)throw Error(`norm must be precomputed from the plain float array`);if(!e.get(`packed.md`).vector)throw Error(`the base64 int8 form a publisher commits must unpack`);if(e.get(`none.md`).vector!==null)throw Error(`a row with no vector must be null, and rank lexically`);if(e.get(`none.md`).norm!==1)throw Error(`a zero norm would divide search by zero`)}{await u({d0:[[{track_id:`p1`,owner:`0xa`,v:`AH+B`,fields:{entityType:`file`,name:`packed.md`,path:`packed.md`}},{track_id:`p2`,owner:`0xa`,embedding:[0,1,-1],fields:{entityType:`file`,name:`old.md`,path:`old.md`}}]]});let e=new Map((await P()).map(e=>[e.path,e])),t=e.get(`packed.md`).vector,n=e.get(`old.md`).vector;if(!t)throw Error("the bake's `v` key must unpack");for(let e=0;e<3;e++)if(Math.abs(t[e]-n[e])>1/127)throw Error(`packed and float forms disagree at ${e}: ${t[e]} vs ${n[e]}`);if(Math.abs(e.get(`packed.md`).norm-e.get(`old.md`).norm)>.02)throw Error(`norm must match across wire forms`)}await u({near:[[a(`0xa`,`a.md`)]],far:[[a(`0xb`,`b.md`)]],old:[[a(`0xc`,`c.md`)]]},{coverage:{near:{vectors:[[1,0]]},far:{vectors:[[0,1]]}}});{let e=await Ae([1,0]);if(e.map(e=>e.name).join(`,`)!==`near,far,old`)throw Error(`suggestDomains misranked: ${e.map(e=>e.name)}`)}await d([[a(`0xa`,`before.md`)]]);{let e=new Map,t=``,n=!1;globalThis.EventSource=class{constructor(e){t=e}addEventListener(t,n){e.set(t,n)}close(){n=!0}};let r=0,i=ke(()=>{r++});if(await new Promise(e=>setTimeout(e,0)),t!==`${s}/stream`)throw Error(`watchShard must open the view's SSE stream, got ${t}`);if(e.has(`snapshot`))throw Error(`snapshot is the connect-time census, not a change`);if(f(await P())!==`before.md`)throw Error(`watchShard fixture never loaded`);if(await d([[a(`0xa`,`after.md`)]],{keep:!0}),e.get(`change`)(),r!==1)throw Error("a `change` event must reach the caller");if(f(await P())!==`after.md`)throw Error("a `change` must drop the cache so the next read re-pulls");if(i(),!n)throw Error(`the unsubscribe must close the stream`)}if(await u({d0:[[a(`0xa`,`home.md`)]],other:[[a(`0xb`,`guest.md`)]]}),await P(),ge(`${s}/cdn`),_e()!==s)throw Error(`setView must trim to the base every route hangs off`);if(ge(null),_e()!==null)throw Error(`setView(null) must go back to the app default`);console.warn=r,console.info=i,console.log(`shard.js self-check ok — resolveView, gzip sniff, shard digests, domain merge + tombstones + allowlist, streaming, late subscriber, cache poisoning, rowText, vectors, suggestDomains, watchShard`)}var je=new Set([`text`,`vector`,`norm`,`id`,`owner`,`embed`]),I=(e,t,n=[])=>{let r=c(e,t,n),i=d(e,t);return i?{...r,url:i}:r};function Me(e){let t=new Map,n=0,r=0;for(let i of e){i.vector&&(n++,r||=i.vector.length);for(let e of Object.keys(i))je.has(e)||i[e]!=null&&i[e]!==``&&t.set(e,(t.get(e)??0)+1)}return{rows:e.length,withVectors:n,vectorDim:r,owners:[...new Set(e.map(e=>e.owner).filter(Boolean))],fields:[...t].sort((e,t)=>t[1]-e[1]).map(([t,n])=>({name:t,on:n,pct:Math.round(100*n/(e.length||1))}))}}var Ne=.15,Pe=new WeakMap,Fe=(e,t)=>{let n=Pe.get(e);return n===void 0&&(n=[s(e,t),i(e,t)??``,...(t.tags??[]).flatMap(t=>a(e[t]??``)),e.text??``].join(` `),Pe.set(e,n)),n};function L(e,t,n,{qv:r=null,limit:i=10,fields:a,where:o,lexBoost:s=Ne}={}){let c=t.trim().toLowerCase();if(!c)return[];let l=r?S(r):1;o&&(e=e.filter(e=>R(e,o)));let u=e.map(e=>r&&e.vector?{r:e,score:x(e,r,l)+(s?s*T({text:Fe(e,n)},c):0),mode:`semantic`}:{r:e,score:T({text:Fe(e,n)},c),mode:`lexical`}),d=u.filter(e=>e.mode===`semantic`).map(e=>e.score),f=Math.min(C(d),d.length?Math.max(...d):1/0);return u.filter(e=>e.mode===`semantic`?e.score>=f:e.score>0).sort((e,t)=>t.score-e.score).slice(0,i).map(({r:e,score:t,mode:r})=>({...I(e,n,a),score:Number(t.toFixed(4)),mode:r}))}var R=(e,t)=>!t||Object.entries(t).every(([t,n])=>a(e[t]??``).some(e=>e.toLowerCase()===String(n).toLowerCase()));function z(e,t,{limit:n=20,where:r}={}){let i=new Map,o=0;for(let n of e){if(!R(n,r))continue;let e=n[t];if(e==null||e===``){o++;continue}for(let t of a(e))i.set(t,(i.get(t)??0)+1)}let s=[...i].sort((e,t)=>t[1]-e[1]);return{field:t,distinct:i.size,missing:o,top:s.slice(0,n).map(([e,t])=>({value:e,count:t}))}}function B(e,t,{type:n,where:r,sort:i,desc:a=!0,limit:s=20,offset:c=0,fields:l}={}){let d=e.filter(e=>(!n||o(e)?.toLowerCase()===String(n).toLowerCase())&&R(e,r)),f=d.length;if(i){let e=e=>e==null||e===``?null:Number(e);d=[...d].sort((t,n)=>{let[r,o]=[t[i],n[i]];if((r==null||r===``)!=(o==null||o===``))return r==null||r===``?1:-1;let[s,c]=[e(r),e(o)],l=Number.isFinite(s)&&Number.isFinite(c)?s-c:String(r??``).localeCompare(String(o??``));return a?-l:l})}return{total:f,offset:c,returned:Math.min(s,Math.max(0,f-c)),collections:n?void 0:u(t,e).map(e=>({type:e.type,plural:e.plural,count:e.count})),rows:d.slice(c,c+s).map(e=>I(e,t,l))}}function Ie(e,t,n,{limit:r=10,fields:i}={}){let a=e.find(e=>e.id===t);if(!a?.vector)return{seed:a?I(a,n,i):null,near:[]};let o=a.norm;return{seed:I(a,n,i),near:e.filter(e=>e.id!==t&&e.vector).map(e=>({r:e,score:x(e,a.vector,o)})).sort((e,t)=>t.score-e.score).slice(0,r).map(({r:e,score:t})=>({...I(e,n,i),score:Number(t.toFixed(4))}))}}function V(e,t,n){if(!t)return null;let r=e.find(e=>e.id===t)??(n?.identity?e.find(e=>String(e[n.identity])===String(t)):null)??e.find(e=>e.path===t||e.name===t||s(e,n)===t);if(!r)return null;let{vector:i,norm:a,...o}=r;return{...o,hasVector:!!i,vectorDim:i?.length??0}}if(typeof process<`u`&&import.meta.url===`file://${process.argv[1]}`&&process.argv[1].endsWith(`/tools.js`)){let{rolesFrom:e}=await t(async()=>{let{rolesFrom:e}=await import(`./roles-YDRjv_PY.js`);return{rolesFrom:e}},__vite__mapDeps([4,5]),import.meta.url),n=(e,t,n)=>({id:e,owner:`0xa`,...t,text:[t.name,t.desc].filter(Boolean).join(` `),vector:n??null,norm:n?S(n):1}),r=[n(`1`,{name:`Duet S01E02`,series:`Duet`,year:`1987`,subject:`['Sitcom', 'Comedy']`,desc:`a forgotten fox sitcom`},[1,0,0]),n(`2`,{name:`Duet S01E03`,series:`Duet`,year:`1987`,subject:`['Sitcom']`,desc:`more of the same sitcom`},[.99,.1,0]),n(`3`,{name:`Atomic Cafe`,series:``,year:`1982`,subject:`['Documentary', 'Cold War']`,desc:`nuclear test footage`},[0,1,0]),n(`4`,{name:`untitled reel`,year:`1954`})],i=e([],r);if(i.title[0]!==`name`)throw Error(`fixture roles wrong: ${i.title}`);let o=Me(r);if(o.rows!==4||o.withVectors!==3||o.vectorDim!==3)throw Error(`describe lost the corpus shape`);let s=o.fields.find(e=>e.name===`series`);if(s.on!==2||s.pct!==50)throw Error(`empty strings must not count as coverage: ${JSON.stringify(s)}`);if(o.fields.some(e=>je.has(e.name)))throw Error(`plumbing fields must not be offered as content`);if(o.owners.join()!==`0xa`)throw Error(`owners lost`);let c=L(r,`sitcom`,i,{qv:[1,0,0],limit:10});if(c[0].id!==`1`||c[0].title!==`Duet S01E02`)throw Error(`semantic: wrong top hit ${c[0]?.id}`);if(c[0].mode!==`semantic`)throw Error(`mode must be reported, not inferred`);if(c.some(e=>e.id===`3`))throw Error(`the z-floor must drop the unrelated row: ${JSON.stringify(c)}`);if(!L([r[0],r[1],r[2]],`sitcom`,i,{qv:[1,0,0]}).length)throw Error(`an exact match must never be filtered out by its own floor`);if(c=L(r,`nuclear`,i,{limit:10}),c[0]?.id!==`3`||c[0].mode!==`lexical`)throw Error(`must degrade to lexical, and say so`);if(L(r,` `,i).length)throw Error(`an empty query must return nothing, not everything`);if(L(r,`untitled reel`,i)[0]?.id!==`4`)throw Error(`a vectorless row must still be searchable lexically`);let l=z(r,`subject`);if(l.top[0].value!==`Sitcom`||l.top[0].count!==2)throw Error(`list-ish values must split: ${JSON.stringify(l.top)}`);if(l.missing!==1)throw Error(`a field absent from a row must be counted missing, not skipped silently`);if(z(r,`year`).distinct!==3)throw Error(`plain values must not be split`);if(a(`Sitcom,Comedy,Fox`).length!==3)throw Error(`a machine-joined list must split`);if(a(`Wilson, Michael G.`).length!==1)throw Error(`a comma followed by a space is prose, not a list`);if(a(`1987`).length!==1)throw Error(`a plain value must stay whole`);let u=z(r,`year`,{where:{subject:`Sitcom`}});if(u.distinct!==1||u.top[0].value!==`1987`)throw Error(`where must narrow the facet: ${JSON.stringify(u.top)}`);if(z(r,`year`,{where:{subject:`sitcom`}}).distinct!==1)throw Error(`where must match case-insensitively`);if(z(r,`year`,{where:{subject:`Sit`}}).distinct!==0)throw Error(`where must match whole values, not substrings`);let d=B(r,i,{limit:2});if(d.total!==4||d.rows.length!==2)throw Error(`browse must page: ${JSON.stringify(d)}`);if(d.collections.length!==0)throw Error(`a typeless corpus must report zero collections, not invent one`);if(B(r,i,{offset:3}).rows.length!==1)throw Error(`offset must page to the tail`);if(B(r,i,{offset:99}).rows.length!==0)throw Error(`an offset past the end must be empty, not wrapped`);let f=r.map(e=>({...e,entityType:e.id===`3`?`Film`:`Episode`})),p=B(f,i,{limit:1}).collections;if(p.map(e=>`${e.type}:${e.count}`).join()!==`Episode:3,Film:1`)throw Error(`collections must count and sort: ${JSON.stringify(p)}`);if(B(f,i,{type:`film`}).total!==1)throw Error(`type must match case-insensitively`);if(B(f,i,{type:`film`}).collections!==void 0)throw Error(`a typed browse is already inside a collection`);if(B(f,i,{type:`Episode`,where:{year:`1987`}}).total!==2)throw Error(`browse must honour where`);let m=[{id:`a`,rating:`9`},{id:`b`,rating:`10`},{id:`c`}],ee=B(m,i,{sort:`rating`}).rows.map(e=>e.id);if(ee.join()!==`b,a,c`)throw Error(`numeric sort + missing-last wrong: ${ee}`);if(B(m,i,{sort:`rating`,desc:!1}).rows[0].id!==`a`)throw Error(`ascending sort ignored`);let h=Ie(r,`1`,i);if(h.near[0].id!==`2`)throw Error(`nearest neighbour missed the near-identical row`);if(h.near.some(e=>e.id===`1`))throw Error(`a row must not be its own neighbour`);if(h.near.some(e=>e.id===`4`))throw Error(`a vectorless row cannot be a neighbour`);if(Ie(r,`4`,i).near.length)throw Error(`a vectorless seed must return nothing, not throw`);if(Ie(r,`nope`,i).seed!==null)throw Error(`an unknown seed must be null, not undefined`);let g=V(r,`3`,i);if(g.name!==`Atomic Cafe`||!g.hasVector||g.vectorDim!==3)throw Error(`getRow lost the record`);if(`vector`in g||`norm`in g)throw Error(`the raw vector must never reach a tool result`);if(V(r,`nope`,i)!==null)throw Error(`an unknown id must be null`);let _=I(n(`x`,{name:`n`,categories:`d`.repeat(500)}),e([{role_map:{title:`name`,tags:[`categories`]}}]));if(_.categories.length>200)throw Error(`brief must truncate a long field`);if(_.title!==`n`)throw Error(`brief must title from the declared field`);{let e=[{id:`a`,name:`cold war thriller`,year:1965,vector:[1,0],norm:1},{id:`b`,name:`cold war comedy`,year:1999,vector:[1,0],norm:1},{id:`c`,name:`beach party`,year:1964,vector:[0,1],norm:1},{id:`d`,name:`surf movie`,year:1966,vector:[0,1],norm:1}],t={title:[`name`],subtitle:[],tags:[],text:[`name`],measures:[],fields:[`year`]};if(!L(e,`cold war`,t,{qv:[1,0],limit:10}).some(e=>e.title===`cold war comedy`))throw Error(`unfiltered search must see every era`);let n=L(e,`cold war`,t,{qv:[1,0],limit:10,where:{year:1965}});if(n.length!==1||n[0].title!==`cold war thriller`)throw Error(`where must narrow a semantic search: ${JSON.stringify(n)}`);if(L(e,`cold war`,t,{qv:[0,1],limit:10,where:{year:1964}})[0]?.title!==`beach party`)throw Error(`the floor must be measured over the filtered rows`)}{let e=[{id:`a`,name:`cold war thriller`,vector:[1,0],norm:1}],t={title:[`name`],subtitle:[],tags:[],text:[`name`],measures:[],fields:[]},n=L(e,`cold war`,t,{qv:[1,0],limit:5});if(!Array.isArray(n))throw Error(`search must return a flat array, not {hits}`);if(n[0].score===void 0||n[0].mode===void 0)throw Error(`search rows carry score+mode`);for(let n of[void 0,null,``])if(V(e,n,t)!==null)throw Error(`getRow(${JSON.stringify(n)}) must be null`)}console.log(`tools.js self-check ok — coverage not schema, semantic/lexical modes + z-floor, list-ish facets + where, neighbours, whole-row fetch, previews truncate`)}var H=e=>({content:[{type:`text`,text:JSON.stringify(e)}]}),U=(e,t=[])=>({type:`object`,properties:e,required:t});function Le(e){let t=document.modelContext;t?.registerTool&&(t.registerTool({name:`describe`,description:`What this app holds: its fields and how often each is present. Call it first.`,inputSchema:U({}),execute:async()=>H(Me(e.rows))}),t.registerTool({name:`search`,description:"Search by meaning: a question in plain words. Optional equality filters in `where`.",inputSchema:U({query:{type:`string`},limit:{type:`number`},where:{type:`object`}},[`query`]),execute:async({query:t,limit:n=10,where:r})=>H(L(e.rows,t,e.roles,{qv:await e.queryVector(t),limit:n,where:r}))}),t.registerTool({name:`get`,description:`One record in full, by the id search returns.`,inputSchema:U({id:{type:`string`}},[`id`]),execute:async({id:t})=>H(V(e.rows,t,e.roles)??{error:`no record ${t}`})}),t.registerTool({name:`count`,description:`Count the distinct values of a field: the shape of the data before reading it.`,inputSchema:U({field:{type:`string`},where:{type:`object`},limit:{type:`number`}},[`field`]),execute:async({field:t,where:n,limit:r=20})=>H(z(e.rows,t,{where:n,limit:r}))}))}function Re(e,t){let n=t=>new Set(e.slice(0,5e3).flatMap(e=>a(e[t]??``))).size;return t.facets?.[0]??t.tags.find(e=>{let t=n(e);return t>=2&&t<=12})??null}function ze(e,t,{facet:r=null,only:a=null,today:o=new Date().toISOString().slice(0,10)}={}){let c=new Map;for(let o of e){let e=String(n(o,t.temporal)??``).slice(0,10);if(!e)continue;let l=r?String(o[r]??``):``;if(a&&l!==a)continue;let u=i(o,t)??``,d=`${l}|${u}|${e}`,f=c.get(d);f||c.set(d,f={key:d,date:e,place:l,label:u,byTitle:new Map});let p=s(o,t).toLowerCase(),m=f.byTitle.get(p);(!m||W(m,t).length<W(o,t).length)&&f.byTitle.set(p,o)}let l=[...c.values()].map(({byTitle:e,...t})=>({...t,items:[...e.values()]}));return{upcoming:l.filter(e=>e.date>o).sort((e,t)=>e.date.localeCompare(t.date)||e.place.localeCompare(t.place)),past:l.filter(e=>e.date<=o).sort((e,t)=>t.date.localeCompare(e.date)||e.place.localeCompare(t.place))}}function W(e,t){let n=s(e,t),r=String(e.text??``),i=r.startsWith(n.replace(/\.$/,``))?r.slice(n.replace(/\.$/,``).length).replace(/^[.\s]+/,``):r;return i===n?``:i.trim()}if(typeof process<`u`&&process.argv[1]?.endsWith(`/feed.js`)){let{rolesFrom:e}=await t(async()=>{let{rolesFrom:e}=await import(`./roles-YDRjv_PY.js`);return{rolesFrom:e}},__vite__mapDeps([4,5]),import.meta.url),n=e([{role_map:{title:`heading`,subtitle:`meeting`,temporal:`date`,tags:[`city`,`body`],text:[`text`]},presentation:{facets:[`city`]}}]),r=(e,t,n,r=n)=>({city:e,date:t,heading:n,meeting:`Board · ${t}`,body:`Board`,text:r}),i=[r(`A`,`2026-01-01`,`Budget`),r(`A`,`2026-01-01`,`Budget`,`Budget. Approved 5-0.`),r(`A`,`2026-01-01`,`Roads`),r(`B`,`2026-02-01`,`Parks`),r(`A`,`2026-03-01`,`Zoning`),{heading:`undated`}],a=(e,t,n)=>{if(JSON.stringify(e)!==JSON.stringify(t))throw Error(`${n}: ${JSON.stringify(e)}`)};a(Re(i,n),`city`,`the declared facet wins`);let{upcoming:o,past:s}=ze(i,n,{facet:`city`,today:`2026-02-15`});a(o.map(e=>e.key),[`A|Board · 2026-03-01|2026-03-01`],`a future date is coming up`),a(s.map(e=>`${e.place}${e.date}`),[`B2026-02-01`,`A2026-01-01`],`past, latest first`),a(s[1].items.map(e=>W(e,n)),[`Approved 5-0.`,``],`one item per title, the one that says what happened`),a(ze(i,n,{facet:`city`,only:`B`,today:`2026-02-15`}).past.length,1,`narrowed to one place`),console.log(`feed.js self-check ok — declared facet, upcoming vs past, one item per title keeping the decision, narrowed by place`)}var G=e=>document.querySelector(e),K={rows:[],roles:r([]),queryVector:e=>ie(e).catch(()=>null)};j({onManifests:e=>{K.roles=r(e)},rowText:e=>l(e,K.roles)});var Be=await fetch(`./.well-known/agent-card.json`).then(e=>e.json()).catch(()=>({}));document.title=Be.name??`Fangorn app`,G(`h1`).textContent=Be.name??`Fangorn app`,G(`#about`).textContent=Be.description??``,K.rows=await P(new URL(`view`,location.href).href),Le(K);var Ve=new Map(K.rows.map(e=>[e.id,e])),q=(e,t={},...n)=>{let r=Object.assign(document.createElement(e),t);return r.append(...n.filter(e=>e!=null&&e!==!1)),r},He=(e,t=220)=>e.length>t?`${e.slice(0,e.lastIndexOf(` `,t))} …`:e,Ue=e=>new Date(`${e}T12:00:00`).toLocaleDateString(void 0,{weekday:`short`,month:`short`,day:`numeric`,year:`numeric`}),J=Re(K.rows,K.roles),Y=null;try{Y=new URL(location.href).searchParams.get(J??``)||null}catch{}var X=J?[...K.rows.reduce((e,t)=>{for(let n of a(t[J]??``))e.set(n,(e.get(n)??0)+1);return e},new Map)].sort((e,t)=>e[0].localeCompare(t[0])):[],We=G(`#chips`),Ge=()=>We.replaceChildren(...[[null,K.rows.length],...X].map(([e,t])=>q(`button`,{type:`button`,className:e===Y?`chip on`:`chip`,title:`${t.toLocaleString()} records`,onclick:()=>{Y=e,Ge(),Ye(),Z.value.trim()&&Z.oninput()}},e??`All`)));X.length>1&&Ge(),G(`#status`).textContent=`${K.rows.length.toLocaleString()} records${X.length>1?` · ${X.length} ${J===`city`?`places`:J}`:``}`;var Z=G(`input`);Z.placeholder=`Search ${K.rows.length.toLocaleString()} records by meaning…`,Z.disabled=!1;var Ke=(e,{where:t=!1}={})=>{let n=d(e,K.roles),r=s(e,K.roles),a=W(e,K.roles);return q(`li`,{},n?q(`a`,{href:n,target:`_blank`,rel:`noopener`},r):q(`span`,{},r),a?q(`p`,{className:`detail`},He(a)):null,t?q(`small`,{},[J&&e[J],i(e,K.roles)].filter(Boolean).join(` · `)):null)},qe=e=>{let t=q(`ul`,{},...e.items.slice(0,5).map(e=>Ke(e))),n=e.items.length>5?q(`button`,{type:`button`,className:`more`,onclick:n=>{t.append(...e.items.slice(5).map(e=>Ke(e))),n.target.remove()}},`${e.items.length-5} more`):null,r=e.label.replace(/\s*·\s*\d{4}-\d\d-\d\d$/,``);return q(`article`,{},q(`header`,{},q(`time`,{dateTime:e.date},Ue(e.date)),q(`h3`,{},[e.place,r].filter(Boolean).join(` — `))),t,n)},Q=G(`#feed`),Je=12;function Ye(){let{upcoming:e,past:t}=ze(K.rows,K.roles,{facet:J,only:Y}),n=(e,t,n)=>t.length?q(`section`,{},q(`h2`,{},e),...t.map(qe),n):null;Q.replaceChildren(...[n(`Coming up`,e.slice(0,8)),n(`Recently`,t.slice(0,Je),t.length>Je?q(`button`,{type:`button`,className:`more`,onclick:()=>{Je+=12,Ye()}},`Show more`):null)].filter(Boolean)),Q.children.length||Q.append(q(`p`,{className:`empty`},`Nothing dated here yet. Search above.`))}var $=G(`#results`);Z.oninput=async()=>{let e=Z.value.trim();if(Q.hidden=!!e,$.hidden=!e,!e)return $.replaceChildren();let t=L(K.rows,e,K.roles,{qv:await K.queryVector(e),limit:30,...Y&&J?{where:{[J]:Y}}:{}});e===Z.value.trim()&&$.replaceChildren(...t.length?t.map(e=>Ke(Ve.get(e.id)??e,{where:!0})):[q(`li`,{className:`empty`},`No matches.`)])},Ye();
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var e=function(e,t){return new URL(e,t).href},t={},n=function(n,r,i){let a=Promise.resolve();if(r&&r.length>0){let n=document.getElementsByTagName(`link`),o=document.querySelector(`meta[property=csp-nonce]`),s=o?.nonce||o?.getAttribute(`nonce`);function c(e){return Promise.all(e.map(e=>Promise.resolve(e).then(e=>({status:`fulfilled`,value:e}),e=>({status:`rejected`,reason:e}))))}function l(e){return import.meta.resolve?import.meta.resolve(e):new URL(e,import.meta.url).href}a=c(r.map(r=>{if(r=e(r,i),r=l(r),r in t)return;t[r]=!0;let a=r.endsWith(`.css`);for(let e=n.length-1;e>=0;e--){let t=n[e];if(t.href===r&&(!a||t.rel===`stylesheet`))return}let o=document.createElement(`link`);if(o.rel=a?`stylesheet`:`modulepreload`,a||(o.as=`script`),o.crossOrigin=``,o.href=r,s&&o.setAttribute(`nonce`,s),document.head.appendChild(o),a)return new Promise((e,t)=>{o.addEventListener(`load`,e),o.addEventListener(`error`,()=>t(Error(`Unable to preload CSS for ${r}`)))})}).filter(e=>e!==void 0))}function o(e){let t=new Event(`vite:preloadError`,{cancelable:!0});if(t.payload=e,window.dispatchEvent(t),!t.defaultPrevented)throw e}return a.then(e=>{for(let t of e||[])t.status===`rejected`&&o(t.reason);return n().catch(o)})};export{n as t};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var e=/^\s*[[(].*[\])]\s*$/,t=/,(?! )/;function n(n){if(Array.isArray(n))return n.map(e=>String(e));let r=String(n??``),i=e.test(r)?r.trim().slice(1,-1):t.test(r)?r:null;return i===null?r===``?[]:[r]:i.split(`,`).map(e=>e.trim().replace(/^['"]|['"]$/g,``)).filter(Boolean)}var r={title:[`title`,`name`,`label`,`headline`,`path`],subtitle:[`subtitle`,`primaryType`,`entityType`,`kind`,`series`,`creator`],text:[`text`,`desc`,`description`,`summary`,`body`,`abstract`]},i=e=>Array.isArray(e)?e.filter(Boolean):e?[e]:[];function a(e=[],t=[]){let n=new Set;for(let e of t)for(let t of Object.keys(e))n.add(t);let a=[],o=[],s=[],c=[],l=[],u=[],d=[],f=[],p=[],m={},h={},g={},_={},v={},y=null,b=null,x=[],S=[],C=null,w=(e,t)=>{for(let n of i(t))n&&!e.includes(n)&&e.push(n)};for(let t of e){let e=t?.role_map??{};w(a,e.title),w(o,e.subtitle),w(s,e.tags),w(c,e.text),w(l,e.measures),w(u,e.spatial),w(d,e.media),w(f,e.temporal),C??=e.identity??null,e.refers?.field&&e.refers?.corpus&&e.refers?.to&&(b??={...e.refers}),Object.assign(m,e.labels??{});for(let t of i(e.fields))n.add(t);let r=t?.presentation??{};Object.assign(h,r.types??{}),Object.assign(g,r.fieldLabels??{}),Object.assign(_,r.externalUrl??{}),Object.assign(v,r.actions??{}),w(p,r.facets),y??=r.launch??null;for(let e of i(t?.entity_types))x.push(e);t?.paywall&&S.push({domain:t.name??null,...t.paywall})}let T=(e,t)=>{e.length||(w(e,t.filter(e=>n.has(e))),w(e,t))};return T(a,r.title),T(o,r.subtitle),T(c,r.text),{title:a,subtitle:o,tags:s,text:c,measures:l,spatial:u,media:d,temporal:f,facets:p,identity:C,labels:m,types:h,fieldLabels:g,externalUrl:_,actions:v,launch:y,refers:b,entityTypes:x,gates:S,fields:[...n].sort(),declared:e.some(e=>e?.role_map)}}var o=e=>{let t=e?.entityType??e?.kind;return t==null||t===``?null:String(t)};function s(e){let t=String(e);return/s$/i.test(t)?t:/(x|z|ch|sh)$/i.test(t)?`${t}es`:/[^aeiou]y$/i.test(t)?`${t.slice(0,-1)}ies`:`${t}s`}function c(e,t=[]){let n=new Map;for(let t of e.entityTypes)n.set(t.type,(n.get(t.type)??0)+(t.count??0));if(!n.size)for(let e of t){let t=o(e);t&&n.set(t,(n.get(t)??0)+1)}return[...n].map(([t,n])=>{let r=e.types[t]??{};return{type:t,count:n,icon:r.icon??null,accent:r.accent??null,singular:r.singular??t,plural:r.plural??s(r.singular??t)}}).sort((e,t)=>t.count-e.count)}function l(e){return(e.gates??[]).map(e=>({domain:e.domain??null,locked:[...e.locked??[]],free:[...e.free??[]],price:String(e.price??`0`),asset:e.asset??`USDC`,resourceId:e.resourceId??null,workerUrl:e.workerUrl??null,files:(e.files??[]).map(e=>({file:e.file,count:e.count,bytes:e.bytes})),unpublished:!e.resourceId}))}var u=(e,t)=>(t.gates??[]).some(t=>(t.locked??[]).includes(e));async function d(e,t){if(!(t>0))return!1;if(t>=1)return!0;let n=await crypto.subtle.digest(`SHA-256`,new TextEncoder().encode(String(e)));return new DataView(n).getUint32(0)<t*2**32}var f=e=>`${(Number(e.price??0)/1e6).toFixed(e.price%1e3?6:2)} ${e.asset??`USDC`}`,p=(e,t=[])=>{for(let n of t){let t=e?.[n];if(t!=null&&t!==``)return t}return null},m=(e,t)=>String(p(e,t.title)??e?.id??`untitled`),h=(e,t)=>{let n=p(e,t.subtitle);return n==null?null:String(n)};function g(e,t){let n=t.text.map(t=>e?.[t]).filter(e=>e!=null&&e!==``);if(n.length)return n.join(` `);if(t.text.some(e=>u(e,t)))return``;let r=p(e,t.title);return r?String(r):``}function _(e,t,r=[]){let i={id:e.id,title:m(e,t)},a=h(e,t);a&&(i.subtitle=a);for(let a of[...t.tags,...t.measures,...r]){let r=e[a];if(r==null||r===``)continue;let o=t.tags.includes(a)?n(r):null;i[a]=o?o.length>6?[...o.slice(0,6),`…+${o.length-6} more`]:o:typeof r==`string`&&r.length>160?`${r.slice(0,160)}…`:r}return i}function v(e,t){let n=t.externalUrl?.[o(e)];if(!n)return null;let r=!1,i=String(n).replace(/\{(\w+)\}/g,(t,n)=>{let i=e?.[n];return i==null||i===``?(r=!0,``):String(i)});return r?null:i}function y(e,t){return(t?.actions?.[o(e)]??[]).map(n=>{let r=[],i=[],a=String(n.href??``).replace(/\{(\w+)\}/g,(n,a)=>{let o=e?.[a];return o!=null&&o!==``?String(o):((u(a,t)?r:i).push(a),``)});return{kind:n.kind??`link`,label:n.label??n.kind??`open`,href:a,locked:r,missing:i}}).filter(e=>e.locked.length||!e.missing.length)}if(typeof process<`u`&&import.meta.url===`file://${process.argv[1]}`&&process.argv[1].endsWith(`/roles.js`)){let e={role_map:{identity:`placeId`,title:`title`,subtitle:`primaryType`,spatial:`locality`,media:`googleMapsUri`,tags:[`categories`],measures:[`rating`,`userRatingCount`],temporal:null,relations:[],text:[`text`,`editorialSummary`],labels:{title:`Title`},fields:[`address`,`phone`,`website`]},presentation:{types:{Business:{icon:`🏪`,singular:`Place`}},externalUrl:{Business:`{googleMapsUri}`,Event:`{ticketUrl}`}},entity_types:[{type:`Event`,count:654},{type:`Business`,count:263}]},t={id:`r1`,entityType:`Business`,title:`Cira's Red, White & Brew Bar`,primaryType:`Bar & Grill`,text:`Bar & Grill in Saint Germain, WI.`,editorialSummary:``,categories:`['Bar', 'Restaurant']`,rating:`4.7`,userRatingCount:`293`,googleMapsUri:`https://maps.google.com/?cid=140971`},r=a([e],[t]);if(!r.declared)throw Error(`a manifest with a role_map is declared, not sniffed`);if(m(t,r)!==`Cira's Red, White & Brew Bar`)throw Error(`declared title lost`);if(h(t,r)!==`Bar & Grill`)throw Error(`declared subtitle lost`);if(g(t,r)!==`Bar & Grill in Saint Germain, WI.`)throw Error(`empty declared text fields must be skipped, not joined as blanks`);{let e={field:`videoPath`,corpus:`archive-films`,to:`path`},t=a([{role_map:{identity:`videoPath`,refers:e}}]).refers;if(t?.corpus!==`archive-films`||t.field!==`videoPath`||t.to!==`path`)throw Error(`a complete reference must survive the manifest`);for(let t of[`field`,`corpus`,`to`]){let n={...e};if(delete n[t],a([{role_map:{refers:n}}]).refers)throw Error(`a reference missing '${t}' must not be honoured`)}if(a([{role_map:{title:`name`}}]).refers!==null)throw Error(`a corpus that declared no reference has none — this is never sniffed`)}if(v(t,r)!==`https://maps.google.com/?cid=140971`)throw Error(`presentation.externalUrl not applied`);if(v({...t,entityType:`Event`},r)!==null)throw Error(`a template with a missing field must be null, not a half-built link`);if(!r.fields.includes(`phone`)||!r.fields.includes(`website`))throw Error(`role_map.fields must reach the facet picker`);if(r.types.Business.icon!==`🏪`)throw Error(`presentation.types lost`);for(let e of[[`Bar`,`Restaurant`],`['Bar', 'Restaurant']`,`Bar,Restaurant`]){let n=_({...t,categories:e},r).categories;if(!Array.isArray(n)||n.join()!==`Bar,Restaurant`)throw Error(`tags must normalize: ${JSON.stringify(e)} → ${JSON.stringify(n)}`)}if(n(`Wilson, Michael G.`).length!==1)throw Error(`a comma followed by a space is prose, not a list`);if(n(``).length!==0)throw Error(`an empty value is no values, not one empty one`);if(Array.isArray(_(t,r).rating))throw Error(`a measure must stay scalar`);let i=_({...t,categories:Array.from({length:50},(e,t)=>`c${t}`)},r);if(i.categories.length!==7)throw Error(`long tag lists must truncate: ${i.categories.length}`);if(!String(i.categories.at(-1)).includes(`+44 more`))throw Error(`truncation must say how much it dropped`);if(_({...t,categories:[`a`,`b`]},r).categories.length!==2)throw Error(`a short list must pass through whole`);let p=_(t,r);if(p.title!==t.title||p.rating!==`4.7`||!p.categories)throw Error(`brief must carry declared tags + measures: ${JSON.stringify(p)}`);if(`googleMapsUri`in p)throw Error(`brief must not dump undeclared fields`);let b=a([{...e,name:`places`,paywall:{free:[`title`,`primaryType`],locked:[`text`,`phone`],price:`50000`,asset:`USDC`,resourceId:`0xdead`,workerUrl:`https://w`,files:[{file:`locked-0000-abc.ndjson.gz`,count:917,bytes:1234}]}}]),[x]=l(b);if(x.locked.join()!==`text,phone`)throw Error(`the locked field list must survive`);if(x.unpublished)throw Error(`a gate with a resourceId is buyable`);if(f(x)!==`0.05 USDC`)throw Error(`price must render as USDC, got ${f(x)}`);if(!u(`phone`,b)||u(`title`,b))throw Error(`isLocked must read the manifest`);if([x]=l(a([{...e,paywall:{locked:[`text`],price:`1`}}])),!x.unpublished)throw Error(`no resourceId means there is nothing to buy yet`);if(l(a([e])).length)throw Error(`an ungated view must report no gates`);if(u(`text`,a([e])))throw Error(`nothing is locked in an ungated view`);for(let[e,t]of[[`row-47`,!0],[`row-169`,!0],[`row-0`,!1],[`row-3`,!1],[`row-19`,!1]])if(await d(e,.01)!==t)throw Error(`inSample must match the bake: ${e}`);if(await d(`row-0`,0)!==!1||await d(`row-0`,1)!==!0)throw Error(`rate 0 gives nothing away, rate 1 gives everything`);let S=0;for(let e=0;e<2e3;e++)await d(`row-${e}`,.01)&&S++;if(S!==19)throw Error(`the bake picks exactly 19 of these 2000 — got ${S}`);let C=a([{...e,name:`places`,role_map:{...e.role_map,text:[`text`]},paywall:{free:[`title`],locked:[`text`],price:`1`}}]);if(g({title:`Cira's Bar`},C)!==``)throw Error(`a locked text role must read empty, not as the title`);if(g({title:`Cira's Bar`,text:`A bar.`},C)!==`A bar.`)throw Error(`a sampled row's real text must still win`);if(g({title:`Cira's Bar`},a([e]))!==`Cira's Bar`)throw Error(`the title fallback still stands when nothing is gated`);let w=c(a([e]));if(w.map(e=>e.type).join()!==`Event,Business`)throw Error(`collections must sort by count: ${w.map(e=>e.type)}`);if(w[1].icon!==`🏪`||w[1].singular!==`Place`)throw Error(`presentation.types must reach the collection`);if(w[1].plural!==`Places`)throw Error(`plural must derive from the declared singular: ${w[1].plural}`);if(w[0].singular!==`Event`||w[0].icon!==`🎫`&&w[0].icon!==null)throw Error(`an undescribed type must still be a collection`);if(w=c(a([{role_map:{title:`title`}}]),[t,t,{id:`e`,entityType:`Event`}]),w[0].type!==`Business`||w[0].count!==2)throw Error(`row counting fallback wrong: ${JSON.stringify(w)}`);for(let[e,t]of[[`subtitles`,`subtitles`],[`series`,`series`],[`video`,`videos`],[`folder`,`folders`],[`box`,`boxes`],[`category`,`categories`],[`day`,`days`]])if(s(e)!==t)throw Error(`pluralize(${e}) = ${s(e)}, want ${t}`);if(o({kind:`video`})!==`video`)throw Error("the older `kind` spelling is still on-chain");if(o({})!==null)throw Error(`a typeless row must be null, not 'undefined'`);let T={name:`videos`,shards:[]},E={id:`v1`,name:`S01E02 - Overture.mp4`,path:`classic_tv/Duet/S01E02.mp4`,kind:`video`,desc:`A forgotten Fox sitcom.`};if(r=a([T],[E]),r.declared)throw Error(`a manifest with no role_map must report itself as sniffed`);if(m(E,r)!==`S01E02 - Overture.mp4`)throw Error(`sniff must prefer name over path: ${m(E,r)}`);if(r.title[0]!==`name`)throw Error(`a field the sample HAS must outrank one it does not`);if(h(E,r)!==`video`)throw Error(`sniff must find a subtitle`);if(!g(E,r).includes(`forgotten Fox`))throw Error(`sniff must find the prose`);if(m({id:`x`},a([],[]))!==`x`)throw Error(`a title must always fall back to the id`);if(g({id:`x`},a([],[]))!==``)throw Error(`no prose and no title must be empty, not 'undefined'`);let D=a([],[]);if(!D.text.includes(`desc`)||!D.title.includes(`name`))throw Error(`a blind sniff must still offer candidates`);if(g(E,D)!==`A forgotten Fox sitcom.`)throw Error(`a row parsed before any sample must still get its prose`);if(m({id:`z`,title:`declared later`},D)!==`declared later`)throw Error(`a blind sniff must resolve per row`);if(r=a([e,{role_map:{title:`name`,text:[`body`]}}]),m({name:`a note`},r)!==`a note`)throw Error(`the second domain's title field must resolve too`);if(m(t,r)!==t.title)throw Error(`the first domain's title must still resolve`);if(!r.text.includes(`text`)||!r.text.includes(`body`))throw Error(`text fields must union across domains`);if(r.title.includes(`label`))throw Error(`a declared title must not be widened by guesses`);{let e=a([{name:`films`,role_map:{identity:`identifier`,title:`name`,text:[`desc`]},paywall:{free:[`identifier`,`name`],locked:[`path`],price:`50000`,asset:`USDC`},presentation:{actions:{video:[{kind:`video`,label:`Play`,href:`https://cdn.test/{path}`},{kind:`link`,label:`On archive.org`,href:`https://archive.org/details/{identifier}`},{kind:`link`,label:`Dead`,href:`https://x.test/{nosuchfield}`}]}}}]),t=y({entityType:`video`,identifier:`the-temp`,name:`temp.mp4`},e);if(t.length!==2)throw Error(`a template naming a field the bundle never had must drop: ${JSON.stringify(t)}`);if(!t.find(e=>e.kind===`video`).locked.includes(`path`))throw Error(`a player whose url is behind the paywall must say which field is locked`);let n=t.find(e=>e.kind===`link`);if(n.href!==`https://archive.org/details/the-temp`)throw Error(`free fields must interpolate: ${n.href}`);if(n.locked.length)throw Error(`a fully resolved action is not locked`);if(y({entityType:`video`,identifier:`the-temp`,name:`temp.mp4`,path:`a/b.mp4`},e).find(e=>e.kind===`video`).href!==`https://cdn.test/a/b.mp4`)throw Error(`an unlocked player must resolve`);if(y({entityType:`video`},a([])).length)throw Error(`no declaration, no actions`)}console.log(`roles.js self-check ok — declared role_map wins, sniffed fallback, fused domains merge, external links, declared actions resolve and name their locked fields, a cross-corpus reference is honoured only when whole and never sniffed, title never undefined`)}export{d as a,p as c,a as d,h as f,n as g,o as h,l as i,s as l,m,_ as n,u as o,g as p,c as r,v as s,y as t,f as u};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{d as e}from"./roles-C9V_uDr_.js";export{e as rolesFrom};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,t)=>()=>(t||(e((t={exports:{}}).exports,t),e=null),t.exports),s=(e,n)=>{let r={};for(var i in e)t(r,i,{get:e[i],enumerable:!0});return n||t(r,Symbol.toStringTag,{value:`Module`}),r},c=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},l=(n,r,o)=>(o=n==null?{}:e(i(n)),c(r||!n||!n.__esModule||!a.call(n,`default`)?t(o,`default`,{value:n,enumerable:!0}):o,n)),u=(e=>typeof require<`u`?require:typeof Proxy<`u`?new Proxy(e,{get:(e,t)=>(typeof require<`u`?require:e)[t]}):e)(function(e){if(typeof require<`u`)return require.apply(this,arguments);throw Error('Calling `require` for "'+e+"\" in an environment that doesn't expose the `require` function. See https://rolldown.rs/in-depth/bundling-cjs#require-external-modules for more details.")});export{l as i,s as n,u as r,o as t};
|