Skip to content

feat: full node/attribute/param table with Tables.jl-forwarding tree - #343

Closed
seabbs-bot wants to merge 13 commits into
feat/wrapper-inner-leaffrom
feat/227-single-table-slice-1
Closed

feat: full node/attribute/param table with Tables.jl-forwarding tree#343
seabbs-bot wants to merge 13 commits into
feat/wrapper-inner-leaffrom
feat/227-single-table-slice-1

Conversation

@seabbs-bot

@seabbs-bot seabbs-bot commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator

Slice 1 of #227: a single pre-order walk now feeds two projections.

  • composed_to_table(d) is new: one row per composer node, leaf (wrapper) layer, fixed-structure attribute, and scalar free parameter (columns edge, param, node, role, value, support, prior).
  • params_table(d) output is unchanged and is exactly the role == :param projection of the same walk via sink dispatch — the parameter-only path does no extra work (a CountingLeaf test pins that it never calls the layer machinery; this protects the DistributionsInference per-gradient hot path). Note: params_table is slated for removal in a stacked follow-up per Sam's single-surface decision; this PR keeps it byte-identical so the parity is provable first.
  • A composed tree is now a Tables.jl source (DataFrame(tree) gives the full table), with an explicit guard making update(a, b) throw when b is a tree rather than silently bulk-writing.
  • update(d, table) filters role-carrying tables to :param rows; role-less tables unchanged.
  • Four public node-emission hooks for downstream nodes/wrappers: node_kind, node_children, node_attributes, leaf_layers. The rebuild half is deferred to slice 2.

Golden order-sensitive projection-parity tests over 16 fixtures, row invariants, structural-recovery tests (Sequential vs Parallel now distinguishable, truncation bounds visible, shared occurrences get node rows), Tables-interface and role-aware-update tests. Quality 244/244, full non-AD suite 2060/2060, full docs build clean. In-memory round trip is the documented target (Varying maps and composite solvers are live objects).

This was opened by a bot. Please ping @seabbs for any questions.

Closes #227.

@codecov

