spec(hooks): declarative hook payload scoping (#528)#535
Conversation
…sts (#528) Plugins must declare what data they need at registration time via required options object. Adds HookScope/PagesScope/PagesScopeMode types, RegisterWithOptions, ScopeFor, ValidateScope specs, hook availability matrix, and addPages return shape. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
zeroedin
left a comment
There was a problem hiding this comment.
QA Review — Declarative hook payload scoping spec + red tests
Action Items
-
Hook availability matrix says glob is "n/a" on
onConfig/onBeforeValidation/onAfterValidation/onDataFetched, but no test rejects it. TheValidateScopetests check taxonomy rejection onOnPagesReadyandOnConfig, and glob acceptance onOnPagesReady. But the matrix marks glob as "n/a" on all 4 early hooks (steps 2-6) — those hooks don't receive pages, so glob filtering is meaningless. There's no test thatValidateScope(OnConfig, HookScope{Pages: PagesScope{Mode: PagesScopeGlob, Glob: "/blog/**"}})returns an error. Same gap forPagesScopeAllonOnConfig— the test at line 334-347 deliberately omitsOnConfigfrom thePagesScopeAllacceptance loop, but doesn't verify rejection either. -
PagesScopeAllon pageless hooks is untested. The availability matrix showsonConfig-onDataFetchedwith "n/a" for both glob and taxonomy. ButValidateScopetests only rejectPagesScopeTaxonomyon those hooks. RequestingPagesScopeAllononConfig(which has no pages) should either be an error or a no-op — the spec doesn't say which, and there's no test either way. The developer needs a clear contract. -
onPageRenderedexample in PLAN.md has contradictory scope. Line ~536:alloy.hook("onPageRendered", { pages: true, pageFields: ["html"] }, (html) => {
This hook receives a raw HTML string, not a page object.
pages: truewithpageFields: ["html"]implies the callback receives a page with anhtmlfield, but the callback parameter is(html)— a string. Either the scope is wrong for this hook type (per-page hooks that receive strings don't usepages/pageFields) or the example callback signature is wrong. Clarify the interaction between scope declarations and hooks that already fire per-page with a specific payload shape. -
No test for
addPagesreturn shape. The spec describes{ addPages: [...] }as the return shape whenpages: false, butscope_test.goonly tests types, registration, and validation — not pipeline behavior. This is understandable for a scope-focused test file, but theaddPagescontract needs its own test. Either add a test inscope_test.gothat validates the shape (struct or map check), or document thataddPagestests belong in the pipeline test suite. -
OnDataCascadeReadymissing fromValidateScopetests. The hook availability matrix shows it as post-taxonomy ("11+", glob: yes, taxonomy: yes), but no test confirmsValidateScope(OnDataCascadeReady, taxonomyScope)succeeds. The test coversOnContentLoadedandOnContentTransformedfor post-taxonomy acceptance —OnDataCascadeReadyis in the same position but untested. -
OnDataCascadeReadymissing from theValidateScopepre-taxonomy rejection list in the spec. The IMPLEMENTATION.md spec says: "Pre-taxonomy hooks:OnConfig,OnBeforeValidation,OnAfterValidation,OnDataFetched,OnPagesReady." But looking at the pipeline,OnDataCascadeReadyfires at line 516-518 — afterapplyBatchContext(which includesBuildTaxonomies). The hook availability matrix correctly shows it as post-taxonomy. The pre-taxonomy list is correct by exclusion, but it would be clearer to also list the post-taxonomy hooks explicitly so the developer doesn't have to infer. -
Spec ambiguity:
PageFields: nilvsPageFields: ["*"]. The spec says both mean "all fields." Butnilis the default (omitted in JS), and["*"]is explicit. There's no test that verifiesPageFields: nilandPageFields: ["*"]produce the same serialization behavior. Add a test or document which one the pipeline should check for.
Code Quality
- Test structure is clean — focused
Describeblocks per type, separateItfor each behavior. - Zero-value test for
PagesScopeNone(line 61-66) is a smart contract lock — prevents accidental iota reordering. ScopeForreturningnilfor no hooks vs[]*HookScopewith nil entries for unscoped hooks is a clean distinction.- PLAN.md examples are updated consistently with the new
(event, options, fn)signature.
Spec Quality
- The
addPagesdesign is good — plugins that only inject virtual pages don't need to round-trip the entire pages array. Solves the N-pages-serialized-for-nothing problem. - Taxonomy AND/OR semantics (OR within taxonomy, AND across taxonomies) are clearly specified.
- The availability matrix is a valuable reference. Consider adding
onAssetProcess,onBuildComplete,onDevServerStart,onFileChangedfor completeness — even if they're "n/a" for everything.
Risk
Low. Spec + red tests only, no production code changed. The main risk is items #1-#3 — gaps between the availability matrix and the test coverage that could lead the developer to implement an incomplete ValidateScope.
zeroedin
left a comment
There was a problem hiding this comment.
Multi-Agent Code Review — Declarative hook payload scoping spec
Scope: merge-base with main → PR branch (3 files, ~500 lines)
Intent: Spec + failing red tests for declarative hook payload scoping (issue #528)
Reviewers: correctness, testing, maintainability, project-standards, agent-native, learnings, previous-comments
P1 — High
| # | File | Issue | Reviewer | Confidence | Route |
|---|---|---|---|---|---|
| 1 | plans/PLAN.md:609 |
onPageRendered scope example contradictory — hook receives HTML string but scope declares { pages: true, pageFields: ["html"] } implying page object |
previous-comments | 100 | manual → downstream-resolver |
| 2 | scope_test.go:262 |
ValidateScope tests miss 3 of 5 pre-taxonomy hooks — OnBeforeValidation, OnAfterValidation, OnDataFetched untested for taxonomy rejection | correctness, testing, previous-comments | 100 | manual → downstream-resolver |
| 3 | scope_test.go:340 |
No test for scope modes (glob, PagesScopeAll) on hooks that don't receive pages — availability matrix marks onConfig/onBeforeValidation/onAfterValidation/onDataFetched as "n/a" but ValidateScope has no rejection test | correctness, previous-comments | 100 | manual → downstream-resolver |
P2 — Moderate
| # | File | Issue | Reviewer | Confidence | Route |
|---|---|---|---|---|---|
| 4 | scope_test.go:32 |
No test for nil/empty/wildcard semantics — Data: nil vs Data: []string{} vs Data: ["*"] have distinct meanings per spec but no test distinguishes them; same gap for PageFields |
testing, previous-comments | 100 | manual → downstream-resolver |
| 5 | scope_test.go:226 |
ScopeFor priority test registers hooks in ascending priority order (10, then 20) — doesn't distinguish priority-based ordering from insertion order; swap registration to prove priority wins | correctness | 75 | manual → downstream-resolver |
| 6 | scope_test.go:288 |
OnDataCascadeReady and OnPageRendered missing from ValidateScope post-taxonomy acceptance tests despite appearing in availability matrix | previous-comments | 75 | advisory → human |
| 7 | scope_test.go:77 |
No negative test for invalid scope mode combinations — e.g., Mode=PagesScopeGlob with empty Glob, or Mode=PagesScopeTaxonomy with nil Taxonomies | testing | 75 | advisory → human |
| 8 | scope_test.go:1 |
No test for addPages return shape — spec describes { addPages: [...] } with path/url validation and collision detection but no test exercises it |
testing, previous-comments | 75 | advisory → human |
Prior Review Status
All 7 action items from the previous QA review remain unaddressed.
Coverage
- Suppressed: 5 findings below anchor 75; 3 maintainability findings demoted (spec tests verifying struct shapes are intentional in red-test workflow)
- Residual risks: No test verifies RegisterWithOptions/RegisterBatchWithOptions calls ValidateScope internally at registration time
- Testing gaps: No test for glob filtering behavior; no test for taxonomy union/intersection matching; no integration test for scope-aware serialization union; no test for mixed scoped/unscoped hooks coexisting on same event
- Clean passes: project-standards, agent-native (CLI tool, no gaps), learnings (no docs/solutions/ directory)
Verdict: Not ready
3 P1 findings: (1) contradictory onPageRendered example in PLAN.md, (2) 3 of 5 pre-taxonomy hooks untested for taxonomy rejection, (3) no test for scope modes on pageless hooks. All items from prior QA review remain unaddressed.
Fix order: #1 fix PLAN.md example → #2 add missing pre-taxonomy rejection tests → #3 add pageless hook scope rejection tests → #4-5 edge case tests
- Fix contradictory onPageRendered scope example (receives HTML string, not page object) - Add pageless hook rejection tests (OnConfig/OnBeforeValidation/OnAfterValidation/OnDataFetched reject All/Glob/Taxonomy) - Add OnDataCascadeReady and OnPageRendered to post-taxonomy acceptance tests - Swap ScopeFor priority test registration order to prove priority-based ordering - Expand availability matrix with all 13 hooks, clarify pageless vs per-page constraints - List post-taxonomy hooks explicitly in IMPLEMENTATION.md ValidateScope spec Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Response to QA ReviewAll items addressed in 6e714e3. #1 — Glob/All "n/a" on pageless hooks but no test rejects it. #2 — #3 — #4 — No test for #5 — #6 — Post-taxonomy hooks not listed explicitly in spec. #7 — Spec quality suggestions:
|
Response to Multi-Agent Code ReviewAll P1 and P2 findings addressed in 6e714e3. P1-1 (onPageRendered scope contradictory): Fixed — example now uses P1-2 (3 of 5 pre-taxonomy hooks untested): Added — all 4 pageless hooks ( P1-3 (no test for scope modes on pageless hooks): Added — pageless hooks reject any P2-4 (nil/empty/wildcard semantics): Serialization-layer concern — belongs in pipeline test suite, not scope registry tests. Spec is clear on semantics. P2-5 (ScopeFor registration order): Fixed — now registers priority 20 first, then 10, proving priority-based ordering not insertion order. P2-6 (OnDataCascadeReady/OnPageRendered missing from post-taxonomy tests): Added to acceptance loop. P2-7 (invalid scope mode combinations): Advisory — implementation PR will validate Glob/Taxonomies field consistency. P2-8 (addPages test): Pipeline-level concern — will be tested in the implementation PR. Residual risk (ValidateScope not called from RegisterWithOptions): By design — |
zeroedin
left a comment
There was a problem hiding this comment.
Re-Review: PR #535
Prior findings
| # | Finding | Status |
|---|---|---|
| P1-1 | onPageRendered scope contradictory | Partial — 4 instances remain |
| P1-2 | ValidateScope pre-taxonomy untested | Fixed |
| P1-3 | Pageless hook rejection untested | Fixed |
| P2-4 | nil/wildcard semantics | Deferred (partially valid) |
| P2-5 | ScopeFor priority ordering | Fixed |
| P2-6 | DataCascadeReady/PageRendered post-taxonomy | Fixed |
| P2-7 | Invalid scope combinations | Deferred (contested — see advisory) |
| P2-8 | addPages return shape | Deferred (valid) |
P1: 4 per-page hook examples still contradictory
plans/PLAN.md — "Hook execution order" section and "Per-page hooks" section
Line 601 states: "Per-page hooks fire once per page with a fixed payload shape — pages/pageFields scope does not apply. Scope options on per-page hooks are limited to data and priority."
These 4 examples (all changed in this diff) contradict that rule:
L539: alloy.hook("onPageRendered", { priority: 10, pages: true, pageFields: ["html"] }, transformsFn);
L543: alloy.hook("onPageRendered", { pages: true }, analyticsFn);
L547: alloy.hook("onPageRendered", { priority: 100, pages: true }, ssrFn);
L665: alloy.hook("onContentTransformed", { pages: true, pageFields: ["html", "toc"] }, (page) => {
The fix at line 679 (onPageRendered with {}) is correct — these 4 need the same treatment. onPageRendered examples → { priority: N }. onContentTransformed example → {}.
Advisory
1. ValidateScope covers 9 of 13 hooks. OnAssetProcess, OnBuildComplete, OnDevServerStart, OnFileChanged are absent from all ValidateScope tests. IMPLEMENTATION.md only lists 4 pageless hooks in the rejection rule, so tests match the spec — but the spec may need expanding. An implementation that accepts PagesScopeAll on OnBuildComplete would pass all current tests.
2. Deferred P2-7: empty Glob / nil Taxonomies are ValidateScope concerns. PagesScope{Mode: Glob, Glob: ""} and PagesScope{Mode: Taxonomy, Taxonomies: nil} are invalid configurations that ValidateScope exists to catch at plugin load time. These are validation concerns, not pipeline serialization concerns.
3. Deferred P2-4 refinement: nil semantics asymmetry. nil Data = skip, nil PageFields = all fields. Both should at minimum pass ValidateScope — encoding that nil is a valid input with defined semantics.
4. ValidateScope as free function. All other hook operations (RegisterWithOptions, ScopeFor, HasHooks) are *HookRegistry methods. ValidateScope is a package-level function. The pipeline position knowledge (which hooks are pageless, which are pre-taxonomy) lives in the free function, separate from the registry. Design observation for the implementer.
Verdict
Request changes — 1 P1 (4 contradictory PLAN.md examples, same class as prior P1-1). scope_test.go is solid — all prior P1 test findings properly addressed. The remaining issue is a PLAN.md fix: update 4 examples to match the per-page hook rule the PR itself establishes.
Re-review of commit 6e714e3. 5 reviewers: correctness, testing, maintainability, project-standards, previous-comments.
onPageRendered receives an HTML string and onContentTransformed receives a fixed per-page payload — pages/pageFields scope does not apply to per-page hooks. Fixes 4 remaining contradictory examples in PLAN.md. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Response to Re-ReviewP1 fixed in d2b0039. P1 — 4 per-page hook examples still contradictory. Advisory #1 — ValidateScope covers 9 of 13 hooks. Advisory #2 — Empty Glob / nil Taxonomies are ValidateScope concerns. Advisory #3 — nil semantics asymmetry. Advisory #4 — ValidateScope as free function. |
Summary
alloy.hook(event, options, fn)signature — options is required, declares what data the hook needs (data,pages,pageFields)HookScope,PagesScope,PagesScopeModetype specs to IMPLEMENTATION.mdRegisterWithOptions,RegisterBatchWithOptions,ScopeFor,ValidateScopemethod specsPagesScopeTaxonomy)addPagesreturn shape for injection-only hooks withpages: false(event, options, fn)signatureinternal/plugin/scope_test.gocovering all new types and methodsTest plan
go test ./internal/plugin/... -count=1fails to compile (types/methods undefined) — confirms tests are red(event, options, fn)signatureRelated: #528
🤖 Generated with Claude Code