Repository navigation
fix(verify): bootStack composes the artifact-less MetadataPlugin os serve gives a host config, so declared apis endpoints answer under the harness - #23127
Conversation
…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>
…ugin precedence 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>
Claude-Session: https://claude.ai/code/session_01VoSxBQujKLZKPwK2u5ehQ6 Co-authored-by: Claude <noreply@anthropic.com>
…otstack-metadata-plugin
…e proofs that own it Claude-Session: https://claude.ai/code/session_01VoSxBQujKLZKPwK2u5ehQ6 Co-authored-by: Claude <noreply@anthropic.com>
…otstack-metadata-plugin
📓 Docs Drift Check2 anchor(s) derived from 1 changed package(s); no hand-written page names any of them. What this run could not see
Coarse fallback — 5 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # 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 |
…'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>
…otstack-metadata-plugin
…otstack-metadata-plugin
Contract reviewServed-tier: Inputs: card #23088 (its body and all six comments — triage, the claim, both ① Derived judgments1. The composition against its published rule — right.
2. What moves for every caller of
3. 4. The flipped dogfood precondition — right, and it bears weight. The case pins the occupant by public reads: 5. The showcase file's hand-added plugin removed — right. The artifact door's 6. Every docs sentence against the diff — true. The 7. The verify pin, 8. CI on the head. ② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
Fixes #23088
Clause-②: no
bootStacknow fills itsmetadataslot the wayobjectstack servefills it for a host configuration:@objectstack/metadata'sMetadataPluginwith no artifact, the composition PR #23078 gaveserve. Before, the slot was the kernel's in-memory fallback, which has nomatchEndpoint. So a stack's declaredapisanswered404 ENDPOINT_NOT_FOUNDunder the harness and their declared answer underos serve, and every such boot loggedAppPlugin'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.tsandpackages/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-aliasand the moved precondition are green. The PR stays a draft for the seat. The whole dogfood suite has one red left: amain-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.new MetadataPlugin({ watch: false, config: { bootstrap: 'lazy' }, rootDir: hostRoot }). These are the optionsserve.tspasses tohostConfigMetadataDoor, read at:3335-:3347: noartifactSource, noartifactWatch,registerSystemObjectsleft at its default.HonoServerPluginand beforeAppPlugin. That isserve's order: HTTP server, then the door (:3606), then the app wrap and the app's plugins. As inserve, thekernel.usehas no catch.MetadataPluginthe caller hands inextraPlugins, or one the app declares in its ownplugins, takes the composed one's place. The check reads the two keysserve's condition reads (name === 'com.objectstack.metadata', or constructor nameMetadataPlugin). In that case the door is never registered, so the kernel logs no supersede.packages/verify/package.jsongains@objectstack/metadata(workspace:*).@objectstack/metadatadepends on neitherverifynorruntime, so this adds no cycle (check:workspace-manifest-cyclesexit 0).pnpm-lock.yamlwas regenerated bypnpm install, as was the merge ofmainthat 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'sverifydependency line.packages/verify/src/harness.metadata-door.test.ts(new; 2 cases, 2 boots).matchEndpoint. The declared path answers401 UNAUTHENTICATEDwithout a session and200with one. An undeclared path under the same mount answers404 ENDPOINT_NOT_FOUND. No no-matcher warning is logged. The case asserts all of these as one composite.MetadataPlugininextraPlugins: themetadataservice is that instance's manager (by identity), no supersede warning namescom.objectstack.metadata, and the declared path answers401.appId,plugin), never by their prose.packages/qa/dogfood/test/showcase-declarative-endpoints.dogfood.test.ts: the hand-added artifact-backedMetadataPluginis removed, and its header is corrected. The reason it goes:os serve/os startof 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, withAppPluginstill the registrar) was a boot no production composition runs.bootStackof the showcase with noMetadataPlugininextraPlugins: 401 without a session and 200 with one, onGET /api/v1/apps/showcase/tasks.apis:ingestion keeps its own dogfood proofs:declarative-endpoint-policyanddeclarative-endpoint-anonymous-guest, both green..changeset/23088-verify-bootstack-metadata-plugin.md:@objectstack/verifypatch, withClause-②: no.packages/verify/vitest.config.ts(patch round 1): the one anchored@objectstack/metadataalias thatcheck:test-source-aliasprescribes, beside thecoreentry.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: onobjectstack startthe federation service reads ametadataservice it captured at init, before that service registers —external/validateanswers 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/main965a1bfc72,bootStackofexamples/app-showcase(hostRoot= the app,OS_CLOUD_URL=off):GET /api/v1/apps/showcase/tasksanswered404 ENDPOINT_NOT_FOUND, both without a session and with one.POST …/inquiries/purgeanswered the same 404.{"appId":"com.example.showcase","count":2}.Object,matchEndpointundefined,__serviceInfo.statusdegraded.Census, before and after
How it was measured: a scratch script booted each app through the BUILT
@objectstack/verify. BEFORE is the965a1bfc72build. 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.metadataoccupantdegraded, nomatchEndpoint)NodeMetadataManager,matchEndpointNodeMetadataManagercom.objectstack.metadata)GET …/tasks, no sessionENDPOINT_NOT_FOUNDUNAUTHENTICATEDapis)/meta/TYPE, 22 types: status, count, body hashRegistered stack-declared security metadataonce, count 23position/permission/capability/sharing_ruleon/metaregisterSystemObjects)Registered system metadata objectsonce, and the set does not grow)sys_metadata/_history/_commit/_auditrows after boothostRoot/.objectstack/metadatawrittenFileSystemRepository attached, writes nothing)GET /api/v1/discovery,services.metadata.statusdegradedavailabledegradedavailableGET /api/v1/dev/metadata-eventstext/event-stream(development posture, as underos dev)The four security collections keep one registrar,
AppPlugin('app-plugin'). The door reads none of the stack:lazy bootstrapis logged once, and no artifact is named. The CRM row is the CONTROL: a stack with noapisgives the same proof answers before and after.Across the whole dogfood suite (next section), the two answers that move, discovery's
metadatastatus 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:0c232d83fb(patch round 1, after mergingmain):Test Files 1 failed | 258 passed | 1 skipped (260),Tests 2121 passed | 18 skipped (2139).mail-message-pool-read-gate.dogfood.test.ts. It fails in itsbeforeAll(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.showcase-declarative-endpoints17/17,external-validate-start-ordering3/3,external-validate-sees-runtime-save3/3 andmulti-package-artifact9/9. The artifact-door proofs are green too:declarative-endpoint-policy8/8 anddeclarative-endpoint-anonymous-guest6/6.d072256c131 failed | 257 passed | 1 skipped (259)files, and at96eaff46d01 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-gatered, measured on this tree with and without the composition. Both legs ran inside the lock, with a trap-armed restore.Test Files 1 failed (1),Tests 9 skipped (9), the same assertion.704910003254→5b13a59a72e7, verify rebuilt, the marker preflight-absent from all 6 built files): the same1 failed (1),9 skipped (9), the same assertion.git status --porcelainempty.82ccd6962d. Between that head and this one,mainlanded 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 asys_permission_setrow; the boot seeds no row for a declared set"), and this file's fixture reads such a row. So it is amain-side interaction of 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 with PR feat(service-mailbox): sys_mail_message, its record associations and the restricted unassociated pool — the inbound-mail capability (manage_mailboxes, requires: mailbox) #22993, which redsmain's full battery whatever this PR does. It is reported to the seat, not fixed here.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.
packages/verify/vitest.config.ts(3867e0a312): one anchored alias line beside thecoreentry,{ find: /^@objectstack\/metadata$/, replacement: path.resolve(HERE, '../metadata/src/index.ts') }. That is exactly the remedycheck:test-source-aliasprescribed, and the gate now printscheck-test-source-alias OK.packages/qa/dogfood/test/external-validate-start-ordering.dogfood.test.ts(8b2647eb19), option A:kernel.hasPlugin('com.objectstack.metadata'), themetadataservicetoBeInstanceOf(MetadataManager)(@objectstack/metadata), andmatchEndpointa function.objectstack startshape" and "the verify harness composes no metadata plugin" framing. It names the seam pin that holds the service-datasource: onobjectstack startthe federation service reads ametadataservice it captured at init, before that service registers —external/validateanswers no rows and the boot gate checks zero federated objects #21876 init-ordering fix:packages/services/service-datasource/src/__tests__/external-validate-reads-live-registry.test.ts.describetitle follows the header. The two federation cases are unchanged: the diff has no line inside either of them.hasPlugin(expected false to be true), and the two federation cases stay green (ablation leg 3 below).cf2065001e):multi-package-artifact.dogfood.test.tsabout:163andexternal-validate-sees-runtime-save.dogfood.test.tsabout:24. A non-comment-line filter over that commit's diff counts 0 changed lines outside//comments.mainwas then merged (480dcb717e, and again0c232d83fbwhen it moved during the first lock wait). The new head is0c232d83fb. All the measurements below are at it.Ablations
Each leg ran inside
scripts/pm/os-verify-lock.shthroughnode scripts/ablation-replace.mjs. The anchor wasawait 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 emptygit diff HEAD.704910003254→082b863f6fb3. Result:Tests 1 failed | 1 passed (2).matcher: "undefined",anonymous: { status: 404, code: "ENDPOINT_NOT_FOUND" }andsignedIn: 404.{"appId":"com.example.mdoor","count":1}.verifythroughdist/. The mutation wasvoid MetadataPlugin; // ablation-23088…, blob →8efaf19cc8cf. Thenpnpm --filter @objectstack/verify build, andablation-dist-preflight.mjs @objectstack/verify 'kernel.use(new MetadataPlugin(' --absentread the marker as absent from all 6 built files.Tests 7 failed | 10 passed (17). The 401 cases received{"code":"ENDPOINT_NOT_FOUND"}404, and the run logged the showcase's warning once.dist/index.js, with the tree clean.TS6133). The trap restored the file and the rebuilt dist. The successful run above is the second attempt, with the import kept referenced.verifythroughdist/.704910003254→8efaf19cc8cf. Then a rebuild, and the marker read absent from all 6 built files.Test Files 1 passed (1),Tests 3 passed (3). Without the door:Tests 1 failed | 2 passed (3). The precondition reds athasPlugin(expected false to be true), and both federation cases stay green.git status --porcelainempty.Tests at
0c232d83fbAll of these ran through
scripts/pm/os-verify-lock.sh, under one acquisition of slotdev-23088-test. Each exit code was captured before any pipe.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.main-side red above.82ccd6962d, before the ruling,@objectstack/cliverify-*(7 files) gaveTest Files 7 passed (7),Tests 21 passed (21). Patch round 1 touches no file those tests load.Gates at
0c232d83fbnode scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, with no paths: 80 commands, over a change set of 10 paths vs merge basef931e765b(committed 10, working tree 0). Each command ran with its exit code captured before any pipe.--ranprintedRun reconciliation — 80 derived, 80 run, 0 NOT-MEASURED, 0 UNRUNanda DERIVED zero — all 80 recorded an exit code and none of them is 3.origin/mainmoved 5 commits tod206d08cab. None of them touches this diff's paths,packages/verifyorpackages/qa/dogfood.Lint, a proven narrowing at
0c232d83fbThe repo-wide
pnpm lintis CI's.--no-inline-config --print-configresolves rules for all 7 changed TypeScript files (6 forharness.tsandvitest.config.ts, 5 for each test file). It resolves none for the changeset,package.jsonorpnpm-lock.yaml.--format json:files=7 errors=0 warnings=0, exit 0.eslint.config.mjsenables no type-aware linting (noparserOptions.projectand noprojectService: 0 hits). Its only file reads are two baseline JSONs this diff does not touch.Acceptance notes
/meta, persistence and the object set are unmoved (census). Two answers of a test boot move with the slot: discovery'smetadatastatus, and the development-only HMR route. The changeset says so.main-side red, not this PR's:mail-message-pool-read-gate.dogfood.test.ts(PR feat(service-mailbox): sys_mail_message, its record associations and the restricted unassociated pool — the inbound-mail capability (manage_mailboxes, requires: mailbox) #22993) reds after 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 on any tree, measured with and without this PR's composition (above). It is reported to the seat for the main-red convention.packages/services/service-datasource/src/plugin.tsabout:83says "objectstack startcomposes no metadata plugin". That has been stale for a host config since 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. Noted by the seat, not filed.chdirintoexamples/app-showcaseand leave.objectstack/data/showcase_external.dbthere (gitignored). This is pre-existing. Noted by the seat, not filed.Generated by Claude Code