@torknetwork/governance-contract 0.0.0-stage → 0.1.0
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/LICENSE +21 -0
- package/dist/cache/exact-key.d.ts +33 -0
- package/dist/cache/exact-key.d.ts.map +1 -0
- package/dist/cache/exact-key.js +63 -0
- package/dist/cache/exact-key.js.map +1 -0
- package/dist/cache/exact.d.ts +54 -0
- package/dist/cache/exact.d.ts.map +1 -0
- package/dist/cache/exact.js +96 -0
- package/dist/cache/exact.js.map +1 -0
- package/dist/cache/gateway.d.ts +27 -0
- package/dist/cache/gateway.d.ts.map +1 -0
- package/dist/cache/gateway.js +32 -0
- package/dist/cache/gateway.js.map +1 -0
- package/dist/cache/semantic.d.ts +46 -0
- package/dist/cache/semantic.d.ts.map +1 -0
- package/dist/cache/semantic.js +78 -0
- package/dist/cache/semantic.js.map +1 -0
- package/dist/canonical.d.ts +17 -0
- package/dist/canonical.d.ts.map +1 -0
- package/dist/canonical.js +86 -0
- package/dist/canonical.js.map +1 -0
- package/dist/client.d.ts +40 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +82 -0
- package/dist/client.js.map +1 -0
- package/dist/feedback.d.ts +35 -0
- package/dist/feedback.d.ts.map +1 -0
- package/dist/feedback.js +51 -0
- package/dist/feedback.js.map +1 -0
- package/dist/golden-set/checks.d.ts +15 -0
- package/dist/golden-set/checks.d.ts.map +1 -0
- package/dist/golden-set/checks.js +104 -0
- package/dist/golden-set/checks.js.map +1 -0
- package/dist/golden-set/runner.d.ts +20 -0
- package/dist/golden-set/runner.d.ts.map +1 -0
- package/dist/golden-set/runner.js +109 -0
- package/dist/golden-set/runner.js.map +1 -0
- package/dist/golden-set/types.d.ts +116 -0
- package/dist/golden-set/types.d.ts.map +1 -0
- package/dist/golden-set/types.js +22 -0
- package/dist/golden-set/types.js.map +1 -0
- package/dist/golden-set/validate.d.ts +40 -0
- package/dist/golden-set/validate.d.ts.map +1 -0
- package/dist/golden-set/validate.js +135 -0
- package/dist/golden-set/validate.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +40 -0
- package/dist/index.js.map +1 -0
- package/dist/langfuse.d.ts +164 -0
- package/dist/langfuse.d.ts.map +1 -0
- package/dist/langfuse.js +98 -0
- package/dist/langfuse.js.map +1 -0
- package/dist/otel.d.ts +38 -0
- package/dist/otel.d.ts.map +1 -0
- package/dist/otel.js +193 -0
- package/dist/otel.js.map +1 -0
- package/dist/providers/decision.d.ts +84 -0
- package/dist/providers/decision.d.ts.map +1 -0
- package/dist/providers/decision.js +114 -0
- package/dist/providers/decision.js.map +1 -0
- package/dist/providers/pricing.d.ts +17 -0
- package/dist/providers/pricing.d.ts.map +1 -0
- package/dist/providers/pricing.js +29 -0
- package/dist/providers/pricing.js.map +1 -0
- package/dist/providers/prompt-guard.d.ts +40 -0
- package/dist/providers/prompt-guard.d.ts.map +1 -0
- package/dist/providers/prompt-guard.js +73 -0
- package/dist/providers/prompt-guard.js.map +1 -0
- package/dist/react/FeedbackButtons.d.ts +34 -0
- package/dist/react/FeedbackButtons.d.ts.map +1 -0
- package/dist/react/FeedbackButtons.js +81 -0
- package/dist/react/FeedbackButtons.js.map +1 -0
- package/dist/react/index.d.ts +2 -0
- package/dist/react/index.d.ts.map +1 -0
- package/dist/react/index.js +6 -0
- package/dist/react/index.js.map +1 -0
- package/dist/receipt.d.ts +252 -0
- package/dist/receipt.d.ts.map +1 -0
- package/dist/receipt.js +506 -0
- package/dist/receipt.js.map +1 -0
- package/dist/switches.d.ts +82 -0
- package/dist/switches.d.ts.map +1 -0
- package/dist/switches.js +116 -0
- package/dist/switches.js.map +1 -0
- package/dist/tower-link.d.ts +127 -0
- package/dist/tower-link.d.ts.map +1 -0
- package/dist/tower-link.js +296 -0
- package/dist/tower-link.js.map +1 -0
- package/package.json +59 -4
- package/README.md +0 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Tork Network Pty Ltd
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tenant-scoped cache identities keep answer content private and invalidate
|
|
3
|
+
* when sources or generation settings change. Role-dependent answers bypass
|
|
4
|
+
* caching; embedding identities preserve the exact text sent to the model.
|
|
5
|
+
*/
|
|
6
|
+
import type { Surface } from '../receipt';
|
|
7
|
+
export declare const EXACT_CACHE_KEY_VERSION = "v1";
|
|
8
|
+
export interface SourceVersion {
|
|
9
|
+
document_id: string;
|
|
10
|
+
version: string;
|
|
11
|
+
}
|
|
12
|
+
export interface ExactCacheKeyInput {
|
|
13
|
+
org_id: string;
|
|
14
|
+
product: string;
|
|
15
|
+
surface: Surface;
|
|
16
|
+
question: string;
|
|
17
|
+
model: string;
|
|
18
|
+
prompt_version: string;
|
|
19
|
+
config_version: string;
|
|
20
|
+
/** The documents (and their versions) the answer was built from. */
|
|
21
|
+
source_versions: readonly SourceVersion[];
|
|
22
|
+
/** True when the answer depends on the asker's role or clearance — never cached. */
|
|
23
|
+
role_dependent: boolean;
|
|
24
|
+
locale?: string;
|
|
25
|
+
}
|
|
26
|
+
export declare function normaliseQuestion(q: string): string;
|
|
27
|
+
export declare function buildExactCacheKey(input: ExactCacheKeyInput): Promise<string | null>;
|
|
28
|
+
export declare function buildEmbeddingCacheKey(input: {
|
|
29
|
+
org_id: string;
|
|
30
|
+
model: string;
|
|
31
|
+
content: string;
|
|
32
|
+
}): Promise<string>;
|
|
33
|
+
//# sourceMappingURL=exact-key.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exact-key.d.ts","sourceRoot":"","sources":["../../src/cache/exact-key.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAE1C,eAAO,MAAM,uBAAuB,OAAO,CAAC;AAE5C,MAAM,WAAW,aAAa;IAAG,WAAW,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE;AAEvE,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,oEAAoE;IACpE,eAAe,EAAE,SAAS,aAAa,EAAE,CAAC;IAC1C,oFAAoF;IACpF,cAAc,EAAE,OAAO,CAAC;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAID,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAEnD;AAUD,wBAAsB,kBAAkB,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CA0B1F;AAED,wBAAsB,sBAAsB,CAAC,KAAK,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAOvH"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Tenant-scoped cache identities keep answer content private and invalidate
|
|
4
|
+
* when sources or generation settings change. Role-dependent answers bypass
|
|
5
|
+
* caching; embedding identities preserve the exact text sent to the model.
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.EXACT_CACHE_KEY_VERSION = void 0;
|
|
9
|
+
exports.normaliseQuestion = normaliseQuestion;
|
|
10
|
+
exports.buildExactCacheKey = buildExactCacheKey;
|
|
11
|
+
exports.buildEmbeddingCacheKey = buildEmbeddingCacheKey;
|
|
12
|
+
const canonical_1 = require("../canonical");
|
|
13
|
+
exports.EXACT_CACHE_KEY_VERSION = 'v1';
|
|
14
|
+
const ORG_ID = /^[A-Za-z0-9_-]{1,64}$/;
|
|
15
|
+
function normaliseQuestion(q) {
|
|
16
|
+
return q.normalize('NFKC').trim().replace(/\s+/g, ' ').toLowerCase().replace(/[?!.\s]+$/g, '');
|
|
17
|
+
}
|
|
18
|
+
function nonEmpty(value) {
|
|
19
|
+
return typeof value === 'string' && value.length > 0;
|
|
20
|
+
}
|
|
21
|
+
function compare(a, b) {
|
|
22
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
23
|
+
}
|
|
24
|
+
async function buildExactCacheKey(input) {
|
|
25
|
+
try {
|
|
26
|
+
if (input.role_dependent || !ORG_ID.test(input.org_id))
|
|
27
|
+
return null;
|
|
28
|
+
const q = normaliseQuestion(input.question);
|
|
29
|
+
if (!q || ![input.product, input.model, input.prompt_version, input.config_version].every(nonEmpty))
|
|
30
|
+
return null;
|
|
31
|
+
const sources = [];
|
|
32
|
+
const seen = new Set();
|
|
33
|
+
for (const source of input.source_versions) {
|
|
34
|
+
if (!nonEmpty(source.document_id) || !nonEmpty(source.version))
|
|
35
|
+
return null;
|
|
36
|
+
const pair = [source.document_id, source.version];
|
|
37
|
+
const identity = (0, canonical_1.canonicalJson)(pair);
|
|
38
|
+
if (!seen.has(identity)) {
|
|
39
|
+
seen.add(identity);
|
|
40
|
+
sources.push(pair);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
sources.sort((a, b) => compare(a[0], b[0]) || compare(a[1], b[1]));
|
|
44
|
+
const hash = await (0, canonical_1.sha256Hex)((0, canonical_1.canonicalJson)({
|
|
45
|
+
product: input.product, surface: input.surface, q, model: input.model,
|
|
46
|
+
prompt_version: input.prompt_version, config_version: input.config_version,
|
|
47
|
+
locale: input.locale ?? null, sources,
|
|
48
|
+
}));
|
|
49
|
+
return `tork:xc:${exports.EXACT_CACHE_KEY_VERSION}:${input.org_id}:${hash}`;
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return null; // Invalid input or unavailable hashing makes caching a miss.
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
async function buildEmbeddingCacheKey(input) {
|
|
56
|
+
if (!ORG_ID.test(input.org_id) || !nonEmpty(input.model)) {
|
|
57
|
+
throw new TypeError('Embedding cache requires a valid org_id and non-empty model');
|
|
58
|
+
}
|
|
59
|
+
const modelHash = await (0, canonical_1.sha256Hex)(input.model);
|
|
60
|
+
const contentHash = await (0, canonical_1.sha256Hex)(input.content);
|
|
61
|
+
return `tork:emb:${exports.EXACT_CACHE_KEY_VERSION}:${input.org_id}:${modelHash.slice(0, 16)}:${contentHash}`;
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=exact-key.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exact-key.js","sourceRoot":"","sources":["../../src/cache/exact-key.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Exact-match answer cache and embedding cache, on any Redis-like store
|
|
3
|
+
* (Upstash Redis in production: `new Redis({...})` satisfies CacheStore).
|
|
4
|
+
*
|
|
5
|
+
* Rules enforced here, not left to the caller:
|
|
6
|
+
* - keys are scoped per tenant and include the source-document versions the
|
|
7
|
+
* answer used, so publishing a new version invalidates by construction
|
|
8
|
+
* (see exact-key.ts);
|
|
9
|
+
* - an answer that depends on the asker's role or clearance is NEVER cached
|
|
10
|
+
* (the key builder returns null and nothing is read or written);
|
|
11
|
+
* - the tenant's `exact_cache` switch gates every read and write;
|
|
12
|
+
* - a cache hit still produces a receipt: `cachedAnswer` calls `onReceipt`
|
|
13
|
+
* on hits and misses alike, with `cache_hit` set on hits.
|
|
14
|
+
*
|
|
15
|
+
* Flow: retrieve first (cheap), then look up with the retrieved documents'
|
|
16
|
+
* versions, and only generate on a miss.
|
|
17
|
+
*/
|
|
18
|
+
import type { TenantSettings } from '../switches';
|
|
19
|
+
import type { CacheHit } from '../receipt';
|
|
20
|
+
import { type ExactCacheKeyInput } from './exact-key';
|
|
21
|
+
export interface CacheStore {
|
|
22
|
+
get(key: string): Promise<unknown>;
|
|
23
|
+
set(key: string, value: string, opts?: {
|
|
24
|
+
ex?: number;
|
|
25
|
+
}): Promise<unknown>;
|
|
26
|
+
}
|
|
27
|
+
export interface CachedAnswerResult<T> {
|
|
28
|
+
value: T;
|
|
29
|
+
cache_hit: CacheHit | null;
|
|
30
|
+
}
|
|
31
|
+
export declare function cachedAnswer<T>(opts: {
|
|
32
|
+
store: CacheStore | null | undefined;
|
|
33
|
+
settings: TenantSettings;
|
|
34
|
+
key: ExactCacheKeyInput;
|
|
35
|
+
compute: () => Promise<T>;
|
|
36
|
+
/** Always called — on a hit with cache_hit 'exact', on a miss with null. */
|
|
37
|
+
onReceipt: (r: {
|
|
38
|
+
cache_hit: CacheHit | null;
|
|
39
|
+
}) => Promise<void> | void;
|
|
40
|
+
ttlSeconds?: number;
|
|
41
|
+
}): Promise<CachedAnswerResult<T>>;
|
|
42
|
+
/** Embeddings cached by content hash, per tenant and per model. */
|
|
43
|
+
export declare function cachedEmbedding(opts: {
|
|
44
|
+
store: CacheStore | null | undefined;
|
|
45
|
+
settings: TenantSettings;
|
|
46
|
+
model: string;
|
|
47
|
+
content: string;
|
|
48
|
+
embed: (content: string) => Promise<number[]>;
|
|
49
|
+
ttlSeconds?: number;
|
|
50
|
+
}): Promise<{
|
|
51
|
+
embedding: number[];
|
|
52
|
+
cache_hit: boolean;
|
|
53
|
+
}>;
|
|
54
|
+
//# sourceMappingURL=exact.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exact.d.ts","sourceRoot":"","sources":["../../src/cache/exact.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAA8C,KAAK,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAElG,MAAM,WAAW,UAAU;IACzB,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACnC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE;QAAE,EAAE,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAC3E;AAED,MAAM,WAAW,kBAAkB,CAAC,CAAC;IACnC,KAAK,EAAE,CAAC,CAAC;IACT,SAAS,EAAE,QAAQ,GAAG,IAAI,CAAC;CAC5B;AAiBD,wBAAsB,YAAY,CAAC,CAAC,EAAE,IAAI,EAAE;IAC1C,KAAK,EAAE,UAAU,GAAG,IAAI,GAAG,SAAS,CAAC;IACrC,QAAQ,EAAE,cAAc,CAAC;IACzB,GAAG,EAAE,kBAAkB,CAAC;IACxB,OAAO,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,CAAC;IAC1B,4EAA4E;IAC5E,SAAS,EAAE,CAAC,CAAC,EAAE;QAAE,SAAS,EAAE,QAAQ,GAAG,IAAI,CAAA;KAAE,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACvE,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAC,CAAC,CAAC,CA2BjC;AAED,mEAAmE;AACnE,wBAAsB,eAAe,CAAC,IAAI,EAAE;IAC1C,KAAK,EAAE,UAAU,GAAG,IAAI,GAAG,SAAS,CAAC;IACrC,QAAQ,EAAE,cAAc,CAAC;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAC9C,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,GAAG,OAAO,CAAC;IAAE,SAAS,EAAE,MAAM,EAAE,CAAC;IAAC,SAAS,EAAE,OAAO,CAAA;CAAE,CAAC,CAwBvD"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Exact-match answer cache and embedding cache, on any Redis-like store
|
|
4
|
+
* (Upstash Redis in production: `new Redis({...})` satisfies CacheStore).
|
|
5
|
+
*
|
|
6
|
+
* Rules enforced here, not left to the caller:
|
|
7
|
+
* - keys are scoped per tenant and include the source-document versions the
|
|
8
|
+
* answer used, so publishing a new version invalidates by construction
|
|
9
|
+
* (see exact-key.ts);
|
|
10
|
+
* - an answer that depends on the asker's role or clearance is NEVER cached
|
|
11
|
+
* (the key builder returns null and nothing is read or written);
|
|
12
|
+
* - the tenant's `exact_cache` switch gates every read and write;
|
|
13
|
+
* - a cache hit still produces a receipt: `cachedAnswer` calls `onReceipt`
|
|
14
|
+
* on hits and misses alike, with `cache_hit` set on hits.
|
|
15
|
+
*
|
|
16
|
+
* Flow: retrieve first (cheap), then look up with the retrieved documents'
|
|
17
|
+
* versions, and only generate on a miss.
|
|
18
|
+
*/
|
|
19
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
+
exports.cachedAnswer = cachedAnswer;
|
|
21
|
+
exports.cachedEmbedding = cachedEmbedding;
|
|
22
|
+
const exact_key_1 = require("./exact-key");
|
|
23
|
+
const DEFAULT_TTL_SECONDS = 24 * 60 * 60;
|
|
24
|
+
function decode(raw) {
|
|
25
|
+
if (raw === null || raw === undefined)
|
|
26
|
+
return undefined;
|
|
27
|
+
// Upstash auto-deserialises JSON; other clients return the string.
|
|
28
|
+
if (typeof raw === 'string') {
|
|
29
|
+
try {
|
|
30
|
+
return JSON.parse(raw);
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return raw;
|
|
37
|
+
}
|
|
38
|
+
async function cachedAnswer(opts) {
|
|
39
|
+
const enabled = opts.settings.switches.exact_cache && !!opts.store;
|
|
40
|
+
const key = enabled ? await (0, exact_key_1.buildExactCacheKey)(opts.key) : null;
|
|
41
|
+
if (key && opts.store) {
|
|
42
|
+
let hit;
|
|
43
|
+
try {
|
|
44
|
+
hit = decode(await opts.store.get(key));
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
hit = undefined; // a cache outage is a miss, never an error
|
|
48
|
+
}
|
|
49
|
+
if (hit !== undefined) {
|
|
50
|
+
await opts.onReceipt({ cache_hit: 'exact' });
|
|
51
|
+
return { value: hit, cache_hit: 'exact' };
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const value = await opts.compute();
|
|
55
|
+
if (key && opts.store) {
|
|
56
|
+
try {
|
|
57
|
+
await opts.store.set(key, JSON.stringify(value), { ex: opts.ttlSeconds ?? DEFAULT_TTL_SECONDS });
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
/* best effort */
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
await opts.onReceipt({ cache_hit: null });
|
|
64
|
+
return { value, cache_hit: null };
|
|
65
|
+
}
|
|
66
|
+
/** Embeddings cached by content hash, per tenant and per model. */
|
|
67
|
+
async function cachedEmbedding(opts) {
|
|
68
|
+
if (!opts.store || !opts.settings.switches.exact_cache) {
|
|
69
|
+
return { embedding: await opts.embed(opts.content), cache_hit: false };
|
|
70
|
+
}
|
|
71
|
+
let key;
|
|
72
|
+
try {
|
|
73
|
+
key = await (0, exact_key_1.buildEmbeddingCacheKey)({ org_id: opts.settings.org_id, model: opts.model, content: opts.content });
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
// An org id the key builder will not scope is never cached, never an error.
|
|
77
|
+
return { embedding: await opts.embed(opts.content), cache_hit: false };
|
|
78
|
+
}
|
|
79
|
+
try {
|
|
80
|
+
const hit = decode(await opts.store.get(key));
|
|
81
|
+
if (Array.isArray(hit))
|
|
82
|
+
return { embedding: hit, cache_hit: true };
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
/* miss */
|
|
86
|
+
}
|
|
87
|
+
const embedding = await opts.embed(opts.content);
|
|
88
|
+
try {
|
|
89
|
+
await opts.store.set(key, JSON.stringify(embedding), { ex: opts.ttlSeconds ?? 7 * DEFAULT_TTL_SECONDS });
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
/* best effort */
|
|
93
|
+
}
|
|
94
|
+
return { embedding, cache_hit: false };
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=exact.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exact.js","sourceRoot":"","sources":["../../src/cache/exact.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vercel AI Gateway prompt caching.
|
|
3
|
+
*
|
|
4
|
+
* `providerOptions.gateway.caching = 'auto'` asks the Gateway to apply each
|
|
5
|
+
* provider's own prompt-caching strategy (Vercel docs, "Automatic caching",
|
|
6
|
+
* read 9 Oct 2026). `x-session-affinity` carries a stable value per
|
|
7
|
+
* conversation so turns of one conversation land where the cached prefix is.
|
|
8
|
+
*
|
|
9
|
+
* The affinity value is a SHA-256 of (org_id, conversation_id), never the raw
|
|
10
|
+
* conversation id: it leaves Tork's boundary in a request header.
|
|
11
|
+
*/
|
|
12
|
+
import type { TenantSettings } from '../switches';
|
|
13
|
+
export interface GatewayCallOptions {
|
|
14
|
+
providerOptions?: {
|
|
15
|
+
gateway: {
|
|
16
|
+
caching: 'auto';
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
headers?: Record<string, string>;
|
|
20
|
+
}
|
|
21
|
+
export declare function sessionAffinity(org_id: string, conversation_id: string): Promise<string>;
|
|
22
|
+
/**
|
|
23
|
+
* Options to spread into an AI SDK call through the Gateway. Returns `{}`
|
|
24
|
+
* when the tenant's `gateway_caching` switch is off.
|
|
25
|
+
*/
|
|
26
|
+
export declare function gatewayCallOptions(settings: TenantSettings, conversation_id: string | null | undefined): Promise<GatewayCallOptions>;
|
|
27
|
+
//# sourceMappingURL=gateway.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gateway.d.ts","sourceRoot":"","sources":["../../src/cache/gateway.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD,MAAM,WAAW,kBAAkB;IACjC,eAAe,CAAC,EAAE;QAAE,OAAO,EAAE;YAAE,OAAO,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE,CAAC;IACnD,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC;AAED,wBAAsB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAE9F;AAED;;;GAGG;AACH,wBAAsB,kBAAkB,CAAC,QAAQ,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAK1I"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Vercel AI Gateway prompt caching.
|
|
4
|
+
*
|
|
5
|
+
* `providerOptions.gateway.caching = 'auto'` asks the Gateway to apply each
|
|
6
|
+
* provider's own prompt-caching strategy (Vercel docs, "Automatic caching",
|
|
7
|
+
* read 9 Oct 2026). `x-session-affinity` carries a stable value per
|
|
8
|
+
* conversation so turns of one conversation land where the cached prefix is.
|
|
9
|
+
*
|
|
10
|
+
* The affinity value is a SHA-256 of (org_id, conversation_id), never the raw
|
|
11
|
+
* conversation id: it leaves Tork's boundary in a request header.
|
|
12
|
+
*/
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
exports.sessionAffinity = sessionAffinity;
|
|
15
|
+
exports.gatewayCallOptions = gatewayCallOptions;
|
|
16
|
+
const canonical_1 = require("../canonical");
|
|
17
|
+
async function sessionAffinity(org_id, conversation_id) {
|
|
18
|
+
return (await (0, canonical_1.sha256Hex)(`tork-affinity:v1:${org_id}:${conversation_id}`)).slice(0, 32);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Options to spread into an AI SDK call through the Gateway. Returns `{}`
|
|
22
|
+
* when the tenant's `gateway_caching` switch is off.
|
|
23
|
+
*/
|
|
24
|
+
async function gatewayCallOptions(settings, conversation_id) {
|
|
25
|
+
if (!settings.switches.gateway_caching)
|
|
26
|
+
return {};
|
|
27
|
+
const out = { providerOptions: { gateway: { caching: 'auto' } } };
|
|
28
|
+
if (conversation_id)
|
|
29
|
+
out.headers = { 'x-session-affinity': await sessionAffinity(settings.org_id, conversation_id) };
|
|
30
|
+
return out;
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=gateway.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gateway.js","sourceRoot":"","sources":["../../src/cache/gateway.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;GAUG;;AAUH,0CAEC;AAMD,gDAKC;AArBD,4CAAyC;AAQlC,KAAK,UAAU,eAAe,CAAC,MAAc,EAAE,eAAuB;IAC3E,OAAO,CAAC,MAAM,IAAA,qBAAS,EAAC,oBAAoB,MAAM,IAAI,eAAe,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AACzF,CAAC;AAED;;;GAGG;AACI,KAAK,UAAU,kBAAkB,CAAC,QAAwB,EAAE,eAA0C;IAC3G,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,eAAe;QAAE,OAAO,EAAE,CAAC;IAClD,MAAM,GAAG,GAAuB,EAAE,eAAe,EAAE,EAAE,OAAO,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;IACtF,IAAI,eAAe;QAAE,GAAG,CAAC,OAAO,GAAG,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC,QAAQ,CAAC,MAAM,EAAE,eAAe,CAAC,EAAE,CAAC;IACrH,OAAO,GAAG,CAAC;AACb,CAAC"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Semantic cache slot (Upstash `@upstash/semantic-cache`). OFF by default.
|
|
3
|
+
*
|
|
4
|
+
* Allowed only when ALL hold:
|
|
5
|
+
* - the tenant's `semantic_cache` switch is on;
|
|
6
|
+
* - the surface is `website_widget` (public content, same answer for every
|
|
7
|
+
* visitor) — never a product panel, voice or API surface;
|
|
8
|
+
* - the answer is not role- or clearance-dependent;
|
|
9
|
+
* - the similarity threshold is at least 0.95.
|
|
10
|
+
*
|
|
11
|
+
* The package is not a dependency: the product constructs
|
|
12
|
+
* `new SemanticCache({ index, minProximity, namespace })` and passes it in as
|
|
13
|
+
* a SemanticCacheStore. `semanticCacheNamespace` gives the per-tenant
|
|
14
|
+
* namespace to construct it with.
|
|
15
|
+
*/
|
|
16
|
+
import type { TenantSettings } from '../switches';
|
|
17
|
+
import type { Surface } from '../receipt';
|
|
18
|
+
export declare const MIN_SEMANTIC_THRESHOLD = 0.95;
|
|
19
|
+
export interface SemanticCacheStore {
|
|
20
|
+
get(question: string): Promise<string | undefined | null>;
|
|
21
|
+
set(question: string, answer: string): Promise<unknown>;
|
|
22
|
+
}
|
|
23
|
+
export declare class SemanticCacheRefused extends Error {
|
|
24
|
+
readonly reason: string;
|
|
25
|
+
constructor(reason: string);
|
|
26
|
+
}
|
|
27
|
+
export declare function semanticCacheNamespace(org_id: string, source_versions_digest: string): string;
|
|
28
|
+
/** Why the semantic cache may not be used, or null when it may. */
|
|
29
|
+
export declare function semanticCacheRefusal(opts: {
|
|
30
|
+
settings: TenantSettings;
|
|
31
|
+
surface: Surface;
|
|
32
|
+
role_dependent: boolean;
|
|
33
|
+
threshold: number;
|
|
34
|
+
}): string | null;
|
|
35
|
+
export declare class GuardedSemanticCache {
|
|
36
|
+
private readonly store;
|
|
37
|
+
private readonly opts;
|
|
38
|
+
constructor(store: SemanticCacheStore, opts: {
|
|
39
|
+
settings: TenantSettings;
|
|
40
|
+
surface: Surface;
|
|
41
|
+
threshold: number;
|
|
42
|
+
});
|
|
43
|
+
get(question: string, role_dependent: boolean): Promise<string | null>;
|
|
44
|
+
set(question: string, answer: string, role_dependent: boolean): Promise<boolean>;
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=semantic.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"semantic.d.ts","sourceRoot":"","sources":["../../src/cache/semantic.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAE1C,eAAO,MAAM,sBAAsB,OAAO,CAAC;AAE3C,MAAM,WAAW,kBAAkB;IACjC,GAAG,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,GAAG,IAAI,CAAC,CAAC;IAC1D,GAAG,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACzD;AAED,qBAAa,oBAAqB,SAAQ,KAAK;aACjB,MAAM,EAAE,MAAM;gBAAd,MAAM,EAAE,MAAM;CAI3C;AAED,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,MAAM,EAAE,sBAAsB,EAAE,MAAM,GAAG,MAAM,CAE7F;AAED,mEAAmE;AACnE,wBAAgB,oBAAoB,CAAC,IAAI,EAAE;IACzC,QAAQ,EAAE,cAAc,CAAC;IACzB,OAAO,EAAE,OAAO,CAAC;IACjB,cAAc,EAAE,OAAO,CAAC;IACxB,SAAS,EAAE,MAAM,CAAC;CACnB,GAAG,MAAM,GAAG,IAAI,CAMhB;AAED,qBAAa,oBAAoB;IAE7B,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,IAAI;gBADJ,KAAK,EAAE,kBAAkB,EACzB,IAAI,EAAE;QAAE,QAAQ,EAAE,cAAc,CAAC;QAAC,OAAO,EAAE,OAAO,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE;IAQpF,GAAG,CAAC,QAAQ,EAAE,MAAM,EAAE,cAAc,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAStE,GAAG,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;CASvF"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Semantic cache slot (Upstash `@upstash/semantic-cache`). OFF by default.
|
|
4
|
+
*
|
|
5
|
+
* Allowed only when ALL hold:
|
|
6
|
+
* - the tenant's `semantic_cache` switch is on;
|
|
7
|
+
* - the surface is `website_widget` (public content, same answer for every
|
|
8
|
+
* visitor) — never a product panel, voice or API surface;
|
|
9
|
+
* - the answer is not role- or clearance-dependent;
|
|
10
|
+
* - the similarity threshold is at least 0.95.
|
|
11
|
+
*
|
|
12
|
+
* The package is not a dependency: the product constructs
|
|
13
|
+
* `new SemanticCache({ index, minProximity, namespace })` and passes it in as
|
|
14
|
+
* a SemanticCacheStore. `semanticCacheNamespace` gives the per-tenant
|
|
15
|
+
* namespace to construct it with.
|
|
16
|
+
*/
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
exports.GuardedSemanticCache = exports.SemanticCacheRefused = exports.MIN_SEMANTIC_THRESHOLD = void 0;
|
|
19
|
+
exports.semanticCacheNamespace = semanticCacheNamespace;
|
|
20
|
+
exports.semanticCacheRefusal = semanticCacheRefusal;
|
|
21
|
+
exports.MIN_SEMANTIC_THRESHOLD = 0.95;
|
|
22
|
+
class SemanticCacheRefused extends Error {
|
|
23
|
+
constructor(reason) {
|
|
24
|
+
super(`semantic cache refused: ${reason}`);
|
|
25
|
+
this.reason = reason;
|
|
26
|
+
this.name = 'SemanticCacheRefused';
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
exports.SemanticCacheRefused = SemanticCacheRefused;
|
|
30
|
+
function semanticCacheNamespace(org_id, source_versions_digest) {
|
|
31
|
+
return `tork:sc:v1:${org_id}:${source_versions_digest}`;
|
|
32
|
+
}
|
|
33
|
+
/** Why the semantic cache may not be used, or null when it may. */
|
|
34
|
+
function semanticCacheRefusal(opts) {
|
|
35
|
+
if (!opts.settings.switches.semantic_cache)
|
|
36
|
+
return 'semantic_cache switch is off';
|
|
37
|
+
if (opts.surface !== 'website_widget')
|
|
38
|
+
return `surface ${opts.surface} is not a public website surface`;
|
|
39
|
+
if (opts.role_dependent)
|
|
40
|
+
return 'answer depends on the asker role or clearance';
|
|
41
|
+
if (!(opts.threshold >= exports.MIN_SEMANTIC_THRESHOLD))
|
|
42
|
+
return `threshold ${opts.threshold} is below ${exports.MIN_SEMANTIC_THRESHOLD}`;
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
class GuardedSemanticCache {
|
|
46
|
+
constructor(store, opts) {
|
|
47
|
+
this.store = store;
|
|
48
|
+
this.opts = opts;
|
|
49
|
+
// Fail at construction for the static conditions, so a mis-wired cache
|
|
50
|
+
// is a startup error rather than a silent leak.
|
|
51
|
+
const r = semanticCacheRefusal({ ...opts, role_dependent: false });
|
|
52
|
+
if (r)
|
|
53
|
+
throw new SemanticCacheRefused(r);
|
|
54
|
+
}
|
|
55
|
+
async get(question, role_dependent) {
|
|
56
|
+
if (semanticCacheRefusal({ ...this.opts, role_dependent }))
|
|
57
|
+
return null;
|
|
58
|
+
try {
|
|
59
|
+
return (await this.store.get(question)) ?? null;
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
async set(question, answer, role_dependent) {
|
|
66
|
+
if (semanticCacheRefusal({ ...this.opts, role_dependent }))
|
|
67
|
+
return false;
|
|
68
|
+
try {
|
|
69
|
+
await this.store.set(question, answer);
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
exports.GuardedSemanticCache = GuardedSemanticCache;
|
|
78
|
+
//# sourceMappingURL=semantic.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"semantic.js","sourceRoot":"","sources":["../../src/cache/semantic.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;GAcG;;;AAmBH,wDAEC;AAGD,oDAWC;AA9BY,QAAA,sBAAsB,GAAG,IAAI,CAAC;AAO3C,MAAa,oBAAqB,SAAQ,KAAK;IAC7C,YAA4B,MAAc;QACxC,KAAK,CAAC,2BAA2B,MAAM,EAAE,CAAC,CAAC;QADjB,WAAM,GAAN,MAAM,CAAQ;QAExC,IAAI,CAAC,IAAI,GAAG,sBAAsB,CAAC;IACrC,CAAC;CACF;AALD,oDAKC;AAED,SAAgB,sBAAsB,CAAC,MAAc,EAAE,sBAA8B;IACnF,OAAO,cAAc,MAAM,IAAI,sBAAsB,EAAE,CAAC;AAC1D,CAAC;AAED,mEAAmE;AACnE,SAAgB,oBAAoB,CAAC,IAKpC;IACC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,cAAc;QAAE,OAAO,8BAA8B,CAAC;IAClF,IAAI,IAAI,CAAC,OAAO,KAAK,gBAAgB;QAAE,OAAO,WAAW,IAAI,CAAC,OAAO,kCAAkC,CAAC;IACxG,IAAI,IAAI,CAAC,cAAc;QAAE,OAAO,+CAA+C,CAAC;IAChF,IAAI,CAAC,CAAC,IAAI,CAAC,SAAS,IAAI,8BAAsB,CAAC;QAAE,OAAO,aAAa,IAAI,CAAC,SAAS,aAAa,8BAAsB,EAAE,CAAC;IACzH,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAa,oBAAoB;IAC/B,YACmB,KAAyB,EACzB,IAAuE;QADvE,UAAK,GAAL,KAAK,CAAoB;QACzB,SAAI,GAAJ,IAAI,CAAmE;QAExF,uEAAuE;QACvE,gDAAgD;QAChD,MAAM,CAAC,GAAG,oBAAoB,CAAC,EAAE,GAAG,IAAI,EAAE,cAAc,EAAE,KAAK,EAAE,CAAC,CAAC;QACnE,IAAI,CAAC;YAAE,MAAM,IAAI,oBAAoB,CAAC,CAAC,CAAC,CAAC;IAC3C,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,QAAgB,EAAE,cAAuB;QACjD,IAAI,oBAAoB,CAAC,EAAE,GAAG,IAAI,CAAC,IAAI,EAAE,cAAc,EAAE,CAAC;YAAE,OAAO,IAAI,CAAC;QACxE,IAAI,CAAC;YACH,OAAO,CAAC,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,IAAI,IAAI,CAAC;QAClD,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,QAAgB,EAAE,MAAc,EAAE,cAAuB;QACjE,IAAI,oBAAoB,CAAC,EAAE,GAAG,IAAI,CAAC,IAAI,EAAE,cAAc,EAAE,CAAC;YAAE,OAAO,KAAK,CAAC;QACzE,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;YACvC,OAAO,IAAI,CAAC;QACd,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;CACF;AA7BD,oDA6BC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical serialisation and hashing used by every hash in the contract.
|
|
3
|
+
*
|
|
4
|
+
* Same rule as Tork Tower's canonicalJson (tork-tower-t2
|
|
5
|
+
* src/lib/crypto/canonical-json.ts): object keys sorted at every depth, arrays
|
|
6
|
+
* kept in order, `undefined` dropped. Two parties that agree on this function
|
|
7
|
+
* agree on the exact bytes a hash covers without agreeing on JSON key order.
|
|
8
|
+
*
|
|
9
|
+
* Pure: no I/O, no keys. Uses Web Crypto so it runs unchanged in Node 18+,
|
|
10
|
+
* the Vercel Edge runtime, and browsers.
|
|
11
|
+
*/
|
|
12
|
+
export declare function canonicalJson(value: unknown): string;
|
|
13
|
+
/** SHA-256 over UTF-8, lowercase hex. */
|
|
14
|
+
export declare function sha256Hex(text: string): Promise<string>;
|
|
15
|
+
/** SHA-256 of the canonical JSON of `value`. */
|
|
16
|
+
export declare function hashCanonical(value: unknown): Promise<string>;
|
|
17
|
+
//# sourceMappingURL=canonical.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"canonical.d.ts","sourceRoot":"","sources":["../src/canonical.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAEpD;AAqDD,yCAAyC;AACzC,wBAAsB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAK7D;AAED,gDAAgD;AAChD,wBAAsB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAEnE"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Canonical serialisation and hashing used by every hash in the contract.
|
|
4
|
+
*
|
|
5
|
+
* Same rule as Tork Tower's canonicalJson (tork-tower-t2
|
|
6
|
+
* src/lib/crypto/canonical-json.ts): object keys sorted at every depth, arrays
|
|
7
|
+
* kept in order, `undefined` dropped. Two parties that agree on this function
|
|
8
|
+
* agree on the exact bytes a hash covers without agreeing on JSON key order.
|
|
9
|
+
*
|
|
10
|
+
* Pure: no I/O, no keys. Uses Web Crypto so it runs unchanged in Node 18+,
|
|
11
|
+
* the Vercel Edge runtime, and browsers.
|
|
12
|
+
*/
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
exports.canonicalJson = canonicalJson;
|
|
15
|
+
exports.sha256Hex = sha256Hex;
|
|
16
|
+
exports.hashCanonical = hashCanonical;
|
|
17
|
+
function canonicalJson(value) {
|
|
18
|
+
return emit(value);
|
|
19
|
+
}
|
|
20
|
+
/** Code-point order — what Python's sorted() and Rust's String ordering use. */
|
|
21
|
+
function compareCodePoints(a, b) {
|
|
22
|
+
const ia = a[Symbol.iterator]();
|
|
23
|
+
const ib = b[Symbol.iterator]();
|
|
24
|
+
for (;;) {
|
|
25
|
+
const x = ia.next();
|
|
26
|
+
const y = ib.next();
|
|
27
|
+
if (x.done || y.done)
|
|
28
|
+
return x.done === y.done ? 0 : x.done ? -1 : 1;
|
|
29
|
+
const cx = x.value.codePointAt(0);
|
|
30
|
+
const cy = y.value.codePointAt(0);
|
|
31
|
+
if (cx !== cy)
|
|
32
|
+
return cx - cy;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Built by hand rather than by sorting into an object and calling
|
|
37
|
+
* JSON.stringify: a JS object always enumerates integer-like keys ("2",
|
|
38
|
+
* "10") first and numerically, which would make {"2":…,"10":…} hash
|
|
39
|
+
* differently here than in the Python and Rust SDKs. (Tower's canonicaliser
|
|
40
|
+
* has that quirk; contract blocks never carry integer-like keys, so the two
|
|
41
|
+
* agree on every contract hash — tool-argument hashes are SDK-side only.)
|
|
42
|
+
*/
|
|
43
|
+
function emit(value) {
|
|
44
|
+
if (value === null)
|
|
45
|
+
return 'null';
|
|
46
|
+
switch (typeof value) {
|
|
47
|
+
case 'number':
|
|
48
|
+
// JSON.stringify turns NaN/Infinity into null silently; refuse instead,
|
|
49
|
+
// so a hash never covers a value that did not round-trip.
|
|
50
|
+
if (!Number.isFinite(value))
|
|
51
|
+
throw new TypeError('canonicalJson: non-finite number');
|
|
52
|
+
return JSON.stringify(value);
|
|
53
|
+
case 'string':
|
|
54
|
+
case 'boolean':
|
|
55
|
+
return JSON.stringify(value);
|
|
56
|
+
case 'object': {
|
|
57
|
+
// Array.from visits holes (map skips them): a hole emits null, as JSON.stringify does.
|
|
58
|
+
if (Array.isArray(value))
|
|
59
|
+
return `[${Array.from(value, (v) => (v === undefined ? 'null' : emit(v))).join(',')}]`;
|
|
60
|
+
const obj = value;
|
|
61
|
+
const keys = Object.keys(obj).filter((k) => obj[k] !== undefined).sort(compareCodePoints);
|
|
62
|
+
return `{${keys.map((k) => `${JSON.stringify(k)}:${emit(obj[k])}`).join(',')}}`;
|
|
63
|
+
}
|
|
64
|
+
default:
|
|
65
|
+
throw new TypeError(`canonicalJson: unsupported type ${typeof value}`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function getSubtle() {
|
|
69
|
+
const c = globalThis.crypto;
|
|
70
|
+
if (!c?.subtle)
|
|
71
|
+
throw new Error('Web Crypto (crypto.subtle) is not available in this runtime');
|
|
72
|
+
return c.subtle;
|
|
73
|
+
}
|
|
74
|
+
/** SHA-256 over UTF-8, lowercase hex. */
|
|
75
|
+
async function sha256Hex(text) {
|
|
76
|
+
const buf = await getSubtle().digest('SHA-256', new TextEncoder().encode(text));
|
|
77
|
+
let out = '';
|
|
78
|
+
for (const b of new Uint8Array(buf))
|
|
79
|
+
out += b.toString(16).padStart(2, '0');
|
|
80
|
+
return out;
|
|
81
|
+
}
|
|
82
|
+
/** SHA-256 of the canonical JSON of `value`. */
|
|
83
|
+
async function hashCanonical(value) {
|
|
84
|
+
return sha256Hex(canonicalJson(value));
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=canonical.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"canonical.js","sourceRoot":"","sources":["../src/canonical.ts"],"names":[],"mappings":"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"}
|