Skip to content

todo: Stage 2 needs a CI job that type-checks the packed artifact - #1757

Merged
sergey-shandar merged 19 commits into
mainfrom
claude/private-ts-todo-partial-bydyc9
Aug 28, 2026
Merged

todo: Stage 2 needs a CI job that type-checks the packed artifact#1757
sergey-shandar merged 19 commits into
mainfrom
claude/private-ts-todo-partial-bydyc9

Conversation

@sergey-shandar

@sergey-shandar sergey-shandar commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Records the design for Stage 2's package validation, after #1750 landed Stage 1. Every decision in it is a measurement rather than a proposal.

Exclude private.d.ts from packing, don't delete it

The section previously prescribed deleting generated private.d.ts as a final prepack step. A negation in package.json's files does the same job declaratively:

"files": ["**/*.js", "**/*.d.ts", "**/*.mjs", "**/*.d.mts", "!**/private.d.ts"]

Measured with npm pack --dry-run --json: 675 packed files become 659, and the 16 that disappear are exactly the 16 emitted private.d.ts. No script, no directory walk, no proof for a path predicate, and prepack keeps doing exactly what it does now. It also leaves the working tree alone — a deletion step would take declarations that a contributor's following npx tsc expects.

Check the artifact in a job with no checkout

Excluding the file is invisible to every check the repository has: npx tsc reads the source private.ts, npm pack never type-checks its output, and the global installs in CI take the published CLI rather than the artifact just built. So Stage 2 would ship a claim nothing could falsify — the "a sweep, not a check" gap the Stage 1 grep guard closes.

The shape recorded: pack and upload the tarball as a CI artifact, then a second job with no repository checkout, ordered after it, that installs the artifact and type-checks it. The missing checkout is the point — no tsconfig.json up the tree to inherit, no node_modules to resolve into, no source file that could stand in for an omitted declaration.

Four details decide whether such a job can fail at all, each established by measurement:

  • Type-check every packed declaration, enumerated from the installed artifact — not a hand-written consumer. A fixed import list cannot see a module that gains a private.ts later, which is the case the stage exists to catch.
  • skipLibCheck stays at its false default. tsc --init writes true; that silently turns the job into a no-op, and it applies to declaration files however they enter the program, root files included.
  • Install the tarball as a real dependency. Unpacking into node_modules by hand is undone by the next npm install, leaving the check passing on an empty file list.
  • Pin the compiler. With no checkout there is no lockfile, so a bare npm install typescript lets the registry change the verdict with no repository change. The version is readable without a checkout — npm pack keeps devDependencies in the packed package.json.

Making it a required check turns a failure into the author's problem at the moment it is introduced.

Measurements

Built the real tarball, installed it into a scratch consumer, removed the 16 private.d.ts:

  1. the remaining 377 declarations type-check with skipLibCheck: false — exit 0, so the exclusion is safe today (surviving private.ts mentions are inert JSDoc @import comments);
  2. one real import type … from './private.js' in a packed declaration turns that exit 2 with TS2307 — the check is falsifiable;
  3. placed in fjs/emergent_testing (no private.ts today, standing in for a future one), a consumer importing all 16 of today's private-carrying surfaces exits 0 while the exhaustive form exits 2 — a fixed import list would ship a check blind to its own purpose;
  4. and the failure is reachable organically, not only by editing packed output. Exporting one previously-unexported binding whose signature names a private type (export const divide in fjs/types/bigfloat/module.f.mjs) makes ordinary prepack emit a real import type { _BigFloatWithRemainder } from './private.ts' — not the inlined structural type — and the packed artifact then fails TS2307. Throughout, every in-repo gate stays green: npx tsc exits 0 and npm pack exits 0. The artifact is already broken while nothing in the repository can say so, which is the whole case for a consumer-side job.

The check's real target is therefore sharper than "a private dependency": it is a public-declaration-closure violation reaching an exported signature. Today none does — every binding annotated with a private type is module-private — which is why the tree measures clean.

Ownership

