mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-28 12:04:41 +01:00
test: qualify fresh connection credential handoffs
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
b31c3fd98b
commit
bf760912fa
4 files changed
+31
-4
No files matched your search
@@ -219,3 +219,14 @@ The expanded journey proves pending state before human entry, after unrelated bo
|
|||||||
An added actual-auth race then suspended the responsible user inside provider authentication. The request returned 403 but had replaced refs (`/tmp/parity-credential-commit-revocation-red.log`). Configuration now reauthorizes before persistence and inside the reference transaction, so revocation leaves the original refs intact. Final **82 API/OpenAPI tests passed** in fresh PostgreSQL `parity_review_48` (`/tmp/parity-credential-completion-qualified.log`), and final server typecheck/build passed (`/tmp/parity-credential-completion-commit-types.log`, `/tmp/parity-credential-completion-commit-build.log`).
|
An added actual-auth race then suspended the responsible user inside provider authentication. The request returned 403 but had replaced refs (`/tmp/parity-credential-commit-revocation-red.log`). Configuration now reauthorizes before persistence and inside the reference transaction, so revocation leaves the original refs intact. Final **82 API/OpenAPI tests passed** in fresh PostgreSQL `parity_review_48` (`/tmp/parity-credential-completion-qualified.log`), and final server typecheck/build passed (`/tmp/parity-credential-completion-commit-types.log`, `/tmp/parity-credential-completion-commit-build.log`).
|
||||||
|
|
||||||
The preceding full run had one unrelated rate assertion fail as two calls crossed a minute boundary (`/tmp/parity-credential-completion-final-green.log`). The fixture database recorded consumed counters at 23:35 and 23:36 for the same policy and bucket (`/tmp/parity-credential-completion-rate-window-evidence.log`). Rate cases now freeze only `Date`; transport and asynchronous timers remain real. No production rate behavior changed. The final run above includes all rate cases. Companion Slack authorization/installation map entries now explicitly describe the required addressed-human browser-session and consent step; those individual operations retain source-only evidence. Fresh current-head CI and reviews remain required.
|
The preceding full run had one unrelated rate assertion fail as two calls crossed a minute boundary (`/tmp/parity-credential-completion-final-green.log`). The fixture database recorded consumed counters at 23:35 and 23:36 for the same policy and bucket (`/tmp/parity-credential-completion-rate-window-evidence.log`). Rate cases now freeze only `Date`; transport and asynchronous timers remain real. No production rate behavior changed. The final run above includes all rate cases. Companion Slack authorization/installation map entries now explicitly describe the required addressed-human browser-session and consent step; those individual operations retain source-only evidence. Fresh current-head CI and reviews remain required.
|
||||||
|
|
||||||
|
|
||||||
|
### 2026-10-26: unavailable credential repair and distinct fresh handoffs
|
||||||
|
|
||||||
|
The exact `805d165bc3` review found three valid regressions. The actual-auth Slack/GitHub baseline reproduced all three in `/tmp/parity-repair-handoffs-red.log`: complete human replacements failed for disabled, deleted and unreadable old signing secrets; a fresh GitHub draft received `reconnect=1`; and a subsequent private-key-only request collided with the broader import request's idempotency key. The red Slack run also reached a consequential test-message failure because the unrepaired corrupt secret remained bound. That is retained as a consequence of failed repair, not a separate provider outage.
|
||||||
|
|
||||||
|
Implementation `f70c1a05a3` compares each old protected reference separately. Known unavailable secret/version states and typed provider-client failures count as changed; unexpected errors and company/scope failures still propagate before reference writes. The actual human session repairs all three real persisted secret states. A controlled unexpected resolver fault returns 500 and leaves refs unchanged; this is fault-injected resolver evidence, not a reproduced database outage. Opaque generation and verification requirements remain unchanged.
|
||||||
|
|
||||||
|
Saved credential links now use reconnect only for a configured endpoint; fresh GitHub imports retain their webhook-secret form. Idempotency keys include the canonical protected field set and completion mode; matching retries retain their interaction and original generation baseline. The actual run-JWT GitHub journey proves separate broad/private-key handoffs, retry reuse and continued human import. The companion wizard test also submits the fresh saved form and verifies App import rather than reconnect.
|
||||||
|
|
||||||
|
Both focused journeys passed (`/tmp/parity-repair-handoffs-green.log`). All **55 actual-auth integration scenarios** then passed in fresh PostgreSQL `parity_review_51` (`/tmp/parity-repair-handoffs-full.log`). That invocation accidentally named a nonexistent OpenAPI test file, so its reported pass covered only the integration module. The correctly named OpenAPI route suite ran separately and all **27 contracts passed** (`/tmp/parity-repair-handoffs-openapi.log`): 82 tests across the two commands. Server typecheck/build passed (`/tmp/parity-repair-handoffs-types.log`, `/tmp/parity-repair-handoffs-build.log`). All **41 GitHub wizard component cases** and UI typecheck passed (`/tmp/parity-repair-handoffs-ui.log`, `/tmp/parity-repair-handoffs-ui-types.log`). Maps and token gates passed (`/tmp/parity-repair-handoffs-maps.log`, `/tmp/parity-repair-handoffs-tokens.log`). Fresh current-head CI and reviews remain required; no live provider or full local sweep claim is added.
|
||||||
@@ -213,7 +213,7 @@ Gaps: This individual operation has source review but no recorded HTTP verificat
|
|||||||
API: `present` — `POST /api/chat-endpoints/{endpointId}/github/app`; JSON REST.
|
API: `present` — `POST /api/chat-endpoints/{endpointId}/github/app`; JSON REST.
|
||||||
Agent access: `available` — agent REST and default native `search_api` / `call_api` return a saved human credential-entry handoff; agents may send an empty body.
|
Agent access: `available` — agent REST and default native `search_api` / `call_api` return a saved human credential-entry handoff; agents may send an empty body.
|
||||||
Policy: `tools:manage_connections`; [shared management policy](../../doc/plans/2026-10-13-connection-api-parity.md#authorization-contract). A human session imports the App credentials and signing key. Delegated service import is rejected, and agent requests persist no caller-chosen key.
|
Policy: `tools:manage_connections`; [shared management policy](../../doc/plans/2026-10-13-connection-api-parity.md#authorization-contract). A human session imports the App credentials and signing key. Delegated service import is rejected, and agent requests persist no caller-chosen key.
|
||||||
Evidence: `verified` — [route](../../server/src/routes/chat-channels.ts), [provider service](../../server/src/services/chat-channels/github/app-setup.ts), and [actual-auth fixture](../../server/src/__tests__/connection-management.integration.test.ts). Agent key/JWT handoffs leave vault refs unchanged; human import persists credentials, then agent repository discovery and verification continue.
|
Evidence: `verified` — [route](../../server/src/routes/chat-channels.ts), [provider service](../../server/src/services/chat-channels/github/app-setup.ts), and [actual-auth fixture](../../server/src/__tests__/connection-management.integration.test.ts). Agent key/JWT handoffs leave vault refs unchanged. Fresh drafts open the saved import form without the reconnect flag; its component test submits App import with the required webhook secret. Human import persists credentials, then agent repository discovery and verification continue.
|
||||||
Verification: `passed` — deterministic provider fixtures, assembled server and [saved-screen tests](../../ui/src/pages/apps/chat/GitHubChatSetup.test.tsx).
|
Verification: `passed` — deterministic provider fixtures, assembled server and [saved-screen tests](../../ui/src/pages/apps/chat/GitHubChatSetup.test.tsx).
|
||||||
Gaps: Human credential entry is intentional; no live provider qualification.
|
Gaps: Human credential entry is intentional; no live provider qualification.
|
||||||
|
|
||||||
@@ -268,7 +268,7 @@ API: `present` — `POST /api/chat-endpoints/{endpointId}/setup`; JSON REST.
|
|||||||
Agent access: `available` — agent REST and default native `search_api` / `call_api`. Agent-supplied signing/webhook secrets, private keys, client secrets, and project secrets return a saved human credential-entry handoff. Authorized write-only bot-token and API-key setup remains delegated.
|
Agent access: `available` — agent REST and default native `search_api` / `call_api`. Agent-supplied signing/webhook secrets, private keys, client secrets, and project secrets return a saved human credential-entry handoff. Authorized write-only bot-token and API-key setup remains delegated.
|
||||||
Policy: `tools:manage_connections`; [shared management policy](../../doc/plans/2026-10-13-connection-api-parity.md#authorization-contract). The service rejects delegated verification-key entry before provider work or vault writes. Human sessions and verified provider handoffs retain credential entry; agent configuration uses the responsible user's current authority.
|
Policy: `tools:manage_connections`; [shared management policy](../../doc/plans/2026-10-13-connection-api-parity.md#authorization-contract). The service rejects delegated verification-key entry before provider work or vault writes. Human sessions and verified provider handoffs retain credential entry; agent configuration uses the responsible user's current authority.
|
||||||
Evidence: `verified` — [route](../../server/src/routes/chat-channels.ts); reviewed in the working tree based on the source commit above. [Authenticated fixtures](../../server/src/__tests__/connection-management.integration.test.ts) passed with database persistence and actual bearer/session middleware.
|
Evidence: `verified` — [route](../../server/src/routes/chat-channels.ts); reviewed in the working tree based on the source commit above. [Authenticated fixtures](../../server/src/__tests__/connection-management.integration.test.ts) passed with database persistence and actual bearer/session middleware.
|
||||||
Verification: `passed` — actual key/JWT configure/reconnect requests return the addressed handoff with unchanged secret refs and vault signing value. The saved card stays pending through old health, bot-token repair, unchanged signing-key entry and retries. It completes only after a protected value change, renewed webhook verification and current authority; cancellation or user revocation still denies continuation. Credential-write revocation during provider authentication leaves refs unchanged. A human session configures Slack; an agent can repair its bot token and send a policy-governed test message. Native setup receipts are protected. No live provider qualification.
|
Verification: `passed` — actual key/JWT configure/reconnect requests return the addressed handoff with unchanged secret refs and vault signing value. The saved card stays pending through old health, bot-token repair, unchanged signing-key entry and retries. It completes only after a protected value change, renewed webhook verification and current authority; cancellation or user revocation still denies continuation. Credential-write revocation during provider authentication leaves refs unchanged. Complete human replacements repair disabled, deleted or unreadable old signing material, while unexpected resolver failures abort without changing refs. Different credential field sets/completion modes save separate handoffs; matching retries preserve the original baseline. A human session configures Slack; an agent can repair its bot token and send a policy-governed test message. Native setup receipts are protected. No live provider qualification.
|
||||||
Gaps: Provider fixtures do not qualify live provider setup or availability.
|
Gaps: Provider fixtures do not qualify live provider setup or availability.
|
||||||
|
|
||||||
### `post-chat-endpoints-setup-secret`
|
### `post-chat-endpoints-setup-secret`
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ Message the configured bot/address from its external provider and follow the cor
|
|||||||
|
|
||||||
### `endpoint-management`
|
### `endpoint-management`
|
||||||
|
|
||||||
Delegated setup can save ordinary bot tokens and API keys. Provider verification keys require the addressed human credential-entry step or verified provider provisioning. Generic setup returns the saved handoff without changing those keys, and generic connection updates cannot replace channel credential references. A replacement card waits for an actual protected-value change and restored verification; old health, unchanged entry and unrelated bot-token repair do not complete it. The actual-auth Slack fixture covers these boundaries, retries, revocation and permitted bot-token repair; live provider repair remains separately qualified.
|
Delegated setup can save ordinary bot tokens and API keys. Provider verification keys require the addressed human credential-entry step or verified provider provisioning. Generic setup returns the saved handoff without changing those keys, and generic connection updates cannot replace channel credential references. A replacement card waits for an actual protected-value change and restored verification; old health, unchanged entry and unrelated bot-token repair do not complete it. Fresh GitHub handoffs open the import form; configured endpoints retain reconnect. Credential-entry field sets and completion modes stay distinct while matching retries reuse their saved baseline. Complete human replacements can repair disabled, deleted or unreadable old vault values. The actual-auth Slack/GitHub fixtures cover these boundaries, retries, revocation and permitted bot-token repair; live provider repair remains separately qualified.
|
||||||
|
|
||||||
Open `/apps/chat/:endpointId/settings` and the endpoint’s other available tabs; identity confirmation uses `/chat-identity/confirm`.
|
Open `/apps/chat/:endpointId/settings` and the endpoint’s other available tabs; identity confirmation uses `/chat-identity/confirm`.
|
||||||
|
|
||||||
|
|||||||
@@ -258,12 +258,28 @@ describe("GitHub App wizard", () => {
|
|||||||
expect(container.textContent).not.toContain("GitHub connected");
|
expect(container.textContent).not.toContain("GitHub connected");
|
||||||
expect(githubChatApi.registration).not.toHaveBeenCalled();
|
expect(githubChatApi.registration).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
it("opens a saved credential-entry handoff without starting another App registration", async () => {
|
it("completes a fresh credential-entry handoff through App import", async () => {
|
||||||
await render("resume=draft-1&stage=credentials");
|
await render("resume=draft-1&stage=credentials");
|
||||||
expect(container.querySelector("#github-app-id")).not.toBeNull();
|
expect(container.querySelector("#github-app-id")).not.toBeNull();
|
||||||
expect(container.querySelector("#github-webhook-secret")).not.toBeNull();
|
expect(container.querySelector("#github-webhook-secret")).not.toBeNull();
|
||||||
expect(githubChatApi.advance).not.toHaveBeenCalled();
|
expect(githubChatApi.advance).not.toHaveBeenCalled();
|
||||||
expect(githubChatApi.registration).not.toHaveBeenCalled();
|
expect(githubChatApi.registration).not.toHaveBeenCalled();
|
||||||
|
const privateKey = "-----BEGIN PRIVATE KEY-----\nfixture\n-----END PRIVATE KEY-----";
|
||||||
|
for (const [selector, value] of [["#github-app-id", "1234"], ["#github-webhook-secret", "human-webhook-secret"]]) {
|
||||||
|
await act(async () => {
|
||||||
|
const input = container.querySelector<HTMLInputElement>(selector)!;
|
||||||
|
Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "value")!.set!.call(input, value);
|
||||||
|
input.dispatchEvent(new Event("input", { bubbles: true }));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const paste = new Event("paste", { bubbles: true, cancelable: true });
|
||||||
|
Object.defineProperty(paste, "clipboardData", { value: { getData: () => privateKey } });
|
||||||
|
await act(async () => container.querySelector("#github-private-key")!.dispatchEvent(paste));
|
||||||
|
const connect = container.querySelector<HTMLButtonElement>('[aria-label="Connect existing App"]')!;
|
||||||
|
expect(connect.disabled).toBe(false);
|
||||||
|
await act(async () => connect.click());
|
||||||
|
expect(githubChatApi.connectApp).toHaveBeenCalledWith("draft-1", { appId: "1234", privateKey, webhookSecret: "human-webhook-secret" });
|
||||||
|
expect(chatEndpointsApi.setup).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
it("keeps a saved rotation handoff open through generation and reconnect failures", async () => {
|
it("keeps a saved rotation handoff open through generation and reconnect failures", async () => {
|
||||||
fixture.endpoint.status = "active";
|
fixture.endpoint.status = "active";
|
||||||
|
|||||||
Reference in new issue
Block a user