Skip to content

Commit 68a5ace

Browse files
RellWilsonclaude
andcommitted
Simplify feedback API to download-intent model; fix invalid non_responsive outcome
The feedback API is consolidated onto recordDownloadFeedback(intentId, outcome, notes), which reports outcomes against the download intent created when a case is downloaded for application. The prior recordApplicationResult/confirmCase/ refuteCase surface (keyed to a bare case ID) is removed in favor of this single path, matching how the Cases API actually links reputation to download intents. Also fixes recordDownloadFeedback and the GitHub Action's entrypoint accepting "non_responsive" as a caller-supplied outcome: the Cases API's outcome alias map (atvp-feedback-gateway.js) has no such entry, so every call using it would have failed. That state is detected automatically by the server on deadline timeout, not reported by a caller. Valid outcomes are now confirm, refute, derive, and rollback, matching the server exactly. Verified end-to-end: registered a real user, created a real API key, downloaded a real case bundle to create a download intent, then ran the GitHub Action's entrypoint.mjs (using the published @querykey-research/atvp-client package) against a local server — feedback was recorded successfully. 38/38 unit tests pass. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
1 parent f8b7483 commit 68a5ace

11 files changed

Lines changed: 87 additions & 324 deletions

File tree

CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Contributing to @querykey/atvp-client
1+
# Contributing to @querykey-research/atvp-client
22

33
Thank you for your interest in contributing to the official ATVP client SDK.
44

README.md

Lines changed: 17 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# @querykey/atvp-client
1+
# @querykey-research/atvp-client
22

33
Official Node.js client for the **Automated Trust and Verification Protocol (ATVP)**.
44

@@ -9,13 +9,13 @@ ATVP is the trust layer that ensures every case submitted to QueryKey Cases has
99
## Install
1010

1111
```bash
12-
npm install @querykey/atvp-client
12+
npm install @querykey-research/atvp-client
1313
```
1414

1515
## Quick Start
1616

1717
```js
18-
import { AtvpClient } from '@querykey/atvp-client';
18+
import { AtvpClient } from '@querykey-research/atvp-client';
1919

2020
const client = new AtvpClient({
2121
apiKey: process.env.QUERYKEY_API_KEY,
@@ -93,50 +93,26 @@ Publishes a case. `evidence` must be an object with:
9393

9494
Boolean flags are **rejected**. `exitCode` must be exactly `0`.
9595

96-
### `client.cases.feedback(caseId, { feedback_type, outcome, reasoning })`
97-
98-
Records feedback (REFUTE, ROLLBACK, etc.).
99-
100-
### `client.feedback.recordApplicationResult(caseId, result, notes?, evidence?)`
96+
### `client.feedback.recordDownloadFeedback(intentId, outcome, notes?)`
10197

102-
Closes the feedback loop by recording whether a fix succeeded or failed after deployment.
98+
Records post-application feedback. In ATVP, application feedback is keyed to a
99+
**download intent**: an actor downloads a published case (creating an intent),
100+
applies the fix, and reports the outcome against that intent — this is what
101+
drives the downloader's reputation. There is no bare per-case feedback endpoint;
102+
positive verification at publish time goes through `client.cases.confirm()` with
103+
evidence.
103104

104-
`result` must be one of: `'success'`, `'failure'`, `'partial'`, `'rolled_back'`.
105+
- `outcome`: `'confirm'` | `'refute'` | `'non_responsive'`
105106

106107
```js
107-
// After staging deploy passes
108-
await client.feedback.recordApplicationResult(
109-
caseId,
110-
'success',
111-
'Smoke tests passed on staging',
112-
{ command: 'bash scripts/check.sh', exitCode: 0, output: 'PASS' }
113-
);
114-
115-
// After production rollback
116-
await client.feedback.recordApplicationResult(
117-
caseId,
118-
'rolled_back',
119-
'Fix caused regression in billing service — reverted in hotfix #4821'
108+
// After applying a downloaded fix and verifying it
109+
await client.feedback.recordDownloadFeedback(
110+
intentId,
111+
'confirm',
112+
'Smoke tests passed on staging'
120113
);
121114
```
122115

123-
### `client.feedback.confirmCase(caseId, evidence, notes?)`
124-
125-
Shortcut for `recordApplicationResult(caseId, 'success', notes, evidence)`.
126-
127-
### `client.feedback.refuteCase(caseId, reasoning)`
128-
129-
Shortcut for `recordApplicationResult(caseId, 'failure', reasoning)`.
130-
131-
### `client.feedback.recordRollback(caseId, reasoning?)`
132-
133-
Shortcut for `recordApplicationResult(caseId, 'rolled_back', reasoning)`.
134-
135-
### `client.feedback.recordDownloadFeedback(intentId, outcome, notes?)`
136-
137-
Records feedback on a download intent after the 72-hour window:
138-
- `outcome`: `'confirm'` | `'refute'` | `'non_responsive'`
139-
140116
### `client.feedback.deriveOnRecurrence(originalCaseId, newPayload, sessionId?)`
141117

142118
Creates a derived case when a published error signature reappears. For multi-hop derivation chains with context sessions, use `@querykey/cases-client`.
@@ -166,7 +142,7 @@ Runs a shell command and returns an evidence object `{ command, exitCode, output
166142
All SDK methods throw `AtvpError` on failure:
167143

168144
```js
169-
import { AtvpError } from '@querykey/atvp-client';
145+
import { AtvpError } from '@querykey-research/atvp-client';
170146

171147
try {
172148
await client.cases.confirm(caseId, badEvidence);

docs/github-action/README.md

Lines changed: 11 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
GitHub Action to record the outcome of a fix application to the [QueryKey Cases](https://www.querykey.com) ATVP ledger.
44

5+
In ATVP, application feedback is keyed to a **download intent** — the intent created when an agent downloads a published case to apply it. This action records the outcome against that intent.
6+
57
## Usage
68

79
Add this step after your deployment to record whether the fix held:
@@ -11,43 +13,32 @@ Add this step after your deployment to record whether the fix held:
1113
uses: querykey/record-fix-action@v1
1214
with:
1315
api_key: ${{ secrets.QUERYKEY_API_KEY }}
14-
case_id: ${{ env.CASE_ID }}
15-
result: success
16+
intent_id: ${{ env.ATVP_INTENT_ID }}
17+
outcome: confirm
1618
notes: "Deployed to production, smoke tests passed"
17-
evidence_command: "bash scripts/check.sh"
1819
```
1920
2021
## Inputs
2122
2223
| Input | Required | Default | Description |
2324
| :--- | :--- | :--- | :--- |
2425
| `api_key` | **Yes** | — | QueryKey API key |
25-
| `case_id` | **Yes** | — | ATVP case ID to record feedback for |
26-
| `result` | No | `success` | Outcome: `success`, `failure`, `partial`, `rolled_back` |
26+
| `intent_id` | **Yes** | — | ATVP download intent ID (created when the case was downloaded) |
27+
| `outcome` | No | `confirm` | Outcome: `confirm`, `refute`, or `non_responsive` |
2728
| `notes` | No | — | Human-readable notes |
28-
| `evidence_command` | No | — | Shell command to capture structured evidence |
2929
| `querykey_base_url` | No | `https://www.querykey.com` | QueryKey API base URL |
3030

31-
## Result Values
32-
33-
- `success` — Fix resolved the issue
34-
- `failure` — Fix did not resolve the issue or caused regressions
35-
- `partial` — Fix partially resolved the issue
36-
- `rolled_back` — Fix was rolled back
37-
38-
## Evidence Command
39-
40-
If provided, the action runs the shell command and captures:
41-
- `command` — the command that was run
42-
- `exitCode` — 0 for success, non-zero for failure
43-
- `output` — stdout/stderr of the command
31+
## Outcome Values
4432

45-
This structured evidence is attached to the feedback record in the ATVP ledger.
33+
- `confirm` — Fix resolved the issue; counts toward case verification
34+
- `refute` — Fix did not resolve the issue; resets case verification
35+
- `non_responsive` — Unable to determine outcome (e.g. could not run validation)
4636

4737
## Requirements
4838

4939
- Node.js 20+ (GitHub Actions `node20` runtime)
5040
- A QueryKey API key with feedback permissions
41+
- A download intent ID — obtain one by downloading a case via the API before applying the fix
5142

5243
## License
5344

docs/github-action/action.yml

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,17 @@ inputs:
99
api_key:
1010
description: 'QueryKey API key'
1111
required: true
12-
case_id:
13-
description: 'The ATVP case ID to record feedback for'
12+
intent_id:
13+
description: 'The ATVP download intent ID created when the case was downloaded for application'
1414
required: true
15-
result:
16-
description: 'Outcome of the fix application: success, failure, partial, or rolled_back'
15+
outcome:
16+
description: 'Outcome of the fix application: confirm (worked), refute (failed), derive (needed changes), or rollback (had to revert)'
1717
required: true
18-
default: 'success'
18+
default: 'confirm'
1919
notes:
2020
description: 'Optional human-readable notes about the outcome'
2121
required: false
2222
default: ''
23-
evidence_command:
24-
description: 'Shell command to run for structured evidence (e.g. "bash scripts/check.sh")'
25-
required: false
26-
default: ''
2723
querykey_base_url:
2824
description: 'QueryKey API base URL'
2925
required: false

docs/github-action/entrypoint.mjs

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,44 +2,44 @@
22
/**
33
* @file entrypoint.mjs
44
* @description GitHub Action entrypoint for recording ATVP fix outcomes.
5-
* Uses @querykey/atvp-client to send feedback to the Cases ledger.
5+
* Uses @querykey-research/atvp-client to send feedback to the Cases ledger.
6+
*
7+
* Feedback is keyed to a download intent, not a case ID.
8+
* The intent ID is created when an agent downloads a published case to apply it.
69
*/
710

8-
import { AtvpClient } from '@querykey/atvp-client';
11+
import { AtvpClient } from '@querykey-research/atvp-client';
12+
13+
// Must match FeedbackLoop.recordDownloadFeedback's validOutcomes in the SDK.
14+
// "non_responsive" is excluded: the server detects that state automatically
15+
// on deadline timeout — it is not a caller-reported outcome.
16+
const VALID_OUTCOMES = ['confirm', 'refute', 'derive', 'rollback'];
917

1018
async function main() {
1119
const apiKey = process.env.INPUT_API_KEY;
12-
const caseId = process.env.INPUT_CASE_ID;
13-
const result = process.env.INPUT_RESULT || 'success';
20+
const intentId = process.env.INPUT_INTENT_ID;
21+
const outcome = process.env.INPUT_OUTCOME || 'confirm';
1422
const notes = process.env.INPUT_NOTES || '';
15-
const evidenceCommand = process.env.INPUT_EVIDENCE_COMMAND || '';
1623
const baseUrl = process.env.INPUT_QUERYKEY_BASE_URL || 'https://www.querykey.com';
1724

1825
if (!apiKey) {
1926
console.error('::error::Input api_key is required');
2027
process.exit(1);
2128
}
22-
if (!caseId) {
23-
console.error('::error::Input case_id is required');
29+
if (!intentId) {
30+
console.error('::error::Input intent_id is required (the download intent ID, not the case ID)');
31+
process.exit(1);
32+
}
33+
if (!VALID_OUTCOMES.includes(outcome)) {
34+
console.error(`::error::Input outcome must be one of: ${VALID_OUTCOMES.join(', ')} — got "${outcome}"`);
2435
process.exit(1);
2536
}
2637

2738
const client = new AtvpClient({ apiKey, baseUrl });
2839

29-
let evidence;
30-
if (evidenceCommand) {
31-
const { execSync } = await import('node:child_process');
32-
try {
33-
const output = execSync(evidenceCommand, { encoding: 'utf-8', timeout: 120000 });
34-
evidence = { command: evidenceCommand, exitCode: 0, output: output.trim() };
35-
} catch (err) {
36-
evidence = { command: evidenceCommand, exitCode: err.status || 1, output: (err.stdout || err.stderr || err.message).trim() };
37-
}
38-
}
39-
4040
try {
41-
const feedback = await client.feedback.recordApplicationResult(caseId, result, notes, evidence);
42-
console.log(`::notice::ATVP feedback recorded for case ${caseId}: ${result}`);
41+
const feedback = await client.feedback.recordDownloadFeedback(intentId, outcome, notes || undefined);
42+
console.log(`::notice::ATVP feedback recorded for intent ${intentId}: ${outcome}`);
4343
console.log(JSON.stringify(feedback, null, 2));
4444
} catch (err) {
4545
console.error(`::error::Failed to record ATVP feedback: ${err.message}`);

package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@
44
"description": "Official Node.js client for the Automated Trust and Verification Protocol (ATVP)",
55
"main": "src/index.js",
66
"type": "module",
7+
"files": [
8+
"src",
9+
"docs/github-action",
10+
"README.md"
11+
],
712
"scripts": {
813
"test": "node --test test/*.test.js",
914
"lint": "eslint src/ test/",

src/AtvpClient.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* Composes HttpTransport, AgentRegistration, CaseFlow, EvidenceGate, and FeedbackLoop.
55
*
66
* Usage:
7-
* import { AtvpClient } from '@querykey/atvp-client';
7+
* import { AtvpClient } from '@querykey-research/atvp-client';
88
* const client = new AtvpClient({ apiKey, baseUrl });
99
* const { caseId } = await client.submitVerifiedFix({ ... });
1010
*/

src/CaseFlow.js

Lines changed: 6 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,10 @@ export class CaseFlow {
3838
if (!payload.title) throw new AtvpError('title is required', { step: 'create' });
3939
if (!payload.error_signature) throw new AtvpError('error_signature is required', { step: 'create' });
4040

41+
// Spread caller-provided fields first so the explicit keys below always win
42+
// (prevents an absent payload.body from clobbering the computed default).
4143
const body = {
44+
...payload,
4245
title: payload.title,
4346
summary: payload.summary ?? payload.title,
4447
body: payload.body ?? {
@@ -47,9 +50,10 @@ export class CaseFlow {
4750
error_signature: payload.error_signature,
4851
ecosystem: payload.ecosystem ?? 'unknown',
4952
root_cause: payload.root_cause ?? '',
50-
context_tags: payload.context_tags ?? { environments: ['production'], domains: ['cases-api'] },
53+
metadata: {
54+
context_tags: payload.context_tags ?? { environments: ['production'], domains: ['cases-api'] },
55+
},
5156
},
52-
...payload,
5357
};
5458

5559
return this.transport.post('/api/v1/cases', body);
@@ -164,32 +168,6 @@ export class CaseFlow {
164168
};
165169
}
166170

167-
/**
168-
* Feedback — record a REFUTE, ROLLBACK, or other feedback outcome.
169-
*
170-
* @param {string} caseId
171-
* @param {Object} feedback
172-
* @param {string} feedback.feedback_type — 'REFUTE', 'CONFIRM', 'ROLLBACK'
173-
* @param {string} feedback.outcome — FEEDBACK_OUTCOME value
174-
* @param {string} feedback.reasoning — human-readable reasoning
175-
* @param {string} [feedback.ownership] — FEEDBACK_OWNERSHIP value
176-
* @returns {Promise<Object>} feedback record
177-
*/
178-
async feedback(caseId, { feedback_type, outcome, reasoning, ownership = 'FEEDBACK_OWNERSHIP' }) {
179-
if (!caseId) throw new AtvpError('caseId is required', { step: 'feedback' });
180-
if (!feedback_type) throw new AtvpError('feedback_type is required', { step: 'feedback' });
181-
182-
const body = {
183-
case_id: caseId,
184-
feedback_type,
185-
outcome: outcome ?? 'FEEDBACK_OUTCOME',
186-
reasoning: reasoning ?? '',
187-
ownership,
188-
};
189-
190-
return this.transport.post(`/api/v1/cases/${caseId}/feedback`, body);
191-
}
192-
193171
/**
194172
* Derive a linked follow-on case (recurrence).
195173
*

0 commit comments

Comments
 (0)