Add instructions directory, gate committed build output and repair the type gate - #9
Merged
Merged
Conversation
This package commits its build output and its exports point directly at it, and there is no prepare script, so installing it straight from git performs no build. Consumers therefore execute the committed output while reviewers read the TypeScript source. Those are different files, and nothing in a pull request diff can reveal a mismatch between them: the source diff looks correct because the source is exactly what it claims to be. Add a build step that fails when the committed output differs from a clean build of the source, with a message naming the fix. It uses `git status --porcelain` rather than `git diff --exit-code` because git diff only sees tracked files, so a newly added source module compiles to untracked output files and git diff exits 0 -- the case that arises precisely when someone adds a module. Also add a check for private markers and attribution trailers covering tracked files, commit messages and commit authorship. Commit messages are included because they never appear in a file diff. The script proves its own patterns against synthetic markers on every run, so a pass means the patterns were demonstrated rather than assumed, and it holds no exemption for itself.
Adds .github/instructions/ with six task-scoped guides and rewrites
.github/copilot-instructions.md to point at them.
The most important content is the public-repository constraint. The
"private" flag in package.json means "do not publish to the npm registry"
and says nothing about visibility; this repository is public while the
services consuming it are not. Every guide leads with that, because the
leak vector is code comments, fixtures, commit messages and PR bodies
rather than documentation alone.
Repairs the type gate, which was inert in two separate ways:
- tsconfig.test.json could not resolve the vitest globals types because
typeRoots was narrowed to node_modules/@types by the base config, so
`tsc -p tsconfig.test.json` failed on a clean tree and had evidently
never run. Widening typeRoots fixes it.
- `vitest run --typecheck` is not a substitute: its typecheck.include
defaults to **/*.test-d.ts and this project has no such files, so it
checked zero files and reported "no errors" regardless.
This matters because the runtime suite cannot fail on a type change --
interfaces are erased, so a test that declares a literal and reads a
property back still passes once the field is deleted from the source.
Deleting a field outright left 480/480 tests green. The repaired
typecheck catches that, a wrong-typed assignment, and a removed enum
member. Exposed as `npm run typecheck` and gated in CI.
Adds zod as a runtime dependency for the schema layer that will follow,
and pins the existing git dependency to the exact commit the lockfile
already resolved. That spec carried no ref, so it floated to whatever the
default branch pointed at on any plain `npm install`; the lockfile hid
this from CI, which runs `npm ci`. The pin records existing behaviour --
the resolved commit is unchanged.
CONTRIBUTING.md described a different project entirely -- it instructed contributors to run `bower install` and `polymer test`, neither of which exists here, and none of its guidance matched this toolchain. Replaced with the real workflow. Both documents now lead with the two things most likely to cause a silently wrong change: that this repository is public while its consumers are not, and that the committed build output is what consumers execute, so a src/ change that is not rebuilt is reviewed, approved, merged and never run. Also documents that `npm test` and `npm run typecheck` check different things and that both are required, since interfaces are erased at runtime and the suite cannot fail on a type change; records the CI step order; and corrects the dependency list.
`npm audit fix` without --force, so every change is semver-compatible. Takes the advisory count from 17 (1 low, 11 moderate, 5 high) to 13 (11 moderate, 2 high). Only package-lock.json changes. Verified afterwards: npm ci, build, test, typecheck and lint all exit 0, and the build output is unchanged. The remaining 13 all arrive transitively through the single runtime dependency and are not reachable from this package's own code, which has no runtime logic. None can be resolved from here -- they need an upstream release. They are left visible rather than suppressed with an `overrides` entry, since forcing an unrelated version could break a consumer's runtime in a way this package cannot test.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Brings this repository's tooling and documentation up to the standard used across the codebase. Foundation only — no change to any type in
src/model/orsrc/interface/. The compiled output is byte-identical tomain.Two CI gates are the substance here. Both close gaps where a change could pass review and not take effect.
1. The committed build output is now gated
lib/is committed,exportspoints straight at it, and there is nopreparescript — so installing this package from git performs no build. Consumers execute the committedlib/; reviewers readsrc/. Those are different files, and nothing in a PR diff can reveal a mismatch, because thesrc/diff is exactly what it claims to be.There is no drift today — this is a latent hazard, not a live defect. Verified by deleting
lib/entirely, rebuilding, and observing zero diff.CI now regenerates the output after every build and fails if it differs from what was committed.
The check uses
git status --porcelain, not the more obviousgit diff --exit-code. Measured, not assumed:git diff --exit-codegit status --porcelaingit diffsees only tracked files, so a new module compiles to untracked output and slips through — i.e. the obvious form would have been inert exactly when someone adds a module.2. The type gate was inert in two separate ways, and is now repaired
The runtime suite cannot fail on a type change. Interfaces are erased, so a test that declares an object literal and reads a property back is testing JavaScript. Measured:
npm testresultpending?: numberfrom an interface outrightnpx vitest run --typecheckwas not a fallback:typecheck.includedefaults to**/*.test-d.ts, this repo has none, so it checked zero files and reportedType Errors no errorswith the field deleted.And
tsc -p tsconfig.test.jsonfailed on a clean tree (TS2688) becausetypeRootswas narrowed tonode_modules/@typesby the base config — it had evidently never run successfully.Fixed by widening
typeRoots, exposed asnpm run typecheck, and gated in CI. Positive-controlled against three mutations — deleted interface field, wrong-typed assignment, removed enum member — each now exits non-zero, with the clean tree at 0.3. Disclosure guard
.github/scripts/check-private-markers.shscans tracked files, commit messages and authorship. Commit messages are included because they never appear in a file diff.integrityhashes, 4 from a video id in a fixture) against 0 true positives. A check that fires on every lockfile refresh gets removed, which is the same inert outcome as no check.4. Instructions, docs, dependencies
.github/instructions/(6 files) and a rewritten.github/copilot-instructions.md.CONTRIBUTING.mdwas boilerplate from an unrelated project — it told contributors to runbower installandpolymer test. Rewritten.zod@^4.4.3added as a runtime dependency, unused for now, so the schema work that follows touches no manifest.#ref, so it floated to whatever the default branch pointed at on any plainnpm install. The lockfile hid this from CI, which runsnpm ci. Now pinned to the exact commit already resolved — behaviour unchanged, the manifest simply states it.npm audit fix(no--force): 17 → 13 advisories (5 high → 2 high).Verification
All exit codes observed on the final tree:
Mutation tests run, each reverted and re-verified green: interface field deleted (suite blind, typecheck catches), enum value changed (3 red), enum member removed (typecheck catches), wrong-typed assignment (typecheck catches), new/changed/removed module vs both drift probes, synthetic marker in a tracked file, in a commit message, inside the guard itself, and an attribution trailer.
Attribution-trailer count in commit messages: 0, with the grep positive-controlled (it returns 1 against a synthetic trailer). Authorship: a single identity for both author and committer across all four commits, so a squash merge cannot synthesise a trailer server-side.
Found but deliberately NOT fixed
Reported rather than silently changed, since narrowing a published type is a breaking change for consumers:
anyremain insrc/;T | anyis 0 — the earlier narrowing has not regressed (both counts positive-controlled). Six of the eight are timestamp fields whose honest type is a three-way union needing the server SDK'sFieldValue— a type this package should not pull into every consumer's dependency closure. The other two are a diagnostic snapshot and a deliberate catch-all index signature. The real resolution is runtime schemas, not a type edit.BaseFirestorecarries[x: string]: any, which disables excess-property checking for every extending interface everywhere. Deliberate for a sparse document store, but it is why runtime validation is necessary rather than optional.@typescript-eslint/no-explicit-anyisoff;tsconfig.jsonsets"strict": truethen overrides it with"noImplicitAny": falseand"strictNullChecks": false; and ESLint'sfilesis scoped tosrc/**/*.ts, sotest/is not linted. Each is a compile-breaking change to flip and belongs in its own reviewed unit of work.overrides, since forcing an unrelated version could break a consumer's runtime in a way this package cannot test.Notes for the follow-up schema work
This branch is the base for it.
zodis installed,npm run typechecknow actually checks, and the committed-output gate means a schema that never gets compiled intolib/will fail CI rather than silently never running.