#148

Duplicate provider names silently overwrite documents and make compilation order-dependent

Opensdivyanshu90opened 14d ago

I reproduced compileBundle accepting multiple connector sources with the same provider.name. It combines their distinct endpoints but retains only the last provider document. Reversing the input order changes the surviving provider metadata and the compiled bundle.

This silently loses one provider definition and makes a successful compilation depend on input enumeration order, despite the compiler's deterministic sorting rule. Distinct endpoint paths avoid the existing duplicate-endpoint guard.

Reproduction

Save this as repro-duplicate-provider.ts at the repository root, then run:

deno run --check --allow-read --allow-env repro-duplicate-provider.ts

It compiles in-memory sources and needs no network or credentials.

import { compileBundle } from "@shared/compiler";
import { defineEndpoint, defineProvider, presets, stableStringify } from "@shared/core";

const options = {
    compilerVersion: "0.1.0", builtWithEngineVersion: "0.5.0",
    catalogVersion: "0.0.0-test", generatedAt: "1970-01-01T00:00:00.000Z",
    leafCategories: [{ id: "demo-cat", displayName: "Demo" }],
};
function connector(displayName: string, path: string) {
    return {
        provider: defineProvider({
            name: "demo", meta: { displayName, summary: "Demo provider." },
            auth: { inject: presets.auth.header("x-key") },
            request: { baseUrl: "https://api.demo.test" },
            usage: { model: { kind: "FREE" } },
        }),
        endpoints: [{ name: path, def: defineEndpoint({
            meta: { displayName: "Read", summary: "Reads data.", categories: ["demo-cat"] },
            request: { method: "GET", path: `/${path}` },
        }) }],
    };
}
const sources = [connector("First", "one"), connector("Second", "two")];
const forward = await compileBundle(sources, options);
const reverse = await compileBundle([...sources].reverse(), options);
console.log("duplicate provider metadata:", forward.providers.demo.meta.displayName, reverse.providers.demo.meta.displayName);
console.log("combined endpoints:", Object.keys(forward.endpoints));
console.log("order-independent:", stableStringify(forward) === stableStringify(reverse));

Observed:

duplicate provider metadata: Second First
combined endpoints: [ "demo#one", "demo#two" ]
order-independent: false

Expected: both input orders reject with a CompileError whose code is DOC_MALFORMED and whose message identifies the duplicate provider name. Two different provider names should continue to compile deterministically.

Root cause and proposed resolution

shared/compiler/compile.ts validates provider definitions and sorts by provider.name, but never checks that those names are unique. Both sources write to providers[providerName], so the later one overwrites the first. Their endpoint identities differ, so compilation succeeds with a combined endpoint map and only one surviving provider document.

Reject duplicate provider names during intake, before compiling documents. Add a regression using distinct endpoint paths in both source orders and retain a positive test for distinct providers and order-independent output.

Activity

0

No activity yet.