Skip to content

fix(verify): bootStack composes the artifact-less MetadataPlugin os serve gives a host config, so declared apis endpoints answer under the harness - #23127

Draft
objectstack-fleet[bot] wants to merge 12 commits into
mainfrom
claude/issue-23088-bootstack-metadata-plugin
Draft

objectstack-fleet[bot] wants to merge 12 commits into
mainfrom
claude/issue-23088-bootstack-metadata-plugin

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #23088
Clause-②: no

bootStack now fills its metadata slot the way objectstack serve fills it for a host configuration: @objectstack/metadata's MetadataPlugin with no artifact, the composition PR #23078 gave serve. Before, the slot was the kernel's in-memory fallback, which has no matchEndpoint. So a stack's declared apis answered 404 ENDPOINT_NOT_FOUND under the harness and their declared answer under os serve, and every such boot logged AppPlugin's no-matcher warning.

Patch round 1 is applied at 0c232d83fb. The seat's ruling (comment 6136737187) extended the claimed surface by two files, packages/verify/vitest.config.ts and packages/qa/dogfood/test/external-validate-start-ordering.dogfood.test.ts, and ruled the open question A. Two comment sentences rode the same round. check:test-source-alias and the moved precondition are green. The PR stays a draft for the seat. The whole dogfood suite has one red left: a main-side interaction of PR #22900 with PR #22993 that reds with or without this PR's composition (measured below).

What changes

  • packages/verify/src/harness.ts, bootStack's composition.
    • It composes new MetadataPlugin({ watch: false, config: { bootstrap: 'lazy' }, rootDir: hostRoot }). These are the options serve.ts passes to hostConfigMetadataDoor, read at :3335-:3347: no artifactSource, no artifactWatch, registerSystemObjects left at its default.
    • The slot is right after HonoServerPlugin and before AppPlugin. That is serve's order: HTTP server, then the door (:3606), then the app wrap and the app's plugins. As in serve, the kernel.use has no catch.
    • A MetadataPlugin the caller hands in extraPlugins, or one the app declares in its own plugins, takes the composed one's place. The check reads the two keys serve's condition reads (name === 'com.objectstack.metadata', or constructor name MetadataPlugin). In that case the door is never registered, so the kernel logs no supersede.
    • The docblock's composition sentence now names the plugin. Its exception list stays the two it names: the MCP endpoint and pinyin search.
  • packages/verify/package.json gains @objectstack/metadata (workspace:*). @objectstack/metadata depends on neither verify nor runtime, so this adds no cycle (check:workspace-manifest-cycles exit 0). pnpm-lock.yaml was regenerated by pnpm install, as was the merge of main that brought PR feat(service-mailbox): sys_mail_message, its record associations and the restricted unassociated pool — the inbound-mail capability (manage_mailboxes, requires: mailbox) #22993's verify dependency line.
  • packages/verify/src/harness.metadata-door.test.ts (new; 2 cases, 2 boots).
    • The harness's own composition: the slot has matchEndpoint. The declared path answers 401 UNAUTHENTICATED without a session and 200 with one. An undeclared path under the same mount answers 404 ENDPOINT_NOT_FOUND. No no-matcher warning is logged. The case asserts all of these as one composite.
    • A caller's MetadataPlugin in extraPlugins: the metadata service is that instance's manager (by identity), no supersede warning names com.objectstack.metadata, and the declared path answers 401.
    • Warnings are matched by their structured fields (appId, plugin), never by their prose.
  • packages/qa/dogfood/test/showcase-declarative-endpoints.dogfood.test.ts: the hand-added artifact-backed MetadataPlugin is removed, and its header is corrected. The reason it goes:
    • The file's own stated intent is "boot the showcase the way a deployment boots it". The showcase is a host configuration, and os serve / os start of it now compose the artifact-less door (PR fix(cli,runtime): a host-config os serve composes a MetadataPlugin, so its declared apis are served; a matcher-less composition warns once #23078). So the harness's own composition is that deployment. The old hybrid (an artifact door, with AppPlugin still the registrar) was a boot no production composition runs.
    • The file now pins bootStack of the showcase with no MetadataPlugin in extraPlugins: 401 without a session and 200 with one, on GET /api/v1/apps/showcase/tasks.
    • The artifact door's apis: ingestion keeps its own dogfood proofs: declarative-endpoint-policy and declarative-endpoint-anonymous-guest, both green.
    • The stand-in artifact is still written through the build's own schema parse, so section 0 and the E7 publish-gate proof keep their subject.
  • .changeset/23088-verify-bootstack-metadata-plugin.md: @objectstack/verify patch, with Clause-②: no.
  • packages/verify/vitest.config.ts (patch round 1): the one anchored @objectstack/metadata alias that check:test-source-alias prescribes, beside the core entry.
  • packages/qa/dogfood/test/external-validate-start-ordering.dogfood.test.ts (patch round 1, option A): the precondition pins the composed door. The header drops the fallback framing and names the seam pin of the service-datasource: on objectstack start the federation service reads a metadata service it captured at init, before that service registers — external/validate answers no rows and the boot gate checks zero federated objects #21876 ordering repair. The federation cases are unchanged.
  • multi-package-artifact.dogfood.test.ts, external-validate-sees-runtime-save.dogfood.test.ts (patch round 1): one comment sentence each.