codecov Bot commented Aug 4, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 90.90909% with 14 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/composers/introspection.jl 90.76% 12 Missing ⚠️
src/composers/Pool.jl 87.50% 1 Missing ⚠️
src/composers/Resolve.jl 50.00% 1 Missing ⚠️
Flag Coverage Δ
ad-enzyme-forward 0.00% <0.00%> (ø)
ad-enzyme-reverse 13.76% <0.65%> (-0.41%) ⬇️
ad-forwarddiff 12.22% <0.65%> (-0.37%) ⬇️
ad-mooncake-forward 0.00% <0.00%> (ø)
ad-mooncake-reverse 12.41% <0.65%> (-0.37%) ⬇️
ad-reversediff 12.22% <0.65%> (-0.37%) ⬇️
unit 87.04% <94.59%> (+0.17%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
src/composers/Choose.jl 70.78% <100.00%> (+0.33%) ⬆️
src/composers/Shared.jl 63.41% <100.00%> (+0.91%) ⬆️
src/composers/Uncertain.jl 93.75% <100.00%> (+0.04%) ⬆️
src/composers/convolved_interop.jl 98.36% <100.00%> (+0.11%) ⬆️
src/composers/varying.jl 79.66% <100.00%> (+0.35%) ⬆️
src/composers/wrapped_leaves.jl 44.23% <100.00%> (+2.23%) ⬆️
src/composers/Pool.jl 88.43% <87.50%> (-1.38%) ⬇️
src/composers/Resolve.jl 77.95% <50.00%> (-0.23%) ⬇️
src/composers/introspection.jl 85.43% <90.76%> (+0.50%) ⬆️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Try this Pull Request!

Option 1: Julia Package Manager

Open Julia and type:

import Pkg
Pkg.activate(temp=true)
Pkg.add(url="https://github.com/EpiAware/ComposedDistributions.jl", rev="feat/227-single-table-slice-1")
using ComposedDistributions

Option 2: Local Checkout

If you have the repo locally:

git checkout feat/227-single-table-slice-1
julia --project=. -e "using Pkg; Pkg.instantiate()"

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

📖 Documentation preview is ready!

View the docs for this PR at: https://EpiAware.github.io/ComposedDistributions.jl/previews/PR343/

This preview will be updated automatically when you push new commits.

@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Benchmark comparison vs base

Minimum time per call. Buckets are PR time as a % of base, so lower is faster (🟢 faster, ⚪ within 5%, 🔴 slower). Counts of benchmarks per bucket:

Group 🟢 <50% 🟢 50–75% 🟢 75–95% ⚪ 95–105% 🔴 105–125% 🔴 125–150% 🔴 >150%
Evaluation · · 1 8 7 · 1
ForwardDiff · · · 2 6 1 ·
ReverseDiff (tape) · · · 4 5 · ·
Mooncake reverse · · · 1 5 3 ·
Enzyme reverse · · · 5 4 · ·
Evaluation — 17 benchmarks (by time change)
Benchmark base PR time memory
Composition / Resolve / construct 19.0 ns 29.0 ns 🔴 1.53×
Composition / Choose / construct 17.0 ns 20.0 ns 🔴 1.18×
Composition / Compete / construct 17.0 ns 20.0 ns 🔴 1.18×
Composition / Choose / logpdf 57.0 ns 49.0 ns 🟢 0.86×
Composition / Sequential / rand 1.09 μs 1.24 μs 🔴 1.14× ⚪ 1.0×
Composition / Parallel / rand 1.09 μs 1.23 μs 🔴 1.13× ⚪ 1.0×
Composition / Nested / rand 3.86 μs 4.22 μs 🔴 1.09× ⚪ 1.0×
Composition / Sequential / construct 1.27 μs 1.38 μs 🔴 1.09× ⚪ 1.0×
Composition / Parallel / construct 1.28 μs 1.37 μs 🔴 1.08× ⚪ 1.0×
Composition / Nested / logpdf 1.7 μs 1.62 μs ⚪ 0.95× ⚪ 1.0×
Composition / Nested / compose 2.47 μs 2.58 μs ⚪ 1.05× ⚪ 1.0×
Composition / Resolve / logpdf 134.0 ns 131.0 ns ⚪ 0.98×
Composition / Compete / logccdf 315.0 ns 320.0 ns ⚪ 1.02×
Composition / Compete / rand 548.0 ns 551.0 ns ⚪ 1.01× ⚪ 1.0×
Composition / Parallel / logpdf 100.0 ns 100.0 ns ⚪ 1.0× ⚪ 1.0×
Composition / Resolve / rand 532.0 ns 532.0 ns ⚪ 1.0× ⚪ 1.0×
Composition / Sequential / logpdf 100.0 ns 100.0 ns ⚪ 1.0× ⚪ 1.0×
AD gradients — 36 benchmarks (by time change)
Benchmark base PR time memory
AD gradients / Sequential Gamma+LogNormal logpdf / Mooncake reverse 286.61 μs 423.79 μs 🔴 1.48× ⚪ 1.0×
AD gradients / Shared-tag unflatten/update codec / Mooncake reverse 81.69 μs 109.19 μs 🔴 1.34× ⚪ 1.0×
AD gradients / Censored leaf marginal logpdf / Mooncake reverse 342.96 μs 448.14 μs 🔴 1.31× ⚪ 1.0×
AD gradients / Sequential Gamma+LogNormal logpdf / ForwardDiff 9.33 μs 12.15 μs 🔴 1.3× ⚪ 1.0×
AD gradients / Resolve stick-breaking branch-prob logpdf / Mooncake reverse 79.38 μs 97.45 μs 🔴 1.23× ⚪ 1.0×
AD gradients / Censored leaf marginal logpdf / ForwardDiff 9.72 μs 11.73 μs 🔴 1.21× ⚪ 1.0×
AD gradients / Resolve mixture marginal logpdf / Mooncake reverse 79.66 μs 94.0 μs 🔴 1.18× ⚪ 1.0×
AD gradients / Censored leaf marginal logpdf / Enzyme reverse 115.47 μs 133.6 μs 🔴 1.16× ⚪ 1.0×
AD gradients / Truncated uncertain-leaf unflatten/update codec / ForwardDiff 3.32 μs 3.8 μs 🔴 1.14× ⚪ 1.0×
AD gradients / Compete racing-hazard marginal logpdf / Mooncake reverse 46.42 μs 52.47 μs 🔴 1.13× ⚪ 1.0×
AD gradients / Truncated uncertain-leaf unflatten/update codec / Enzyme reverse 17.33 μs 19.54 μs 🔴 1.13× ⚪ 1.0×
AD gradients / Truncated uncertain-leaf unflatten/update codec / Mooncake reverse 128.64 μs 144.0 μs 🔴 1.12× ⚪ 1.0×
AD gradients / Pool non-centred reconstruction logpdf / Mooncake reverse 19.54 μs 21.74 μs 🔴 1.11× ⚪ 1.0×
AD gradients / Sequential Gamma+LogNormal logpdf / Enzyme reverse 96.45 μs 106.13 μs 🔴 1.1× ⚪ 1.0×
AD gradients / Truncated uncertain-leaf unflatten/update codec / ReverseDiff (tape) 12.17 μs 13.34 μs 🔴 1.1× ⚪ 1.0×
AD gradients / Compete racing-hazard marginal logpdf / ForwardDiff 7.28 μs 7.98 μs 🔴 1.1× ⚪ 1.0×
AD gradients / Resolve stick-breaking branch-prob logpdf / ForwardDiff 6.37 μs 6.9 μs 🔴 1.08× ⚪ 1.0×
AD gradients / Censored leaf marginal logpdf / ReverseDiff (tape) 26.24 μs 28.45 μs 🔴 1.08× ⚪ 1.0×
AD gradients / Resolve mixture marginal logpdf / ReverseDiff (tape) 59.41 μs 63.83 μs 🔴 1.07× ⚪ 1.0×
AD gradients / Pool non-centred reconstruction logpdf / ForwardDiff 944.0 ns 1.01 μs 🔴 1.07× ⚪ 1.0×
AD gradients / Sequential Gamma+LogNormal logpdf / ReverseDiff (tape) 25.76 μs 27.59 μs 🔴 1.07× ⚪ 1.0×
AD gradients / Choose selected-branch logpdf / ForwardDiff 643.0 ns 681.0 ns 🔴 1.06× ⚪ 1.0×
AD gradients / Resolve stick-breaking branch-prob logpdf / ReverseDiff (tape) 51.81 μs 54.72 μs 🔴 1.06× ⚪ 1.0×
AD gradients / Compete racing-hazard marginal logpdf / Enzyme reverse 9.25 μs 9.77 μs 🔴 1.06× ⚪ 1.0×
AD gradients / Shared-tag unflatten/update codec / ReverseDiff (tape) 11.3 μs 11.83 μs ⚪ 1.05× ⚪ 1.0×
AD gradients / Compete racing-hazard marginal logpdf / ReverseDiff (tape) 47.68 μs 49.81 μs ⚪ 1.04× ⚪ 1.0×
AD gradients / Pool non-centred reconstruction logpdf / ReverseDiff (tape) 24.92 μs 25.95 μs ⚪ 1.04× ⚪ 1.0×
AD gradients / Resolve mixture marginal logpdf / ForwardDiff 6.48 μs 6.69 μs ⚪ 1.03× ⚪ 1.0×
AD gradients / Choose selected-branch logpdf / Mooncake reverse 16.3 μs 16.78 μs ⚪ 1.03× ⚪ 1.0×
AD gradients / Resolve mixture marginal logpdf / Enzyme reverse 20.45 μs 20.76 μs ⚪ 1.02× ⚪ 1.0×
AD gradients / Resolve stick-breaking branch-prob logpdf / Enzyme reverse 20.59 μs 20.88 μs ⚪ 1.01× ⚪ 1.0×
AD gradients / Pool non-centred reconstruction logpdf / Enzyme reverse 1.93 μs 1.95 μs ⚪ 1.01× ⚪ 1.0×
AD gradients / Shared-tag unflatten/update codec / Enzyme reverse 7.68 μs 7.76 μs ⚪ 1.01× ⚪ 1.0×
AD gradients / Shared-tag unflatten/update codec / ForwardDiff 1.8 μs 1.82 μs ⚪ 1.01× ⚪ 1.0×
AD gradients / Choose selected-branch logpdf / ReverseDiff (tape) 13.79 μs 13.89 μs ⚪ 1.01× ⚪ 1.0×
AD gradients / Choose selected-branch logpdf / Enzyme reverse 3.23 μs 3.24 μs ⚪ 1.0× ⚪ 1.0×

Introduce composed_to_table(d), the full node/attribute/param inventory
of a composed tree, alongside the existing params_table(d). Both are
produced by one pre-order walk threading a sink object (_ParamSink or
_FullSink) instead of five positional column vectors, so params_table
stays the exact role == :param projection with no extra traversal or
allocation on its existing AD-adjacent call sites (e.g. centred_pool_rows).

Every composer node and leaf (wrapper) layer gets a :node row and any
node_attributes :attribute rows; a leaf's wrapper stack (Truncated,
Censored, Shared, Varying, Uncertain) is listed via the new leaf_layers
hook, one row per layer at the leaf's real path, emitted before the
shared-tag dedup so every shared occurrence is visible structurally even
though its :param rows are still inventoried once under the tag. A
Resolve's own branch_probs rows stay emitted after its children's rows,
matching the codec's type-level walk order.

node_kind, node_children (renamed from _node_children, aliased for
source compatibility), node_attributes and leaf_layers are the new
public node-emission hooks a downstream node/leaf-wrapper type overrides.

Golden projection-parity, row-invariant and structural-recovery tests
cover Sequential/Parallel/Resolve/Compete/Choose, shared and pooled
leaves, Truncated/Censored/Varying/Uncertain wrappers, a Convolved
composite, and a third-party leaf wrapper that only extends
free_leaf/rewrap_leaf (an opaque node row, by design).
A composed distribution is now a Tables.jl source in its own right:
Tables.columns/columnnames/getcolumn/schema/rows all forward to its
full composed_to_table, so DataFrame(tree) yields the full table
(DataFrame(params_table(tree)) stays the parameter-only one). No
Base.getproperty override, so field access on every node keeps working.

Because a composed distribution is now Tables.istable, update(a, b)
with b a tree would otherwise silently reach update's table arm and
bulk-write b's rows into a. Add an explicit
update(::AbstractComposedDistribution, ::AbstractComposedDistribution)
guard that throws, naming composed_to_table/params_table as the
explicit way to copy another tree's rows.

update(d, table) now filters a role-carrying table (composed_to_table
or a DataFrame of one) to its role == :param rows first, so passing a
tree or its full table straight to update only ever writes parameters;
a table with no role column (the existing params_table shape) is
unaffected.
Mention composed_to_table alongside params_table in the introspection
contract section of the developer interface-contracts page, and add an
Unreleased NEWS.md entry summarising the new full table, the tree's
Tables.jl forwarding, and role-aware update. Every new public
docstring (composed_to_table, node_kind, node_children,
node_attributes, leaf_layers) already carries # Arguments and a
runnable @example, verified against a full docs build.
`_walk_rows!`'s leaf branch built and iterated `leaf_layers(leaf)`
unconditionally, so `params_table`'s AD-hot path paid for the full
layer walk even though `_ParamSink`'s node/attribute pushes are
no-ops. Add `_emit_layers!`, dispatched on the sink: a no-op for
`_ParamSink`, the layer loop for `_FullSink`, so the params-only path
never touches `leaf_layers` at all.
…e table surface

