From 527bc581cada649fc1d503c309f19fc501b38cf8 Mon Sep 17 00:00:00 2001 From: Jason Woltje Date: Thu, 3 Sep 2026 10:56:07 -0500 Subject: [PATCH] refactor(layout): role contracts move to roles/ - root is bootstrap-only Owner direction: the repository root holds first-class, bootstrap-required configuration only. conductor-policy.json is a ROLE contract (the conductor's authority), one of scores of future role contracts (agent-policy, coder-policy, ...) - such files get a dedicated home. - roles/conductor-policy.json (git mv) - conductor-apply.sh + test-conductor.sh read the new path - CONDUCTOR.md records the roles/ convention Closes UX follow-up from owner layout review; no issue (convention change). --- docs/plans/CONDUCTOR.md | 7 +++++++ .../conductor-policy.json | 0 scripts/conductor-apply.sh | 8 ++++---- scripts/test-conductor.sh | 13 +++++++------ 4 files changed, 18 insertions(+), 10 deletions(-) rename conductor-policy.json => roles/conductor-policy.json (100%) diff --git a/docs/plans/CONDUCTOR.md b/docs/plans/CONDUCTOR.md index 4f047924..0d5deff5 100644 --- a/docs/plans/CONDUCTOR.md +++ b/docs/plans/CONDUCTOR.md @@ -2,6 +2,13 @@ How the stack orchestrates headless pi workers to do work on itself. +## Role contracts + +Role authority is declared in role contracts, one file per role, under +`roles/` (e.g. `roles/conductor-policy.json`). The repository root holds +only first-class, bootstrap-required configuration; role contracts are +tracked, versioned files whose changes arrive as reviewed commits. + ## Roles | Role | Runs where | Powers | Never has | diff --git a/conductor-policy.json b/roles/conductor-policy.json similarity index 100% rename from conductor-policy.json rename to roles/conductor-policy.json diff --git a/scripts/conductor-apply.sh b/scripts/conductor-apply.sh index 3492dd92..0b305473 100755 --- a/scripts/conductor-apply.sh +++ b/scripts/conductor-apply.sh @@ -3,7 +3,7 @@ # # Usage: scripts/conductor-apply.sh [--dry-run] # -# Policy (conductor-policy.json in the target repo, strictly validated): +# Policy (roles/conductor-policy.json in the target repo, strictly validated): # autoApply.enabled master switch # autoApply.allowedPaths glob allowlist ('dir/**' = everything under dir) # autoApply.suites suite scripts that must pass AFTER applying @@ -27,19 +27,19 @@ cd "$TARGET_ROOT" fail() { echo "conductor-apply: $*" >&2; exit "${2:-1}"; } [ -d .git ] || fail "target is not a git repository: $TARGET_ROOT" 4 -[ -f conductor-policy.json ] || fail "no conductor-policy.json in target" 2 +[ -f roles/conductor-policy.json ] || fail "no roles/conductor-policy.json in target" 2 # ---- policy (strict) ---- POLICY_JSON="$(node -e ' const fs = require("fs"); -const p = JSON.parse(fs.readFileSync("conductor-policy.json", "utf8")); +const p = JSON.parse(fs.readFileSync("roles/conductor-policy.json", "utf8")); if (p.policyVersion !== 1) process.exit(3); if (!p.autoApply || typeof p.autoApply.enabled !== "boolean" || !Array.isArray(p.autoApply.allowedPaths) || !Array.isArray(p.autoApply.suites)) process.exit(3); for (const g of p.autoApply.allowedPaths) { if (typeof g !== "string" || !/^[A-Za-z0-9_.*/-]+$/.test(g) || g.startsWith("/") || g.includes("..")) process.exit(3); } console.log(JSON.stringify(p.autoApply)); -')" || fail "invalid conductor-policy.json" 2 +')" || fail "invalid roles/conductor-policy.json" 2 ENABLED="$(node -e 'console.log(JSON.parse(process.argv[1]).enabled)' "$POLICY_JSON")" [ "$ENABLED" = "true" ] || fail "auto-apply is disabled by policy" 2 diff --git a/scripts/test-conductor.sh b/scripts/test-conductor.sh index e93fea34..da81800d 100755 --- a/scripts/test-conductor.sh +++ b/scripts/test-conductor.sh @@ -40,8 +40,9 @@ check() { git clone -q . "$SANDBOX/repo" # The clone carries committed state only - give the target its policy and # commit it so the tree starts clean (untracked policy would fail target_clean). -cp conductor-policy.json "$SANDBOX/repo/conductor-policy.json" -git -C "$SANDBOX/repo" add conductor-policy.json +mkdir -p "$SANDBOX/repo/roles" +cp roles/conductor-policy.json "$SANDBOX/repo/roles/conductor-policy.json" +git -C "$SANDBOX/repo" add roles/conductor-policy.json git -C "$SANDBOX/repo" -c user.name=suite -c user.email=suite@local commit -q -m policy mkdir -p "$SANDBOX/data/workspaces" "$SANDBOX/data/runs" git clone -q "$SANDBOX/repo" "$SANDBOX/data/workspaces/stack-repo" @@ -66,8 +67,8 @@ target_clean() { [ -z "$(git -C "$TARGET" status --porcelain)" ]; } set_policy() { # enabled suites (commits: the target tree must stay clean) local suites="[\"$2\"]" printf '{"policyVersion":1,"autoApply":{"enabled":%s,"allowedPaths":["scripts/**","docs/**","README.md"],"suites":%s}}' "$1" "$suites" \ - > "$TARGET/conductor-policy.json" - git -C "$TARGET" add conductor-policy.json + > "$TARGET/roles/conductor-policy.json" + git -C "$TARGET" add roles/conductor-policy.json git -C "$TARGET" -c user.name=suite -c user.email=suite@local commit -q -m "policy update" } set_policy true "test-config" @@ -131,9 +132,9 @@ ws_reset # T8/T9: missing run + invalid policy check_rc "missing run exits 4" 4 scripts/conductor-apply.sh r-missing -printf '{"policyVersion":9}' > "$TARGET/conductor-policy.json" +printf '{"policyVersion":9}' > "$TARGET/roles/conductor-policy.json" check_rc "invalid policy exits 2" 2 scripts/conductor-apply.sh "$RUN_OK" -git -C "$TARGET" checkout -q -- conductor-policy.json +git -C "$TARGET" checkout -q -- roles/conductor-policy.json echo echo "selftest: $PASS passed, $FAIL failed"