Skip to content

No gate reads repository.directory, so a package move silently 404s its own npm source link — the rule never existed #15991

Description

@os-warren

Split out of #15478 on that card's own instruction: its triage comment recommends fixing the two stale strings and adding the check in one PR, but adds the escape hatch —

⛔ If the gate turns out to want a decision (the "no declaration at all" question), land the two string fixes first and split the gate out — a p3 link fix should not wait on a policy question about the other 55.

The decision question is real and is measured below, so this is that split. #15478 lands the string corrections; this card carries the gate.

Why nothing caught it

Measured on origin/main at aa6ba0623, in a clean worktree:

  • Grepping all of scripts/ for a read of the manifest repository field returns zero hits against repository.directory. Every textual match on repository is CI's github.repository context (owner/repo) in release-github-releases.mjs and scripts/pm/check-half-states.mjs, or prose in comments.

So the answer is not "wrong population" and not "missing rule inside an existing rule set" — the rule never existed. No gate has ever read this field.

The near-miss worth naming

scripts/check-published-files.mjs has exactly the right population — every non-private workspace package — and already parses each manifest and validates several of its fields (files, types, module, exports subpaths). Its six invariants (DECLARED / COMPLETE / SUFFICIENT / MINIMAL / …) are all about the files whitelist: what the tarball contains. Nothing in it asks where the tarball says its source lives. The un-enforced notch is a category, not an off-by-one: this repo gates published package contents and does not gate published package provenance metadata.

Two adjacent gates confirm the notch rather than closing it:

The measurement a gate would start from

Census over git ls-files for every tracked package.json (81 files, node_modules excluded), comparing repository.directory against the manifest's own directory and against the filesystem. Control: a directory known to exist must report EXISTS and a deliberately impossible one must report MISSING; the census refuses to print numbers unless both fire.

before #15478 after #15478
tracked package.json 81 81
declare repository.directory 57 57
declared value resolves and equals own directory 55 57
mismatched 2 0
declare nothing 24 24

⚠️ The decision this gate needs before it can be written

The 24 manifests that declare no repository.directory at all are not a homogeneous population. Counted mechanically off the same census, not by eye: 10 are private (the docs app, five examples/*, the repo root @objectstack/spec-monorepo, a create-objectstack template, and two packages/qa/*), and 14 are publishable and simply declare nothing:

@objectstack/connector-mcp        @objectstack/connector-openapi
@objectstack/connector-rest       @objectstack/connector-slack
@objectstack/embedder-openai      @objectstack/knowledge-memory
@objectstack/knowledge-ragflow    @objectstack/plugin-approvals
@objectstack/plugin-email         @objectstack/plugin-pinyin-search
@objectstack/plugin-reports       @objectstack/plugin-sharing
@objectstack/service-sms          @objectstack/trigger-api

⭐ Note the last one: @objectstack/trigger-api sits in the same packages/triggers/ family as the two packages #15478 corrects, and declares nothing at all. The family that produced the broken links is also the family with an undeclared sibling.

Is declaring repository.directory mandatory for a publishable package, or is silence allowed?

  • Mandatory turns this into a 14-manifest backfill, and every one of those 14 npm pages currently has no source deep link rather than a broken one.
  • Silence allowed makes the gate a pure consistency check over whoever opts in — cheap and uncontroversial, but it leaves the 14 permanently unlinked and gives the next new package no signal, which is the #3786 "hand-copied line with no gate" shape this repo has already been bitten by.

That is a policy call, not a correction, which is why it is not being guessed here.

⭐ Anti-vacuity requirement, quoted from the #15478 triage

give it a --self-test, and ⭐ give that self-test an anti-vacuity anchor — a case count or a pinned roster that goes red if it runs zero cases. #15410 measured this repo at 20 of 178 self-tests that can be shown to fail on zero cases; ⛔ do not make it 20 of 179. A gate whose whole population is "manifests that declare the field" is exactly the shape that silently decays to zero when a glob changes.

This applies with full force: the population here is defined by git ls-files plus a repository.directory predicate, so both a glob change and a field rename would silently empty it. The floor should pin the measured counts above (81 / 57) the way check:dual-build-cjs-loads pins entries/packages/cjsFiles/probes.

Recurrence, for the record

The #15478 residue came from two same-day renames, not the one the card names: plugin-trigger-* to trigger-* inside packages/plugins/ (f15d6f6f6) broke the first segment, then promotion to a first-class packages/triggers/ (ea4941ad8) broke the second. Both were pure directory renames — ea4941ad8 is 16 files, 0 insertions, 0 deletions — so no content-touching diff existed for a reviewer to notice. That is precisely the defect shape a mechanical check exists for, and it will recur at the next package move.

Unassigned and untriaged.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions