Skip to content

docs(runtime,metadata-protocol): the scope-less booted row is a marketplace / offline import, not a multi-package artifact's module - #16123

Merged
zhuangjianguo merged 3 commits into
mainfrom
claude/issue-14803-scope-less-row-attribution
Sep 6, 2026
Merged

docs(runtime,metadata-protocol): the scope-less booted row is a marketplace / offline import, not a multi-package artifact's module#16123
zhuangjianguo merged 3 commits into
mainfrom
claude/issue-14803-scope-less-row-attribution

Conversation

@claude

@claude claude Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Fixes #14803

Comment and prose only. No predicate, no assertion and no served shape changes; both pin suites the card names stay green as written.

Five platform-side comments attributed the scope-less served row that isWritablePackage answers false for to the type: module sub-package a multi-package artifact carries. That attribution is false. #14597 corrected it at the fixture (examples/app-multi-package's docblock, README and dogfood harness); this is the platform-side sweep the card fenced out of that PR.

The corrected claim, proved here rather than inherited

Measured on origin/main 9b459b791, against the built dist/ of @objectstack/spec and @objectstack/objectql, using the app-multi-package orders manifest body verbatim (which authors no scope key):

AUTHORED body has own "scope" key: false
A. ManifestSchema.parse(...).scope        = "project"
A. parsed has own "scope" key             = true
B. record found                           = true
B. record.manifest has own "scope" key    = false
B. record.manifest.scope                  = undefined
B. record.manifest keys                   = ["id","name","namespace","version","type","description",
                                             "engines","dependencies","navigationContributions"]
  • A is the defineStack path: ArtifactPackageEntrySchema.manifest is ManifestSchema (packages/spec/src/stack.zod.ts:233), whose scope is .default('project') (packages/spec/src/kernel/manifest.zod.ts:319 — the card cited :311; the line moved, the default did not). So no package of a compiled artifact is ever scope-less.
  • B is the registry path on the SAME unparsed body: installPackage stores toRecordManifest(manifest) (packages/objectql/src/registry.ts:3994, :1250), a key-by-key copy that applies no defaults, so the record has no scope key at all.

A genuinely scope-less row therefore arises only where a manifest reaches the registry without that parse — a marketplace install / offline file import (manifestService.register(rawBody) to ql.registerApp: booted, hence read-only), or a Studio-created base through POST /api/v1/packages (body.manifest || body to installPackage: writable). Every corrected comment now names those two paths.

What was kept, because it is true and load-bearing: a scope-less booted package is read-only while a scope-less database base is writable, and only engine.manifests tells them apart — which is why the server owns the verdict. No paragraph was deleted.

Sites, re-located by text on current origin/main

The card's line numbers are stale — PR #14767 (merged 2026-09-03) and PR #15592 (merged 2026-09-05) both moved protocol.ts. Every site below was found by text; the line is the one actually found.

# file card said found at what changed
1 packages/runtime/src/domains/packages.ts 286-289 495-496 withWritableVerdict docblock
2 packages/runtime/src/domains/packages-writable-verdict.test.ts 15-16 15-16 file docblock
3 same file 39-44 39-41 Pin 2 const docblock
4 packages/metadata-protocol/src/protocol.ts 7233-7234 7293 inline comment in getMetaItems
5 packages/metadata-protocol/src/get-meta-items-package-writable.test.ts 13-14 13-14 file docblock
6 .changeset/packages-read-door-writable-verdict.md 17-18 absent see below

Representative before / after, site 4:

- // FIRST, so a scope-less module booted from a multi-package artifact is
- // read-only while a scope-less Studio-created base is writable, and only
- // the server can tell the two apart.
+ // FIRST, so a scope-less BOOTED package — a marketplace install / offline
+ // file import, which reaches the registry through `ql.registerApp` with no
+ // `ManifestSchema` parse — is read-only while a scope-less Studio-created
+ // base (`POST /api/v1/packages`) is writable, and only the server can tell
+ // the two apart. ⛔ Neither is a module carried by a multi-package
+ // artifact: `defineStack` parses every `packages[]` entry through
+ // `ManifestSchema`, whose `scope` is `.default('project')`, so no package
+ // of a compiled artifact is ever scope-less.

Site 6 does not exist, and the deadline it carried has already passed

.changeset/packages-read-door-writable-verdict.md was consumed by 8a1bad8b8 (chore: version packages, 2026-09-04 10:20:24 +0000) and its body is now published release history at packages/runtime/CHANGELOG.md:1708 and packages/metadata-protocol/CHANGELOG.md:957, under ## 17.3.0 — a version npm reports as dist-tags.latest. This PR deliberately does not rewrite either CHANGELOG.md; the disposition of published release history is filed for a ruling as #16122. What this PR does instead is add a changeset that states the correction, so the retraction reaches the next release notes.

Three further sites the card did not enumerate, corrected here

The card asked for the enumeration to be tested. It is short by three, all the same defect class, all in files already claimed by this PR:

  • packages-writable-verdict.test.ts:120 — the Pin 2 test title: pin 2: a booted, SCOPE-LESS module (multi-package artifact sub-package) is writable: false, now pin 2: a booted, SCOPE-LESS package (marketplace / offline import) is writable: false. No gate or checklist pins that string (grepped whole-tree; the only occurrence was the definition).
  • packages-writable-verdict.test.ts:74-75 and get-meta-items-package-writable.test.ts:50 — the boot-map comments read "what ObjectQL.registerApp records for every package of a loaded artifact", sitting directly above a map whose scope-less member cannot have come from one. Both now name the two real boot sources and say which constant is which.

No identifier was renamed — CODE_MODULE and its id stay as they are, because that would be a code change.

Clause-② verdict: no, and here is the measurement

Built @objectstack/runtime and @objectstack/metadata-protocol (with their dependency closures) at this branch's head and grepped every published artifact under each package's files[]:

artifact carries the corrected comment text positive control
runtime/dist/index.js, index.cjs, index.d.ts 0 handlePackagesRequest = 3 in index.js
runtime/dist/index.js.map, index.cjs.map 1 each
metadata-protocol/dist/index.js, index.cjs, index.d.ts 0 isWritablePackage = 10 / 5
metadata-protocol/dist/index.js.map, index.cjs.map 1 each

Comments are stripped from the emitted JS and never enter the declaration files; the only published byte movement is inside sourcesContent of the sourcemaps, which is the source text itself, not a declaration. Reinforcing it for site 1: withWritableVerdict is a non-exported local, and grep -c withWritableVerdict runtime/dist/index.d.ts is 0 while that same file carries 419 TSDoc blocks — so TSDoc is retained in general, and this docblock is simply not in the published surface. No exported symbol or signature moves, and no request is newly accepted or rejected.

One follow-up commit: a docs anchor this PR's own line growth rotted

Lint & Repo Gates went red on head 33c6aab9a at step Docs anchors resolve to real headings, with two errors that are one problem. The withWritableVerdict docblock above grew by 8 lines, which pushed the ec.isSystem elevation read in the same file from :535 down to :543 — so content/docs/permissions/system-context.mdx:163 (census row 52) anchored a line that is no longer the read (anchor-is-not-a-read-site), and the read had no anchor (site-without-a-row).

Reproduced locally at that exact head before touching anything (pnpm check:system-context-census, exit 1, the same two errors), then repaired with the gate's own node scripts/check-system-context-census.mjs --fix, which reported 1 anchor(s) rewritten. Its entire output diff is one number in one table cell:

-| 52 | ... `domains/packages.ts:535`, `external-datasource-routes.ts:302` ...
+| 52 | ... `domains/packages.ts:543`, `external-datasource-routes.ts:302` ...

:543 is the real read — confirmed by reading the file at both revisions: on origin/main the shouldDenyAnonymous({ userId: ec?.userId, isSystem: ec?.isSystem, method: m }) statement is at :535, and on this branch it is at :543. Nothing but the anchor's number changed, and content/docs/releases/** was not touched (merge-base diff = six paths, none under it).

Verification

Two pin suites, both green as written (no assertion touched):

packages/runtime      src/domains/packages-writable-verdict.test.ts    Test Files 1 passed  Tests 12 passed
packages/metadata-protocol  src/get-meta-items-package-writable.test.ts  Test Files 1 passed  Tests  7 passed

pnpm --filter @objectstack/runtime --filter @objectstack/metadata-protocol run typecheck — both Done; runtime's test layer also compiles (check:test-typecheck: OK — 27 file(s) / 191 error(s) / 69 pinned signature(s) held, ratchet unmoved).

