@weaveaijs/mcp-observatory 0.3.0 → 0.4.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/dist/core/wrapper.d.ts.map +1 -1
- package/dist/core/wrapper.js +57 -8
- package/dist/core/wrapper.js.map +1 -1
- package/dist/demo/server.d.ts.map +1 -1
- package/dist/demo/server.js +22 -2
- package/dist/demo/server.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/proposal/proposer.d.ts +21 -0
- package/dist/proposal/proposer.d.ts.map +1 -1
- package/dist/proposal/proposer.js +62 -7
- package/dist/proposal/proposer.js.map +1 -1
- package/dist/proposal/token.d.ts +18 -0
- package/dist/proposal/token.d.ts.map +1 -1
- package/dist/proposal/token.js +84 -2
- package/dist/proposal/token.js.map +1 -1
- package/dist/tests/proposer.test.js +6 -0
- package/dist/tests/proposer.test.js.map +1 -1
- package/dist/tests/proposerGuards.test.d.ts +2 -0
- package/dist/tests/proposerGuards.test.d.ts.map +1 -0
- package/dist/tests/proposerGuards.test.js +95 -0
- package/dist/tests/proposerGuards.test.js.map +1 -0
- package/dist/tests/secrets.test.d.ts +2 -0
- package/dist/tests/secrets.test.d.ts.map +1 -0
- package/dist/tests/secrets.test.js +92 -0
- package/dist/tests/secrets.test.js.map +1 -0
- package/dist/tests/wrapper.test.d.ts +2 -0
- package/dist/tests/wrapper.test.d.ts.map +1 -0
- package/dist/tests/wrapper.test.js +141 -0
- package/dist/tests/wrapper.test.js.map +1 -0
- package/dist/utils/limits.d.ts +33 -0
- package/dist/utils/limits.d.ts.map +1 -0
- package/dist/utils/limits.js +55 -0
- package/dist/utils/limits.js.map +1 -0
- package/dist/utils/secrets.d.ts +47 -0
- package/dist/utils/secrets.d.ts.map +1 -0
- package/dist/utils/secrets.js +68 -0
- package/dist/utils/secrets.js.map +1 -0
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wrapper.d.ts","sourceRoot":"","sources":["../../src/core/wrapper.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"wrapper.d.ts","sourceRoot":"","sources":["../../src/core/wrapper.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AA8B5C,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,OAAO,GAAG,QAAQ,GAAG,OAAO,CAAC;IACrC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,aAAa;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,OAAO,CAAC;IAChB,IAAI,EAAE,YAAY,CAAC;IACnB,QAAQ,EAAE,eAAe,CAAC;IAC1B,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,UAAU,CAAC,EAAE,YAAY,CAAC;CAC3B;AAED,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,MAAM,CAA0B;gBAE5B,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,aAAa;IAQ7C,MAAM,CAAC,OAAO,EAAE;QACpB,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC;QAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,MAAM,EAAE,MAAM,CAAC;QACf,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACvC,IAAI,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;QACvC,UAAU,CAAC,EAAE,OAAO,CAAC;QACrB,UAAU,CAAC,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;KAC/C,GAAG,OAAO,CAAC,aAAa,CAAC;IAkF1B,OAAO,CAAC,MAAM;IA+Bd,OAAO,CAAC,MAAM;CAKf"}
|
package/dist/core/wrapper.js
CHANGED
|
@@ -2,6 +2,31 @@ import { estimateTokens } from '../cost/tokenizer.js';
|
|
|
2
2
|
import { estimateCost } from '../cost/pricing.js';
|
|
3
3
|
import { hashText } from '../utils/hashing.js';
|
|
4
4
|
import { Tracer } from './tracer.js';
|
|
5
|
+
/**
|
|
6
|
+
* JSON.stringify() is not a total function: it throws for BigInt values and
|
|
7
|
+
* for circular references, and it returns the *value* `undefined` (not a
|
|
8
|
+
* string) when given `undefined`. Span bookkeeping must never throw on
|
|
9
|
+
* account of what a wrapped call happened to return, so fall back to
|
|
10
|
+
* String() -- and finally to a fixed placeholder -- for anything
|
|
11
|
+
* JSON.stringify can't turn into a string.
|
|
12
|
+
*/
|
|
13
|
+
function safeStringifyForSpan(value) {
|
|
14
|
+
try {
|
|
15
|
+
const json = JSON.stringify(value);
|
|
16
|
+
if (typeof json === 'string') {
|
|
17
|
+
return json;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
// fall through to the String() fallback below
|
|
22
|
+
}
|
|
23
|
+
try {
|
|
24
|
+
return String(value);
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return '[unserializable]';
|
|
28
|
+
}
|
|
29
|
+
}
|
|
5
30
|
export class InvocationWrapper {
|
|
6
31
|
constructor(service, policy) {
|
|
7
32
|
this.tracer = new Tracer(service);
|
|
@@ -21,6 +46,7 @@ export class InvocationWrapper {
|
|
|
21
46
|
const startTime = Date.now();
|
|
22
47
|
let output;
|
|
23
48
|
let shadowOutput;
|
|
49
|
+
let callFailed = false;
|
|
24
50
|
try {
|
|
25
51
|
output = await Promise.resolve(options.call());
|
|
26
52
|
if (options.dualInvoke && options.shadowCall) {
|
|
@@ -28,17 +54,40 @@ export class InvocationWrapper {
|
|
|
28
54
|
}
|
|
29
55
|
}
|
|
30
56
|
catch (error) {
|
|
57
|
+
callFailed = true;
|
|
31
58
|
span.statusCode = 500;
|
|
32
59
|
throw error;
|
|
33
60
|
}
|
|
34
61
|
finally {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
span
|
|
62
|
+
// Span finalisation must never throw: this runs inside the try/catch's
|
|
63
|
+
// finally, so an exception here (e.g. hashText(undefined) when a
|
|
64
|
+
// rejected call() left `output` unset, or JSON.stringify choking on a
|
|
65
|
+
// BigInt/circular output) would replace whatever the catch block above
|
|
66
|
+
// just threw, hiding the caller's real error behind an unrelated
|
|
67
|
+
// TypeError. Telemetry for a failed call still matters, so we still
|
|
68
|
+
// finish and export the span on that path -- just without an output
|
|
69
|
+
// hash/tokens -- and any unexpected error while computing metrics is
|
|
70
|
+
// swallowed rather than allowed to propagate.
|
|
71
|
+
try {
|
|
72
|
+
if (callFailed) {
|
|
73
|
+
span.outputTokens = 0;
|
|
74
|
+
span.totalTokens = span.inputTokens || 0;
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
const outputText = safeStringifyForSpan(output);
|
|
78
|
+
span.outputHash = hashText(outputText);
|
|
79
|
+
const outputTokens = estimateTokens(outputText, options.model);
|
|
80
|
+
span.outputTokens = outputTokens;
|
|
81
|
+
span.totalTokens = (span.inputTokens || 0) + outputTokens;
|
|
82
|
+
}
|
|
83
|
+
span.costUsd = estimateCost(span.totalTokens, options.model || 'gpt-4', options.source);
|
|
84
|
+
}
|
|
85
|
+
catch (finalizationError) {
|
|
86
|
+
console.error('[mcp-observatory] span finalisation failed; continuing without output hash/tokens', finalizationError);
|
|
87
|
+
}
|
|
88
|
+
finally {
|
|
89
|
+
span.finish();
|
|
90
|
+
}
|
|
42
91
|
}
|
|
43
92
|
const decision = this.decide(span, output);
|
|
44
93
|
const result = {
|
|
@@ -87,7 +136,7 @@ export class InvocationWrapper {
|
|
|
87
136
|
return output;
|
|
88
137
|
if (output === null || output === undefined)
|
|
89
138
|
return '';
|
|
90
|
-
return
|
|
139
|
+
return safeStringifyForSpan(output);
|
|
91
140
|
}
|
|
92
141
|
}
|
|
93
142
|
//# sourceMappingURL=wrapper.js.map
|
package/dist/core/wrapper.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wrapper.js","sourceRoot":"","sources":["../../src/core/wrapper.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAC/C,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"wrapper.js","sourceRoot":"","sources":["../../src/core/wrapper.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/demo/server.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/demo/server.ts"],"names":[],"mappings":"AASA,UAAU,QAAQ;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC/B;AAED,qBAAa,SAAS;IACpB,OAAO,CAAC,QAAQ,CAAe;IAC/B,OAAO,CAAC,QAAQ,CAAiB;IACjC,OAAO,CAAC,MAAM,CAAS;;IA+BjB,cAAc,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC;YAcxC,cAAc;YAsBd,YAAY;YAkCZ,WAAW;CAyB1B;AAED,wBAAsB,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAwBnD"}
|
package/dist/demo/server.js
CHANGED
|
@@ -1,10 +1,30 @@
|
|
|
1
1
|
import { ToolProposer } from '../proposal/proposer.js';
|
|
2
2
|
import { CommitVerifier } from '../proposal/verifier.js';
|
|
3
|
+
import { TokenManager } from '../proposal/token.js';
|
|
3
4
|
import { Tracer } from '../core/tracer.js';
|
|
5
|
+
import { ALLOW_DEV_SECRET_ENV, COMMIT_SECRET_ENV, } from '../utils/secrets.js';
|
|
4
6
|
export class MCPServer {
|
|
5
7
|
constructor() {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
+
// The demo is meant to run with no configuration, so it opts itself
|
|
9
|
+
// into the public development secret — loudly, and only if the real
|
|
10
|
+
// one is absent. The opt-in happens here rather than at module scope
|
|
11
|
+
// because `src/index.ts` re-exports this class, and a module-scope
|
|
12
|
+
// `process.env` write would silently weaken every consumer that merely
|
|
13
|
+
// imports the package.
|
|
14
|
+
if (!process.env[COMMIT_SECRET_ENV] &&
|
|
15
|
+
process.env[ALLOW_DEV_SECRET_ENV] !== '1') {
|
|
16
|
+
console.warn(`[mcp-observatory] demo: ${COMMIT_SECRET_ENV} is unset, enabling ` +
|
|
17
|
+
`${ALLOW_DEV_SECRET_ENV}=1 for this process. Never do this outside a demo.`);
|
|
18
|
+
process.env[ALLOW_DEV_SECRET_ENV] = '1';
|
|
19
|
+
}
|
|
20
|
+
// One manager, shared. Issuing and verifying under two independently
|
|
21
|
+
// constructed managers is what made the demo's commit phase fail with
|
|
22
|
+
// `bad_signature` while the random per-instance secret was in place;
|
|
23
|
+
// sharing the instance makes the demo independent of how the secret
|
|
24
|
+
// happens to be resolved.
|
|
25
|
+
const tokenManager = new TokenManager();
|
|
26
|
+
this.proposer = new ToolProposer(tokenManager);
|
|
27
|
+
this.verifier = new CommitVerifier(tokenManager);
|
|
8
28
|
this.tracer = new Tracer('mcp-server');
|
|
9
29
|
}
|
|
10
30
|
async handleToolCall(call) {
|
package/dist/demo/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/demo/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AACvD,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AACzD,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/demo/server.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ export { CommitVerifier } from './proposal/verifier.js';
|
|
|
8
8
|
export { InMemoryProposalStorage, PostgresProposalStorage, } from './proposal/storage.js';
|
|
9
9
|
export type { ProposalOptions, ProposalResult, } from './proposal/proposer.js';
|
|
10
10
|
export { hashText, hashJson, normalizePrompt, computePromptHash, } from './utils/hashing.js';
|
|
11
|
+
export { InsecureDefaultSecretError, resolveSecret, COMMIT_SECRET_ENV, ALLOW_DEV_SECRET_ENV, DEV_COMMIT_SECRET, } from './utils/secrets.js';
|
|
11
12
|
export { getCurrentTimeMs, getCurrentTimeIso, addMs, diffMs, formatDuration, } from './utils/time.js';
|
|
12
13
|
export { estimateTokens, getTokenLimits } from './cost/tokenizer.js';
|
|
13
14
|
export { estimateCost, getPricing } from './cost/pricing.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACtD,YAAY,EAAE,eAAe,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAGvF,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,EACL,uBAAuB,EACvB,uBAAuB,GACxB,MAAM,uBAAuB,CAAC;AAC/B,YAAY,EACV,eAAe,EACf,cAAc,GACf,MAAM,wBAAwB,CAAC;AAGhC,OAAO,EACL,QAAQ,EACR,QAAQ,EACR,eAAe,EACf,iBAAiB,GAClB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,gBAAgB,EAChB,iBAAiB,EACjB,KAAK,EACL,MAAM,EACN,cAAc,GACf,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrE,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAG7D,OAAO,EACL,6BAA6B,EAC7B,iBAAiB,EACjB,qBAAqB,EACrB,2BAA2B,EAC3B,2BAA2B,EAC3B,uBAAuB,GACxB,MAAM,4BAA4B,CAAC;AACpC,YAAY,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AAGvE,OAAO,EACL,gBAAgB,EAChB,cAAc,GACf,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAGrD,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACtD,YAAY,EAAE,eAAe,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAGvF,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,EACL,uBAAuB,EACvB,uBAAuB,GACxB,MAAM,uBAAuB,CAAC;AAC/B,YAAY,EACV,eAAe,EACf,cAAc,GACf,MAAM,wBAAwB,CAAC;AAGhC,OAAO,EACL,QAAQ,EACR,QAAQ,EACR,eAAe,EACf,iBAAiB,GAClB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,0BAA0B,EAC1B,aAAa,EACb,iBAAiB,EACjB,oBAAoB,EACpB,iBAAiB,GAClB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,gBAAgB,EAChB,iBAAiB,EACjB,KAAK,EACL,MAAM,EACN,cAAc,GACf,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrE,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAG7D,OAAO,EACL,6BAA6B,EAC7B,iBAAiB,EACjB,qBAAqB,EACrB,2BAA2B,EAC3B,2BAA2B,EAC3B,uBAAuB,GACxB,MAAM,4BAA4B,CAAC;AACpC,YAAY,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AAGvE,OAAO,EACL,gBAAgB,EAChB,cAAc,GACf,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAGrD,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -9,6 +9,7 @@ export { CommitVerifier } from './proposal/verifier.js';
|
|
|
9
9
|
export { InMemoryProposalStorage, PostgresProposalStorage, } from './proposal/storage.js';
|
|
10
10
|
// Utils
|
|
11
11
|
export { hashText, hashJson, normalizePrompt, computePromptHash, } from './utils/hashing.js';
|
|
12
|
+
export { InsecureDefaultSecretError, resolveSecret, COMMIT_SECRET_ENV, ALLOW_DEV_SECRET_ENV, DEV_COMMIT_SECRET, } from './utils/secrets.js';
|
|
12
13
|
export { getCurrentTimeMs, getCurrentTimeIso, addMs, diffMs, formatDuration, } from './utils/time.js';
|
|
13
14
|
// Cost
|
|
14
15
|
export { estimateTokens, getTokenLimits } from './cost/tokenizer.js';
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO;AACP,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAGtD,kBAAkB;AAClB,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,EACL,uBAAuB,EACvB,uBAAuB,GACxB,MAAM,uBAAuB,CAAC;AAM/B,QAAQ;AACR,OAAO,EACL,QAAQ,EACR,QAAQ,EACR,eAAe,EACf,iBAAiB,GAClB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,gBAAgB,EAChB,iBAAiB,EACjB,KAAK,EACL,MAAM,EACN,cAAc,GACf,MAAM,iBAAiB,CAAC;AAEzB,OAAO;AACP,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrE,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAE7D,gBAAgB;AAChB,OAAO,EACL,6BAA6B,EAC7B,iBAAiB,EACjB,qBAAqB,EACrB,2BAA2B,EAC3B,2BAA2B,EAC3B,uBAAuB,GACxB,MAAM,4BAA4B,CAAC;AAGpC,OAAO;AACP,OAAO,EACL,gBAAgB,EAChB,cAAc,GACf,MAAM,mBAAmB,CAAC;AAG3B,OAAO;AACP,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO;AACP,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAGtD,kBAAkB;AAClB,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,EACL,uBAAuB,EACvB,uBAAuB,GACxB,MAAM,uBAAuB,CAAC;AAM/B,QAAQ;AACR,OAAO,EACL,QAAQ,EACR,QAAQ,EACR,eAAe,EACf,iBAAiB,GAClB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,0BAA0B,EAC1B,aAAa,EACb,iBAAiB,EACjB,oBAAoB,EACpB,iBAAiB,GAClB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,gBAAgB,EAChB,iBAAiB,EACjB,KAAK,EACL,MAAM,EACN,cAAc,GACf,MAAM,iBAAiB,CAAC;AAEzB,OAAO;AACP,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrE,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAE7D,gBAAgB;AAChB,OAAO,EACL,6BAA6B,EAC7B,iBAAiB,EACjB,qBAAqB,EACrB,2BAA2B,EAC3B,2BAA2B,EAC3B,uBAAuB,GACxB,MAAM,4BAA4B,CAAC;AAGpC,OAAO;AACP,OAAO,EACL,gBAAgB,EAChB,cAAc,GACf,MAAM,mBAAmB,CAAC;AAG3B,OAAO;AACP,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC"}
|
|
@@ -13,6 +13,12 @@ export interface ProposalResult {
|
|
|
13
13
|
commitToken?: string;
|
|
14
14
|
fallbackResponse?: unknown;
|
|
15
15
|
}
|
|
16
|
+
/** A composite score, plus how many signals informed it. */
|
|
17
|
+
export interface CompositeScore {
|
|
18
|
+
/** `undefined` when no signal was supplied — not zero. */
|
|
19
|
+
score: number | undefined;
|
|
20
|
+
definedCount: number;
|
|
21
|
+
}
|
|
16
22
|
export interface ScoringOptions {
|
|
17
23
|
outputInstability?: number;
|
|
18
24
|
numericVariance?: number;
|
|
@@ -24,6 +30,21 @@ export declare class ToolProposer {
|
|
|
24
30
|
private reviewThreshold;
|
|
25
31
|
constructor(tokenManager?: TokenManager);
|
|
26
32
|
propose(options: ProposalOptions): Promise<ProposalResult>;
|
|
33
|
+
/**
|
|
34
|
+
* Weighted mean over the signals that were actually supplied.
|
|
35
|
+
*
|
|
36
|
+
* Returns `undefined` when none was, which is not the same as zero. The
|
|
37
|
+
* previous form read each optional signal as `value || 0` and divided by a
|
|
38
|
+
* fixed denominator of 1.0, so an absent signal contributed no risk at full
|
|
39
|
+
* weight: a proposal carrying no signals at all scored 0 and was allowed,
|
|
40
|
+
* with a commit token. That is the fail-open this renormalisation exists to
|
|
41
|
+
* prevent — missing evidence must not read as evidence of safety — and it
|
|
42
|
+
* was reachable from the ordinary API, since all three signals are optional
|
|
43
|
+
* parameters.
|
|
44
|
+
*
|
|
45
|
+
* `|| 0` also conflated a genuine instability of 0 with an absent one. The
|
|
46
|
+
* explicit `undefined` check keeps a real zero in the defined set.
|
|
47
|
+
*/
|
|
27
48
|
private computeCompositeScore;
|
|
28
49
|
private deterministicFallback;
|
|
29
50
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"proposer.d.ts","sourceRoot":"","sources":["../../src/proposal/proposer.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"proposer.d.ts","sourceRoot":"","sources":["../../src/proposal/proposer.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE1C,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,SAAS,GAAG,SAAS,GAAG,QAAQ,CAAC;IACzC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED,4DAA4D;AAC5D,MAAM,WAAW,cAAc;IAC7B,0DAA0D;IAC1D,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,cAAc;IAC7B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,qBAAa,YAAY;IACvB,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,cAAc,CAAe;IACrC,OAAO,CAAC,eAAe,CAAe;gBAE1B,YAAY,CAAC,EAAE,YAAY;IAIjC,OAAO,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,cAAc,CAAC;IA0EhE;;;;;;;;;;;;;;OAcG;IACH,OAAO,CAAC,qBAAqB;IAkC7B,OAAO,CAAC,qBAAqB;CAW9B"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { randomUUID } from 'crypto';
|
|
2
2
|
import { hashJson } from '../utils/hashing.js';
|
|
3
|
+
import { argsExceedLimit } from '../utils/limits.js';
|
|
3
4
|
import { TokenManager } from './token.js';
|
|
4
5
|
export class ToolProposer {
|
|
5
6
|
constructor(tokenManager) {
|
|
@@ -9,12 +10,35 @@ export class ToolProposer {
|
|
|
9
10
|
}
|
|
10
11
|
async propose(options) {
|
|
11
12
|
const proposalId = randomUUID();
|
|
13
|
+
// Cost is linear in input size and the gate cannot bound its own latency,
|
|
14
|
+
// so refuse before hashing rather than after (see utils/limits.ts).
|
|
15
|
+
if (argsExceedLimit(options.toolArgs)) {
|
|
16
|
+
return {
|
|
17
|
+
proposalId,
|
|
18
|
+
status: 'blocked',
|
|
19
|
+
reason: 'input_too_large',
|
|
20
|
+
fallbackResponse: this.deterministicFallback(options.toolName, options.toolArgs),
|
|
21
|
+
};
|
|
22
|
+
}
|
|
12
23
|
const toolArgsHash = hashJson(options.toolArgs);
|
|
13
|
-
const
|
|
24
|
+
const { score, definedCount } = this.computeCompositeScore({
|
|
14
25
|
outputInstability: options.outputInstability,
|
|
15
26
|
numericVariance: options.numericVariance,
|
|
16
27
|
promptDrift: options.promptDrift,
|
|
17
28
|
});
|
|
29
|
+
// No computable signal is not zero risk. Refuse rather than allow, and
|
|
30
|
+
// refuse rather than `review`: a `review` result still issues a usable
|
|
31
|
+
// commit token here, so it is not a refusal.
|
|
32
|
+
if (score === undefined) {
|
|
33
|
+
return {
|
|
34
|
+
proposalId,
|
|
35
|
+
status: 'blocked',
|
|
36
|
+
reason: 'no_signals',
|
|
37
|
+
fallbackResponse: this.deterministicFallback(options.toolName, options.toolArgs),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
const compositeScore = score;
|
|
41
|
+
void definedCount;
|
|
18
42
|
if (compositeScore > this.blockThreshold) {
|
|
19
43
|
return {
|
|
20
44
|
proposalId,
|
|
@@ -48,18 +72,49 @@ export class ToolProposer {
|
|
|
48
72
|
commitToken: token,
|
|
49
73
|
};
|
|
50
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* Weighted mean over the signals that were actually supplied.
|
|
77
|
+
*
|
|
78
|
+
* Returns `undefined` when none was, which is not the same as zero. The
|
|
79
|
+
* previous form read each optional signal as `value || 0` and divided by a
|
|
80
|
+
* fixed denominator of 1.0, so an absent signal contributed no risk at full
|
|
81
|
+
* weight: a proposal carrying no signals at all scored 0 and was allowed,
|
|
82
|
+
* with a commit token. That is the fail-open this renormalisation exists to
|
|
83
|
+
* prevent — missing evidence must not read as evidence of safety — and it
|
|
84
|
+
* was reachable from the ordinary API, since all three signals are optional
|
|
85
|
+
* parameters.
|
|
86
|
+
*
|
|
87
|
+
* `|| 0` also conflated a genuine instability of 0 with an absent one. The
|
|
88
|
+
* explicit `undefined` check keeps a real zero in the defined set.
|
|
89
|
+
*/
|
|
51
90
|
computeCompositeScore(scoring) {
|
|
52
|
-
const instability = Math.max(0, Math.min(1, scoring.outputInstability || 0));
|
|
53
|
-
const variance = Math.max(0, Math.min(1, scoring.numericVariance || 0));
|
|
54
|
-
const drift = Math.max(0, Math.min(1, scoring.promptDrift || 0));
|
|
55
91
|
const weights = {
|
|
56
92
|
instability: 0.5,
|
|
57
93
|
variance: 0.3,
|
|
58
94
|
drift: 0.2,
|
|
59
95
|
};
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
96
|
+
const supplied = [
|
|
97
|
+
[scoring.outputInstability, weights.instability],
|
|
98
|
+
[scoring.numericVariance, weights.variance],
|
|
99
|
+
[scoring.promptDrift, weights.drift],
|
|
100
|
+
];
|
|
101
|
+
let weightedSum = 0;
|
|
102
|
+
let totalWeight = 0;
|
|
103
|
+
let definedCount = 0;
|
|
104
|
+
for (const [value, weight] of supplied) {
|
|
105
|
+
if (value === undefined || value === null || Number.isNaN(value))
|
|
106
|
+
continue;
|
|
107
|
+
weightedSum += Math.max(0, Math.min(1, value)) * weight;
|
|
108
|
+
totalWeight += weight;
|
|
109
|
+
definedCount += 1;
|
|
110
|
+
}
|
|
111
|
+
if (totalWeight === 0) {
|
|
112
|
+
return { score: undefined, definedCount: 0 };
|
|
113
|
+
}
|
|
114
|
+
return {
|
|
115
|
+
score: Math.max(0, Math.min(1, weightedSum / totalWeight)),
|
|
116
|
+
definedCount,
|
|
117
|
+
};
|
|
63
118
|
}
|
|
64
119
|
deterministicFallback(toolName, toolArgs) {
|
|
65
120
|
return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"proposer.js","sourceRoot":"","sources":["../../src/proposal/proposer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AACpC,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"proposer.js","sourceRoot":"","sources":["../../src/proposal/proposer.ts"],"names":[],"mappings":"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"}
|
package/dist/proposal/token.d.ts
CHANGED
|
@@ -13,9 +13,27 @@ export interface TokenVerificationResult {
|
|
|
13
13
|
reason?: string;
|
|
14
14
|
payload?: TokenPayload;
|
|
15
15
|
}
|
|
16
|
+
/** A token string that decodes, but not from its one canonical spelling. */
|
|
17
|
+
export declare class NonCanonicalTokenError extends Error {
|
|
18
|
+
constructor(message: string);
|
|
19
|
+
}
|
|
16
20
|
export declare class TokenManager {
|
|
17
21
|
private secret;
|
|
18
22
|
private tokenExpiryMs;
|
|
23
|
+
/**
|
|
24
|
+
* @param secret HMAC key for commit tokens. Falls back to
|
|
25
|
+
* `MCP_OBSERVATORY_COMMIT_SECRET`. Throws `InsecureDefaultSecretError`
|
|
26
|
+
* when neither is set, unless `MCP_OBSERVATORY_ALLOW_DEV_SECRET=1`.
|
|
27
|
+
*
|
|
28
|
+
* This used to default to `randomBytes(32)`, which made the key
|
|
29
|
+
* per-instance: a restarted or scaled-out process could not verify a
|
|
30
|
+
* token it had issued, and the proposer and the verifier — each of which
|
|
31
|
+
* builds its own `TokenManager` when none is supplied — never shared a
|
|
32
|
+
* key at all. Both failures surfaced as `bad_signature`, which is what a
|
|
33
|
+
* forgery looks like, so missing configuration was indistinguishable
|
|
34
|
+
* from an attack. Resolving the secret explicitly moves that failure to
|
|
35
|
+
* construction time, where the message can name the cause.
|
|
36
|
+
*/
|
|
19
37
|
constructor(secret?: string);
|
|
20
38
|
issueToken(options: {
|
|
21
39
|
proposalId: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"token.d.ts","sourceRoot":"","sources":["../../src/proposal/token.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"token.d.ts","sourceRoot":"","sources":["../../src/proposal/token.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,YAAY,CAAC;CACxB;AAED,4EAA4E;AAC5E,qBAAa,sBAAuB,SAAQ,KAAK;gBACnC,OAAO,EAAE,MAAM;CAK5B;AAmDD,qBAAa,YAAY;IACvB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,aAAa,CAAkB;IAEvC;;;;;;;;;;;;;OAaG;gBACS,MAAM,CAAC,EAAE,MAAM;IAW3B,UAAU,CAAC,OAAO,EAAE;QAClB,UAAU,EAAE,MAAM,CAAC;QACnB,QAAQ,EAAE,MAAM,CAAC;QACjB,YAAY,EAAE,MAAM,CAAC;QACrB,cAAc,EAAE,MAAM,CAAC;QACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,GAAG;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,YAAY,CAAA;KAAE;IAuB5C,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,uBAAuB;IAgCnD,OAAO,CAAC,IAAI;IAKZ,OAAO,CAAC,QAAQ;CAGjB"}
|
package/dist/proposal/token.js
CHANGED
|
@@ -1,9 +1,82 @@
|
|
|
1
1
|
import { createHmac, randomBytes } from 'crypto';
|
|
2
2
|
import { getCurrentTimeMs, addMs } from '../utils/time.js';
|
|
3
|
+
import { COMMIT_SECRET_ENV, DEV_COMMIT_SECRET, resolveSecret, } from '../utils/secrets.js';
|
|
4
|
+
/** A token string that decodes, but not from its one canonical spelling. */
|
|
5
|
+
export class NonCanonicalTokenError extends Error {
|
|
6
|
+
constructor(message) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = 'NonCanonicalTokenError';
|
|
9
|
+
Object.setPrototypeOf(this, NonCanonicalTokenError.prototype);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Decode a token, accepting only the exact string the issuer would produce.
|
|
14
|
+
*
|
|
15
|
+
* The HMAC covers the payload's *values*, not the token *string*, and both
|
|
16
|
+
* layers of the encoding are lenient, so one authorisation had unboundedly
|
|
17
|
+
* many accepted spellings:
|
|
18
|
+
*
|
|
19
|
+
* - Node's base64 decoder silently drops characters outside the alphabet,
|
|
20
|
+
* so `token + "!"`, `token + "=="`, a trailing newline and whitespace
|
|
21
|
+
* injected anywhere in the middle all decoded to identical bytes and
|
|
22
|
+
* verified.
|
|
23
|
+
* - The signature is recomputed from the re-serialised payload object
|
|
24
|
+
* rather than from the received bytes, so any JSON spelling that parses
|
|
25
|
+
* to the same object verified too — a pretty-printed payload, or one
|
|
26
|
+
* with a space after `{`.
|
|
27
|
+
*
|
|
28
|
+
* Replay is still caught either way, because the nonce is inside the
|
|
29
|
+
* signed payload. The cost is in anything keyed on the token string: a
|
|
30
|
+
* caller that records `sha256(token)` on a span or an audit row would file
|
|
31
|
+
* one authorisation under arbitrarily many hashes, and a nonce cache keyed
|
|
32
|
+
* on the string rather than on `payload.nonce` would not catch a replay at
|
|
33
|
+
* all.
|
|
34
|
+
*
|
|
35
|
+
* Requiring both round-trips — re-encoding the bytes must reproduce the
|
|
36
|
+
* base64, and re-serialising the object must reproduce the JSON — makes
|
|
37
|
+
* the token string canonical, so `sha256(token)` identifies exactly one
|
|
38
|
+
* authorisation. This is the same property the Python line states as P3 in
|
|
39
|
+
* `docs/gate-properties.md`; note the encoding here is plain base64, not
|
|
40
|
+
* the base64url Python uses, so the re-encode is checked against the
|
|
41
|
+
* padded standard alphabet that `issueToken` emits.
|
|
42
|
+
*/
|
|
43
|
+
function decodeCanonicalToken(token) {
|
|
44
|
+
const raw = Buffer.from(token, 'base64');
|
|
45
|
+
if (raw.toString('base64') !== token) {
|
|
46
|
+
throw new NonCanonicalTokenError('token is not canonical base64');
|
|
47
|
+
}
|
|
48
|
+
const json = raw.toString('utf8');
|
|
49
|
+
const data = JSON.parse(json);
|
|
50
|
+
if (data === null || typeof data !== 'object' || Array.isArray(data)) {
|
|
51
|
+
throw new NonCanonicalTokenError('token payload is not an object');
|
|
52
|
+
}
|
|
53
|
+
if (JSON.stringify(data) !== json) {
|
|
54
|
+
throw new NonCanonicalTokenError('token payload is not canonical JSON');
|
|
55
|
+
}
|
|
56
|
+
return data;
|
|
57
|
+
}
|
|
3
58
|
export class TokenManager {
|
|
59
|
+
/**
|
|
60
|
+
* @param secret HMAC key for commit tokens. Falls back to
|
|
61
|
+
* `MCP_OBSERVATORY_COMMIT_SECRET`. Throws `InsecureDefaultSecretError`
|
|
62
|
+
* when neither is set, unless `MCP_OBSERVATORY_ALLOW_DEV_SECRET=1`.
|
|
63
|
+
*
|
|
64
|
+
* This used to default to `randomBytes(32)`, which made the key
|
|
65
|
+
* per-instance: a restarted or scaled-out process could not verify a
|
|
66
|
+
* token it had issued, and the proposer and the verifier — each of which
|
|
67
|
+
* builds its own `TokenManager` when none is supplied — never shared a
|
|
68
|
+
* key at all. Both failures surfaced as `bad_signature`, which is what a
|
|
69
|
+
* forgery looks like, so missing configuration was indistinguishable
|
|
70
|
+
* from an attack. Resolving the secret explicitly moves that failure to
|
|
71
|
+
* construction time, where the message can name the cause.
|
|
72
|
+
*/
|
|
4
73
|
constructor(secret) {
|
|
5
74
|
this.tokenExpiryMs = 300000; // 5 minutes
|
|
6
|
-
this.secret =
|
|
75
|
+
this.secret = Buffer.from(resolveSecret(secret, {
|
|
76
|
+
envVar: COMMIT_SECRET_ENV,
|
|
77
|
+
devDefault: DEV_COMMIT_SECRET,
|
|
78
|
+
label: COMMIT_SECRET_ENV,
|
|
79
|
+
}), 'utf8');
|
|
7
80
|
}
|
|
8
81
|
issueToken(options) {
|
|
9
82
|
const now = getCurrentTimeMs();
|
|
@@ -23,8 +96,17 @@ export class TokenManager {
|
|
|
23
96
|
return { token, payload };
|
|
24
97
|
}
|
|
25
98
|
verifyToken(token) {
|
|
99
|
+
let data;
|
|
100
|
+
try {
|
|
101
|
+
data = decodeCanonicalToken(token);
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
if (error instanceof NonCanonicalTokenError) {
|
|
105
|
+
return { valid: false, reason: 'non_canonical_token' };
|
|
106
|
+
}
|
|
107
|
+
return { valid: false, reason: 'invalid_token_format' };
|
|
108
|
+
}
|
|
26
109
|
try {
|
|
27
|
-
const data = JSON.parse(Buffer.from(token, 'base64').toString());
|
|
28
110
|
const { signature, ...payload } = data;
|
|
29
111
|
if (!signature) {
|
|
30
112
|
return { valid: false, reason: 'missing_signature' };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"token.js","sourceRoot":"","sources":["../../src/proposal/token.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"token.js","sourceRoot":"","sources":["../../src/proposal/token.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { test } from 'node:test';
|
|
2
2
|
import { strict as assert } from 'node:assert';
|
|
3
3
|
import { ToolProposer } from '../proposal/proposer.js';
|
|
4
|
+
// `ToolProposer` builds a `TokenManager` when none is supplied, and that
|
|
5
|
+
// constructor now refuses to invent a signing secret. Tests opt into the
|
|
6
|
+
// public development secret. The secret is read when a manager is
|
|
7
|
+
// constructed, not when the module loads, so setting it here is enough;
|
|
8
|
+
// `node --test` gives each file its own process, so it does not leak.
|
|
9
|
+
process.env.MCP_OBSERVATORY_ALLOW_DEV_SECRET = '1';
|
|
4
10
|
test('Proposer creates proposals', async () => {
|
|
5
11
|
const proposer = new ToolProposer();
|
|
6
12
|
const result = await proposer.propose({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"proposer.test.js","sourceRoot":"","sources":["../../src/tests/proposer.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,MAAM,IAAI,MAAM,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"proposer.test.js","sourceRoot":"","sources":["../../src/tests/proposer.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,MAAM,IAAI,MAAM,EAAE,MAAM,aAAa,CAAC;AAE/C,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAGvD,yEAAyE;AACzE,yEAAyE;AACzE,kEAAkE;AAClE,wEAAwE;AACxE,sEAAsE;AACtE,OAAO,CAAC,GAAG,CAAC,gCAAgC,GAAG,GAAG,CAAC;AAEnD,IAAI,CAAC,4BAA4B,EAAE,KAAK,IAAI,EAAE;IAC5C,MAAM,QAAQ,GAAG,IAAI,YAAY,EAAE,CAAC;IAEpC,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,OAAO,CAAC;QACpC,QAAQ,EAAE,gBAAgB;QAC1B,QAAQ,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,EAAE,EAAE,UAAU,EAAE;QACzC,iBAAiB,EAAE,GAAG;KACvB,CAAC,CAAC;IAEH,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;IAC7B,MAAM,CAAC,EAAE,CAAC,CAAC,SAAS,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;AACtE,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,qCAAqC,EAAE,KAAK,IAAI,EAAE;IACrD,MAAM,QAAQ,GAAG,IAAI,YAAY,EAAE,CAAC;IAEpC,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,OAAO,CAAC;QACpC,QAAQ,EAAE,gBAAgB;QAC1B,QAAQ,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,EAAE,SAAS,EAAE;QAC5C,iBAAiB,EAAE,GAAG;QACtB,eAAe,EAAE,GAAG;QACpB,WAAW,EAAE,GAAG;KACjB,CAAC,CAAC;IAEH,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IACvC,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC;AACrC,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,oCAAoC,EAAE,KAAK,IAAI,EAAE;IACpD,MAAM,QAAQ,GAAG,IAAI,YAAY,EAAE,CAAC;IAEpC,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,OAAO,CAAC;QACpC,QAAQ,EAAE,kBAAkB;QAC5B,QAAQ,EAAE,EAAE,SAAS,EAAE,UAAU,EAAE;QACnC,iBAAiB,EAAE,IAAI;QACvB,eAAe,EAAE,IAAI;QACrB,WAAW,EAAE,IAAI;KAClB,CAAC,CAAC;IAEH,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IACvC,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;AAChC,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,0DAA0D,EAAE,KAAK,IAAI,EAAE;IAC1E,MAAM,QAAQ,GAAG,IAAI,YAAY,EAAE,CAAC;IAEpC,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,OAAO,CAAC;QACpC,QAAQ,EAAE,WAAW;QACrB,QAAQ,EAAE,EAAE,IAAI,EAAE,eAAe,EAAE;QACnC,iBAAiB,EAAE,GAAG;KACvB,CAAC,CAAC;IAEH,IAAI,MAAM,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;QAChC,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;IAChC,CAAC;AACH,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"proposerGuards.test.d.ts","sourceRoot":"","sources":["../../src/tests/proposerGuards.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The two fail-opens the guards exist to prevent.
|
|
3
|
+
*
|
|
4
|
+
* Both were reachable from the ordinary API and both returned a usable commit
|
|
5
|
+
* token, so each test asserts the absence of the token as well as the status.
|
|
6
|
+
*/
|
|
7
|
+
import { test } from 'node:test';
|
|
8
|
+
import assert from 'node:assert/strict';
|
|
9
|
+
import { ToolProposer } from '../proposal/proposer.js';
|
|
10
|
+
import { DEFAULT_MAX_INPUT_BYTES, MAX_INPUT_BYTES_ENV, argsExceedLimit, exceedsLimit, maxInputBytes, } from '../utils/limits.js';
|
|
11
|
+
process.env.MCP_OBSERVATORY_ALLOW_DEV_SECRET = '1';
|
|
12
|
+
// --- no evidence must not read as evidence of safety ------------------------
|
|
13
|
+
test('a proposal carrying no signals is refused, not allowed', async () => {
|
|
14
|
+
const result = await new ToolProposer().propose({
|
|
15
|
+
toolName: 'delete_all_users',
|
|
16
|
+
toolArgs: { confirm: true },
|
|
17
|
+
});
|
|
18
|
+
assert.equal(result.status, 'blocked');
|
|
19
|
+
assert.equal(result.reason, 'no_signals');
|
|
20
|
+
assert.equal(result.commitToken, undefined);
|
|
21
|
+
});
|
|
22
|
+
test('a genuine zero signal is an observation and still scores', async () => {
|
|
23
|
+
// Distinct from the case above: 0 is a real measurement, not an absence.
|
|
24
|
+
const result = await new ToolProposer().propose({
|
|
25
|
+
toolName: 'read_file',
|
|
26
|
+
toolArgs: {},
|
|
27
|
+
outputInstability: 0,
|
|
28
|
+
});
|
|
29
|
+
assert.equal(result.status, 'allowed');
|
|
30
|
+
assert.ok(result.commitToken);
|
|
31
|
+
});
|
|
32
|
+
test('one supplied signal is renormalised, not diluted by the absent ones', async () => {
|
|
33
|
+
// Instability 1.0 alone: weight 0.5 over a defined weight of 0.5 is 1.0, so
|
|
34
|
+
// it clears the 0.7 block threshold. Under the old fixed denominator it
|
|
35
|
+
// scored 0.5 and was merely 'review' — the absent signals voted "safe".
|
|
36
|
+
const result = await new ToolProposer().propose({
|
|
37
|
+
toolName: 'drop_table',
|
|
38
|
+
toolArgs: {},
|
|
39
|
+
outputInstability: 1,
|
|
40
|
+
});
|
|
41
|
+
assert.equal(result.status, 'blocked');
|
|
42
|
+
assert.equal(result.reason, 'low_integrity');
|
|
43
|
+
});
|
|
44
|
+
// --- input size is bounded upstream of hashing ------------------------------
|
|
45
|
+
test('an oversized argument mapping is refused before it is hashed', async () => {
|
|
46
|
+
const result = await new ToolProposer().propose({
|
|
47
|
+
toolName: 'upload',
|
|
48
|
+
toolArgs: { blob: 'A'.repeat(DEFAULT_MAX_INPUT_BYTES * 2) },
|
|
49
|
+
outputInstability: 0,
|
|
50
|
+
});
|
|
51
|
+
assert.equal(result.status, 'blocked');
|
|
52
|
+
assert.equal(result.reason, 'input_too_large');
|
|
53
|
+
assert.equal(result.commitToken, undefined);
|
|
54
|
+
});
|
|
55
|
+
test('the ceiling is configurable and a malformed value does not disable it', () => {
|
|
56
|
+
const original = process.env[MAX_INPUT_BYTES_ENV];
|
|
57
|
+
try {
|
|
58
|
+
process.env[MAX_INPUT_BYTES_ENV] = '64';
|
|
59
|
+
assert.equal(maxInputBytes(), 64);
|
|
60
|
+
assert.equal(exceedsLimit('A'.repeat(65)), true);
|
|
61
|
+
assert.equal(exceedsLimit('A'.repeat(64)), false);
|
|
62
|
+
for (const bad of ['0', '-1', 'not-a-number', '']) {
|
|
63
|
+
process.env[MAX_INPUT_BYTES_ENV] = bad;
|
|
64
|
+
assert.equal(maxInputBytes(), DEFAULT_MAX_INPUT_BYTES, `bad value ${bad}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
finally {
|
|
68
|
+
if (original === undefined)
|
|
69
|
+
delete process.env[MAX_INPUT_BYTES_ENV];
|
|
70
|
+
else
|
|
71
|
+
process.env[MAX_INPUT_BYTES_ENV] = original;
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
test('size is measured in UTF-8 bytes, not characters', () => {
|
|
75
|
+
const original = process.env[MAX_INPUT_BYTES_ENV];
|
|
76
|
+
try {
|
|
77
|
+
process.env[MAX_INPUT_BYTES_ENV] = '10';
|
|
78
|
+
// Five 3-byte characters are 15 bytes despite being 5 characters.
|
|
79
|
+
assert.equal(exceedsLimit('あいうえお'), true);
|
|
80
|
+
}
|
|
81
|
+
finally {
|
|
82
|
+
if (original === undefined)
|
|
83
|
+
delete process.env[MAX_INPUT_BYTES_ENV];
|
|
84
|
+
else
|
|
85
|
+
process.env[MAX_INPUT_BYTES_ENV] = original;
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
test('an unserialisable argument mapping counts as over the limit', () => {
|
|
89
|
+
const circular = {};
|
|
90
|
+
circular.self = circular;
|
|
91
|
+
assert.equal(argsExceedLimit(circular), true);
|
|
92
|
+
assert.equal(argsExceedLimit({ n: 1n }), true);
|
|
93
|
+
assert.equal(argsExceedLimit({ ok: 'small' }), false);
|
|
94
|
+
});
|
|
95
|
+
//# sourceMappingURL=proposerGuards.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"proposerGuards.test.js","sourceRoot":"","sources":["../../src/tests/proposerGuards.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"secrets.test.d.ts","sourceRoot":"","sources":["../../src/tests/secrets.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { test } from 'node:test';
|
|
2
|
+
import { strict as assert } from 'node:assert';
|
|
3
|
+
import { TokenManager } from '../proposal/token.js';
|
|
4
|
+
import { ALLOW_DEV_SECRET_ENV, COMMIT_SECRET_ENV, DEV_COMMIT_SECRET, InsecureDefaultSecretError, } from '../utils/secrets.js';
|
|
5
|
+
function withEnv(env, fn) {
|
|
6
|
+
const saved = {};
|
|
7
|
+
for (const key of Object.keys(env)) {
|
|
8
|
+
saved[key] = process.env[key];
|
|
9
|
+
if (env[key] === undefined)
|
|
10
|
+
delete process.env[key];
|
|
11
|
+
else
|
|
12
|
+
process.env[key] = env[key];
|
|
13
|
+
}
|
|
14
|
+
try {
|
|
15
|
+
return fn();
|
|
16
|
+
}
|
|
17
|
+
finally {
|
|
18
|
+
for (const key of Object.keys(saved)) {
|
|
19
|
+
if (saved[key] === undefined)
|
|
20
|
+
delete process.env[key];
|
|
21
|
+
else
|
|
22
|
+
process.env[key] = saved[key];
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
test('TokenManager construction throws when no secret is configured', async () => {
|
|
27
|
+
withEnv({ [COMMIT_SECRET_ENV]: undefined, [ALLOW_DEV_SECRET_ENV]: undefined }, () => {
|
|
28
|
+
assert.throws(() => new TokenManager(), (error) => {
|
|
29
|
+
assert.ok(error instanceof InsecureDefaultSecretError);
|
|
30
|
+
assert.equal(error.name, 'InsecureDefaultSecretError');
|
|
31
|
+
// The message must name the configuration that is missing;
|
|
32
|
+
// a bare `bad_signature` at verify time is what this replaces.
|
|
33
|
+
assert.match(error.message, new RegExp(COMMIT_SECRET_ENV));
|
|
34
|
+
return true;
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
test('TokenManager construction throws when the dev secret is passed explicitly', async () => {
|
|
39
|
+
withEnv({ [ALLOW_DEV_SECRET_ENV]: undefined }, () => {
|
|
40
|
+
assert.throws(() => new TokenManager(DEV_COMMIT_SECRET), InsecureDefaultSecretError);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
test('TokenManager reads the secret from the environment', async () => {
|
|
44
|
+
const token = withEnv({ [COMMIT_SECRET_ENV]: 'env-provided-secret', [ALLOW_DEV_SECRET_ENV]: undefined }, () => {
|
|
45
|
+
const manager = new TokenManager();
|
|
46
|
+
return manager.issueToken({
|
|
47
|
+
proposalId: 'p1',
|
|
48
|
+
toolName: 'transfer_funds',
|
|
49
|
+
toolArgsHash: 'hash',
|
|
50
|
+
compositeScore: 0.1,
|
|
51
|
+
}).token;
|
|
52
|
+
});
|
|
53
|
+
// A second manager built from the same environment verifies the token:
|
|
54
|
+
// this is what a restart or a second replica does, and what a
|
|
55
|
+
// per-instance random secret made impossible.
|
|
56
|
+
const verified = withEnv({ [COMMIT_SECRET_ENV]: 'env-provided-secret', [ALLOW_DEV_SECRET_ENV]: undefined }, () => new TokenManager().verifyToken(token));
|
|
57
|
+
assert.equal(verified.valid, true);
|
|
58
|
+
});
|
|
59
|
+
test('TokenManager construction succeeds under the dev-secret override', async () => {
|
|
60
|
+
withEnv({ [COMMIT_SECRET_ENV]: undefined, [ALLOW_DEV_SECRET_ENV]: '1' }, () => {
|
|
61
|
+
const a = new TokenManager();
|
|
62
|
+
const b = new TokenManager();
|
|
63
|
+
const { token } = a.issueToken({
|
|
64
|
+
proposalId: 'p1',
|
|
65
|
+
toolName: 'transfer_funds',
|
|
66
|
+
toolArgsHash: 'hash',
|
|
67
|
+
compositeScore: 0.1,
|
|
68
|
+
});
|
|
69
|
+
// Two independently constructed managers now agree on the key.
|
|
70
|
+
assert.equal(b.verifyToken(token).valid, true);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
test('demo server completes the commit phase it previously failed', async () => {
|
|
74
|
+
const { MCPServer } = await import('../demo/server.js');
|
|
75
|
+
const server = await withEnv({ [COMMIT_SECRET_ENV]: undefined, [ALLOW_DEV_SECRET_ENV]: '1' }, () => new MCPServer());
|
|
76
|
+
const proposal = (await server.handleToolCall({
|
|
77
|
+
toolName: 'transfer_funds_propose',
|
|
78
|
+
args: { amount: 1000, to: 'acct_bob' },
|
|
79
|
+
}));
|
|
80
|
+
assert.notEqual(proposal.status, 'blocked');
|
|
81
|
+
const commit = (await server.handleToolCall({
|
|
82
|
+
toolName: 'transfer_funds_commit',
|
|
83
|
+
proposalId: proposal.proposalId,
|
|
84
|
+
commitToken: proposal.commitToken,
|
|
85
|
+
args: { amount: 1000, to: 'acct_bob' },
|
|
86
|
+
}));
|
|
87
|
+
// Before the fix this was `{ success: false, reason: 'bad_signature' }`,
|
|
88
|
+
// because the proposer and the verifier each held their own random key.
|
|
89
|
+
assert.equal(commit.reason, undefined);
|
|
90
|
+
assert.equal(commit.success, true);
|
|
91
|
+
});
|
|
92
|
+
//# sourceMappingURL=secrets.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"secrets.test.js","sourceRoot":"","sources":["../../src/tests/secrets.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wrapper.test.d.ts","sourceRoot":"","sources":["../../src/tests/wrapper.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { test } from 'node:test';
|
|
2
|
+
import { strict as assert } from 'node:assert';
|
|
3
|
+
import { InvocationWrapper } from '../core/wrapper.js';
|
|
4
|
+
import { Tracer } from '../core/tracer.js';
|
|
5
|
+
import { hashText } from '../utils/hashing.js';
|
|
6
|
+
import { estimateTokens } from '../cost/tokenizer.js';
|
|
7
|
+
class BoomError extends Error {
|
|
8
|
+
constructor(message) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.name = 'BoomError';
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* `invoke()` never hands its internal span back out on the failure path (it
|
|
15
|
+
* only throws), so to assert on span state after a rejection we spy on
|
|
16
|
+
* `Tracer.prototype.startSpan` and capture every span it creates.
|
|
17
|
+
*/
|
|
18
|
+
function captureSpans() {
|
|
19
|
+
const original = Tracer.prototype.startSpan;
|
|
20
|
+
const spans = [];
|
|
21
|
+
Tracer.prototype.startSpan = function (...args) {
|
|
22
|
+
const span = original.apply(this, args);
|
|
23
|
+
spans.push(span);
|
|
24
|
+
return span;
|
|
25
|
+
};
|
|
26
|
+
return {
|
|
27
|
+
spans,
|
|
28
|
+
restore: () => {
|
|
29
|
+
Tracer.prototype.startSpan = original;
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
test('a rejecting sync call() propagates the original error unchanged', async () => {
|
|
34
|
+
const wrapper = new InvocationWrapper('test-service');
|
|
35
|
+
const original = new BoomError('tool exploded');
|
|
36
|
+
await assert.rejects(() => wrapper.invoke({
|
|
37
|
+
source: 'model',
|
|
38
|
+
prompt: 'do the thing',
|
|
39
|
+
call: () => {
|
|
40
|
+
throw original;
|
|
41
|
+
},
|
|
42
|
+
}), (error) => {
|
|
43
|
+
// Must be the exact same error instance -- not a TypeError from
|
|
44
|
+
// hashText(undefined) replacing it in the `finally` block.
|
|
45
|
+
assert.equal(error, original);
|
|
46
|
+
assert.ok(error instanceof BoomError);
|
|
47
|
+
assert.equal(error.message, 'tool exploded');
|
|
48
|
+
return true;
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
test('a rejecting async call() propagates the original error unchanged', async () => {
|
|
52
|
+
const wrapper = new InvocationWrapper('test-service');
|
|
53
|
+
const original = new Error('async rejection');
|
|
54
|
+
await assert.rejects(() => wrapper.invoke({
|
|
55
|
+
source: 'model',
|
|
56
|
+
prompt: 'do the thing',
|
|
57
|
+
call: async () => {
|
|
58
|
+
throw original;
|
|
59
|
+
},
|
|
60
|
+
}), (error) => {
|
|
61
|
+
assert.equal(error, original);
|
|
62
|
+
assert.equal(error.message, 'async rejection');
|
|
63
|
+
return true;
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
test('failed call: span is still finished with statusCode 500, no output hash, zero output tokens', async () => {
|
|
67
|
+
const wrapper = new InvocationWrapper('test-service');
|
|
68
|
+
const capture = captureSpans();
|
|
69
|
+
try {
|
|
70
|
+
await assert.rejects(() => wrapper.invoke({
|
|
71
|
+
source: 'model',
|
|
72
|
+
prompt: 'do the thing',
|
|
73
|
+
call: () => {
|
|
74
|
+
throw new Error('boom');
|
|
75
|
+
},
|
|
76
|
+
}));
|
|
77
|
+
assert.equal(capture.spans.length, 1);
|
|
78
|
+
const [span] = capture.spans;
|
|
79
|
+
assert.equal(span.statusCode, 500);
|
|
80
|
+
assert.ok(span.endTime, 'span.finish() should have been called');
|
|
81
|
+
assert.equal(span.outputHash, undefined);
|
|
82
|
+
assert.equal(span.outputTokens, 0);
|
|
83
|
+
assert.equal(span.totalTokens, span.inputTokens || 0);
|
|
84
|
+
assert.equal(typeof span.costUsd, 'number');
|
|
85
|
+
}
|
|
86
|
+
finally {
|
|
87
|
+
capture.restore();
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
test('successful call still produces the pre-fix outputHash and outputTokens', async () => {
|
|
91
|
+
const wrapper = new InvocationWrapper('test-service');
|
|
92
|
+
const output = { result: 'ok', count: 3 };
|
|
93
|
+
const result = await wrapper.invoke({
|
|
94
|
+
source: 'model',
|
|
95
|
+
prompt: 'do the thing',
|
|
96
|
+
call: () => output,
|
|
97
|
+
});
|
|
98
|
+
const expectedText = JSON.stringify(output);
|
|
99
|
+
assert.equal(result.span.outputHash, hashText(expectedText));
|
|
100
|
+
assert.equal(result.span.outputTokens, estimateTokens(expectedText, undefined));
|
|
101
|
+
assert.equal(result.span.totalTokens, (result.span.inputTokens || 0) + result.span.outputTokens);
|
|
102
|
+
assert.equal(result.output, output);
|
|
103
|
+
assert.notEqual(result.span.endTime, undefined);
|
|
104
|
+
});
|
|
105
|
+
test('invoke() does not throw when a successful output contains a BigInt (JSON.stringify would throw)', async () => {
|
|
106
|
+
const wrapper = new InvocationWrapper('test-service');
|
|
107
|
+
const output = { big: 10n };
|
|
108
|
+
const result = await wrapper.invoke({
|
|
109
|
+
source: 'model',
|
|
110
|
+
prompt: 'do the thing',
|
|
111
|
+
call: () => output,
|
|
112
|
+
});
|
|
113
|
+
assert.ok(result.span.outputHash);
|
|
114
|
+
assert.equal(typeof result.span.outputTokens, 'number');
|
|
115
|
+
assert.equal(result.decision.action, 'allow');
|
|
116
|
+
});
|
|
117
|
+
test('invoke() does not throw when a successful output is circular (JSON.stringify would throw)', async () => {
|
|
118
|
+
const wrapper = new InvocationWrapper('test-service');
|
|
119
|
+
const output = { name: 'circular' };
|
|
120
|
+
output.self = output;
|
|
121
|
+
const result = await wrapper.invoke({
|
|
122
|
+
source: 'model',
|
|
123
|
+
prompt: 'do the thing',
|
|
124
|
+
call: () => output,
|
|
125
|
+
});
|
|
126
|
+
assert.ok(result.span.outputHash);
|
|
127
|
+
assert.equal(typeof result.span.outputTokens, 'number');
|
|
128
|
+
assert.equal(result.decision.action, 'allow');
|
|
129
|
+
});
|
|
130
|
+
test('invoke() does not throw when a successful output is undefined (JSON.stringify returns the value undefined)', async () => {
|
|
131
|
+
const wrapper = new InvocationWrapper('test-service');
|
|
132
|
+
const result = await wrapper.invoke({
|
|
133
|
+
source: 'model',
|
|
134
|
+
prompt: 'do the thing',
|
|
135
|
+
call: () => undefined,
|
|
136
|
+
});
|
|
137
|
+
// Empty/undefined output is a legitimate 'blocked' decision, not a crash.
|
|
138
|
+
assert.equal(result.decision.action, 'block');
|
|
139
|
+
assert.equal(result.output, undefined);
|
|
140
|
+
});
|
|
141
|
+
//# sourceMappingURL=wrapper.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wrapper.test.js","sourceRoot":"","sources":["../../src/tests/wrapper.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Input-size ceiling applied before anything expensive touches a payload.
|
|
3
|
+
*
|
|
4
|
+
* Every step of the gate is linear in the size of its input — canonical JSON
|
|
5
|
+
* serialisation, SHA-256 hashing, the signal computations — so the gate cannot
|
|
6
|
+
* bound its own latency. A caller (or an attacker) handing it a multi-megabyte
|
|
7
|
+
* argument mapping buys multi-megabyte hashing on the request path.
|
|
8
|
+
*
|
|
9
|
+
* The control therefore has to sit *upstream of scoring*: refuse the call
|
|
10
|
+
* before hashing rather than after. This mirrors the Python line's P5, where
|
|
11
|
+
* the same bound is documented in `docs/gate-properties.md` and the same
|
|
12
|
+
* `MCP_OBSERVATORY_MAX_INPUT_BYTES` variable names the ceiling.
|
|
13
|
+
*
|
|
14
|
+
* Read from the environment on each call rather than cached at module load, so
|
|
15
|
+
* a test or a host can change it without re-importing the module.
|
|
16
|
+
*/
|
|
17
|
+
export declare const MAX_INPUT_BYTES_ENV = "MCP_OBSERVATORY_MAX_INPUT_BYTES";
|
|
18
|
+
/** Default ceiling, in bytes. Matches the Python package's default. */
|
|
19
|
+
export declare const DEFAULT_MAX_INPUT_BYTES = 10240;
|
|
20
|
+
export declare function maxInputBytes(): number;
|
|
21
|
+
/** True when `value`'s UTF-8 length exceeds the configured ceiling. */
|
|
22
|
+
export declare function exceedsLimit(value: string | undefined | null): boolean;
|
|
23
|
+
/**
|
|
24
|
+
* True when any argument mapping is over the ceiling.
|
|
25
|
+
*
|
|
26
|
+
* Serialising to measure is itself linear, but it is one pass with no hashing
|
|
27
|
+
* and no signal work behind it, which is the cost the ceiling exists to avoid.
|
|
28
|
+
* A value that cannot be serialised at all (a circular reference, a BigInt) is
|
|
29
|
+
* treated as over the limit: the gate cannot hash what it cannot serialise, so
|
|
30
|
+
* refusing is the fail-secure reading rather than proceeding to a crash.
|
|
31
|
+
*/
|
|
32
|
+
export declare function argsExceedLimit(args: unknown): boolean;
|
|
33
|
+
//# sourceMappingURL=limits.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"limits.d.ts","sourceRoot":"","sources":["../../src/utils/limits.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,eAAO,MAAM,mBAAmB,oCAAoC,CAAC;AAErE,uEAAuE;AACvE,eAAO,MAAM,uBAAuB,QAAQ,CAAC;AAE7C,wBAAgB,aAAa,IAAI,MAAM,CAOtC;AAED,uEAAuE;AACvE,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,OAAO,CAGtE;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAQtD"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Input-size ceiling applied before anything expensive touches a payload.
|
|
3
|
+
*
|
|
4
|
+
* Every step of the gate is linear in the size of its input — canonical JSON
|
|
5
|
+
* serialisation, SHA-256 hashing, the signal computations — so the gate cannot
|
|
6
|
+
* bound its own latency. A caller (or an attacker) handing it a multi-megabyte
|
|
7
|
+
* argument mapping buys multi-megabyte hashing on the request path.
|
|
8
|
+
*
|
|
9
|
+
* The control therefore has to sit *upstream of scoring*: refuse the call
|
|
10
|
+
* before hashing rather than after. This mirrors the Python line's P5, where
|
|
11
|
+
* the same bound is documented in `docs/gate-properties.md` and the same
|
|
12
|
+
* `MCP_OBSERVATORY_MAX_INPUT_BYTES` variable names the ceiling.
|
|
13
|
+
*
|
|
14
|
+
* Read from the environment on each call rather than cached at module load, so
|
|
15
|
+
* a test or a host can change it without re-importing the module.
|
|
16
|
+
*/
|
|
17
|
+
export const MAX_INPUT_BYTES_ENV = 'MCP_OBSERVATORY_MAX_INPUT_BYTES';
|
|
18
|
+
/** Default ceiling, in bytes. Matches the Python package's default. */
|
|
19
|
+
export const DEFAULT_MAX_INPUT_BYTES = 10240;
|
|
20
|
+
export function maxInputBytes() {
|
|
21
|
+
const raw = process.env[MAX_INPUT_BYTES_ENV];
|
|
22
|
+
if (raw === undefined || raw === '')
|
|
23
|
+
return DEFAULT_MAX_INPUT_BYTES;
|
|
24
|
+
const parsed = Number(raw);
|
|
25
|
+
// A malformed ceiling must not silently disable the limit.
|
|
26
|
+
if (!Number.isFinite(parsed) || parsed <= 0)
|
|
27
|
+
return DEFAULT_MAX_INPUT_BYTES;
|
|
28
|
+
return Math.floor(parsed);
|
|
29
|
+
}
|
|
30
|
+
/** True when `value`'s UTF-8 length exceeds the configured ceiling. */
|
|
31
|
+
export function exceedsLimit(value) {
|
|
32
|
+
if (value === undefined || value === null)
|
|
33
|
+
return false;
|
|
34
|
+
return Buffer.byteLength(value, 'utf8') > maxInputBytes();
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* True when any argument mapping is over the ceiling.
|
|
38
|
+
*
|
|
39
|
+
* Serialising to measure is itself linear, but it is one pass with no hashing
|
|
40
|
+
* and no signal work behind it, which is the cost the ceiling exists to avoid.
|
|
41
|
+
* A value that cannot be serialised at all (a circular reference, a BigInt) is
|
|
42
|
+
* treated as over the limit: the gate cannot hash what it cannot serialise, so
|
|
43
|
+
* refusing is the fail-secure reading rather than proceeding to a crash.
|
|
44
|
+
*/
|
|
45
|
+
export function argsExceedLimit(args) {
|
|
46
|
+
let serialised;
|
|
47
|
+
try {
|
|
48
|
+
serialised = JSON.stringify(args) ?? '';
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
return exceedsLimit(serialised);
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=limits.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"limits.js","sourceRoot":"","sources":["../../src/utils/limits.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,MAAM,CAAC,MAAM,mBAAmB,GAAG,iCAAiC,CAAC;AAErE,uEAAuE;AACvE,MAAM,CAAC,MAAM,uBAAuB,GAAG,KAAK,CAAC;AAE7C,MAAM,UAAU,aAAa;IAC3B,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC;IAC7C,IAAI,GAAG,KAAK,SAAS,IAAI,GAAG,KAAK,EAAE;QAAE,OAAO,uBAAuB,CAAC;IACpE,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;IAC3B,2DAA2D;IAC3D,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,MAAM,IAAI,CAAC;QAAE,OAAO,uBAAuB,CAAC;IAC5E,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;AAC5B,CAAC;AAED,uEAAuE;AACvE,MAAM,UAAU,YAAY,CAAC,KAAgC;IAC3D,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IACxD,OAAO,MAAM,CAAC,UAAU,CAAC,KAAK,EAAE,MAAM,CAAC,GAAG,aAAa,EAAE,CAAC;AAC5D,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,eAAe,CAAC,IAAa;IAC3C,IAAI,UAAkB,CAAC;IACvB,IAAI,CAAC;QACH,UAAU,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;IAC1C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO,YAAY,CAAC,UAAU,CAAC,CAAC;AAClC,CAAC"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fail-closed resolution for the HMAC signing secret used by commit tokens.
|
|
3
|
+
*
|
|
4
|
+
* `TokenManager` used to fall back to `randomBytes(32)` whenever no secret
|
|
5
|
+
* was passed. That is worse than a hardcoded default rather than better:
|
|
6
|
+
* the key is unguessable, but it is also unshareable. Two `TokenManager`
|
|
7
|
+
* instances in one process (the bundled demo builds one inside
|
|
8
|
+
* `ToolProposer` and another inside `CommitVerifier`) never agreed on a
|
|
9
|
+
* key, so every commit failed with `bad_signature`; and a process that
|
|
10
|
+
* restarted, or a second replica behind a load balancer, could not verify
|
|
11
|
+
* a token it had itself issued moments earlier. The failure surfaced as
|
|
12
|
+
* `bad_signature` — indistinguishable from an actual forgery — with
|
|
13
|
+
* nothing pointing at the missing configuration that caused it.
|
|
14
|
+
*
|
|
15
|
+
* `resolveSecret` makes the development key explicit and opt-in instead:
|
|
16
|
+
* the secret comes from the caller or the environment, and anything else
|
|
17
|
+
* throws at construction time, where the cause is obvious, rather than at
|
|
18
|
+
* verification time, where it is not.
|
|
19
|
+
*/
|
|
20
|
+
export declare const COMMIT_SECRET_ENV = "MCP_OBSERVATORY_COMMIT_SECRET";
|
|
21
|
+
export declare const ALLOW_DEV_SECRET_ENV = "MCP_OBSERVATORY_ALLOW_DEV_SECRET";
|
|
22
|
+
/**
|
|
23
|
+
* The documented development secret. It is public — it is in this file and
|
|
24
|
+
* in every clone of the repository — and is only ever used when
|
|
25
|
+
* `MCP_OBSERVATORY_ALLOW_DEV_SECRET=1` asks for it explicitly.
|
|
26
|
+
*/
|
|
27
|
+
export declare const DEV_COMMIT_SECRET = "dev-commit-secret";
|
|
28
|
+
/** Raised when a signing secret would fall back to a known-insecure default. */
|
|
29
|
+
export declare class InsecureDefaultSecretError extends Error {
|
|
30
|
+
constructor(message: string);
|
|
31
|
+
}
|
|
32
|
+
export interface ResolveSecretOptions {
|
|
33
|
+
envVar: string;
|
|
34
|
+
devDefault: string;
|
|
35
|
+
label: string;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Resolve a signing secret, refusing to silently use a development default.
|
|
39
|
+
*
|
|
40
|
+
* Returns `explicit` when given, otherwise `process.env[envVar]`. If neither
|
|
41
|
+
* is set, or the resolved value is the known development default (someone
|
|
42
|
+
* copied it out of an example), the secret is treated as insecure: this
|
|
43
|
+
* throws unless `MCP_OBSERVATORY_ALLOW_DEV_SECRET=1`, in which case
|
|
44
|
+
* `devDefault` is returned and a warning is emitted once per process.
|
|
45
|
+
*/
|
|
46
|
+
export declare function resolveSecret(explicit: string | undefined, options: ResolveSecretOptions): string;
|
|
47
|
+
//# sourceMappingURL=secrets.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"secrets.d.ts","sourceRoot":"","sources":["../../src/utils/secrets.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,eAAO,MAAM,iBAAiB,kCAAkC,CAAC;AACjE,eAAO,MAAM,oBAAoB,qCAAqC,CAAC;AAEvE;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,sBAAsB,CAAC;AAErD,gFAAgF;AAChF,qBAAa,0BAA2B,SAAQ,KAAK;gBACvC,OAAO,EAAE,MAAM;CAM5B;AAID,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,OAAO,EAAE,oBAAoB,GAC5B,MAAM,CA0BR"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fail-closed resolution for the HMAC signing secret used by commit tokens.
|
|
3
|
+
*
|
|
4
|
+
* `TokenManager` used to fall back to `randomBytes(32)` whenever no secret
|
|
5
|
+
* was passed. That is worse than a hardcoded default rather than better:
|
|
6
|
+
* the key is unguessable, but it is also unshareable. Two `TokenManager`
|
|
7
|
+
* instances in one process (the bundled demo builds one inside
|
|
8
|
+
* `ToolProposer` and another inside `CommitVerifier`) never agreed on a
|
|
9
|
+
* key, so every commit failed with `bad_signature`; and a process that
|
|
10
|
+
* restarted, or a second replica behind a load balancer, could not verify
|
|
11
|
+
* a token it had itself issued moments earlier. The failure surfaced as
|
|
12
|
+
* `bad_signature` — indistinguishable from an actual forgery — with
|
|
13
|
+
* nothing pointing at the missing configuration that caused it.
|
|
14
|
+
*
|
|
15
|
+
* `resolveSecret` makes the development key explicit and opt-in instead:
|
|
16
|
+
* the secret comes from the caller or the environment, and anything else
|
|
17
|
+
* throws at construction time, where the cause is obvious, rather than at
|
|
18
|
+
* verification time, where it is not.
|
|
19
|
+
*/
|
|
20
|
+
export const COMMIT_SECRET_ENV = 'MCP_OBSERVATORY_COMMIT_SECRET';
|
|
21
|
+
export const ALLOW_DEV_SECRET_ENV = 'MCP_OBSERVATORY_ALLOW_DEV_SECRET';
|
|
22
|
+
/**
|
|
23
|
+
* The documented development secret. It is public — it is in this file and
|
|
24
|
+
* in every clone of the repository — and is only ever used when
|
|
25
|
+
* `MCP_OBSERVATORY_ALLOW_DEV_SECRET=1` asks for it explicitly.
|
|
26
|
+
*/
|
|
27
|
+
export const DEV_COMMIT_SECRET = 'dev-commit-secret';
|
|
28
|
+
/** Raised when a signing secret would fall back to a known-insecure default. */
|
|
29
|
+
export class InsecureDefaultSecretError extends Error {
|
|
30
|
+
constructor(message) {
|
|
31
|
+
super(message);
|
|
32
|
+
this.name = 'InsecureDefaultSecretError';
|
|
33
|
+
// Required for `instanceof` to work when compiled to ES2020 targets.
|
|
34
|
+
Object.setPrototypeOf(this, InsecureDefaultSecretError.prototype);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
let devSecretWarned = false;
|
|
38
|
+
/**
|
|
39
|
+
* Resolve a signing secret, refusing to silently use a development default.
|
|
40
|
+
*
|
|
41
|
+
* Returns `explicit` when given, otherwise `process.env[envVar]`. If neither
|
|
42
|
+
* is set, or the resolved value is the known development default (someone
|
|
43
|
+
* copied it out of an example), the secret is treated as insecure: this
|
|
44
|
+
* throws unless `MCP_OBSERVATORY_ALLOW_DEV_SECRET=1`, in which case
|
|
45
|
+
* `devDefault` is returned and a warning is emitted once per process.
|
|
46
|
+
*/
|
|
47
|
+
export function resolveSecret(explicit, options) {
|
|
48
|
+
const fromEnv = process.env[options.envVar];
|
|
49
|
+
const value = explicit !== undefined ? explicit : fromEnv;
|
|
50
|
+
if (value === undefined || value === '' || value === options.devDefault) {
|
|
51
|
+
if (process.env[ALLOW_DEV_SECRET_ENV] === '1') {
|
|
52
|
+
if (!devSecretWarned) {
|
|
53
|
+
devSecretWarned = true;
|
|
54
|
+
console.warn(`[mcp-observatory] ${ALLOW_DEV_SECRET_ENV}=1: signing commit tokens ` +
|
|
55
|
+
`with the public development secret. Tokens issued by this process ` +
|
|
56
|
+
`can be forged by anyone. Set ${options.envVar} for any real deployment.`);
|
|
57
|
+
}
|
|
58
|
+
return options.devDefault;
|
|
59
|
+
}
|
|
60
|
+
throw new InsecureDefaultSecretError(`${options.label} is unset (or set to the known development default). ` +
|
|
61
|
+
`Set ${options.envVar} to a strong, private secret shared by every ` +
|
|
62
|
+
`process that issues or verifies commit tokens, or set ` +
|
|
63
|
+
`${ALLOW_DEV_SECRET_ENV}=1 for local development, demos, and tests ` +
|
|
64
|
+
`only — never in production.`);
|
|
65
|
+
}
|
|
66
|
+
return value;
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=secrets.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"secrets.js","sourceRoot":"","sources":["../../src/utils/secrets.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,MAAM,CAAC,MAAM,iBAAiB,GAAG,+BAA+B,CAAC;AACjE,MAAM,CAAC,MAAM,oBAAoB,GAAG,kCAAkC,CAAC;AAEvE;;;;GAIG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,mBAAmB,CAAC;AAErD,gFAAgF;AAChF,MAAM,OAAO,0BAA2B,SAAQ,KAAK;IACnD,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,4BAA4B,CAAC;QACzC,qEAAqE;QACrE,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,0BAA0B,CAAC,SAAS,CAAC,CAAC;IACpE,CAAC;CACF;AAED,IAAI,eAAe,GAAG,KAAK,CAAC;AAQ5B;;;;;;;;GAQG;AACH,MAAM,UAAU,aAAa,CAC3B,QAA4B,EAC5B,OAA6B;IAE7B,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IAC5C,MAAM,KAAK,GAAG,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC;IAE1D,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,EAAE,IAAI,KAAK,KAAK,OAAO,CAAC,UAAU,EAAE,CAAC;QACxE,IAAI,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC,KAAK,GAAG,EAAE,CAAC;YAC9C,IAAI,CAAC,eAAe,EAAE,CAAC;gBACrB,eAAe,GAAG,IAAI,CAAC;gBACvB,OAAO,CAAC,IAAI,CACV,qBAAqB,oBAAoB,4BAA4B;oBACnE,oEAAoE;oBACpE,gCAAgC,OAAO,CAAC,MAAM,2BAA2B,CAC5E,CAAC;YACJ,CAAC;YACD,OAAO,OAAO,CAAC,UAAU,CAAC;QAC5B,CAAC;QACD,MAAM,IAAI,0BAA0B,CAClC,GAAG,OAAO,CAAC,KAAK,uDAAuD;YACrE,OAAO,OAAO,CAAC,MAAM,+CAA+C;YACpE,wDAAwD;YACxD,GAAG,oBAAoB,6CAA6C;YACpE,6BAA6B,CAChC,CAAC;IACJ,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC"}
|