Extend agent principals: a team of their own, and inheritance up a ladder - #5204
Extend agent principals: a team of their own, and inheritance up a ladder#5204vivekchand wants to merge 15 commits into
Conversation
✅ Drift Bot (ClawMetry): no drift detectedDrift Bot analyzed the changed files against this project's blueprints and requirements and found no drift. |
Visual diffComparing 44 of 70 comparison(s) flagged (>1% pixel diff).
Folder: e2cb66b6c3e0. Full PNGs also attached as a workflow artefact. Generated by visual-diff bot. Pixel diffs >1% flagged; eyeball the table before merging. This check is non-blocking — fail = bot bug, not a code problem. |
✅ Drift Bot (ClawMetry): no drift detectedDrift Bot analyzed the changed files against this project's blueprints and requirements and found no drift. |
✅ Drift Bot (ClawMetry): no drift detectedDrift Bot analyzed the changed files against this project's blueprints and requirements and found no drift. |
831359b to
8f934ce
Compare
✅ Drift Bot (ClawMetry): no drift detectedDrift Bot analyzed the changed files against this project's blueprints and requirements and found no drift. |
8f934ce to
896fbfc
Compare
|
vivekchand
left a comment
There was a problem hiding this comment.
Test plan — principal scope ladder (3-level ownership)
Fast checks first:
make lint
python3 -m pytest tests/test_principal_scope_ladder.py -v
make testThe new test file covers all six AC-OBS-004 criteria (12 tests). All should pass before merge.
Manual smoke test — new /api/govern/scopes/ endpoint:
# Write owner at node scope
curl -X POST http://localhost:8900/api/govern/scopes/node/<node_id>/owner \
-H 'Content-Type: application/json' \
-d '{"owner": "alice", "team": "infra"}'
# Verify inheritance: an agent on that node with no explicit owner should inherit alice/infra
curl http://localhost:8900/api/govern/principals | jq '.[] | select(.principal_id == "<pid>") | {owner, owner_source, team, team_source}'
# Expect: owner_source = "node", team_source = "node"
# Write a more-specific agent-level owner; node-level should be shadowed
curl -X POST http://localhost:8900/api/govern/principals/<pid>/owner \
-H 'Content-Type: application/json' \
-d '{"owner": "bob", "team": "ml"}'
curl http://localhost:8900/api/govern/principals | jq '.[] | select(.principal_id == "<pid>") | {owner, owner_source, team, team_source}'
# Expect: owner_source = "agent", team_source = "agent"Edge cases to verify:
- Agent has
ownerset but noteam→teamshould fall through to node-level, then runtime-level (independent resolution) - Node scope key format: must be
"node:<node_id>"(matches_NODE_SCOPE_PREFIXconstant) - Unobserved scope ID in
POST /api/govern/scopes/node/<id>/owner→ should 404 (scope must exist inagent_metaor be observed)
Drift-bot finding — real contradiction, blueprint update required before merge:
The Governance Policy blueprint currently documents a 2-level hierarchy (agent → runtime). This PR implements a 3-level hierarchy (agent → node → runtime). Drift bot correctly flags this as a contradiction, not just undocumented code — so the E2E Gate will stay red until the blueprint is updated.
Action needed: update the "Governance Policy and Approval" blueprint at factory.8090.ai to document the node-scope level, then re-sync (make ac-sync or equivalent) so the E2E Gate can go green.
The implementation itself looks correct — owner and team resolve independently through the ladder, notes takes the first non-empty value, and the schema migration for the team column follows the established _SCHEMA_MIGRATIONS pattern.
Generated by Claude Code
896fbfc to
7604a83
Compare
|
There was a problem hiding this comment.
The blueprint specifies that ownership resolves at agent level first and falls back to the agent's runtime (2-level hierarchy), but the code implements a 3-level hierarchy (agent → node → runtime) as amended in REQ-OBS-004.2 before implementation. The blueprint must be updated to reflect the machine-level scope in the inheritance chain.
| _store_call("set_agent_meta", agent_key=pid, owner=owner, notes=notes) | ||
| _store_call("set_agent_meta", agent_key=pid, owner=owner, | ||
| notes=notes, team=team) | ||
| except Exception: |
There was a problem hiding this comment.
The blueprint does not document the new POST /api/govern/scopes/<node|runtime>/<value>/owner endpoint that allows setting ownership at the machine or runtime level, which enables the multi-level inheritance hierarchy described in REQ-OBS-004.2.
|
blocked on author decision — skipping (auto-mergeability sweep) Drift Bot found 3 drift finding(s) on the head commit. The blueprint contradictions need to be addressed by the author before this PR can be merged. Generated by Claude Code |
Bring branch up to date with main. Co-Authored-By: Claude <noreply@anthropic.com>
|
✨ auto-fixed: merged main into branch to bring it up to date (was BEHIND by multiple commits) Generated by Claude Code |
|
| _store_call("set_agent_meta", agent_key=pid, owner=owner, notes=notes) | ||
| _store_call("set_agent_meta", agent_key=pid, owner=owner, | ||
| notes=notes, team=team) | ||
| except Exception: |
There was a problem hiding this comment.
The blueprint does not document the new POST /api/govern/scopes/<node|runtime>//owner endpoint that allows setting ownership and team attributes at the machine or runtime level, enabling the multi-level inheritance hierarchy.
There was a problem hiding this comment.
The blueprint specifies a 2-level ownership hierarchy (agent → runtime), but the code implements a 3-level hierarchy (agent → node → runtime) as amended in REQ-OBS-004.2. The blueprint must document the machine-level scope and the independent resolution of owner and team attributes.
| @@ -3803,13 +3834,18 @@ def query_agent_principals( | |||
| Each row:: | |||
There was a problem hiding this comment.
The blueprint does not document that team is a separate attribute from owner with independent inheritance up the scope ladder. The code implements them as distinct fields resolving independently per AC-OBS-004.1 and AC-OBS-004.4.
|
| _store_call("set_agent_meta", agent_key=pid, owner=owner, notes=notes) | ||
| _store_call("set_agent_meta", agent_key=pid, owner=owner, | ||
| notes=notes, team=team) | ||
| except Exception: |
There was a problem hiding this comment.
The blueprint does not document the new POST /api/govern/scopes/<node|runtime>//owner endpoint that allows setting ownership and team attributes at the machine or runtime level, enabling the multi-level inheritance hierarchy described in REQ-OBS-004.
There was a problem hiding this comment.
The blueprint specifies a 2-level ownership hierarchy (agent → runtime), but the code implements a 3-level hierarchy (agent → machine → runtime) as amended in REQ-OBS-004.2. The blueprint must be updated to document machine-level scopes and the independent resolution of owner and team attributes.
There was a problem hiding this comment.
The blueprint does not document that team is a separate attribute from owner with independent inheritance up the scope ladder. The code implements both attributes as distinct fields resolving independently per AC-OBS-004.1 and AC-OBS-004.4.
| @@ -6117,13 +6070,18 @@ def query_agent_principals( | |||
| Each row:: | |||
There was a problem hiding this comment.
The query_agent_principals() method implements a 3-level inheritance ladder (agent → node → runtime) per AC-OBS-004.2, but the blueprint's AgentPrincipalRegistry Key Contracts only document a 2-level hierarchy (agent → runtime), omitting the node/machine scope as an intermediate rung.
| """Return the agent_meta key for a machine-wide label on node_id.""" | ||
| return AgentMetaMixin._NODE_SCOPE_PREFIX + str(node_id or "").strip() | ||
|
|
||
| def set_agent_meta( |
There was a problem hiding this comment.
The set_agent_meta() method now accepts team as a distinct, independently updateable parameter per AC-OBS-004.1, but the blueprint's AgentPrincipalRegistry Key Contracts section does not document team as a separate parameter from owner and notes.
| return jsonify({"ok": False, "error": "write failed"}), 200 | ||
| return jsonify({"ok": True, "principalId": pid, "owner": owner}) | ||
| return jsonify({"ok": True, "principalId": pid, "owner": owner, "team": team}) | ||
|
|
There was a problem hiding this comment.
A new endpoint POST /api/govern/scopes/<node|runtime>//owner is implemented to assign ownership and team at machine or runtime scope per AC-OBS-004.2, enabling inheritance across multiple agents, but this endpoint and its Key Contracts are not documented in the blueprint's AgentPrincipalRegistry section.
|
✨ auto-fixed: merged main into branch to resolve stale base and trigger fresh CI Generated by Claude Code |
|
Maintainer bot status (2026-09-13): Action needed: Visit https://factory.8090.ai, find the Blueprint/requirement for the agent principals / team ownership feature, and approve the drift or update the product record. Once Generated by Claude Code |
|
Auto-janitor (mergeability sweep): All other CI legs (Syntax & Lint, API Tests, MOAT, pip install matrix, E2E Browser, Store invariants, Entitlement API, etc.) are green on the current head. Generated by Claude Code |
|
Autonomous maintainer audit (2026-09-13) All major CI jobs are green on this PR. The single blocker is: The E2E Gate exits immediately because Drift Bot has already posted a failure. Despite this PR citing a proper 8090 factory product record (REQ-OBS-004), the Drift Bot is reporting failure. Possible causes:
What's needed: Re-sync the Drift Bot against the amended blueprint. The PR body explicitly documents the requirement change with the correct reasoning; the implementation appears to match the amended spec. This likely just needs the factory's Drift Bot to re-read the updated blueprint. Generated by Claude Code |
|
blocked on author decision — skipping (auto-mergeability sweep) E2E Gate fails due to Drift Bot — the code contradicts a Blueprint in the 8090 Software Factory. This requires the author to reconcile the implementation with the product record. Generated by Claude Code |
|
Auto-rebase attempted; aborted due to 80+ add/add conflicts between this branch and main. Both sides added the same files (i18n docs, new test files, ci.yml, CHANGELOG.md, app.js, MODULE_MAP.md, etc.) across 80+ paths — well beyond the 5-file mechanical-resolution limit. This needs a manual rebase or the GitHub "Update branch" (merge) button to bring it up to date with main. Generated by Claude Code |
|
blocked on author decision — skipping (auto-mergeability sweep) E2E Gate (required) is failing because the Drift Bot leg reports a blueprint contradiction ( Generated by Claude Code |
|
Automated sweep — mergeability blocker identified E2E Gate fails on this PR due to Drift Bot ( Root cause: The Governance Policy blueprint at factory.8090.ai documents a 2-level hierarchy (agent → runtime), but this PR implements a 3-level hierarchy (agent → node → runtime). Drift Bot flags the implementation as contradicting the documented architecture. What's needed to unblock:
Additionally, this branch is behind No code changes were made by this automated sweep. Generated by Claude Code |
|
Automated maintainer check — action needed The E2E Gate is blocked by a Drift Bot failure. Drift Bot is a required leg that verifies the diff matches the product Blueprint from 8090 Software Factory. It fails immediately at [0s]: This is not a transient failure — Drift Bot catches "this diff contradicts a Blueprint" and cannot be resolved by re-running CI. Needed from a human: Review the Blueprint for this feature in 8090 Software Factory and reconcile the implementation. Either update the code to match the Blueprint, or update the Blueprint to reflect the intended design, and re-run CI. This PR cannot be merged until Drift Bot passes. Generated by Claude Code |
|
Blocked by Drift Bot failure. The E2E Gate log shows Generated by Claude Code |
|
blocked on author decision — skipping (auto-mergeability sweep): E2E Gate fails on Generated by Claude Code |
|
Automated maintainer run blocked: E2E Gate failing because the Drift Bot check ( All other CI legs (39 checks) are green. Generated by Claude Code |
|
blocked on author decision — skipping (auto-mergeability sweep) Blocker: Generated by Claude Code |
|
Autonomous maintainer sweep (2026-09-13) Status: Blocked by Drift Bot — 7 blueprint drift findings, all in the same blueprint. All CI checks pass (API tests, MOAT, store invariants, E2E, install matrix, etc.). The only blocker is the Root cause: The "Governance Policy and Approval" blueprint was not updated when the code added:
All 7 Drift Bot findings point to the same blueprint: https://factory.8090.ai/project/b415065f-ab2f-4f53-8864-0c009fd098cb/blueprints/b1dc4360-9a11-4e4b-9488-9b5ddead1cdc Human action needed: Update the blueprint at the link above to document:
The PR body already has the narrative (see "What this adds" section). The blueprint update should mirror it. Once the blueprint is updated and Drift Bot re-checks, the E2E Gate will pass. This cannot be auto-resolved from the sandbox — it requires access to the 8090 Software Factory product portal. Generated by Claude Code |
|
| @@ -6117,13 +6070,18 @@ def query_agent_principals( | |||
| Each row:: | |||
There was a problem hiding this comment.
The query_agent_principals() method implements a 3-level inheritance ladder (agent → node → runtime) where owner and team resolve independently, but the blueprint only documents 2-level inheritance (agent → runtime), omitting the node/machine scope as an intermediate rung.
| """Return the agent_meta key for a machine-wide label on node_id.""" | ||
| return AgentMetaMixin._NODE_SCOPE_PREFIX + str(node_id or "").strip() | ||
|
|
||
| def set_agent_meta( |
There was a problem hiding this comment.
The set_agent_meta() method now accepts team as a distinct, independently updateable parameter separate from owner and notes, but the blueprint's AgentPrincipalRegistry Key Contracts section does not document team as a separate parameter.
| return jsonify({"ok": False, "error": "write failed"}), 200 | ||
| return jsonify({"ok": True, "principalId": pid, "owner": owner}) | ||
| return jsonify({"ok": True, "principalId": pid, "owner": owner, "team": team}) | ||
|
|
There was a problem hiding this comment.
A new endpoint POST /api/govern/scopes/<node|runtime>//owner is implemented to assign ownership and team at machine or runtime scope, enabling inheritance across multiple agents, but this endpoint and its behavior are not documented in the blueprint's AgentPrincipalRegistry Key Contracts section.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0154SEoKpixxxoFgQNvd3rgJ
|
| @@ -6117,13 +6070,18 @@ def query_agent_principals( | |||
| Each row:: | |||
There was a problem hiding this comment.
The query_agent_principals() method implements a 3-level inheritance ladder (agent → node → runtime) where owner and team resolve independently per AC-OBS-004.2, but the blueprint's AgentPrincipalRegistry documentation only describes 2-level inheritance (agent → runtime), omitting the node/machine scope as an intermediate rung.
| """Return the agent_meta key for a machine-wide label on node_id.""" | ||
| return AgentMetaMixin._NODE_SCOPE_PREFIX + str(node_id or "").strip() | ||
|
|
||
| def set_agent_meta( |
There was a problem hiding this comment.
The set_agent_meta() method accepts team as a distinct, independently updateable parameter per AC-OBS-004.1, but the blueprint's AgentPrincipalRegistry Key Contracts section does not document team as a separate parameter.
| return jsonify({"ok": False, "error": "write failed"}), 200 | ||
| return jsonify({"ok": True, "principalId": pid, "owner": owner}) | ||
| return jsonify({"ok": True, "principalId": pid, "owner": owner, "team": team}) | ||
|
|
There was a problem hiding this comment.
A new endpoint POST /api/govern/scopes/<node|runtime>//owner is implemented to assign ownership and team at machine or runtime scope per AC-OBS-004.2, enabling inheritance across multiple agents, but this endpoint is not documented in the blueprint's AgentPrincipalRegistry Key Contracts section.
PR health check · 2026-09-13Blocked — 7 Drift Bot findings on the [Governance Policy and Approval] Blueprint The E2E Gate is failing at second 0 because the code expands the scope of the Blueprint without a corresponding Blueprint update. No amount of local fixes resolves this — the Blueprint at factory.8090.ai needs to be updated first. The 7 gaps Drift Bot identified:
Next step: Update the [Governance Policy and Approval] Blueprint at factory.8090.ai to document items 1–7 above, then re-run CI. Drift Bot re-checks on every push to the PR branch. Smoke test plan (for when Blueprint is updated and CI is green): make test
# Read principals for a runtime
curl -sS "http://localhost:8900/api/govern/principals?runtime=claude_code"
# Set node-level owner with team
curl -sS -X POST http://localhost:8900/api/govern/scopes/node/my-box/owner \
-H 'Content-Type: application/json' \
-d '{"owner":"Ada","team":"Platform"}'
# Verify inheritance: agent-level owner should take precedence over node
# (set agent owner and confirm it wins in the resolved principal response)No code changes are needed on this PR unless the Blueprint update reveals an inconsistency. The implementation looks correct against the intent; it just outran the documented spec. Generated by Claude Code |
Product record: https://factory.8090.ai/project/b415065f-ab2f-4f53-8864-0c009fd098cb/requirements/d518c6c3-eb50-4b0f-9ed0-59440380b7bf (REQ-OBS-004)
Risk: one new nullable column (
agent_meta.team) and a deeper inheritance chain, which means an owner can appear without anyone naming that agent — the confusion the existing rung-reporting was built to prevent. Mitigated byowner_source/team_sourcenaming the rung, guarded bytest_each_value_names_its_rung. No session-record change, no ingest change, no re-stamp. Undone by dropping the column.The call
I built a second ownership model in #5165. Principals are better and it isn't close — so that PR is closed and this keeps only the gap.
Identity there is derived, not stamped: a hash of
(node_id, runtime, agent_id), all of whichsessionsalready carries. No migration, retroactive by construction, cannot go stale. My version persisted owner/team onto session rows at ingest — buying a cheapWHEREand paying with a migration, a full-table re-stamp inside the write lock on an ordinary label edit, and stored values that drift from the labels producing them. Against a columnar store, that's a bad trade. Deriving won, and the stamping version was mine.What principals genuinely didn't cover
Inheritance was one rung deep. An agent inherited only its runtime's owner, so "everything on this build box belongs to Platform" was unsayable and a fleet had to be labelled agent by agent — the labelling that never gets done, which is why ownership renders empty.
Team shared the owner's free-text field.
agent_metawas(agent_key, owner, notes), and the route said "claim it for a person or team". One field, two questions.What this adds
teamas its own column, and the fallback becomes a ladder — agent → machine → runtime — with owner and team resolving independently:A machine can belong to a team while a person owns one agent on it. Bind both to one rung and that pair is unexpressible.
New:
POST /api/govern/scopes/<node|runtime>/<value>/owner, which validates the scope was actually observed — same reason the principal write checks its id.The requirement changed before the code did
Written the new way (FLYWHEEL §0c): REQ-OBS-004 first. Implementation then found it wrong — a principal routinely spans workspaces, so a workspace rung has nothing unambiguous to inherit from, and adding workspace to identity would split one agent into several.
AC-OBS-004.2was amended to machine-then-runtime, with the reason, before any code. That's the order working.REQ-OBS-RSO-004and the two blueprint sections describing the stamped model are marked withdrawn/superseded, so nobody builds from a spec we rejected.Tests
11 new. The load-bearing one is
test_ownership_is_not_stored_on_sessions, which asserts the columns from #5165 never come back, andtest_a_label_applies_to_history_already_collected, which pins the property stamping gives up. Ratchet 43/108 → 49/114.