BREAKING CHANGE: params_table(d) is removed. composed_to_table(d) is now
the only table-reading verb: it returns the full node/attribute/parameter
inventory, and the parameter-only view is a filter over it
(`filter(row -> row.role == :param, Tables.rows(composed_to_table(d)))`).
This folds into the unregistered 0.2.0 breaking window rather than a
deprecation cycle.

- build_priors and update now both accept a composed_to_table-shaped table
  directly, filtering to :param rows internally the same way, so
  build_priors(composed_to_table(tree)) and
  update(tree, composed_to_table(tree)) work without hand-filtering.
- centred_pool_rows (Pool.jl) and required_parameters (varying.jl) migrate
  to the _ParamSink walk directly, the same zero-extra-work path
  params_table used internally, so neither regresses to building the full
  table on their AD-adjacent call sites.
- ParamsTable's show header drops the now-dead params_table/composed_to_table
  branch.
- Tests recast the golden parity fixture as an explicit role == :param
  filter over composed_to_table, verified against an internal _param_rows
  helper that still runs the parameter-only walk independently (proving the
  filter reproduces the historical params_table output). Other tests move
  to composed_to_table directly where row count is not significant, or to
  _param_rows where a test's assertions depend on the historical five-column
  shape or exact row count.
- Docs and tutorials teach composed_to_table plus the role filter as the
  parameter-view idiom.
