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
29 changes: 29 additions & 0 deletions .github/workflows/lint.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -4034,6 +4034,35 @@ jobs:
- name: Check no exported spec type resolves to `any`
run: pnpm --filter @objectstack/spec run check:exported-any

# [#11927] The same question, asked of the SDK boundary instead of the
# spec surface — and asked because #8140 bound 51 erased return types
# across `packages/client` by hand, which buys a snapshot and not a
# property. Nothing stopped the 52nd.
#
# It reads the built `.d.ts` for the reason the neighbour above does, plus
# one this package makes sharper: the largest erasure class here carries NO
# return annotation at all, taking its type from `unwrapResponse<any>`
# (#11925) or from a bare `res.json()`, which lib.dom declares
# `Promise<any>` (#12104). Neither spelling contains the text `Promise` or
# `any`, so no grep can see them — only a checker asking what the export
# RESOLVES to. So it belongs here with the other dist-reading consumer
# gates, after the build steps, and on an unbuilt or stale tree it is a
# hard refusal naming the build command rather than a skip (#4690, #7122).
#
# Baseline, not zero: 65 sites are carried as NAMED, shrink-only ledger
# entries with a written reason each (packages/client/exported-any-returns.json),
# because four of them keep `any` deliberately — no contract exists to bind
# (#11924) — and a gate demanding zero would either block on that work or
# invite a false declaration to reach green. Self-tests first, like its
# neighbours, and that self-test is load-bearing in a specific direction
# here: a caller-supplied `<T = any>` is NOT erasure, and a detector that
# cannot tell the two apart produces pressure to replace a correct generic
# with a wrong concrete type. Both directions are pinned there.
#
# Adds no required context — a step in an existing lane (#9325).
- name: Check no exported client callable resolves to `any`
run: pnpm --filter @objectstack/client run check:exported-any-returns

# Third axis on the same surface: api-surface/ shows a name on two
# entries but not whether the two are ONE declaration re-exported (fine)
# or TWO declarations sharing a name — the #4411 trap, where which type a
Expand Down
70 changes: 70 additions & 0 deletions packages/client/exported-any-returns.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions packages/client/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -16,6 +16,7 @@
"build": "tsup --config ../../tsup.config.ts",
"test": "vitest run",
"test:integration": "vitest run --config vitest.integration.config.ts",
"check:exported-any-returns": "tsx ../../scripts/check-exported-any-returns.mts --self-test && tsx ../../scripts/check-exported-any-returns.mts --package packages/client",
"check:test-typecheck": "tsx ../../scripts/check-test-typecheck.mts --self-test && tsx ../../scripts/check-test-typecheck.mts --package packages/client --project tsconfig.test.json",
"gen:test-typecheck-debt": "tsx ../../scripts/check-test-typecheck.mts --update --package packages/client --project tsconfig.test.json",
"typecheck": "tsc --noEmit && pnpm check:test-typecheck"
Expand Down
592 changes: 592 additions & 0 deletions scripts/check-exported-any-returns.mts

Large diffs are not rendered by default.

20 changes: 20 additions & 0 deletions scripts/check-ratchet-remedy-authority.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -632,6 +632,26 @@ const CONTROL = {
why: 'PR for #10534 follow-up 4. Its main remedy (ledger the mount, with evidence) is the author\'s and touches no ratchet; its PENDING_DISPOSITION paths expand a shrink-only exemption list whose one entry exists by maintainer ruling, so both are marked rather than refused.',
},

// The ninth, and the third to join as a NEW gate rather than by retrofit
// (#11927). Marking rather than refusal, on the same reading as the seventh
// and eighth: there IS a legitimate act here and it has an owner. The gate
// ratchets what `packages/client`'s BUILT exports resolve to, and four of the
// 65 sites it ledgers keep `any` deliberately because no published contract
// exists to bind (#11924) — so the ledger cannot be demanded to zero, and a
// 66th site that genuinely cannot be typed yet is a real entry someone must be
// able to add. That act expands a shrink-only ledger, which makes it the
// maintainer's rather than the landing author's, and the gate's unledgered-file
// message says so beside the offer.
//
// ⚠️ Recorded from the sweep's own verdict (`--list` reports it under `marked`,
// anchor `shrink`), not from the author's intent. The two agreeing is the point
// of the corpus; asserting the intent and never checking it is what the control
// exists to prevent.
'check-exported-any-returns.mts': {
expect: 'marked',
why: 'PR for #11927, and the second .mts instance after check-test-typecheck.mts. Its per-site ledger is shrink-only and the message says so in the same breath; adding a site is a real remedy when no contract exists to bind (#11924), so the path is named with its owner rather than denied.',
},

'check-adr-links.mjs': {
expect: 'refused',
why: 'Refuses by binding a negation to the verb, over a shrink-only registry.',
Expand Down
3 changes: 2 additions & 1 deletion scripts/check-type-check-coverage.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -619,7 +619,8 @@ const DEBT = {
+ 'the next new error here goes red on arrival (#5278 option A).',
},
'@objectstack/spec-monorepo': {
errors: 80,
errors: 26,
compositionAt: 80,
note: 'the workspace root itself: code-tier 4 (TS2304 x2, TS2339 x2); config-tier 68 '
+ '(TS2591 x28 / TS2584 x22 -- the root tsconfig still has no `types:["node"]` -- plus TS2307 x17 '
+ 'and TS2550); noise 8 (TS7006 x7, TS6133). Re-measured 80 at 5ab08428, up from 50. This entry '
Expand Down
29 changes: 29 additions & 0 deletions scripts/invoked-as.d.mts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
// Types for the entry-point predicate `invoked-as.mjs` publishes to the gates
// that import it (#10549's mirror corpus covers this file automatically).
//
// The module itself stays `.mjs` for the reason its two sibling mirrors state:
// `pre-commit` and the gates invoke these scripts with bare `node`, and every
// root script here is authored that way. What needs the declaration is the
// other direction — a TypeScript-authored gate (`.mts`) importing the predicate
// from inside the ROOT tsc program, where an untyped `.mjs` import is TS7016.
// Left untyped, `isEntrypoint` silently becomes `any`, and the mistake that
// costs the most — `if (isEntrypoint)` instead of `if (isEntrypoint(...))` —
// type-checks clean while running the gate's whole audit on import.
//
// PARTIAL on purpose, the `check-regen-pending.d.mts` shape: the module also
// exports `invokedAs` and `selfTest`, and omitting them cannot fail green — a
// consumer importing an undeclared name gets TS2305, which is loud and
// immediate. Keep this file in step with the module by hand; the mirror gate
// checks name, kind and required arity, never types.

/**
* Was this module the process entry point, rather than imported by another?
*
* Compares `process.argv[1]` against the module's own path, resolving symlinks
* on both sides — the difference between what node puts in `argv[1]` and what
* it puts in `import.meta.url` is the whole reason this is a shared predicate
* and not a one-liner in each gate.
*
* @param importMetaUrl The caller's own `import.meta.url`.
*/
export function isEntrypoint(importMetaUrl: string): boolean;
Loading