Implements docs/todo/role-package-abstraction-design.md. Requirements
review (docs/todo/graph-engineering-open-items.md's H section) had already
corrected the requirements doc's own dependency analysis before this;
scope confirmed with user 2026-08-06: relax all 4 org_node CHECK
constraints in one pass, not staged.
Format is JSON, not YAML: 05 §5.7's pure-stdlib constraint rules out
PyYAML, and unlike Skill files (whose YAML frontmatter only Claude Code
itself ever parses, never this codebase's own Python), role.json genuinely
needs to be read by agent_profile.py -- json.load is the zero-risk choice
over a hand-rolled parser.
role.json is a REFERENCE manifest, not a content copy: identity.soul_md_path/
agents_md_path still point at the untouched agents/<role>/SOUL.md/AGENTS.md
files. This is what makes "deployed output must stay identical" close to
automatic -- render_agent_definition()'s body-assembly logic doesn't
change at all, only the frontmatter's data source does (hardcoded
FRONTMATTER dict -> parsed role.json). Verified byte-for-byte: diffed the
old (git HEAD) render_agent_definition() output against the new one for
all three roles, zero differences.
agent_profile.py: ORG_NODES is now discovered by scanning roles/ for
role.json files (_discover_role_ids()) instead of a hardcoded 3-tuple;
FRONTMATTER/_DESCRIPTIONS/_MIGRATED_FROM_PROFILE dicts are gone. Every
public function takes an optional roles_dir override specifically so a
test can prove "a new role needs zero code changes" against a throwaway
fixture directory (NewRoleWithoutCodeChangeTest) rather than asserting it
by inspection.
schema.py: skill.owner_org_node/task.assigned_org_node/work_graph_run.org_node/
agent_profile.org_node relaxed from CHECK(x IN ('planner','coder','validator'))
to CHECK(x != ''). Real blast radius found during design (bigger than the
requirements doc's own risk note): task/work_graph_run/skill are
referenced by several other tables' foreign keys, but since only the
parent tables' own CHECK text changes (child FK definitions are untouched),
only these 4 parent tables need rebuilding -- followed SQLite's documented
procedure for schema changes under active foreign keys (foreign_keys=OFF
for the duration, rebuild, foreign_key_check to confirm nothing went
dangling, foreign_keys=ON). migrate_relax_org_node_constraints() depends
on D's migrate_add_work_graph_fork_columns() having run (called
unconditionally, idempotent) so work_graph_run's rebuild has the right
column set either way. ux_work_graph_run_active and task's 3 indexes
verified to survive the rebuild.
work_graph_run_query.py's own separately-hardcoded _VALID_ORG_NODES
(a real dependency the requirements doc's risk section missed) now reuses
agent_profile.ORG_NODES instead of maintaining a second copy.
Explicitly NOT done (scoped out during requirements review, not an
oversight): frontend/app.js's 4 hardcoded 3-role spots (route regex, sort
order, dropdown, lookup object) -- a new role still won't be recognized by
the PH3 console. A `role` table with real FK referential integrity was
also not built; this uses the simpler "just relax the enum" approach.
Full src suite (623 tests) + supervisor suite (125 tests) pass with zero
regressions.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>