todo/README.md says a higher-level todo/ must not duplicate tasks belonging in a child, and fjs/ci/todo/ci-integration-tests.md already owns the npm pack upload and the artifact download. So the work is split by owner:

  • ci-integration-tests.md — the job-ordering edge, and the jobSchema / Job / proof extension it needs. jobSchema is deliberately closed at runs-on and steps, and is the same schema parseGitHubAction reads the generated workflow back through, so a bare needs: key would fail that round-trip. That issue's own two-stage split needs the same edge, so owning it there stops whichever consumer lands first from implementing another issue's task.
  • f-mjs-package-support.md — the checkout-less type-check job, the four details above, and the package fixture (with its mandatory proof.f.mjs, per fjs/AGENTS.md §1.2).
  • separate-private-types.md — only the dependency, the conditions it needs that job to satisfy, and the measurements justifying them.

Also

  • Falsifiability and exhaustiveness are separate controls. Can it fail? — any module with a private.ts. Is it exhaustive? — the violation must land where a hand-written import list would not look: a module with no private.ts today, and specifically not the package fixture, since any plausible list names the fixture. Conflating them yields a control that passes whether or not enumeration was implemented.
  • The fixture must itself conform — private type out of every exported signature — or it permanently reddens the check it exists to support. Violations are deliberate and temporary, applied while verifying and then reverted.
  • f-mjs-package-support.md's fixture task retargeted off retired .f.ts, and its last two .f.ts requirements resolved: the .ts.mjs runtime direction is retired (all 226 imports in authored .ts are import type, and types.js is not emitted), while the reject-runtime-import rule is kept and widened — the authored .ts that remain are exactly the type-level companions.
  • Superseded prescriptions removed: that file still told implementers to delete private.d.ts before packaging, and still listed microsoft/TypeScript#46407 as a blocker. The Related section also linked todo/blocked/jsdoc-typedef-strip-internal.md, which Stage 1 deleted.

Documentation only — no behavior or public API change, so no changelog entry.

🤖 Generated with Claude Code

https://claude.ai/code/session_01LqeS5t2ZKkSu3chRPMXR7n

Deleting private.d.ts at prepack is invisible to every existing check:
npx tsc reads the source private.ts, npm pack never type-checks its
output, and the global installs in CI take the published CLI rather than
the artifact just built. Record what makes such a job real rather than
theatre — skipLibCheck off in the consumer, a consumer that imports the
affected module surfaces, a directory outside the repository — plus a
falsifiability task, and point the work at the packed-consumer fixture
fjs/ci/todo/f-mjs-package-support.md already scopes instead of a second
validation path.

Also repairs the Related section: the blocked @internal todo it linked
was deleted in Stage 1.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LqeS5t2ZKkSu3chRPMXR7n
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for security reviews. Please try again later.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 121d393a6b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread fjs/todo/separate-private-types.md Outdated
Removing the prepack deletion step leaves private.d.ts in the tarball,
where every reference to it resolves — that reddens the contents
assertion and leaves the consumer green, so it cannot serve as the
consumer's negative control. The consumer's control is the reverse: a
packed declaration depending on a private module the tarball does not
carry, which resolves in-repo and dangles once packed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LqeS5t2ZKkSu3chRPMXR7n

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5c33e089b7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread fjs/todo/separate-private-types.md Outdated
Stage 2 is pointed at this fixture, and its scope still called for a
mixed module.f.ts / module.f.mjs pair — a source form the completed
migration retired, so following it literally would reintroduce .f.ts.
Retarget it to the current model and name the sibling private.ts the
private-declaration check needs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LqeS5t2ZKkSu3chRPMXR7n
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
functionalscript 2bd8494 Commit Preview URL

Branch Preview URL
Aug 28 2026, 06:41 PM

Two simplifications measured rather than assumed.

A negation in package.json's files ("!**/private.d.ts") drops exactly the
16 emitted private.d.ts from the tarball -- 675 packed files become 659
under npm pack --dry-run -- so Stage 2 needs no prepack deletion step, no
script, and no proof for a path predicate. It also leaves the working
tree alone, where a deletion would take declarations a following npx tsc
expects.

The consumer check moves to a job with no repository checkout, fed the
tarball as a CI artifact. That is stronger than a directory outside the
repository: with no repository on the runner there is no tsconfig.json to
inherit, no node_modules to resolve into, and no source file that could
stand in for an omitted declaration. Records that skipLibCheck must stay
at its false default, that the job should be a required check, and the
measurements showing the exclusion is safe today and the check
falsifiable.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LqeS5t2ZKkSu3chRPMXR7n

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6cd4923cdb

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread fjs/ci/todo/f-mjs-package-support.md Outdated
Comment thread fjs/todo/separate-private-types.md Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2feb2802d2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread fjs/todo/separate-private-types.md Outdated
claude added 4 commits August 28, 2026 16:33
A consumer that imports the surfaces known to carry private types is only as
current as its import list. Measured on an installed tarball: with a dangling
`./private.js` import injected into `fjs/emergent_testing` — a module with no
`private.ts` today, standing in for a future one — a consumer importing all 16
of today's private-carrying surfaces exits 0, while type-checking all 377
packed declarations exits 2 with TS2307. The job would have shipped unable to
see the case it exists to catch.