Reach, before

On origin/main 965a1bfc72, bootStack of examples/app-showcase (hostRoot = the app, OS_CLOUD_URL=off):

  • GET /api/v1/apps/showcase/tasks answered 404 ENDPOINT_NOT_FOUND, both without a session and with one. POST …/inquiries/purge answered the same 404.
  • The boot logged the warning once, with {"appId":"com.example.showcase","count":2}.
  • The slot was the fallback: constructor Object, matchEndpoint undefined, __serviceInfo.status degraded.

Census, before and after

How it was measured: a scratch script booted each app through the BUILT @objectstack/verify. BEFORE is the 965a1bfc72 build. AFTER is the build with only the composition commit (a23a3b8cc6) on top. Both ran under the shared verification lock. The script and its logs stayed in the scratchpad and are not committed.

item showcase BEFORE showcase AFTER crm BEFORE crm AFTER
metadata occupant fallback (degraded, no matchEndpoint) NodeMetadataManager, matchEndpoint fallback NodeMetadataManager
plugins on the kernel 34 35 (+ com.objectstack.metadata) 22 23
declared GET …/tasks, no session 404 ENDPOINT_NOT_FOUND 401 UNAUTHENTICATED n/a (no apis) n/a
the same, admin session 404 200, 10 records n/a n/a
undeclared path under the mount 404 404 n/a n/a
no-matcher warning 1 0 0 0
/meta/TYPE, 22 types: status, count, body hash baseline 22 of 22 equal baseline 22 of 22 equal
security registrar Registered stack-declared security metadata once, count 23 same, once, 23 once, 5 once, 5
position / permission / capability / sharing_rule on /meta 16 / 17 / 11 / 2 equal bodies — equal bodies
registry objects (registerSystemObjects) 104 104 (the door logs Registered system metadata objects once, and the set does not grow) 79 79
sys_metadata / _history / _commit / _audit rows after boot 0 / 0 / 0 / 0 0 / 0 / 0 / 0 0 each 0 each
hostRoot/.objectstack/metadata written no no (FileSystemRepository attached, writes nothing) no no
GET /api/v1/discovery, services.metadata.status degraded available degraded available
GET /api/v1/dev/metadata-events 404 200 text/event-stream (development posture, as under os dev) 404 200
verify CRUD family summary 24 objects / 15 verified / 1 needs fixture / 8 skipped / 0 gaps identical 6 / 4 / 1 / 1 / 0 identical
verify RLS family summary — — 6 objects, 6 probe-blocked identical
boot, median of 7 interleaved boots (shared box) 4750 ms 4745 ms — —

