Skip to content

The composed-host-stack plan sees 8 of ~80 control-plane tables: objectstack#12952's start()-registration residue is the dominant case, not a zero-instance one #13028

Description

@hotlong

Follow-up to objectstack#12938 / PR objectstack#12952, found while running the
consumer acceptance that PR named (objectstack-ai/cloud#1710).

objectstack#12952 states its residue plainly:

a host plugin that registers its objects in start() rather than init() is
invisible to this composition — its tables stay out of the plan.

and its ACCEPT adjudicated Option A partly on the ground that the residue had
"zero measured instances". Measured against the deployment the fix was written
for, the residue is not the exception — it is almost the whole set.

Measurement

ObjectStack Cloud's migrate-control-db.yml, plane=staging, apply=false, against
the real Neon staging control plane, framework pinned at 15d55fb2430f. Run:
objectstack-ai/cloud actions run 33182129326. The composition worked, and said
so:

  Database: postgresql://…neon.tech/neondb
  Examined 8 managed table(s).
      Composed the host stack from objectstack.config.ts: 36 plugin(s), registered
      for their declarations only (init runs, start does not — a plan writes nothing).

composition.hostConfigLoaded is true. 36 host plugins were composed. The
resulting managed set is 8 tables, and every one of them belongs to the
messaging / notification family:

sys_http_delivery            sys_notification_preference
sys_inbox_message            sys_notification_receipt
sys_notification             sys_notification_subscription
sys_notification_delivery    sys_notification_template

The control plane carries roughly eighty sys_* tables. sys_position and
sys_permission_set — the two objectstack#12938 named by hand as the proof that
the old five-table set was wrong — are still not in the diffed set, even
though com.objectstack.security is among the 36 composed plugins. Neither are
sys_organization, sys_environment, sys_domain, sys_user or anything else
createCloudStack() brings.

So one plugin out of thirty-six registers its objects in init(). The other
thirty-five register in start(), and start() is exactly what the composition
suppresses.

Why this matters more than the count

The before/after reads like progress — five tables became eight — and every
consumer-side signal the fix emits is green: the config loaded, the composition
printed a healthy note, the plan produced findings and named real tables. A
coverage gate written against "more than the artifact-less baseline of five"
passes. Cloud's gate (cloud#1710) passes. Nothing reports that seventy-plus
tables are still unexamined, and Physical schema is in sync with metadata is
one composed plugin away from printing again.

That is the same failure shape objectstack#12938 was filed for, one notch
narrower and now wearing a green composition note.

What a fix has to decide

Not adjudicated here; both axes are real:

  • Is init()-registration the contract? If declaring objects in init() is
    binding, thirty-five plugins across cloud and this repo are in breach and the
    fix is a lint plus a migration, not a CLI change. That is the contract-first
    reading and it is the one this repo's own doctrine points at.
  • Or does the composition need to reach further? objectstack#12952 measured
    full-start composition as unsafe (14 writes during a documented dry run), so
    "just run start()" is already ruled out. Something narrower — a declared
    registration phase the kernel can run without side effects — would be a spec
    change, not a CLI change.

Either way the composition should say what it could not see rather than report a
number that reads as coverage. A count of composed plugins next to a count of
plugins that contributed objects would have made this visible in the run that
found it.

Filed without an assignee.

Metadata

Metadata

Assignees

Labels

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions