Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
121d393
todo: Stage 2 needs a CI job that type-checks the packed artifact
claude Aug 28, 2026
5c33e08
todo: the two package checks need opposite negative controls
claude Aug 28, 2026
458170e
todo: the package fixture is .f.mjs + types.ts, not a retired .f.ts
claude Aug 28, 2026
6cd4923
todo: exclude private.d.ts from packing, and check it without a checkout
claude Aug 28, 2026
2feb280
Merge branch 'main' into claude/private-ts-todo-partial-bydyc9
sergey-shandar Aug 28, 2026
fb39e61
todo: check every packed declaration, not a fixed consumer
claude Aug 28, 2026
631a874
Merge remote-tracking branch 'origin/claude/private-ts-todo-partial-b…
claude Aug 28, 2026
cc3e13b
todo: correct the import-type count after the merge
claude Aug 28, 2026
d537c72
todo: the packed-artifact job needs an ordering edge on the pack job
claude Aug 28, 2026
7313688
todo: pin the packed-artifact job's compiler
claude Aug 28, 2026
8b80a91
todo: give each Stage 2 task one owner
claude Aug 28, 2026
a4b4d75
Merge branch 'main' into claude/private-ts-todo-partial-bydyc9
sergey-shandar Aug 28, 2026
ccb4858
Merge branch 'main' into claude/private-ts-todo-partial-bydyc9
sergey-shandar Aug 28, 2026
31bffb6
todo: the negative control should be organic, and it works
claude Aug 28, 2026
a201e03
todo: the packaging issue still prescribed deleting private.d.ts
claude Aug 28, 2026
f55f58f
todo: the package fixture needs its proof, and must itself conform
claude Aug 28, 2026
1513826
todo: falsifiability and exhaustiveness need separate controls
claude Aug 28, 2026
2dac7d5
todo: record that in-repo gates stay green, and align the leak tolerance
claude Aug 28, 2026
2bd8494
Merge branch 'main' into claude/private-ts-todo-partial-bydyc9
claude Aug 28, 2026
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
15 changes: 15 additions & 0 deletions fjs/ci/todo/ci-integration-tests.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,21 @@ Open questions:

- [ ] Define the scenario interface (`export const main: NodeProgram` or similar).
- [ ] Implement the artifact publish step in the CI generator (run `npm pack`, upload as a GitHub Actions artifact).
- [ ] Teach the CI generator to express job ordering, so a consuming job cannot
start before the artifact is uploaded. `jobSchema` in
`fjs/ci/common/module.f.mjs` is deliberately **closed** and names only
`runs-on` and `steps`, and it is the same schema `parseGitHubAction`
reads the generated workflow back through (`fjs/ci/proof.f.mjs`), so a
bare `needs:` key would fail that round-trip rather than merely being
unmodelled. Add `needs: or(option, array(string))` — the optional-field
idiom already used in `stepSchema` — which widens `Job` in
`fjs/ci/common/types.ts`, and cover the new field in the proof. Without
it the two stages race and the consumer fails at `download-artifact`:
red for the wrong reason, which is the one failure mode that trains
people to re-run a check instead of reading it. This blocks the stage
split below and the packed-declaration check in
[`../../todo/separate-private-types.md`](../../todo/separate-private-types.md)
alike, so it is owned here rather than by either consumer.
- [ ] Implement scenario job generation: download artifact, install, run `main`.
- [ ] Port existing demo/smoke-test steps (`fjs t`, `deno run … t`, `bunx … t`) to the scenario model.
- [ ] Document the scenario authoring convention.
108 changes: 93 additions & 15 deletions fjs/ci/todo/f-mjs-package-support.md
Original file line number Diff line number Diff line change
Expand Up @@ -166,9 +166,17 @@ exposes private types as `_`-prefixed names in `types.d.ts` and as generated
`private.d.ts` files. Both are package-private by contract, not public API:
clean-consumer tests must exercise documented public types and must not turn
`_`-prefixed declaration artifacts into supported API merely because TypeScript
emitted them. Deleting generated `private.d.ts` before packaging is the second
stage of
[`fjs/todo/separate-private-types.md`](../../todo/separate-private-types.md).
emitted them. Unshipping generated `private.d.ts` is the second stage of
[`fjs/todo/separate-private-types.md`](../../todo/separate-private-types.md),
by a `!**/private.d.ts` negation in `package.json`'s `files` — an exclusion at
pack time, with `prepack` unchanged and the working tree left alone. An earlier
draft of that design deleted the files instead; do not reintroduce a deletion
step. Once it lands, `private.d.ts` is no longer among the package-private
artifacts above — what remains is the `_`-prefixed names that still ship by
design: `_` types emitted into `types.d.ts` and exported `_` constants emitted
into `module.d.mts`. The leak-tolerance contract narrows to those, and stays
permanent for them; see
[`../../fsc/README.md`](../../fsc/README.md) for the contract itself.

