Skip to content

Resumed session cannot cancel its own job (§7.4) #137

@nficano

Description

@nficano

Category: spec-conformance Severity: blocker
Location: server/session.go:604-616
Spec: ARCP v1.1 §7.4

What

Spec §7.4 reserves cancellation for 'the submitting session'; §7.7 documents that Resume 'Carries cancel authority: Yes'. Authorization here compares the live *session pointer (job.session != s); after resume, server.handshake builds a new *session struct with the same session_id, but job.session still points to the dead struct created at submit time. Any cancel attempted on the resumed connection is rejected with PERMISSION_DENIED, breaking resume-then-cancel.

Evidence

func (s *session) handleJobCancel(env arcp.Envelope) error {
	var req messages.JobCancel
	_ = env.DecodePayload(&req)
	job := s.srv.lookupJob(env.JobID)
	if job == nil { return s.sendError(arcp.CodeJobNotFound, "job "+env.JobID+" not found") }
	if job.session != s { return s.sendError(arcp.CodePermissionDenied, "only the submitting session can cancel") }
	job.cancelWithReason(req.Reason)
	return nil
}

Proposed fix

Authorize cancellation by comparing the session id and principal recorded on the job (e.g., job.sessionID == s.id && job.principal == s.principal), not the *session pointer; on successful resume, also re-point job.session to the live session so outbound sends route through the new outbox.

Acceptance criteria

  • Disconnect → resume with the rotated token → job.cancel succeeds and produces job.cancelled + job.error{CANCELLED}.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions