Skip to content

feat: add filter hooks so taxonomy-owning plugins can enrich AI tool descriptions and refuse AI-supplied values#2154

Merged
chubes4 merged 1 commit into
mainfrom
feat-2152-taxonomy-filter
May 21, 2026
Merged

feat: add filter hooks so taxonomy-owning plugins can enrich AI tool descriptions and refuse AI-supplied values#2154
chubes4 merged 1 commit into
mainfrom
feat-2152-taxonomy-filter

Conversation

@chubes4
Copy link
Copy Markdown
Member

@chubes4 chubes4 commented May 21, 2026

Summary

Adds two filter hooks to TaxonomyHandler so plugins that own a taxonomy can attach domain semantics — enrich the AI tool description, add enum constraints, refuse AI-supplied values — without polluting Data Machine core with any vendor-specific taxonomy knowledge.

Closes #2152.

What changed

inc/Core/WordPress/TaxonomyHandler.php

New filter: datamachine_taxonomy_tool_parameter

Fires inside getTaxonomyToolParameters() after the auto-generated description is built, before the param def is added to the returned array. Lets a taxonomy-owning plugin:

  • Replace the generic auto-description with domain context.
  • Add an enum constraint to lock the AI to a fixed term list.
  • Mark the field required.
  • Mutate type / items if needed.

Signature: apply_filters( 'datamachine_taxonomy_tool_parameter', array $param_def, WP_Taxonomy $taxonomy, array $handler_config, ?string $post_type )

New filter: datamachine_taxonomy_assign_value

Fires inside assignTaxonomy() before processTerms() runs. Lets a taxonomy-owning plugin:

  • Sanity-check the AI's pick against domain rules.
  • Coerce the value (e.g. AI said "Tucson, AZ" → owner returns "Tucson" to hit the existing canonical term).
  • Refuse assignment entirely by returning '' / null / array()assignTaxonomy() then returns a clean success result with term_count: 0 and no terms are created. No error raised, no junk terms.

Signature: apply_filters( 'datamachine_taxonomy_assign_value', mixed $taxonomy_value, string $taxonomy_name, int $post_id )

Both filters are documented in the method docblocks with inline usage examples that reference a generic region taxonomy — not any specific Extra Chill taxonomy.

tests/Unit/Core/WordPress/TaxonomyHandlerFiltersTest.php

New test file with 5 cases covering the contract:

  1. Default tool parameter output is unchanged when no filter is hooked.
  2. datamachine_taxonomy_tool_parameter can replace description and add enum, and receives full context (taxonomy, handler_config, post_type).
  3. Default assignTaxonomy() creates terms when no filter is hooked.
  4. datamachine_taxonomy_assign_value returning '' refuses creation silently (no term, no error).
  5. datamachine_taxonomy_assign_value can coerce the value to a different term.

The test registers a generic dm_test_region taxonomy in set_up() and tears it down in tear_down() — no specific Extra Chill taxonomy name appears.

Layer purity check

```
$ grep -E 'location|venue|artist|festival' inc/Core/WordPress/TaxonomyHandler.php
$ echo exit=$?
exit=1
```

Zero matches. TaxonomyHandler remains generic.

Default behavior unchanged

When no filter is hooked:

  • getTaxonomyToolParameters() returns the exact same shape as before (verified by test_default_tool_parameter_is_unchanged_when_no_filter_hooked).
  • assignTaxonomy() creates terms exactly as before (verified by test_default_assignment_creates_term_when_no_filter_hooked).

Test output

homeboy test data-machine1325 passed, 3 skipped, 1 failed.

The single failure (ContentActionHandlersTest::test_resolved_pending_action_fires_hook_once) is pre-existing on main and unrelated to this PR — confirmed by running the suite against the base branch.

All 5 new TaxonomyHandlerFiltersTest cases pass. Verified the tests are meaningful by running them against the unfiltered baseline (where the implementation hooks don't exist yet) — 3 of the 5 tests fail there as expected, proving the filters are doing real work.

Out of scope

This PR adds the hooks but does not add any consumers for them. The downstream extrachill-events work (using these hooks so location is derived from venue, never AI-decided) is Extra-Chill/extrachill-events#98 and ships in a separate PR.

…descriptions and refuse AI-supplied values

TaxonomyHandler now fires two filters that let the plugin that owns a
taxonomy attach domain semantics without polluting Data Machine core:

- 'datamachine_taxonomy_tool_parameter' fires in getTaxonomyToolParameters()
  after the auto-generated description is built. Consumers can replace the
  description, add an enum constraint, mark the field required, change the
  type, etc. Default behavior is unchanged when no filter is hooked.

- 'datamachine_taxonomy_assign_value' fires in assignTaxonomy() before
  processTerms() runs. Consumers can mutate, sanitize, or refuse the
  AI-supplied value. Returning empty (''/null/array()) skips assignment
  for that taxonomy without raising an error - useful for taxonomies that
  are derived from another data source and the AI must never pick.

Both filters are documented inline with usage examples that reference a
generic 'region' taxonomy, not any vendor-specific taxonomy. DM core
remains oblivious to any specific taxonomy name.

Closes #2152
@chubes4
Copy link
Copy Markdown
Member Author

chubes4 commented May 21, 2026

@Chubes ready for review.

Diff summary

```
inc/Core/WordPress/TaxonomyHandler.php | +118 / -3
tests/Unit/Core/WordPress/TaxonomyHandlerFiltersTest.php | +225 (new)
```

What's in here

  • datamachine_taxonomy_tool_parameter — fires in getTaxonomyToolParameters() after the auto-generated description is built. 4 args: $param_def, WP_Taxonomy $taxonomy, $handler_config, $post_type. Consumer can mutate description, add enum, mark required, etc.
  • datamachine_taxonomy_assign_value — fires in assignTaxonomy() before processTerms(). 3 args: $taxonomy_value, $taxonomy_name, $post_id. Returning empty ('' / null / array()) skips assignment silently with a clean success: true, term_count: 0 result — no term created, no error.

Both filters documented inline with usage examples referencing a generic region taxonomy, not any EC taxonomy. Grep is clean: grep -E 'location|venue|artist|festival' inc/Core/WordPress/TaxonomyHandler.php returns zero matches.

Test output

  • Full suite: 1325 passed / 3 skipped / 1 failed.
  • The single failure (ContentActionHandlersTest::test_resolved_pending_action_fires_hook_once) is pre-existing on main — verified by running the suite against the base. Not caused by this PR.
  • All 5 new TaxonomyHandlerFiltersTest cases pass:
    • test_default_tool_parameter_is_unchanged_when_no_filter_hooked
    • test_tool_parameter_filter_can_enrich_description_and_add_enum
    • test_default_assignment_creates_term_when_no_filter_hooked
    • test_assign_value_filter_can_refuse_ai_supplied_value
    • test_assign_value_filter_can_coerce_value
  • Confirmed the tests are meaningful: running them against the un-modified TaxonomyHandler.php produces 3 failures (the 2 default-behavior tests still pass, proving the default path is unchanged).

Default behavior unchanged

When no filter is hooked, both paths produce byte-identical output to pre-#2152.

Downstream

The extrachill-events consumer (extrachill-events#98 — making location derived from venue, never AI-decided) is a separate PR against a separate repo. Not touched here.

@homeboy-ci
Copy link
Copy Markdown
Contributor

homeboy-ci Bot commented May 21, 2026

Homeboy Results — data-machine

Lint

lint — passed

ℹ️ Full options: homeboy docs commands/lint
Deep dive: homeboy lint data-machine --changed-since 6c6d706

Test

test — failed

ℹ️ No tests ran — the runner failed before producing results. See raw_output.stderr_tail / raw_output.stdout_tail for the underlying error (bootstrap failure, missing deps, DB connection, etc.).
ℹ️ To run specific tests: homeboy test data-machine -- --filter=TestName
ℹ️ Auto-fix lint issues: homeboy refactor data-machine --from lint --write
ℹ️ Collect coverage: homeboy test data-machine --coverage
ℹ️ Analyze failures: homeboy test data-machine --analyze
ℹ️ Pass args to test runner: homeboy test -- [args]
ℹ️ Full options: homeboy docs commands/test
Deep dive: homeboy test data-machine --changed-since 6c6d706

Audit

audit — passed

  • requested_detectors — 118 finding(s)
  • test_coverage — 26 finding(s)
  • intra-method-duplication — 7 finding(s)
  • dead_code — 3 finding(s)
  • Directives — 1 finding(s)
  • Retention — 1 finding(s)
  • Total: 156 finding(s)

Deep dive: homeboy audit data-machine --changed-since 6c6d706

Tooling versions
  • Homeboy CLI: homeboy 0.194.0+ae667c8c
  • Extension: wordpress from https://github.com/Extra-Chill/homeboy-extensions
  • Extension revision: edb75b19
  • Action: unknown@unknown

@chubes4 chubes4 merged commit 8a4d1fe into main May 21, 2026
4 of 5 checks passed
@chubes4 chubes4 deleted the feat-2152-taxonomy-filter branch May 21, 2026 01:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant