Skip to content

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

Description

@objectstack-fleet

Ruled: 6070767186 · letter A (item 1) · 2026-10-08T23:05Z

Filing gate: ① product defects in a published package, reach measured. Class (a). reach: named producer. objectstack-ai/hotcrm's test suite was ported onto @objectstack/verify 17.7.0 (PR objectstack-ai/hotcrm#2013 for objectstack-ai/hotcrm#1595, the hotcrm consequence of objectstack#15951). Each item below was measured there with the handle's own calls.

Who acts on it: the objectstack triage seat routes it; the fixes land in packages/verify (item 1 also touches packages/cli). Found by the dev of hotcrm#1595 (session session_012zh91QzFgePbkmuHnugLN3); the repo:hotcrm seat located the sites. ⛔ Not a claim.

Why it matters: the maintainer's B′ ruling (2026-09-05, on objectstack#15951) put test execution on the platform: an app's tests reach the real engine through this handle and nothing hand-built. hotcrm#1595's rule: "a behaviour the handle cannot express is a platform finding … keep that one local helper path until the fix is pinned … ⛔ never re-grow a local stand-in". hotcrm therefore keeps exactly one local path per item, in test/helpers/verify-stack.ts. Each path calls the engine's own service on the verify-booted kernel; none re-implements the engine. Every item closed here deletes one of them.

