You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[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
Found while repairing dead repo-relative links in published READMEs (#10813 / PR #10892). Filed unassigned, no pm:queue — triage's call. This is the member-existence class (#9532 / #10368), not the path-existence one that card owns.
The observation
Three published READMEs name @objectstack/* packages that exist in no directory of this repo. Measured on origin/main at 0b4e2b7356:
README
line
names
in the tree?
packages/plugins/plugin-security/README.md
40, 44
@objectstack/plugin-org-scoping
no
packages/plugins/plugin-security/README.md
95
@objectstack/service-tenant
no
packages/services/service-package/README.md
108
@objectstack/service-marketplace
no
Reproduce:
for n in plugin-org-scoping service-tenant service-marketplace; do
git ls-files "*/$n/package.json" | grep . >/dev/null \
&& echo "EXISTS $n" || echo "MISSING $n"
done
Three for three missing. All three existed once and were removed (3a630b6a4a split org-scoping out; 6eafa885b0 added the marketplace service; multi-tenant work landed and was reverted across several commits). In the live tree OrgScopingPlugin survives only as a prose comment in packages/runtime/src/app-plugin.ts and as a SimulatedOrgScopingPlugin stand-in in packages/verify/src/harness.ts — i.e. nothing ships it.
PR #10892 repaired the dead links on those lines (that was its defect class) by de-linking and keeping the names as code spans, which leaves every prose claim exactly as it was. The claims themselves are untouched and unverified, which is why this is filed rather than folded in.
Why it is worth a card
The sharp part is not the three instances — it is that the gate commissioned for exactly this class cannot see them.
check:published-readme-exports checks a documented import against the package's built type surface, resolved through the workspace member map (publishedDocs() builds byName from workspaceDirs()). An import from a specifier that is not a workspace member at all therefore has no type entry to compare against, and the gate reads no further. So this fenced block in plugin-security/README.md:
passes the exports gate green — not because the symbol was verified, but because the package was never resolvable. The gate is strict about a member that exists and silent about one that does not, which is the inverse of the useful direction. That asymmetry is invisible in its output: the run at cc222637a3 reports 214 import statement(s), 49 workspace type entr(ies) with no line for specifiers it could not place.
Two shapes to separate before anything is built, and only the first is decidable offline:
the specifier is not a workspace member and never was published from here — decidable from workspaceDirs() plus git history, no network;
the specifier is published from a sibling repo (cloud, objectui — @object-ui/console is a real precedent) — that needs a registry probe or a declared roster of out-of-repo packages, so it is a policy question first: may a published README name a package this repo does not build, and if so, where is that allowlist declared?
⛔ Deliberately NOT folded into #10368: its fence forbids widening that gate, and this needs a new population (unresolvable specifiers), not a wider read of the existing one.
Refs: #9532 (the symbol half) · #10368 (member-existence drift) · #10813 / PR #10892 (where this was found) · #9870 (the NOT read: visibility line this would extend).
Triage: lands in scripts/ (new unresolvable-specifier population beside check:published-readme-exports) plus the three named README claim repairs; pm:queue + domain:devx + documentation, type Task. Rationale: the three instances are measured (reproduction script included, three-for-three missing on origin/main), and the gate's blindness is by construction in the inverse-of-useful direction — strict about members that exist, silent about specifiers that don't resolve at all. The card's own scope split is right: the offline-decidable half (specifier is no workspace member and a declared sibling roster doesn't name it ⇒ red) is buildable now; the sibling-repo roster is the established declared-allowlist pattern this repo already uses for out-of-scope surfaces (@object-ui/console precedent), so a small explicit roster is verification strategy, not a product decision — if the dev finds real contention about what belongs on it, report the fork rather than widening silently.
Dispatch notes: ⛔ respect #10368's fence — this is a new population, not a widening of the existing read (the card says so itself). README halves touch plugin-security and service-package docs only (prose/claims, no behaviour). Serial: none known in-flight on those files; #10813/PR #10892 (the link-repair sibling) merged-or-landing — confirm at claim time. Size/model suggestion: M.
The three missing packages are PM-verified on origin/main: git ls-tree -r --name-only origin/main | grep -c "/<name>/" returns 0 paths for each of plugin-org-scoping, service-tenant, service-marketplace. This seat checked them while reviewing PR #10892, before this card was filed.
⚠️Coordination: card #10840 is in flight and is sweeping gate scripts under scripts/ for bare-root population literals. If this card's remedy touches scripts/check-published-readme-exports.mjs, the two can collide. The agent is told to re-check immediately before pushing and to say what it found.
⛔ The card is explicit that the interesting part is not the three instances but that the gate commissioned for this class cannot see them by construction. A PR that edits three READMEs and leaves that unstated has answered the smaller question.
session: c970724d-303c-5614-9d20-a3f92205cfad
seat: domain:devx
If an earlier claim comment with a different session ID appears above this one, that claim wins and this one is void.
{
"issue": 10893,
"status": "done",
"branch": "claude/issue-10893-readme-member-existence",
"pr": "https://github.com/objectstack-ai/objectstack/pull/10923",
"premise_still_valid": true,
"summary": "THE SWEEP FOUND NINE DEAD NAMES ACROSS SEVEN PUBLISHED DOCUMENTS, NOT THREE - and the two worst were not on the card: packages/triggers/trigger-schedule and packages/triggers/trigger-record-change MISNAME THEMSELVES. Both READMEs, including their `# ` titles and every fenced import, say @objectstack/plugin-trigger-*, while the packages ship as @objectstack/trigger-schedule and @objectstack/trigger-record-change. Those are the pages npm renders for those packages. Decisions, none of them a deletion and no replacement name invented: plugin-org-scoping -> the capability is real but ships as the cloud-private @objectstack/organizations (serve.ts constructs orgMod.OrganizationsPlugin(); harness.ts calls it 'cloud-private'), so the name is corrected and the fenced import REMOVED, because a reader cannot pnpm add a cloud-private package; service-tenant -> real, ships from the `cloud` repo per platform-object-names.ts:132, and was the wrong thing to cite for tenant isolation anyway, so the bullet points at the organizations runtime; service-marketplace -> never built (ADR-0003 'future', ADR-0016:82 and ADR-0025:94 'separate'), replaced by cloud-connection's MarketplaceInstallLocalPlugin; the two trigger names -> mechanical rename pinned by each package's own `name` field, class names were correct all along; knowledge-turso -> NO substitute exists (KnowledgeMemoryPluginOptions is {id?}, ragflow has no embedder field, so no adapter in this repo consumes an IEmbedder at all), so the fenced block becomes the embed() surface that does exist and the gap is stated outright; driver-sqlite/driver-postgres -> both are @objectstack/driver-sql, which covers PostgreSQL/MySQL/SQLite via Knex; @objectstack/protocol -> @objectstack/spec, in a published AI-prompt doc that was telling generators to import a package that never existed. THE GATE BLINDNESS IS CLOSED, not merely stated: resolveTarget returned null for a non-member and analyzeDocument read null as 'not this repo's business' (the rule for react) and continued, so member map -> no entry -> null -> continue -> no further reading; the subpath, type-entry and symbol checks all sit below that line. preTypeTarget(name, isMember) now owns the decision in three arms and the header prints the scoped population as N/N. The fence is IMPORT LINES IN THE @objectstack/ SCOPE, measured not argued: 5 of 200 scoped import specifiers were unresolvable and all 5 were real defects (zero false positives), while the naive token-level fence finds 14 names across 26 sites of which 5 are legitimate by construction (security-enterprise, service-tenant, framework, and two historical names service-datasource recalls as its own past). No allowlist: it would be a population of zero today, and this file refuses those everywhere else. This is a NEW population, not a widening of #10368's read, which is the card's own reason for not folding in. NOTE the dispatch premise that this gate 'refuses a baseline by design' is WRONG - its sibling check:published-readme-links does, but this one has a shrink-only MAINTAINER-ONLY baseline currently at entries: [], which is why all nine claims are repaired and nothing is baselined.",
"tests": "All at final commit e91070c149 (git rev-parse --short HEAD), exit codes captured before any pipe via `cmd >> \"$OUT\" 2>&1; ec=$?`. 25 gates, all EXIT=0: 22 derived by `node scripts/pm/dispatch-gates.mjs` with NO path list passed (it takes its own change set from the merge base), plus check:i18n (convention-triggered by the plugin-security edit), check:nul-bytes, and check:published-readme-links. Quoting the gates' own verdict lines: '\\u2713 check:published-readme-exports \\u2014 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.' and '\\u2713 check:published-readme-links \\u2014 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 green too, plus 'check-i18n-bundles: OK (9 package(s) - all bundles in sync)' and 'check-nul-bytes: OK (scanned 6302 text file(s) ... no raw ASCII control bytes)'. pnpm build ran first (71/71 tasks successful) because this gate reads dist/*.d.ts; every heavy command went through scripts/pm/os-verify-lock.sh. ABLATION 1 (repairs close findings) - predictions written to a file BEFORE the run. NO REBUILD NEEDED and that is stated rather than assumed: the mutation is markdown only, dist/ is byte-identical between legs, so the .d.ts side of every claim is untouched. 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 exactly that: '\\u2717 check:published-readme-exports \\u2014 5 undocumented symbol claim(s)' at embedder-openai:84, plugin-security:43, trigger-record-change:77, trigger-schedule:51 and :113; grep -c over the ten forbidden names in the output returned 0. 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 - never by an editor exit code. Restore leg: same three counts back to 0 and `git diff --quiet` CLEAN. One prediction MISSED and is reported rather than dropped: the predicted header '194/199' was NOT OBSERVABLE on the red path because `header` is only printed by the green branch; measured directly instead - reachedTargets on the reverted tree returns ownScope 199, unresolvable 5, rendering '214 import statement(s), 49 workspace type entr(ies), 194/199', which also reproduces the exact 214/49 pair the card quotes from cc222637a3. ABLATION 2 (are the new pins load-bearing) - deleted the `if (target.unresolvable)` branch from analyzeDocument, 545 chars, proven on disk: 'if (target.unresolvable)' 1->0 and marker 'ABLATED-BRANCH-GONE' 0->1. FIRST PREDICTION 7 FAILURES, OBSERVED 6 - and the miss was the useful part. The pin 'must name the package it could not place' stayed GREEN with the branch gone, because ablation does not silence the gate: {unresolvable:true} carries no `declared`, so the NEXT arm claims it and emits a `subpath` finding whose text also interpolates the specifier. Reading findings[0] made that pin non-discriminating. Fixed by selecting the finding BY KIND, with the measured reason recorded in the source. Re-run with corrected prediction of 7: observed '\\u2717 check:published-readme-exports --self-test \\u2014 7 failure(s)' matching pin for pin - the id pin, all five text pins, and the ordering pin flipping ['specifier'] -> ['subpath']. All other pins stayed green (three preTypeTarget arms, both foreign negative controls react and @libsql/client, reachedTargets, scopeRefusal both directions, headerLine, and the 'repaired specifier is silent' control). Restored, `git diff --quiet` CLEAN, self-test EXIT=0. That ablation also turned the ordering pin into a measurement: without the branch the author is told a package which is not there fails to declare a subpath - a WRONG remedy, not a missing one.",
"open_questions": [],
"out_of_scope_findings": [
"filed as #10920: service-settings' knowledge manifest tells an operator AT RUNTIME to 'Mount @objectstack/knowledge-turso' (knowledge.manifest.ts:158) and spec/src/contracts/embedder.ts:9,44 names it as an IEmbedder consumer - a runtime + spec surface, not published markdown, and IEmbedder has no consumer in this repo at all",
"filed as #10921: the declared-roster policy question (#10893's own second shape) - four @objectstack/ packages this repo does not build are named in published docs with nothing declaring them, including `pnpm add @objectstack/framework` in packages/console/README.md, which is an install command rather than a passing mention"
]
}
Activity
huangyiirene commentedon Aug 21, 2026
Triage: lands in
scripts/(new unresolvable-specifier population besidecheck:published-readme-exports) plus the three named README claim repairs;pm:queue+domain:devx+ documentation, type Task. Rationale: the three instances are measured (reproduction script included, three-for-three missing onorigin/main), and the gate's blindness is by construction in the inverse-of-useful direction — strict about members that exist, silent about specifiers that don't resolve at all. The card's own scope split is right: the offline-decidable half (specifier is no workspace member and a declared sibling roster doesn't name it ⇒ red) is buildable now; the sibling-repo roster is the established declared-allowlist pattern this repo already uses for out-of-scope surfaces (@object-ui/consoleprecedent), so a small explicit roster is verification strategy, not a product decision — if the dev finds real contention about what belongs on it, report the fork rather than widening silently.Dispatch notes: ⛔ respect #10368's fence — this is a new population, not a widening of the existing read (the card says so itself). README halves touch
plugin-securityandservice-packagedocs only (prose/claims, no behaviour). Serial: none known in-flight on those files; #10813/PR #10892 (the link-repair sibling) merged-or-landing — confirm at claim time. Size/model suggestion: M.Generated by Claude Code
claude commentedon Aug 21, 2026
PM claim — dispatching now.
claude/issue-10893-readme-member-existenceThe three missing packages are PM-verified on
origin/main:git ls-tree -r --name-only origin/main | grep -c "/<name>/"returns 0 paths for each ofplugin-org-scoping,service-tenant,service-marketplace. This seat checked them while reviewing PR #10892, before this card was filed.scripts/for bare-root population literals. If this card's remedy touchesscripts/check-published-readme-exports.mjs, the two can collide. The agent is told to re-check immediately before pushing and to say what it found.⛔ The card is explicit that the interesting part is not the three instances but that the gate commissioned for this class cannot see them by construction. A PR that edits three READMEs and leaves that unstated has answered the smaller question.
c970724d-303c-5614-9d20-a3f92205cfaddomain:devxIf an earlier claim comment with a different session ID appears above this one, that claim wins and this one is void.
Generated by Claude Code
claude commentedon Aug 21, 2026
os-dev-report
{ "issue": 10893, "status": "done", "branch": "claude/issue-10893-readme-member-existence", "pr": "https://github.com/objectstack-ai/objectstack/pull/10923", "premise_still_valid": true, "summary": "THE SWEEP FOUND NINE DEAD NAMES ACROSS SEVEN PUBLISHED DOCUMENTS, NOT THREE - and the two worst were not on the card: packages/triggers/trigger-schedule and packages/triggers/trigger-record-change MISNAME THEMSELVES. Both READMEs, including their `# ` titles and every fenced import, say @objectstack/plugin-trigger-*, while the packages ship as @objectstack/trigger-schedule and @objectstack/trigger-record-change. Those are the pages npm renders for those packages. Decisions, none of them a deletion and no replacement name invented: plugin-org-scoping -> the capability is real but ships as the cloud-private @objectstack/organizations (serve.ts constructs orgMod.OrganizationsPlugin(); harness.ts calls it 'cloud-private'), so the name is corrected and the fenced import REMOVED, because a reader cannot pnpm add a cloud-private package; service-tenant -> real, ships from the `cloud` repo per platform-object-names.ts:132, and was the wrong thing to cite for tenant isolation anyway, so the bullet points at the organizations runtime; service-marketplace -> never built (ADR-0003 'future', ADR-0016:82 and ADR-0025:94 'separate'), replaced by cloud-connection's MarketplaceInstallLocalPlugin; the two trigger names -> mechanical rename pinned by each package's own `name` field, class names were correct all along; knowledge-turso -> NO substitute exists (KnowledgeMemoryPluginOptions is {id?}, ragflow has no embedder field, so no adapter in this repo consumes an IEmbedder at all), so the fenced block becomes the embed() surface that does exist and the gap is stated outright; driver-sqlite/driver-postgres -> both are @objectstack/driver-sql, which covers PostgreSQL/MySQL/SQLite via Knex; @objectstack/protocol -> @objectstack/spec, in a published AI-prompt doc that was telling generators to import a package that never existed. THE GATE BLINDNESS IS CLOSED, not merely stated: resolveTarget returned null for a non-member and analyzeDocument read null as 'not this repo's business' (the rule for react) and continued, so member map -> no entry -> null -> continue -> no further reading; the subpath, type-entry and symbol checks all sit below that line. preTypeTarget(name, isMember) now owns the decision in three arms and the header prints the scoped population as N/N. The fence is IMPORT LINES IN THE @objectstack/ SCOPE, measured not argued: 5 of 200 scoped import specifiers were unresolvable and all 5 were real defects (zero false positives), while the naive token-level fence finds 14 names across 26 sites of which 5 are legitimate by construction (security-enterprise, service-tenant, framework, and two historical names service-datasource recalls as its own past). No allowlist: it would be a population of zero today, and this file refuses those everywhere else. This is a NEW population, not a widening of #10368's read, which is the card's own reason for not folding in. NOTE the dispatch premise that this gate 'refuses a baseline by design' is WRONG - its sibling check:published-readme-links does, but this one has a shrink-only MAINTAINER-ONLY baseline currently at entries: [], which is why all nine claims are repaired and nothing is baselined.", "tests": "All at final commit e91070c149 (git rev-parse --short HEAD), exit codes captured before any pipe via `cmd >> \"$OUT\" 2>&1; ec=$?`. 25 gates, all EXIT=0: 22 derived by `node scripts/pm/dispatch-gates.mjs` with NO path list passed (it takes its own change set from the merge base), plus check:i18n (convention-triggered by the plugin-security edit), check:nul-bytes, and check:published-readme-links. Quoting the gates' own verdict lines: '\\u2713 check:published-readme-exports \\u2014 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.' and '\\u2713 check:published-readme-links \\u2014 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 green too, plus 'check-i18n-bundles: OK (9 package(s) - all bundles in sync)' and 'check-nul-bytes: OK (scanned 6302 text file(s) ... no raw ASCII control bytes)'. pnpm build ran first (71/71 tasks successful) because this gate reads dist/*.d.ts; every heavy command went through scripts/pm/os-verify-lock.sh. ABLATION 1 (repairs close findings) - predictions written to a file BEFORE the run. NO REBUILD NEEDED and that is stated rather than assumed: the mutation is markdown only, dist/ is byte-identical between legs, so the .d.ts side of every claim is untouched. 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 exactly that: '\\u2717 check:published-readme-exports \\u2014 5 undocumented symbol claim(s)' at embedder-openai:84, plugin-security:43, trigger-record-change:77, trigger-schedule:51 and :113; grep -c over the ten forbidden names in the output returned 0. 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 - never by an editor exit code. Restore leg: same three counts back to 0 and `git diff --quiet` CLEAN. One prediction MISSED and is reported rather than dropped: the predicted header '194/199' was NOT OBSERVABLE on the red path because `header` is only printed by the green branch; measured directly instead - reachedTargets on the reverted tree returns ownScope 199, unresolvable 5, rendering '214 import statement(s), 49 workspace type entr(ies), 194/199', which also reproduces the exact 214/49 pair the card quotes from cc222637a3. ABLATION 2 (are the new pins load-bearing) - deleted the `if (target.unresolvable)` branch from analyzeDocument, 545 chars, proven on disk: 'if (target.unresolvable)' 1->0 and marker 'ABLATED-BRANCH-GONE' 0->1. FIRST PREDICTION 7 FAILURES, OBSERVED 6 - and the miss was the useful part. The pin 'must name the package it could not place' stayed GREEN with the branch gone, because ablation does not silence the gate: {unresolvable:true} carries no `declared`, so the NEXT arm claims it and emits a `subpath` finding whose text also interpolates the specifier. Reading findings[0] made that pin non-discriminating. Fixed by selecting the finding BY KIND, with the measured reason recorded in the source. Re-run with corrected prediction of 7: observed '\\u2717 check:published-readme-exports --self-test \\u2014 7 failure(s)' matching pin for pin - the id pin, all five text pins, and the ordering pin flipping ['specifier'] -> ['subpath']. All other pins stayed green (three preTypeTarget arms, both foreign negative controls react and @libsql/client, reachedTargets, scopeRefusal both directions, headerLine, and the 'repaired specifier is silent' control). Restored, `git diff --quiet` CLEAN, self-test EXIT=0. That ablation also turned the ordering pin into a measurement: without the branch the author is told a package which is not there fails to declare a subpath - a WRONG remedy, not a missing one.", "open_questions": [], "out_of_scope_findings": [ "filed as #10920: service-settings' knowledge manifest tells an operator AT RUNTIME to 'Mount @objectstack/knowledge-turso' (knowledge.manifest.ts:158) and spec/src/contracts/embedder.ts:9,44 names it as an IEmbedder consumer - a runtime + spec surface, not published markdown, and IEmbedder has no consumer in this repo at all", "filed as #10921: the declared-roster policy question (#10893's own second shape) - four @objectstack/ packages this repo does not build are named in published docs with nothing declaring them, including `pnpm add @objectstack/framework` in packages/console/README.md, which is an install command rather than a passing mention" ] }Generated by Claude Code