So the second job now enumerates the packed `.d.ts` / `.d.mts` from the
installed artifact and passes them all to `tsc` as root files. Two further
details recorded from doing it: `skipLibCheck` must stay false even with
declarations as root files, and the tarball must be installed as a dependency
rather than unpacked into `node_modules` by hand, since a later `npm install`
prunes it and leaves an empty file list. The type-check's negative control now
goes in a module with no `private.ts`, so it proves exhaustiveness too.

Also retires the fixture task's last two `.f.ts` requirements. The `.ts` ->
`.mjs` runtime direction no longer exists to test: all 225 imports in authored
`.ts` are `import type`, and `types.js` is not emitted, so the form is doubly
excluded. The reject-runtime-import rule got wider rather than lapsing — the
authored `.ts` that remain are exactly the type-level companions — and is
restated that way, with the matching acceptance criterion.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LqeS5t2ZKkSu3chRPMXR7n
…ydyc9' into claude/private-ts-todo-partial-bydyc9
The merge of main added one type-only import to an authored .ts, so the
measurement now reads 226. Phrased so the claim is the property, not the
number.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LqeS5t2ZKkSu3chRPMXR7n
Without `needs`, GitHub Actions starts the two jobs in parallel and
`download-artifact` fails before the check runs — a red required check for the
wrong reason. It is a prerequisite rather than a detail: `jobSchema` in
`fjs/ci/common/module.f.mjs` is closed and names only `runs-on` and `steps`,
and it is the same schema `parseGitHubAction` reads the generated workflow back
through in `fjs/ci/proof.f.mjs`, so emitting a bare `needs:` key would fail
that round-trip. Records extending the schema, `Job`, and the proof as part of
Stage 2, with the matching acceptance criterion.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LqeS5t2ZKkSu3chRPMXR7n

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d537c72cb8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread fjs/todo/separate-private-types.md Outdated
With no checkout there is no lockfile, so a bare `npm install typescript`
resolves whatever the registry publishes that day — the required check could
redden, or quietly change module-resolution behaviour, with no change to this
repository. That is the same wrong-reason failure the `needs` edge avoids.

The version is readable without a checkout: `npm pack` keeps
`devDependencies` in the packed `package.json`, verified on the tarball built
here, which carries `"typescript": "=7.0.2"` — the repository's exact pin and
the version the measurements in this document were taken with. Records the
trade too: determinism over consumer-compiler coverage, with a second pinned
version as the way to widen it rather than letting the first float.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LqeS5t2ZKkSu3chRPMXR7n

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7313688ad9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread fjs/todo/separate-private-types.md Outdated
claude and others added 3 commits August 28, 2026 16:46
todo/README.md says a higher-level todo/ must not duplicate tasks belonging in
a child todo/, and this had started to: fjs/ci/todo/ci-integration-tests.md
already owns "run npm pack, upload as a GitHub Actions artifact" and "download
artifact, install", and f-mjs-package-support.md already owns the clean
packed-package consumer. Two owners for one task is how contradictory
requirements appear when only one of them is updated.

Split along ownership rather than by deleting:

- ci-integration-tests.md takes the job-ordering edge and the jobSchema / Job /
  proof extension it needs. That issue's own two-stage split needs the same
  edge, so owning it there stops whichever consumer lands first from
  implementing another issue's task.
- f-mjs-package-support.md takes the checkout-less type-check job and the four
  details that decide whether it can fail — exhaustive file set, skipLibCheck
  false, install as a dependency, pinned compiler — each with its reasoning.
- separate-private-types.md keeps what is actually its own: the requirement, the
  conditions it needs that job to satisfy, and the measurements that justify
  them. Nothing was dropped; the conditions are stated as conditions instead of
  as generator tasks.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LqeS5t2ZKkSu3chRPMXR7n

@o2alexanderfedin o2alexanderfedin left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved.

