Repository navigation
feat(verify): bootStack boots the compiled artifact, so a handle test runs each hook's lowered body in the sandbox - #22808
Conversation
Claude-Session: https://claude.ai/code/session_016njDy8ozy9B9Ns5Y8kAWEK Co-authored-by: Claude <noreply@anthropic.com>
…t door Claude-Session: https://claude.ai/code/session_016njDy8ozy9B9Ns5Y8kAWEK Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016njDy8ozy9B9Ns5Y8kAWEK Co-authored-by: Claude <noreply@anthropic.com>
…em7-lowered-body-door
Claude-Session: https://claude.ai/code/session_016njDy8ozy9B9Ns5Y8kAWEK Co-authored-by: Claude <noreply@anthropic.com>
…em7-lowered-body-door
📓 Docs Drift CheckThis PR changes 1 package(s): 12 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 5 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 3 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 de8cd6a2bc16b32f8b176f8b59de87b7b76f8777 && git checkout de8cd6a2bc16b32f8b176f8b59de87b7b76f8777
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 098481744fd7dd9d0eade7cb61701891c5a890cb c14e269ed055e8e8ef350e701040cafd14f9cee3 && git checkout -B drift-repro 098481744fd7dd9d0eade7cb61701891c5a890cb && git merge --no-ff c14e269ed055e8e8ef350e701040cafd14f9cee3
node scripts/docs-audit/affected-docs.mjs --json 098481744fd7dd9d0eade7cb61701891c5a890cb
|
Contract reviewServed-tier: Item 7 of the card, first round. Head resolved from ① Derived judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS |
Part of #22301
Clause-②: yes (widening)
Item 7 of the card: there was no lowered-body door. The handle booted the source configuration, so the production body-only path, and its refusal envelope, were not what a handle test ran. Item 1's remaining divergence (the MCP and pinyin host defaults) is not in this PR, and the card stays open for it.
What changes
bootStack(config, { artifact })(BootOptions.artifact,packages/verify/src/harness.ts) names the compiledobjectstack.json. It is a path, resolved againsthostRoot(so'dist/objectstack.json'names the app's own build), or anhttp(s)://URL.loadArtifactBundle,@objectstack/runtime,unwrapEnvelope: true, ascreateStandaloneStackreads it). It is mounted as the app (new AppPlugin(bundle)) in the slot the configuration takes on a source boot.hooks.run,actions.run,api, …) then runs what the build shipped.config, by the one composition rule: itsrequires, its ownplugins, its declared default profile, its datasources.objectstack serve CONFIGcomposes the same way for a configuration that mounts no plugin instance: the configuration module composes, and the compiled artifact is the app.codeandstatus. No error code is added.RESOURCE_NOT_FOUND/ 404. The boot never falls back to the source.config:RESOURCE_CONFLICT/ 409.onEnable, which no artifact carries:INVALID_REQUEST/ 400. See Acceptance notes.INVALID_REQUEST/ 400.handle.ts's header names the door, as a boot option rather than a method;bootStack's docblock names the two apps it can mount.packages/verify/README.mdadds a section and the option to the options list..changeset/22301-verify-lowered-body-door.md:@objectstack/verifyminor,Clause-②: yes (widening). No other published package moved. The exported types are unchanged (one optionalBootOptionsmember), so the dogfood fakeVerifyStack(rls-runner.test.ts) does not move.The three mechanism assumptions, measured
Each was measured on this branch over
origin/maina8f24b092c.@objectstack/clidepends on@objectstack/verify(packages/cli/package.json), and verify does not depend on cli. So verify cannot importlowerCallableswithout a cycle.objectstack build, which its own toolchain already carries. The handle reads it with the runtime's loader. No new package edge: verify already depends on@objectstack/runtime, which exportsloadArtifactBundleandisHttpUrl.lower-callables.ts→extract-hook-body.ts→detect-free-identifiers.ts, which importsts-morph) into a package both depend on would let the handle lower a configuration in memory. That is a second route to an artifact the build did not write: no docs collection, no runtime module, no--strict-body. That is the hand-assembled artifact ruling6070767186(A) rules out. So no four-axis package-graph decision is owed.os verify/os testcould build the artifact and pass it here. That needs no new edge, since cli already depends on verify. It is not built, because nothing measured pulls it.bodyat boot.AppPlugin→bindAppArtifactHandlers(packages/runtime/src/app-artifact-handlers.ts) binds hooks withbodyRunner: hookBodyRunnerFactory(new QuickJSScriptRunner(), …).bindHooksToEngine'sresolveHandler(packages/objectql/src/hook-binder.ts) takes a hook'sbodyahead of itshandler.bootStackalready went through that path for whatever bundle it mounted. So the door is "mount the artifact as the app", plus documentation and pins. No runtime, objectql or cli file is touched.ctx.dispatch.scope, the cross-phase scratchHookContextSchema.dispatchnames, lowers cleanly. The sandbox hands a bodyctx.dispatchas{ mode, index }withoutscope, so the body throwsTypeError: cannot set property 'stashed' of undefined.SandboxError(hook 'lbd_stash' threw: TypeError: …, nocode), and no row is stored.POST /api/v1/data/OBJECTanswers500{ code: 'INTERNAL_ERROR' }, where the source boot answers 2xx.{ code: 'VALIDATION_FAILED', status: 400 }:Error;SandboxErrorcarrying the samecodeandstatusout of the VM;toEqual)..sudo(, a free identifier) is not reachable from a local artifact. The build bundles that callable intoobjectstack-runtime.*.mjs, whichloadArtifactBundlemerges for a local file, so it runs in-process as underobjectstack start.objectstack build --strict-bodyrefuses it at build time. The README and the changeset say so; it is not pinned.Pins
packages/verify/src/harness.artifact-door.test.ts(6 cases):bodyruns in place of the configuration's in-processhandler, through a relative path underhostRoot.code+status.objectstack buildships unchanged, because verify cannot reach the lowering.packages/qa/dogfood/test/lowered-body-door.dogfood.test.ts(8 cases). Here the artifact is written bybuildShapedArtifact, which runs the build's real pipeline (normalizeStackInput→lowerCallables→ObjectStackDefinitionSchema, reaching cli's source by relative path, the route that file already documents).SandboxErrorwith the declared code and status, and REST wire parity.Ablations
Predictions were written before any run, at
4f8ab6b8dd. Each mutation went throughscripts/ablation-replace.mjs(anchor must hit, blob must change, restore proven byblob == HEADand an emptygit diff HEAD).configinstead of the artifact.ablation-dist-preflight.mjs @objectstack/verify ABLATION_A1_MOUNTS_CONFIGfound the marker indist/. The rebuild exited 1, with its output not captured. The JS bundles carried the marker per the preflight, and they are what the suite reads.SandboxErrorenvelope).--absentpreflight exit 0, tree clean.ablation-replacerefused it ("anchor count moved 1 -> 1") and restored. It was re-run on a different anchor.onEnablecheck deleted: predicted and observed 1 failed / 5 passed.process.cwd(): predicted and observed 2 failed / 4 passed.dispatch.scope): NOT RUN.@objectstack/runtimefrom source. It does not: it is dist-resolved, and the source paths in the stack traces were source maps. So A6 needs two runtime rebuilds.Tests and gates
The head is
c14e269ed0, which mergesorigin/main5fc57b382e.c14e269ed0:harness.artifact-door.test.ts: 6/6.lowered-body-door.dogfood.test.ts: 8/8.pnpm --filter @objectstack/spec build && … check:generated: all 14 generated artifacts up to date.197daa3248(the second merge brought only spec contracts, docs and governance text):@objectstack/verifysuite: 30 files / 240 tests;pnpm --filter @objectstack/verify typecheck: tsc, plus the test layer undertsconfig.test.json;lowered-body-door,rls-runnerandshowcase-declarative-endpoints(the otherbuildShapedArtifactuser): 3 files / 42 tests;pnpm --filter @objectstack/dogfood typecheck: 0 errors, closure built;--project uniton the two contracts that readharness.ts(serve-verify-security-parity,serve-audit-registration): 2 files / 20 tests. The cli integration tier is declared to CI; no cli file moved.c14e269ed0:node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderived 68 commands. All 68 exited 0.--ranwith exit codes: "68 derived famil(ies) accounted for — 68 run, 0 NOT-MEASURED (a DERIVED zero …)". Among them:check:dual-build-cjs-loads: 107 require entry points across 66 packages load;check:dts-closure,check:cross-package-test-inputs,check:test-source-alias,check:nul-bytes,check:published-files,check:type-check-coverage/-debt,check:doc-authoringandcheck:issue-citations(6 citations resolve).eslint --no-inline-config --format jsonon the 4 changed TS files: 4 files, 0 errors, 0 warnings. Each file is in the config's population (--print-configresolves its rules). The config enables no type-aware linting (noparserOptions.project, pereslint.config.mjs), so this diff cannot move a verdict on an untouched file. The repo-widepnpm lintis CI's.Deviation from the claim's file surface
The divergence, envelope and control pins live in
packages/qa/dogfood, notpackages/verify. The real lowering is reachable there without a manifest edge (build-shaped-artifact.tsalready reaches cli's source). From verify it would be a reverse test-time edge onto the package that depends on it. The verify-local file pins the door and its refusals.Acceptance notes
onEnableis refused, not grafted.objectstack serve CONFIGgrafts the configuration module'sonEnableonto the artifact's bundle (graftAuthoredRuntimeMembers,packages/cli/src/utils/graft-runtime-hooks.ts). An artifact-only deployment has none. This package cannot import the cli rule, and re-deriving it here would be a second copy. If an app's tests needonEnableunder the artifact door, the route is to move that rule into a package both can import. Carrier: none.BootOptions.artifactdoes not check that the artifact is current againstconfig; a stale build boots stale bodies (stated in the JSDoc, README and changeset).AppPlugin's own security-metadata registrar, as it does for a source boot.createStandaloneStackcomposes aMetadataPluginartifact door as the registrar instead. That is item 1's composition axis, unchanged here.hotcrm
test/helpers/verify-stack.ts'srunShippedHookcan move ontobootStack(config, { artifact: 'dist/objectstack.json' })afterobjectstack build, once hotcrm takes a release carrying this. The release is the Version Packages PR, a human act.Generated by Claude Code