mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-25 17:45:53 +01:00
docs(runner): reconcile production protocol bindings
This commit is contained in:
1 parent
274f597555
commit
2e2a40dba9
4 files changed
+56
-16
No files matched your search
@@ -6,8 +6,9 @@ This roster is the implementation ledger for binding the canonical Paperclip Run
|
||||
|
||||
- Canonical protocol actions: **41**
|
||||
- Actions in the Codex semantic catalog: **28**
|
||||
- Actions with a real Paperclip service binding: **0**
|
||||
- Actions audited end-to-end over production PRP: **0**
|
||||
- Actions advertised by the production Paperclip authority: **13**
|
||||
- Actions with a real Paperclip service binding: **13**
|
||||
- Actions audited through the shared Paperclip-server PRP route: **13 service bindings; route smoke audit pending**
|
||||
- Existing semantic conformance bridge: five route-backed test operations (`report_progress`, `write_document`, confirmation-only `request_human_input`, `set_dependencies`, and `finish_task`). It is test-only and is not a production binding.
|
||||
|
||||
The generated action metadata currently says `realServiceBinding: unbound` and `prpBindingStatus: audit_pending` for every action. `realBindingStatus: live_codex` means only that a Codex scenario exercised the mock semantic implementation; it must not be interpreted as a production Paperclip binding.
|
||||
@@ -16,15 +17,15 @@ The generated action metadata currently says `realServiceBinding: unbound` and `
|
||||
|
||||
| Group | Actions | Real authority target | Status |
|
||||
| --- | --- | --- | --- |
|
||||
| Active task reads | `get_task_context`, `get_task_history`, `search_tasks` | issue, comment, project, and assignment services | unbound |
|
||||
| Task mutations | `report_progress`, `answer_status_question`, `finish_task`, `block_task`, `request_review`, `set_dependencies`, `create_task` | issue/comment services and native status arbiter | unbound |
|
||||
| Documents and deliverables | `list_documents`, `read_document`, `list_document_revisions`, `write_document`, `register_deliverable` | document and attachment services | unbound |
|
||||
| Human interaction | `request_human_input` | interaction service and continuation scheduler | unbound |
|
||||
| Agents | `list_agents`, `get_agent` | agent service with company scope | unbound |
|
||||
| Approvals | `list_approvals`, `get_approval`, `get_approval_context`, `request_approval`, `decide_approval`, `comment_on_approval` | approval service | unbound |
|
||||
| Active task reads | `get_task_context`, `get_task_history`, `search_tasks` | issue, comment, project, and assignment services | bound; run/issue/agent/company assignment checked on every call |
|
||||
| Task mutations | `report_progress`, `answer_status_question`, `finish_task`, `block_task`, `request_review`, `set_dependencies`, `create_task` | issue/comment services and native status arbiter | `report_progress` bound with durable run receipt; remaining actions withheld pending mutation/status-arbiter audit |
|
||||
| Documents and deliverables | `list_documents`, `read_document`, `list_document_revisions`, `write_document`, `register_deliverable` | document and attachment services | three reads bound; mutations withheld pending idempotency audit |
|
||||
| Human interaction | `request_human_input` | interaction service and continuation scheduler | bound for confirmation, checkbox, questions, suggested tasks, and item verdicts |
|
||||
| Agents | `list_agents`, `get_agent` | agent service with company scope | bound with credential/config redaction and company checks |
|
||||
| Approvals | `list_approvals`, `get_approval`, `get_approval_context`, `request_approval`, `decide_approval`, `comment_on_approval` | approval service | three reads bound; mutations withheld. `decide_approval` is board-user-only in the current product and must not be agent-advertised |
|
||||
| Workspace | `get_workspace_runtime`, `control_workspace_service` | workspace runtime service | unbound |
|
||||
| Scheduling | `schedule_wake` | wakeup service | unbound |
|
||||
| Discovery | tool search and tool schema lookup | authorized catalog projection | mock implementation only |
|
||||
| Discovery | tool search and tool schema lookup | authorized catalog projection | runner-local authorized projection; production authority supplies only the 13 bound definitions |
|
||||
| Administrative surface | `administer_company`, `export_company`, `list_company_skills`, `sync_company_skills`, `list_projects`, `list_goals`, `list_routines`, `manage_routine`, `list_cases`, `upsert_case`, `inspect_operation_result` | existing company-scoped API/services; individually authorization-gated | protocol-only/scenario mock |
|
||||
| Secrets | `list_secret_metadata`, `read_secret_value` | secrets service; metadata/value claims separated and output redacted | protocol-only/scenario mock |
|
||||
| Escape hatch | `generic_api_request` | no production binding planned until a narrow allowlist is designed | test-only |
|
||||
@@ -42,4 +43,14 @@ An action moves from `unbound` only when all of the following are true:
|
||||
|
||||
## Cutover order
|
||||
|
||||
The first production vertical slice is `get_task_context` + `report_progress` + `finish_task`. Next are documents, interactions, task dependencies/creation, approvals, agent/project reads, workspace/scheduling, then the separately privileged administrative and secret surfaces. `generic_api_request` stays unavailable in production.
|
||||
The first production vertical slice is now `get_task_context` + `report_progress` + all five structured `request_human_input` variants, plus bounded document/agent/approval reads. The next slice is document writes and dependency/child-task mutations with durable receipts. Terminal task actions must be reconciled with the native status arbiter before they are advertised. Approval decisions remain board-user-only. Workspace/scheduling follow, then the separately privileged administrative and secret surfaces. `generic_api_request` stays unavailable in production.
|
||||
|
||||
## Production transport status
|
||||
|
||||
Paperclip now owns a shared WebSocket upgrade route at
|
||||
`/api/runner/v1/connect/:runId`. The native executor registers one run-bound
|
||||
`DurablePrpControlPlane` authority, runnerd receives that loopback URL, and the
|
||||
authority still authenticates the one-time bootstrap ticket and durable lease
|
||||
inside PRP. Unknown runs and malformed run IDs fail closed before the protocol
|
||||
handshake. The run ID is a path segment because runnerd deliberately rejects
|
||||
query-bearing WebSocket URLs to avoid URL ambiguity.
|
||||
@@ -10,13 +10,38 @@ export interface CapabilityCanonicalOperation {
|
||||
readonly requiredClaims: readonly string[]; readonly taskModes: readonly CapabilityToolTaskMode[];
|
||||
readonly allowedRoles?: readonly string[]; readonly sideEffectClass: CapabilitySideEffectClass;
|
||||
readonly idempotency: ScenarioChatdempotencyBehavior; readonly disabledByDefault: boolean;
|
||||
readonly realBindingStatus: CapabilityRealBindingStatus; readonly realServiceBinding: "unbound";
|
||||
readonly prpEvidence: string; readonly prpBindingStatus: "audit_pending";
|
||||
readonly realBindingStatus: CapabilityRealBindingStatus; readonly realServiceBinding: string;
|
||||
readonly prpEvidence: string; readonly prpBindingStatus: "audit_pending" | "bound";
|
||||
readonly legacyAliases: readonly string[]; readonly note?: string;
|
||||
}
|
||||
|
||||
const PRODUCTION_BINDINGS: Readonly<Record<string, { service: string; evidence: string }>> = Object.freeze({
|
||||
get_task_context: { service: "PaperclipRunnerToolAuthority + issue/run assignment query", evidence: "shared PRP route + bound-context authorization test" },
|
||||
get_task_history: { service: "issueComments scoped by bound issue", evidence: "shared PRP route + bound-context authorization test" },
|
||||
search_tasks: { service: "issueService.list", evidence: "shared PRP route + company scope test" },
|
||||
report_progress: { service: "issueService.addComment + heartbeatRuns semanticToolReceipts", evidence: "real service mutation + idempotent replay test" },
|
||||
request_human_input: { service: "issueThreadInteractionService.create", evidence: "real structured interaction + continuation projection test" },
|
||||
list_documents: { service: "documentService.listIssueDocuments", evidence: "bound issue document read" },
|
||||
read_document: { service: "documentService.getIssueDocumentByKey", evidence: "bound issue/key document read" },
|
||||
list_document_revisions: { service: "documentService.listIssueDocumentRevisions", evidence: "bound issue/key revision read" },
|
||||
list_agents: { service: "agentService.list (redacted projection)", evidence: "company-scoped actor projection" },
|
||||
get_agent: { service: "agentService.getById (redacted projection)", evidence: "company equality + redacted actor projection" },
|
||||
list_approvals: { service: "approvalService.list", evidence: "company-scoped approval projection" },
|
||||
get_approval: { service: "approvalService.getById", evidence: "company equality check" },
|
||||
get_approval_context: { service: "approvalService.getById + issueApprovals", evidence: "company equality + linked-task projection" },
|
||||
});
|
||||
|
||||
export const CAPABILITY_CANONICAL_OPERATIONS: readonly CapabilityCanonicalOperation[] = Object.freeze(
|
||||
PAPERCLIP_PROTOCOL_ACTIONS.map((action) => action.canonical as unknown as CapabilityCanonicalOperation)
|
||||
PAPERCLIP_PROTOCOL_ACTIONS.map((action) => {
|
||||
const operation = action.canonical as unknown as CapabilityCanonicalOperation;
|
||||
const binding = PRODUCTION_BINDINGS[operation.operationId];
|
||||
return binding === undefined ? operation : {
|
||||
...operation,
|
||||
realServiceBinding: binding.service,
|
||||
prpEvidence: binding.evidence,
|
||||
prpBindingStatus: "bound" as const,
|
||||
};
|
||||
})
|
||||
.sort((left, right) => left.operationId.localeCompare(right.operationId)),
|
||||
);
|
||||
const byId = new Map(CAPABILITY_CANONICAL_OPERATIONS.map((operation) => [operation.operationId, operation]));
|
||||
|
||||
@@ -376,7 +376,7 @@ export function renderOperationGroupsDocument(input: OperationGroupsInput): stri
|
||||
"",
|
||||
"### Reconciled semantic-operation ledger",
|
||||
"",
|
||||
"`live_codex` means a live provider dispatcher exists against the deterministic runner/mock port; it does **not** mean a production Paperclip service is bound. Every row remains `realServiceBinding: unbound` until the shared mock/real adapter work lands. `generic_api_request` is test-only and cannot satisfy product coverage.",
|
||||
"`live_codex` means a live provider dispatcher exists. Production binding is tracked separately: `bound` rows are advertised by Paperclip's run-scoped authority over the shared PRP route, while `audit_pending` rows remain unavailable to production agents. `generic_api_request` is test-only and cannot satisfy product coverage.",
|
||||
"",
|
||||
"| Operation | Placement | Claims | Modes / roles | Side effect | Idempotency | Redacts | Mock | Catalogs / current runner | Production / PRP evidence |",
|
||||
"| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |",
|
||||
|
||||
@@ -92,9 +92,13 @@ describe("canonical semantic-catalog reconciliation authority", () => {
|
||||
expect(["none", "required", "recommended"]).toContain(operation.idempotency);
|
||||
expect(typeof operation.redacts).toBe("boolean");
|
||||
expect(["live_codex", "scenario_mock", "test_only"]).toContain(operation.realBindingStatus);
|
||||
expect(operation.realServiceBinding).toBe("unbound");
|
||||
if (operation.prpBindingStatus === "bound") {
|
||||
expect(operation.realServiceBinding).not.toBe("unbound");
|
||||
} else {
|
||||
expect(operation.realServiceBinding).toBe("unbound");
|
||||
}
|
||||
expect(operation.prpEvidence.length).toBeGreaterThan(0);
|
||||
expect(operation.prpBindingStatus).toBe("audit_pending");
|
||||
expect(["audit_pending", "bound"]).toContain(operation.prpBindingStatus);
|
||||
// Scenario-surface ops carry a mock mapping; live-only ops resolve inline.
|
||||
if (operation.surfaces.includes("scenario")) {
|
||||
expect(operation.mockCommandMapping).not.toBeNull();
|
||||
|
||||
Reference in new issue
Block a user