Package selection does not need to distinguish every authored `.mjs` by public
API status during this transition. Incidental authored files such as
Expand Down Expand Up @@ -214,9 +222,35 @@ emission, `npm pack`, and a clean consumer.
outputs.
- [ ] Keep package/publish jobs on a clean CI checkout; do not add generated
output tracking or cleanup for artifacts from previous revisions.
- [ ] Add a mixed `module.f.ts` / `module.f.mjs` plus authored `types.ts` package
fixture. Scope: the fixture exercises the supported, fully erased
`import type` form only. The forbidden inline `import { type X }` /
- [ ] Add a package fixture in the current source model — `module.f.mjs` with a
co-located `proof.f.mjs`, an authored `types.ts` and, for the
private-declaration check, a sibling `private.ts` (authored
implementation and proof `.f.ts` are retired, so the fixture must not
reintroduce them). The proof is not optional paperwork: `fjs/AGENTS.md`
§1.2 requires 100% proof coverage for every authored `.f.mjs`, so a
fixture without one fails `npm run cov` and lands the repository in
violation of its own rule — while demonstrating package support.
Two constraints follow from what the fixture is *for*:
- It must be a **conforming** module: its private type stays out of every
exported signature, matching the public-declaration-closure rule and
the rest of the tree. A fixture that exports a private-typed binding
would permanently redden the packed-declaration check it exists to
support.
- Any violation is therefore *deliberate and temporary*, applied while
verifying the check and then reverted — never the fixture's steady
state. Two different controls are needed, and they must not be run in
the same place:
- **Can the check fail at all?** Export a binding whose signature names
the private type, here in the fixture, and confirm `TS2307`.
- **Is the check exhaustive?** This one must go in a module the
consumer would *not* name — one with no private surface today, and
in particular **not** this fixture. A hand-written import list would
name the fixture, so a violation placed here fails under a fixed list
too and proves nothing about enumeration. Measured end to end with
`fjs/emergent_testing` in
[`../../todo/separate-private-types.md`](../../todo/separate-private-types.md).
Scope: the fixture exercises the
supported, fully erased `import type` form only. The forbidden inline `import { type X }` /
`import * as` / side-effect forms are a documented one-time measurement
([`packed-consumer-validation.md`](../packed-consumer-validation.md),
"`types.js` is not a real module") — their behavior belongs to consumer
Expand Down Expand Up @@ -245,11 +279,25 @@ emission, `npm pack`, and a clean consumer.
`types.ts` or `private.ts`, per the file-scope-typedef prohibition) whose
name reaches the emitted declarations; tolerate that declaration form
without treating it as clean-consumer public API.
- [ ] Test the allowed `.ts` -> `.mjs` runtime dependency direction in a clean
checkout and CI-built package archive.
- [ ] Reject authored `.mjs` runtime imports to remaining relative implementation
`.ts` / `.f.ts`; type-only imports to intentional `types.ts` companions are
allowed.
- [x] Test the allowed `.ts` -> `.mjs` runtime dependency direction in a clean
checkout and CI-built package archive. Retired, not performed: the
direction no longer exists to test. Every authored `.ts` left is a
`types.ts` / `private.ts`, and every one of their import statements is
`import type` (226 at the time of writing) — measured on the tree after
[#1750](https://github.com/functionalscript/functionalscript/pull/1750).
A runtime dependency out of an authored `.ts` would also need emitted
JavaScript for it, and the decision above settled that `types.js` is not
part of the package layout, so the form is doubly excluded. Writing a
fixture for it would manufacture a source shape the repository forbids.
- [ ] Reject authored `.mjs` runtime imports to any relative authored `.ts` —
the rule outlived the migration and got *wider*, not narrower. It once
guarded against importing implementation `.ts` / `.f.ts`; with those
retired, the remaining authored `.ts` are exactly the type-level
`types.ts` / `private.ts` companions, for which no JavaScript is emitted,
so a runtime import would resolve in the source tree and dangle in the
package. Type-only imports (`import type`, JSDoc `@import`) stay allowed
and are the only permitted form. Currently zero authored `.mjs` violate
this, so the fixture pins a property that already holds.
- [x] Type-check and run a clean packed-package consumer under TypeScript, Node,
Deno, and Bun using the `types.ts`-backed API. Measured manually in
[#1520](https://github.com/functionalscript/functionalscript/pull/1520)
Expand All @@ -258,6 +306,33 @@ emission, `npm pack`, and a clean consumer.
CI fixture is the remaining fixture work above.
- [ ] Verify the CI-built archive contains exactly the generated/runtime/type
artifacts needed for the `types.ts` convention during stage 1.
- [ ] Run the clean packed-package consumer **in CI**, in a job with no
repository checkout, consuming the tarball handed over as an artifact by
[`ci-integration-tests.md`](ci-integration-tests.md) (which also owns the
job-ordering edge that keeps it from racing the upload). The missing
checkout is the point and is stronger than merely working outside the
repository: with no repository on the runner there is no `tsconfig.json`
up the tree to inherit, no `node_modules` to resolve into, and no source
file that could stand in for a declaration the tarball omits. Four
details decide whether such a job can fail at all, each learned by
measurement rather than reasoning:
- **Type-check every packed declaration**, enumerated from the installed
artifact — not a hand-written consumer importing today's known
surfaces, whose import list goes stale the moment a module changes.
- **Leave `skipLibCheck` at its `false` default.** `tsc --init` writes
`true`; that silently turns the job into a no-op. It applies to
declaration files however they enter the program, root files included.
- **Install the tarball as a real dependency**, never by unpacking into
`node_modules` by hand — a later `npm install` prunes what is not in
`package.json`, leaving the check passing on an empty file list.
- **Pin the compiler** to the repository's exact `typescript` version.
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`.
The private-declaration assertion this job carries for
[`../../todo/separate-private-types.md`](../../todo/separate-private-types.md)
is a condition on it, specified there; the job itself belongs here.
- [x] Update `AGENTS.md` to the asymmetric `.f.ts` / `.f.mjs` migration policy.
- [x] Decide, based on the fixture, whether the second TypeScript runtime-emission
pass can ever be removed while authored `types.ts` files remain, or whether
Expand All @@ -283,9 +358,11 @@ emission, `npm pack`, and a clean consumer.
- `_`-prefixed JSDoc typedefs are treated as private API even if declaration
emission currently writes them as exported aliases; clean-consumer tests do
not depend on those names.
- Remaining implementation `.ts` may import migrated `.mjs`; migrated `.mjs`
cannot runtime-import remaining implementation `.ts` / `.f.ts` or generated
`.js`.
- Authored `.mjs` cannot runtime-import any relative authored `.ts` or generated
`.js`; type-only imports of `types.ts` / `private.ts` companions are the only
permitted form. (The converse allowance — implementation `.ts` importing
migrated `.mjs` — lapsed with the migration: no authored implementation `.ts`
remains to exercise it.)
- A clean consumer can import the CI-built `.mjs` runtime and type-check its
`types.ts`-backed public API.
- `.f.mjs` carries no current-compiler compatibility promise during stage 1.
Expand Down Expand Up @@ -325,7 +402,8 @@ not, and the pipeline is simplified accordingly.
— private-type placement rules and the packaging stage that unships
generated private declarations.
- [microsoft/TypeScript#46407](https://github.com/microsoft/TypeScript/issues/46407)
— upstream blocker for stripping private JSDoc typedefs.
— upstream JSDoc typedef stripping limitation; no longer a blocker here, since
no authored `.mjs` declares a file-scope typedef to strip.
- [`publishing-packages.md`](./publishing-packages.md) — broader package roadmap.
- [`f-js-package-support.md`](./f-js-package-support.md) — stage-2 authored
`.f.js` package prerequisite.
Expand Down
Loading
Loading