You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
platform-objects' keyed-text-bounds pin cannot reach plugin/service packages — four keyed unbounded text columns in plugin-audit and plugin-security still break MySQL schema-sync #12059
Found while landing route A's last column for #11374 (PR #12058). Filed unassigned.
What
The #11374 route-A rule is "a text-family column that a declared index keys on
must declare a maxLength, because a bound is what lets the column be a key at
all". On MySQL an unbounded keyed text column is emitted TEXT, the ALTER TABLE ... ADD INDEX is refused with ER_BLOB_KEY_WITHOUT_LENGTH, and
the object lands registered-but-broken with its declared index silently absent.
That rule is now enforced by a pin — but the pin lives in @objectstack/platform-objects and enumerates only the objects that package
exports. Platform objects that moved out to plugins under ADR-0029 K2 are
therefore outside it, and four of them declare keyed text columns with no bound:
All four are Field.text({ ... }) with no maxLength key in the declaration.
Evidence, and what is NOT claimed
Declaration-level, measured by walking every *.object.ts outside packages/platform-objects/ and intersecting each object's indexes with its
text-family fields. Each row above was then read by hand to confirm it is a
real Field.text with no maxLength and a real index entry naming it.
The mechanism is measured live on this exact code path, on the same driver,
in PR #12058: sys_import_job.created_by, an unbounded keyed Field.text,
produced
[sql-driver] cannot create index 'idx_sys_import_job_created_by_created_at'
on "sys_import_job" — MySQL refuses a TEXT/BLOB column in a key without a key length
against live MySQL 8.0.46, with the column reading back text / CHARACTER_MAXIMUM_LENGTH 65535 from information_schema and its declared
index absent. Declaring the bound moved it to varchar(255) and the index
appeared.
⛔ What I did not do: run these four objects through a live syncSchema.
They are not reachable from their packages' export surface — plugin-audit's package.json declares only the . export and its root barrel does not
re-export ./objects, so a probe cannot import SysActivity at all. Whoever
picks this up should drive them through the plugin's own registration path
rather than by importing the object, and should treat the counts as unverified
until they do.
Why it matters (declared ≠ enforced)
Two separate failures, and the second is the one worth fixing:
Four declared indexes cannot exist on MySQL, so those objects are
registered-but-broken there — sys_audit_log and sys_activity lose the (object_name, record_id) lookup that the ActivityPointer read path
(ADR-0052 §5) assumes.
The pin is scoped to a package, not to the defect class. This is the
second time that exact shape has bitten: route A's original pin was scoped to identity/ and that is precisely how sys_import_job.created_by in audit/
survived the first pass. PR feat(platform-objects): declare a sourced maxLength on sys_import_job.created_by (#11374 route A, last column) #12058 widened it from identity/ to the whole
of platform-objects (20+ objects → 45, 30+ keyed text columns → 78), but
the package boundary is still a boundary, and objects keep moving across it
under ADR-0029.
Direction (for triage — not prescribing a fix)
Declare a sourced bound on each of the four, in route A's shape (every bound
derived from a named producer, stated so it is vetoable in review). For the
two record_id columns the obvious source is referenced-column transitivity
from the record id they hold, exactly as feat(platform-objects): declare a sourced maxLength on sys_import_job.created_by (#11374 route A, last column) #12058 did for created_by; the
physical id column driver-sql creates is table.string('id').primary(),
knex's varchar(255).
And/or lift the pin so it walks every package that ships platform objects
rather than one package — the class-level fix, and the one that stops this
recurring the next time an object moves.
Related: #11374 (route A), PR #11699 (13 identity columns), PR #12058 (this
card's origin), #11627 (the separate >768-character key class).
Found while landing route A's last column for #11374 (PR #12058). Filed unassigned.
What
The #11374 route-A rule is "a text-family column that a declared index keys on
must declare a
maxLength, because a bound is what lets the column be a key atall". On MySQL an unbounded keyed text column is emitted
TEXT, theALTER TABLE ... ADD INDEXis refused withER_BLOB_KEY_WITHOUT_LENGTH, andthe object lands registered-but-broken with its declared index silently absent.
That rule is now enforced by a pin — but the pin lives in
@objectstack/platform-objectsand enumerates only the objects that packageexports. Platform objects that moved out to plugins under ADR-0029 K2 are
therefore outside it, and four of them declare keyed text columns with no bound:
sys_activityrecord_idpackages/plugins/plugin-audit/src/objects/sys-activity.object.ts:176{ fields: ['object_name', 'record_id'] }(:262)sys_audit_logrecord_idpackages/plugins/plugin-audit/src/objects/sys-audit-log.object.ts:229{ fields: ['object_name', 'record_id'] }(:334)sys_audience_binding_suggestionpackage_idpackages/plugins/plugin-security/src/objects/sys-audience-binding-suggestion.object.ts:44(package_id, permission_set_name, anchor)sys_audience_binding_suggestionpermission_set_nameAll four are
Field.text({ ... })with nomaxLengthkey in the declaration.Evidence, and what is NOT claimed
Declaration-level, measured by walking every
*.object.tsoutsidepackages/platform-objects/and intersecting each object'sindexeswith itstext-family fields. Each row above was then read by hand to confirm it is a
real
Field.textwith nomaxLengthand a real index entry naming it.The mechanism is measured live on this exact code path, on the same driver,
in PR #12058:
sys_import_job.created_by, an unbounded keyedField.text,produced
against live MySQL 8.0.46, with the column reading back
text/CHARACTER_MAXIMUM_LENGTH65535 frominformation_schemaand its declaredindex absent. Declaring the bound moved it to
varchar(255)and the indexappeared.
⛔ What I did not do: run these four objects through a live
syncSchema.They are not reachable from their packages' export surface —
plugin-audit'spackage.jsondeclares only the.export and its root barrel does notre-export
./objects, so a probe cannot importSysActivityat all. Whoeverpicks this up should drive them through the plugin's own registration path
rather than by importing the object, and should treat the counts as unverified
until they do.
Why it matters (declared ≠ enforced)
Two separate failures, and the second is the one worth fixing:
registered-but-broken there —
sys_audit_logandsys_activitylose the(object_name, record_id)lookup that the ActivityPointer read path(ADR-0052 §5) assumes.
second time that exact shape has bitten: route A's original pin was scoped to
identity/and that is precisely howsys_import_job.created_byinaudit/survived the first pass. PR feat(platform-objects): declare a sourced maxLength on sys_import_job.created_by (#11374 route A, last column) #12058 widened it from
identity/to the wholeof
platform-objects(20+ objects → 45, 30+ keyed text columns → 78), butthe package boundary is still a boundary, and objects keep moving across it
under ADR-0029.
Direction (for triage — not prescribing a fix)
derived from a named producer, stated so it is vetoable in review). For the
two
record_idcolumns the obvious source is referenced-column transitivityfrom the record id they hold, exactly as feat(platform-objects): declare a sourced maxLength on sys_import_job.created_by (#11374 route A, last column) #12058 did for
created_by; thephysical
idcolumndriver-sqlcreates istable.string('id').primary(),knex's
varchar(255).rather than one package — the class-level fix, and the one that stops this
recurring the next time an object moves.
Related: #11374 (route A), PR #11699 (13 identity columns), PR #12058 (this
card's origin), #11627 (the separate >768-character key class).