#126

Settlement accepts inherited names as declared credit pools

Opensdivyanshu90opened 18d ago

I found that endpoint settlement accepts vendor claims for inherited property names as if they were declared credit pools.

With only default declared, {constructor: 5, toString: 1} becomes the authoritative usage.credits vector. The host receives consumption for pools absent from the rate card instead of the documented FN_CONTRACT rejection.

Reproduction

Save the following as repro-credit-pool.ts at the repository root and run:

deno run --allow-read --allow-env repro-credit-pool.ts

The transport is mocked; no credentials or network calls are needed.

import { defineEndpoint, defineProvider, presets, sealUnit } from "@shared/core";
import { compileBundle } from "@shared/compiler";
import { directTransport, Engine, ENGINE_VERSION } from "@monid/connector-engine";

const bundle = await compileBundle([{
    provider: defineProvider({
        name: "demo",
        meta: { displayName: "Demo", summary: "Demo provider." },
        auth: { inject: presets.auth.header("x-demo-key") },
        request: { baseUrl: "https://api.demo.test" },
        usage: { credits: { default: { label: "Demo credits" } } },
    }),
    endpoints: [{
        name: "read",
        def: defineEndpoint({
            meta: { displayName: "Read", summary: "Reads data.", categories: ["demo-search"] },
            request: { method: "GET", path: "/read" },
            usage: {
                model: { kind: "PER_CALL", consumes: { credit: "default", amount: 1 } },
                consolidate: () => ({ credits: { constructor: 5, toString: 1 } }),
            },
        }),
    }],
}], {
    compilerVersion: "0.1.0", builtWithEngineVersion: ENGINE_VERSION,
    catalogVersion: "0.0.0-test", generatedAt: "1970-01-01T00:00:00.000Z",
    leafCategories: [{ id: "demo-search", displayName: "Demo Search" }],
});
const engine = new Engine({ transport: directTransport({
    params: () => Promise.resolve({ apiKey: "test" }),
    fetch: () => Promise.resolve(new Response("{}")),
}) });
const endpoint = await engine.load(sealUnit(bundle, "demo#read"));
console.log(JSON.stringify((await endpoint.run({})).usage));

Observed output:

{"credits":{"constructor":5,"toString":1},"evidence":{"CALL":1},"mismatch":{"derived":{"default":1}}}

Expected: reject with EngineErrorCode.FN_CONTRACT because neither claimed pool is declared.

Cause and proposed fix

In LoadedEndpoint.settle (engine/engine.ts), the undeclared-pool guard uses pool in doc.usage.credits. The in operator includes Object.prototype members. constructor, toString, valueOf, and hasOwnProperty therefore bypass the guard.

Use Object.hasOwn(doc.usage.credits, pool) and add regression tests that run settlement with these undeclared names. Existing declared-pool settlement should continue to pass.

This is separate from #121: it affects the credit-pool declaration guard, without using an inherited JSONPath lookup.

Activity

0

No activity yet.