CI review found the general case behind the pinned-hash fix. `script_to_payload`
resolves an un-pinned step path with the root DB handle and returns the
referenced runnable's `on_behalf_of`, which the step job then adopts. Composing
a path is therefore enough to run it, and to run it as whoever it runs as, so
the previous commit closed only the narrow variant.
- The walk now reports every referenced workspace runnable, and
`validate_operator_composed_flow` checks each under the caller's RLS. The
pinned `(path, hash)` comparison stays on top: the dispatch ignores the path
beside a hash, so a readable path paired with another script's hash still runs
that other script.
- Same reasoning for apps: a policy triggerable is what `execute_component` will
resolve, also with the root DB handle, so an unreadable path there means an
admin who merely opens the app runs code the builder could not see, as
themselves. `validate_operator_composed_app` checks every `script/<path>` and
`flow/<path>` key and refuses hub ones. The value-and-policy half stays a pure
function so it keeps its unit test.
- `execute_component`'s preview branch refuses a hub path for operators:
`require_path_read_access_for_preview` admits `hub/` for everyone, and
`get_payload_tag_from_prefixed_path` then downloads and enqueues it, which is
exactly the unreviewed code the composition check refuses in a flow.
Also: the app row menu's relaxed entries are gated on the row being a full-code
app, and the notify trigger only fires when `operator_settings` actually
changed, matching its closest sibling.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>