@mcp-com-ai/hapi 1.1.0 → 1.3.1
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/README.md +6 -0
- package/dist/bin.js +121 -113
- package/dist/core/document-source.d.ts +4 -0
- package/dist/core/index.js +64 -64
- package/dist/core/plugin-loader.d.ts +2 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +122 -114
- package/dist/skills/hapi-federated-apis/SKILL.md +452 -0
- package/dist/skills/hapi-federated-apis/scripts/audit-planning-facts.ts +127 -0
- package/dist/skills/hapi-intent-based-apis/SKILL.md +199 -0
- package/dist/skills/hapi-intent-based-apis/references/examples.md +135 -0
- package/dist/skills/hapi-intent-based-apis/references/extension-rules.md +90 -0
- package/dist/skills/hapi-intent-based-apis/references/runtime-behavior.md +52 -0
- package/dist/skills/hapi-intent-based-apis/scripts/check-intent-metadata.ts +163 -0
- package/dist/workspace-plugins.d.ts +1 -1
- package/package.json +2 -1
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: hapi-federated-apis
|
|
3
|
+
description: Build a runnable multi-domain HAPI project from two or more valid OpenAPI 3.x contracts, with an Arazzo 1.1 composition, deterministic HAPI Capability Graph planning, local Controller/Service implementations, and repeatable MCP/HTTP demos. Use whenever a user asks to compose, federate, orchestrate, coordinate, or demonstrate multiple APIs/services as one HAPI MCP experience; create Arazzo workflows over several contracts; or build a state-aware multi-step Capability Graph demo. Use this skill even when the request merely says “combine these APIs into HAPI,” “make a multi-service MCP server,” or “create a workflow project.”
|
|
4
|
+
license: MIT
|
|
5
|
+
compatibility: Requires Bun, HAPI CLI with OpenAPI, Arazzo, and Capability Graph plugins, and valid OpenAPI 3.x source contracts. HAPI `init` additionally requires a local writable HAPI home.
|
|
6
|
+
metadata:
|
|
7
|
+
author: mcp.com.ai agent-skills
|
|
8
|
+
version: "1.4.0"
|
|
9
|
+
allowed-tools: Bash(hapi:*) Bash(bun:*) Bash(curl:*) Read Write
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Build a composed HAPI Capability Graph project
|
|
13
|
+
|
|
14
|
+
Use this skill to turn several API domains into a demonstrable HAPI project
|
|
15
|
+
without pretending that a collection of contracts is already a distributed
|
|
16
|
+
runtime graph. The useful outcome is a working local system: source APIs,
|
|
17
|
+
Arazzo workflows, explicit business-state planning, a testable MCP surface,
|
|
18
|
+
and a clear map of what remains future product work.
|
|
19
|
+
|
|
20
|
+
## Start with the correct mental model
|
|
21
|
+
|
|
22
|
+
There are two different things people call “federation.” Keep them separate:
|
|
23
|
+
|
|
24
|
+
1. **Composition available now:** several OpenAPI contracts are referenced by
|
|
25
|
+
an Arazzo workflow. Arazzo can execute the resolved workflow steps as one
|
|
26
|
+
MCP tool.
|
|
27
|
+
2. **One-document graph compatibility available now:** generate a composed
|
|
28
|
+
OpenAPI façade that represents the same operations for the current
|
|
29
|
+
single-document Capability Graph runtime.
|
|
30
|
+
3. **Runtime graph federation not available by implication:** independent
|
|
31
|
+
graph identities, source conflict resolution, cross-document authorization,
|
|
32
|
+
persistent graph stores, and distributed state are separate work. Never
|
|
33
|
+
market a façade or Arazzo source list as this capability.
|
|
34
|
+
|
|
35
|
+
Call the generated single-document input a **composed façade** or **Plan B**,
|
|
36
|
+
not “graph federation.” Put that statement in the contract and project docs.
|
|
37
|
+
|
|
38
|
+
## Preconditions
|
|
39
|
+
|
|
40
|
+
Collect these before writing code:
|
|
41
|
+
|
|
42
|
+
- Two or more valid OpenAPI 3.x source paths or URLs. Preserve source language
|
|
43
|
+
and do not overwrite originals unless the user explicitly requests it.
|
|
44
|
+
- A business narrative: actors, desired outcomes, known choices, mutations,
|
|
45
|
+
confirmation needs, and at least one realistic interruption or failure.
|
|
46
|
+
- Whether sources already have usable `servers` URLs. For a local demo, use
|
|
47
|
+
loopback server URLs and synthetic data only.
|
|
48
|
+
- A target project directory. Default to a new sibling project, never alter
|
|
49
|
+
HAPI OSS or Enterprise packages to make a demo work.
|
|
50
|
+
|
|
51
|
+
If source contracts are invalid, stop and report the validator output. Repair
|
|
52
|
+
the source as a distinct task before composing it. Do not invent operations,
|
|
53
|
+
security, or business policy to force a connected graph.
|
|
54
|
+
|
|
55
|
+
## Choose the project layout
|
|
56
|
+
|
|
57
|
+
Use a layout that makes inputs, generated artifacts, implementation, and
|
|
58
|
+
evidence easy to inspect:
|
|
59
|
+
|
|
60
|
+
```text
|
|
61
|
+
<project>/
|
|
62
|
+
contracts/
|
|
63
|
+
flights.openapi.yaml # source contract example
|
|
64
|
+
hotels.openapi.yaml
|
|
65
|
+
transfers.openapi.yaml
|
|
66
|
+
trip-composed.openapi.yaml # generated Plan B façade
|
|
67
|
+
arazzo/
|
|
68
|
+
trip.arazzo.yaml # Plan A composition
|
|
69
|
+
specs/
|
|
70
|
+
<project-name>.yaml # local cache for hapi init
|
|
71
|
+
src/<project-name>/
|
|
72
|
+
Controller.ts # generated then completed entry points
|
|
73
|
+
<Domain>Service.ts # business logic
|
|
74
|
+
Store.ts # deterministic demo state when local
|
|
75
|
+
scripts/
|
|
76
|
+
generate-composed.ts
|
|
77
|
+
check-contract-parity.ts
|
|
78
|
+
review.ts
|
|
79
|
+
test/
|
|
80
|
+
docs/
|
|
81
|
+
<project-name>.http
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
For a self-contained local HAPI project, set `HAPI_HOME` to `<project>`.
|
|
85
|
+
HAPI then uses `<project>/specs`, `<project>/src`, and `<project>/config`,
|
|
86
|
+
instead of a developer's global `~/.hapi` directory.
|
|
87
|
+
|
|
88
|
+
## Use the installed `hapi` command first
|
|
89
|
+
|
|
90
|
+
All normal setup, generation, review, OpenAPI serving, and Arazzo serving
|
|
91
|
+
commands in this skill use the installed `hapi` binary. Do not make a user run
|
|
92
|
+
`bun packages/cli/src/bin.ts`, depend on a checkout layout, or need HAPI source
|
|
93
|
+
code merely to run their generated project.
|
|
94
|
+
|
|
95
|
+
Before generating the project, verify the installed plugins and flags:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
hapi --version
|
|
99
|
+
hapi plugins list
|
|
100
|
+
hapi serve --help
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Keep regular scripts portable, for example:
|
|
104
|
+
|
|
105
|
+
```json
|
|
106
|
+
{
|
|
107
|
+
"scripts": {
|
|
108
|
+
"serve": "HAPI_HOME=$PWD hapi serve demo --specs ./contracts/composed.openapi.yaml --mcp --capability-graph --capability-planning --dev --port 3100",
|
|
109
|
+
"serve:workflows": "HAPI_HOME=$PWD hapi arazzo serve ./arazzo/workflows.arazzo.yaml --mcp --port 3101"
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
An opt-in development script can add `--capability-trace` only when the
|
|
115
|
+
installed `hapi serve --help` advertises it. If it is unavailable, explain that
|
|
116
|
+
an HAPI update is required for traces; do not replace normal project scripts
|
|
117
|
+
with source-tree commands. The offline graph/Arazzo review and ordinary MCP
|
|
118
|
+
demo must still run on the installed command.
|
|
119
|
+
|
|
120
|
+
## Step 1: inventory the domains before modelling a workflow
|
|
121
|
+
|
|
122
|
+
Read every relevant operation, parameter, schema, response, server, security
|
|
123
|
+
definition, and existing OpenAPI Link. Create a compact inventory:
|
|
124
|
+
|
|
125
|
+
| Domain | Discovery / selection | Mutations | Irreversible or confirmed actions |
|
|
126
|
+
| --- | --- | --- | --- |
|
|
127
|
+
| Flights | list, inspect, select | book, modify | cancel |
|
|
128
|
+
| Hotels | list, inspect, select | book, modify | cancel |
|
|
129
|
+
| Transfers | list, inspect, select | book | cancel |
|
|
130
|
+
|
|
131
|
+
This makes missing seams visible. For example, a workflow cannot safely book a
|
|
132
|
+
transfer that has no source operation or a meaningful verified selection.
|
|
133
|
+
|
|
134
|
+
Treat an empty **selection** cell as a blocker whenever the same row has a
|
|
135
|
+
mutation that needs a choice. Either add the selection operation to the source
|
|
136
|
+
contract (as a separate, explicit repair task) or record the choice as a
|
|
137
|
+
client-verified fact. A domain with a search and a booking but no selection
|
|
138
|
+
compiles cleanly and still cannot be planned.
|
|
139
|
+
|
|
140
|
+
Also list, per domain, each declared response that forces a new decision, such
|
|
141
|
+
as a booking `409` with `unavailable: true`. These are the demo's re-plan
|
|
142
|
+
triggers. Declare each one on the source contract's response with
|
|
143
|
+
`x-hapi-outcome` (for example, produces `hotel.unavailable` and removes
|
|
144
|
+
`hotel.selected`), and make the façade parity check compare outcomes as well
|
|
145
|
+
as `x-hapi-planning`.
|
|
146
|
+
|
|
147
|
+
Keep Controller methods thin. Put deterministic sample inventory, state
|
|
148
|
+
transitions, and failure fixtures in Services. A local demo must not use real
|
|
149
|
+
accounts, payments, customer data, or vendor credentials.
|
|
150
|
+
|
|
151
|
+
## Step 2: create accurate HCG metadata on source operations
|
|
152
|
+
|
|
153
|
+
Use `x-hapi-capability`, `x-hapi-intent`, `x-hapi-effects`,
|
|
154
|
+
`x-hapi-prerequisites`, and `x-hapi-planning` only when each statement is
|
|
155
|
+
true. The HCG enrichment skill is the specialist companion when the source
|
|
156
|
+
metadata itself needs deeper authoring.
|
|
157
|
+
|
|
158
|
+
```yaml
|
|
159
|
+
x-hapi-capability:
|
|
160
|
+
- Travel/Flights/Booking
|
|
161
|
+
x-hapi-intent:
|
|
162
|
+
use-when:
|
|
163
|
+
- The traveller has selected a flight and wants to book it.
|
|
164
|
+
avoid-when:
|
|
165
|
+
- The flight is only a search result or the traveller has not approved it.
|
|
166
|
+
x-hapi-effects:
|
|
167
|
+
mode: create # read | create | update | delete | action
|
|
168
|
+
side-effecting: true
|
|
169
|
+
destructive: false
|
|
170
|
+
idempotent: false
|
|
171
|
+
requires-confirmation: true
|
|
172
|
+
x-hapi-prerequisites:
|
|
173
|
+
- Verified traveller identity
|
|
174
|
+
- Explicit flight selection
|
|
175
|
+
x-hapi-planning:
|
|
176
|
+
requires:
|
|
177
|
+
- predicate: traveller.identity.verified
|
|
178
|
+
- predicate: flight.selected
|
|
179
|
+
produces:
|
|
180
|
+
- predicate: flight.booked
|
|
181
|
+
cost: 1
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Facts are business state, not commands, unverified fields, or intention:
|
|
185
|
+
|
|
186
|
+
- Good: `traveller.identity.verified`, `flight.selected`, `hotel.booked`.
|
|
187
|
+
- Bad: `bookHotel`, `user.wants.trip`, `date.2026-10-10` before verification.
|
|
188
|
+
|
|
189
|
+
A search can produce `hotels.consulted`; it must not produce
|
|
190
|
+
`hotel.selected` unless the API actually performs that selection. A choice
|
|
191
|
+
made outside the API belongs in the client's verified initial facts. This
|
|
192
|
+
boundary is how a plan tells an MCP client exactly what it must clarify.
|
|
193
|
+
|
|
194
|
+
Use `requires`, `produces`, `removes`, and a finite nonnegative `cost`. A
|
|
195
|
+
cancellation, for example, produces `flight.cancelled` and removes
|
|
196
|
+
`flight.booked`. Do not infer planning transitions from prose, HTTP method,
|
|
197
|
+
classifier output, or a speculative OpenAPI Link.
|
|
198
|
+
|
|
199
|
+
Cross-domain `requires` need a real business rule. `bookHotel` requiring
|
|
200
|
+
`flight.booked` means no traveller can book a hotel alone. Write such rules in
|
|
201
|
+
`docs/FACTS.md` or remove them.
|
|
202
|
+
|
|
203
|
+
Never add an OpenAPI Link that maps a search result into a selection input. A
|
|
204
|
+
selection is the person's decision, and the plan must pause to ask for it.
|
|
205
|
+
|
|
206
|
+
## Step 3: make Plan A Arazzo composition work first
|
|
207
|
+
|
|
208
|
+
Create an Arazzo 1.1 document with one relative `sourceDescription` per source
|
|
209
|
+
contract. Reference operations by their stable operation IDs and map each
|
|
210
|
+
input, output, and success condition completely.
|
|
211
|
+
|
|
212
|
+
```yaml
|
|
213
|
+
arazzo: 1.1.0
|
|
214
|
+
info: { title: Trip workflows, version: 1.0.0 }
|
|
215
|
+
sourceDescriptions:
|
|
216
|
+
- name: flights
|
|
217
|
+
url: ../contracts/flights.openapi.yaml
|
|
218
|
+
type: openapi
|
|
219
|
+
- name: hotels
|
|
220
|
+
url: ../contracts/hotels.openapi.yaml
|
|
221
|
+
type: openapi
|
|
222
|
+
workflows:
|
|
223
|
+
- workflowId: bookTrip
|
|
224
|
+
summary: Book a selected flight and hotel
|
|
225
|
+
inputs:
|
|
226
|
+
type: object
|
|
227
|
+
required: [flightId, hotelId, travellerId]
|
|
228
|
+
properties:
|
|
229
|
+
flightId: { type: string }
|
|
230
|
+
hotelId: { type: string }
|
|
231
|
+
travellerId: { type: string }
|
|
232
|
+
steps:
|
|
233
|
+
- stepId: bookFlight
|
|
234
|
+
operationId: $sourceDescriptions.flights.bookFlight
|
|
235
|
+
requestBody:
|
|
236
|
+
contentType: application/json
|
|
237
|
+
payload: { flightId: $inputs.flightId, travellerId: $inputs.travellerId }
|
|
238
|
+
successCriteria: [{ condition: $statusCode == 200 }]
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
Give a workflow `x-hapi-planning` only when its resolved steps make one stable,
|
|
242
|
+
atomic business transition. Derive its metadata from the steps it calls:
|
|
243
|
+
|
|
244
|
+
- `requires` covers every step operation's requirement that no earlier step
|
|
245
|
+
produces. A `bookTrip` workflow that books a transfer requires
|
|
246
|
+
`transfer.selected`.
|
|
247
|
+
- `produces` includes each domain fact the steps produce, such as
|
|
248
|
+
`flight.booked`, `hotel.booked` and `transfer.booked`, alongside any
|
|
249
|
+
aggregate such as `trip.booked`. Otherwise modify and cancel goals become
|
|
250
|
+
unplannable after the workflow runs.
|
|
251
|
+
- `removes` includes every fact a step removes.
|
|
252
|
+
|
|
253
|
+
Write a parity check that compares each planning-eligible workflow against its
|
|
254
|
+
steps' operation metadata, the same way the façade parity check compares
|
|
255
|
+
operations. A workflow with nested workflows or dynamic
|
|
256
|
+
success/failure branches must not become planning-eligible merely to make a
|
|
257
|
+
diagram look more complete. Failure and compensation are not automatically
|
|
258
|
+
provided by Arazzo or Capability Planning.
|
|
259
|
+
|
|
260
|
+
Validate before continuing:
|
|
261
|
+
|
|
262
|
+
```bash
|
|
263
|
+
hapi arazzo validate ./arazzo/trip.arazzo.yaml
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
## Step 4: generate a checked Plan B composed façade
|
|
267
|
+
|
|
268
|
+
Current Capability Graph compilation consumes one OpenAPI document per runtime.
|
|
269
|
+
Generate one façade from the source contracts; do not hand-maintain a second
|
|
270
|
+
copy. The generator should:
|
|
271
|
+
|
|
272
|
+
1. Merge paths only after checking globally unique operation IDs.
|
|
273
|
+
2. Merge or safely namespace component names.
|
|
274
|
+
3. Preserve each operation's HCG metadata and request/response contract.
|
|
275
|
+
4. Add only project-owned aggregate routes, such as a synthetic itinerary, in
|
|
276
|
+
a clearly separate generator section.
|
|
277
|
+
5. Mark `info.x-hapi-demo-composition.not-runtime-federation: true`.
|
|
278
|
+
|
|
279
|
+
Create a parity check that fails if a source operation is missing, duplicated,
|
|
280
|
+
or has different `x-hapi-planning` metadata in the façade. Then compile it:
|
|
281
|
+
|
|
282
|
+
```bash
|
|
283
|
+
hapi graph compile ./contracts/trip-composed.openapi.yaml --out artifacts/trip.graph.json
|
|
284
|
+
hapi graph inspect artifacts/trip.graph.json
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
## Step 5: review state behaviour before running mutations
|
|
288
|
+
|
|
289
|
+
First close the fact vocabulary. Every required fact must be produced by some
|
|
290
|
+
transition or be a documented client-verified fact, and every outcome must be
|
|
291
|
+
reachable. Run the audit on the compiled façade:
|
|
292
|
+
|
|
293
|
+
```bash
|
|
294
|
+
hapi graph audit artifacts/trip.graph.json
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
Declare client-established facts such as identity with root-level
|
|
298
|
+
`x-hapi-client-facts` in each source contract that requires them, and have the
|
|
299
|
+
façade generator merge them without duplicates.
|
|
300
|
+
|
|
301
|
+
It exits 1 on an orphan requirement or an unreachable outcome, so wire it into
|
|
302
|
+
the project's review script. When the installed CLI predates `graph audit`,
|
|
303
|
+
probe for it and fall back to the bundled script:
|
|
304
|
+
|
|
305
|
+
```bash
|
|
306
|
+
bun <skill-dir>/scripts/audit-planning-facts.ts artifacts/trip.graph.json \
|
|
307
|
+
--verified traveller.identity.verified --simulate
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
`hapi arazzo validate` also warns when a workflow's planning metadata disagrees
|
|
311
|
+
with its steps. Run it with `--strict` in the review script so those warnings
|
|
312
|
+
fail the review. A `clarification-required` result with
|
|
313
|
+
`missing-planning-facts`, `max-depth-exceeded` and zero steps almost always
|
|
314
|
+
means an orphan requirement. Record any intentionally unreachable outcome in
|
|
315
|
+
`docs/FACTS.md` with the reason.
|
|
316
|
+
|
|
317
|
+
Then run four planning cases for each important goal:
|
|
318
|
+
|
|
319
|
+
```bash
|
|
320
|
+
# Goal is already true.
|
|
321
|
+
hapi graph simulate artifacts/trip.graph.json \
|
|
322
|
+
--initial-fact trip.booked --goal-fact trip.booked
|
|
323
|
+
|
|
324
|
+
# Missing verified choice or confirmation must remain clarification-required.
|
|
325
|
+
hapi graph simulate artifacts/trip.graph.json \
|
|
326
|
+
--initial-fact traveller.identity.verified --goal-fact trip.booked
|
|
327
|
+
|
|
328
|
+
# A permitted, gate-satisfied path is planned deterministically.
|
|
329
|
+
hapi graph simulate artifacts/trip.graph.json \
|
|
330
|
+
--initial-fact traveller.identity.verified \
|
|
331
|
+
--initial-fact flight.selected \
|
|
332
|
+
--initial-fact hotel.selected \
|
|
333
|
+
--goal-fact trip.booked --satisfied-gate mutation-confirmation
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
Generate a Business State Flow and a goal-specific Plan Trace. Facts are not
|
|
337
|
+
formal UML states: the planner state is a set of facts. The HTML is local,
|
|
338
|
+
read-only inspection output; it never holds credentials, invokes an API, or
|
|
339
|
+
executes a plan.
|
|
340
|
+
|
|
341
|
+
```bash
|
|
342
|
+
hapi graph visualize artifacts/trip.graph.json \
|
|
343
|
+
--initial-fact traveller.identity.verified \
|
|
344
|
+
--goal-fact trip.booked \
|
|
345
|
+
--out artifacts/trip-state-flow.html
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
For a presentation, add `--initial-view flow-cards`. The artifact then opens
|
|
349
|
+
on step cards with confirmation waits, required inputs and "Otherwise" lines
|
|
350
|
+
for declared response outcomes, followed by any gap cards. Declared client
|
|
351
|
+
facts are used automatically.
|
|
352
|
+
|
|
353
|
+
## Step 6: initialize and implement the local HAPI backend
|
|
354
|
+
|
|
355
|
+
Cache the composed façade at `specs/<project-name>.yaml`, then run:
|
|
356
|
+
|
|
357
|
+
```bash
|
|
358
|
+
HAPI_HOME=$PWD hapi openapi init <project-name>
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
Retain the generated `src/<project-name>/Controller.ts` as the entry-point
|
|
362
|
+
layer. Complete its methods so they delegate to Services; never replace it
|
|
363
|
+
with a parallel ad-hoc server. Make seed state resettable and deterministic.
|
|
364
|
+
|
|
365
|
+
For an all-local setup, source contracts use the backend's loopback `servers`
|
|
366
|
+
URL. Start the OpenAPI backend without `--url` or `--headless`:
|
|
367
|
+
|
|
368
|
+
```bash
|
|
369
|
+
HAPI_HOME=$PWD hapi serve <project-name> \
|
|
370
|
+
--specs ./contracts/trip-composed.openapi.yaml \
|
|
371
|
+
--mcp --capability-graph --capability-planning --dev --port 3100
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
Start the workflow MCP surface separately, usually on port 3101. It uses the
|
|
375
|
+
source contracts' declared loopback server and needs no URL override:
|
|
376
|
+
|
|
377
|
+
```bash
|
|
378
|
+
HAPI_HOME=$PWD hapi arazzo serve ./arazzo/trip.arazzo.yaml --mcp --port 3101
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
If the active CLI supports it, use `--capability-trace` only with graph mode
|
|
382
|
+
and development mode. Treat `[HCG_TRACE]` as redacted development evidence,
|
|
383
|
+
not a business audit log. It must not contain raw query text, fact values,
|
|
384
|
+
arguments, responses, credentials, principal data, or confirmations.
|
|
385
|
+
|
|
386
|
+
## Step 7: demonstrate the client-led loop accurately
|
|
387
|
+
|
|
388
|
+
The portable decision-authority loop is:
|
|
389
|
+
|
|
390
|
+
1. `capability_context` receives a natural-language request and returns a
|
|
391
|
+
bounded declared vocabulary of candidate goals, relations, gates, and tools.
|
|
392
|
+
2. The client chooses a declared goal. Context does not establish truth.
|
|
393
|
+
3. `capability_plan` receives that goal and **only verified** initial facts.
|
|
394
|
+
4. A `clarification-required` result tells the client what must be verified or
|
|
395
|
+
confirmed next; gather it conversationally or through a client capability.
|
|
396
|
+
5. The client invokes a permitted direct tool or an Arazzo workflow.
|
|
397
|
+
6. Observe the result, retain only verified state, and re-plan.
|
|
398
|
+
|
|
399
|
+
In default `decision` authority, HAPI does not call the next tool for the
|
|
400
|
+
client. `capability_plan` is read-only and never grants authorization.
|
|
401
|
+
|
|
402
|
+
Guarded execution is optional and needs `--runtime-authority guarded`, an
|
|
403
|
+
exact plan, current revalidation, and a trusted externally issued confirmation
|
|
404
|
+
receipt for mutations. Do not add a fake in-process verifier just to make a
|
|
405
|
+
demo look autonomous. TypeSafe/Jev classification, when licensed, only ranks
|
|
406
|
+
an already authorized shortlist; it cannot establish business facts, approve,
|
|
407
|
+
or execute a plan.
|
|
408
|
+
|
|
409
|
+
## Test like a reviewer, not just a developer
|
|
410
|
+
|
|
411
|
+
Create a `.http` collection and a repeatable script. Cover:
|
|
412
|
+
|
|
413
|
+
- `tools/list`, `capability_context`, and `capability_plan` requests.
|
|
414
|
+
- Discovery that produces consultation but not selection.
|
|
415
|
+
- Missing fact / missing confirmation clarification.
|
|
416
|
+
- A valid book, modify, and cancel flow.
|
|
417
|
+
- A deterministic failure (for example unavailable inventory), an explicit new
|
|
418
|
+
user choice, and a re-plan. Assert no fabricated booking fact or automatic
|
|
419
|
+
continuation. Cover every re-plan trigger from the Step 1 inventory.
|
|
420
|
+
- `hapi graph audit` (or the fallback script with `--simulate`) and `hapi arazzo validate`
|
|
421
|
+
without planning warnings, or with each exception documented.
|
|
422
|
+
- Arazzo source validation and one successful multi-step workflow run.
|
|
423
|
+
- Contract parity, graph compile/inspect/simulate/visualize, direct Service
|
|
424
|
+
tests, and trace-redaction checks.
|
|
425
|
+
|
|
426
|
+
Do not put secrets, real customers, payment details, or production inventory
|
|
427
|
+
in examples, logs, fixtures, screenshots, or generated HTML.
|
|
428
|
+
|
|
429
|
+
## Deliverables and handoff
|
|
430
|
+
|
|
431
|
+
Finish with:
|
|
432
|
+
|
|
433
|
+
1. Source contract paths, the Arazzo document, and generated façade path.
|
|
434
|
+
2. Project-local setup/start/stop commands and ports.
|
|
435
|
+
3. HCG fact vocabulary and the distinction between declared vocabulary and
|
|
436
|
+
verified state.
|
|
437
|
+
4. Test commands/results, graph fingerprint, and a link to the visualization.
|
|
438
|
+
5. The fact audit output: client-verified facts, outcomes, and the
|
|
439
|
+
reachability sweep.
|
|
440
|
+
6. Re-plan triggers: each failure response, its declared `x-hapi-outcome`,
|
|
441
|
+
and the question asked next.
|
|
442
|
+
7. An explicit current-versus-future matrix covering true federation,
|
|
443
|
+
persistence, elicitation, autonomous cross-turn orchestration,
|
|
444
|
+
compensation, resumable execution, and event or schedule triggers. HCG
|
|
445
|
+
plans toward a goal. Webhooks and callbacks can be described with
|
|
446
|
+
`x-hapi-event`, but HAPI never receives them, and AsyncAPI messages and
|
|
447
|
+
time are not modelled, so never fake those with invented facts.
|
|
448
|
+
|
|
449
|
+
If a user asks only to enrich existing contracts, use the narrower
|
|
450
|
+
`jev-mcp-capability-graph` skill instead. If they want a single API turned into
|
|
451
|
+
an MCP server, use `swagger-to-mcp`. Use this skill when several domains must
|
|
452
|
+
be composed into a runnable project and demo.
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* Audit the planning fact vocabulary of a compiled HAPI Capability Graph.
|
|
4
|
+
*
|
|
5
|
+
* Reports:
|
|
6
|
+
* - orphan requirements: facts some transition requires but no transition
|
|
7
|
+
* produces and the author has not declared as client-verified;
|
|
8
|
+
* - outcomes: business facts produced but never required (candidate goals);
|
|
9
|
+
* - operations without a planning transition;
|
|
10
|
+
* - with --simulate, a reachability sweep that runs `hapi graph simulate`
|
|
11
|
+
* for every outcome with all planning operations eligible and all
|
|
12
|
+
* non-authorization gates satisfied. Any non-`planned` result is a gap.
|
|
13
|
+
*
|
|
14
|
+
* Usage:
|
|
15
|
+
* bun audit-planning-facts.ts <graph.json> [--verified <predicate>]... [--simulate]
|
|
16
|
+
*
|
|
17
|
+
* Exit code 1 when an orphan requirement or unreachable outcome is found.
|
|
18
|
+
* Read-only: never calls an upstream API.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
type Fact = { predicate: string; value?: unknown };
|
|
22
|
+
type Gate = { code: string; kind: string };
|
|
23
|
+
type Transition = {
|
|
24
|
+
id: string;
|
|
25
|
+
requires: Fact[];
|
|
26
|
+
produces: Fact[];
|
|
27
|
+
removes: Fact[];
|
|
28
|
+
gates?: Gate[];
|
|
29
|
+
target: { id: string; kind: string };
|
|
30
|
+
};
|
|
31
|
+
type Graph = {
|
|
32
|
+
operations: Array<{ id: string }>;
|
|
33
|
+
planning?: { transitions: Transition[] };
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const TECHNICAL = /^operation\..+\.completed$/;
|
|
37
|
+
const factKey = (fact: Fact) =>
|
|
38
|
+
fact.value === undefined ? fact.predicate : `${fact.predicate}=${JSON.stringify(fact.value)}`;
|
|
39
|
+
|
|
40
|
+
function parseArgs(argv: string[]) {
|
|
41
|
+
const verified = new Set<string>();
|
|
42
|
+
let graphPath: string | undefined;
|
|
43
|
+
let simulate = false;
|
|
44
|
+
for (let i = 0; i < argv.length; i++) {
|
|
45
|
+
const arg = argv[i];
|
|
46
|
+
if (arg === "--verified") verified.add(argv[++i] ?? "");
|
|
47
|
+
else if (arg === "--simulate") simulate = true;
|
|
48
|
+
else graphPath ??= arg;
|
|
49
|
+
}
|
|
50
|
+
if (!graphPath) {
|
|
51
|
+
console.error("Usage: bun audit-planning-facts.ts <graph.json> [--verified <predicate>]... [--simulate]");
|
|
52
|
+
process.exit(2);
|
|
53
|
+
}
|
|
54
|
+
return { graphPath, verified, simulate };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function indexFacts(transitions: Transition[]) {
|
|
58
|
+
const requiredBy = new Map<string, string[]>();
|
|
59
|
+
const producedBy = new Map<string, string[]>();
|
|
60
|
+
const add = (map: Map<string, string[]>, key: string, id: string) =>
|
|
61
|
+
map.set(key, [...(map.get(key) ?? []), id]);
|
|
62
|
+
for (const t of transitions) {
|
|
63
|
+
for (const f of t.requires) add(requiredBy, factKey(f), t.target.id);
|
|
64
|
+
for (const f of t.produces) if (!TECHNICAL.test(f.predicate)) add(producedBy, factKey(f), t.target.id);
|
|
65
|
+
}
|
|
66
|
+
return { requiredBy, producedBy };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** A consultation nothing requires usually means a missing selection step. */
|
|
70
|
+
function deadEndHint(key: string) {
|
|
71
|
+
return /\.consulted$/.test(key) ? " ⚠ dead-end consultation: is a selection operation missing?" : "";
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async function simulateOutcome(graphPath: string, goal: string, verified: string[], ops: string[], gates: string[]) {
|
|
75
|
+
const args = ["graph", "simulate", graphPath, "--goal-fact", goal];
|
|
76
|
+
for (const f of verified) args.push("--initial-fact", f);
|
|
77
|
+
for (const o of ops) args.push("--eligible-operation", o);
|
|
78
|
+
for (const g of gates) args.push("--satisfied-gate", g);
|
|
79
|
+
const proc = Bun.spawn(["hapi", ...args], { stdout: "pipe", stderr: "pipe" });
|
|
80
|
+
const out = (await new Response(proc.stdout).text()) + (await new Response(proc.stderr).text());
|
|
81
|
+
await proc.exited;
|
|
82
|
+
const outcome = out.match(/Outcome:\s*(\S+)/)?.[1] ?? "error";
|
|
83
|
+
const diagnostics = out.match(/Diagnostics:\s*(.+)/)?.[1]?.trim() ?? "-";
|
|
84
|
+
return { outcome, diagnostics };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async function main() {
|
|
88
|
+
const { graphPath, verified, simulate } = parseArgs(process.argv.slice(2));
|
|
89
|
+
const graph = (await Bun.file(graphPath).json()) as Graph;
|
|
90
|
+
const transitions = graph.planning?.transitions ?? [];
|
|
91
|
+
const { requiredBy, producedBy } = indexFacts(transitions);
|
|
92
|
+
let failed = false;
|
|
93
|
+
|
|
94
|
+
const orphans = [...requiredBy.keys()].filter((k) => !producedBy.has(k) && !verified.has(k)).sort();
|
|
95
|
+
const declaredVerified = [...verified].filter((k) => requiredBy.has(k)).sort();
|
|
96
|
+
const outcomes = [...producedBy.keys()].filter((k) => !requiredBy.has(k)).sort();
|
|
97
|
+
const planned = new Set(transitions.filter((t) => t.target.kind === "operation").map((t) => t.target.id));
|
|
98
|
+
const unplanned = graph.operations.map((o) => o.id).filter((id) => !planned.has(id)).sort();
|
|
99
|
+
|
|
100
|
+
console.log(`Transitions: ${transitions.length}`);
|
|
101
|
+
console.log("\nOrphan requirements (required, never produced, not declared client-verified):");
|
|
102
|
+
if (orphans.length === 0) console.log(" none");
|
|
103
|
+
for (const k of orphans) {
|
|
104
|
+
failed = true;
|
|
105
|
+
console.log(` ✗ ${k} required by ${requiredBy.get(k)!.join(", ")}`);
|
|
106
|
+
}
|
|
107
|
+
console.log("\nDeclared client-verified facts:");
|
|
108
|
+
console.log(declaredVerified.length ? declaredVerified.map((k) => ` • ${k}`).join("\n") : " none");
|
|
109
|
+
console.log("\nOutcomes (produced, never required; candidate goals):");
|
|
110
|
+
console.log(outcomes.length ? outcomes.map((k) => ` • ${k} by ${producedBy.get(k)!.join(", ")}${deadEndHint(k)}`).join("\n") : " none");
|
|
111
|
+
console.log("\nOperations without a planning transition (discovery-only):");
|
|
112
|
+
console.log(unplanned.length ? unplanned.map((id) => ` • ${id}`).join("\n") : " none");
|
|
113
|
+
|
|
114
|
+
if (simulate) {
|
|
115
|
+
const gates = [...new Set(transitions.flatMap((t) => (t.gates ?? []).filter((g) => g.kind !== "authorization").map((g) => g.code)))].sort();
|
|
116
|
+
console.log(`\nReachability sweep from {${declaredVerified.join(", ")}} with gates satisfied: ${gates.join(", ") || "-"}`);
|
|
117
|
+
for (const goal of outcomes) {
|
|
118
|
+
const result = await simulateOutcome(graphPath, goal, declaredVerified, [...planned].sort(), gates);
|
|
119
|
+
const ok = result.outcome === "planned" || result.outcome === "complete";
|
|
120
|
+
if (!ok) failed = true;
|
|
121
|
+
console.log(` ${ok ? "✓" : "✗"} ${goal}: ${result.outcome}${ok ? "" : ` (${result.diagnostics})`}`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
process.exit(failed ? 1 : 0);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
await main();
|