Skip to content

spec/kernel: carve the package-registry persistence out of marketplace into an always-on core capability - #18694

Merged
os-bill merged 1 commit into
mainfrom
claude/issue-18053-package-registry-capability
Sep 17, 2026
Merged

os-bill merged 1 commit into
mainfrom
claude/issue-18053-package-registry-capability

Conversation

@os-bill

@os-bill os-bill commented Sep 17, 2026

Copy link
Copy Markdown
Collaborator

Fixes #18053
Clause-②: yes

Director ruling 5650202813 on #17676 (decision batch #125 item 2, maintainer verbatim 「同意」), item 1 — the spec-constant half. The runtime half (items 2, 3, 5) stays on #17676 with the domain:engine lane. ⛔ The ruling's direction is not re-opened here; the one judgment this card carries is the NAME, which the ruling deliberately declines to give.

What changed

Three edits in packages/spec/src/kernel/platform-capabilities.ts, plus the pins that read it.

constant before after
PLATFORM_CAPABILITY_TOKENS 28 tokens 29 — package-registry added after marketplace; none removed; the relative order of all 28 unchanged
PLATFORM_ALWAYS_ON_CAPABILITIES 10 entries 11 — package-registry appended at the tail; none removed; the relative order of all 10 unchanged
PLATFORM_CAPABILITY_PROVIDERS 1:1 with the vocabulary 1:1 still — one row added: package-registry = @objectstack/service-package, open edition

The full before/after item lists are in the report comment on #18053.

The name — and the candidates it beat

The comparison set, every token in the vocabulary today: ai, ai-studio, i18n, ui, auth, automation, analytics, audit, cache, storage, queue, job, messaging, triggers, realtime, mcp, marketplace, email, sms, sharing, pinyin-search, reports, approvals, settings, webhooks, hierarchy-security, ai-seat, governance.

Chosen: package-registry. Four reasons, in order of weight:

  1. ⭐ It is not a new word in this tree — the spec kernel already spells it. packages/spec/src/kernel/package-registry.zod.ts sits in the same directory and opens "# Package Registry Protocol — Defines the runtime state and lifecycle operations for installed packages"; it exports InstalledPackageSchema and the installed-package lifecycle types, and its own prose calls a package row "the 'row' in the installed-packages table". The token now names the capability that PERSISTS exactly the protocol that module DEFINES, at the same spelling, in the same kernel. Nothing has to be learned to read it.
  2. Same naming grammar as the set. Every token is lower-case kebab-case, and every compound one is QUALIFIER-NOUN where the noun is the thing and the qualifier narrows it: ai-studio, ai-seat, pinyin-search, hierarchy-security. package-registry is that shape exactly.
  3. It says what the thing IS — the registry of installed packages: the sys_packages container and the boot hydration that replays it — rather than what a deployment might additionally sell on top of it.
  4. It is not repo-private. "Package registry" is the industry term for this exact object; ADR-0016's own Architecture Alignment list cites "npm: Package registry with install/uninstall/version management", and @objectstack/service-package already says it in caller-visible prose ("The package registry could not store this package").

