Repository navigation
feat(agent): audit writes made from inside a smart action - #426
Conversation
The audit trail registered its hooks with the public add_hook, which sits above the action layer: a create/update/delete made from an action's execute through context.datasource never reached them, so only the action row was recorded. Add an internal hook layer below `action` (parity with agent-nodejs) and register the audit hooks there. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Coverage Impact This PR will not change total coverage. Modified Files with Diff Coverage (3)
🛟 Help
|
Forest Validator · Review: ✅ clean
|
matthv
left a comment
There was a problem hiding this comment.
Approved after /validator run 426-20261104103741-matthv: nothing blocking posted, 0 preferential or code style, 0 suppressed below all.
# [1.48.0](v1.47.0...v1.48.0) (2026-11-04) ### Features * **agent:** audit writes made from inside a smart action ([#426](#426)) ([13c26c7](13c26c7))
|
🎉 This PR is included in version 1.48.0 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |

Why
Raised in Slack (Audit Trail v2, Swan demo): a create done inside a smart action via
context.datasource.get_collection(...).createis never audited as acreaterow on the Ruby agent — only the action invocation is.Root cause:
AuditTrail::Captureregistered its hooks with the publicadd_hook, i.e. on thehooklayer, which sits aboveactionin the decorators stack. Writes issued from an action'sexecutego down from the action layer and never cross it.agent-nodejs already handles this with an
internalHooklayer belowaction(decorators-stack.ts,addInternalHook). This PR ports that.What
DecoratorsStack: new@internal_hooklayer (aHookCollectionDecorator) placed betweenchartandaction.CollectionCustomizer#add_internal_hook: same signature asadd_hook, registers on that layer.AuditTrail::Capture: registers its Before/After Create/Update/Delete hooks withadd_internal_hook.AUDIT_TRAIL.md: documents that writes from inside an action are audited under the action's correlation key.Behavioral changes to verify manually
create/update/deleterows: writes done inside smart actions through Forest are now audited, under the samecorrelationKeyas theactionrow.writedecorator (e.g. a create with nested relation data that the write layer splits) now traverse the internal layer too, so they produce one audit row per underlying collection write. Same as Node.add_hookhandlers, and a user after-hook raising can no longer drop the audit confirmation.Capturestill reads columns from the top-of-stack schema, while the internal layer sees pre-rename names. A collection with arename_fieldcustomization could record those columns asnil. Out of scope here.Tests
collection_customizer_spec: an action creating throughcontext.datasourcefiresadd_internal_hookand notadd_hook; a direct create fires both. Verified the first one fails whenadd_internal_hookis pointed at the public layer.🤖 Generated with Claude Code
Note
Add internal hook layer so writes inside smart actions are audited
HookCollectionDecoratorlayer inDecoratorsStack#initialize, placed below the smart action decorator, so writes made through Forest inside a smart action traverse it (decorators_stack.rb)CollectionCustomizer#add_internal_hook, which mirrorsadd_hookbut registers on the internal layer (collection_customizer.rb)Captureto register via the internal layer, so action-triggered writes are recorded under the action'scorrelationKeyin the audit trail (capture.rb)add_hook) no longer fire for writes initiated inside a smart action; only internal hooks run for those, and tests in collection_customizer_spec.rb assert this splitMacroscope summarized de9440a.