guards and tests

Reviewer follow-ups on the #227 params_table removal:

- extend the leaf hot-path testitem to exercise required_parameters and
  centred_pool_rows directly, not just _param_rows
- rename the composed_to_table wrapper type from ParamsTable to
  ComposedTable (breaking, same unregistered 0.2.0 window)
- add the missing # Arguments section to composed_to_table's docstring
- pin and document the empty-path/root-row convention that
  required_parameters and centred_pool_rows fall back to for a bare leaf
- give build_priors the same up-front Tables.istable/edge/param guard
  update already has, so a DI-shaped table is refused loudly
- dedupe the restated row-structure sentence in NEWS.md and fix an
  unrewrapped line
dependabot Bot and others added 3 commits August 6, 2026 15:27
Bumps the github-actions group with 9 updates:

| Package | From | To |
| --- | --- | --- |
| [EpiAware/.github/.github/workflows/tagbot.yml](https://github.com/epiaware/.github) | `11263ddd07f3d88c54a6245828a81d10a83fcb29` | `d024dc6927cc3523292a159dc0d4a45193c9c65d` |
| [EpiAware/.github/.github/workflows/ad.yml](https://github.com/epiaware/.github) | `11263ddd07f3d88c54a6245828a81d10a83fcb29` | `d024dc6927cc3523292a159dc0d4a45193c9c65d` |
| [EpiAware/.github/.github/workflows/cancel-on-close.yml](https://github.com/epiaware/.github) | `11263ddd07f3d88c54a6245828a81d10a83fcb29` | `d024dc6927cc3523292a159dc0d4a45193c9c65d` |
| [EpiAware/.github/.github/workflows/coverage.yml](https://github.com/epiaware/.github) | `11263ddd07f3d88c54a6245828a81d10a83fcb29` | `d024dc6927cc3523292a159dc0d4a45193c9c65d` |
| [EpiAware/.github/.github/workflows/docs-preview-cleanup.yml](https://github.com/epiaware/.github) | `11263ddd07f3d88c54a6245828a81d10a83fcb29` | `d024dc6927cc3523292a159dc0d4a45193c9c65d` |
| [EpiAware/.github/.github/workflows/documentation.yml](https://github.com/epiaware/.github) | `11263ddd07f3d88c54a6245828a81d10a83fcb29` | `d024dc6927cc3523292a159dc0d4a45193c9c65d` |
| [EpiAware/.github/.github/workflows/format-check.yml](https://github.com/epiaware/.github) | `11263ddd07f3d88c54a6245828a81d10a83fcb29` | `d024dc6927cc3523292a159dc0d4a45193c9c65d` |
| [EpiAware/.github/.github/workflows/release-nudge.yml](https://github.com/epiaware/.github) | `11263ddd07f3d88c54a6245828a81d10a83fcb29` | `d024dc6927cc3523292a159dc0d4a45193c9c65d` |
| [EpiAware/.github/.github/workflows/tests.yml](https://github.com/epiaware/.github) | `11263ddd07f3d88c54a6245828a81d10a83fcb29` | `d024dc6927cc3523292a159dc0d4a45193c9c65d` |


Updates `EpiAware/.github/.github/workflows/tagbot.yml` from 11263ddd07f3d88c54a6245828a81d10a83fcb29 to d024dc6927cc3523292a159dc0d4a45193c9c65d
- [Release notes](https://github.com/epiaware/.github/releases)
- [Commits](EpiAware/.github@11263dd...d024dc6)

Updates `EpiAware/.github/.github/workflows/ad.yml` from 11263ddd07f3d88c54a6245828a81d10a83fcb29 to d024dc6927cc3523292a159dc0d4a45193c9c65d
- [Release notes](https://github.com/epiaware/.github/releases)
- [Commits](EpiAware/.github@11263dd...d024dc6)

Updates `EpiAware/.github/.github/workflows/cancel-on-close.yml` from 11263ddd07f3d88c54a6245828a81d10a83fcb29 to d024dc6927cc3523292a159dc0d4a45193c9c65d
- [Release notes](https://github.com/epiaware/.github/releases)
- [Commits](EpiAware/.github@11263dd...d024dc6)

Updates `EpiAware/.github/.github/workflows/coverage.yml` from 11263ddd07f3d88c54a6245828a81d10a83fcb29 to d024dc6927cc3523292a159dc0d4a45193c9c65d
- [Release notes](https://github.com/epiaware/.github/releases)
- [Commits](EpiAware/.github@11263dd...d024dc6)

Updates `EpiAware/.github/.github/workflows/docs-preview-cleanup.yml` from 11263ddd07f3d88c54a6245828a81d10a83fcb29 to d024dc6927cc3523292a159dc0d4a45193c9c65d
- [Release notes](https://github.com/epiaware/.github/releases)
- [Commits](EpiAware/.github@11263dd...d024dc6)

Updates `EpiAware/.github/.github/workflows/documentation.yml` from 11263ddd07f3d88c54a6245828a81d10a83fcb29 to d024dc6927cc3523292a159dc0d4a45193c9c65d
- [Release notes](https://github.com/epiaware/.github/releases)
- [Commits](EpiAware/.github@11263dd...d024dc6)

Updates `EpiAware/.github/.github/workflows/format-check.yml` from 11263ddd07f3d88c54a6245828a81d10a83fcb29 to d024dc6927cc3523292a159dc0d4a45193c9c65d
- [Release notes](https://github.com/epiaware/.github/releases)
- [Commits](EpiAware/.github@11263dd...d024dc6)

Updates `EpiAware/.github/.github/workflows/release-nudge.yml` from 11263ddd07f3d88c54a6245828a81d10a83fcb29 to d024dc6927cc3523292a159dc0d4a45193c9c65d
- [Release notes](https://github.com/epiaware/.github/releases)
- [Commits](EpiAware/.github@11263dd...d024dc6)

Updates `EpiAware/.github/.github/workflows/tests.yml` from 11263ddd07f3d88c54a6245828a81d10a83fcb29 to d024dc6927cc3523292a159dc0d4a45193c9c65d
- [Release notes](https://github.com/epiaware/.github/releases)
- [Commits](EpiAware/.github@11263dd...d024dc6)

---
updated-dependencies:
- dependency-name: EpiAware/.github/.github/workflows/tagbot.yml
  dependency-version: d024dc6927cc3523292a159dc0d4a45193c9c65d
  dependency-type: direct:production
  dependency-group: github-actions
- dependency-name: EpiAware/.github/.github/workflows/ad.yml
  dependency-version: d024dc6927cc3523292a159dc0d4a45193c9c65d
  dependency-type: direct:production
  dependency-group: github-actions
- dependency-name: EpiAware/.github/.github/workflows/cancel-on-close.yml
  dependency-version: d024dc6927cc3523292a159dc0d4a45193c9c65d
  dependency-type: direct:production
  dependency-group: github-actions
- dependency-name: EpiAware/.github/.github/workflows/coverage.yml
  dependency-version: d024dc6927cc3523292a159dc0d4a45193c9c65d
  dependency-type: direct:production
  dependency-group: github-actions
- dependency-name: EpiAware/.github/.github/workflows/docs-preview-cleanup.yml
  dependency-version: d024dc6927cc3523292a159dc0d4a45193c9c65d
  dependency-type: direct:production
  dependency-group: github-actions
- dependency-name: EpiAware/.github/.github/workflows/documentation.yml
  dependency-version: d024dc6927cc3523292a159dc0d4a45193c9c65d
  dependency-type: direct:production
  dependency-group: github-actions
- dependency-name: EpiAware/.github/.github/workflows/format-check.yml
  dependency-version: d024dc6927cc3523292a159dc0d4a45193c9c65d
  dependency-type: direct:production
  dependency-group: github-actions
- dependency-name: EpiAware/.github/.github/workflows/release-nudge.yml
  dependency-version: d024dc6927cc3523292a159dc0d4a45193c9c65d
  dependency-type: direct:production
  dependency-group: github-actions
- dependency-name: EpiAware/.github/.github/workflows/tests.yml
  dependency-version: d024dc6927cc3523292a159dc0d4a45193c9c65d
  dependency-type: direct:production
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
…b-actions-6efa85718e

ci(deps): bump the github-actions group with 9 updates
The reworked node-emission surface derives a leaf's wrapper layers by
folding `inner_dist` rather than shipping its own `leaf_layers` hook, so
this branch stacks on #362.
The single-table walk shipped four public hooks. Three of them asked a
downstream type for something the package can work out for itself, so
only one survives.

- `node_kind` is now the internal `_node_kind`. It reads the type name off
  the value, which is correct for every type that can appear in a tree, so
  there is nothing for a downstream type to supply.
- `leaf_layers` is gone. A leaf's wrapper layers are the fold of the
  `inner_dist` peel a wrapper already defines to take part in the leaf
  protocol, so the internal `_leaf_layers` folds it instead of asking for a
  second, redundant method. `Truncated`, `Shared`, `Varying`, `Uncertain`
  and `Censored` lose their per-type layer methods; `Censored` gains the
  `inner_dist` method it was missing, which also retires its four
  read-through forwards.
- `node_children` is left as the internal `_node_children` it was on main.
  Promoting it here would collide with the independent promotion in the
  node-surface work, which documents it as the accessor a downstream node
  defines for `has_varying`/`has_uncertain`. This walk consumes it under
  its existing internal name instead, so exactly one definition ships.

`node_attributes` earns its place: a node's fixed, non-parameter structure
(a `Choose`'s selector, a truncation bound, a shared tag) cannot be derived
from the type or from the peel.

A downstream leaf wrapper now defines two methods for the table
(`inner_dist`, `node_attributes`) instead of four (`node_kind`,
`leaf_layers`, `node_attributes` plus the peel), and a downstream composer
node defines one (`node_attributes`) instead of three.
Two repairs the reworked branch needs to run its own gates green.

- `_pool_hyper_rows!` pushed a parameter name whose type the compiler
  cannot pin down for a population template of non-concrete type, so the
  typed row push did not resolve statically. A leaf's parameter names are
  `Symbol`s by contract; state that at the loop, which the row push
  already required.
- Three codec tests still called `params_table`, removed earlier on this
  branch, and errored on load. They assert the parameter-row projection's
  order, so they now run it through `_param_rows`.
`node_attributes` is public but not exported, so an `@ref` from a page
whose module context is `Main` cannot resolve it. The neighbouring
leaf-wrapper section already names such hooks in plain backticks.
@seabbs-bot
seabbs-bot changed the base branch from main to feat/wrapper-inner-leaf August 10, 2026 18:27
@seabbs-bot

Copy link
Copy Markdown
Collaborator Author

Reworked per @seabbs's steer: adding a node may require methods, but they must be as few and as clean as possible, public but not exported, with a stated interface and developer docs.

Retargeted onto #362. Dropping leaf_layers in favour of inner_dist requires inner_dist to exist, and defining it here would have recreated the same collision this PR just resolved with #365. So this now genuinely depends on #362 landing first; GitHub will retarget to main automatically when it does.

The hook set, cut from four to one

Hook Verdict
node_attributes Kept, public. A node's fixed non-parameter structure — a Choose's selector, a truncation bound, a Shared tag — cannot be derived from the type or from the peel. The only genuine extension point of the four.
node_kind Removed. Now internal: Base.typename(typeof(x)).name is correct for every type that can ever exist, so it is an accessor, not an extension point.
leaf_layers Removed. Now internal, folding inner_dist. A wrapper already defines inner_dist in order to be a wrapper at all; a second redundant method bought nothing. The per-type methods for Truncated, Shared, Varying, Uncertain and Censored are all deleted.
node_children Not promoted here. #365's promotion survives, since it is already public and documented as what earns has_varying/has_uncertain over AbstractComposedDistribution. This branch's region was reverted to exactly main's text, verified by diff, so the two PRs now merge in either order with exactly one definition shipping.

The numbers

Surface Before After
Composer node, table-facing 3 1
Leaf wrapper, table-facing 3 1 (and optional)
Leaf wrapper peel methods, with #362 5 1 (inner_dist)

Distributions.Censored had no inner_dist method — #362 missed it — so it gained one, which also retired its four read-through forwards.

Two pre-existing defects fixed, both worth knowing about

This branch's task test-fast was red before this rework. Three testitems in test/composers/codec_gen.jl still called params_table, which the branch itself removes, so they errored on load. The PR body above claims 2060/2060; that number predates the stacked removal merging into this branch. Now repointed and green.

test-quality went red under the stack, JET flagging _pool_hyper_rows! (src/composers/Pool.jl:337) because this branch's typed _push_param! cannot resolve against a parameter name of unknown type. Measured: the base branch alone is JET-clean, so this is a stacking interaction with #362's inference widening, not a latent bug in either alone. Fixed by asserting the leaf-protocol contract at the loop.

Gates

task test-fast 2172/2172, task test-quality 244/244, full task docs exit 0 with no new warnings — the one warning added during the work was fixed, and the count dropped by exactly one.

Expected conflict

The two developer pages updated here are the ones #365 deletes into extending.md. Expect a delete/modify conflict, trivially resolved in #365's favour by carrying the wording across.

This comment was posted by a bot. Please ping @seabbs for any questions.

@seabbs-bot

Copy link
Copy Markdown
Collaborator Author

Superseded by #381, which folds this and seven other PRs into one branch off main so the 0.2.0 contract reviews as a single diff. Its gates: test-fast 2379/2379, test-quality 255/255, full docs build clean.

This branch is preserved and the PR body is kept for its rationale — the detail here is the record of why, which #381 summarises rather than repeats. Closing so it does not compete for review.

This comment was posted by a bot. Please ping @seabbs for any questions.

@seabbs-bot seabbs-bot closed this Aug 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

params_table

2 participants