The four security collections keep one registrar, AppPlugin ('app-plugin'). The door reads none of the stack: lazy bootstrap is logged once, and no artifact is named. The CRM row is the CONTROL: a stack with no apis gives the same proof answers before and after.

Across the whole dogfood suite (next section), the two answers that move, discovery's metadata status and the development-only HMR route, are read by no pin.

The dogfood suite, whole

pnpm --filter @objectstack/dogfood exec vitest run --maxWorkers=2, under the lock:

  • at 0c232d83fb (patch round 1, after merging main): Test Files 1 failed | 258 passed | 1 skipped (260), Tests 2121 passed | 18 skipped (2139).
    • The one failed file is mail-message-pool-read-gate.dogfood.test.ts. It fails in its beforeAll (fixture permission set mail_pool_22870_read seeded: expected undefined to be truthy), so its 9 tests are skipped. That red is not this PR's, as measured below.
    • Every file this PR touches is green: showcase-declarative-endpoints 17/17, external-validate-start-ordering 3/3, external-validate-sees-runtime-save 3/3 and multi-package-artifact 9/9. The artifact-door proofs are green too: declarative-endpoint-policy 8/8 and declarative-endpoint-anonymous-guest 6/6.
    • The no-matcher warning appears 0 times in the whole run's log.
  • Earlier, before the ruling: at d072256c13 1 failed | 257 passed | 1 skipped (259) files, and at 96eaff46d0 1 failed | 256 passed | 1 skipped (258) files. Both times the one red was the precondition that patch round 1 moved.

The mail-message-pool-read-gate red, measured on this tree with and without the composition. Both legs ran inside the lock, with a trap-armed restore.

Patch round 1: the seat's ruling (comment 6136737187)

The seat extended the claimed surface by two files and ruled the open question A. Each item is its own commit.

  1. packages/verify/vitest.config.ts (3867e0a312): one anchored alias line beside the core entry, { find: /^@objectstack\/metadata$/, replacement: path.resolve(HERE, '../metadata/src/index.ts') }. That is exactly the remedy check:test-source-alias prescribed, and the gate now prints check-test-source-alias OK.
  2. packages/qa/dogfood/test/external-validate-start-ordering.dogfood.test.ts (8b2647eb19), option A:
  3. Two comment sentences (cf2065001e): multi-package-artifact.dogfood.test.ts about :163 and external-validate-sees-runtime-save.dogfood.test.ts about :24. A non-comment-line filter over that commit's diff counts 0 changed lines outside // comments.

main was then merged (480dcb717e, and again 0c232d83fb when it moved during the first lock wait). The new head is 0c232d83fb. All the measurements below are at it.

Ablations

Each leg ran inside scripts/pm/os-verify-lock.sh through node scripts/ablation-replace.mjs. The anchor was await kernel.use(new MetadataPlugin({ watch: false, config: { bootstrap: 'lazy' }, rootDir: hostRoot }));, x1 → x0. Each restore was trap-armed and proved by the blob equal to HEAD (704910003254) and an empty git diff HEAD.

  • Leg 1: the verify test, source-resolved. It ran in wrap mode, with blob 704910003254 → 082b863f6fb3. Result: Tests 1 failed | 1 passed (2).
    • The composite case received matcher: "undefined", anonymous: { status: 404, code: "ENDPOINT_NOT_FOUND" } and signedIn: 404.
    • It also received one matcher warning, carrying {"appId":"com.example.mdoor","count":1}.
    • The caller's-own-plugin case stayed green.
  • Leg 2: the showcase dogfood pin, which resolves verify through dist/. The mutation was void MetadataPlugin; // ablation-23088…, blob → 8efaf19cc8cf. Then pnpm --filter @objectstack/verify build, and ablation-dist-preflight.mjs @objectstack/verify 'kernel.use(new MetadataPlugin(' --absent read the marker as absent from all 6 built files.
    • Result: Tests 7 failed | 10 passed (17). The 401 cases received {"code":"ENDPOINT_NOT_FOUND"} 404, and the run logged the showcase's warning once.
    • Restore: rebuild, and the preflight found the marker present again in dist/index.js, with the tree clean.
    • A first attempt at this leg never ran. Its plain comment replacement left the import unused, and the DTS build refused that (TS6133). The trap restored the file and the rebuilt dist. The successful run above is the second attempt, with the import kept referenced.
  • Leg 3 (patch round 1): the moved precondition, which resolves verify through dist/.
    • Mutation, the same anchor: blob 704910003254 → 8efaf19cc8cf. Then a rebuild, and the marker read absent from all 6 built files.
    • CONTROL run with the door, as built: Test Files 1 passed (1), Tests 3 passed (3). Without the door: Tests 1 failed | 2 passed (3). The precondition reds at hasPlugin (expected false to be true), and both federation cases stay green.
    • Restore: blob equal to HEAD, rebuilt, the marker present again, and git status --porcelain empty.

Tests at 0c232d83fb

All of these ran through scripts/pm/os-verify-lock.sh, under one acquisition of slot dev-23088-test. Each exit code was captured before any pipe.

  • Build of the closure: exit 0.
  • pnpm --filter @objectstack/verify exec vitest run --maxWorkers=2: Test Files 37 passed (37), Tests 291 passed (291).
  • pnpm --filter @objectstack/verify typecheck: exit 0, check:test-typecheck: OK.
  • pnpm check:test-source-alias: exit 0, check-test-source-alias OK — 74 packages with tests scanned.
  • pnpm --filter @objectstack/dogfood typecheck: exit 0.
  • The whole dogfood suite: exit 1, the one main-side red above.
  • At 82ccd6962d, before the ruling, @objectstack/cli verify-* (7 files) gave Test Files 7 passed (7), Tests 21 passed (21). Patch round 1 touches no file those tests load.

Gates at 0c232d83fb

node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, with no paths: 80 commands, over a change set of 10 paths vs merge base f931e765b (committed 10, working tree 0). Each command ran with its exit code captured before any pipe.

  • All 80 exit 0.
  • --ran printed Run reconciliation — 80 derived, 80 run, 0 NOT-MEASURED, 0 UNRUN and a DERIVED zero — all 80 recorded an exit code and none of them is 3.
  • After this run, origin/main moved 5 commits to d206d08cab. None of them touches this diff's paths, packages/verify or packages/qa/dogfood.

Lint, a proven narrowing at 0c232d83fb

The repo-wide pnpm lint is CI's.

  1. Population, from eslint's own config: --no-inline-config --print-config resolves rules for all 7 changed TypeScript files (6 for harness.ts and vitest.config.ts, 5 for each test file). It resolves none for the changeset, package.json or pnpm-lock.yaml.
  2. Count, from --format json: files=7 errors=0 warnings=0, exit 0.
  3. Invariance: eslint.config.mjs enables no type-aware linting (no parserOptions.project and no projectService: 0 hits). Its only file reads are two baseline JSONs this diff does not touch.

Acceptance notes


Generated by Claude Code

…erve gives a host config

The metadata slot was the kernel's in-memory fallback, which has no
matchEndpoint, so every declared apis endpoint answered 404 under the
harness while os serve served it. A MetadataPlugin in extraPlugins or in
the app's own plugins takes the composed one's place.

Claude-Session: https://claude.ai/code/session_01VoSxBQujKLZKPwK2u5ehQ6
Co-authored-by: Claude <noreply@anthropic.com>
… the harness's own MetadataPlugin

The hand-added artifact-backed MetadataPlugin is dropped: bootStack now
composes the one objectstack serve gives this host configuration, so the
endpoints are the ones a deployment of it serves. The artifact door's
apis ingestion keeps its own dogfood proofs. Adds the verify changeset.

Claude-Session: https://claude.ai/code/session_01VoSxBQujKLZKPwK2u5ehQ6
Co-authored-by: Claude <noreply@anthropic.com>
…e proofs that own it

Claude-Session: https://claude.ai/code/session_01VoSxBQujKLZKPwK2u5ehQ6
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/m dependencies Pull requests that update a dependency file documentation Improvements or additions to documentation tests tooling labels Oct 13, 2026
@github-actions

github-actions Bot commented Oct 13, 2026 •

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. ⚠️ 2 changed file(s) yielded no anchor (packages/verify/package.json, packages/verify/vitest.config.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

What this run could not see
  • 2 changed file(s) yielded no anchor (packages/verify/package.json, packages/verify/vitest.config.ts) — pages documenting those are invisible to this run
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 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.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 5 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 f931e765b72fcdc49f6c28d59e0155b10f79b8ee → packageMentionDocs.

Which tree this was computed on

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

node scripts/docs-audit/affected-docs.mjs --json f931e765b72fcdc49f6c28d59e0155b10f79b8ee

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

…'s tests

check:test-source-alias prescribes it: every verify test reaches the
metadata package through harness.ts, and the gate's registry is
shrink-only.

Claude-Session: https://claude.ai/code/session_01VoSxBQujKLZKPwK2u5ehQ6
Co-authored-by: Claude <noreply@anthropic.com>
…dataPlugin, not the fallback

The precondition was written to red the day bootStack composes a metadata
plugin; it now pins that occupant (registered, a MetadataManager, with the
endpoint matcher). The header drops the objectstack start framing and
names the seam pin that holds the init-ordering fix. The two federation
cases are unchanged.

Claude-Session: https://claude.ai/code/session_01VoSxBQujKLZKPwK2u5ehQ6
Co-authored-by: Claude <noreply@anthropic.com>
…ata plugin

Comment lines only.

Claude-Session: https://claude.ai/code/session_01VoSxBQujKLZKPwK2u5ehQ6
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 0c232d83fb586b00a33dab206e4279a1fcaab8b8
Local-runs: none

Inputs: card #23088 (its body and all six comments — triage, the claim, both os-dev-reports, the seat's ruling and its ACCEPT, the last four read as claims to test); PR #23127 (its body, its 10-file list, the three-dot diff against origin/main); and the check-runs on the head, latest run per name: 35 success, 7 skipped by the roster, no failure. Reference reads on origin/main, read-only: serve.ts's hostConfigMetadataDoor and its kernel.use site, stackBootPlugins, isHostConfig, the kernel's use and registerPluginByName, MetadataPlugin's options, repository attach and HMR mount, FileSystemRepository.start, the #21876 seam pin, the two artifact-door dogfood proofs. Rules judged against: AGENTS.md Prime Directives #10 and #12, Post-Task Checklist step 3. Nothing was built, run or re-run.

① Derived judgments

1. The composition against its published rule — right.

  • Option for option: new MetadataPlugin({ watch: false, config: { bootstrap: 'lazy' }, rootDir: hostRoot }) is what serve.ts builds for hostConfigMetadataDoor: no artifactSource, no artifactWatch, registerSystemObjects at its default. rootDir: serve hands servedAppRootOrCwd(), the harness opts.hostRoot ?? process.cwd() — the same anchor (the app's root, cwd when none is given).
  • Slot for slot: serve registers its HTTP server, then the door at its own kernel.use site with no catch, then the app's plugins with the AppPlugin wrap appended; the harness registers the door right after HonoServerPlugin and before AppPlugin, with no catch. Same order, same failure mode: a door that cannot compose fails the boot instead of falling back to the matcher-less occupant quietly — Prime Directive Add comprehensive test suite for Zod schema validation #12's shape, no lenient fallback in a consumer.
  • The "already in plugins" rule: serve tests p?.name === 'com.objectstack.metadata' || p?.constructor?.name === 'MetadataPlugin' over stackBootPlugins(config), which returns the RAW config.plugins array; the harness tests the same two keys over opts.extraPlugins plus the raw config.plugins. Equivalent, with the same blind spot on both sides: a string specifier that materializes into a MetadataPlugin is not seen, the door composes, and the kernel's last-one-wins supersedes it with one warn — the net occupant is still the app's own, which is what the changeset promises. Adding extraPlugins to the scan is the right reading of the harness's own precedence rule.
  • Composed where serve would not: serve gates this door on isHostConfig(config) (an instantiated plugin in plugins) and on no dev artifact door; the harness composes it for every configuration, a library configuration included, where serve would boot createStandaloneStack's artifact-backed door (securityMetadataRegistrar: 'artifact-door') instead. Judged right and not this PR's gap: ruling A already mounts the host-configuration path for every configuration (AppPlugin(config), registrar 'app-plugin'), and the artifact-less door is the one door consistent with that registrar — an artifact door beside an 'app-plugin' registrar is exactly the hybrid the showcase file used to hand in, a boot no production composition runs. The docblock names the modelled shape ("for a host configuration"). The library-configuration divergence predates this PR (verify: the in-process handle boots a leaner stack than serve and has no door for eight things an app's tests need (requires[] capabilities, system/predicate update, the form door, user-less triggers, …), measured by hotcrm#2013 #22301) and is outside it. BootOptions.artifact is unaffected: AppPlugin loads the artifact, the door reads nothing, as the docblock's "read from config either way" says.
  • Composed here that serve would not for the same configuration: the dev-only HMR route. MetadataPlugin.start mounts /api/v1/dev/metadata-events only under NODE_ENV=development; the harness forces that posture (pre-existing, "mirrors objectstack dev"), so the route answers 200 here and would not under a production serve. The changeset states it. Right.
  • Writes: FileSystemRepository attaches at hostRoot/.objectstack/metadata; start() scans and deliberately does not mkdir (cli/metadata: os migrate plan still creates .objectstack/metadata/ on a fresh project (the residual half of #6743's dry-run write side effect) #7000, "attaching is not a write"), and disableWatch holds because watch: false. The dev's "nothing written" census is what the code predicts.
  • The exception list stays the two it named (MCP endpoint, pinyin search). No third exception was owed: the measurement moved nothing the suite pins.

2. What moves for every caller of bootStack — right, stated by its answers.

  • Moves on every boot: the metadata occupant (a plain fallback object, __serviceInfo.status degraded, no matchEndpoint → NodeMetadataManager with one); kernel.hasPlugin('com.objectstack.metadata'); a declared apis endpoint's answer (404 ENDPOINT_NOT_FOUND → 401 or its declared answer); AppPlugin's no-matcher warning (gone); GET /api/v1/discovery services.metadata.status (degraded → available); GET /api/v1/dev/metadata-events (404 → 200); and three boot log lines (lazy bootstrap, FileSystemRepository attached, Registered system metadata objects).
  • Does not move, per the census read as a claim and consistent with the code: the 22 /meta type bodies, the security registrar (AppPlugin once, same counts — the door has no artifact and reads none of the stack), the registry object set, sys_metadata* rows (0), boot time.
  • A published caller's test CAN break: one that pinned a declared endpoint's 404 under the harness, the fallback's __serviceInfo (as this repo's own external-validate-start-ordering did, and had to move), the plugin's absence, or the warning line. The changeset states every moved answer — the occupant, the endpoint answers, the precedence rule, discovery, the HMR route, /meta unchanged — so a reader who greps degraded or 404 lands on the sentence; it states them as answers, not as "a test that pinned the old fallback now fails". Adequate for a patch to a harness whose consumers are test suites. Noted, not a failing finding.

3. packages/verify/package.json gains @objectstack/metadata — right. harness.ts imports it at module top and ships it in dist/, so dependencies, beside every other runtime plugin the harness mounts, is the right section — not devDependencies, not a peer. No cycle: @objectstack/metadata depends on core, metadata-core, metadata-fs, spec and types, none of which depends on verify; Validate Package Dependencies is green on the head. pnpm-lock.yaml moves the one importer link and nothing else (+3). The vitest.config.ts alias is the anchored form the core entry uses and the remedy check:test-source-alias prescribes (Lint & Repo Gates green, red on this at round 0). The file's header still explains only the core entry — a comment nit, not a defect.

4. The flipped dogfood precondition — right, and it bears weight. The case pins the occupant by public reads: hasPlugin('com.objectstack.metadata'), toBeInstanceOf(MetadataManager) (NodeMetadataManager extends MetadataManager, so the check holds on the real occupant), and matchEndpoint a function. The dev's ablation leg 3 — door removed, verify rebuilt, the marker preflight-absent from all six built files — reds it at hasPlugin with both federation cases green: the pin's weight and its independence from the federation answers, both shown. The #21876 ordering fix stays pinned at its seam: external-validate-reads-live-registry.test.ts's case "asks for the registry when validation runs, so an engine registered after init is read". What is no longer guarded: the dogfood-level, HTTP-door reproduction of a matcher-less composition (the fallback registered after every init()); that composition now exists only on OS_MODE=off boots and embedders, and its mechanism is held by the seam pin. Ruling A is the right one of the three: B (a stand-in named com.objectstack.metadata that registers nothing) declares a plugin the runtime does not deliver, Prime Directive #10's corollary; C drops the HTTP federation proof no other layer holds.

5. The showcase file's hand-added plugin removed — right. The artifact door's apis: → api ingestion keeps two proofs that hand an artifact-backed MetadataPlugin through extraPlugins with artifactSource: { mode: 'local-file' } (declarative-endpoint-policy, declarative-endpoint-anonymous-guest); under the precedence rule the composed door is skipped there, so both still measure the artifact door, and multi-package-artifact's second block does the same. The showcase file still writes the build-shaped artifact and its section 0 reads it back, so the E7 publish-gate proof keeps its subject. Its new rootDir is the showcase directory (the file chdirs there); attach writes nothing. The header sentences match what the boot now does.

6. Every docs sentence against the diff — true. The bootStack docblock: the composition sentence names the plugin and the precedence rule, the exception list is unchanged. The changeset: the options, rootDir = BootOptions.hostRoot (cwd when unset, as that option documents), "reads none of the stack", the precedence rule, the three moved answers, /meta unchanged. The three test headers and the multi-package-artifact comment. The start-ordering header's "serve and start give a host configuration" matches serve.ts's own note that os start's serve child takes the door's branch. One stale sentence stays in the tree outside the claimed surface: packages/services/service-datasource/src/plugin.ts about :83, "objectstack start composes no metadata plugin", false since PR #23078 — a code comment with no reader harmed, noted by the seat; not a filing class under Prime Directive #10. The runtime's own datasource-*.test.ts comments ("host-config shape: NO MetadataPlugin") describe kernels those files build themselves, not bootStack, and stay true.

7. The verify pin, harness.metadata-door.test.ts — right. The composite case was ablated red (leg 1: matcher undefined, 404 and 404, one matcher warning). The second case's "never registered" claim rests on its supersede filter, which bears weight: registerPluginByName warns with a structured plugin: name field on a duplicate name, and the door registers before extraPlugins, so a composed-then-superseded door is caught by exactly that field. Nit: the first case's warning filter includes line.includes('MetadataPlugin'), a prose match, while the header says "never by prose"; the composite's other three fields carry the weight.

8. CI on the head. Check Changeset, Governed Surface Queue Guard (not governed), Validate Package Dependencies, Lint & Repo Gates, all six Test Core shards, the four type checks: green. The dogfood shards skip on a PR by design (#22998, the Dogfood Regression Gate context counts zero attestations), so the whole-suite result on this head is the dev's local run — 258 passed, 1 skipped, 1 red, judged in ③ — and the full battery measures it after landing.

② Semver level

@objectstack/verify patch — matches. The diff makes a published package keep its own published rule (the docblock ships in its .d.ts): a fix in a released package, patch by Post-Task Checklist step 3, never none. Clause-②: no holds: packages/verify/src/index.ts is untouched, BootOptions and VerifyStack are unchanged, no authorable key moves, no accept set widens or narrows (a configuration that booted still boots, measured over the whole dogfood suite; a MetadataPlugin in plugins or extraPlugins is honoured as before). The new runtime dependency is additive. The changeset carries the Clause-② line; no breaking marker and no migration are owed. Check Changeset green.

③ Boundary flags

  • Round 0 blocked_on and deviation 1 (two edits outside the surface): extended by the seat's ruling, done in round 1 one commit per item; the start-ordering diff has no line inside either federation case. Answered.
  • Round 0 deviation 2 (a stray /build-base.pid at the dev container's filesystem root): outside the repository, nothing lands; an operator cleanup note. Answered.
  • Round 0 deviation 3 — ESCALATED to the maintainer. The dev printed the value of OS_FLEET_PRIVATE_KEY into round 0's local tool output and asks that the key be treated as exposed to that transcript. Neither the seat's ruling nor its ACCEPT addresses it. It is not a diff defect and not repository state, so it does not move this verdict; it is a credential-handling incident for the maintainer's rotation decision. No value is reproduced here.
  • Round 0 deviations 4 to 9 (the first ablation attempt a non-run, replaced by the second; trailer form per AGENTS.md; scratch instruments uncommitted; the gitignored showcase_external.db cleaned; Fixes on line 1 while the card is not in a decision box, the Part-of check green; zero label writes): answered.
  • Round 0 open_questions: ruled A; judged right (① 4).
  • Round 0 out_of_scope_findings: the stale service-datasource comment (① 6); the 17 chdir files' leftover database (pre-existing test-infrastructure state, outside this surface); the two comment sentences (fixed in round 1). Noted-not-filed is the right class for the first two.
  • Round 1 deviations: the worktree recreated at the branch tip; a lock queue timeout re-queued; a stale gate runner stopped by pid; the describe title moved with the header; the precondition by public reads; @objectstack/cli verify-* not re-run locally this round — Test Core on the head covers that package's unit tier, green; no environment value printed this round; database cleanup. Answered.
  • Round 1 out_of_scope_findings — the one red dogfood file, mail-message-pool-read-gate.dogfood.test.ts. Judged main's own red, not this PR's: (a) the dev's CONTROL ran both legs inside the lock — door as built, and door removed with verify rebuilt and the marker preflight-absent — and both red at the same beforeAll assertion (fixture permission set seeded: expected undefined to be truthy), 9 skipped; (b) hourly full run: red on main (CI) #23148, the hourly full-run card on main, names the same file, line and assertion on main's own run of 287fd6e5d0, with PR feat(plugin-security)!: no seeder or materializer writes a sys_permission_set row; the boot seeds no row for a declared set (#15204 stage 6b-2) #22900 ("no seeder or materializer writes a sys_permission_set row") in the range since the last green run; (c) the mechanism matches — the fixture reads a sys_permission_set row that feat(plugin-security)!: no seeder or materializer writes a sys_permission_set row; the boot seeds no row for a declared set (#15204 stage 6b-2) #22900 stopped writing, and this PR's door registers nothing of the stack (security registrar and /meta/permission bodies unchanged in the census); (d) the fix is dispatched on C3 #15204 stage 8: S8-T #22962 + S8 retire the four catalog row objects; both Tier H; land right after D #22900 (handed to the engine lane) #23056 in another lane. Nothing is skipped or narrowed here. The ACCEPT's sequencing behind hourly full run: red on main (CI) #23148 is the two-speed doctrine's, not this record's.
  • Prime Directive chore: version packages #10: no defect, contract violation or authoring trap found in this diff goes unfiled; the one defect found beside it (the main red) has its card and its fix in flight.

Implemented-by: claude/issue-23088-bootstack-metadata-plugin
Reviewed-by: session_01VoSxBQujKLZKPwK2u5ehQ6

VERDICT: PASS


Generated by Claude Code

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

2 participants