Gate union re-run after the final commit, at f8e396c99, each verdict quoted from the gate's own output. Both pin suites were re-run at that same head after the anchor fix and are unchanged (12 passed / 7 passed), so no red was traded for another:

  • check-nul-bytes: OK (scanned 7805 text file(s) ... no raw ASCII control bytes)
  • check-engine-double-contract: OK — 797 pinned, 133 in the DEBT ledger, 3 exempt
  • where-matcher conformance holds: 362 matcher(s) discovered, 362 answer the combinator battery correctly or refuse it loudly
  • OK check:comment-mask-adoption — 14 private comment-stripper(s) ... all 14 recorded
  • check-driver-memory-census: OK — every declaration is ledgered
  • doc authoring guard — three green lines (published skills, spec strings, sibling-package prose ids)
  • OK: 27 package(s) read outside themselves, all declared (check:cross-package-test-inputs)
  • check-dispatcher-error-vocabulary: OK — 65 unregistered code-stamping site(s), all classified
  • No empty-frontmatter changeset introduced by this diff
  • This diff introduces no major bump
  • check-adr-0087-registration: this PR adds no declared-breaking changeset
  • check-system-context-census: OK — 105 elevation read sites in 19 packages across 44 files, all anchored; 140 anchors resolve, 27 declared non-read (plus --self-test: all cases passed)
  • check-doc-anchors: 313 internal #fragment link(s) across 407 source file(s) all resolve to a real heading
  • the docs family the .mdx edit newly implicates, all green: check:docs-single-h1, check:doc-frontmatter, check:docs-section-name, check:doc-route-spelling --self-test, check:docs-redirects, check:corpus-claim-drift, check:role-word, check:docs-audit-scope, check:docs-transcript-drift, check:merge-driver — 24 gate invocations in total, every exit code captured by redirect-then-read

Lint was narrowed, and the narrowing is declared. pnpm exec eslint --no-inline-config --format json over the four changed source files: 4 files, 0 errors, 0 warnings. Three pieces of evidence that this is a measurement and not a skip: (i) the population is read from eslint's own configuration, which never enables type-aware linting for any file in this repo — no parserOptions.project, no typed @typescript-eslint rules, stated with its own positive control in eslint.config.mjs:320-335; (ii) the file count is eslint's own --format json output, not a guess; (iii) with no cross-file type information in play and no lint configuration in the diff (git diff --name-only against the merge base returns five paths, none of them an eslint or tsconfig file), this change cannot move the verdict on a file it did not touch. The whole-repo pnpm lint remains CI's run.

Authored by the domain:engine execution seat, session session_01ARYe3yQTQCUFm5qPYNgKaJ.


Generated by Claude Code

…tplace / offline import, not a multi-package artifact's module

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ARYe3yQTQCUFm5qPYNgKaJ
…r the next release notes

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ARYe3yQTQCUFm5qPYNgKaJ
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tests tooling labels Sep 6, 2026
@github-actions

github-actions Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/metadata-protocol, @objectstack/runtime, touching 8 documentable anchor(s).

16 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: node scripts/docs-audit/affected-docs.mjs --json 1f2a02ba0d68f2d93c96258c16517dca14f64358.

2 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails.

What this run could not see
  • the SDK route bridge reached 61 of 219 client-bound route-ledger rows — the other 158 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 158: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 28 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 1f2a02ba0d68f2d93c96258c16517dca14f64358packageMentionDocs.

Which tree this was computed on

This run read content/docs from 163ba578c752310b483f8443ec3bab59dfd96948 — the merge of head f8e396c99bf4a0bc7de2a2428d27b1c9b8f8130f into base 1f2a02ba0d68f2d93c96258c16517dca14f64358, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 163ba578c752310b483f8443ec3bab59dfd96948 && git checkout 163ba578c752310b483f8443ec3bab59dfd96948
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 1f2a02ba0d68f2d93c96258c16517dca14f64358 f8e396c99bf4a0bc7de2a2428d27b1c9b8f8130f && git checkout -B drift-repro 1f2a02ba0d68f2d93c96258c16517dca14f64358 && git merge --no-ff f8e396c99bf4a0bc7de2a2428d27b1c9b8f8130f

node scripts/docs-audit/affected-docs.mjs --json 1f2a02ba0d68f2d93c96258c16517dca14f64358

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 1f2a02ba0d68f2d93c96258c16517dca14f64358 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

…he elevation read moved to

Pure line rot from this PR's own comment expansion in
packages/runtime/src/domains/packages.ts: the shouldDenyAnonymous read moved
from :535 to :543, so row 52's anchor and the read no longer met. Written by
`node scripts/check-system-context-census.mjs --fix`; the anchor's number is
the only thing that changed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ARYe3yQTQCUFm5qPYNgKaJ
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

2 participants