feat(ops): add workspace MCP deployment overlays

This commit is contained in:
Codex
2026-07-19 12:34:06 +03:00
parent 56b766b23b
commit 847a08da93
28 changed files with 9865 additions and 0 deletions
@@ -0,0 +1,280 @@
# Architecture: NODE.DC Tasker Codex API
Last updated: 2026-07-18.
## Goal
Give selected NODE.DC users a controlled way to connect their local Codex or another AI coding agent to Tasker so the agent can maintain project work items according to NODE.DC task-card rules.
The agent can create projects inside an explicitly granted workspace, create and
update cards, move them through states, write structured task documentation,
update checkers, add comments and bounded file attachments, assign existing
workspace users to project cards when permitted, and apply labels. It cannot
delete cards or projects, manage workspace settings, bypass workspace/project
grants, or call arbitrary Tasker endpoints.
## Product boundary
This module is a standalone service:
```text
Launcher / Hub
owns platform entitlement: can this user/client use Codex agents?
Tasker / Operational Core
owns workspaces, projects, issues, labels, states, members, comments
Agent Gateway
owns agents, tokens, scopes, grants, MCP tools, audit, rate limits
```
The service is deployed as its own Docker container and has its own repository and database. It may call Launcher and Tasker through internal APIs, but it does not read or write either database directly.
## Runtime shape
```text
Local Codex
-> independent Ops and read-only Ontology MCP servers over HTTPS
-> Agent Gateway public endpoints (/mcp and /ontology-mcp)
-> scope and grant checks
-> Tasker internal adapter
-> Tasker domain models
```
```text
User UI
-> Launcher entitlement
-> Tasker workspace feature settings
-> Agent Gateway agent/token management
```
## Components
### Launcher integration
Launcher decides whether a user can use the module at all.
Required Launcher concepts:
- service: `operational-core`
- module entitlement: `codex_agents`
- scope owner: client/user/access matrix
- open contour support
- enterprise contour support
Launcher should not create low-level Tasker cards or tokens. It only projects entitlement and global lifecycle state.
### Tasker integration
Tasker shows the feature in `Workspace settings -> Features` only when:
- the current user has Tasker access;
- Launcher access check says the user has `codex_agents`;
- the workspace policy allows this workspace to host agents.
Tasker remains the owner of issues, states, labels, project members, comments, and structured blocks.
### Agent Gateway
Agent Gateway owns:
- agent profiles;
- pairing codes;
- opaque access tokens;
- token hashes;
- grants;
- scopes;
- audit events;
- idempotency keys;
- rate limits;
- MCP tools exposed to local Codex.
It should not execute user code and should not run Codex itself.
Agent lifecycle management is not public. Tasker/Launcher-facing management calls use `NODEDC_AGENT_GATEWAY_INTERNAL_TOKEN`, while external Codex calls use only the opaque agent token issued for one agent. The owner-scoped internal routes verify that the requested agent belongs to the requested `owner_user_id`.
### Tasker internal adapter
The adapter is a narrow Tasker API layer for Agent Gateway. It exists because the current Plane REST API is broad and includes operations the agent must not receive directly.
The adapter should expose intent-level operations:
- list allowed workspaces/projects;
- read project states, labels, members;
- create a project within a granted workspace after Tasker admin revalidation;
- search/list issues;
- read one issue with full comments and attachment metadata;
- create issue;
- patch allowed issue fields;
- append comment;
- attach a bounded base64 file through Tasker-owned object storage;
- update NODE.DC structured blocks;
- add existing workspace member to project when explicitly allowed;
- publish audit/realtime events.
It must reject delete, archive, workspace settings, project deletion, invite
creation, arbitrary path/URL attachments, and arbitrary path proxying.
## Data model
Initial Agent Gateway entities:
```text
agent
id
owner_user_id
owner_email
display_name
avatar_url
status: active | disabled | revoked
created_at
updated_at
agent_token
id
agent_id
token_hash
name
purpose: ops | ontology
status: active | revoked | expired
expires_at
last_used_at
created_at
agent_grant
id
agent_id
workspace_slug
project_id (empty string internally means workspace-level grant)
scopes[]
mode: voluntary | reporting
created_by_user_id
created_at
updated_at
pairing_code
id
agent_id
code_hash
status: active | used | expired | revoked
expires_at
created_at
used_at
agent_audit_event
id
agent_id
event_type
actor_user_id
metadata
created_at
idempotency_key
key
agent_id
request_hash
response_body
created_at
expires_at
```
## Actor model
Writes should be visible as agent actions, not anonymous platform automation.
Recommended Tasker-side identity:
```text
display_name: Codex Agent: <name>
email: agent+<agent_id>@agents.nodedc.local
is_bot: true
owner_user_id: <human user id>
```
For audit and UI, every write must preserve both:
- `agent_id`;
- `owner_user_id`.
If Tasker cannot yet represent owned bot users cleanly, the adapter can initially use a system actor and store the agent metadata in audit payloads, but this should be treated as a migration step, not the final model.
## Capability model
Allowed initial scopes:
```text
workspace:read
project:read
project:create
project:member:add_existing
issue:read
issue:create
issue:update
issue:move
issue:comment
issue:label
issue:assign
issue:structured_blocks:write
issue:attachment:write
```
Explicitly denied for MVP:
```text
issue:delete
issue:archive
comment:delete
label:delete
state:create
state:delete
project:delete
workspace:settings
workspace:member:invite
workspace:member:remove
raw_tasker_api
```
Deleting or archiving a card remains a human-only operation.
## Two operating modes
### Voluntary mode
The user creates an agent and gives its token to their local Codex. Codex updates Tasker when the user asks it to.
This mode is user-owned and best for personal development workflow.
### Reporting mode
The enterprise/project admin requires agent reporting for a project. The developer still runs local Codex, but the expected workflow is:
- start work session;
- update active issue/checker;
- append implementation notes;
- finish work session.
This mode is policy-visible but cannot be fully enforced if Codex runs entirely on the developer machine outside a managed wrapper. The system can enforce token scope and show stale/missing reports, but it cannot force an arbitrary local agent to report unless the organization distributes a managed Codex config or wrapper.
## Deployment
Initial local target:
```text
ops-agents.local.nodedc -> Agent Gateway
```
Production-like domains should follow platform conventions:
```text
ops-agents.nodedc.ru or ops-agents.<deployment-domain>
```
The service should support:
- local `.env`;
- staging `.env.staging`;
- production secret store;
- Docker image build;
- container startup migrations;
- health endpoint;
- preflight script validating URLs/secrets.
@@ -0,0 +1,418 @@
# MCP Tools Contract
Last updated: 2026-07-18.
## Position
The current Tasker / Plane fork does not expose a dedicated MCP server. It exposes REST endpoints and NODE.DC internal endpoints. The new module should expose MCP tools externally and translate those tool calls into validated Tasker adapter calls.
Codex should not call generic Tasker REST directly.
Current implementation status:
- Agent Gateway exposes `/mcp` as JSON-RPC over HTTP.
- Implemented MCP methods: `initialize`, `ping`, `tools/list`, `tools/call`.
- `tools/list` returns only tools allowed by the authenticated agent session scopes.
- `tools/call` uses the same product runtime as REST tool endpoints.
- Server-sent event streaming is intentionally not required for the first product slice.
## Authentication
MCP clients authenticate to Agent Gateway with an opaque agent token.
Recommended transport options:
- HTTPS MCP endpoint for general clients;
- local stdio connector later if useful;
- REST fallback for non-MCP clients.
Current route:
```text
POST /mcp
POST /ontology-mcp
```
`/mcp` accepts only an Ops-purpose token. `/ontology-mcp` accepts only the
separately minted Ontology-purpose token and proxies the read-only Ontology MCP.
The two endpoints, credentials, and MCP server entries remain independent.
Required request headers for authenticated tool calls:
```text
Authorization: Bearer <agent-token>
Accept: application/json
MCP-Protocol-Version: 2025-06-18
```
Token rules:
- token is opaque;
- server stores only token hash;
- token is scoped to one agent;
- token can be revoked immediately;
- token expires;
- owner blocked/annulled disables tokens.
## Tool list
### `tasker_get_agent_instructions`
Returns operating rules, allowed projects, card guide, and reporting expectations.
Required scope:
```text
workspace:read
```
### `tasker_list_projects`
Lists projects granted to the agent.
Required scope:
```text
project:read
```
### `tasker_get_project_context`
Returns project states, labels, members, and card-writing rules.
Required scope:
```text
project:read
```
### `tasker_search_issues`
Searches existing work items within granted projects.
Required scope:
```text
issue:read
```
### `tasker_get_issue`
Returns one issue with description, structured blocks, labels, state, assignees,
full comment bodies, and attachment metadata. The Tasker adapter caps comments at
1000 and reports truncation explicitly.
Required scope:
```text
issue:read
```
### `tasker_create_project`
Creates or idempotently resolves a project inside an explicitly granted
workspace. Only a long-lived agent-scoped session can call it; token-scoped run
grants cannot expand themselves. Tasker revalidates that the human owner is an
active workspace administrator before creating the project. The new project is
then added to the same agent's grants so subsequent card tools can address it.
Required scope:
```text
project:create
```
Allowed fields:
```text
workspace_slug
name
identifier (optional)
description (optional)
idempotency_key
```
### `tasker_create_issue`
Creates a work item.
Required scope:
```text
issue:create
```
Allowed fields:
```text
project_id
name
description_html
detail_layout
state_id
priority
label_ids
assignee_ids
start_date
target_date
parent_id
```
Validation:
- project must be granted;
- state must belong to project;
- labels must belong to project;
- assignees must be active project members;
- detail layout must match NODE.DC structured block schema.
### `tasker_update_issue`
Patches allowed issue fields.
Required scope:
```text
issue:update
```
Allowed fields:
```text
name
description_html
detail_layout
priority
start_date
target_date
parent_id
```
Deletion, archive, and project transfer are not allowed.
### `tasker_move_issue`
Changes issue state and optional sort order.
Required scope:
```text
issue:move
```
Allowed fields:
```text
state_id
sort_order
```
Validation:
- target state must belong to the same project;
- cancelled/completed moves are allowed only if the grant permits them.
### `tasker_set_issue_labels`
Sets or merges labels.
Required scope:
```text
issue:label
```
Validation:
- labels must already exist in project for MVP;
- creating new labels can be added later under `label:create`.
### `tasker_assign_issue`
Sets assignees.
Required scope:
```text
issue:assign
```
Validation:
- assignees must be active project members;
- if the user asks to add a workspace member to the project, use `tasker_add_existing_project_member` first.
### `tasker_add_existing_project_member`
Adds an existing workspace member to a granted project.
Required scope:
```text
project:member:add_existing
```
Validation:
- target user must already be an active workspace member;
- target user role cannot exceed workspace role;
- launcher-managed workspace rules must be respected;
- the request should be traceable to an explicit human instruction or reporting policy.
### `tasker_append_comment`
Adds a comment to an issue.
Required scope:
```text
issue:comment
```
Allowed fields:
```text
comment_html
```
### `tasker_attach_file`
Uploads a bounded file to an issue through Tasker's existing FileAsset and
S3/MinIO storage path. The MCP accepts base64 content only: it never reads a
client or server filesystem path and never fetches a remote URL. Raw content is
limited to 5 MiB and Tasker rechecks MIME allowlists, workspace storage limits,
project quota, and content deduplication.
Required scope:
```text
issue:attachment:write
```
Allowed fields:
```text
project_id
issue_id
file_name
content_type
content_base64
idempotency_key
```
### `tasker_update_structured_blocks`
Replaces or patches NODE.DC structured blocks in `detail_layout`.
Required scope:
```text
issue:structured_blocks:write
```
Supported blocks:
```text
text
checker
```
Canonical key:
```text
nodedc_structured_blocks
```
The tool should support high-level patch actions:
```text
append_text_block
append_checker
update_checker_item
replace_blocks
append_implementation_note
```
### `tasker_start_work_session`
Starts a reporting session for a project or issue.
Required scope:
```text
issue:comment
```
This is mainly for enterprise reporting mode.
### `tasker_finish_work_session`
Finishes a reporting session and appends summary/validation notes.
Required scope:
```text
issue:comment
issue:structured_blocks:write
```
## Idempotency
All write tools must accept:
```text
idempotency_key
```
Agent Gateway requires this value for all write tools. The key is scoped by agent, hashed together with the tool name and normalized arguments, and stored with a processing/completed state.
Behavior:
- first successful request stores the tool result for 24 hours;
- duplicate key with identical arguments returns the stored result and does not call Tasker again;
- duplicate key with different arguments returns `idempotency_key_conflict`;
- duplicate key while the first request is still processing returns `idempotency_key_in_progress`;
- failed writes release the key so the same operation can be retried.
## Denied tools
These should not exist in MVP:
```text
tasker_delete_issue
tasker_archive_issue
tasker_delete_comment
tasker_delete_label
tasker_delete_project
tasker_invite_workspace_member
tasker_raw_api_request
tasker_attach_server_path
tasker_attach_remote_url
```
## Instruction pack
`tasker_get_agent_instructions` should include the effective card guide. The local Codex setup file should instruct the agent to call this tool before planning Tasker changes.
Agent Gateway also exposes:
```text
GET /api/v1/agent-session/setup
```
This endpoint is authenticated by the same bearer token and returns:
- MCP server config template with `<agent-token>` placeholder;
- tools available to the current agent grants;
- generated AGENTS.md content with NODE.DC Tasker operating rules;
- no raw token echo.
Minimum instruction:
```text
Before creating or updating Tasker cards, call tasker_get_agent_instructions.
Use Tasker for durable project planning, current architecture, planned architecture, stage checkers, implementation notes, and validation results.
Do not create multiple top-level cards for substeps of one product topic.
Do not delete or archive cards.
Do not operate outside granted projects.
```
@@ -0,0 +1,217 @@
# Tasker API Audit
Last updated: 2026-05-14.
## Summary
Current Tasker / Plane fork is partially ready for the Codex Agent API use case through existing REST endpoints and NODE.DC structured blocks. It is not ready as a direct external API for agents because it has broad routes, session-oriented permissions, delete/archive endpoints, and no MCP layer.
The correct approach is to add a narrow internal Tasker adapter for Agent Gateway instead of exposing raw Plane API to local Codex.
## Existing useful API surface
### Issues
Routes exist for issue list/create/update/retrieve:
```text
GET /api/workspaces/:slug/projects/:project_id/issues/
POST /api/workspaces/:slug/projects/:project_id/issues/
GET /api/workspaces/:slug/projects/:project_id/issues/:issue_id/
PATCH /api/workspaces/:slug/projects/:project_id/issues/:issue_id/
```
The same route also supports `DELETE`, but Agent Gateway must never expose it.
Existing serializers already support:
- name;
- state;
- priority;
- dates;
- labels;
- assignees;
- parent issue;
- description HTML;
- `detail_layout`.
Validation already checks:
- assignees are active project members with sufficient role;
- labels belong to project;
- state belongs to project;
- parent belongs to workspace/project;
- description HTML is sanitized.
### Structured blocks
NODE.DC structured task content lives in:
```text
Issue.detail_layout["nodedc_structured_blocks"]
```
Known block types:
```text
text
checker
```
This is the right storage layer for:
- current architecture;
- planned architecture;
- stages;
- checkers;
- implementation notes.
Tasker already computes checker progress from this structure.
### Comments
Routes exist:
```text
GET /api/workspaces/:slug/projects/:project_id/issues/:issue_id/comments/
POST /api/workspaces/:slug/projects/:project_id/issues/:issue_id/comments/
PATCH /api/workspaces/:slug/projects/:project_id/issues/:issue_id/comments/:comment_id/
DELETE /api/workspaces/:slug/projects/:project_id/issues/:issue_id/comments/:comment_id/
```
Agent Gateway should expose comment creation and possibly own-comment edit later, but not comment deletion.
### Labels
Routes exist:
```text
GET /api/workspaces/:slug/projects/:project_id/issue-labels/
POST /api/workspaces/:slug/projects/:project_id/issue-labels/
PATCH /api/workspaces/:slug/projects/:project_id/issue-labels/:label_id/
DELETE /api/workspaces/:slug/projects/:project_id/issue-labels/:label_id/
```
MVP should let agents apply existing labels. Creating labels can be added later under an explicit admin scope.
### States
Routes exist:
```text
GET /api/workspaces/:slug/projects/:project_id/states/
PATCH /api/workspaces/:slug/projects/:project_id/states/:state_id/
```
Agent Gateway should allow moving issues to existing states. It should not allow state creation/deletion in MVP.
### Project members
Routes exist:
```text
GET /api/workspaces/:slug/projects/:project_id/members/
POST /api/workspaces/:slug/projects/:project_id/members/
PATCH /api/workspaces/:slug/projects/:project_id/members/:member_id/
DELETE /api/workspaces/:slug/projects/:project_id/members/:member_id/
```
Current code checks workspace membership and blocks launcher-managed workspace self-management.
Agent Gateway may expose `add_existing_project_member` only with an explicit scope and only for existing workspace members.
## Missing MCP layer
No dedicated MCP server exists in Tasker today. References to MCP are documentation/guideline-oriented, not an operational API server.
Required new layer:
```text
Agent Gateway MCP tools -> Agent Gateway service -> Tasker internal adapter
```
Tasker should not become the MCP host. Keeping MCP in Agent Gateway preserves standalone Tasker and keeps the external agent surface outside Plane.
## Required Tasker adapter additions
Add internal endpoints under a namespace such as:
```text
/api/internal/nodedc/agent/...
```
Current implemented adapter routes:
```text
POST /api/internal/nodedc/agent/projects/resolve
POST /api/internal/nodedc/agent/projects
GET /api/internal/nodedc/agent/projects/:project_id/context
GET /api/internal/nodedc/agent/issues?project_id=...
POST /api/internal/nodedc/agent/issues
GET /api/internal/nodedc/agent/issues/:issue_id
PATCH /api/internal/nodedc/agent/issues/:issue_id
POST /api/internal/nodedc/agent/issues/:issue_id/attachments
POST /api/internal/nodedc/agent/issues/:issue_id/move
POST /api/internal/nodedc/agent/issues/:issue_id/comments
PUT /api/internal/nodedc/agent/issues/:issue_id/labels
PUT /api/internal/nodedc/agent/issues/:issue_id/assignees
```
The implemented adapter uses NODE.DC internal bearer auth and receives normalized agent metadata in headers:
```text
X-NODEDC-Agent-Id
X-NODEDC-Agent-Owner-User-Id
X-NODEDC-Agent-Token-Id
```
The current adapter creates or reuses a dedicated bot actor with email `agent+<agent_id>@agents.nodedc.local` and `bot_type=nodedc_codex_agent`.
Issue detail returns full comment bodies plus attachment metadata. Project
creation is workspace-admin gated and idempotent by agent external id.
Attachment upload uses Plane's existing FileAsset, S3/MinIO, quota, MIME, and
deduplication implementation; it accepts no server path or remote URL.
These endpoints must use `NODEDC_INTERNAL_ACCESS_TOKEN` / `PLANE_NODEDC_ACCESS_TOKEN` style auth and must be callable only from Agent Gateway.
Suggested adapter endpoints:
```text
POST /api/internal/nodedc/agent/context/
POST /api/internal/nodedc/agent/issues/search/
POST /api/internal/nodedc/agent/issues/
PATCH /api/internal/nodedc/agent/issues/:issue_id/
POST /api/internal/nodedc/agent/issues/:issue_id/comments/
POST /api/internal/nodedc/agent/issues/:issue_id/structured-blocks/
POST /api/internal/nodedc/agent/projects/:project_id/members/add-existing/
```
The adapter should receive normalized Agent Gateway metadata:
```text
agent_id
owner_user_id
owner_email
workspace_slug
project_id
scopes
idempotency_key
```
The adapter should validate Tasker domain rules and return stable errors rather than leaking raw Plane serializer details.
## Why raw Plane API is not enough
Raw Plane API is too broad for external agents:
- it includes delete/archive routes;
- it assumes a session user, not an external agent identity;
- it has more fields than agents should control;
- it does not know Launcher module entitlements;
- it does not have agent idempotency;
- it does not produce agent-specific audit by default;
- it is not MCP-native.
## Compatibility note
Tasker must remain standalone-capable. All agent-specific behavior should be disabled when NODE.DC Agent Gateway env vars are absent.
@@ -0,0 +1,157 @@
# Threat Model
Last updated: 2026-07-18.
## Security objective
Let external local Codex agents maintain Tasker cards without turning Tasker into an open automation surface.
## Main threats
### Raw Tasker access leakage
Risk: a user copies a broad Tasker token or cookie into local Codex, allowing arbitrary API calls.
Mitigation:
- never issue Plane session cookies to agents;
- never expose raw Tasker API tokens;
- use opaque Agent Gateway tokens;
- only expose allowlisted MCP tools.
### Project scope escape
Risk: an agent writes to another project or workspace.
Mitigation:
- Agent Gateway grants are project-scoped;
- Tasker adapter revalidates workspace/project membership;
- every tool requires explicit `project_id`;
- gateway rejects projects outside grant set.
- project creation requires `project:create` on a workspace grant, an
agent-scoped token, and a second workspace-admin check inside Tasker;
- token-scoped AI Workspace sessions cannot add projects to their own grants.
### Attachment exfiltration or storage abuse
Risk: an agent reads arbitrary host files, fetches a remote payload, or bypasses
Tasker storage quotas through the attachment tool.
Mitigation:
- MCP accepts explicit base64 bytes only, never filesystem paths or remote URLs;
- Agent Gateway limits decoded content to 5 MiB and never logs the base64 body;
- Tasker enforces MIME allowlists, workspace storage limits, project quota,
S3/MinIO ownership, and content deduplication.
### Cross-service token confusion
Risk: an Ops token is replayed against Ontology or an Ontology token gains Tasker
write capabilities.
Mitigation:
- setup redemption mints two independent opaque token records;
- token purpose is persisted and checked on every endpoint;
- `/mcp` accepts only `ops`, `/ontology-mcp` only `ontology`;
- Ontology upstream access token remains server-side.
### Destructive action
Risk: an agent deletes or archives cards, labels, comments, projects, or members.
Mitigation:
- no delete/archive MCP tools in MVP;
- adapter rejects delete/archive intents;
- raw API proxy is forbidden.
### Privilege confusion
Risk: an agent acts as the human user and hides automation history.
Mitigation:
- create dedicated agent identity;
- store owner user separately;
- every audit event includes both `agent_id` and `owner_user_id`;
- UI displays agent-originated changes.
### Prompt injection
Risk: text inside a card tells Codex to exfiltrate token or call forbidden tools.
Mitigation:
- MCP tools enforce server-side scopes;
- instruction pack says Tasker content is untrusted;
- Gateway never exposes secrets through read tools;
- deny arbitrary HTTP fetch/proxy tools.
### Token theft
Risk: local token leaks from developer machine.
Mitigation:
- token hash storage;
- expiry;
- immediate revoke;
- last used metadata;
- rate limits;
- optional IP/device binding later.
### Lifecycle API exposure
Risk: an external caller creates agents, grants projects, or mints tokens without going through Launcher/Tasker entitlement.
Mitigation:
- lifecycle routes require `NODEDC_AGENT_GATEWAY_INTERNAL_TOKEN`;
- owner-scoped routes verify `owner_user_id` against the stored agent owner;
- external Codex tokens can call only agent-session, setup, tool, and MCP routes;
- raw agent token is returned only once on token creation.
### Owner lifecycle bypass
Risk: blocked/annulled user keeps active agent token.
Mitigation:
- Gateway checks Launcher owner status;
- blocked/annulled owner disables agent tokens;
- periodic sync plus request-time access check.
### Replay and duplicate writes
Risk: network retry creates duplicate cards/comments.
Mitigation:
- required idempotency keys for write tools;
- store operation result by agent and idempotency key;
- reject same key with different arguments;
- release failed writes so safe retries can run again.
### Reporting mode false confidence
Risk: enterprise admin assumes local Codex must report, but the developer bypasses the managed config.
Mitigation:
- UI distinguishes `connected`, `stale`, `never connected`;
- reporting mode is visibility and policy, not hard enforcement, unless a managed wrapper is used;
- CI/workflow checks can require Tasker session updates later.
## Hard rules
- No database access from Agent Gateway to Tasker DB.
- No arbitrary Tasker HTTP proxy.
- No user session cookie reuse.
- No delete/archive tools in MVP.
- No secrets in generated markdown instruction files.
- No token logging.
- No frontend access to service secrets.
- No cross-use between Ops and Ontology bearer tokens.
- No attachment from a filesystem path or remote URL.