Uh oh!
There was an error while loading. Please reload this page.
fix(devx): read a published README's import specifier against the packages that exist - #10923
Draft
claude[bot] wants to merge 1 commit into
Draft
fix(devx): read a published README's import specifier against the packages that exist#10923claude[bot] wants to merge 1 commit into
claude[bot] wants to merge 1 commit into
Conversation
…kages that exist Nine `@objectstack/` names across seven published documents named a package in no directory of this repo; five sat on import lines inside runnable fences. `check:published-readme-exports` was blind to all of them by construction — it resolves a documented import through the workspace member map, so a specifier that is not a member has no type entry to compare against and the gate reads no further. The gate now makes the member-existence claim before the symbol one, fenced to this repo's own npm scope, and the run header prints the scoped population as N/N. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DdCnBGcHeufjrq7drTD3wt
Contributor
📓 Docs Drift CheckNothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 7 changed package(s)), so this run has no opinion about the docs. What this run could not see
Coarse fallback — 131 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): |
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 freeto 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.
Fixes#10893
Verified at
e91070c149— every gate quoted below was run on that exact commit.The sweep found nine, not three
The card named three claims. A population-level sweep — every
@objectstack/token in all 60 published documents, checked against the workspace member map — found nine dead names across seven published documents, and the two worst were not on the card:triggers/trigger-schedule/README.md(#title, ×4)@objectstack/plugin-trigger-scheduletriggers/trigger-record-change/README.md(#title, ×2)@objectstack/plugin-trigger-record-changeplugins/plugin-security/README.md@objectstack/plugin-org-scopingplugins/plugin-security/README.md@objectstack/service-tenantcloudservices/service-package/README.md@objectstack/service-marketplaceplugins/embedder-openai/README.md@objectstack/knowledge-tursodrivers/driver-sqlite-wasm/README.md@objectstack/driver-sqlite,@objectstack/driver-postgresspec/prompts/architecture.md@objectstack/protocolTwo packages tell readers to install themselves under a name that has never been published.
packages/triggers/trigger-scheduleships as@objectstack/trigger-schedule; its README is titled# @objectstack/plugin-trigger-scheduleand both of its fenced examples import from that name. Same fortrigger-record-change. These are the pages npm renders for those two packages, and the exports gate has been green over them for its whole life.Why the gate could not see any of it
check:published-readme-exportsresolves a documented import through the workspace member map —publishedDocs()buildsbyNamefromworkspaceDirs(), andrun()'sresolveTargetopened withif (!byName.has(name)) return null.analyzeDocumentreadsnullas "not this repo's business" andcontinues, which is the rule written forreact.So the chain is: member map → no entry →
null→continue→ no further reading. The subpath check, the type-entry check and the symbol check all sit below that line and never ran. The gate was strict about a member that exists and silent about one that does not — the inverse of the useful direction, and a strictly worse defect than the fabricated symbol it was built for (#9532), because a fabricated symbol at least installs.The asymmetry was invisible in the output too. The header printed two numbers about what resolved and none about what it could not place:
What changed, and where the fence is
preTypeTarget(name, isMember)— exported, three arms — now owns the pre-type decision: a member defers to type resolution, an@objectstack/-scoped non-member is a finding, anything else is skipped exactly as before. The header states the population as a resolved/total pair, followingcheck-published-readme-links' assertion 5, so a recogniser that stops matching shows up as a denominator that fell rather than as a defect count that never moved:The fence is the scope, and it was measured, not argued. Of 214 import statements, 200 are
@objectstack/-scoped and 5 named no workspace member — 4 documents, 4 packages, every one a real defect. Zero false positives.The wider fence — every
@objectstack/token anywhere in a published document — was measured too, and it is the naive version this file's header warns about: 14 distinct non-member names across 26 sites, of which five are legitimate by construction and would each need muting.@objectstack/security-enterprise(the enterprise edition, whose install hintpackages/cliprints andcapability-preflight.test.ts:74pins),@objectstack/service-tenant(the cloud runtime, so named inpackages/spec/src/system/constants/platform-object-names.ts:132),@objectstack/framework(the umbrella install name), and the two package namesservice-datasource's README recalls as its own past. Prose may name a package this repo does not build; a runnable import may not. That is the same boundary the existingdiff-fence rule draws ("a removed import is a statement about the past").⛔ No allowlist, deliberately. A roster of out-of-repo
@objectstack/packages is today a population of zero — no published document imports one from a fence — and this file refuses populations of zero everywhere else. Whether a runnable example may ever name one is a policy question; it is #10893's second shape and is filed as #10921 rather than answered with a blank file a future author fills in to quiet a red gate.Is this a widening of #10368's fence?
No, and the distinction is the card's own. #10368 forbids widening what the gate reads about a resolved member — it is a hand-read, not a recognizer change. This adds a new population (specifiers that resolve to nothing), which #10893 says explicitly is why it was not folded into that card. Nothing about the member/symbol/call-site reads changed:
symbolChecksandcallChecksare unmoved except for the two import lines the repairs deleted.The population refusal
scopeRefusalis a separate exported post-pass, modelled onbindingRefusalrather than bolted ontopopulationRefusal's axis. It has to be separate:targetscounts specifiers that resolved, and this half's whole subject is the ones that did not — a scope recogniser that broke would driveownScopeto zero whiletargetsstayed non-zero on the workspace members that carry no scope (create-objectstack,objectstack-blank).What each dead claim now says, with the evidence
⛔ No replacement package name is invented anywhere below. Where the honest answer is "not available in this repo", the page says that.
plugin-org-scoping→ the capability is real and ships elsewhere.packages/cli/src/commands/serve.ts:2379-2449resolves@objectstack/organizationsfrom the host app and constructsorgMod.OrganizationsPlugin();packages/verify/src/harness.ts:466records it as "cloud-private and only ever lives in the host app'snode_modules";doctor.ts:535calls it "the enterprise@objectstack/organizationsruntime". The README now names that package and itsOrganizationsPlugin, and records that asking for the wall without it is a refusal to boot (ADR-0093 D5), not a silent downgrade. The fencedimport { OrgScopingPlugin }block is gone rather than re-pointed: a reader cannotpnpm adda cloud-private package, so a runnable fence would still be a lie.service-tenant→ real, cloud, and not what that bullet was about. The "enforcing tenant isolation" bullet pointed at it;platform-object-names.ts:132shows it contributes cloud control-plane objects, not the isolation wall. The bullet now points at the organizations runtime, which is where the wall comes from.service-marketplace→ never built. ADR-0003 lists it under "future", ADR-0016:82 and ADR-0025:94 both say "(separateservice-marketplace)" as out of scope. The loading half that exists here is@objectstack/cloud-connection'sMarketplaceInstallLocalPlugin, which installs a marketplace package into the local kernel through themanifestservice.plugin-trigger-schedule/plugin-trigger-record-change→ a rename, pinned. The exported class names (ScheduleTriggerPlugin,TimeRelativeTriggerPlugin,RecordChangeTriggerPlugin) were correct all along; only the package name was wrong, and each package's ownnamefield pins the right one.knowledge-turso→ the honest answer is a gap, and the page says so. The fenced block importedKnowledgeTursoPluginand passed it anembeddingoption — the paste-and-run hazard the card names. There is no substitute:KnowledgeMemoryPluginOptionsis{ id?: string }andKnowledgeRagflowAdapterOptionshas no embedder field, so no adapter in this repo consumes anIEmbedderat all. The example is now theembed()surface that does exist, and the missing half is stated. The runtime message that tells operators to mount that package is a different surface — filed as [finding] The knowledge settings manifest tells an operator at runtime to mount@objectstack/knowledge-turso, a package in no directory of this repo #10920.driver-sqlite/driver-postgres→ one package covers both.@objectstack/driver-sql's own README says "Supports PostgreSQL, MySQL, SQLite via Knex.js", withbetter-sqlite3optional andpgan optional peer. The comparison table names it twice, and says why.@objectstack/protocol→@objectstack/spec. A published AI-prompt document instructing generators to writeimport { User } from '@objectstack/protocol')— the same sentence namespackages/specas the path it is replacing.Two ablations, each with the prediction written down before the run
1. Do the repairs close findings the new assertion makes? Reverted the seven documents to
origin/main, gate change in place. Predicted: exit 1, exactly 5 findings, all ids|specifier|, at five named file:line pairs, and zero findings naming the ten prose-only names. Observed:✗ check:published-readme-exports — 5 undocumented symbol claim(s), all five|specifier|, at exactlyembedder-openai:84,plugin-security:43,trigger-record-change:77,trigger-schedule:51and:113, andgrep -cover the ten forbidden names in the output returned 0.No rebuild was needed for this leg and that is stated rather than assumed: the mutation is markdown only,
dist/is identical between legs, and the.d.tsside of every claim is untouched. Mutation proven on disk by marker counts anchored on the reverted text (from '@objectstack/plugin-trigger-0→3,from '@objectstack/plugin-org-scoping'0→1,from '@objectstack/knowledge-turso'0→1), not by an editor's exit code. Restore leg proven withgit diff --quiet— clean — and the same three counts back at 0.The header prediction (
194/199) was not observable on the red path, becauseheaderis only printed by the green branch. Reported rather than quietly dropped, and measured directly instead:reachedTargetson the reverted tree returnsownScope 199, unresolvable 5, rendering214 import statement(s), 49 workspace type entr(ies), 194/199— which also reproduces the exact214 / 49pair the card quotes fromcc222637a3.2. Are the new self-test pins load-bearing? Deleted the
if (target.unresolvable)branch fromanalyzeDocument(545 chars, replaced with a marker;if (target.unresolvable)1→0, marker 0→1).First prediction: 7 failures.Observed 6 — and the miss was the useful part. The pin
must name the package it could not placestayed green with the branch deleted, because ablation does not make the gate fall silent:{unresolvable:true}carries nodeclared, so the next arm claims it and emits asubpathfinding whose text also interpolates the specifier. Readingfindings[0]made that pin non-discriminating.Fixed by selecting the finding by kind rather than by position, and the measured reason is recorded in the source beside it. Re-run with the corrected prediction of 7: observed
✗ check:published-readme-exports --self-test — 7 failure(s), matching pin for pin — the id pin, all five text pins, and the ordering pin flipping from['specifier']to['subpath']. Every other pin stayed green, including the threepreTypeTargetarms, both foreign negative controls,reachedTargets,scopeRefusalin both directions,headerLine, and the "repaired specifier is silent" control. Restored,git diff --quietclean, self-test exit 0.That ablation also turned the ordering pin from an argument into a measurement: without the branch, the author is told that a package which is not there fails to declare a subpath — a wrong remedy, not a missing one.
The negative control that decides whether this is usable at all
react,hono,zod,@libsql/clientand@oclif/coreare all imported by published READMEs in this tree and none is a workspace member. A scope-blind recogniser would redden five correct documents on its first run. Pinned aspreTypeTarget('react', false) === nullandpreTypeTarget('@libsql/client', false) === null, plus a prefix-not-substring pin.Does this gate refuse a baseline?
No — and the card's dispatch assumed it did. Its sibling
check:published-readme-linksrefuses one by design; this one hasscripts/published-readme-exports.baseline.json, a shrink-only⛔ MAINTAINER-ONLYledger reconciled in both directions. It currently holdsentries: [], which is its documented success state. That is why all nine claims are repaired here and nothing is baselined: an author may not add a row, and a new fabrication must arrive as a fresh finding.Changeset
Owed, and derived rather than recalled. All seven packages have
privateunset and afilesarray carrying the edited document —README.mdfor six, andpackages/specpublishesprompts/, which is wherearchitecture.mdlives. A published README ships inside the npm tarball, so these edits reach an npm reader. That is the same conclusion PR #10892 reached for the same files and the precedent #9906 and PR #10365 set.patchfor all seven;skip-changesetwould have been wrong.Coordination (#10840)
Re-checked immediately before pushing, against all 17 open PRs. #10906 is #10840's remedy and touches
check-driver-conformance.mjs,check-skill-compatibility-version.mjs,check-skill-frame-sync.mjs,scripts/pm/bare-root-worklist.mjsandlint.yml— notcheck-published-readme-exports.mjs. No collision, and none is likely: this gate derives its population frompnpm-workspace.yamlrather than from a bare-root literal, so it is not in that species, and #10906'sTRIAGEmap does not list it. No open PR touches any of the seven documents either, andorigin/mainmoved to58563be069with no overlap on my paths.Gates
25 run at
e91070c149, exit codes captured before any pipe (cmd >> "$OUT" 2>&1; ec=$?). 22 derived bynode scripts/pm/dispatch-gates.mjswith no path list passed — it takes its own change set from the merge base — pluscheck:i18n(convention-triggered by theplugin-securityedit),check:nul-bytes, andcheck:published-readme-links(the sibling gate in this family, and the one whose assertion 5 reads relative targets in the files edited here). All 25EXIT=0.The two the card asks to be quoted, in their own words:
✓ check:published-readme-exports — 60 published document(s) across 77 workspace package(s); 212 import statement(s), 51 workspace type entr(ies), 197/197 @objectstack/ specifier(s) naming a workspace member.✓ check:published-readme-links — 175 outbound link(s) across 60 published markdown file(s): 0 root-relative, 0 non-canonical origin(s), 27 docs-site page(s) resolved (0 via redirect), 1 anchor(s) verified, 102/102 relative target(s) found in the tree.Both self-tests too:
✓ check:published-readme-exports --self-testand✓ check:published-readme-links --self-test. Alsocheck-i18n-bundles: OK (9 package(s) — all bundles in sync, no undeclared authoring keys),check-nul-bytes: OK (scanned 6302 text file(s) … no raw ASCII control bytes)and✓ check-nul-bytes --self-test: 75 assertions over a temp git repo (real scan() path). The remaining 20 are in the branch's run log, each with its ownEXIT=0.check:published-readme-exportsneeds a built workspace (it readsdist/*.d.ts), sopnpm buildran first — 71/71 tasks successful — under the shared verify lock. Every heavy command in this task went throughscripts/pm/os-verify-lock.sh.Filed, not fixed here
@objectstack/knowledge-turso, a package in no directory of this repo #10920 —service-settings' knowledge manifest tells an operator at runtime toMount @objectstack/knowledge-turso, andspec/src/contracts/embedder.tsnames it as anIEmbedderconsumer. Runtime + spec surface, not published markdown, and it needs the prior question answered: is that package published anywhere?@objectstack/packages this repo does not build are named in published docs with no declared roster — including one the CLI tells users topnpm add#10921 — the declared-roster policy question: four@objectstack/packages this repo does not build are named in published docs with nothing declaring them, includingpnpm add @objectstack/frameworkinpackages/console/README.md. This is [finding] Three published READMEs tell readers to install packages that are in no directory of this repo — and check:published-readme-exports is blind to it by construction #10893's second shape.Generated by Claude Code