Summary
Caller-authorization on MeshJob lifecycle operations. Split from #1074 (the missing-facade concern is mechanical and lands separately; this concern is design-heavy).
Problem
Today, the only enforced authorization on a MeshJob is at submit time — the registry sets submitted_by from the caller's agent_id (dependency_injector.py:541-552). After submit, any agent that knows a job_id can:
Knowing a job_id is not a permission — it's metadata. Job IDs are stored in the registry, observable via traces, persisted in any state agent.
Concrete threat model
In a dogfood deployment (the ai-debate v2.2.4 refactor):
- orch-agent submits
run_debate(debate_id=...) → registers job_id in debate-state (postgres)
- frontend cancel-button calls
end_debate(debate_id) → orch reads job_id from debate-state → (once facade lands) calls mesh.jobs.cancel(job_id)
That works because orch is both the submitter AND the agent exposing cancel — but it's coincidence, not enforcement. Any other agent in the mesh (observer-agent, future agents) that holds the debate_state DI could read the same job_id and call cancel() — there's nothing stopping them.
Proposed authorization matrix
| Op |
Authorized callers |
Rationale |
submit |
any (already enforced — sets submitted_by) |
Job creation is open |
claim |
any agent advertising the capability |
Needed for failover across replicas |
cancel(job_id) |
agent_id == submitted_by |
Owner-only — destructive |
status(job_id) |
submitted_by OR claimer |
Owner needs visibility; claimer needs it for resume |
wait(job_id) |
same as status |
Same reasoning |
post_event(job_id, type, payload) |
needs design — split by event type, or by caller, or both? |
Some events (cancel_requested) are owner-only; some (progress) are claimer-only |
subscribe_events(job_id, ...) |
open question — opt-in by submitter? Anyone in same mesh? Observability sidecars? |
Reading is less destructive but still privacy-relevant |
Enforcement design
Caller identity is already on the wire via the existing mesh transport. The registry HTTP handlers (ent_handlers_jobs.go:924-993 for PostJobEvent, future handler for CancelJob) need to gate on it. Today they don't.
Backward-compat tension
Today's permissive default lets observers/sidecars post into foreign jobs. Tightening would break in-the-wild patterns.
Three options for the rollout shape:
- Per-job opt-in flag at submit time — e.g.,
restrict_lifecycle_to_submitter=True. Default false → flips to true in a future major. Backward-compatible; consistent enforcement per-job once set; user-facing API surface.
- ACL field on the job —
authorized_callers: list[agent_id] with submitted_by always included. More flexible (multiple authorized agents) but more complex.
- Capability-token model — submit returns a token that's required for lifecycle ops. Cleanest theoretically; biggest break with current "knows job_id = can call" semantics.
Option 1 is probably the right first step (simplest, opt-in, doesn't preclude options 2 or 3 later).
Cross-runtime scope
Authorization model needs to ship coherently across Python / TypeScript / Java. The enforcement is registry-side (so language-agnostic), but the SDK helpers need to surface auth-failure errors consistently — likely a new JobAuthorizationError typed exception in each SDK.
Open questions
- Default behavior in the first patch release that ships this: still open (current behavior), or opt-in to tightening?
- Should
subscribe_events authorization differ from status/wait? Reading is less dangerous.
- How does this interact with the
submitted_by semantics during cross-agent submit chains? (Agent A submits on behalf of user U; should U's authorization carry forward, or does A "own" the job?)
- How do we audit auth decisions? Trace events? Registry log entries?
- Observability sidecar pattern — observability needs broad read access to jobs across the mesh. Do they get a system-level "auditor" role? Or do users opt in per job?
Scope of this issue
Design-level. No code in this PR. Output: an agreed-on authorization model, with concrete decisions on the open questions above, and a phased rollout plan.
Related: #1074 (Concern 1 — DDDI-clean facades for cancel/status/wait).
Context
Surfaced dogfooding ai-debate's v2.2.4 refactor (moving from in-process driver to MeshJob substrate). The orch-as-both-submitter-and-canceller setup happens to satisfy the implicit auth, but the gap is real and other deployments won't naturally preserve it.
Summary
Caller-authorization on MeshJob lifecycle operations. Split from #1074 (the missing-facade concern is mechanical and lands separately; this concern is design-heavy).
Problem
Today, the only enforced authorization on a MeshJob is at submit time — the registry sets
submitted_byfrom the caller'sagent_id(dependency_injector.py:541-552). After submit, any agent that knows ajob_idcan:cancel(job_id)/status(job_id)/wait(job_id)(no submitter-check)Knowing a
job_idis not a permission — it's metadata. Job IDs are stored in the registry, observable via traces, persisted in any state agent.Concrete threat model
In a dogfood deployment (the ai-debate v2.2.4 refactor):
run_debate(debate_id=...)→ registersjob_idindebate-state(postgres)end_debate(debate_id)→ orch readsjob_idfromdebate-state→ (once facade lands) callsmesh.jobs.cancel(job_id)That works because orch is both the submitter AND the agent exposing cancel — but it's coincidence, not enforcement. Any other agent in the mesh (observer-agent, future agents) that holds the
debate_stateDI could read the samejob_idand callcancel()— there's nothing stopping them.Proposed authorization matrix
submitsubmitted_by)claimcancel(job_id)agent_id == submitted_bystatus(job_id)submitted_byOR claimerwait(job_id)post_event(job_id, type, payload)cancel_requested) are owner-only; some (progress) are claimer-onlysubscribe_events(job_id, ...)Enforcement design
Caller identity is already on the wire via the existing mesh transport. The registry HTTP handlers (
ent_handlers_jobs.go:924-993forPostJobEvent, future handler forCancelJob) need to gate on it. Today they don't.Backward-compat tension
Today's permissive default lets observers/sidecars post into foreign jobs. Tightening would break in-the-wild patterns.
Three options for the rollout shape:
restrict_lifecycle_to_submitter=True. Default false → flips to true in a future major. Backward-compatible; consistent enforcement per-job once set; user-facing API surface.authorized_callers: list[agent_id]withsubmitted_byalways included. More flexible (multiple authorized agents) but more complex.Option 1 is probably the right first step (simplest, opt-in, doesn't preclude options 2 or 3 later).
Cross-runtime scope
Authorization model needs to ship coherently across Python / TypeScript / Java. The enforcement is registry-side (so language-agnostic), but the SDK helpers need to surface auth-failure errors consistently — likely a new
JobAuthorizationErrortyped exception in each SDK.Open questions
subscribe_eventsauthorization differ fromstatus/wait? Reading is less dangerous.submitted_bysemantics during cross-agent submit chains? (Agent A submits on behalf of user U; should U's authorization carry forward, or does A "own" the job?)Scope of this issue
Design-level. No code in this PR. Output: an agreed-on authorization model, with concrete decisions on the open questions above, and a phased rollout plan.
Related: #1074 (Concern 1 — DDDI-clean facades for cancel/status/wait).
Context
Surfaced dogfooding ai-debate's v2.2.4 refactor (moving from in-process driver to MeshJob substrate). The orch-as-both-submitter-and-canceller setup happens to satisfy the implicit auth, but the gap is real and other deployments won't naturally preserve it.