todo: Stage 2 needs a CI job that type-checks the packed artifact - #1757
Conversation
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
|
You have reached your Codex usage limits for security reviews. Please try again later. |
There was a problem hiding this comment.
💡 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".
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
There was a problem hiding this comment.
💡 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".
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
Deploying with
|
| 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
There was a problem hiding this comment.
💡 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".
There was a problem hiding this comment.
💡 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".
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
There was a problem hiding this comment.
💡 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".
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
There was a problem hiding this comment.
💡 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".
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
left a comment
There was a problem hiding this comment.
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
|
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
Two things fall out that are worth more than the control itself, both now in the note:
Generated by Claude Code |
There was a problem hiding this comment.
💡 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".
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
There was a problem hiding this comment.
💡 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".
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
There was a problem hiding this comment.
💡 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".
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
left a comment
There was a problem hiding this comment.
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 green — tsc --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
|
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 The leak-tolerance narrowing was mine, in a201e03, and you read it correctly. "The Thanks also for checking the Stage 1 attribution. That distinction — Stage 1 removed file-scope Generated by Claude Code |
o2alexanderfedin
left a comment
There was a problem hiding this comment.
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.
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
left a comment
There was a problem hiding this comment.
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.
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.tsfrom packing, don't delete itThe section previously prescribed deleting generated
private.d.tsas a finalprepackstep. A negation inpackage.json'sfilesdoes the same job declaratively:Measured with
npm pack --dry-run --json: 675 packed files become 659, and the 16 that disappear are exactly the 16 emittedprivate.d.ts. No script, no directory walk, no proof for a path predicate, andprepackkeeps doing exactly what it does now. It also leaves the working tree alone — a deletion step would take declarations that a contributor's followingnpx tscexpects.Check the artifact in a job with no checkout
Excluding the file is invisible to every check the repository has:
npx tscreads the sourceprivate.ts,npm packnever 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.jsonup the tree to inherit, nonode_modulesto 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:
private.tslater, which is the case the stage exists to catch.skipLibCheckstays at itsfalsedefault.tsc --initwritestrue; that silently turns the job into a no-op, and it applies to declaration files however they enter the program, root files included.node_modulesby hand is undone by the nextnpm install, leaving the check passing on an empty file list.npm install typescriptlets the registry change the verdict with no repository change. The version is readable without a checkout —npm packkeepsdevDependenciesin the packedpackage.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:skipLibCheck: false— exit0, so the exclusion is safe today (survivingprivate.tsmentions are inert JSDoc@importcomments);import type … from './private.js'in a packed declaration turns that exit2withTS2307— the check is falsifiable;fjs/emergent_testing(noprivate.tstoday, standing in for a future one), a consumer importing all 16 of today's private-carrying surfaces exits0while the exhaustive form exits2— a fixed import list would ship a check blind to its own purpose;export const divideinfjs/types/bigfloat/module.f.mjs) makes ordinaryprepackemit a realimport type { _BigFloatWithRemainder } from './private.ts'— not the inlined structural type — and the packed artifact then failsTS2307. Throughout, every in-repo gate stays green:npx tscexits0andnpm packexits0. 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.mdsays a higher-leveltodo/must not duplicate tasks belonging in a child, andfjs/ci/todo/ci-integration-tests.mdalready owns thenpm packupload and the artifact download. So the work is split by owner:ci-integration-tests.md— the job-ordering edge, and thejobSchema/Job/ proof extension it needs.jobSchemais deliberately closed atruns-onandsteps, and is the same schemaparseGitHubActionreads the generated workflow back through, so a bareneeds: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 mandatoryproof.f.mjs, perfjs/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
private.ts. Is it exhaustive? — the violation must land where a hand-written import list would not look: a module with noprivate.tstoday, 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.f-mjs-package-support.md's fixture task retargeted off retired.f.ts, and its last two.f.tsrequirements resolved: the.ts→.mjsruntime direction is retired (all 226 imports in authored.tsareimport type, andtypes.jsis not emitted), while the reject-runtime-import rule is kept and widened — the authored.tsthat remain are exactly the type-level companions.private.d.tsbefore packaging, and still listed microsoft/TypeScript#46407 as a blocker. TheRelatedsection also linkedtodo/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