The premise is real and I demonstrated it rather than reasoning about it: packed the artifact, installed the tarball into a scratch consumer outside the repo, enumerated all 377 shipped declaration files programmatically and type-checked them with skipLibCheck: false — exit 0. Then made the check falsifiable by injecting an unresolvable private.js import into a packed declaration: TS2307, exit 2.

The argument for an exhaustive check over a fixed import list also holds up empirically. Injecting the same breakage into fjs/emergent_testing — a module with no private.ts today — is caught by the 377-file sweep and missed by a list of the 16 modules that currently have one. That is the difference between a check that stays true and one that decays as modules are added.

Numbers match: 675 packed files today, 659 with the proposed !**/private.d.ts negation, exactly 16 fewer and none left. The packed package.json does retain devDependencies, so the compiler pinning works. jobSchema really is closed for the reason the note gives, and the note correctly prescribes going through the generator rather than editing ci.yml, which npm run ci-update regenerates and CI already pins byte-identical.

The three notes agree with each other and with what Stage 1 actually shipped; all links resolve, and this incidentally clears a reference to jsdoc-typedef-strip-internal.md, deleted by #1750. No changelog needed — todo-only. tsc 0; npm test skipped, no code changed.

One honest limit: the failure cases were injected into already-packed declarations, the same method the note used. That shows the check can fail and that its numbers are right; it does not show today's workflow would organically produce a broken artifact — which is the future-tense gap the job exists to close.

The review's closing note was right that the three measurements so far inject
the failure into an already-packed declaration: they show the check can fail,
not that this repository's workflow could produce the artifact that fails it.

It can. The organic control is a source-level violation of the
public-declaration-closure rule — exporting a binding whose signature names a
private type. Run end to end with `export const divide` in
fjs/types/bigfloat/module.f.mjs: ordinary prepack emits a real
`import type { _BigFloatWithRemainder } from './private.ts'` rather than the
inlined structural type; npm pack with the files negation ships 0 private.d.ts;
installing that tarball and type-checking all 377 declarations exits 2 with
TS2307 on that line.

Two consequences recorded. The check's real target is a closure-rule violation
reaching an exported signature — today none does, because every binding
annotated with a private type is module-private, which is why the tree measures
clean. And the fixture's control should be this source-level one: it exercises
emit, packing and consumption together, so it also fails if a future TypeScript
starts inlining the reference and the design's premise quietly stops holding.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LqeS5t2ZKkSu3chRPMXR7n

Copy link
Copy Markdown
Contributor Author

Thanks for reproducing it independently rather than taking the numbers on trust.

Your closing limit was the useful part, so I went and closed it — 31bffb6. You're right that injecting into an already-packed declaration shows the check can fail without showing this repository's workflow could produce the artifact that fails it. It can, and the whole design now has an end-to-end organic run behind it.

The organic control is a source-level violation of the public-declaration-closure rule — exporting a binding whose signature names a private type. Using export const divide in fjs/types/bigfloat/module.f.mjs (typed _BigFloatWithRemainder), with nothing else touched:

  1. ordinary prepack emits a real import type { _BigFloatWithRemainder } from './private.ts' into module.f.d.mts — not the inlined structural type, and the specifier keeps its .ts extension, the same way declaration emit treats types.ts;
  2. npm pack with the files negation ships 0 private.d.ts;
  3. installing that tarball and type-checking all 377 declarations exits 2 with TS2307 naming that line.

Two things fall out that are worth more than the control itself, both now in the note:

  • The check's real target is sharper than "a private dependency". It is a closure-rule violation reaching an exported signature. Today no private type does — every binding annotated with one is module-private, which is exactly why the tree measures clean. That reframes the clean measurement from "nothing depends on private types" to "nothing exported does", which is the property that can actually regress.
  • The fixture should use the source-level control, not an artifact edit. It exercises emit, packing and consumption together, so it also fails if a future TypeScript starts inlining the reference — the case where the design's premise quietly stops holding and an artifact-level control would keep passing while guarding nothing.

Generated by Claude Code

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 31bffb614f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread fjs/todo/separate-private-types.md
f-mjs-package-support.md said "Deleting generated private.d.ts before packaging
is the second stage of separate-private-types.md" — the superseded design. Now
that the owning issue is where an implementer starts, that contradiction is the
exact failure the ownership split was meant to prevent, one commit later.

It now prescribes the files negation, says prepack is unchanged and the working
tree untouched, says explicitly not to reintroduce a deletion step, and notes
that once it lands private.d.ts leaves the package-private set entirely, so the
leak tolerance narrows to the _-prefixed names in types.d.ts.

Also corrects that file's Related entry for microsoft/TypeScript#46407, still
listed as an upstream blocker: Stage 1 removed every file-scope JSDoc typedef,
so there is nothing left for stripInternal to strip.

Swept the repository for other copies: fjs/fsc/README.md describes the outcome
("removing shipped private declaration artifacts from the package") rather than
a mechanism, so it needs no change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LqeS5t2ZKkSu3chRPMXR7n

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a201e03d9b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread fjs/ci/todo/f-mjs-package-support.md Outdated
The retargeted fixture named module.f.mjs with types.ts and private.ts but no
proof.f.mjs. fjs/AGENTS.md §1.2 requires 100% proof coverage for every authored
.f.mjs, so implementing it literally would fail npm run cov and put the
repository in violation of its own rule while demonstrating package support.

Adding the proof surfaced a second constraint the task did not state. The
fixture carries a private.ts for the packed-declaration check, so it is tempting
to have it export a private-typed binding — but that is exactly the failure the
check reports, and it would redden the check permanently. The fixture must be a
conforming module, private type out of every exported signature, like the rest
of the tree; the falsifiability control is a deliberate temporary violation
applied while verifying the check can fail, which is how it was measured end to
end.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LqeS5t2ZKkSu3chRPMXR7n

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f55f58f6e7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread fjs/ci/todo/f-mjs-package-support.md Outdated
f55f58f put the temporary violation in the package fixture. But the fixture is
precisely the module a hand-written import list would name, so a violation
there fails under a fixed list too — it proves the check reports a dangling
reference and says nothing about whether the file set was enumerated. That also
contradicted the parent's requirement to place the control in a module with no
private surface.

Both documents now keep the two questions apart:

- Can it fail? Any module with a private.ts; measured in fjs/types/bigfloat.
- Is it exhaustive? A module with no private.ts today — which means temporarily
  giving one to a module that has none — and specifically not the fixture.
  Measured in fjs/emergent_testing.

This matches how they were actually measured; the previous wording implied one
control could answer both, which is the mistake it would have taught an
implementer to make.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LqeS5t2ZKkSu3chRPMXR7n

@o2alexanderfedin o2alexanderfedin left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved. The organic control answers the limit I raised, and the latest commit sharpens it correctly.

Reproduced the organic case: exporting one previously-unexported binding whose signature names a private type — a single edit to an authored .mjs, nothing touched in packed output — emits import type { _BigFloatWithRemainder } from './private.ts' into module.f.d.mts, and with the negation applied the installed tarball fails TS2307 on exactly that line. The part that carries the argument: with the violation present, the in-repo gates stayed greentsc --noEmit exit 0, npm pack succeeded. That is the case for a consumer-side job made by demonstration rather than assertion.

Splitting falsifiability from exhaustiveness is the right correction, and the reason given is the load-bearing one: a violation placed in the fixture proves nothing about enumeration, because any plausible hand-written list names the fixture. Both attributions check out — fjs/types/bigfloat/ does carry a private.ts, fjs/emergent_testing/ does not, and 16 remain in total. They also match what was actually measured on each side, which is what makes the note a record rather than a plan.

The note is careful about what it claims elsewhere too: it says today's tree measures clean because no exported binding names a private type — true, and independently confirmed — rather than claiming an existing break. The deletion-to-negation correction is not confused about Stage 1: the 16 private.ts files still declare export type _* and still ship; what Stage 1 removed was the file-scope @typedef in authored .mjs, and the CI grep for those matches zero today, which is what makes the stripInternal blocker moot. The fixture's proof requirement is right — the earlier shape would have failed npm run cov — and fjs/types/bigfloat/ is a live example of the prescribed layout.

One cosmetic thing: f-mjs-package-support.md says the leak-tolerance contract narrows to "the _-prefixed names in types.d.ts". Coherent inside its own enumeration, but read alone it is narrower than separate-private-types.md and fsc/README.md, which also cover exported _ constants in module.d.mts.

tsc 0; npm test skipped — docs-only against the true merge-base.

Two points from review.

The organic control's most useful property was implicit: with the violation in
place, npx tsc exits 0 and npm pack succeeds. Verified here directly rather
than taken from the review. That is this section's opening claim — the
exclusion is invisible to every check the repository has — demonstrated instead
of argued: the artifact is already broken while nothing in the repository can
say so. It is the whole case for a consumer-side job, so it belongs in the
measurements.

a201e03 also narrowed the leak-tolerance contract to "the _-prefixed names in
types.d.ts". Coherent inside its own enumeration but narrower than
separate-private-types.md and fsc/README.md, which also cover exported _
constants emitted into module.d.mts. Now says both, and points at fsc/README.md
for the contract itself so the three cannot drift apart again.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LqeS5t2ZKkSu3chRPMXR7n

Copy link
Copy Markdown
Contributor Author

Both points acted on in 2dac7d5.

The in-repo-green observation was the better finding, and it wasn't in the note. You're right that it carries the argument: I had recorded that the exclusion is invisible to every repository check as reasoning, and separately measured the artifact failing — but never stated that the two hold simultaneously. I verified it here rather than relaying it: with export const divide in place, npx tsc exits 0 and npm pack exits 0, while the installed tarball fails TS2307. The artifact is already broken and nothing in the repository can say so. That is now measurement 4, since it is the actual case for a consumer-side job — the other three only show the job would work once it exists.

The leak-tolerance narrowing was mine, in a201e03, and you read it correctly. "The _-prefixed names in types.d.ts" is coherent inside that paragraph's own enumeration but narrower than separate-private-types.md and fsc/README.md, which also cover exported _ constants emitted into module.d.mts. It now names both and points at fsc/README.md for the contract itself, so the three cannot drift apart again — which was how that paragraph went stale in the first place.

Thanks also for checking the Stage 1 attribution. That distinction — Stage 1 removed file-scope @typedef from authored .mjs, not the export type _* in the 16 private.ts, which still ship — is exactly the thing an implementer could get backwards, and it is what makes the stripInternal blocker moot rather than merely deprioritized.


Generated by Claude Code

@o2alexanderfedin o2alexanderfedin left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved. Both edits land.

The leak-tolerance wording now covers both forms and points at the contract itself; fjs/fsc/README.md does state it, and line 190 says _ constants keep shipping in emitted declarations regardless — so the alignment is with what that file actually says, not just a broader phrasing.

Recording the green-gates step as measurement 4 is the right place for it. That fact is the argument for the job: the artifact is already broken while nothing in the repository can say so.

@sergey-shandar
sergey-shandar added this pull request to the merge queue Aug 28, 2026
@sergey-shandar
sergey-shandar removed this pull request from the merge queue due to a manual request Aug 28, 2026
@sergey-shandar
sergey-shandar added this pull request to the merge queue Aug 28, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to a conflict with the base branch Aug 28, 2026
The merge queue dequeued this for MERGE_CONFLICT. One conflict, in the Related
section: both sides were additive, so both survive — my two ownership links,
and main's note recording that jsdoc-typedef-strip-internal is retired rather
than merely unlinked.

Main's heading normalization and todo renames (#1756) auto-merged; all links in
the three touched files still resolve and tsc is clean.

Re-measured everything the documents assert, since main moved a lot. The
invariants hold exactly: 16 private.ts, fjs/emergent_testing still has none
(so it remains a valid stand-in for a future module), 0 runtime imports among
the 226 in authored .ts, and bigfloat's divide is still unexported with
_BigFloatWithRemainder still private.

The absolute counts drifted for the third time, though: 675/659 is now 677/661
and 377 declarations is 378. The numbers were never the claim — "exactly 16
disappear, and they are exactly the private.d.ts" is — so the prose now says
that, with the totals marked as a re-measured snapshot. Future drift makes the
snapshot old rather than making the document wrong.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LqeS5t2ZKkSu3chRPMXR7n

@o2alexanderfedin o2alexanderfedin left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved. Beyond the merge, the real change is replacing the absolute counts with the invariant, and that is the right call — 675/659/377 were true when measured and would have gone stale silently as modules were added. Re-measured just now at this head: 677 packed files, of which exactly 16 are private.d.ts. The figures you record match, and the claim that survives them ("drops by exactly 16, nothing else moves") is the one worth pinning.

@sergey-shandar
sergey-shandar added this pull request to the merge queue Aug 28, 2026
Merged via the queue into main with commit 0283ffa Aug 28, 2026
19 checks passed
@sergey-shandar
sergey-shandar deleted the claude/private-ts-todo-partial-bydyc9 branch August 28, 2026 19:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants