Skip to content

[finding] os migrate plan on examples/app-crm runs the app's onEnable hook, which reads sys_position / sys_permission_set the plan never declares: 6 DATABASE_ERROR + 6 WARN lines on every plan #21054

Description

@objectstack-fleet

Filing gate: ① a defect with a named producer, examples/app-crm/objectstack.config.ts onEnable → registerCrmPositionBindings (examples/app-crm/src/security/bind-position-sets.ts:111), which hooks kernel:bootstrapped. Finding class (a). reach: the public CLI door os migrate plan --database-url file:X on examples/app-crm, measured at origin/main b253fadfb by #20821's dev (os-dev-report on #20821, out_of_scope_findings[0]).

Filed by the domain:engine execution seat 2 (seat post #20966, session_01Ujdtvqs7ree7WyQmEDwEnG, os-litant). ⛔ Filed bare: routing and grading belong to triage. ⛔ Not a claim.

What happens

X is the file os migrate apply --yes has just migrated, so every table the plan manages exists. The plan's run then prints:

  • stderr: 6 [sql-driver] DATABASE_ERROR lines (sys_position ×3, sys_permission_set ×3) and 2 "Paged read … is NOT deterministic" warns;
  • stdout: 6 WARN [crm] position binding lookup failed lines and 3 "skipped (row missing)" lines.

On an absent file, the same lines come on top of #20821's 6 deferred-DDL lines.

  • The plan's declaration boot runs the app's runtime.onEnable; stdout shows "Executing runtime.onEnable". The hook then reads plugin-security tables.
  • The plan's composition does not declare those tables. Stdout reads "Examined 15 managed table(s)", and neither table is in the list. So apply never creates them, and the hook's reads are refused on every plan.
  • These lines are not a deferred-DDL artefact. They print on a fully migrated file.

Scope for whoever takes it (⛔ not a ruling)

  • Decide which side is wrong:
    • (a) a declaration boot (migrate plan / apply) should not run host onEnable hooks that read data; or
    • (b) the plan's composition should include the plugin-security objects the app's own hook depends on, so apply creates them; or
    • (c) the example's hook should not assume those tables in a composition that does not carry plugin-security.
  • Measure first which composition examples/app-crm is meant to boot with under os migrate.
  • Pin: os migrate plan on a migrated app-crm file prints 0 DATABASE_ERROR lines and 0 position binding lookup failed lines.

Dedupe

mcp__github__search_issues, repo-scoped, open and closed: "migrate plan app-crm onEnable hook sys_position sys_permission_set DATABASE_ERROR position binding lookup failed" gave 2 hits, neither this door. #14846 (closed) is a plugin-auth test harness; #3728 (closed) is unique-index DDL visibility.

Activity

  1. objectstack-fleet commented on Oct 1, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: first grade — bug · priority:p3 · domain:cli · area:devpath · pm:queue. Direction: os migrate plan's declaration boot does not run app lifecycle hooks

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

    Why p3. It is noise, not damage: 6 DATABASE_ERROR and 6 WARN lines on every plan of examples/app-crm. But a plan that runs an app's onEnable side effects is the wrong shape for a read-only planning boot.

    Direction:


    Generated by Claude Code

  2. objectstack-fleet commented on Oct 1, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1 of the domain:cli seat's session session_01VvcEokUG1tvVxkceYfR5XB (batch 3): priority:p3, triage's first grade 5924795251, filling a free slot under the maintainer's 「并发保持3」
    Session: session_01VvcEokUG1tvVxkceYfR5XB
    Account: huangyiirene
    Branch: claude/issue-21054-plan-no-app-hooks
    Worktree: objectstack-issue-21054
    Domain: domain:cli
    Seat: domain:cli#1
    File surface:

    • The plan's declaration boot: packages/cli/src/utils/schema-migrate.ts (the boot around :355–:395 on 9c8b65aa23) and packages/cli/src/utils/schema-migration-plugins.ts (composeForDeclarations, :244, and its phase handling).
      • The declaration boot does not fire an app's runtime.onEnable or kernel:bootstrapped / kernel:listening app hooks.
      • It is fixed at that one point, for every app.
    • packages/runtime/src/app-plugin.ts: only if the onEnable executor itself must read a declaration-boot signal to stand down. Otherwise it is read only.
    • Pins beside the CLI's existing declaration-boot suites (schema-migration-plugins.declaration-boot-write-guard.test.ts, schema-migrate.host-composition.integration.test.ts):
      • the plan on examples/app-crm prints 0 DATABASE_ERROR lines and 0 position-binding warnings, on a migrated file and on an absent one;
      • a hook registered from init() is not fired on a plan (the control: apply's confirmed work after the boot is unchanged).
    • One .changeset/21054-*.md for @objectstack/cli (and @objectstack/runtime if touched).
    • ⛔ Not here: teaching app-crm's hook to guard its reads as the fix (examples/** read only). Also not here: the deferred-DDL reads ([finding] os migrate plan against a database that does not exist yet prints 6 [sql-driver] DATABASE_ERROR … no such table warnings: the dry run defers the DDL, then its boot reads sys_metadata, sys_metadata_activation and sys_migration anyway #20821, closed).
    • Stop clause: if the plan needs an app hook for its declarations, stop and report which one, per the grade.
      (stop on a breach outside these; explain in the report)
      Container & model: M, mode:subagent, model: opus (dispatch-gates --tier: no path-derived mandate, floor sonnet · default opus · ceiling fable).
      Clause-②: no
      Thread-read: 5924795251
      Serial constraints cleared: #20821 (the same command, domain:engine) is closed. No open PR touches packages/cli/src/commands/migrate/**, packages/cli/src/utils/schema-migrat*, packages/runtime/src/app-plugin.ts or examples/app-crm/src/security/** (the file lists of all open PRs were read in this act, main 9c8b65aa23). In flight on this seat: #21002 (rest-server.ts published door, runtime domains/meta.ts) and #20800 (a merge of main into PR #21038). Both are disjoint. area:devpath: #20800 is in flight on a disjoint surface.

    Generated by Claude Code

  3. objectstack-fleet commented on Oct 1, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 21054,
      "status": "done",
      "branch": "claude/issue-21054-plan-no-app-hooks",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/21138",
      "session": "session_01VvcEokUG1tvVxkceYfR5XB — mode:subagent, the PM's session id; this run is told apart by the branch",
      "premise_still_valid": true,
      "summary": "Reproduced the premise at origin/main 9c8b65aa23. On examples/app-crm, every plan and apply printed 6 DATABASE_ERROR lines (sys_position x3, sys_permission_set x3), 6 \"position binding lookup failed\" warnings and \"Executing runtime.onEnable\", on an absent file and on a file apply had just migrated. Host code reaches a declaration boot through two doors, and the fix closes each one where it enters, for every app. (1) The config's onEnable: AppPlugin gains a skipOnEnable option, beside skipSeedData. The migrate composition sets it on the app it builds, and start() withholds the hook, logs that it did, and exposes onEnableWithheld. A compiled artifact cannot carry onEnable, and a host AppPlugin is already wrapped. (2) A host plugin's init(): composeForDeclarations now forwards init with a context whose hook() does not register kernel:bootstrapped or kernel:listening, the two phases IPluginLifecycleEvents defines as work after registration ends. kernel:ready host hooks still run, and the write guard still refuses their row writes; kernel:shutdown and data hooks register as before. This repo's own plugins are untouched, so the value-shape gate announcement still prints. No in-repo plugin registers a post-declaration hook from init(): all 7 sites are in start(). So no hook the plan needs for its declarations was cut, and the stop clause did not fire. The plan's notes and --json composition.notes carry one line naming what was withheld. After the fix, app-crm prints 0 errors and 0 warnings on all 5 runs. The table list, the pending DDL, the drift and the rest of --json are identical. The triage direction reverses #13332's claim-time preference (keep log-only hooks running) for these two phases only. The existing pins were inverted in place, and the guard's phase-agnostic property stays pinned by an unwrapped writer.",
      "tests": "Repro: node packages/cli/bin/run.js migrate plan|apply in examples/app-crm, at base 9c8b65aa23 (dist built) and at fix af9ac5ded3 (runtime and cli rebuilt); table in repro. Suites at 3781713631 (pre-merge): runtime vitest --project local, 297 files, 4252 passed, 5 skipped. cli --project unit, 240 files, 3422 passed. cli --project integration over 11 migrate-related files (duplicates, meta.stored-flow-resolution, plan.deferred-reads, platform-migrations-arming, schema-migrate deferred-ddl/host-composition/integration/readonly-probe/teardown, write-guard, unmanaged-tables): 59 passed. After merging origin/main: at 5bd79b1b3c, runtime app-plugin.test.ts 35/35; cli schema-migration-plugins.test.ts 32/32; write-guard, host-composition and plan.deferred-reads 36/36; runtime and cli typecheck (with check:test-typecheck) exit 0. At 6d4ef7c9aa, host-composition 14/14 and cli typecheck exit 0. HEAD dc1c40ec39 differs by one comment line. New and changed pins: runtime skipOnEnable (withheld, logged, reported; bundle.default; no onEnable means nothing withheld). cli unit: the init context declines the 2 phases and forwards the rest; the composed app carries skipOnEnable. Write-guard file, real ObjectKernel: a POSITIVE CONTROL; THE FIX (host fires only kernel:ready, teardown kept, an unwrapped platform plugin keeps all 3 phases in the same boot); a host registering later from kernel:ready is declined; a new PHASE-AGNOSTIC guard pin with an unwrapped writer. Host-composition #21054 block: app-crm-shaped fixture, served-composition POSITIVE CONTROL, apply flush and coverage CONTROL, plan on migrated and absent files with 0 DATABASE_ERROR. Ablations, fix committed first, each through scripts/ablation-replace.mjs (anchor 1 to 0, blob changed; restore blob == HEAD, git diff HEAD empty). A1: POST_DECLARATION_PHASES emptied (blob 2f397895 to 1c1f8d3f). Unit red 2/32; integration red 9/34 (write-guard DEFECT, FIX and embedder; both #21054 kernel cases; #13332 FIX and R1; both #21054 plan cases, failing on DATABASE_ERROR sys_position from the host hook). Positive controls, the phase-agnostic pin and the apply control stayed green. A2: skipOnEnable true to false at the composition (blob 2f397895 to 7f29e92e). Red: unit compose case 1/32; both #21054 plan cases 2/34 (DATABASE_ERROR on sys_position and sys_permission_set from onEnable). A3: AppPlugin executor branch neutralised with planted marker __OS21054_ABLATION__ (blob b5996d12 to ab2c4acb); runtime rebuilt, ablation-dist-preflight found the marker in 2 built files (index.js, index.cjs). Red: runtime 2/35, cli unit 1/32, cli integration 2/34. Restore leg: rebuilt; preflight --absent found the marker in 0 of 6 built files, tree clean; runtime 35/35, unit 32/32, integration 34/34 green. Lint, a proven narrowing at dc1c40ec39: (1) population: eslint --print-config resolves a config for all 7 touched TS files, 5-6 rules each, no parserOptions.project; (2) count: --format json reports 7 files, 0 errors, 0 warnings; (3) invariance: eslint.config.mjs enables no type-aware linting, so this diff cannot move the verdict on any untouched file. Whole-repo pnpm lint left to CI.",
      "mcp_calls": "0 — no MCP GitHub tool was called",
      "api_writes": "3 — all through the fleet-write relay (scripts/pm), each a POST /repos/objectstack-ai/objectstack/dispatches that the board workflow executed as objectstack-fleet[bot]: (1) pr_create, POST /repos/objectstack-ai/objectstack/pulls, draft, relay run 36842002037, body read back identical (11632 bytes); (2) label-write --assign huangyiirene, POST /repos/objectstack-ai/objectstack/issues/21138/assignees, relay run 36842086967, read back matched; (3) this os-dev-report, POST /repos/objectstack-ai/objectstack/issues/21054/comments. git push (branch marker plus 7 pushes) is not a REST write.",
      "open_questions": [],
      "out_of_scope_findings": [],
      "gates": "At dc1c40ec39, node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands derived 64 commands (same list at 5bd79b1b3c, 6d4ef7c9aa and dc1c40ec39). All 64 ran with exit codes recorded before any pipe, and all 64 exited 0. --ran: \"64 derived, 64 run, 0 NOT-MEASURED, 0 UNRUN\". Quoted verdicts: check-nul-bytes \"OK (scanned 9738 text file(s) ... no raw ASCII control bytes)\"; check-test-source-alias \"OK — 73 packages with tests scanned\"; check-i18n-coverage \"OK (13 config(s), 621 baselined untranslated string(s), none new)\"; check:cross-package-test-inputs \"OK: 30 package(s) read outside themselves, all declared\". The first union, at 5bd79b1b3c, had check:dual-build-cjs-loads and check:i18n-coverage at exit 3 (PREREQUISITE NOT MET: 9 packages had no dist); they were built. It also had check:test-source-alias at exit 1, a real finding in the new test (a dynamic import of @objectstack/runtime inside a test body), fixed in 6d4ef7c9aa. CI at dc1c40ec39, read once at report time: 31 check runs, 10 success, 3 skipped, 18 in_progress, 0 failure.",
      "deviations": [
        "packages/runtime/src/app-plugin.ts was changed under the claim's condition. The onEnable executor must read the signal: it alone resolves the hook owner (bundle.default before the bundle). Stripping onEnable off a copy at the CLI would re-state that rule at the call site, and the boot would then log \"No runtime.onEnable function found\" about an app that has one. Precedent: the existing skipSeedData option for the same commands. The changeset covers @objectstack/runtime as a patch.",
        "The app-crm pin uses an app-crm-shaped fixture inside the test's tempdir, not examples/app-crm itself. A cli test reading examples/** is a cross-package test input, and declaring it would need edits to scripts/cross-package-test-inputs.mjs and turbo.json, outside the claim's file surface. The real app-crm is measured by the CLI repro (see repro).",
        "Existing #13332 pins changed in place, per the triage direction 5924795251. Host log-only and write hooks on kernel:bootstrapped/kernel:listening no longer run on a declaration boot (refusal counts 6 to 4 and 4 to 2 in the host-composition #13332 cases). Three write-guard residue pins moved their writer from bootstrapped/listening to kernel:ready, because those cases are about the guard, not the phase. A new pin keeps the guard's phase-agnostic property with an unwrapped writer.",
        "origin/main merged once (merge commit 5bd79b1b3c, main tip a11faeecb3; no conflict, no lockfile change); the 9 packages it touched were rebuilt. The full runtime and cli-unit suites ran at pre-merge 3781713631; after the merge, only the touched suites plus typecheck ran. The incoming commits touch cli and runtime but none of the files in this diff.",
        "The cli integration tier was not run in full: 11 migrate-related files ran, and the rest is declared to CI. Nightly-tier e2e files that spawn migrate (migrate-plan-exits, json-stdout-purity, migrate-apply-refuses-before-ddl, migrate-exit-code, migrate-unloadable-host-config-exit, config-miss-stdout-purity) are NOT MEASURED locally (reason: nightly tier, OS_TEST_TIERS). Plan --json stdout parsing on app-crm was measured in the repro.",
        "The gate union was re-run on the final head. The run at 5bd79b1b3c was superseded. A second loop at 6d4ef7c9aa was stopped by its recorded PID after one command, because a one-line comment fix (dc1c40ec39) followed; the reported union is the full run at dc1c40ec39."
      ],
      "files_changed": [
        ".changeset/21054-plan-runs-no-app-hooks.md",
        "packages/cli/src/utils/schema-migrate.ts",
        "packages/cli/src/utils/schema-migration-plugins.ts",
        "packages/cli/src/utils/schema-migration-plugins.test.ts",
        "packages/cli/src/utils/schema-migration-plugins.declaration-boot-write-guard.test.ts",
        "packages/cli/src/utils/schema-migrate.host-composition.integration.test.ts",
        "packages/runtime/src/app-plugin.ts",
        "packages/runtime/src/app-plugin.test.ts"
      ],
      "repro": {
        "tree": "examples/app-crm, no dist artifact; node packages/cli/bin/run.js (dist); base origin/main 9c8b65aa23 built, fix af9ac5ded3 with runtime and cli rebuilt",
        "before": {
          "plan_absent": {
            "database_error_lines": 6,
            "position_binding_warnings": 6,
            "row_missing_warnings": 3,
            "paged_read_warns": 2,
            "onEnable_executed": true,
            "exit": 0
          },
          "plan_absent_json": {
            "database_error_lines": 6,
            "position_binding_warnings": 6,
            "onEnable_executed": true,
            "exit": 0
          },
          "apply_yes": {
            "database_error_lines": 6,
            "position_binding_warnings": 6,
            "onEnable_executed": true,
            "exit": 0
          },
          "plan_migrated": {
            "database_error_lines": 6,
            "position_binding_warnings": 6,
            "row_missing_warnings": 3,
            "paged_read_warns": 2,
            "onEnable_executed": true,
            "exit": 0
          },
          "plan_migrated_json": {
            "database_error_lines": 6,
            "position_binding_warnings": 6,
            "onEnable_executed": true,
            "exit": 0
          }
        },
        "after": {
          "plan_absent": {
            "database_error_lines": 0,
            "position_binding_warnings": 0,
            "row_missing_warnings": 0,
            "paged_read_warns": 0,
            "onEnable_executed": false,
            "exit": 0
          },
          "plan_absent_json": {
            "database_error_lines": 0,
            "position_binding_warnings": 0,
            "onEnable_executed": false,
            "exit": 0
          },
          "apply_yes": {
            "database_error_lines": 0,
            "position_binding_warnings": 0,
            "onEnable_executed": false,
            "exit": 0
          },
          "plan_migrated": {
            "database_error_lines": 0,
            "position_binding_warnings": 0,
            "row_missing_warnings": 0,
            "paged_read_warns": 0,
            "onEnable_executed": false,
            "exit": 0
          },
          "plan_migrated_json": {
            "database_error_lines": 0,
            "position_binding_warnings": 0,
            "onEnable_executed": false,
            "exit": 0
          }
        },
        "stderr_lines": "base 8 on each plan (6 DATABASE_ERROR plus 2 Paged-read warns); fix 0",
        "plan_output_identical": "Yes, apart from one added notes line. Non-log stdout diff = 1 line added, 0 removed, for plan absent, plan migrated and apply. --json is identical except composition.notes (2 to 3 entries): pending 15/15 (absent) and 0/0 (migrated), total 0/0, managedTables 15/15. \"Examined 15 managed table(s)\" on both. The absent file is not created on either side."
      }
    }

    Generated by Claude Code

  4. objectstack-fleet commented on Oct 1, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT: PR #21138 at afc44ba5 (os migrate plan / apply run no app onEnable and no host post-declaration hooks on their declaration boot)

    domain:cli seat · session_01VvcEokUG1tvVxkceYfR5XB · 2026-10-01T10:03Z

    • Contract review of record: 5929160258 on the PR, CONTRACT_REVIEW_TIER, head afc44ba5, PASS. It succeeds the FAIL 5928867906 on dc1c40ec, which failed on ② alone.
    • What the records found:
    • The ② correction, in this window:
      • AppPlugin is exported from @objectstack/runtime's root, and gains an optional constructor option and a public getter. Under WHICH LEVEL and the in-class precedents (skipSeedData, securityMetadataRegistrar), that is minor with Clause-②: yes (widening).
      • Commit afc44ba5 re-grades the changeset; @objectstack/cli stays patch.
      • The seat sent the matching PR body patch itself (line 2 and a ## Release grading section), because os-dev.md reserves post-create body edits for the seat. The read-back was identical.
    • Seat verification on adoption:
      • git diff dc1c40ec afc44ba5 is the changeset alone.
      • The export at packages/runtime/src/index.ts:61 and the minor grading of the two sibling options (33a5ff499e) were checked.
      • Checks on afc44ba5: 34 names, 29 success, 5 skipped, 0 red.
      • git merge-tree against origin/main (e952cff578) is clean.
      • check-governed-merges --pr 21138: NOT governed, +913 / −60.
    • Checklist:
      • Draft, base main, first line Fixes #21054, Clause-②: yes (widening).
      • 8 files: packages/cli/src/utils/schema-migrat* with their pins, packages/runtime/src/app-plugin.ts with its pin (under the claim's condition, which the record judged met), and one changeset.
      • examples/** untouched.
    • Out-of-scope findings: the dev reported none. The record's ③ notes carry no carrier, so they stay as Acceptance notes and nothing is filed:
      • POST_DECLARATION_PHASES is a list; a fourth post-declaration phase would need adding to it.
      • A host that registers through getKernel() is outside the composition's reach.
      • The advisory docs-drift pages were not re-read.
    • Next: readied and armed in this act. At the merge, the Fixes closes this card, and the seat removes pm:dispatched.

    Generated by Claude Code

  5. objectstack-fleet commented on Oct 1, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed: PR #21138 → f20f669e17 (os migrate plan / apply run no app onEnable and no host post-declaration hooks). The card is closed by the Fixes, and the seat removes pm:dispatched

    domain:cli seat · session_01VvcEokUG1tvVxkceYfR5XB · 2026-10-01T10:25Z

    • Landing reading: f20f669e17 is on origin/main as a single-parent squash, 8 files, +913 / −60. Its diff equals the PR's net diff at head afc44ba5 line for line, once the index and hunk-header lines are set aside.
    • Content check, parent against squash:
      • skipOnEnable in packages/runtime/src/app-plugin.ts: 0 lines, then 8.
      • POST_DECLARATION_PHASES in packages/cli/src/utils/schema-migration-plugins.ts: 0, then 6.
    • Release input: .changeset/21054-plan-runs-no-app-hooks.md lands with @objectstack/runtime minor, @objectstack/cli patch and Clause-②: yes (widening), as the PASS record 5929160258 confirmed.
    • State: the merge closed this card as completed. pm:dispatched is removed in the same act as this note. domain:cli, area:devpath, bug and priority:p3 stay.

    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:clipriority:p3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions