Skip to content

feat(workcontext): WorkScopeV1 selects a resource owner; containment fails closed without it - #845

Merged
antoinetoussaint-byte merged 1 commit into
mainfrom
feat/work-scope-resource-owner
Oct 26, 2026
Merged

antoinetoussaint-byte merged 1 commit into
mainfrom
feat/work-scope-resource-owner

Conversation

@antoinetoussaint-byte

Copy link
Copy Markdown
Contributor

What we are trying to do

An installed solution becomes a principal of its organization (the solution-principal model, specified in codefly-dev/authority#11). Its roles grant scopes on the data that principal owns, not on the whole kind and not as lists of ids. A capability therefore needs an owner selector, and every reader must fail closed when it cannot compare owners. This is step 1 of the implementation; the authority and the readers depend on it.

The change

  • WorkScopeV1.resource_owner_id = 4 (proto/codefly/base/v0/work_context.proto).
    • It narrows a scope to the resources one principal owns. With resource_ids, a resource is covered only when both match.
    • Validated by buf.validate: at most 512 bytes, no whitespace, no NUL, no *.
    • Go and Python bindings were regenerated with codefly generate proto (--local for Go, --template buf.gen.python.yaml for Python).
  • workcontext.ScopeContained is owner-aware. A parent row naming an owner covers only a child naming that same owner. So:
    • a child may add an owner to a row that names none, but never drop or change one;
    • an ask naming no owner is never covered by an owner-selected row, so every owner-blind check built on ScopeContained fails closed instead of reading the scope as the whole kind. That includes the authority client's Claims.Allows.
    • Mint (Child) and verify (chain attenuation) use this rule unchanged, so signing and verifying carry the owner as part of the scope.
  • Runnable operations never declare an owner (runnable/operation.go). The authority selects the owner per installation; a declared one is refused by name.
  • Docs: docs/work-context.md describes the scope shape and the owner rule.

There is deliberately no Claims.AllowsOwned in core. Core has no Claims; the owner-aware reader question (AllowsOwned, allows_owned, allowsOwned) belongs to the authority client libraries in codefly-dev/authority, built on this ScopeContained.

Reuse

The rule is an added condition in the existing per-row loop of ScopeContained (one if), so every caller inherits it: mint, verify, the runnable admission and the readers. No second containment function.

Classification

This is a fix at the owner: the scope schema and the one attenuation rule.

Tests

  • workcontext/owner_test.go:
    • containment with an owner: same owner, ids within the owner, an owner added to a whole kind or to named ids, another owner, a dropped owner, an id asked without its owner, ids widened, and an owner row beside a plain row;
    • mint, verify and attenuation end to end: a child narrows within the owner; dropping or changing the owner is refused ErrInvalid;
    • malformed owners (surrounding or inner whitespace, a newline, *, 513 bytes) never mint.
  • runnable/operation_test.go: a declared resource owner is refused ("never declared").
  • make buf-lint is clean, and make buf-breaking reports "no breaking change against origin/main".
  • Python contract tests (CI's step) pass: 16 OK. The regenerated binding accepts resource_owner_id.
  • go vet ./... is clean. go test ./... passes in every package (no FAIL). go test -race passes in workcontext/..., runnable/..., policy/... and solutionhost/....

Not run: the sandbox_e2e,nix_required tagged suite and proto_companion_required tests (they touch neither surface).

🤖 Generated with Claude Code

…fails closed without it

WorkScopeV1 gains resource_owner_id (field 4): the scope covers only the
resources that principal owns (an installed solution's principal); with
resource_ids, both must match. ScopeContained treats the owner as a
narrowing: a parent row naming an owner covers only a child naming the same
owner, so a child may add an owner but never drop or change one, and an ask
naming no owner is never covered by an owner-selected row, so owner-blind
checks fail closed. Runnable operations never declare an owner.

Go and Python bindings regenerated with codefly generate proto.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@antoinetoussaint-byte
antoinetoussaint-byte merged commit 8af4b78 into main Oct 26, 2026
10 checks passed
antoinetoussaint-byte added a commit that referenced this pull request Oct 26, 2026
fix(workcontext): owner scope follow-ups from review r109 of #845
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant