@ouispec/contract 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/INTEGRATOR-GUIDE.md +729 -0
- package/LICENSE +21 -0
- package/README.md +10 -0
- package/dist/codegen.d.ts +71 -0
- package/dist/codegen.d.ts.map +1 -0
- package/dist/codegen.js +195 -0
- package/dist/codegen.js.map +1 -0
- package/dist/generated/contract.d.ts +1286 -0
- package/dist/generated/contract.d.ts.map +1 -0
- package/dist/generated/contract.js +14 -0
- package/dist/generated/contract.js.map +1 -0
- package/dist/generated/schemas.d.ts +111 -0
- package/dist/generated/schemas.d.ts.map +1 -0
- package/dist/generated/schemas.js +3256 -0
- package/dist/generated/schemas.js.map +1 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +28 -0
- package/dist/index.js.map +1 -0
- package/dist/render-contract.d.ts +47 -0
- package/dist/render-contract.d.ts.map +1 -0
- package/dist/render-contract.js +123 -0
- package/dist/render-contract.js.map +1 -0
- package/dist/render-guide.d.ts +4 -0
- package/dist/render-guide.d.ts.map +1 -0
- package/dist/render-guide.js +132 -0
- package/dist/render-guide.js.map +1 -0
- package/dist/schema-document.d.ts +7 -0
- package/dist/schema-document.d.ts.map +1 -0
- package/dist/schema-document.js +2 -0
- package/dist/schema-document.js.map +1 -0
- package/dist/validate.d.ts +22 -0
- package/dist/validate.d.ts.map +1 -0
- package/dist/validate.js +89 -0
- package/dist/validate.js.map +1 -0
- package/package.json +65 -0
- package/schemas/action-effect.json +113 -0
- package/schemas/agent-binding.json +58 -0
- package/schemas/approvals.json +249 -0
- package/schemas/control-kind-registration.json +187 -0
- package/schemas/control-table.json +276 -0
- package/schemas/event-declarations.json +316 -0
- package/schemas/generated-knowledge.json +58 -0
- package/schemas/json-schema.json +153 -0
- package/schemas/oui-config.json +178 -0
- package/schemas/oui-manifest.json +346 -0
- package/schemas/room-catalog-data.json +455 -0
- package/schemas/tier2-mapping.json +195 -0
package/dist/validate.js
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @ouispec/contract/validate — checks a value against the
|
|
3
|
+
* contract's schemas with a full JSON Schema (2020-12) validator: a control
|
|
4
|
+
* table, a mapping, a catalog, a manifest, knowledge, `oui.config.json`, an
|
|
5
|
+
* approval message or an event declaration document. For build steps, tests
|
|
6
|
+
* and servers; the browser needs only the types.
|
|
7
|
+
*/
|
|
8
|
+
import { Ajv2020 } from 'ajv/dist/2020.js';
|
|
9
|
+
import formatsPlugin from 'ajv-formats';
|
|
10
|
+
import { CONTRACT_SCHEMAS, CONTRACT_TYPES } from './generated/schemas.js';
|
|
11
|
+
// ajv-formats is CommonJS; under ESM interop its function is on `default`.
|
|
12
|
+
const addFormats = formatsPlugin.default ?? formatsPlugin;
|
|
13
|
+
export class ContractError extends Error {
|
|
14
|
+
what;
|
|
15
|
+
ref;
|
|
16
|
+
problems;
|
|
17
|
+
constructor(what, ref, problems) {
|
|
18
|
+
super(`${what} does not match the OUI contract's ${ref}:\n${problems.map((p) => ` - ${p}`).join('\n')}`);
|
|
19
|
+
this.what = what;
|
|
20
|
+
this.ref = ref;
|
|
21
|
+
this.problems = problems;
|
|
22
|
+
this.name = 'ContractError';
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/** The schema `$id` (and pointer) a reference names. */
|
|
26
|
+
export function contractSchemaId(ref) {
|
|
27
|
+
const target = ref in CONTRACT_TYPES ? CONTRACT_TYPES[ref] : ref;
|
|
28
|
+
const [file, pointer] = target.split('#');
|
|
29
|
+
const schema = CONTRACT_SCHEMAS[file];
|
|
30
|
+
if (!schema)
|
|
31
|
+
throw new Error(`"${ref}" is not a schema or type of the OUI contract`);
|
|
32
|
+
return pointer ? `${schema.$id}#${pointer}` : schema.$id;
|
|
33
|
+
}
|
|
34
|
+
function describe(error) {
|
|
35
|
+
const at = error.instancePath || '/';
|
|
36
|
+
switch (error.keyword) {
|
|
37
|
+
case 'additionalProperties':
|
|
38
|
+
return `${at} has "${error.params.additionalProperty}", which the contract does not define`;
|
|
39
|
+
case 'unevaluatedProperties':
|
|
40
|
+
return `${at} has "${error.params.unevaluatedProperty}", which the contract does not define`;
|
|
41
|
+
case 'enum':
|
|
42
|
+
return `${at} must be one of ${error.params.allowedValues.map((v) => JSON.stringify(v)).join(', ')}`;
|
|
43
|
+
default:
|
|
44
|
+
return `${at} ${error.message ?? 'is invalid'}`;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/** Compile every schema of the contract once. */
|
|
48
|
+
export function createContractValidator() {
|
|
49
|
+
const ajv = new Ajv2020({ allErrors: true, strict: true, strictRequired: false });
|
|
50
|
+
addFormats(ajv);
|
|
51
|
+
for (const schema of Object.values(CONTRACT_SCHEMAS))
|
|
52
|
+
ajv.addSchema(schema);
|
|
53
|
+
const compiled = new Map();
|
|
54
|
+
const validator = (ref) => {
|
|
55
|
+
const id = contractSchemaId(ref);
|
|
56
|
+
let fn = compiled.get(id);
|
|
57
|
+
if (!fn) {
|
|
58
|
+
fn = ajv.getSchema(id);
|
|
59
|
+
if (!fn)
|
|
60
|
+
throw new Error(`the OUI contract has no schema at ${id}`);
|
|
61
|
+
compiled.set(id, fn);
|
|
62
|
+
}
|
|
63
|
+
return fn;
|
|
64
|
+
};
|
|
65
|
+
const problems = (ref, value) => {
|
|
66
|
+
const validate = validator(ref);
|
|
67
|
+
if (validate(value))
|
|
68
|
+
return [];
|
|
69
|
+
// A `oneOf`'s branches each report why they do not match; the most specific
|
|
70
|
+
// reasons are the deepest ones.
|
|
71
|
+
const errors = validate.errors ?? [];
|
|
72
|
+
return [...new Set(errors.map(describe))];
|
|
73
|
+
};
|
|
74
|
+
return {
|
|
75
|
+
problems,
|
|
76
|
+
assert(ref, value, what) {
|
|
77
|
+
const found = problems(ref, value);
|
|
78
|
+
if (found.length)
|
|
79
|
+
throw new ContractError(what, ref, found);
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
let shared = null;
|
|
84
|
+
/** Every way `value` breaks the contract's `ref`. Compiles the schemas on first use. */
|
|
85
|
+
export function contractProblems(ref, value) {
|
|
86
|
+
shared ??= createContractValidator();
|
|
87
|
+
return shared.problems(ref, value);
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=validate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate.js","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,OAAO,EAA2C,MAAM,kBAAkB,CAAC;AACpF,OAAO,aAAa,MAAM,aAAa,CAAC;AAExC,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAkD,MAAM,wBAAwB,CAAC;AAG1H,2EAA2E;AAC3E,MAAM,UAAU,GACb,aAAwD,CAAC,OAAO,IAAK,aAA0C,CAAC;AAYnH,MAAM,OAAO,aAAc,SAAQ,KAAK;IAE3B;IACA;IACA;IAHX,YACW,IAAY,EACZ,GAAgB,EAChB,QAA2B;QAEpC,KAAK,CAAC,GAAG,IAAI,sCAAsC,GAAG,MAAM,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAJjG,SAAI,GAAJ,IAAI,CAAQ;QACZ,QAAG,GAAH,GAAG,CAAa;QAChB,aAAQ,GAAR,QAAQ,CAAmB;QAGpC,IAAI,CAAC,IAAI,GAAG,eAAe,CAAC;IAC9B,CAAC;CACF;AAED,wDAAwD;AACxD,MAAM,UAAU,gBAAgB,CAAC,GAAgB;IAC/C,MAAM,MAAM,GAAW,GAAG,IAAI,cAAc,CAAC,CAAC,CAAC,cAAc,CAAC,GAAuB,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;IAC7F,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC1C,MAAM,MAAM,GAAG,gBAAgB,CAAC,IAA0B,CAAC,CAAC;IAC5D,IAAI,CAAC,MAAM;QAAE,MAAM,IAAI,KAAK,CAAC,IAAI,GAAG,+CAA+C,CAAC,CAAC;IACrF,OAAO,OAAO,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,GAAG,IAAI,OAAO,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;AAC3D,CAAC;AAED,SAAS,QAAQ,CAAC,KAAkB;IAClC,MAAM,EAAE,GAAG,KAAK,CAAC,YAAY,IAAI,GAAG,CAAC;IACrC,QAAQ,KAAK,CAAC,OAAO,EAAE,CAAC;QACtB,KAAK,sBAAsB;YACzB,OAAO,GAAG,EAAE,SAAU,KAAK,CAAC,MAAyC,CAAC,kBAAkB,uCAAuC,CAAC;QAClI,KAAK,uBAAuB;YAC1B,OAAO,GAAG,EAAE,SAAU,KAAK,CAAC,MAA0C,CAAC,mBAAmB,uCAAuC,CAAC;QACpI,KAAK,MAAM;YACT,OAAO,GAAG,EAAE,mBAAoB,KAAK,CAAC,MAAuC,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACzI;YACE,OAAO,GAAG,EAAE,IAAI,KAAK,CAAC,OAAO,IAAI,YAAY,EAAE,CAAC;IACpD,CAAC;AACH,CAAC;AAED,iDAAiD;AACjD,MAAM,UAAU,uBAAuB;IACrC,MAAM,GAAG,GAAG,IAAI,OAAO,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,cAAc,EAAE,KAAK,EAAE,CAAC,CAAC;IAClF,UAAU,CAAC,GAAG,CAAC,CAAC;IAChB,KAAK,MAAM,MAAM,IAAI,MAAM,CAAC,MAAM,CAAC,gBAAgB,CAAC;QAAE,GAAG,CAAC,SAAS,CAAC,MAAgB,CAAC,CAAC;IACtF,MAAM,QAAQ,GAAG,IAAI,GAAG,EAA4B,CAAC;IACrD,MAAM,SAAS,GAAG,CAAC,GAAgB,EAAoB,EAAE;QACvD,MAAM,EAAE,GAAG,gBAAgB,CAAC,GAAG,CAAC,CAAC;QACjC,IAAI,EAAE,GAAG,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAC1B,IAAI,CAAC,EAAE,EAAE,CAAC;YACR,EAAE,GAAG,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;YACvB,IAAI,CAAC,EAAE;gBAAE,MAAM,IAAI,KAAK,CAAC,qCAAqC,EAAE,EAAE,CAAC,CAAC;YACpE,QAAQ,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;QACvB,CAAC;QACD,OAAO,EAAE,CAAC;IACZ,CAAC,CAAC;IACF,MAAM,QAAQ,GAAG,CAAC,GAAgB,EAAE,KAAc,EAAY,EAAE;QAC9D,MAAM,QAAQ,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC;QAChC,IAAI,QAAQ,CAAC,KAAK,CAAC;YAAE,OAAO,EAAE,CAAC;QAC/B,4EAA4E;QAC5E,gCAAgC;QAChC,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,IAAI,EAAE,CAAC;QACrC,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;IAC5C,CAAC,CAAC;IACF,OAAO;QACL,QAAQ;QACR,MAAM,CAAC,GAAG,EAAE,KAAK,EAAE,IAAI;YACrB,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;YACnC,IAAI,KAAK,CAAC,MAAM;gBAAE,MAAM,IAAI,aAAa,CAAC,IAAI,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC;QAC9D,CAAC;KACF,CAAC;AACJ,CAAC;AAED,IAAI,MAAM,GAA6B,IAAI,CAAC;AAE5C,wFAAwF;AACxF,MAAM,UAAU,gBAAgB,CAAC,GAAgB,EAAE,KAAc;IAC/D,MAAM,KAAK,uBAAuB,EAAE,CAAC;IACrC,OAAO,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;AACrC,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ouispec/contract",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "The OUI integrator contract (ADR-0226 §3): versioned JSON Schemas for the control table, control-kind registration, tier 2 mapping, binding, action effect, room catalog, manifest, knowledge, oui.config.json, approvals and event declarations; the TypeScript types generated from them; a validator; and the integrator guide",
|
|
5
|
+
"homepage": "https://github.com/wesreid/oui/tree/main/packages/contract#readme",
|
|
6
|
+
"bugs": {
|
|
7
|
+
"url": "https://github.com/wesreid/oui/issues"
|
|
8
|
+
},
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/wesreid/oui.git",
|
|
12
|
+
"directory": "packages/contract"
|
|
13
|
+
},
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"type": "module",
|
|
16
|
+
"main": "./dist/index.js",
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"import": "./dist/index.js",
|
|
22
|
+
"default": "./dist/index.js"
|
|
23
|
+
},
|
|
24
|
+
"./validate": {
|
|
25
|
+
"types": "./dist/validate.d.ts",
|
|
26
|
+
"import": "./dist/validate.js",
|
|
27
|
+
"default": "./dist/validate.js"
|
|
28
|
+
},
|
|
29
|
+
"./codegen": {
|
|
30
|
+
"types": "./dist/codegen.d.ts",
|
|
31
|
+
"import": "./dist/codegen.js",
|
|
32
|
+
"default": "./dist/codegen.js"
|
|
33
|
+
},
|
|
34
|
+
"./schemas/*": "./schemas/*",
|
|
35
|
+
"./INTEGRATOR-GUIDE.md": "./INTEGRATOR-GUIDE.md",
|
|
36
|
+
"./package.json": "./package.json"
|
|
37
|
+
},
|
|
38
|
+
"files": [
|
|
39
|
+
"README.md",
|
|
40
|
+
"INTEGRATOR-GUIDE.md",
|
|
41
|
+
"dist",
|
|
42
|
+
"schemas"
|
|
43
|
+
],
|
|
44
|
+
"sideEffects": false,
|
|
45
|
+
"dependencies": {
|
|
46
|
+
"ajv": "^8.17.0",
|
|
47
|
+
"ajv-formats": "^3.0.1"
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"@types/node": "^22.0.0",
|
|
51
|
+
"typescript": "^5.8.0",
|
|
52
|
+
"vitest": "^3.2.0"
|
|
53
|
+
},
|
|
54
|
+
"publishConfig": {
|
|
55
|
+
"access": "public",
|
|
56
|
+
"provenance": true
|
|
57
|
+
},
|
|
58
|
+
"scripts": {
|
|
59
|
+
"build": "tsc -p tsconfig.build.json && node scripts/generate.mjs --check",
|
|
60
|
+
"generate": "tsc -p tsconfig.build.json && node scripts/generate.mjs && tsc -p tsconfig.build.json",
|
|
61
|
+
"typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.test.json",
|
|
62
|
+
"test": "vitest run",
|
|
63
|
+
"clean": "rm -rf dist"
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://schemas.closurestudio.ai/oui/v1/action-effect.json",
|
|
4
|
+
"title": "ActionEffect",
|
|
5
|
+
"description": "What using an action does (ADR-0226 §2.6): one vocabulary for a design-system control's binding, a room catalog's entry and a generated API tool, so a page control, a room action and an API call are told apart by what they change, never by where they are declared.\n\n| Effect | What it changes | ADR-0210 access |\n|---|---|---|\n| `view`, `selection`, `navigate`, `open` | What is shown | read |\n| `edit` | The document, one undo step | write |\n| `file` | Imports or exports a file | write |\n| `mutate` | Backend data, through an API operation | write |\n| `job` | Starts work that outlives the call, settled on its outcome | write |\n| `transaction` | An irreversible external act: an order, a payment, a send, a publish | write, approved |\n\nA `transaction`, and any `write` declared `destructive`, runs only on an approval the person gave, bound to the call (ADR-0228).",
|
|
6
|
+
"oneOf": [
|
|
7
|
+
{ "$ref": "#/$defs/SimpleEffect" },
|
|
8
|
+
{
|
|
9
|
+
"description": "Goes to another page: a route pattern from the app's routes.",
|
|
10
|
+
"type": "object",
|
|
11
|
+
"properties": { "kind": { "const": "navigate" }, "to": { "type": "string", "minLength": 1 } },
|
|
12
|
+
"required": ["kind", "to"],
|
|
13
|
+
"additionalProperties": false
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"description": "Opens a container on the page: the binding id of a dialog, drawer, popover or tab set.",
|
|
17
|
+
"type": "object",
|
|
18
|
+
"properties": { "kind": { "const": "open" }, "container": { "type": "string", "minLength": 1 } },
|
|
19
|
+
"required": ["kind", "container"],
|
|
20
|
+
"additionalProperties": false
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"description": "Changes backend data through an API operation, by its `operationId` (`updateVoice`).",
|
|
24
|
+
"type": "object",
|
|
25
|
+
"properties": { "kind": { "const": "mutate" }, "operation": { "type": "string", "minLength": 1 } },
|
|
26
|
+
"required": ["kind", "operation"],
|
|
27
|
+
"additionalProperties": false
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"description": "Starts work that outlives the call, such as a GPU job. The handler returns `{ ok: true, pending: { jobId } }`; the action is reported done only when the job's completion arrives, failed when it fails, and never done when it has not finished within `timeoutMs` (default 5 minutes). Its states are `JobStatus`.",
|
|
31
|
+
"type": "object",
|
|
32
|
+
"properties": {
|
|
33
|
+
"kind": { "const": "job" },
|
|
34
|
+
"estimatedDuration": { "type": "string" },
|
|
35
|
+
"timeoutMs": { "type": "number", "exclusiveMinimum": 0 }
|
|
36
|
+
},
|
|
37
|
+
"required": ["kind"],
|
|
38
|
+
"additionalProperties": false
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"description": "An irreversible external act: an order placed, a payment made, a message sent, something published. Modelled on `job`: its handler may return `pending: { jobId }` and it settles on the outcome. It always needs the person's approval of the exact call (ADR-0228), which no policy waives.\n\n- `operation`: the API operation it goes through, when it is one.\n- `approvalMinutes`: how long an approval of it lasts, at most 30 (default 5).",
|
|
42
|
+
"type": "object",
|
|
43
|
+
"properties": {
|
|
44
|
+
"kind": { "const": "transaction" },
|
|
45
|
+
"operation": { "type": "string", "minLength": 1 },
|
|
46
|
+
"estimatedDuration": { "type": "string" },
|
|
47
|
+
"timeoutMs": { "type": "number", "exclusiveMinimum": 0 },
|
|
48
|
+
"approvalMinutes": { "type": "integer", "minimum": 1, "maximum": 30 }
|
|
49
|
+
},
|
|
50
|
+
"required": ["kind"],
|
|
51
|
+
"additionalProperties": false
|
|
52
|
+
}
|
|
53
|
+
],
|
|
54
|
+
"$defs": {
|
|
55
|
+
"SimpleEffect": {
|
|
56
|
+
"description": "An effect that needs nothing but its name.",
|
|
57
|
+
"enum": ["view", "selection", "edit", "file"]
|
|
58
|
+
},
|
|
59
|
+
"ActionEffectKind": {
|
|
60
|
+
"description": "The effect kinds, in the order of the table above. The vocabulary, what each one may change, and what needs the person's approval are OUI's (`oui-spec`).",
|
|
61
|
+
"enum": ["view", "selection", "navigate", "open", "edit", "file", "mutate", "job", "transaction"]
|
|
62
|
+
},
|
|
63
|
+
"EffectAccess": {
|
|
64
|
+
"description": "Whether an effect only changes what is shown (`read`), or changes something (`write`), as ADR-0210 names it. An action that declares no effect is a `write`.",
|
|
65
|
+
"enum": ["read", "write"]
|
|
66
|
+
},
|
|
67
|
+
"JobStatus": {
|
|
68
|
+
"description": "Where an action whose effect is `job` or `transaction` is (plan §2.3, #205/#209):\n\n- `started`: the handler returned; the job is tracked from this moment, so an outcome that arrives before the first poll is kept.\n- `running`: still going; the runtime polls the app's `JobTracker`.\n- `complete`: the job's declared completion arrived; the result exists.\n- `failed`: its declared failure arrived; the result never will.\n- `timeout`: it did not finish within `timeoutMs`. A failure, never a late success.\n- `unverified`: no `JobTracker`, or no job id to follow: the work was started but cannot be confirmed from the page.",
|
|
69
|
+
"enum": ["started", "running", "complete", "failed", "timeout", "unverified"]
|
|
70
|
+
},
|
|
71
|
+
"JobOutcome": {
|
|
72
|
+
"description": "A job's end, as the assistant is told it. `complete` means the result exists; `failed` that it never will. A `complete` outcome carries the declared result fields beside its job id.",
|
|
73
|
+
"oneOf": [
|
|
74
|
+
{
|
|
75
|
+
"type": "object",
|
|
76
|
+
"properties": { "status": { "const": "complete" }, "jobId": { "type": "string" } },
|
|
77
|
+
"required": ["status", "jobId"],
|
|
78
|
+
"additionalProperties": true
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"type": "object",
|
|
82
|
+
"properties": { "status": { "const": "failed" }, "jobId": { "type": "string" }, "error": { "type": "string" } },
|
|
83
|
+
"required": ["status", "jobId", "error"],
|
|
84
|
+
"additionalProperties": false
|
|
85
|
+
}
|
|
86
|
+
]
|
|
87
|
+
},
|
|
88
|
+
"JobSettlement": {
|
|
89
|
+
"description": "What an action that settles on a job reports, from its first answer to its last: `started` (with the job id when the handler gave one), `running` while it polls, then the `JobOutcome`, or `unverified`. A `timeout` is reported as the error `TIMEOUT`, never as data.",
|
|
90
|
+
"oneOf": [
|
|
91
|
+
{
|
|
92
|
+
"type": "object",
|
|
93
|
+
"properties": { "status": { "const": "started" }, "jobId": { "type": "string" } },
|
|
94
|
+
"required": ["status"],
|
|
95
|
+
"additionalProperties": true
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
"type": "object",
|
|
99
|
+
"properties": { "status": { "const": "running" }, "jobId": { "type": "string" } },
|
|
100
|
+
"required": ["status", "jobId"],
|
|
101
|
+
"additionalProperties": false
|
|
102
|
+
},
|
|
103
|
+
{ "$ref": "#/$defs/JobOutcome" },
|
|
104
|
+
{
|
|
105
|
+
"type": "object",
|
|
106
|
+
"properties": { "status": { "const": "unverified" }, "message": { "type": "string" } },
|
|
107
|
+
"required": ["status", "message"],
|
|
108
|
+
"additionalProperties": false
|
|
109
|
+
}
|
|
110
|
+
]
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://schemas.closurestudio.ai/oui/v1/agent-binding.json",
|
|
4
|
+
"title": "AgentBinding",
|
|
5
|
+
"description": "The semantic binding a design-system control carries (ADR-0220 §2.2): what the control means, in the user's terms, declared where the page uses it, as its `agent` prop.\n\nIt uses a room catalog entry's vocabulary (id, title and description; `ActionEffect`; `destructive`), so to the generator a page control and a room entry look the same. What is never written: the input schema, which is derived from the control's own props, and where the control is, which the generator derives from the page's component tree.\n\n**Every value is a build-time constant** (#203). The generator reads bindings from source without running it, so each value is a literal, a `const` it can follow, a property of a constant object, a template literal or `+` over those, or a single-literal type read through the type checker. A value built by a call (`t('save')`, `format(...)`) is refused, naming the binding. A field's build-time `hint` and `placeholder` are added to its tool description the same way.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"properties": {
|
|
8
|
+
"id": {
|
|
9
|
+
"description": "Stable, globally unique, dotted and lower-kebab: `voices.library`, `voices.detail.engine`. The first segment is the area. The tool name is the id with `.` and `-` as `_`, at most 64 characters.",
|
|
10
|
+
"type": "string",
|
|
11
|
+
"pattern": "^[a-z][a-z0-9-]*(\\.[a-z0-9][a-z0-9-]*)+$"
|
|
12
|
+
},
|
|
13
|
+
"title": { "description": "Defaults to the control's visible label or aria-label.", "type": "string" },
|
|
14
|
+
"description": { "description": "What using it does, for someone who cannot see the screen.", "type": "string", "minLength": 1 },
|
|
15
|
+
"effect": {
|
|
16
|
+
"description": "What using it does: what reach paths, data and verification are derived from (ADR-0226 §2.6).",
|
|
17
|
+
"$ref": "action-effect.json"
|
|
18
|
+
},
|
|
19
|
+
"destructive": {
|
|
20
|
+
"description": "It removes or replaces something the person made; running it needs the person's approval (ADR-0228).",
|
|
21
|
+
"type": "boolean"
|
|
22
|
+
},
|
|
23
|
+
"confirm": {
|
|
24
|
+
"description": "It changes what the person is working in (their account, project or role) rather than their work; the assistant asks before using it.",
|
|
25
|
+
"type": "boolean"
|
|
26
|
+
},
|
|
27
|
+
"item": { "description": "Set when the control is one of a list's rows: which row.", "$ref": "#/$defs/AgentItem" }
|
|
28
|
+
},
|
|
29
|
+
"required": ["id", "description"],
|
|
30
|
+
"additionalProperties": false,
|
|
31
|
+
"$defs": {
|
|
32
|
+
"AgentItem": {
|
|
33
|
+
"description": "One of several of the same control rendered from a list: which one it is.",
|
|
34
|
+
"type": "object",
|
|
35
|
+
"properties": {
|
|
36
|
+
"key": { "description": "The id of what the row shows (a voice id, a project id).", "type": "string" },
|
|
37
|
+
"title": { "description": "What the row is called on screen (the voice's name).", "type": "string" },
|
|
38
|
+
"description": {
|
|
39
|
+
"description": "What this row is, when the rows' meanings are only known at run time (a model's parameters, from its manifest). Shown with the row in the page state.",
|
|
40
|
+
"type": "string"
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"required": ["key", "title"],
|
|
44
|
+
"additionalProperties": false
|
|
45
|
+
},
|
|
46
|
+
"NonAgentBinding": {
|
|
47
|
+
"description": "On a control the assistant must never operate — purely decorative, or chrome that duplicates a bound control. The reason is required and is reviewed like any other declaration.",
|
|
48
|
+
"type": "object",
|
|
49
|
+
"properties": { "nonAgent": { "type": "string", "minLength": 1 } },
|
|
50
|
+
"required": ["nonAgent"],
|
|
51
|
+
"additionalProperties": false
|
|
52
|
+
},
|
|
53
|
+
"AgentProp": {
|
|
54
|
+
"description": "The `agent` prop of a single-purpose control.",
|
|
55
|
+
"anyOf": [{ "$ref": "#" }, { "$ref": "#/$defs/NonAgentBinding" }]
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://schemas.closurestudio.ai/oui/v1/approvals.json",
|
|
4
|
+
"title": "Approvals",
|
|
5
|
+
"description": "Approvals (ADR-0228): the shapes the agent worker, the approval store in the realtime server, the browser's approval card, OUI's surface runtime and a conversation engine exchange. An irreversible action — a `transaction`, or a `write` declared `destructive` — runs only on an approval the person gave, bound to the exact call by a single-use token.\n\n1. The worker stops the turn at the call and stores it (`PendingApprovalInput`); the person's tab receives `agent:approval_required` (`ApprovalRequiredEvent`) and renders the approval card from its `preview`, whose every word comes from the action's declaration, never from the model.\n2. In a UI only a click on the card counts: the card sends `approval:decide` (`ApprovalDecidePayload`) from the person's own socket. On a conversation channel, the verbatim `readback` and an affirmative next turn (ADR-0210 §2.6).\n3. The store answers the decider only (`ApprovalDecideResult`): a single-use token, a compact JWS (`ApprovalTokenClaims`), and the call's `argsHash`. The tab's OUI runtime holds an `ApprovalGrant`.\n4. The next turn carries the token (`ApprovalContinuation`); the worker redeems it for the stored call, exactly (`ApprovedCall`), and runs it. A UI action request then carries `approval` (`ActionRequestApproval`), which the browser checks against its grant before it runs the handler.\n\nThe args hash is SHA-256 over the RFC 8785 (JCS) canonical JSON of the call's arguments, lowercase hex; `oui-spec/approval-vectors.json` holds every implementation, in any language, to it.",
|
|
6
|
+
"$defs": {
|
|
7
|
+
"ArgsHash": {
|
|
8
|
+
"description": "SHA-256 of the RFC 8785 canonical JSON of a call's arguments, lowercase hex.",
|
|
9
|
+
"type": "string",
|
|
10
|
+
"pattern": "^[0-9a-f]{64}$"
|
|
11
|
+
},
|
|
12
|
+
"ApprovalChannel": {
|
|
13
|
+
"description": "Where the person confirmed. In a UI only a click on the card counts (`ui`); in a conversation channel, the verbatim readback and an affirmative next turn (ADR-0210 §2.6).",
|
|
14
|
+
"enum": ["ui", "voice", "phone", "sms", "chat"]
|
|
15
|
+
},
|
|
16
|
+
"ApprovalDecision": { "enum": ["approve", "decline"] },
|
|
17
|
+
"ApprovalPreviewArgument": {
|
|
18
|
+
"description": "One argument of the call, as the person reads it: its declared label and its value in words.",
|
|
19
|
+
"type": "object",
|
|
20
|
+
"properties": {
|
|
21
|
+
"name": { "description": "The argument's name in the call.", "type": "string" },
|
|
22
|
+
"label": { "description": "Its label, from the action's input schema.", "type": "string" },
|
|
23
|
+
"value": { "description": "Its value, as text.", "type": "string" }
|
|
24
|
+
},
|
|
25
|
+
"required": ["name", "label", "value"],
|
|
26
|
+
"additionalProperties": false
|
|
27
|
+
},
|
|
28
|
+
"ApprovalPreview": {
|
|
29
|
+
"description": "What the person is asked to approve. Every word comes from the action's declaration (its title, description and input schema labels), never from the model (ADR-0228 §2.2).",
|
|
30
|
+
"type": "object",
|
|
31
|
+
"properties": {
|
|
32
|
+
"title": { "type": "string" },
|
|
33
|
+
"consequence": { "description": "What running it does, from the declaration.", "type": "string" },
|
|
34
|
+
"arguments": { "type": "array", "items": { "$ref": "#/$defs/ApprovalPreviewArgument" } },
|
|
35
|
+
"readback": {
|
|
36
|
+
"description": "One sentence composed from the fields above, read or sent verbatim on a conversation channel.",
|
|
37
|
+
"type": "string"
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
"required": ["title", "arguments", "readback"],
|
|
41
|
+
"additionalProperties": false
|
|
42
|
+
},
|
|
43
|
+
"ApprovalEffect": {
|
|
44
|
+
"description": "The effect the approval is for: the action's declared effect kind (ADR-0226 §2.6, `transaction` for an irreversible external act), or `write` when it declares none.",
|
|
45
|
+
"type": "string",
|
|
46
|
+
"minLength": 1
|
|
47
|
+
},
|
|
48
|
+
"PendingApprovalInput": {
|
|
49
|
+
"description": "A call waiting for the person's approval, as the worker stores it (`POST /internal/approvals`).",
|
|
50
|
+
"type": "object",
|
|
51
|
+
"properties": {
|
|
52
|
+
"approvalId": { "description": "Equals the tool call id.", "type": "string" },
|
|
53
|
+
"toolCallId": { "type": "string" },
|
|
54
|
+
"conversationId": { "type": "string" },
|
|
55
|
+
"turnId": { "type": "string" },
|
|
56
|
+
"userId": { "description": "The user whose turn made the call: the only one who may decide it.", "type": "string" },
|
|
57
|
+
"tool": { "type": "string" },
|
|
58
|
+
"args": { "type": "object", "additionalProperties": true },
|
|
59
|
+
"argsHash": { "description": "`argsHash(args)`; the store checks it.", "$ref": "#/$defs/ArgsHash" },
|
|
60
|
+
"effect": { "$ref": "#/$defs/ApprovalEffect" },
|
|
61
|
+
"destructive": { "type": "boolean" },
|
|
62
|
+
"argsSensitive": {
|
|
63
|
+
"description": "Whether the arguments may be written to logs. They are only when the declaration says they are not sensitive.",
|
|
64
|
+
"type": "boolean"
|
|
65
|
+
},
|
|
66
|
+
"expiresAt": { "description": "Epoch ms, at most 30 minutes away (`MAX_APPROVAL_TTL_MS`).", "type": "number" },
|
|
67
|
+
"preview": { "$ref": "#/$defs/ApprovalPreview" }
|
|
68
|
+
},
|
|
69
|
+
"required": [
|
|
70
|
+
"approvalId",
|
|
71
|
+
"toolCallId",
|
|
72
|
+
"conversationId",
|
|
73
|
+
"turnId",
|
|
74
|
+
"userId",
|
|
75
|
+
"tool",
|
|
76
|
+
"args",
|
|
77
|
+
"argsHash",
|
|
78
|
+
"effect",
|
|
79
|
+
"destructive",
|
|
80
|
+
"argsSensitive",
|
|
81
|
+
"expiresAt",
|
|
82
|
+
"preview"
|
|
83
|
+
],
|
|
84
|
+
"additionalProperties": false
|
|
85
|
+
},
|
|
86
|
+
"ApprovalRequiredEvent": {
|
|
87
|
+
"description": "`agent:approval_required`: the worker stopped the turn at a call that needs the person's approval. The approval card renders it.",
|
|
88
|
+
"type": "object",
|
|
89
|
+
"properties": {
|
|
90
|
+
"turnId": { "type": "string" },
|
|
91
|
+
"conversationId": { "type": "string" },
|
|
92
|
+
"approvalId": { "type": "string" },
|
|
93
|
+
"tool": { "type": "string" },
|
|
94
|
+
"effect": { "$ref": "#/$defs/ApprovalEffect" },
|
|
95
|
+
"destructive": { "type": "boolean" },
|
|
96
|
+
"preview": { "$ref": "#/$defs/ApprovalPreview" },
|
|
97
|
+
"expiresAt": { "type": "number" },
|
|
98
|
+
"timestamp": { "type": "number" }
|
|
99
|
+
},
|
|
100
|
+
"required": ["turnId", "conversationId", "approvalId", "tool", "effect", "destructive", "preview", "expiresAt", "timestamp"],
|
|
101
|
+
"additionalProperties": false
|
|
102
|
+
},
|
|
103
|
+
"ApprovalDecidePayload": {
|
|
104
|
+
"description": "`approval:decide`, from the person's own socket: the card's click.",
|
|
105
|
+
"type": "object",
|
|
106
|
+
"properties": { "approvalId": { "type": "string" }, "decision": { "$ref": "#/$defs/ApprovalDecision" } },
|
|
107
|
+
"required": ["approvalId", "decision"],
|
|
108
|
+
"additionalProperties": false
|
|
109
|
+
},
|
|
110
|
+
"ApprovalRefusalReason": {
|
|
111
|
+
"description": "Why an approval was refused:\n\n- `unknown`: no such pending approval: never stored, already declined or redeemed, or expired and gone.\n- `forbidden`: another user's approval, or another conversation's.\n- `expired`.\n- `decided`: already decided.\n- `used`: already redeemed.\n- `invalid`: not a token this environment signed, or malformed.\n- `mismatch`: signed, but not for the stored call.\n- `channel`: a decision from a channel that may not make it.",
|
|
112
|
+
"enum": ["unknown", "forbidden", "expired", "decided", "used", "invalid", "mismatch", "channel"]
|
|
113
|
+
},
|
|
114
|
+
"ApprovalRefusal": {
|
|
115
|
+
"type": "object",
|
|
116
|
+
"properties": {
|
|
117
|
+
"ok": { "const": false },
|
|
118
|
+
"reason": { "$ref": "#/$defs/ApprovalRefusalReason" },
|
|
119
|
+
"error": { "type": "string" }
|
|
120
|
+
},
|
|
121
|
+
"required": ["ok", "reason", "error"],
|
|
122
|
+
"additionalProperties": false
|
|
123
|
+
},
|
|
124
|
+
"ApprovalDecideResult": {
|
|
125
|
+
"description": "The answer to a decision. An approval's token goes only to the decider: the socket that clicked, or the engine that asked.",
|
|
126
|
+
"oneOf": [
|
|
127
|
+
{
|
|
128
|
+
"type": "object",
|
|
129
|
+
"properties": {
|
|
130
|
+
"ok": { "const": true },
|
|
131
|
+
"decision": { "const": "approve" },
|
|
132
|
+
"approvalId": { "type": "string" },
|
|
133
|
+
"token": { "description": "The single-use token the continuation turn redeems.", "type": "string" },
|
|
134
|
+
"argsHash": { "description": "What the browser checks a UI action's params against before it runs it.", "$ref": "#/$defs/ArgsHash" },
|
|
135
|
+
"expiresAt": { "type": "number" },
|
|
136
|
+
"channel": { "$ref": "#/$defs/ApprovalChannel" }
|
|
137
|
+
},
|
|
138
|
+
"required": ["ok", "decision", "approvalId", "token", "argsHash", "expiresAt", "channel"],
|
|
139
|
+
"additionalProperties": false
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
"type": "object",
|
|
143
|
+
"properties": { "ok": { "const": true }, "decision": { "const": "decline" }, "approvalId": { "type": "string" } },
|
|
144
|
+
"required": ["ok", "decision", "approvalId"],
|
|
145
|
+
"additionalProperties": false
|
|
146
|
+
},
|
|
147
|
+
{ "$ref": "#/$defs/ApprovalRefusal" }
|
|
148
|
+
]
|
|
149
|
+
},
|
|
150
|
+
"ApprovalTokenClaims": {
|
|
151
|
+
"description": "The token's claims: a compact JWS (HS256), signed with the environment's approval key.",
|
|
152
|
+
"type": "object",
|
|
153
|
+
"properties": {
|
|
154
|
+
"aid": { "description": "The approval id, which is the tool call id.", "type": "string" },
|
|
155
|
+
"sub": { "description": "The user.", "type": "string" },
|
|
156
|
+
"cid": { "description": "The conversation.", "type": "string" },
|
|
157
|
+
"tool": { "type": "string" },
|
|
158
|
+
"ah": { "description": "The args hash.", "$ref": "#/$defs/ArgsHash" },
|
|
159
|
+
"eff": { "description": "The effect.", "$ref": "#/$defs/ApprovalEffect" },
|
|
160
|
+
"ch": { "description": "Where the user confirmed.", "$ref": "#/$defs/ApprovalChannel" },
|
|
161
|
+
"iat": { "description": "Issued at, epoch seconds.", "type": "integer" },
|
|
162
|
+
"exp": { "description": "Expires at, epoch seconds.", "type": "integer" },
|
|
163
|
+
"jti": { "description": "A nonce.", "type": "string" }
|
|
164
|
+
},
|
|
165
|
+
"required": ["aid", "sub", "cid", "tool", "ah", "eff", "ch", "iat", "exp", "jti"],
|
|
166
|
+
"additionalProperties": false
|
|
167
|
+
},
|
|
168
|
+
"ApprovedCall": {
|
|
169
|
+
"description": "What redeeming a token returns: the stored call, exactly, for the worker to run.",
|
|
170
|
+
"type": "object",
|
|
171
|
+
"properties": {
|
|
172
|
+
"approvalId": { "type": "string" },
|
|
173
|
+
"toolCallId": { "type": "string" },
|
|
174
|
+
"conversationId": { "type": "string" },
|
|
175
|
+
"turnId": { "type": "string" },
|
|
176
|
+
"userId": { "type": "string" },
|
|
177
|
+
"tool": { "type": "string" },
|
|
178
|
+
"args": { "type": "object", "additionalProperties": true },
|
|
179
|
+
"argsHash": { "$ref": "#/$defs/ArgsHash" },
|
|
180
|
+
"effect": { "$ref": "#/$defs/ApprovalEffect" },
|
|
181
|
+
"destructive": { "type": "boolean" },
|
|
182
|
+
"channel": { "$ref": "#/$defs/ApprovalChannel" }
|
|
183
|
+
},
|
|
184
|
+
"required": ["approvalId", "toolCallId", "conversationId", "turnId", "userId", "tool", "args", "argsHash", "effect", "destructive", "channel"],
|
|
185
|
+
"additionalProperties": false
|
|
186
|
+
},
|
|
187
|
+
"ApprovalRedeemResult": {
|
|
188
|
+
"oneOf": [
|
|
189
|
+
{
|
|
190
|
+
"type": "object",
|
|
191
|
+
"properties": { "ok": { "const": true }, "call": { "$ref": "#/$defs/ApprovedCall" } },
|
|
192
|
+
"required": ["ok", "call"],
|
|
193
|
+
"additionalProperties": false
|
|
194
|
+
},
|
|
195
|
+
{ "$ref": "#/$defs/ApprovalRefusal" }
|
|
196
|
+
]
|
|
197
|
+
},
|
|
198
|
+
"ApprovalStatus": {
|
|
199
|
+
"description": "An approval's state, for the turn that follows a decision.",
|
|
200
|
+
"type": "object",
|
|
201
|
+
"properties": {
|
|
202
|
+
"approvalId": { "type": "string" },
|
|
203
|
+
"status": { "enum": ["pending", "approved", "declined"] },
|
|
204
|
+
"tool": { "type": "string" },
|
|
205
|
+
"title": { "type": "string" }
|
|
206
|
+
},
|
|
207
|
+
"required": ["approvalId", "status", "tool", "title"],
|
|
208
|
+
"additionalProperties": false
|
|
209
|
+
},
|
|
210
|
+
"ApprovalContinuation": {
|
|
211
|
+
"description": "What a turn that follows a decision carries, outside the message text: the token to redeem, or that the person declined.",
|
|
212
|
+
"oneOf": [
|
|
213
|
+
{
|
|
214
|
+
"type": "object",
|
|
215
|
+
"properties": { "approvalId": { "type": "string" }, "decision": { "const": "approve" }, "token": { "type": "string" } },
|
|
216
|
+
"required": ["approvalId", "decision", "token"],
|
|
217
|
+
"additionalProperties": false
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
"type": "object",
|
|
221
|
+
"properties": { "approvalId": { "type": "string" }, "decision": { "const": "decline" } },
|
|
222
|
+
"required": ["approvalId", "decision"],
|
|
223
|
+
"additionalProperties": false
|
|
224
|
+
}
|
|
225
|
+
]
|
|
226
|
+
},
|
|
227
|
+
"ActionRequestApproval": {
|
|
228
|
+
"description": "What a UI action request carries when it runs an approved call (`OUIActionApproval`): the browser runs a `transaction` or destructive action only when this matches a grant this tab received from its own card click (ADR-0228 §2.2.6).",
|
|
229
|
+
"type": "object",
|
|
230
|
+
"properties": {
|
|
231
|
+
"approvalId": { "type": "string" },
|
|
232
|
+
"argsHash": { "description": "`argsHash(params)` of the request the user approved.", "$ref": "#/$defs/ArgsHash" }
|
|
233
|
+
},
|
|
234
|
+
"required": ["approvalId", "argsHash"],
|
|
235
|
+
"additionalProperties": false
|
|
236
|
+
},
|
|
237
|
+
"ApprovalGrant": {
|
|
238
|
+
"description": "What the person's approval click gives this tab's OUI runtime (`runtime.grantApproval`, `OUIApprovalGrant`): the request it approved, until it expires.",
|
|
239
|
+
"type": "object",
|
|
240
|
+
"properties": {
|
|
241
|
+
"approvalId": { "type": "string" },
|
|
242
|
+
"argsHash": { "$ref": "#/$defs/ArgsHash" },
|
|
243
|
+
"expiresAt": { "description": "Epoch ms after which the grant no longer admits anything.", "type": "number" }
|
|
244
|
+
},
|
|
245
|
+
"required": ["approvalId", "argsHash", "expiresAt"],
|
|
246
|
+
"additionalProperties": false
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|