Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 30 additions & 1 deletion docs/adr/0005-parser-independent-relation-completion.md
Original file line number Diff line number Diff line change
Expand Up @@ -484,6 +484,12 @@ state preparation and suppresses later revision listeners; an accidental
Promise result receives best-effort detached rejection draining. Coordinator
disposal revokes the preparation closure before external cleanup. The hook
remains package-private and is not a provider or session extension point.
The combined search coordinator also installs one package-owned disposal
target. Epoch self-quarantine makes the outer coordinator inert before
subscription cleanup continues, so search work cannot outlive its epoch
authority. The target is receiver-free, invoked at most once, and its failure
cannot reopen disposal. It is synchronously exact-return and must return
`undefined`; any other runtime result is detached and rejection-drained.

A search that discovers a higher epoch supersedes itself instead of publishing
against its older captured revision. Pages and cache entries from different
Expand Down Expand Up @@ -555,7 +561,9 @@ callback can always create a poisoned or independent unhandled rejection that
no JavaScript library can retroactively contain. Legitimate asynchronous
teardown must consume or report its own failure before the cleanup closure
returns `undefined`. The valid `undefined` path returns without Promise
allocation or a microtask. A closure avoids a structural TypeScript
allocation or a microtask. Detached assimilation uses module-captured Promise
intrinsics, so replacing the global Promise constructor cannot disable the
drain. A closure avoids a structural TypeScript
contract that would accept class instances or inherited methods which the
hostile runtime boundary could not safely validate. Dropping the last owner
removes the complete scope incarnation before cleanup. A later join creates a
Expand Down Expand Up @@ -654,6 +662,8 @@ outcomes settle through a discriminated request result.
Completion is latest-wins per session:

- a new completion request supersedes the previous request;
- a new request that cannot capture an active epoch still supersedes and
detaches the previous request before reporting its unavailable outcome;
- same-key supersession atomically attaches the new request consumer, or
retags the existing observer as that consumer, before removing old request
ownership; different-key supersession revokes the old observer first;
Expand Down Expand Up @@ -744,6 +754,25 @@ refresh notification and leaves the already-returned incomplete result valid.
No optional catalog promise can keep `complete()` pending indefinitely or
block the local baseline past its product response budget.

The first implementation increment of this section is intentionally
package-private. It combines an authenticated provider with the epoch
coordinator and owns the fixed 8-active/64-queued scheduler, exact-key
in-flight sharing, one latest-wins consumer per owner, independent
cancellation, absolute queue and execution deadlines, response decoding, and
epoch publication. An owner captures its scope and dialect when prepared, so
individual requests cannot substitute provider, scope, dialect, or epoch
authority. The authenticated dialect runtime owns its canonical provider ID;
callers cannot pair an unrelated ID and runtime. Establishing the first
baseline re-keys other joinable unobserved work in that scope, allowing
newly-observed consumers to join it without duplicating a provider call.

Cache entries, loading/retry policy, refresh observers and their leases, the
40 ms completion-response budget, pagination composition, ranking, session
composition, and CodeMirror integration do not belong to that increment. They
remain explicit follow-up increments; the coordinator must not expose a
premature public surface that makes those deferred semantics difficult to add
or test.

The exact structural cache and shared-work key contains:

- service-owned provider configuration identity and unique provider ID;
Expand Down
24 changes: 24 additions & 0 deletions src/vnext/__tests__/relation-catalog-boundary.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
createSqlCatalogSearchRequest,
decodeSqlCatalogInvalidation,
decodeSqlCatalogSearchResponse,
isValidSqlCatalogScope,
MAX_CATALOG_CONTINUATION_TOKEN_LENGTH,
MAX_CATALOG_DETAIL_LENGTH,
MAX_CATALOG_ENTITY_ID_LENGTH,
Expand Down Expand Up @@ -345,6 +346,29 @@ describe("relation catalog provider capture", () => {
});
});

describe("catalog scope validation", () => {
it("accepts only bounded, non-NUL, well-formed text", () => {
expect(isValidSqlCatalogScope("connection:primary")).toBe(true);
expect(
isValidSqlCatalogScope(
`\ud83d\ude80${"x".repeat(MAX_CATALOG_SCOPE_LENGTH - 2)}`,
),
).toBe(true);
for (const candidate of [
null,
1,
"",
"bad\0scope",
"\ud800",
"\ud800x",
"\udc00",
"x".repeat(MAX_CATALOG_SCOPE_LENGTH + 1),
]) {
expect(isValidSqlCatalogScope(candidate)).toBe(false);
}
});
});

describe("catalog search request snapshots", () => {
it("copies and recursively freezes the exact provider request", () => {
const raw = request();
Expand Down
102 changes: 102 additions & 0 deletions src/vnext/__tests__/relation-catalog-epoch-coordinator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,108 @@ describe("catalog epoch coordinator construction and membership", () => {
expect(reads).toBe(0);
});

it("validates, drains, and invokes the package disposal target exactly once", async () => {
expect(
Reflect.apply(createSqlCatalogEpochCoordinator, undefined, [
capturedProvider(),
undefined,
1,
]),
).toEqual({
reason: "invalid-disposal-target",
status: "unavailable",
});

let disposalCalls = 0;
const created = createSqlCatalogEpochCoordinator(
capturedProvider(),
undefined,
() => {
disposalCalls += 1;
throw new Error("package disposal target failed");
},
);
expect(created.status).toBe("created");
if (created.status !== "created") {
throw new Error("Expected a coordinator fixture");
}
expect(() => {
created.coordinator.dispose();
created.coordinator.dispose();
}).not.toThrow();
expect(disposalCalls).toBe(1);

const invalidReturn = Promise.reject(
new Error("invalid async package disposal"),
);
const withInvalidReturn = Reflect.apply(
createSqlCatalogEpochCoordinator,
undefined,
[
capturedProvider(),
undefined,
() => invalidReturn,
],
);
expect(withInvalidReturn.status).toBe("created");
if (withInvalidReturn.status !== "created") {
throw new Error("Expected a coordinator fixture");
}
withInvalidReturn.coordinator.dispose();
await Promise.resolve();
await Promise.resolve();
});

it("drains a plain thenable with captured Promise intrinsics after the global constructor is replaced", async () => {
const intrinsicPromise = Promise;
let thenCalls = 0;
const thenable = new Proxy(
{},
{
get(target, property, receiver): unknown {
if (property === "then") {
return (
_resolve: (value: unknown) => void,
reject: (reason?: unknown) => void,
): void => {
thenCalls += 1;
reject(new Error("detached plain thenable"));
};
}
return Reflect.get(target, property, receiver);
},
},
);
function HostilePromise(): never {
throw new Error("mutable global Promise was used");
}
expect(
Reflect.set(globalThis, "Promise", HostilePromise),
).toBe(true);
try {
const created = Reflect.apply(
createSqlCatalogEpochCoordinator,
undefined,
[
capturedProvider(),
undefined,
() => thenable,
],
);
expect(created.status).toBe("created");
if (created.status !== "created") {
throw new Error("Expected a coordinator fixture");
}
const createdCoordinator = created.coordinator;
expect(() => createdCoordinator.dispose()).not.toThrow();
} finally {
Reflect.set(globalThis, "Promise", intrinsicPromise);
}
await intrinsicPromise.resolve();
await intrinsicPromise.resolve();
expect(thenCalls).toBe(1);
});

it("validates exact bounded well-formed scopes without raw errors", () => {
const owner = coordinator();
expect(
Expand Down
Loading
Loading