Skip to content

feat(spec): PLATFORM_PLUGIN_WIRED_RUNTIMES — provenance roster for plugins[]-wired out-of-repo runtimes - #11613

Merged
os-steve merged 1 commit into
mainfrom
claude/issue-11263-organizations-capability-roster
Aug 24, 2026
Merged

feat(spec): PLATFORM_PLUGIN_WIRED_RUNTIMES — provenance roster for plugins[]-wired out-of-repo runtimes#11613
os-steve merged 1 commit into
mainfrom
claude/issue-11263-organizations-capability-roster

Conversation

@os-steve

Copy link
Copy Markdown
Collaborator

Fixes #11263

Measurement map — why the triage gloss ("add the row") is not directly executable

Re-measured on 6d3550961f (this branch's base), confirming the card's own structural objection:

  • PLATFORM_CAPABILITY_PROVIDERS (packages/spec/src/kernel/platform-capabilities.ts) is keyed by requires capability token, and its own docblock defines the vocabulary as "the union of every token the framework CLI and cloud's objectos-runtime resolve".
  • @objectstack/organizations is resolved by neither through requires. serve.ts loads it off the resolved tenancy posture (multiTenant = tenancyPosture !== 'single', ADR-0105 D1, serve.ts around line 2777), via importFromHost from the host app (cloud#1013). It backs no token; defineStack today rejects requires: ['organizations'] as an unknown token — correctly.
  • @objectstack/security-enterprise is also plugins[]-wired and has a row only because it additionally backs the hierarchy-security token.
  • Consequence: minting an organizations token to make a row keyable would enter the requires vocabulary (the drift test enforces 1:1), flipping requires: ['organizations'] from authoring-time rejection to accepted-but-resolved-by-nothing — new capability-token semantics, which the dispatch marks as maintainer territory. Widening the map's key space past the vocabulary breaks the same 1:1 invariant and would make classifyRequiredCapability classify a non-token as a provider hit. Both rejected by measurement.

Chosen shape — the minimal by-construction alternative the card itself anticipated: a sibling, package-name-keyed provenance export in the same file, covering the plugins[]-wired out-of-repo population, which is exactly two.

Changes

  • packages/spec/src/kernel/platform-capabilities.ts — new exported PLATFORM_PLUGIN_WIRED_RUNTIMES (Readonly Record, frozen; key = npm package name) with row type PlatformPluginWiredRuntime (edition: Exclude of CapabilityEdition minus 'open' — the mis-filing cannot compile; note required). Rows: @objectstack/organizations (enterprise, posture-load provenance in prose) and @objectstack/security-enterprise (enterprise, names its hierarchy-security dual listing). The token-keyed map's docblock now points at the sibling for the population it structurally cannot hold. Provenance only: no new token, no widened key space, no posture-to-token resolution semantics — each runtime's load condition stays in the runtime that loads it.
  • packages/spec/src/index.ts — root-entry named exports for both symbols (kernel subpath re-exports wholesale).
  • packages/cli/test/serve-capability-vocabulary.test.ts — the drift test the card names, extended with the cross-roster pins: (1) the organizations row exists and is enterprise; (2) every enterprise-edition provider package must have a roster row — derived from CapabilityEdition's own definition (enterprise = the app installs and wires via plugins[]), so security-enterprise's double listing is checked rather than divergent, and the next enterprise token is covered on arrival; (3) wherever both rosters name one package they agree on edition; (4) no roster package appears in serve's CAPABILITY_PROVIDERS package values.
  • packages/spec/src/kernel/platform-capabilities.test.ts — shape invariants: frozen/non-empty, keys are @objectstack/ package names, notes non-empty, runtime edition check, and keys disjoint from the capability vocabulary (the single-list rule's load-bearing half: this roster can never grow a second way to spell a requires token).
  • packages/spec/api-surface/ + export-origins/ — regenerated via check:generated --fix (only the 2 proved-stale artifacts); diff is exactly the two new symbols, additive.
  • .changeset/plugin-wired-runtime-roster.md@objectstack/spec: minor (new exported symbols; not breaking, no ADR-0087 marker owed).

On the single-list warning the file carries: the two rosters have disjoint key spaces (tokens vs package names) and disjoint membership criteria (requires-resolved vs plugins[]-wired); the one fact they can state twice (edition of a package in both) is drift-tested to agree. Residual not covered by construction: serve.ts's own organizationsPkg literal is not read from this roster (serve consumption was outside the claimed file surface); if the PM wants the runtime to consume the roster's key, that is a one-line follow-up.

Verification — all at final commit e239aa5fe4 (tree clean; no commits after any run)

  • spec build → ✓ packages/spec/dist/.build-input-hash written, exit 0.
  • pnpm --filter @objectstack/spec testTest Files 420 passed (420) · Tests 11218 passed (11218).
  • pnpm --filter @objectstack/spec typecheckcheck:test-typecheck: OK — @objectstack/spec's test layer compiles, exit 0.
  • pnpm --filter @objectstack/spec check:generated → first run named 2 stale (api-surface/, export-origins/); after --fix: ✓ All 14 generated artifacts are up to date.
  • pnpm --filter @objectstack/cli exec vitest run test/serve-capability-vocabulary.test.tsTests 12 passed (12); pnpm --filter @objectstack/cli typecheck → exit 0.
  • Ablation of the new drift pins, both legs rebuilt and disk-confirmed. Subject resolves through @objectstack/spec/kernel → spec dist/, so both legs rebuilt spec and ran scripts/ablation-dist-preflight.mjs. Mutant leg (organizations edition flipped to cloud + security-enterprise roster row deleted): anchored-write confirmation injected A count: 1 · injected B count: 1 · removed roster anchor count: 0; preflight ✓ marker present in 8 built files for both markers; predicted direction RED on exactly the two targeted pins — observed Tests 2 failed | 10 passed (12), the two failures being the organizations-edition pin and the enterprise-membership pin, nothing else. Restore leg (trap-guarded git checkout HEAD): markers 0/0 on disk, roster anchor back to 1, preflight --absent✓ marker absent from all 209 built files, rerun Tests 12 passed (12).
  • Gate families derived from the actual diff with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack (no paths passed; stderr confirmed derivation from this checkout at e239aa5fe4): 27 path-derived + 3 convention-triggered. All run locally, each exit code captured before any pipe (per-gate log + immediate $?): check:nul-bytes · check:changeset-gate-self-tests · check:objectui-changeset · check-adr-0087-registration · check-changeset-no-major · check-empty-changeset · release-rehearsal-clone --self-test · check:merge-driver · check-dev-prereqs · check-ci-filter-parity · check:cross-package-test-inputs · check:slot-lookup · check:spec-parsed-alias · check:test-source-alias · check:type-source-resolution · check:published-files · check:query-options-erasure · check:engine-double-contract · check:where-matcher · check-plugin-teardown-shape · check-affected-docs · check:empty-state · check:liveness · check:strictness-ledger · check:variant-docs · check:doc-formula-expressions · check:i18n · check:i18n-coverage · check:type-check-coverage · check:type-check-debt --re-measure — all exit 0. Verdict lines quoted rather than bare exits: check-type-check-coverage: OK — 65/78 workspace packages type-checked; check-type-check-coverage --re-measure: OK — 32 ledger entr(ies) re-measured in 224.7s, 1897 raw tsc error(s) total, none above its recorded number (run against the built closure: turbo run build --filter=./packages/* --filter=./packages/*/*70 total, exit 0, so the verdict is a measurement, not a refusal). Three gates (dev-prereqs, i18n, i18n-coverage) first failed on the documented unbuilt-workspace precondition and passed after the closure build — build-state, not diff findings.
  • Repo-wide pnpm lint (eslint . --no-inline-config) run in full, not narrowed → exit 0 at e239aa5fe4.

Draft on purpose — needs:contract-review (clause ②: new public spec surface); the PM flips after contract review. Merge is not blocked on any other card; #10921 is already closed by its own PR and is referenced here as prior fact only.

Generated by Claude Code


Generated by Claude Code

…r plugins[]-wired out-of-repo runtimes

The token-keyed PLATFORM_CAPABILITY_PROVIDERS map structurally cannot hold
@objectstack/organizations: it backs no requires token (serve loads it off
the resolved tenancy posture, ADR-0105). Add a sibling, package-name-keyed
provenance roster covering the plugins[]-wired out-of-repo population of two
(organizations + security-enterprise), edition typed to exclude 'open' by
construction. Provenance only: no new capability token, no widened key space,
no posture-to-token resolution semantics. Drift pins in
serve-capability-vocabulary.test.ts hold the two rosters to one edition
wherever they name the same package, and derive enterprise-row membership so
the next enterprise token is covered on arrival.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T9cDbY2NBiVJWYx3BpWfH2
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

2 anchor(s) derived from 1 changed package(s); no hand-written page names any of them. ✅

What this run could not see
  • 4 changed file(s) yielded no anchor (packages/spec/api-surface/kernel.json, packages/spec/api-surface/root.json, packages/spec/export-origins/kernel.json, …) — pages documenting those are invisible to this run
  • 2 name(s) were too generic to anchor anything (single lowercase words)

Coarse fallback — 126 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 2cf5a96cc77cc4a7b64c2512246ac1e121f348d8packageMentionDocs.

Which tree this was computed on

This run read content/docs from 4a88f205f009f05b7a5638768dd54a9f46bcc4ea — the merge of head e239aa5fe4d807724da894ee17b639cf57fd8a3a into base 2cf5a96cc77cc4a7b64c2512246ac1e121f348d8, 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 4a88f205f009f05b7a5638768dd54a9f46bcc4ea && git checkout 4a88f205f009f05b7a5638768dd54a9f46bcc4ea
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 2cf5a96cc77cc4a7b64c2512246ac1e121f348d8 e239aa5fe4d807724da894ee17b639cf57fd8a3a && git checkout -B drift-repro 2cf5a96cc77cc4a7b64c2512246ac1e121f348d8 && git merge --no-ff e239aa5fe4d807724da894ee17b639cf57fd8a3a

node scripts/docs-audit/affected-docs.mjs --json 2cf5a96cc77cc4a7b64c2512246ac1e121f348d8

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

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