Rejected, and why:

  • marketplace — forbidden by the ruling, and the reason the ruling exists: a token advertising a store that is not there.
  • packages — grammatical (the set has plural tokens: reports, approvals, webhooks, triggers), but in this tree "package" means both an npm workspace package and a metadata package, and a requires: ['packages'] line inside a monorepo is exactly the ambiguity the vocabulary header exists to prevent. It also names the noun, not the service.
  • package-store — "store" is the connotation the ruling is REMOVING; re-importing it under a fresh spelling defeats the split.
  • package-state — collides head-on with packages/runtime/src/package-state-store.ts, which is the OTHER medium (the operator's disabled-id set under the ObjectStack home directory). Item 4 is about those two media not being confused; a token named package-state would make that confusion permanent.
  • package-persistence — names a mechanism, not a service domain. No token in the set names an implementation property; storage is the service, not "file-persistence".
  • sys-packages — the table name. A repo-private abbreviation, which the card forbids by name.
  • registry alone — this tree already has three (the npm registry, ObjectQL's SchemaRegistry, the metadata type registry).
  • package-catalog / catalog — that IS the half marketplace keeps.

Question 1 — does the split move the length, or the always-on boundary derivation?

Measured before and after, not assumed.

  • Length: +1 on both constants (28 to 29 tokens, 10 to 11 slate entries). Nothing is removed, so requires: ['marketplace'] keeps meaning today what it meant yesterday; this widens the accept set and narrows nothing.
  • The boundary derivation does NOT move. Since 68e8b4b53c the boundary is the rule "every entry that is not a bind target is mounted after ALL of them", with BIND_TARGETS = queue / job / cache / settings. package-registry is not a bind target: nothing on the slate binds into it during kernel:ready, and its one hard requirement is the ObjectQL engine, which is not a capability token. So it joins the TAIL, exactly as the declaration's own comment instructs, and the derived rule covers it on arrival with no new target and no new prefix.
  • What DID move is the one assertion that enumerates the tail literally — the falsifiability control in platform-capabilities.test.ts, which pins orderingViolations(slate + a new bind target). That assertion went red on the first edit and is updated from the rule rather than around it. That is the "ordering contract moves with it" half of the card, and it is this PR's first red (Evidence below).
  • Media: unchanged at two. See Item 4.

Question 2 — what does the CLI's serve-capability-vocabulary.test.ts actually follow?

Read, not copied from the card's sentence. That file reads the CONSTANTS (PLATFORM_CAPABILITY_TOKENS, PLATFORM_CAPABILITY_PROVIDERS, PLATFORM_PLUGIN_WIRED_RUNTIMES) together with Serve.CAPABILITY_PROVIDERS / Serve.CAPABILITY_TO_TIER / Serve.ALWAYS_ON_CAPABILITIES, and asserts set relations and package equality. It enumerates no slate ORDER anywhere.

  • So it follows by derivation, not by literal: the only case in it that a carve-out could turn red is the 1:1 pin "classifies every vocabulary token, and adds none outside it" — a token added without a provider row fails there, in both directions. With the row present the file was already green before I edited it (measured).
  • Its case "open-edition service tokens name the SAME package as serve CAPABILITY_PROVIDERS" iterates serve's keys, so it never asks the reverse question. That asymmetry is precisely what leaves the gap in the next section invisible to it.
  • What this PR adds there is the ruling stated through the array serve really appends: Serve.ALWAYS_ON_CAPABILITIES carries package-registry and does not carry marketplace. Both halves are asserted, because a one-sided pin would stay green on a slate that force-mounted the catalogue half too — the outcome the ruling refused.

⚠️ What this PR deliberately does NOT do

Measured on serve's capability resolver at c17ff70f3f: a slate entry is force-appended to every app's requires, and the CLI then mounts it only if Serve.CAPABILITY_PROVIDERS keys the token — a token with no entry that IS in the vocabulary is skipped silently, by design. That registry keys marketplace (= @objectstack/service-package / PackageServicePlugin) and does not key package-registry, and PackageServicePlugin has exactly one mount path in this repo: that row. Therefore:

This is recorded in three places a later reader will stand in: the slate declaration's own comment, the changeset, and the report comment — so the engine half is written against a stated fact rather than a discovered one.

Two related measured facts, neither changed by this PR: (a) the module header's growth instruction says to add a new token "HERE as well as to the runtime's provider registry", i.e. it expects the two in one PR; (b) the same header claims "the CLI's vocabulary-drift test fails if the registries and this list fall out of sync", which holds only in the serve-to-spec direction.

Item 4 — the ADR question (report-back; docs/adr/** is untouched)

What I read: #5047 and its verification comment 5174777602, which is where the phrase comes from. The two media are (1) the operator's disabled-id set in a flat JSON file under the ObjectStack home directory, written by packages/runtime/src/package-state-store.ts, whose header states the choice outright — "intentionally a flat file rather than a sys_* object: package lifecycle state is runtime/operational state, not project metadata"; and (2) the package rows themselves in sys_packages. ADR-0016 §9.7 records medium (1) and ADR-0025 reuses it by reference ("the ADR-0016 §9.7 package-state-store.ts pattern").

My judgment: no ADR note is owed by this diff, and one becomes worth considering only when the runtime half lands.

  • Measured: no ADR names marketplace as the owner of sys_packages; the only ADR that names sys_packages at all is ADR-0087 (a row-level upgrade diagnostic), which this diff does not touch; and no ADR names PLATFORM_ALWAYS_ON_CAPABILITIES. So no recorded decision becomes false, and nothing here reverses one — the ruling's own reading holds: this changes which capability OWNS an existing medium, not the number of media.
  • The honest caveat, which is the engine half's to carry: today medium (2) exists only on deployments that declare requires: ['marketplace'], while medium (1) is written unconditionally. Once the runtime half mounts the registry on every stock boot, the drift window ADR-0016 §9.7 knowingly accepted applies to every deployment rather than to marketplace-enabled ones only. That is a change in the trade-off's EXPOSURE, not in its SHAPE — a candidate for a one-line amendment to §9.7, filed as its own card by the seat, never assumed and never written from here.

Evidence

Red before green (the ordering contract). With only the declaration edited and no test touched: platform-capabilities.test.ts = 1 failed / 26 passed, naming "…and the rule is falsifiable — a hostile slate is named, not shrugged off", diff + "package-registry". Green after the control is updated from the rule: 32 passed.

Ablation — declaration reverted to the merge base, with an on-disk proof and a hash-verified restore.

leg on-disk built artifact spec suite CLI pin
mutate source occurrences 5 to 0 rebuild exit 0; ablation-dist-preflight @objectstack/spec reports the marker absent from all 216 built files 5 failed / 27 passed 1 failed / 13 passed — expected [ 'queue', 'job', 'cache', …(7) ] to include 'package-registry'
restore git hash-object = b6c0292c5d2b5f0c012f12b8cacbdf97aed38ac7 = the HEAD blob rebuild exit 0; preflight reports the marker present in 8 built files exit 0 exit 0

Whole-tree git status --porcelain after the restore: empty. The restore leg is pinned to HEAD (never a bare git checkout --, never a moving ref) and runs from a trap on EXIT INT TERM. The CLI leg is measured against the rebuilt dist/, because packages/cli carries no vitest alias for @objectstack/spec and resolves it through exports.

Controls. LIT: the token reads 5 occurrences in the declaration and the preflight finds it in 8 built files — the instrument fires. DARK, each of which must read 0 and does: docs/adr/** paths in the diff = 0; packages/cli/src/** paths in the diff = 0; 'marketplace' inside PLATFORM_ALWAYS_ON_CAPABILITIES = 0. The CLI's serve-defaults.test.ts control runs over a FIXED synthetic slate rather than the live one, so it neither moved nor needed to.

Suites and gates, all green at 37a112e0f2: @objectstack/spec build; @objectstack/spec full suite 485 files / 13873 tests; @objectstack/spec typecheck; the four CLI capability files (serve-capability-vocabulary, serve-defaults, capability-preflight, serve-settings-ordering.pin) 44 tests; and check:nul-bytes, check-empty-changeset, check-changeset-no-major, spec check:api-surface / check:export-origins / check:authorable-surface, check-spec-docblock-symbol-anchors, check:test-source-alias, check:cross-package-test-inputs, check:published-files. The full derived family list for this diff is 82 commands; the narrowing to the set above is declared in the report comment, and CI runs the farm.


Generated by Claude Code

…e` into an always-on core capability

`sys_packages` and the boot hydration that replays it are the persistence half
of what the `marketplace` token named; a package is a first-class persistent
entity whether or not the deployment has a store. Give that half its own
vocabulary token, its own open-edition provider row, and a place on the
always-on slate; leave `marketplace` naming only the optional catalogue /
browsing half.

The slate's ordering contract moves with it: `package-registry` binds into
nothing on the slate, so it joins the tail, and the falsifiability control that
enumerates the tail literally is updated from the same rule rather than around
it.

Claude-Session: https://claude.ai/code/session_01JbZnqu8bt6YqfJsr9vaFb3

Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec, touching 3 documentable anchor(s).

1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/permissions/capabilities.mdx (via PLATFORM_CAPABILITY_TOKENS (symbol, a top-level const object))

⛔ 1 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v16.mdx (via PLATFORM_CAPABILITY_PROVIDERS (symbol, a top-level const object), PLATFORM_CAPABILITY_TOKENS (symbol, a top-level const object))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 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 — 136 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 1bc22b3dcddc8a30b4826da8625e7787d5518a8f → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 55a4cba1d045fef9c4b609e9fbf0d53ade48e224 — the merge of head 37a112e0f2a32aed323add69f32dda16b55b1899 into base 1bc22b3dcddc8a30b4826da8625e7787d5518a8f, 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 55a4cba1d045fef9c4b609e9fbf0d53ade48e224 && git checkout 55a4cba1d045fef9c4b609e9fbf0d53ade48e224
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 1bc22b3dcddc8a30b4826da8625e7787d5518a8f 37a112e0f2a32aed323add69f32dda16b55b1899 && git checkout -B drift-repro 1bc22b3dcddc8a30b4826da8625e7787d5518a8f && git merge --no-ff 37a112e0f2a32aed323add69f32dda16b55b1899

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

⚠️ 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 1bc22b3dcddc8a30b4826da8625e7787d5518a8f → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tests tooling labels Sep 17, 2026

os-bill commented Sep 17, 2026

Copy link
Copy Markdown
Collaborator Author

Contract review

Served-tier: 92/92 CONTRACT_REVIEW_TIER
Head-sha: 37a112e0f2a32aed323add69f32dda16b55b1899

Isolated reviewer. Inputs: card #18053 body, ruling comment 5650202813 on #17676, PR #18694 body / diff / commit / check runs, and the tree at the head above (detached worktree; @objectstack/spec rebuilt there; the CLI dependency graph restored from turbo cache). No dispatch order and no seat comment on the card or the PR was read. Merge-base measured: c17ff70f3f.

① Derived judgments

Diff = 4 files (git diff --name-only c17ff70f3f..HEAD): the declaration, its spec pin, the CLI vocabulary pin, one changeset. docs/adr/** paths in the diff = 0 and packages/cli/src/** paths = 0, beside the 4 named files as the firing control.

Row 1 — PLATFORM_CAPABILITY_TOKENS gains package-registry (28 to 29, inserted after marketplace, nothing removed). RIGHT. Counted on both trees (28 at c17ff70f3f, 29 at head). The name, judged on my own reading rather than the PR's:

  • Grammar: the file's own pin is /^[a-z0-9]+(-[a-z0-9]+)*$/, and every compound token in the set is qualifier-noun (ai-studio, ai-seat, pinyin-search, hierarchy-security); package-registry is that shape.
  • What it is: sys_packages plus the boot replay is the registry of installed packages, and the same kernel directory already spells it — packages/spec/src/kernel/package-registry.zod.ts ("Package Registry Protocol — runtime state and lifecycle operations for installed packages"; InstalledPackageSchema = "the 'row' in the installed packages table"). @objectstack/service-package says "The package registry could not store this package" / "could not be read" (src/index.ts:44, :297), and the spec docs already publish a page named package-registry under "Packages" (scripts/build-docs.ts:591).
  • Not marketplace: the ruling's one prohibition holds, and marketplace stays in the vocabulary — a split, not a rename.
  • Collision probes: identifier PackageRegistry in non-test source = 0 (control SchemaRegistry = 271); the string package-registry outside the diff names only that kernel module, its docs page and one migration-entry phrase.
  • Weakness I record against the name, which the PR body does not: "registry" is two-sided in this tree. packages/spec/src/system/registry-config.zod.ts is the REMOTE "ObjectStack Registry Service" (upstream URL, federation, sync); marketplace/package.zod.ts:34 glosses marketplace as "public registry"; migration entry 18 calls that remote config's durations "package-registry durations". So the store connotation the ruling wanted out of the token is reachable through this spelling too. It is bounded — the token sits beside kernel/package-registry.zod.ts by directory and spelling, and the declaration comment names sys_packages — so I judge the name acceptable and the best of the set, but "nothing has to be learned to read it" overstates.
  • Rejected candidates, re-derived: marketplace forbidden. packages — the workspace-package / metadata-package ambiguity holds (the "names the noun, not the service" argument is weak: reports, approvals, webhooks name nouns too). package-store — reimports the store sense. package-state — packages/runtime/src/package-state-store.ts exists at head and IS the other medium ("intentionally a flat file rather than a sys_* object"); the strongest rejection. package-persistence — the weakest rejection (storage, cache, queue are mechanisms too), but the chosen name is still better. sys-packages — a mangled table name, rightly rejected, but NOT "forbidden by the card by name": the card body forbids only marketplace and a mechanical rename; that attribution is wrong. registry alone and package-catalog / catalog rightly rejected. One more misattribution: the "npm: Package registry with install/uninstall/version management" line lives in package-registry.zod.ts's own header (line 30), not in ADR-0016 (grep npm docs/adr/0016-* = 0). The point stands; the citation does not.

Row 2 — PLATFORM_CAPABILITY_PROVIDERS['package-registry'] = @objectstack/service-package, open. RIGHT, and necessary: the slate pins "every member has a declared provider" and "open-edition only", the spec 1:1 pin (platform-capabilities.test.ts:57) and the CLI 1:1 pin (serve-capability-vocabulary.test.ts:80) all need the row. @objectstack/service-package exports exactly one plugin class, PackageServicePlugin (src/index.ts:349), which creates sys_packages, replays it at start() and serves publish / get / list / delete; its only hard requirement is the objectql service (index.ts:361) and it declares no plugin dependencies, so the "binds into nothing on the slate" claim holds. @objectstack/cli depends on the package (packages/cli/package.json:99), so classifyRequiredCapability reads ok under serve. The row sharing marketplace's package is inherited, not decided: Serve.CAPABILITY_PROVIDERS.marketplace = @objectstack/service-package / PackageServicePlugin (serve.ts:1958), and the CLI drift pin (serve-capability-vocabulary.test.ts:90) holds spec's marketplace.package equal to serve's, so the spec row for marketplace cannot be repointed without packages/cli/src. See ③.

Row 3 — PLATFORM_ALWAYS_ON_CAPABILITIES gains package-registry at the tail (10 to 11). RIGHT per the ruling ("always-on core capability") and per the ordering rule: not a bind target, so it joins after queue / job / cache / settings; the derived pins (platform-capabilities.test.ts:236; CLI serve-settings-ordering.pin.test.ts case 5) cover it without edit, and the one literal control (platform-capabilities.test.ts:272) is updated from the rule. marketplace inside the slate literal = 0 (control analytics = 1): the "browsing stays optional" half.

Row 4 — spec pin: literal control updated; new describe with 5 cases asserting both halves. RIGHT. The near-miss case is an absence assertion (passes for any rewrite) but is supplementary, not load-bearing.

Row 5 — CLI pin: new surface case on Serve.ALWAYS_ON_CAPABILITIES (contains package-registry, not marketplace; both tokens in the vocabulary). RIGHT, and deliberately NOT pinning the absence of a serve row is the right call — that pin would turn the engine half red for doing the ruled thing.

Public surface touched without an edit (measured):

  • The declaration header, lines 20–23, says a new token is added "HERE as well as to the runtime's provider registry — the CLI's vocabulary-drift test fails if the registries and this list fall out of sync". On this head the two ARE out of sync in the spec-to-serve direction and every pin is green (44/44): the drift pin iterates serve's keys only (serve-capability-vocabulary.test.ts:14, :90). The PR body states this and does not amend the sentence. Minor; owed with the engine half or as a wording fix.
  • Published skill skills/objectstack-platform/SKILL.md §"requires: — which service plugins boot" (lines 435–465) states "The other eight tokens in the vocabulary are not in that map" and "The authoritative list of all 28 is PLATFORM_CAPABILITY_TOKENS". On this head both numerals are false (nine, 29), and the new token belongs to a third category the section has no sentence for (open-edition, on the always-on slate, not yet in serve's map). No gate reads those numerals (check:skill-docs regenerates frontmatter listings; check:skill-refs writes _index.md): measured ungated. A published surface left stale by a diff to the constant it describes; disposition under ③.
  • api-surface / export-origins: no new export symbol; pnpm --filter @objectstack/spec check:generated at head = 15/15 artifacts up to date. requires is z.array(z.string()), so there is no schema enum to regenerate.

Measurements at head (mine, not the PR's): spec pins platform-capabilities, stack-requires, stack-refusal-envelopes, package-registry = 4 files / 115 passed; CLI serve-capability-vocabulary, serve-defaults, capability-preflight, serve-settings-ordering.pin = 4 files / 44 passed; tsc --noEmit in packages/spec exit 0. Ablation with the declaration reverted to c17ff70f3f (trap-guarded; restore verified by git hash-object = HEAD blob b6c0292c5d2b5f0c012f12b8cacbdf97aed38ac7; git status --porcelain empty afterwards): source occurrences 5 to 0; spec pin 5 failed (the falsifiability control plus four carve-out cases); dist rebuilt from the mutated source, CLI pin 1 failed / 13 passed on "expected [ 'queue', 'job', 'cache', … ] to include 'package-registry'"; after restore 32/32 and 14/14. Quoted-token files in dist at head = 8. NOT MEASURED: the quoted-token count in the mutated dist (my bare-marker probe is polluted by the module filename — 26 at head vs 22 mutated is a delta, not a zero); the CLI pin going red on that dist is the behavioural reading I stand on.

② Semver level

Changeset .changeset/18053-package-registry-capability.md declares "@objectstack/spec": minor. CONSISTENT. The diff adds one vocabulary token, one provider row and one slate entry and removes or renames nothing: an additive widening of a closed accept set, which is minor by this repo's rule (Clause-②: yes takes at least minor; the PR body carries Clause-②: yes on its line 2). patch would be refused by the level axis; major is both unwarranted (no narrowing) and refused by the launch-window guard. Re-run offline against the real PR body: node scripts/check-changeset-no-major.mjs --base c17ff70f3f --head HEAD --event event.json = exit 0, "introduces no major bump", "LEVEL AXIS: declares clause-② yes, and no package whose src it moves is graded patch"; CI Check Changeset success on both runs. The fixed group bumps the stack together, so grading only @objectstack/spec is right (the CLI change is test-only). Body: the ⚠️ bullet correctly says the token mounts nothing under the standalone CLI until the engine half lands. Wording caution, not a level error: the headline sentence ("and an always-on one: … no longer hide behind the marketplace token") is true only after that half; if a release compiles this changeset first, the CHANGELOG's first sentence over-claims for the open edition and the bullet is what keeps it honest.

③ Boundary flags

PR question 1 — does the split move the length or the boundary derivation? Answered and verified: +1 on each constant; BIND_TARGETS unchanged; tail derived; only the literal falsifiability control moved.

PR question 2 — what does the CLI vocabulary pin actually follow? Verified: set relations and package equality in the serve-to-spec direction; no order enumerated; the only case a carve-out could redden is the 1:1 pin.

Declared gap — no Serve.CAPABILITY_PROVIDERS row; package-registry is inert under objectstack serve. Verified on serve.ts at head: the slate is force-appended to every app's requires (2785–2790); the mount loop does continue silently for a KNOWN token with no provider (4560–4576) and warns only for unknown ones; serve keys marketplace (1958); package-registry in packages/cli/src = 0 (control marketplace = 89). Is shipping the declaration without the row sound? YES, and the order is forced rather than chosen: the drift pin "every CAPABILITY_PROVIDERS token is in PLATFORM_CAPABILITY_TOKENS" (serve-capability-vocabulary.test.ts:14) goes red if serve keys the token before spec carries it, so spec lands first or both land in one cross-lane PR, which the ruling's state line forbids. Consequences on this head, stated so nobody reads this PR as closing #17676: (a) a stock serve boot is exactly as capable as before — sys_packages still exists only under requires: ['marketplace'], so ruling items 2 and 5 are not yet true; (b) an author may now write requires: ['package-registry'], defineStack accepts it, the preflight classifies it ok, and serve mounts nothing for it — a declared-not-delivered window that closes only with the engine half; (c) the header sentence at lines 20–23 is false in one direction. The runtime half is tracked where the ruling put it: #17676 carries pm:blocked and domain:engine with items 2/3/5. I cannot search for a separate card and assert nothing about one either way.

Escalated — the marketplace provider row still names the persistence package. Ruling item 1 says marketplace "keeps only the catalogue / browsing half"; on this head PLATFORM_CAPABILITY_PROVIDERS.marketplace still names @objectstack/service-package, and neither this PR nor #17676 items 2/3/5 as written owns repointing it. This PR cannot do it alone (the drift pin at :90 couples the spec row to serve's row in packages/cli/src), so deferring is correct — but the deferral must land on a named owner or the clause is executed nowhere. Design note for that owner: the browse surface (MarketplaceProxyPlugin / MarketplaceInstallLocalPlugin in @objectstack/cloud-connection) is mounted off resolveCloudUrl() (serve.ts:3701–3720), never through the token, so what requires: ['marketplace'] should resolve to after the split is itself open (possibly nothing).

Ruling item 4 — is an ADR note owed? I agree with the PR: not by this diff. Measured: ADRs naming PLATFORM_ALWAYS_ON_CAPABILITIES = 0 (control: sys_packages fires on ADR-0087 line 330, where it is a row-level upgrade diagnostic); no ADR defines requires: ['marketplace'] semantics; ADR-0016 §9.7 records the flat-file medium, and the ruling's "which capability owns an existing medium, not the number of media" holds. The exposure change to §9.7's accepted drift window (every deployment, once the registry mounts always) belongs to the engine half, as the PR says.

Cloud per-tenant runtime — NOT MEASURED (out of tree). If its loader keys the spec slate through the spec provider rows, every tenant mounts @objectstack/service-package on this head; if it keys its own registry, the entry is inert there as under serve. The changeset's ⚠️ covers only the standalone CLI.

Published skill staleness (from ①). Disposition: fix the two numerals and add one sentence for the new token in skills/objectstack-platform/SKILL.md §"requires:" — in this PR before enqueue (three lines), or on a named card with the engine half, which must touch the same table anyway (serve's map grows to 21 entries). The verdict does not hinge on it: the section defers authority to the constant, and no operator instruction in it becomes false — the counts are descriptive.

CI at this head: 39 check runs, all completed; every non-skipped conclusion is success (six Test Core shards, four Type Check jobs, Lint & Repo Gates, Check Changeset on both runs, Governed Surface Queue Guard, Temporal Conformance, five Dogfood jobs); the skipped ones are path-filtered (Packed-tarball smoke, Build Docs, Console Pin Gate, one Auto Label, one Check PR Size). No red.

Implemented-by: claude/issue-18053-package-registry-capability
Reviewed-by: session_01JbZnqu8bt6YqfJsr9vaFb3

VERDICT: PASS


Generated by Claude Code

os-bill commented Sep 17, 2026

Copy link
Copy Markdown
Collaborator Author

契约复核 provenance —— PASS 已逐字采纳,双载体同笔已剥。 domain:spec seat 2(session_01JbZnqu8bt6YqfJsr9vaFb3,座位贴 #18549)。⏱️ 本条每一段的读数都取自同一动作:2026-09-17T15:49Z;所判 head 37a112e0f2a32aed323add69f32dda16b55b1899。

记录与采纳

  • 复核记录:本 PR 评论 5717148298,形态齐备 —— ## Contract review 题头、Served-tier: 92/92 CONTRACT_REVIEW_TIER(值是常量名,前置的是盖章对照)、Head-sha: 独占码段、①②③ 逐项、Implemented-by: / Reviewed-by: 独立性对、**VERDICT: PASS**。
  • 机读复验(本席自己跑):check-clause2-carriers --pair 18694 exit 0,C6-RECORD 点名该评论在本 head 上、C7 读在档。⚠️ 该检查器自己说明:它判的是记录存在,「whether it reads PASS is precondition ① of the landing check and stays human」⇒ 读 PASS 这一步是本席做的,不是机器代的。
  • 档位证据:取子代理自身 transcript 的逐请求 harness model,对实时 import 的 CONTRACT_REVIEW_TIER 比对,92 / 92;⛔ get_session 未使用(它在 mode:subagent 里量的是派发会话)。
  • 独立性:复核只拿到卡片、裁决评论 5650202813 与 PR 本体;⛔ 没拿到派发令、⛔ 没拿到本席的结论;席位评论按简报被当作非证据。
  • 采纳方式:逐字。⛔ 未改写、未润色、未删减 —— 包括它指出本 PR 正文两处误归属(「npm: Package registry」出自 package-registry.zod.ts 而非 ADR-0016;sys-packages 并非卡面按名禁止的)那两条。

⇒ needs:contract-review 同笔从卡 #18053 与本 PR 两侧剥除。

本席对它三条 boundary 输出的处置

① flag:发布的 skills/objectstack-platform/SKILL.md 里「all 28」「other eight」在本 head 上都成了假。 本席复测属实::463 逐字「The authoritative list of all 28 is PLATFORM_CAPABILITY_TOKENS」、:452「The other eight tokens」,而本 PR 把词汇表带到 29。

⇒ 不在本 PR 改,另立卡 #18705。 判据不是省事,是路线代价:那是手写的 skills/** 散文,⛔ 不是生成物,所以 #11705 生成物例外抬不起它 —— 一旦进 diff,本 PR 就从「普通队列落地」变成受管四件套留 draft 等人批。而复核自己测到该节把权威交给了常量(:463 那句),⇒ 变假的是计数,⛔ 不是操作指令。两害相权,落地不该被一个陈旧计数卡住;而且它无门禁,下一次加 token 还会再假一次 —— 那才是卡要治的东西。

② escalated:marketplace 的 provider 行仍指向 @objectstack/service-package,而「marketplace 只留目录那一半」这句裁决目前没有任何一方拥有**。** 受理为真缺口,一并写进 #18705 的背景;⛔ 本席不代 domain:engine 车道认领,那是 #17676 items 2/3/5 的面。

③ 它与 dev 对 item 4(ADR)的判断一致:本 diff 不欠 ADR 注记(0 条 ADR 点名该 slate 常量;ADR-0087 对 sys_packages 的提及是行级升级诊断)。⇒ 受理。

NOT MEASURED,原样留着

复核自标:云侧的 per-tenant 运行时行为(树外);被污染的那次 bare-marker dist 探针(按 delta 报);以及「是否已有引擎那半的卡」—— 代理够不到 /search/*,所以它两个方向都没断言。⭐ 最后一条按规矩是采纳席来闭合的,本席已查:#17676 自身带 pm:blocked + domain:engine,items 2/3/5 就在它名下。


Generated by Claude Code

@os-bill
os-bill marked this pull request as ready for review September 17, 2026 15:50
@os-bill
os-bill enabled auto-merge September 17, 2026 15:50
@os-bill
os-bill added this pull request to the merge queue Sep 17, 2026
Merged via the queue into main with commit 51297e9 Sep 17, 2026
44 checks passed
@os-bill
os-bill deleted the claude/issue-18053-package-registry-capability branch September 17, 2026 16:10
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 28, 2026
…its count in objectstack-platform (objectstack-ai#18839)

Fixes objectstack-ai#18705
Clause-②: no

The `requires:` section of the published
`skills/objectstack-platform/SKILL.md` copied two numbers out of the
platform capability vocabulary into prose — 「The other eight tokens in
the vocabulary …」 and 「The authoritative list of all 28 is
`PLATFORM_CAPABILITY_TOKENS`」 — and no gate reads either number against
`packages/spec/src/kernel/platform-capabilities.ts`. PR objectstack-ai#18694 (objectstack-ai#18053)
took the vocabulary 28 → 29 when it landed at 2026-09-17T16:10Z, so on
`origin/main` both sentences were already false when this branch was
cut. This PR is the card's option A in the shape of the objectstack-ai#16853 precedent
(`a256f18962`, PR objectstack-ai#17259 「cite the platform-tool registry instead of
restating it」): the two counts become citations, and nothing is left in
those two sentences that a registry change can falsify. No other
sentence in the file moves. The `package-registry` runtime half is
objectstack-ai#17676's and is not addressed here; objectstack-ai#16767 (the example block's
TypeScript range, same file) remains open and is not folded.

## 维护者速读(草稿)

**改了什么** — 发布给客户项目的 `skills/objectstack-platform/SKILL.md` 里,`requires:`
一节原来写死了两个数字(「the other eight tokens」「the authoritative list of all
28」)。本 PR 把这两句改成引用常量 `PLATFORM_CAPABILITY_TOKENS`,不再写数字;其余 1221
行一字未动(diff 是 3 行增 / 3 行删)。

**为什么改** — 这两个数字是从 `packages/spec` 的能力词汇表抄来的,没有任何门禁核对它们。objectstack-ai#18694 落地后词汇表已是
29 个 token(新增 `package-registry`),这两句在 `main` 上已经是假话,而客户项目里的 AI 每次加载这份
skill 都会读到。同一类缺陷在 `skills/objectstack-ai/SKILL.md` 上已由 objectstack-ai#16853 / PR
objectstack-ai#17259 用同样的办法修过一次:引用注册表,不复述它。

**风险与代价(含回滚)** — 纯文档改动,不发布代码,不改任何运行时行为;skill 的 token 计数 12980 → 12975,上限
12984 不动。回滚就是 revert 这一个提交。留下一处未动:同一节 :440 的「all 20 of its
entries」仍是一个抄写的计数(今天为真,objectstack-ai#17676 引擎侧落地时会变假),按派发令本卡不动其它句子——要不要顺手折进来由席位定,见下方
Acceptance notes。

**席位意见** —

**你要做的** — 这是受管面(`skills/**`),按 Prime Directive objectstack-ai#14 需要你亲自确认并合并;PR 保持
draft。读一眼 diff 里那两句(6 行),没有别的。

## Re-derived readings

All on `origin/main` `5941246b70` (the branch point); none copied from
the card.

| reading | value | how |
|:--|:--|:--|
| `PLATFORM_CAPABILITY_TOKENS` size | **29** | parsed the frozen array
literal in `packages/spec/src/kernel/platform-capabilities.ts`;
`PLATFORM_CAPABILITY_PROVIDERS` has the same 29 keys |
| `Serve.CAPABILITY_PROVIDERS` size (the map the section describes) |
**20** | top-level keys of the static at
`packages/cli/src/commands/serve.ts` :1863–:2009; the skill's :442 table
names the same 20 as a set |
| vocabulary tokens NOT in that map | **9** | `ai` `ai-studio` `i18n`
`ui` `auth` `hierarchy-security` `ai-seat` `governance` — the eight the
section lists — plus `package-registry` |
| 「all 28」 on the tip | **false** (29) | |
| 「the other eight」 on the tip | **false** (nine) | |
| 「all 20 of its entries」 at :440 (same section; not named by the card)
| **true** today (20 = 20) | |

**Gate reading — which gate surfaces read the constant name — with
controls (`git grep -l` at `5941246b70`):**

- `scripts/` · `packages/*/scripts/` · `.github/` for
`PLATFORM_CAPABILITY_TOKENS`: **0 files** (exit 1)
- lit control, the same grep across the whole tree: **18 files** (exit
0; the card read 17 before objectstack-ai#18694 landed —
`.changeset/18053-package-registry-capability.md` and
`content/docs/releases/v16.mdx` are the new hits)
- dark control, a forged constant name across the whole tree: **0
files** (exit 1)

## The two sentences, before / after

:452–:453 before:

```
The other eight tokens in the vocabulary are **not** in that map and do not
resolve through it:
```

after:

```
The tokens in `PLATFORM_CAPABILITY_TOKENS` not in that map do not resolve
through it:
```

:463 before:

```
The authoritative list of all 28 is `PLATFORM_CAPABILITY_TOKENS`
```

after:

```
The authoritative list is `PLATFORM_CAPABILITY_TOKENS`
```

The diff is 3 insertions / 3 deletions in one file.

## The enumeration under :452, re-read against the constant at the tip

Every name the two bullets carry — tier-gated `ai`, `ai-studio`, `i18n`,
`ui`, `auth` (= the five keys of `Serve.CAPABILITY_TO_TIER` at
:1577–:1585, verbatim); enterprise / cloud `hierarchy-security`,
`ai-seat`, `governance` — is in `PLATFORM_CAPABILITY_TOKENS` at the tip,
and none of the eight is a `Serve.CAPABILITY_PROVIDERS` key. So the list
is kept as the list it is: no name dropped, no name added. The ninth
not-in-map token, `package-registry`, is in the vocabulary and in
`PLATFORM_CAPABILITY_PROVIDERS` (`@objectstack/service-package`, open
edition) but has no `Serve.CAPABILITY_PROVIDERS` row yet — the
constant's own comment says it is inert under `objectstack serve` until
objectstack-ai#17676's runtime half lands. Per the card this PR documents no new
token; the Acceptance notes say who holds that.

The `marketplace` provider line (:444, `@objectstack/service-package`)
was read against `Serve.CAPABILITY_PROVIDERS.marketplace.pkg` at :1958
(`@objectstack/service-package`): it matches the tip and is not touched
— repointing it is objectstack-ai#17676's engine lane.

## Token ratchet

`node scripts/check-skills-token-ratchet.mjs`, convention ceil(utf8
bytes / 4):

| | bytes | tokens | ceiling | headroom | exit |
|:--|--:|--:|--:|--:|--:|
| before (`5941246b70`) | 51918 | 12980 | 12984 | 4 | 0 |
| after (`bef0dc6dfa`) | 51898 | 12975 | 12984 | 9 | 0 |

No ceiling moves. The published-skill readings the os-dev definition
asks for: this file 1223 → 1223 lines and 12980 → 12975 tokens; the
package (all ten `skills/*/SKILL.md`) 6145 → 6145 lines and 70695 →
70690 tokens; the whole priced bundle 139987 → 139982 tokens. Lowering
this file's ceiling to 12975 is the script's stated option, but it edits
`scripts/check-skills-token-ratchet.mjs`, outside this card's one-file
surface — left to the seat.

## Gates

Derived with `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` from the worktree (no hand-fed paths; change
set read from the merge base `5941246b70`); every exit code captured
redirect-then-`$?`; all runs at `bef0dc6dfa`.

23 derived commands, every one exit 0:

`node scripts/check-ci-filter-parity.mjs` · `node
scripts/check-closing-keyword-parity.mjs` · `node
scripts/check-closing-keyword-parity.mjs --self-test` · `node
scripts/check-comment-mask-corpus.mjs` · `node
scripts/check-doc-route-spelling.mjs --advisory` · `node
scripts/check-doc-route-spelling.mjs --self-test` · `node
scripts/check-skills-token-ratchet.mjs` · `node
scripts/check-skills-token-ratchet.mjs --self-test` · `pnpm --filter
@objectstack/spec run check:skill-docs` · `pnpm
check:agent-test-spelling` · `pnpm check:corpus-claim-drift` · `pnpm
check:cross-package-test-inputs` · `pnpm check:doc-authoring` · `pnpm
check:driver-memory-census` · `pnpm check:nul-bytes` · `pnpm
check:pm-governed-merges` · `pnpm check:refd-timer-probe` · `pnpm
check:role-word` · `pnpm check:skill-compatibility` · `pnpm
check:skill-frame-sync` · `pnpm check:skill-identifier-liveness` · `pnpm
check:watch-hint-literal` · `pnpm --filter @objectstack/lint run
check:doc-formula-expressions`

- `check:doc-formula-expressions` answered exit **3** (PREREQUISITE NOT
MET) on its first run, before any build; then `pnpm
--workspace-concurrency=2 --filter '@objectstack/lint...' build` under
`scripts/pm/os-verify-lock.sh` (VERDICT command-exit 0, held 169s,
waited 0s); the second run answered exit **0** — the 0 above is the
second run.
- Reconciliation: `node scripts/pm/dispatch-gates.mjs --ran ran.list` →
`✓ dispatch-gates --ran: 23 derived famil(ies) accounted for — 23 run, 0
NOT-MEASURED (a DERIVED zero — all 23 recorded an exit code and none of
them is 3)`, exit 0.
- Named by the dispatch and run in addition (not in the derivation):
`pnpm check:pm-skill-id-lint` exit 0.
- Repo-wide `pnpm lint` (`eslint . --no-inline-config`) under the verify
lock: VERDICT command-exit 0 (held 88s, waited 65s — shared-box
seconds), at `bef0dc6dfa`.
- Control-byte self-scan of the edited file (`grep -naP` over the C0/DEL
range): 0 hits.

Not owed locally, because the diff touches no package: no
dependency-closure build, no package `test` / `typecheck`. What stays
CI's, as the derivation's stderr names it: 51 artifact-roster families,
11 wide-population families, 14 pending-changeset families, and the
path-scheduled `Test Core` job.

## Acceptance notes

- noted, not filed — :440 「all 20 of its entries」 is a third copied
count in the same section, true on the tip (map = 20, table = the same
20 names) and the same class as this card; it turns false the moment
objectstack-ai#17676's runtime half keys `package-registry` in
`Serve.CAPABILITY_PROVIDERS`. Kept out by the dispatch's 「no other
sentence moves」. 承接者: the seat (a patch round on this PR — 「— all 20 of
its entries:」 → 「— its entries:」 — pays for itself in tokens) or
objectstack-ai#17676's runtime-half PR, which must edit the :442 table anyway.
- noted, not filed — `package-registry` (in the vocabulary since objectstack-ai#18694,
no CLI provider row yet) is not documented in this section, so the
enumeration of not-in-map tokens is one name short on the tip.
Incompleteness, not a wrong instruction: an author never writes the
token, it is always-on. It closes by itself when objectstack-ai#17676's runtime half
adds the row (the enumeration is complete again and the :442 table gains
a line). 承接者: objectstack-ai#17676 (open, `domain:engine`); the card's 「objectstack-ai#18694 /
objectstack-ai#18053's own text」 has no live holder — objectstack-ai#18053 is done and objectstack-ai#18694 landed
without touching the skill.
- option B, one sentence for the seat, no code: the gate that already
walks `skills/**` for exactly this shape is
`scripts/check-skill-identifier-liveness.mjs` Leg 2 (its header: 「the
missing row — a doc enumeration presented as exhaustive stopped growing
when the schema did」); registering this section's enumeration (the :442
table plus the two bullets) against `PLATFORM_CAPABILITY_TOKENS` there
would flag `package-registry` today, while a prose COUNT such as 「all
20」 is outside both of its legs (existence, not arithmetic) and
`check-skills-token-ratchet.mjs` prices bytes, not claims.
- objectstack-ai#16767 remains open — the example block's TypeScript range in this
file is a different number and is not folded.

---
_Generated by [Claude
Code](https://claude.ai/code/session_01Gqi43smmqjJ5sUrhfoPeKu)_

Co-authored-by: Claude <noreply@anthropic.com>
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