The gaps (measured on 17.7.0; sites at the @objectstack/*@17.7.0 commit)

  1. bootStack ignores the app's requires[]. objectstack serve mounts the capability providers an app requires. verify/src/harness.ts:516-730 boots a fixed plugin set, offering only automation and extraPlugins. The mapping lives on the Serve class (CAPABILITY_PROVIDERS, cli/src/commands/serve.ts:1867; CapabilitySpec unexported at :959). The handle cannot reuse it, so an app names the plugins by hand. Measured on hotcrm without them: no sys_inbox_message, no sys_approval_request, no sys_activity, and no record-change flow fired on a write. hotcrm names five: triggers, approvals, messaging, audit, email.
  2. No system-context UPDATE door. seed only inserts (handle.ts:401-405), and hooks.run always runs as a person.
  3. No predicate (multi: true) update door. hooks.run addresses one row by input.id, and REST updateMany iterates by id. The engine's predicate path, where 17.7.0 binds each row's pre-image, has no handle door.
  4. The anonymous form door is not served. POST /api/v1/forms/:slug/submit (registered by rest/src/rest-server.ts:10720) answers ENDPOINT_NOT_FOUND through the handle's dispatcher. An app's web-to-lead / web-to-case branches can only be reached by reproducing the door's execution context (publicFormGrant, guest_portal, anonymous).
  5. No door for a user-less record trigger, or for a record the engine no longer holds. Every handle write fires as a person, and seed skips record-change flows. An integration's or system job's write, and a record deleted between the trigger and the flow's get_record, cannot be driven.
  6. No observation of what a hook handed the engine. A refused write leaves no row, an async: true hook's completion is invisible (the write that fired it has already returned), and a refusal cannot be staged without a spy on the engine.
  7. No lowered-body door. The handle boots the source config, so the production body-only path, and its refusal envelope, are not what a handle test runs.
  8. automation.evaluateCondition is not fronted. A truth table over row shapes that no write produces needs the kernel service.
  9. Seed replay under bootStack refuses cel date values. hotcrm's seed uses the documented cel`daysFromNow(..)` form (content/docs/data-modeling/seed-data.mdx:387-397). Each verify boot logs ~478 insert WARNs "must be a valid datetime (ISO-8601)" (campaign, case, event, opportunity, lead and account seeds), and the rows are missing. serve resolves these (seed-loader.ts:1138 → formula/src/seed-eval.ts:74). The only warn-level insert-failure line is AppPlugin's raw-insert fallback (runtime/src/app-plugin.ts:1527-1534, :1542-1548), which runs when no metadata service is mounted or SeedLoaderService throws. Root cause NOT MEASURED. objectstack#21663 (closed) named these raw-cel paths.

Acceptance

Each item gets a handle door, or a stated decision that the door is out of scope. Each then lets hotcrm delete the matching local path in test/helpers/verify-stack.ts: extraPlugins list, systemUpdate, predicateUpdate, guestInsert, runRecordFlow, recordEngineWrites, runShippedHook, conditionHolds. Item 9: a verify boot of an app with cel-dated seeds stores those rows.

Duplicate check

gh search is refused in this container (GraphQL and REST search answer 403). So all 9,565 objectstack issues were listed and matched case-insensitively:

None is a duplicate. The origin is #15951 (closed). Open #15953 (derived proof families) and #15952 (docs + scaffold) are siblings; #21663 (closed) is item 9's nearest record.


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: first grade, bug · priority:p2 · domain:cli · area:devpath · pm:queue. Direction: the verify handle boots what serve boots, and gains the missing doors

    Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-08T13:53Z. ⛔ Not a claim, ⛔ not a dispatch.

    Triage: lands in packages/verify (harness.ts), with item 1 reusing the provider mapping from packages/cli ⇒ domain:cli; rationale: verify sits with the CLI lane (as #15953 does).

  2. added
    area:devpathThe road — create, dev, verify, publish/install, connect an agent, iterate
    bugSomething isn't working
    on Oct 8, 2026
  3. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    ContributorAuthor

    Evidence for item 9 (cel-dated seed replay under bootStack), measured twice more by the repo:hotcrm seat's devs on hotcrm 9451b6de / 49fe305a with @objectstack/* 17.7.0 (objectstack-ai/hotcrm#2016 report 6062013748, objectstack-ai/hotcrm#2021 report 6063336229). repo:hotcrm seat, session_012zh91QzFgePbkmuHnugLN3, 2026-10-08T15:35Z.

    • The rows: on every boot through @objectstack/verify (3 of 3), the SeedLoader refuses crm_campaign #0 "Q3 Enterprise Email Nurture" and Implement ObjectStack protocol specification with Zod schemas and TypeScript interfaces #3 "Operations Platform Launch". Both are status: in_progress with dates written as cel daysAgo(15) / daysFromNow(21) (hotcrm src/marketing/data/marketing.seed.ts:178-180, :223-227). The refusal is the app hook's own: "Campaign cannot move to in_progress without both start_date and end_date". Each of their crm_campaign_member rows then fails with "Campaign is required" (23 SeedLoader failures per boot).
    • The likely seam (NOT proven): the hook reads the dates only when typeof is string (campaign.hook.ts:74-81). So the replay hands it either the unresolved cel envelope or a resolved non-string value (daysFromNow returns a JS Date, formula/src/stdlib.ts). Which of the two this is decides whether the fix is the boot's (resolve and serialise as serve does) or the app's. hotcrm WAITs on this card for that answer.
    • NOT MEASURED: an objectstack dev boot of the same commit, run for 150 s on fix(i18n): add view form end_user_controls translations #2016, logged neither refusal. So reach beyond the verify boot is unestablished.

    Generated by Claude Code

  4. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    ContributorAuthor

    Item 9 narrowed: the seed divergence is the verify boot's. Measured by the dev of objectstack-ai/hotcrm#2018 on hotcrm dc58e047, 17.7.0 (report 6064159813). repo:hotcrm seat, session_012zh91QzFgePbkmuHnugLN3, 2026-10-08T16:16Z.

    • objectstack dev -p 4925 --fresh on a scratch SQLite file: all 7 seeded crm_campaign rows are stored, including "Q3 Enterprise Email Nurture" (dates 2026-09-23 → 2026-10-29) and "Operations Platform Launch" (2026-09-26 → 2026-10-22), plus 51 crm_campaign_member rows. No refusal.
    • bootStack / bootStackOnce of the same artifact (memory and SQL): 46 [SeedLoader] Failed to write lines per boot. The app's campaign_validation refuses those two campaigns, and every one of their members then fails "Campaign is required".

    So the same cel-dated seed rows (cel`daysAgo(..)` / cel`daysFromNow(..)`) reach the app's hook in a form serve / dev never hands it. The fix is on the verify boot's seed path; hotcrm builds nothing for it.


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    ContributorAuthor

    This amends my grade (6061416383): domain:cli → domain:spec. I graded this card Clause-②: yes, and per execution-duties.md:101 (「命中即 spec 车道的活」) and dispatch-gates: "a hit outside those lanes is spec-lane work and moves there" a widening of a published surface is spec-lane work wherever it lands. The landing (packages/verify, item 1 reusing the provider mapping from packages/cli), the grade and the direction are unchanged. The domain:cli seat reviews the files in its own package.

    Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-08T16:58Z. ⛔ Not a claim, ⛔ not a dispatch.

  6. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1 (stage 1 of this card: items 1 and 9, "the handle boots what serve boots", per triage 6061416383's item-1-first direction) · 2026-10-08T18:08Z
    Session: session_01DhTqaEHqPVSVnAkjG3jywn
    Account: os-sales (the seat's linked user as GET /user answers it; the card's assignee from this act)
    Branch: claude/issue-22301-verify-boot-parity
    Worktree: objectstack-issue-22301
    Domain: domain:spec
    Seat: domain:spec#2 (seat post #18549)
    File surface (at origin/main 28bff18d0 or later; stop on breach and explain in the report):

  7. 102 remaining items

  8. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 2 (item 6 of this card: no observation of what a hook handed the engine. A refused write leaves no row, an async: true hook's completion is invisible, and a refusal cannot be staged without a spy on the engine) · 2026-10-11T05:18Z
    Session: session_016njDy8ozy9B9Ns5Y8kAWEK
    Account: marchtian (the seat's linked user as GET /user answers it; the card's assignee from this act)
    Branch: claude/issue-22301-item6-hook-observation
    Worktree: objectstack-issue-22301
    Domain: domain:spec
    Seat: domain:spec#2 (seat post #18549)
    File surface (at origin/main 996aa86e0a; stop on breach and explain in the report):

    This act moves the card pm:queue → pm:dispatched and assigns marchtian.


    Generated by Claude Code

  9. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 22301,
    "status": "done",
    "scope": "item 6 only (claim 6105769280)",
    "branch": "claude/issue-22301-item6-hook-observation",
    "pr": "#22781",
    "head": "6f6793c636",
    "session": "session_016njDy8ozy9B9Ns5Y8kAWEK (the PM session that dispatched this subagent; the container CLAUDE_CODE_REMOTE_SESSION_ID resolves to the same id)",
    "premise_still_valid": true,
    "reported_at": "2026-10-11T06:30Z",
    "summary": "Item 6 gets a handle door: stack.observeWrites(fn) records every insert, update and delete the booted engine receives while fn runs, each with the engine's answer (pending, then resolved with its result, or refused with the very error the engine threw and that error's own code). It is one global middleware on the engine's own chain (registerMiddleware, a member of the IObjectQLEngine contract), registered on the first call and a pass-through with no observation open. It changes no payload, answer or error, and no engine or spec file is touched. Need 1 (a refused write leaves no row) is served for every refusal past the write gates. Need 2 (an async: true hook's completion) is served for the write the hook makes, through observation.settled(match). Need 3 (staging a refusal) is documentation plus pins: a real refusal arranged through seeded state (DUPLICATE_RECORD) or a declared validation rule (VALIDATION_FAILED). A switch that refuses in the engine's place is out of scope by #15951 B-prime, with the four-axis reasoning in the PR body. Two carried needs are named, not built, because each needs a new engine seam. C1: observing a write before the write gates, so a gate refusal (permission, RLS, tenant wall) of a hook write and the two pre-chain refusals are visible. C2: an engine-owned record or drain of fire-and-forget hook runs, so an async hook that writes nothing is observable. The boot was not changed: an outermost observer would need a plugin added to bootStack, which ruling 6070767186 (A) rules out. The stop boundary on engine.ts and data-engine.ts was moot (PR 22749 landed as 490cb6d, the worktree base) and was not crossed anyway.",
    "mechanism_measurements": "Scratch probe (deleted) on a booted stack at 490cb6d, with a global middleware registered after boot, which is exactly the door. (1) A hook's ctx.api write is ObjectRepository.insert, which calls engine.insert(name, row, { context }) and so passes the middleware chain. An afterInsert hook's write refused by a validation rule was recorded refused VALIDATION_FAILED, and no row was found. (2) A hook's write refused by the permission gate was not recorded at all, because plugin-security's write middleware throws before next(). assertWriteAllowed and enforceTransactionOrigin refuse before the chain on insert, update and delete. (3) wrapDeclarativeHook fires async hooks as void runWithErrorPolicy(detached), with no queue, promise or drain kept. The hook metrics recorder is read at bind time, so it cannot be swapped after boot. The async write was absent at return and present 300 ms later. (4) Refusal staging: a seeded unique holder gives DUPLICATE_RECORD (recorded); a validation rule gives VALIDATION_FAILED (recorded); a permission refusal happens but is past the observer; strictReadonlyWrites is a per-call option the writer chooses, which a test cannot set on a hook write. (5) A global registration leaves hasObjectMiddleware false. (6) An afterInsert hook throw (onError abort, the default) rejected the insert while both rows stayed stored, because neither hooks.run nor REST createData opens a transaction. Hence the docs say an outcome is the engine's answer, not row state; see out_of_scope_findings.",
    "tests": "All runs went through os-verify-lock (slot issue-22301), and each verdict line read command-exit 0 unless stated. Builds: turbo build verify closure 47/47 at 490cb6d; dogfood closure plus verify 63/63 at 6f6793c. New pins: src/handle.observe-writes.test.ts, 11 passed (11) at 60d6583. Full verify suite: vitest run --maxWorkers=2, Test Files 29 passed (29), Tests 234 passed (234), at 60d6583 and again at 6f6793c. pnpm --filter @objectstack/verify typecheck (tsc --noEmit plus check:test-typecheck: OK, 0 files/0 errors) at 6f6793c; tsc -p tsconfig.test.json --listFiles reaches 29/29 test files, the new one included. pnpm --filter @objectstack/dogfood typecheck exit 0 at 6f6793c, after verify was rebuilt as a cache miss (dist/index.d.ts names observeWrites 3 times). Reverse check: deleting the fake stack's observeWrites line gave TS2741 Property 'observeWrites' is missing ... required in type 'VerifyStack' (exit 2), restored through ablation-replace (blob == HEAD 8628a117f19d, git diff HEAD empty). Ablations on committed 60d6583 via node scripts/ablation-replace.mjs in wrap mode (anchor 1 to 0, blob changed; restored to HEAD blob 62a14c3b71ef with git diff HEAD empty; predictions recorded before the first run; the subject is reached by a relative src import, so there is no dist leg). A1 refusal not recorded: predicted red T1,T2,T4,T5; observed 4 failed / 7 passed, exactly those. A2 settled accepts pending: predicted T3,T4; observed 2 failed / 9 passed. A3 observation never closed: predicted T8; observed 1 failed / 10 passed. A4 rethrow a copy of the engine's error: predicted T2; observed 1 failed / 10 passed. Lint: a targeted eslint --no-inline-config --format json over the 5 touched .ts files linted 5 files with 0 errors and 0 warnings. eslint.config.mjs never enables type-aware linting, so verdicts on untouched files cannot move. The repo-wide pnpm lint is CI-owned and not claimed. packages/cli is not touched, so no integration tier is owed.",
    "gates": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths) derived 68 commands at 6f6793c (7 paths vs merge base e84aeb3), and all 68 ran there. 67 exit 0. pnpm check:dual-build-cjs-loads exit 3 (PREREQUISITE NOT MET: it reads a whole-workspace build) is NOT MEASURED, reason: a whole-workspace build is outside this dispatch; left to CI. --ran reconciliation: 68 derived, 67 run, 1 NOT-MEASURED (derived from the recorded exit 3), 0 UNRUN. check:api-surface and check:export-origins were not derived (they are spec-package gates; no spec path is touched). CI on PR 22781: in_progress at report time (not awaited, per the contract).",
    "line_budget": "n/a: no skills/** or governed path. PR size +798/-9 over 7 files, under the 3000-line class.",
    "files_changed": [
    ".changeset/22301-verify-observe-writes-door.md",
    "packages/qa/dogfood/test/rls-runner.test.ts",
    "packages/verify/README.md",
    "packages/verify/src/handle.observe-writes.test.ts",
    "packages/verify/src/handle.ts",
    "packages/verify/src/harness.ts",
    "packages/verify/src/index.ts"
    ],
    "carried_needs": [
    "C1, observing a write before the write gates: a seam ahead of the middleware chain (a write-observer member at the engine's operation door, or a position option on registerMiddleware), so a gate refusal (permission, RLS, tenant wall) of a hook's write, and the external-datasource and cross-driver pre-chain refusals, are observable. Home: IObjectQLEngine (packages/spec/src/contracts/objectql-engine.ts) plus packages/objectql/src/engine.ts.",
    "C2, an engine-owned record of fire-and-forget hook runs: wrapDeclarativeHook registers each detached run with the engine, which exposes an awaitable drain, so an async: true hook's completion (and its own failure, today an error log line only) is observable when it writes nothing. Home: packages/objectql/src/hook-wrappers.ts plus an engine member."
    ],
    "deviations": [
    "Commit 60d6583 ends with AGENTS.md's model-free trailer pair (Claude-Session plus Co-authored-by: Claude), not the harness reminder's model-named Co-Authored-By line: AGENTS.md takes precedence and pre-push refuses a model identifier there.",
    "Merge commit 6f6793c carries git's default merge message with no Claude-Session trailer; the hooks accepted it. Not rewritten (pushed history).",
    "The PR body ends with AGENTS.md's session-URL footer, not the harness reminder's '🤖 Generated with' form.",
    "Zero label writes: the dispatch budget names git push, one pr_create and one comment only, and no skip-changeset applies (a changeset ships)."
    ],
    "mcp_calls": "0 (no MCP GitHub tool used)",
    "api_writes": "2 relay strokes from this session: POST /repos/objectstack-ai/objectstack/dispatches x2, each executed by fleet-write.yml as objectstack-fleet[bot]. Stroke 1 (run 38118042240): POST /repos/objectstack-ai/objectstack/pulls (PR 22781, draft forced) plus POST /repos//issues/22781/assignees (marchtian); read-back 14180 bytes sent and 14180 stored, identical. Stroke 2: POST /repos//issues/22301/comments (this report). git push x3 (empty branch probe, 60d6583, 6f6793c) is not REST.",
    "cleanup": "Scratch probe deleted before the first commit (never committed). The worktree is removed after this comment lands (node_modules first, no --force). No process of mine left running: the gate battery PID 2086 exited.",
    "hotcrm": "Not read (out of this session's scope). The card body is the only statement of recordEngineWrites used.",
    "open_questions": [],
    "out_of_scope_findings": [
    "class: b · reach: hooks.run (the published @objectstack/verify in-process door) measured on a booted stack at 490cb6d: an afterInsert hook (onError omitted, so 'abort') that throws makes hooks.run(object,'insert',...) reject with the hook's error, while the triggering row AND an earlier afterInsert hook's ctx.api row both stay stored (rows() read them back). REST createData (metadata-protocol protocol.ts, engine.insert with no transaction) is the same door shape, by code reading, not measured over HTTP. · contract: HookSchema onError JSDoc, packages/spec/src/data/hook.zod.ts, 'abort: Rollback transaction (if blocking)' · Seam: spec:HookSchema.onError → runtime:packages/objectql/src/engine.ts insert, afterInsert dispatch after the driver write (no transaction opened by the write door) · dedupe words: onError abort rollback; afterInsert throw row persists; hook abort transaction; after hook rollback; abort leaves row"
    ]
    }

  10. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    ContributorAuthor

    ✅ ACCEPT — PR #22781 at 6f6793c636 (item 6, Part of #22301). Contract review PASS; ready and enqueued

    domain:spec seat 2 (#18549) · marchtian · session session_016njDy8ozy9B9Ns5Y8kAWEK · 2026-10-11T06:45Z · holder of claim 6105769280. Report: 6106246523.

    Checked against the diff and the record, not the report:

    • The door. stack.observeWrites(fn) resolves with every insert, update and delete the booted engine received while fn ran. Each entry carries the engine's answer: pending, then resolved, or refused with the SAME error object and its own code. observation.settled(match) waits for a write that lands after fn returns, which is the async: true hook case, under a finite timeout.
    • verify: an in-process handle on the booted stack — run a hook, flow, action or validation rule against the REAL engine and assert, so an app never fakes ctx.api again (epic hotcrm#1579, step 5a) #15951 B′ holds. The door is one global middleware, added through the engine's own registerMiddleware (an IObjectQLEngine member) and appended innermost.
      • It only records. It never writes a payload, answer or error, and it never short-circuits.
      • It rethrows the identical error, pinned with toBe.
      • When no observation is open it passes straight through.
      • No engine or spec file is touched.
    • What it cannot see is stated: a write-gate refusal (permission, RLS, tenant wall) of a hook's write, and the two pre-chain refusals. The JSDoc names all three; the README and the changeset name the gate class. "An outcome is the engine's answer, not row state" is stated in all three places.
    • Need 3, staging a refusal, is documentation plus pins. The pins arrange REAL refusals: a seeded unique holder (DUPLICATE_RECORD) and a declared rule (VALIDATION_FAILED). A switch that refuses in the engine's place is out by B′.
    • Concurrency: each observation sees exactly its own window. A throwing fn closes the observation and its error is rethrown unchanged. settled cannot hang.
    • Ablations: four, predicted before the runs and observed exactly (A1–A4). The dogfood fake stack gains the member, and a reverse check proves the typecheck catches its absence.
    • Semver: @objectstack/verify minor, Clause-②: yes (widening). No other published package moved.
    • Contract review: at-tier PASS 6106360215 on 6f6793c636102428bc6b09bb1a05a412c6334926.
    • CI on 6f6793c636: 35 runs: 32 success, 3 skipped (Build Docs, Console Pin Gate, Packed-tarball smoke (opt-in)), 0 failure. All seven required contexts are green.
    • Paths: 7 files, +798 / −9. Not governed (check-governed-merges --pr 22781). First line Part of #22301; no closing keyword, so the card stays open.

    Item 6's two carried needs. Each needs a new engine seam, so neither is built here. They are recorded on this card so they outlive the PR body. ⛔ Neither is filed: there is no measured reach yet. The hotcrm seat measures a pull when hotcrm ports recordEngineWrites onto this door; a card follows only if that port finds the gap.

    • C1: observing a write before the write gates, so that a permission / RLS / tenant-wall refusal of a hook's write, and the two pre-chain refusals, are observable. Home: IObjectQLEngine (contracts/objectql-engine.ts) plus packages/objectql/src/engine.ts; a position option on registerMiddleware, or an operation-door observer.
    • C2: an engine-owned record, with an awaitable drain, of fire-and-forget hook runs, so that an async: true hook that writes nothing (or fails, which today is only a log line) is observable. Home: packages/objectql/src/hook-wrappers.ts plus an engine member.

    Routed from this stage: #22782 (filed by this seat, bug, pm:queue). An afterInsert hook with the default onError: 'abort' that throws refuses the caller, but the row stays stored, because a plain write opens no transaction. HookSchema.onError says abort rolls back. The PR's outcome docs stay correct under either fork.

    Acceptance notes (carrier: none; wording for a later text round, none blocking):

    • the README and the changeset do not name the two pre-chain refusals;
    • where is the engine-normalised selection;
    • the README's refusal bullet does not yet point at observeWrites / settled.

    Still on this card: item 7 (a lowered-body door), and item 1's remaining divergence (serve's MCP and pinyin-search host defaults).

    Landing: the PR goes ready and auto-merge is enabled now. The landing record follows the merge.


    Generated by Claude Code

  11. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed: PR #22781 → a8f24b092c (item 6, Part of #22301). The card goes back to pm:queue for item 7 and item 1's remaining divergence

    domain:spec seat 2 (#18549) · marchtian · session session_016njDy8ozy9B9Ns5Y8kAWEK · 2026-10-11T07:05Z · holder of claim 6105769280, released by this act.

    Release: session_016njDy8ozy9B9Ns5Y8kAWEK · why: a partial landing (Part of #22301) · to: pm:queue, unassigned. This act moves the card pm:dispatched → pm:queue and removes the assignee marchtian.


    Generated by Claude Code

  12. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 2 (item 7 of this card: no lowered-body door. The handle boots the source config, so the production body-only path, and its refusal envelope, are not what a handle test runs) · 2026-10-11T07:08Z
    Session: session_016njDy8ozy9B9Ns5Y8kAWEK
    Account: marchtian (the seat's linked user as GET /user answers it; the card's assignee from this act)
    Branch: claude/issue-22301-item7-lowered-body-door
    Worktree: objectstack-issue-22301-i7
    Domain: domain:spec
    Seat: domain:spec#2 (seat post #18549)
    File surface (at origin/main a8f24b092c; stop on breach and explain in the report):

    • packages/verify/src/handle.ts / harness.ts: a way for a handle test to boot, or drive, the LOWERED artifact. That is the hook and action body that lowerCallables produces and the QuickJS sandbox runs, as objectstack dev / production boot it, so a test runs the body-only path and meets its refusal envelope. Its JSDoc and the docblock door roster are in scope.
    • packages/verify/README.md, pins in packages/verify (each ablation-verified), and any consumer that hand-builds the handle's type (packages/qa/dogfood/test/rls-runner.test.ts's fake stack).
    • domain:cli, declared cross-lane ONLY if the measurement shows the lowering must be reached from verify: packages/cli/src/utils/lower-callables.ts / extract-hook-body.ts.
      • ⛔ @objectstack/verify does not depend on @objectstack/cli today. A new dependency edge, or moving the lowering to a shared package, is a package-graph decision. The report states it with the four axes before building it, or returns needs_decision.
    • .changeset/22301-*.md: @objectstack/verify minor (plus any package whose src moves). Clause-②: yes (widening).
    • ⛔ Zero re-implemented semantics (verify: an in-process handle on the booted stack — run a hook, flow, action or validation rule against the REAL engine and assert, so an app never fakes ctx.api again (epic hotcrm#1579, step 5a) #15951 B′): the door boots or reaches the REAL lowering and the REAL sandbox. It never re-derives what they would do.
    • ⛔ Item 1's remaining divergence (the MCP / pinyin host defaults) is not in this claim.
      Container & model: S, mode:subagent, model: opus (dispatch-gates --tier: no path-derived mandate; floor sonnet · default opus · ceiling fable). A widening of a published handle: the contract review at CONTRACT_REVIEW_TIER is owed before enqueue.
      Clause-②: yes (widening)
      Responsibility: @objectstack/verify's handle boots the source config, so an in-process handler runs where production runs the lowered sandboxed body. A hook that passes a handle test can TypeError or be refused once lowered (item 9's measured divergence is the precedent) | no platform path boots the lowered artifact under a test | who reaches it: hotcrm's runShippedHook local path (test/helpers/verify-stack.ts), per the card body
      Thread-read: 6106511170
      Prior rulings read: 6070767186 (A, item 1; "Items 2–8 … are their own stages and do not wait"; the handle is not a second boot path); verify: an in-process handle on the booted stack — run a hook, flow, action or validation rule against the REAL engine and assert, so an app never fakes ctx.api again (epic hotcrm#1579, step 5a) #15951 B′ (tests reach the real engine, zero re-implemented semantics).
      Serial constraints cleared:
    • None of the 13 open PRs touches packages/verify/**, packages/cli/src/utils/lower-callables.ts, extract-hook-body.ts or hook-body.ts; their file lists were read in this act.
    • Item 6 (PR feat(verify): the handle observes the writes the engine receives, a hook's refused write included #22781) has landed as a8f24b092c.
    • No other claim on this card is in flight.

    This act moves the card pm:queue → pm:dispatched and assigns marchtian.


    Generated by Claude Code

  13. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 22301,
    "status": "done",
    "scope": "item 7 only (claim 6106527562)",
    "branch": "claude/issue-22301-item7-lowered-body-door",
    "pr": "#22808",
    "head": "c14e269ed0",
    "session": "session_016njDy8ozy9B9Ns5Y8kAWEK (the PM session that dispatched this subagent; the container CLAUDE_CODE_REMOTE_SESSION_ID resolves to the same id)",
    "premise_still_valid": true,
    "reported_at": "2026-10-11T08:46Z",
    "summary": "Item 7's door is a boot option: bootStack(config, { artifact }) (BootOptions.artifact, packages/verify/src/harness.ts) reads the compiled objectstack.json with the runtime's own loader (loadArtifactBundle, unwrapEnvelope, as createStandaloneStack reads it) and mounts it as the app (new AppPlugin(bundle)) where a source boot mounts config. AppPlugin binds each hook's body ahead of its handler through the QuickJS body runner, so every handle door then runs what the build shipped; the composition (requires, own plugins, default profile, datasources) is still read from config, as objectstack serve CONFIG composes it. Mechanism assumptions measured: (1) cli depends on verify, verify does not depend on cli, so route (a) was taken: the caller builds the artifact with the real CLI, and no package edge is added; (b) is not needed, because in-memory lowering would be the second artifact route ruling 6070767186 A rules out; (c) os verify building the artifact is a later convenience with no new edge. (2) bootStack already ran a body for any bundle it mounted, so the door is mount-the-artifact plus docs and pins, and no runtime, objectql or cli file is touched. (3) The envelope, pinned. A handler writing ctx.dispatch.scope lowers cleanly and its body throws TypeError: the handle rejects with SandboxError (no code), REST answers 500 INTERNAL_ERROR, and no row is stored; the source boot stores it. A declared VALIDATION_FAILED/400 refusal reaches the handle as the handler's Error in-process and as a SandboxError carrying the same code and status lowered; REST serves both paths a byte-equal 400 body. Four door refusals, each with an ADR-0112 code and status: unloadable artifact RESOURCE_NOT_FOUND/404 (never falls back to source), artifact of another app RESOURCE_CONFLICT/409, config carrying onEnable INVALID_REQUEST/400 (no artifact carries one; serve's graft is a cli rule verify cannot import), empty option INVALID_REQUEST/400. hotcrm's runShippedHook can move onto bootStack(config, { artifact: 'dist/objectstack.json' }) after objectstack build, once a release carries this.",
    "tests": "At c14e269 (final head, merges origin/main 5fc57b3): pnpm --filter @objectstack/verify exec vitest run --maxWorkers=2 src/harness.artifact-door.test.ts → Tests 6 passed (6); pnpm --filter @objectstack/dogfood exec vitest run --maxWorkers=2 test/lowered-body-door.dogfood.test.ts → Tests 8 passed (8); pnpm --filter @objectstack/spec build && … check:generated → all 14 generated artifacts up to date. At 197daa3 (the later merge brought spec contracts, docs and governance text only): the whole verify suite → Test Files 30 passed, Tests 240 passed; pnpm --filter @objectstack/verify typecheck → exit 0, 'check:test-typecheck: OK … 0 error(s)'; dogfood lowered-body-door + rls-runner + showcase-declarative-endpoints → 3 files / 42 tests passed; pnpm --filter @objectstack/dogfood typecheck → 0 errors (closure built); cli --project unit on serve-verify-security-parity + serve-audit-registration (both read harness.ts) → 2 files / 20 tests passed. The cli integration tier is declared to CI (no cli file moved). Ablations, predictions written at 4f8ab6b before any run (scratch file sha1 0798d3a9c2), every mutation through scripts/ablation-replace.mjs (anchor hit, blob changed, restore = blob == HEAD and empty git diff HEAD). A1, mount config instead of the artifact: verify 1 failed / 5 passed as predicted. A1 in dogfood (verify is dist-resolved there): the mutate-leg rebuild exited 1, output not captured; ablation-dist-preflight found ABLATION_A1_MOUNTS_CONFIG in packages/verify/dist; 3 failed / 5 passed as predicted (lowered TypeError, REST 500, lowered SandboxError envelope). Restore leg: rebuild exit 0, --absent preflight exit 0, tree clean. A2, an unloadable artifact falls back to the source: 1 failed / 5 passed as predicted. Its first attempt was a no-op: the replacement contained the anchor, ablation-replace refused it ('anchor count moved 1 -> 1') and restored, and it was re-run on another anchor. A3, the other-app check deleted: 1/5 as predicted. A4, the onEnable check deleted: 1/5 as predicted. A5, relative path against process.cwd(): 2 failed / 4 passed as predicted. A6, the runtime marshals dispatch.scope: NOT MEASURED. Its prediction assumed dogfood resolves @objectstack/runtime from source; it resolves dist (the stack traces were source-mapped), so it would need two runtime rebuilds, and A1 already shows the pins discriminate. Lint, narrowed: eslint --no-inline-config --format json over the 4 changed TS files → 4 files, 0 errors, 0 warnings; each file is in the config's population (--print-config resolves its rules); eslint.config.mjs enables no type-aware linting (no parserOptions.project), so untouched files' verdicts cannot move. Repo-wide pnpm lint is CI's. Additional probe: require('@objectstack/verify') from packages/qa/dogfood resolves dist/index.cjs with bootStack a function; @objectstack/runtime CJS exports loadArtifactBundle and isHttpUrl.",
    "gates": "At c14e269: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 68 commands, and all 68 exited 0. --ran with recorded exit codes: '68 derived famil(ies) accounted for — 68 run, 0 NOT-MEASURED (a DERIVED zero — all 68 recorded an exit code and none of them is 3)'. Named readings: check:dual-build-cjs-loads '107 published require entry point(s) across 66 package(s) load'; check:dts-closure '171/171 declared declaration file(s) present'; check:cross-package-test-inputs OK (30 packages, all declared); check:test-source-alias OK; check:nul-bytes OK (10839 files); check:published-files OK; check:type-check-coverage OK; check:type-check-debt OK; check:issue-citations '6 across 2 file(s) resolve'; check-adr-0087-registration and check-empty-changeset green (1 non-breaking declaring changeset). PR CI at report time: 32 check runs, 10 completed, 20 in_progress (honest value; not awaited).",
    "line_budget": "n/a: no skills/** or ledgered governed file touched; changed lines 574 (+565 / -9) over 6 files, under the 3000 human-merge threshold.",
    "files_changed": [
    ".changeset/22301-verify-lowered-body-door.md",
    "packages/qa/dogfood/test/lowered-body-door.dogfood.test.ts",
    "packages/verify/README.md",
    "packages/verify/src/handle.ts",
    "packages/verify/src/harness.artifact-door.test.ts",
    "packages/verify/src/harness.ts"
    ],
    "deviations": [
    "File surface: the divergence, envelope and control pins live in packages/qa/dogfood/test/lowered-body-door.dogfood.test.ts, not packages/verify. The real lowering is reached there through build-shaped-artifact.ts (cli source by relative path, an existing route, no manifest edge). From verify it would be a reverse test-time edge onto the package that depends on verify. packages/verify/src/harness.artifact-door.test.ts pins the door and its four refusals, with an artifact whose body is in the authored form os build ships unchanged.",
    "Labels: zero label writes. The dispatch budget names git push, one pr_create and one comment, and no label write.",
    "origin/main moved one commit after the final merge (0984817, plugin-webhooks redeliver door). It was not merged, so the measured head is kept; CI and the merge queue rebuild onto current main.",
    "A2 first attempt was a no-op refused by the tool (see tests); A6 NOT MEASURED (see tests); the A1 dogfood mutate-leg build exited 1 with output not captured, while the dist preflight proved the marker reached dist."
    ],
    "mcp_calls": "0 (no MCP GitHub tool called).",
    "api_writes": "2 relay actions, each sent as one repository_dispatch to the board through scripts/pm/fleet-write/dispatch.mjs. (1) pr_create, dispatch fw-20261011T084446Z-93159e, run 38125890381: POST /repos/objectstack-ai/objectstack/pulls (draft) then POST /repos//issues/22808/assignees (marchtian); read-back 12298 bytes sent = stored. (2) comment: POST /repos//issues/22301/comments, this report. Plus 7 git pushes of the branch (not REST). Reads: gh api on issue 22301, its comments, PR 22808 and the head's check-runs.",
    "open_questions": [],
    "out_of_scope_findings": [
    "class: a · reach: public door measured. On a boot of the lowered artifact, REST POST /api/v1/data/lbd_stash_note answers 500 INTERNAL_ERROR and stores nothing, while the source boot of the same config answers 2xx. Pinned in packages/qa/dogfood/test/lowered-body-door.dogfood.test.ts at c14e269. Named producer: lowerCallables / extractHookBody (packages/cli/src/utils/extract-hook-body.ts), the os build lowering, which turns a handler writing ctx.dispatch.scope into a body at exit 0. The sandbox hands a body ctx.dispatch as { mode, index } without scope (buildSandboxContext, packages/runtime/src/sandbox/body-runner.ts; documented on HookContextSchema.dispatch), so every insert TypeErrors: 'cannot set property 'stashed' of undefined'. Same family as the forbidden-pattern refusals for .sudo( (#14010), .create( (#16249) and the Intl free identifier (#14301): a member real in-process and absent from the body. Unmeasured sibling: ctx.submitted, also not marshalled, reads undefined. · dedupe words: dispatch.scope lowered body TypeError; extractHookBody forbidden pattern dispatch; hook body scope not marshalled; lowered handler passes in-process fails sandbox; submitted not marshalled body",
    "carrier: none · noted, not filed. The artifact door refuses a configuration carrying onEnable rather than grafting it. objectstack serve CONFIG grafts it with graftAuthoredRuntimeMembers (packages/cli/src/utils/graft-runtime-hooks.ts), which verify cannot import. If an app's artifact-door tests need onEnable, moving that rule into a package both import is the route. Recorded in PR 22808's Acceptance notes."
    ]
    }

  14. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    ContributorAuthor

    ✅ ACCEPT — PR #22808 at c14e269ed0 (item 7, Part of #22301). Contract review PASS; ready and enqueued

    domain:spec seat 2 (#18549) · marchtian · session session_016njDy8ozy9B9Ns5Y8kAWEK · 2026-10-11T09:06Z · holder of claim 6106527562. Report: 6107272381.

    Checked against the diff and the record, not the report:

    • The door: bootStack(config, { artifact }).

      • It reads the compiled objectstack.json through the runtime's own loadArtifactBundle (with unwrapEnvelope, the same function and options createStandaloneStack uses) and mounts new AppPlugin(bundle) as the app.
      • AppPlugin binds each hook's body ahead of its handler, through the QuickJS body runner, so every handle door runs what the build shipped.
      • Composition (requires, the app's own plugins, profile, datasources) is still read from config.
    • Ruling 6070767186 A holds. The artifact path is the deployment's, so this is not a second boot path. loadCompiledArtifact is a type check, an onEnable check, the loader, a null check and an id compare, with zero re-derived lowering or sandbox semantics (verify: an in-process handle on the booted stack — run a hook, flow, action or validation rule against the REAL engine and assert, so an app never fakes ctx.api again (epic hotcrm#1579, step 5a) #15951 B′).

    • The package graph:

    • Four refusals, each with an ADR-0112 code, thrown before any kernel exists:

      • an unloadable artifact → RESOURCE_NOT_FOUND / 404, ⛔ never a fallback to source;
      • another app's artifact → RESOURCE_CONFLICT / 409;
      • a config carrying onEnable → INVALID_REQUEST / 400 (the graft is cli's);
      • an empty option → INVALID_REQUEST / 400.

      Relative paths resolve against the host root, not process.cwd(), and this is pinned.

    • The envelope pins are real divergences against the real lowering and the real sandbox.

      • A handler writing ctx.dispatch.scope lowers at exit 0 and TypeErrors in the body: the handle gets a SandboxError with no code, REST answers 500, and no row is written. The source boot stores the row.
      • A declared VALIDATION_FAILED / 400 passes through both paths. REST's parsed bodies are equal.
      • A lowerable control behaves identically both ways.
    • Ablations: A1–A5 were predicted and observed. A6 was not measured, and A1's dogfood mutate-leg build output was lost, but the dist preflight proved the marker reached dist. The review judged the evidence sufficient.

    • Semver: @objectstack/verify minor, Clause-②: yes (widening). No other published package moved.

    • Contract review: at-tier PASS 6107414318 on c14e269ed055e8e8ef350e701040cafd14f9cee3.

    • CI on c14e269ed0: 35 runs, 32 success, 3 skipped (Build Docs, Console Pin Gate, Packed-tarball smoke (opt-in)), 0 failure. All required contexts green.

    • Paths: 6 files, +565 / −9. Not governed. First line Part of #22301, no closing keyword. The card stays open for item 1's remaining divergence.

    Routed from this stage: #22810 (filed by this seat, bug, pm:queue, awaiting triage). objectstack build lowers a handler that writes ctx.dispatch.scope (or reads ctx.submitted) at exit 0, although the sandbox marshals neither, so every lowered write TypeErrors and REST answers 500. Note for #22810's dev: a build-side refusal un-lowers this PR's dogfood divergence pin (lbd_stash) and trips its anti-vacuity case. Move that pin to another real divergence in the same PR.

    Acceptance notes (carrier: this card's close-out; none blocking):

    • The bootStack JSDoc and the changeset say serve composes the artifact boot "the same way". That holds for a NON-host configuration only: for a host config, shouldBootWithLibrary is false and serve boots the source module. The PR body carries the qualifier; the JSDoc does not. This is for a later text round.
    • Serve-side, read from source and unmeasured (item 1's territory): mergeBootConfig serves the boot result's plugins whole. So a non-host config's non-instance plugins entries do not reach serve's artifact boot, while the handle mounts them.
    • The onEnable graft (graftAuthoredRuntimeMembers) stays cli's. An artifact-door test that needs onEnable would need that rule moved into a package both import. There is no measured pull.
    • "byte-equal" in the PR body is toEqual on parsed JSON.

    hotcrm: runShippedHook can move onto bootStack(config, { artifact: 'dist/objectstack.json' }) after objectstack build, once a release carries this door.

    Landing: the PR goes ready and auto-merge is enabled now. The landing record follows the merge.


    Generated by Claude Code

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

Metadata

Metadata

Assignees

Labels

area:devpathThe road — create, dev, verify, publish/install, connect an agent, iteratebugSomething isn't workingdomain:specpm:dispatchedpriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions