Built by portal-trainer from plans/uhhm.yaml (uhhm/portal-trainer,
runs/uhhm-2026-09-23): the five live buckets keep their state and
event names, so every record on the owners' desk stays. New: `trials`,
a business trying the trainer on its own workflow, approved by an
owner before anything is built (the n8n preview workflow comes later,
noted as attended_by); `custody_requests`, what reaches UHHM as the
custodian of the redoal network, on a custodians' desk owners can
watch. Owners invite into both desks. needs.yaml holds the site to
its needs from here on; the lint reads it.
Gone from the live site: the starred-repos card, the proposal editor
with its file picker and /review/[record], and the /applied, /project
and /subscribed followups (now /received/<need>; the unsubscribe move
is /decide/subscribe). The pins move to v0.3.42: `invite:` and
`attended_by` need a portal newer than the v0.3.36 this site runs.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A development-proposal PR previously got no check at all (the workflow
only fired on push to main) - so branch protection's required status
check, the thing that makes the automation's merge_when_checks_succeed
an actual lint gate, had nothing to require. Lint now runs on both
events; the content-reload NATS publish stays push-only.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
lint.sh only ever checked shape (missing id/name, duplicate ids, bad
requirement types) - it never validated transition targets against the
real state machines. question_lint runs portal's actual validation
logic (content::validate_questions) with no portal build required in
this repo's CI, since portal's deploy job now publishes it to a stable
path on the same runner/host.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Validates YAML/required fields/duplicate ids/requirement types with
yq+jq against the exact shape content.rs parses (not a full schema
check - that would mean building portal just to lint content), then
publishes portal.content.reload over NATS so every running portal
instance picks up the change without a restart (portal's own
content::watch_for_reload, added alongside this).