feat(server): add access control - #1398
Conversation
…s-control-implementation
…ess-control-implementation
…s-control-implementation
…s-control-implementation
…ess-control-implementation
…s-control-implementation # Conflicts: # tests/test_transport.py
…s-control-implementation
…s-control-implementation # Conflicts: # src/powercontext/builtin/runtime/application.py # src/powercontext/builtin/runtime/composition.py # src/powercontext/client/__init__.py # src/powercontext/client/client.py # src/powercontext/server/app.py
…s-control-implementation # Conflicts: # docs/en/docs/reference/configuration.md # docs/zh/docs/reference/configuration.md # integrations/dsh/plugins/powercontext/lib/index.js # integrations/dsh/plugins/powercontext/openapi/powercontext.yaml # integrations/dsh/plugins/powercontext/src/operations.generated.ts # integrations/opencode/plugins/powercontext/lib/index.js # integrations/opencode/plugins/powercontext/src/operations.generated.ts # integrations/pi/plugins/powercontext/src/operations.generated.ts # openapi/powercontext.yaml # src/powercontext/builtin/persistence/cursors.py # src/powercontext/client/__init__.py # src/powercontext/client/client.py # src/powercontext/http/__init__.py # src/powercontext/http/_generated/models.py # src/powercontext/http/_generated/operations.py # src/powercontext/http/_generated/schema.py # src/powercontext/server/app.py # src/powercontext/server/factory.py # src/powercontext/server/static/skills.js # src/powercontext/server/templates/pages/skills.html # src/powercontext/server/web.py # tests/e2e/real_experience_skill/harness.py # tests/test_api_contract.py # tests/test_dashboard.py
|
…s-control-implementation # Conflicts: # integrations/dsh/plugins/powercontext/lib/index.js # integrations/dsh/plugins/powercontext/openapi/powercontext.yaml # integrations/dsh/plugins/powercontext/src/operations.generated.ts # integrations/opencode/plugins/powercontext/lib/index.js # integrations/opencode/plugins/powercontext/src/operations.generated.ts # integrations/pi/plugins/powercontext/src/operations.generated.ts # openapi/powercontext.yaml # scripts/generate_api.py # src/powercontext/builtin/artifacts/handoff/service.py # src/powercontext/client/client.py # src/powercontext/http/__init__.py # src/powercontext/http/_generated/models.py # src/powercontext/http/_generated/operations.py # src/powercontext/http/_generated/schema.py # src/powercontext/server/app.py # src/powercontext/server/settings.py # src/powercontext/server/web.py # tests/test_api_contract.py # tests/test_client.py # tests/test_dashboard.py
|
Following up from #1395 — congrats on landing this so quickly, the boundary design looks clean (single PEP, fail-closed modes, data-minimized audit are all the right calls). Since the Fit check against the built-in provider's model. The PR's hierarchical RBAC (subject / action / resource / context + role bindings) maps 1:1 onto casbin's
Concrete offer: I'm happy to prototype a One question: for the |
|
@tlyyxjz Thanks for the thoughtful offer. This PR already includes an embedded CasbinAuthorizationProvider and open_casbin_access_control. In the first version, the canonical relational Access repository remains the source of truth for bindings and ownership; the adapter materializes active relationships into a fresh Casbin enforcer for decisions instead of maintaining a second persisted Casbin policy store. RelationshipWriter is intentionally separate from AuthorizationProvider. An external decision provider may supply a paired writer and declare relationship_management=true; without one, binding mutations fail with relationship_management_unavailable and relationships are administered in the external system. Receiver and other bindings are therefore not restricted to the built-in store. Also, handoff.receiver targets the logical Handoff identity across revisions, rather than an exact-revision grant. I clarified this composition and source-of-truth boundary in f6d2600. A native Casbin-backed writer could be a useful follow-up if it preserves the same idempotency, versioning, ownership, audit, and safe-filtering contracts. |
…s-control-implementation # Conflicts: # docs/en/docs/reference/configuration.md # docs/zh/docs/reference/configuration.md # integrations/dsh/plugins/powercontext/lib/index.js # integrations/dsh/plugins/powercontext/src/operations.generated.ts # integrations/opencode/plugins/powercontext/src/operations.generated.ts # integrations/pi/plugins/powercontext/src/operations.generated.ts # openapi/powercontext.yaml # scripts/generate_api.py # src/powercontext/client/client.py # src/powercontext/http/__init__.py # src/powercontext/http/_generated/models.py # src/powercontext/http/_generated/operations.py # src/powercontext/http/_generated/schema.py # src/powercontext/server/app.py # src/powercontext/server/factory.py # tests/test_client.py # uv.lock
…s-control-implementation
|
Thanks for the detailed breakdown — that composition makes sense: enforcer materialized fresh from the relational source of truth on each decision cycle avoids the dual-write consistency problem entirely. Two follow-ups on the native Casbin-backed RelationshipWriter you mentioned:
If the spec looks like a fit, we're glad to prototype a writer + filtered-adapter sync and open a draft PR for review. |
|
@tlyyxjz Thanks for offering to work on this. I'd be happy to review a separate draft PR for a native Casbin writer. Starting with the contract and a small working example makes sense. The most useful references are:
For the Handoff identity, this is the resource we use: ResourceRef.artifact(
"scope-a",
family="handoff",
artifact_id="handoff-a",
)Its canonical key includes the scope, family, and artifact ID. It has no revision field. A On idempotency, Your suggestion to keep writes behind the existing service boundary works for authorization checks and audit emission. The storage side still has to implement the version checks and atomic replacement. For For filtering, For the proposed sync, please describe where the binding IDs, request metadata, versions, and ownership records would live, and how writes become visible to decisions. I want to avoid ending up with two independently writable copies of the same relationship. A short explanation of the storage layout and failure behavior would help us review that before the implementation gets large. I'd start the prototype with one Handoff: establish its owner, grant Bob receiver access, publish another revision, replace Bob with Alice, retry that replacement, then revoke it. Run the permission checks and resource listing throughout, including a caller with no grant and a Handoff in another scope. That would give us something concrete to review early. The |
Which issue or RFC does this PR close?
Implements #1395.
Design reference: #1396.
Rationale for this change
The current optional static Bearer token authenticates one deployment-local caller but cannot express per-user, per-scope, or exact-Handoff visibility. Handoff Receipts also record receiver observations; they are not authorization grants.
This change adds an independent Server Access Control boundary so user A can grant user B least-privilege access to one committed Handoff Revision without exposing the surrounding scope. Runtime domain APIs remain free of Principal, role, and permission parameters, and deployments can replace the built-in decision provider and relationship store with an external authorization system.
What changes are included in this PR?
AuthorizationProvider,RelationshipWriter, andAccessAuditStoreboundaries for integrations such as OpenFGA, Casbin, Oso, or an AuthZEN-compatible PDP./v1/access/me, check, batch-check, safe resource listing, role listing, binding management, and audit APIs plus typed Python SDK methods.x-powercontext-accessrequirements and enforce them at one Server PEP before Runtime dependencies or business metrics.disabled,legacy-static-admin, andenforcedrollout modes; the default legacy mode preserves the existing static-token administrator behavior.Are there any user-facing changes?
Yes. Authenticated deployments gain public Access APIs, stable 403 behavior, exact-Handoff receiver grants, Access audit records, and two Access configuration settings. Existing static-token deployments remain compatible through the default
legacy-static-adminmode.The change adds Server-owned Access tables but does not add identity or ACL fields to Handoff, Source, Memory, Work, or other Runtime domain records.
scope_idremains a business partition rather than an authorization credential.How was this change tested?
PRE_COMMIT_HOME=/tmp/powercontext-prek-cache make check.venv/bin/python -m pytest -q -p no:cacheprovider(974 passed, 9 skipped)make contract-test(30 passed)make docs-test.envOceanBase, generation LLM, and Embedding provider:AI usage statement
OpenAI Codex (GPT-5) was used to inspect the repository contracts, implement the Access Control layer and generated API changes, diagnose the real OceanBase SAVEPOINT failure, and run the validation described above. The author directed the design and publication scope.