Observation-class finding, measured while writing the record-view auditing docs page (#9540 / PR #9860). Not a defect claim about correctness — the capability works exactly as designed when it is constructed with options. The gap is reachability from the shipped CLI boot path.
Measurement
Record-view auditing is enabled only by AuditPlugin's constructor:
newAuditPlugin({readAudit: {objects: ['contact','account']}});packages/cli/src/commands/serve.ts:2438-2440 auto-registers the plugin like this:
const{ AuditPlugin }=awaitimport(auditPkg);awaitkernel.use(newAuditPlugin());No options, and no config-derived helper. Compare the sibling six lines above, where SecurityPlugin gets one:
awaitkernel.use(newSecurityPlugin(appSecurityPluginOptions(config)));
appSecurityPluginOptions exists precisely so the CLI boot and @objectstack/verify's bootStack cannot disagree (#7001). There is no appAuditPluginOptions, and grep -rn "readAudit" packages/ examples/ apps/ finds zero call sites outside plugin-audit itself — nothing in the repo ever supplies an audited object list.
⇒ A deployment served with os serve has record-view auditing off and no declared way to turn it on. The capability is reachable only from code that composes the kernel itself.
The half-path, and why it should not just be documented
An app can put new AuditPlugin({ readAudit: { … } }) in its objectstack.config.tsplugins array. That array is consumed at serve.ts:2455, i.e. after the auto-registration, and ObjectKernel.use() (packages/core/src/kernel.ts:194) stores plugins in a name-keyed map:
this.plugins.set(pluginMeta.name,pluginMeta);
Both instances share the name com.objectstack.audit, so the config-declared one overwrites the auto-registered one and the opt-in does take effect. But that is an undeclared last-wins overwrite, not a contract: nothing documents it, no test pins it, and LiteKernel.use()throws on the same input (filed separately). Documenting it would be teaching an accident, so PR #9860's page deliberately does not.
Dispositions
Not ranked — this needs a decision, not a guess.
- Add
appAuditPluginOptions(config) mirroring the security helper, reading the audited object list off the stack config. Most consistent with the existing pattern, and gives os serve and bootStack one shape. - Leave it composition-only and say so in the plugin README and the docs page. Defensible if the intended consumer really is
@objectstack/security-enterprise composing on top — read-audit.ts's header says the policy "belongs to the caller, which in the enterprise packaging is @objectstack/security-enterprise". But the open edition then has a shipped compliance capability its own CLI cannot reach. - Make the auto-registration skip when the config declares its own audit plugin, using the
hasPluginMatching helper already at serve.ts:2739 for exactly this purpose on other plugins. Turns the accidental overwrite into a declared one.
Refs: #9540 / PR #9860 (the docs card that measured it) · #8992 / PR #9515 (the capability) · #7001 (appSecurityPluginOptions).
Observation-class finding, measured while writing the record-view auditing docs page (#9540 / PR #9860). Not a defect claim about correctness — the capability works exactly as designed when it is constructed with options. The gap is reachability from the shipped CLI boot path.
Measurement
Record-view auditing is enabled only by
AuditPlugin's constructor:packages/cli/src/commands/serve.ts:2438-2440auto-registers the plugin like this:No options, and no config-derived helper. Compare the sibling six lines above, where
SecurityPlugingets one:appSecurityPluginOptionsexists precisely so the CLI boot and@objectstack/verify'sbootStackcannot disagree (#7001). There is noappAuditPluginOptions, andgrep -rn "readAudit" packages/ examples/ apps/finds zero call sites outsideplugin-audititself — nothing in the repo ever supplies an audited object list.⇒ A deployment served with
os servehas record-view auditing off and no declared way to turn it on. The capability is reachable only from code that composes the kernel itself.The half-path, and why it should not just be documented
An app can put
new AuditPlugin({ readAudit: { … } })in itsobjectstack.config.tspluginsarray. That array is consumed atserve.ts:2455, i.e. after the auto-registration, andObjectKernel.use()(packages/core/src/kernel.ts:194) stores plugins in a name-keyed map:Both instances share the name
com.objectstack.audit, so the config-declared one overwrites the auto-registered one and the opt-in does take effect. But that is an undeclared last-wins overwrite, not a contract: nothing documents it, no test pins it, andLiteKernel.use()throws on the same input (filed separately). Documenting it would be teaching an accident, so PR #9860's page deliberately does not.Dispositions
Not ranked — this needs a decision, not a guess.
appAuditPluginOptions(config)mirroring the security helper, reading the audited object list off the stack config. Most consistent with the existing pattern, and givesos serveandbootStackone shape.@objectstack/security-enterprisecomposing on top —read-audit.ts's header says the policy "belongs to the caller, which in the enterprise packaging is@objectstack/security-enterprise". But the open edition then has a shipped compliance capability its own CLI cannot reach.hasPluginMatchinghelper already atserve.ts:2739for exactly this purpose on other plugins. Turns the accidental overwrite into a declared one.Refs: #9540 / PR #9860 (the docs card that measured it) · #8992 / PR #9515 (the capability) · #7001 (
appSecurityPluginOptions).