Skip to content

Recording disclosures

To record AI involvement from code, call ai_disclosure.recorder, which implements DisclosureRecorderInterface. That interface is the only class a calling module needs to know.

suggest()

suggest() leaves a non-binding suggestion for the editor to Accept or Dismiss on the entity form. It never changes the field.

\Drupal::service('ai_disclosure.recorder')->suggest(
  $entity,
  'ai_partly_assisted',
  'my_module',
  'Three paragraphs were rewritten by the assistant.',
);

The entity must already be saved, and a second call from the same source on the same entity and language replaces the first one.

apply() and applyProfile()

apply() and applyProfile() write the disclosure directly, for headless pipelines and cron. Both refuse to downgrade: the write happens only when the requested grade's severity is strictly higher than the entity's currently effective grade. Both set the human review flag to FALSE, because an automated caller cannot attest that a person reviewed anything.

\Drupal::service('ai_disclosure.recorder')->applyProfile(
  $entity,
  'machine_translation',
  'my_module',
);

From inside hook_entity_presave(), pass $save = FALSE. The recorder then changes the field values on the entity object and leaves persistence to the save cycle already running; calling $entity->save() there would recurse:

\Drupal::service('ai_disclosure.recorder')->apply(
  $entity,
  'ai_translated',
  'my_module',
  NULL,
  FALSE,
);

What a binding write leaves behind

A write that actually happens is recorded in the same ai_disclosure_suggestion table suggest() writes to, as a row with status accepted carrying the $source and the $note. It is provenance for a decision already made, not a proposal: the editor's pending callout and the report's pending-suggestion count both look at pending rows only, and the note is never shown to a reader. applyProfile() records the grade the profile resolves to; which profile it was is on the field itself.

Nothing is recorded when the downgrade rule refuses the write, and nothing is recorded when the write happens during an entity's first insert, because the row is keyed by an entity ID that does not exist yet.

Errors never break the caller's save

An unknown grade, an unknown profile or a bundle without the field never breaks the caller's save: they log a warning on the ai_disclosure channel and return. The one exception is suggest() on an unsaved entity, which throws \InvalidArgumentException, because there is no ID to key the suggestion row on.

The $source argument

$source is the calling module's machine name, for example ai_automators or ai_translate. Every recorder call stores it, so the ai_disclosure_suggestion table is where a site answers "which module said this, and why". The compliance report does not expose that column yet - it reports on the field, not on this table - so a source that invents its own naming spoils the record for whoever queries it.