@konneal/engine 0.2.2 → 0.2.3

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.
Files changed (42) hide show
  1. package/dist/ask-YCDXMIZ4.js +12 -0
  2. package/dist/{chunk-LNSDBEKS.js → chunk-5MBWE7WD.js} +29 -4
  3. package/dist/{chunk-Q6LI4T7M.js → chunk-ADXV2DPK.js} +1 -1
  4. package/dist/{chunk-WGXATDXY.js → chunk-DBBGOOMZ.js} +1 -1
  5. package/dist/{chunk-SN3ANQ3Y.js → chunk-EFQALN2Z.js} +2 -2
  6. package/dist/{chunk-PVHSBXZD.js → chunk-OGFH3RDM.js} +56 -21
  7. package/dist/{chunk-6GOSMLRH.js → chunk-RLT4W2VX.js} +28 -5
  8. package/dist/{chunk-VJZLVU3S.js → chunk-THSHLUOS.js} +9 -4
  9. package/dist/{chunk-Q327B27J.js → chunk-TJRTVJW5.js} +13 -2
  10. package/dist/modelplane.d.ts +44 -1
  11. package/dist/profile.gen.d.ts +9 -0
  12. package/dist/prompts/system.md +1 -0
  13. package/dist/requestScope.d.ts +25 -4
  14. package/dist/search-7TO2RWQE.js +12 -0
  15. package/dist/selfquery.d.ts +6 -0
  16. package/dist/worker_mcp/src/index.js +3 -3
  17. package/dist/worker_public/src/config.js +2 -2
  18. package/dist/worker_public/src/index.js +13 -10
  19. package/dist/worker_public/src/profile.js +1 -1
  20. package/dist/worker_public/src/refusal.js +2 -2
  21. package/dist/worker_public/src/requestScope.js +11 -5
  22. package/docs/spec-pipeline.md +1 -0
  23. package/package.json +1 -1
  24. package/profile/prompts.yaml +14 -0
  25. package/profile/sources.yaml +9 -0
  26. package/workers/shared/chunk.ts +7 -0
  27. package/workers/worker_internal/src/index.ts +3 -3
  28. package/workers/worker_public/prompts/system.md +1 -0
  29. package/workers/worker_public/src/ask.ts +33 -10
  30. package/workers/worker_public/src/index.ts +2 -1
  31. package/workers/worker_public/src/modelplane.ts +94 -4
  32. package/workers/worker_public/src/pipeline.ts +13 -5
  33. package/workers/worker_public/src/profile.gen.ts +13 -2
  34. package/workers/worker_public/src/requestScope.ts +49 -7
  35. package/workers/worker_public/src/search.ts +7 -1
  36. package/workers/worker_public/src/selfquery.ts +26 -0
  37. package/workers/worker_public/src/stages/citationProbe.ts +6 -1
  38. package/workers/worker_public/src/stages/index.ts +2 -0
  39. package/workers/worker_public/src/stages/licenseScope.ts +23 -0
  40. package/workers/worker_public/src/stages/types.ts +11 -0
  41. package/dist/ask-VFSAF5WG.js +0 -12
  42. package/dist/search-OMPBMZT4.js +0 -11
@@ -1,10 +1,12 @@
1
1
  // The per-request scope model (MECE: ask.ts orchestrates, this module
2
2
  // owns the derivation): which DATASETS the request searches (the sidebar
3
- // toggles, server-intersected with session permissions) and which
4
- // memory files ride it — plus the answer-cache SALT both selections
3
+ // toggles, server-intersected with session permissions), which memory
4
+ // files ride it, and which LICENSED standards the caller's organization
5
+ // is entitled to — plus the answer-cache SALT all three selections
5
6
  // produce. Pure: D1 access stays with the caller (memoryNote); this
6
7
  // module only derives.
7
8
  import { DATASETS, datasetAllowed } from "./config.ts";
9
+ import { P } from "./profile.ts";
8
10
 
9
11
  export interface RequestScope {
10
12
  /** dataset ids the request may search */
@@ -18,6 +20,37 @@ export interface RequestScope {
18
20
  isoOn: boolean;
19
21
  /** raw (validated) memory ids from the body — empty for anon */
20
22
  memoryIds: string[];
23
+ /** raw (validated) license entitlement keys from the body — empty for
24
+ * anon and for requests that carry none (TODO.external-refs/08) */
25
+ standardKeys: Set<string>;
26
+ }
27
+
28
+ /** The deployment's declared licensed standards (profile sources.yaml
29
+ * `licensed:` — key/package/doc_number rows). Empty = the deployment
30
+ * serves public content only and the entitlement scope is inert. */
31
+ export function licenseDeclared(): boolean {
32
+ return (P().sources?.licensed?.length ?? 0) > 0;
33
+ }
34
+
35
+ /** The request's entitlement set, VALIDATED against the declared
36
+ * whitelist: unknown keys drop (a forged key can never widen scope past
37
+ * the standards the deployment actually keys). Absent field = empty set
38
+ * — the fail-closed default for a deployment that declares licensed
39
+ * content. */
40
+ export function standardKeysFrom(body: any): Set<string> {
41
+ const declared = new Set<string>((P().sources?.licensed ?? []).map((l: any) => String(l.key)));
42
+ const raw = Array.isArray(body?.licensed_standards) ? body.licensed_standards : [];
43
+ return new Set(
44
+ raw.filter((x: unknown): x is string => typeof x === "string" && declared.has(x)),
45
+ );
46
+ }
47
+
48
+ /** The RetrieveOptions value for the hard scope: null when the
49
+ * deployment declares no licensed content (inert — zero behavior
50
+ * change); otherwise the caller's validated set, EMPTY INCLUDED (the
51
+ * unentitled caller: licensed chunks hidden, citation metadata stays). */
52
+ export function entitlementScope(keys: Set<string>): Set<string> | null {
53
+ return licenseDeclared() ? keys : null;
21
54
  }
22
55
 
23
56
  /** Validate + intersect. Returns { error } when the request explicitly
@@ -54,18 +87,27 @@ export function resolveRequestScope(body: any, member: unknown): RequestScope |
54
87
  // federation flag: any session-gated (federated) dataset in scope
55
88
  isoOn: scopeIds.some((id) => DATASETS().find((x) => x.id === id)?.session === true),
56
89
  memoryIds,
90
+ standardKeys: standardKeysFrom(body),
57
91
  };
58
92
  }
59
93
 
60
94
  /** The answer-cache salt: request-scoped context that materially changes
61
- * the answer (dataset scope, memory selection). Requests differing only
62
- * in salt share query text — an unsalted key would serve a scoped (or
63
- * memory-flavored) answer to a plain ask. Null = default scope, no
64
- * memory: keys stay byte-identical to the pre-salt era. */
95
+ * the answer (dataset scope, memory selection, license entitlements —
96
+ * the licensed tier changes the grounding, so two callers asking the
97
+ * same question must never share an entry). Requests differing only in
98
+ * salt share query text — an unsalted key would serve a scoped (or
99
+ * memory-flavored, or licensed-tier) answer to a plain ask. Null =
100
+ * default scope, no memory, no entitlement effect: keys stay
101
+ * byte-identical to the pre-salt era. */
65
102
  export function requestSalt(scope: RequestScope, memoryUsed: string[]): string | null {
66
- if (!scope.narrowed && !memoryUsed.length) return null;
103
+ const licensed = licenseDeclared();
104
+ if (!scope.narrowed && !memoryUsed.length && !licensed) return null;
67
105
  return JSON.stringify({
68
106
  ...(scope.narrowed ? { d: [...scope.corpora].sort() } : {}),
69
107
  ...(memoryUsed.length ? { m: [...memoryUsed].sort() } : {}),
108
+ // the entitlement set rides whenever the deployment keys content at
109
+ // all — an unentitled ask and an entitled ask of the same text are
110
+ // different answers even when the set is empty
111
+ ...(licensed ? { s: [...scope.standardKeys].sort() } : {}),
70
112
  });
71
113
  }
@@ -4,6 +4,7 @@ import { portModelRunner } from "./env.ts";
4
4
  import type { Background } from "./ports/runtime.ts";
5
5
  import { retrieve } from "./pipeline";
6
6
  import { understandQuery } from "./understand";
7
+ import { entitlementScope, standardKeysFrom } from "./requestScope";
7
8
  import { sessionFrom } from "./auth";
8
9
  import { err, json, corsHeaders, readJson, validateQuery, type ApiKey } from "./lib/http";
9
10
  import { checkQuota, clientIp, telemetry } from "./quota";
@@ -32,9 +33,14 @@ export async function handleSearch(
32
33
 
33
34
  const understanding = await understandQuery(portModelRunner(env), MODELS.understand, q.query, []);
34
35
  const graphDocNumbers = await graphExpand(env, understanding);
36
+ // the license entitlement set rides the body exactly as it does on the
37
+ // ask path (TODO.external-refs/08): request-scoped, whitelist-validated,
38
+ // fail-closed — a retrieval with an empty set returns zero licensed
39
+ // chunks at the transport level
40
+ const standardKeys = entitlementScope(standardKeysFrom(body));
35
41
  let retrieved;
36
42
  try {
37
- retrieved = await retrieve(env, q.query, { understanding, graphDocNumbers });
43
+ retrieved = await retrieve(env, q.query, { understanding, graphDocNumbers, standardKeys });
38
44
  } catch {
39
45
  return err(503, "retrieval_unavailable", "Search is briefly busy — please retry in a moment.");
40
46
  }
@@ -23,3 +23,29 @@ export function toVectorizeFilter(f: QueryFilters): Record<string, string> | und
23
23
  }
24
24
  return undefined;
25
25
  }
26
+
27
+ // ── the license entitlement scope (TODO.external-refs/08) ────────────────
28
+ // A chunk's `standard_key` metadata carries the licensed package's
29
+ // entitlement key; public content carries none. The scope is a HARD
30
+ // FILTER (a hit never reaches ranking when its key is outside the
31
+ // caller's set) — but it deliberately does NOT translate into a
32
+ // Vectorize metadata predicate: the wire has no "field missing OR in-set"
33
+ // operator, so a `standard_key $in […]` push-down would exclude every
34
+ // public chunk (they predate the field). The scope binds pool-level
35
+ // (stages/licenseScope.ts, before rerank) and at the lexical lane's
36
+ // source (pipeline.ts, the sealScope posture) instead; the single-doc
37
+ // post-seal fetches (section-descent, typed-pin parent, edition-cover)
38
+ // are doc-scoped and a document's key is a per-document constant, so
39
+ // they cannot re-admit a dropped key.
40
+
41
+ /** The one entitlement predicate: no key = public = always allowed; a key
42
+ * outside the caller's set = never allowed. `null`/undefined keys (the
43
+ * deployment declares no licensed content) disable the scope entirely. */
44
+ export function standardKeyAllowed(
45
+ meta: { standard_key?: string },
46
+ keys: ReadonlySet<string> | null | undefined,
47
+ ): boolean {
48
+ if (!keys) return true;
49
+ const k = meta.standard_key;
50
+ return !k || keys.has(k);
51
+ }
@@ -14,6 +14,7 @@
14
14
  import type { Stage } from "./types.ts";
15
15
  import { namedDocumentIn } from "../context.ts";
16
16
  import { refCodec } from "../codecs.ts";
17
+ import { standardKeyAllowed } from "../selfquery.ts";
17
18
  import type { Hit } from "../../../shared/chunk.ts";
18
19
 
19
20
  const CITE_PATTERN = /\b(?:cite[sd]?|citing|referenc(?:e|es|ed|ing)|list[s]?|quote[sd]?)\b/i;
@@ -123,9 +124,13 @@ export const citationProbe: Stage = {
123
124
  const [probes, citeRows] = (await c.lane["citation-probe"]) as [Hit[], CiteRow[]];
124
125
  const seen = new Set(c.hits.map((m: any) => m.id));
125
126
  let added = 0;
126
- // only bibliography-shaped chunks (clause title or text mentions it)
127
+ // only bibliography-shaped chunks (clause title or text mentions it);
128
+ // the push rides the entitlement predicate — the probe runs past the
129
+ // pool-level license scope, and a licensed family's bibliography
130
+ // passages must obey the same hard scope as everything else
127
131
  for (const h of probes) {
128
132
  if (seen.has(h.id as any)) continue;
133
+ if (!standardKeyAllowed(h.metadata as any, c.opts.standardKeys)) continue;
129
134
  const title = String((h.metadata as any)?.clause_title ?? "");
130
135
  const text = String(h.text ?? "");
131
136
  if (/bibliograph|normative reference/i.test(title + " " + text.slice(0, 300))) {
@@ -23,6 +23,7 @@ import { poolOpen } from "./poolOpen.ts";
23
23
  import { lexicalUnion } from "./lexicalUnion.ts";
24
24
  import { federate } from "./federate.ts";
25
25
  import { seal } from "./seal.ts";
26
+ import { licenseScope } from "./licenseScope.ts";
26
27
  import { corpusScope } from "./corpusScope.ts";
27
28
  import { editionCover } from "./editionCover.ts";
28
29
  import { stdRefNudge } from "./stdRefNudge.ts";
@@ -51,6 +52,7 @@ export const STAGES: Stage[] = [
51
52
  lexicalUnion,
52
53
  federate,
53
54
  seal,
55
+ licenseScope,
54
56
  overviewDemote,
55
57
  familyBoost,
56
58
  rerankStage,
@@ -0,0 +1,23 @@
1
+ // The license entitlement hard scope (TODO.external-refs/08) — pool-level,
2
+ // after every candidate lane has merged, BEFORE rerank + the top-N cut
3
+ // (the `seal` posture, never the post-rerank corpus-scope drop). A chunk
4
+ // whose `standard_key` lies outside the caller's entitlement set never
5
+ // competes for the window: the model never sees licensed text the caller
6
+ // cannot be shown, so no prompt-level leakage either. The predicate is
7
+ // selfquery.standardKeyAllowed; the lane audit that binds every query
8
+ // shape lives in its doc comment.
9
+ import { standardKeyAllowed } from "../selfquery.ts";
10
+ import type { Stage } from "./types.ts";
11
+
12
+ export const licenseScope: Stage = {
13
+ name: "license-scope",
14
+ when: (c) => !!c.opts.standardKeys,
15
+ run: (c) => {
16
+ const keys = c.opts.standardKeys!;
17
+ const before = c.hits.length;
18
+ c.hits = c.hits.filter((h) => standardKeyAllowed(h.metadata, keys));
19
+ if (c.hits.length !== before) {
20
+ console.log("license scope:", before, "→", c.hits.length, "candidates within the caller's entitlement set");
21
+ }
22
+ },
23
+ };
@@ -36,6 +36,17 @@ export interface RetrieveOptions {
36
36
  * intersects the requested ids with session permissions before
37
37
  * building this set. */
38
38
  datasetScope?: Set<string> | null;
39
+ /** The license entitlement set (TODO.external-refs/08): the standard
40
+ * keys the caller's organization is entitled to, resolved
41
+ * request-scoped by the deployment (never a client-tunable filter —
42
+ * it arrives through the same trusted request context as the session,
43
+ * and the profile's declared licensed list is the validation
44
+ * whitelist). NON-NULL activates the hard scope: chunks carrying a
45
+ * `standard_key` outside the set never reach ranking (an EMPTY set is
46
+ * the unentitled caller — licensed content hidden, citation-level
47
+ * metadata stays). Null = the deployment declares no licensed
48
+ * content, the scope is inert. */
49
+ standardKeys?: Set<string> | null;
39
50
  }
40
51
 
41
52
  export interface GlossaryEntry {
@@ -1,12 +0,0 @@
1
- import {
2
- handleAsk
3
- } from "./chunk-PVHSBXZD.js";
4
- import "./chunk-LNSDBEKS.js";
5
- import "./chunk-6GOSMLRH.js";
6
- import "./chunk-SN3ANQ3Y.js";
7
- import "./chunk-WGXATDXY.js";
8
- import "./chunk-Q6LI4T7M.js";
9
- import "./chunk-Q327B27J.js";
10
- export {
11
- handleAsk
12
- };
@@ -1,11 +0,0 @@
1
- import {
2
- handleSearch
3
- } from "./chunk-VJZLVU3S.js";
4
- import "./chunk-6GOSMLRH.js";
5
- import "./chunk-SN3ANQ3Y.js";
6
- import "./chunk-WGXATDXY.js";
7
- import "./chunk-Q6LI4T7M.js";
8
- import "./chunk-Q327B27J.js";
9
- export {
10
- handleSearch
11
- };