howone 0.2.3 → 0.2.6

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.
Files changed (23) hide show
  1. package/package.json +1 -1
  2. package/templates/vite/.howone/skills/howone/01-architect/01-app-generation.md +8 -7
  3. package/templates/vite/.howone/skills/howone/01-architect/02-manifest-codegen.md +121 -436
  4. package/templates/vite/.howone/skills/howone/03-ai-capabilities/04-workflow-operations.md +13 -4
  5. package/templates/vite/.howone/skills/howone/04-app-sdk/01-client-setup.md +94 -261
  6. package/templates/vite/.howone/skills/howone/04-app-sdk/02-entity-operations.md +85 -465
  7. package/templates/vite/.howone/skills/howone/04-app-sdk/03-auth.md +11 -7
  8. package/templates/vite/.howone/skills/howone/04-app-sdk/04-react-integration.md +84 -137
  9. package/templates/vite/.howone/skills/howone/04-app-sdk/05-file-upload.md +66 -273
  10. package/templates/vite/.howone/skills/howone/04-app-sdk/06-raw-http.md +72 -249
  11. package/templates/vite/.howone/skills/howone/04-app-sdk/07-ai-action-calls.md +135 -499
  12. package/templates/vite/.howone/skills/howone/04-app-sdk/08-ai-manifest-handoff.md +49 -196
  13. package/templates/vite/.howone/skills/howone/04-app-sdk/09-extension-boundaries.md +4 -4
  14. package/templates/vite/.howone/skills/howone/04-app-sdk/10-workflow-execute-sse.md +94 -61
  15. package/templates/vite/.howone/skills/howone/04-app-sdk/11-entity-data-access-patterns.md +4 -3
  16. package/templates/vite/.howone/skills/howone/SKILL.md +48 -8
  17. package/templates/vite/.howone/skills/howone/references/common-errors.md +27 -0
  18. package/templates/vite/.howone/skills/howone/references/version-evidence.md +47 -0
  19. package/templates/vite/.howone/skills/howone/scripts/verify-project.mjs +151 -0
  20. package/templates/vite/package.json +1 -1
  21. package/templates/vite/src/App.tsx +9 -5
  22. package/templates/vite/src/lib/sdk.ts +7 -5
  23. package/templates/vite/bun.lock +0 -1478
@@ -1,41 +1,27 @@
1
- # AI Manifest Handoff (App SDK)
1
+ # AI Manifest Handoff
2
2
 
3
- Use this reference after AI capability artifacts have been synced and app code must call the
4
- workflow through `@howone/sdk`.
3
+ Use this track after `sync_ai_artifacts` and external workflow submission. It is the short handoff
4
+ from `.howone/ai/manifest.json` to `src/lib/sdk.ts`; use `07-ai-action-calls.md` for execution
5
+ semantics and `10-workflow-execute-sse.md` for wire details.
5
6
 
6
- This file answers: **how does `.howone/ai/manifest.json` become `src/lib/sdk.ts`, and how should UI
7
- call it?**
7
+ ## Handoff protocol
8
8
 
9
- For live stream wire details, read `04-app-sdk/10-workflow-execute-sse.md`. The current endpoint emits
10
- only `run_start`, `progress`, `run_complete`, `run_error`, and `credit_insufficient`.
9
+ 1. Read the current manifest, not a cached tool response.
10
+ 2. Confirm every action has an exact UUID `workflowId`.
11
+ 3. Generate Zod input/output schemas from the manifest.
12
+ 4. Bind with `defineAiAction` and compose with `withAiActions`.
13
+ 5. Run `node .howone/skills/howone/scripts/verify-project.mjs`, typecheck, and build.
11
14
 
12
- ## Binding Source
15
+ Workflow create/update can rotate the config UUID. After every successful external workflow
16
+ operation, sync and re-read the manifest before deciding whether the app binding changed.
13
17
 
14
- Generate `src/lib/sdk.ts` from `.howone/ai/manifest.json`. Do not write AI bindings from memory,
15
- from the original prompt, or from the workflow service response.
18
+ There is no action-name fallback. A missing UUID is a module initialization error, not a server
19
+ request that can be repaired later.
16
20
 
17
- For each manifest capability/action:
18
-
19
- 1. Read stable action name/ID.
20
- 2. Read `workflowId`.
21
- 3. Read `inputSchema`.
22
- 4. Read `outputSchema`.
23
- 5. Generate Zod input and output schemas.
24
- 6. Bind with `defineAiAction(name, { workflowId, inputSchema, outputSchema })`.
25
- 7. Compose with `withAiActions(client, ai)`.
26
-
27
- `workflowId` is mandatory. Without it, the SDK falls back to the action name as the execution URL
28
- segment, and the workflow service will reject it because the segment is not a UUID.
29
-
30
- ## Generated Binding Example
21
+ ## Example
31
22
 
32
23
  ```ts
33
- import {
34
- createClient,
35
- defineAiAction,
36
- defineAiActions,
37
- withAiActions,
38
- } from '@howone/sdk'
24
+ import { createClient, defineAiAction, defineAiActions, withAiActions } from '@howone/sdk'
39
25
  import { z } from 'zod'
40
26
 
41
27
  const client = createClient({
@@ -43,22 +29,14 @@ const client = createClient({
43
29
  env: import.meta.env.VITE_HOWONE_ENV,
44
30
  })
45
31
 
46
- export const summarizeDocumentInputSchema = z.object({
47
- document_url: z.string().url(),
48
- summary_length: z.string().optional(),
49
- })
50
- export type SummarizeDocumentInput = z.infer<typeof summarizeDocumentInputSchema>
32
+ const inputSchema = z.object({ documentUrl: z.string().url() })
33
+ const outputSchema = z.object({ summary: z.string() })
51
34
 
52
- export const summarizeDocumentOutputSchema = z.object({
53
- summary: z.string(),
54
- })
55
- export type SummarizeDocumentOutput = z.infer<typeof summarizeDocumentOutputSchema>
56
-
57
- export const ai = defineAiActions({
35
+ const ai = defineAiActions({
58
36
  summarizeDocument: defineAiAction('summarizeDocument', {
59
37
  workflowId: '550e8400-e29b-41d4-a716-446655440000',
60
- inputSchema: summarizeDocumentInputSchema,
61
- outputSchema: summarizeDocumentOutputSchema,
38
+ inputSchema,
39
+ outputSchema,
62
40
  }),
63
41
  })
64
42
 
@@ -66,172 +44,47 @@ const howone = withAiActions(client, ai)
66
44
  export default howone
67
45
  ```
68
46
 
69
- ## JSON Schema To Zod
47
+ The UUID above is illustrative only. Copy the real value from the current manifest.
48
+
49
+ ## Schema conversion rules
70
50
 
71
51
  | JSON Schema | Zod |
72
52
  |---|---|
73
- | `string` | `z.string()` |
74
- | `string` + `format: "uri"` | `z.string().url()` |
75
- | `number` | `z.number()` |
76
- | `integer` | `z.number().int()` |
77
- | `boolean` | `z.boolean()` |
78
- | `array` of strings | `z.array(z.string())` |
79
- | `array` of objects | `z.array(z.object({ ... }))` |
80
- | `object` | `z.object({ ... })` |
81
- | string enum | `z.enum([...])` |
82
- | field not in `required` | `.optional()` |
83
- | nullable | `.nullable()` |
84
-
85
- Rules:
86
-
87
- - Required manifest fields must stay required in Zod.
88
- - Do not add `.passthrough()` to hide execution envelope problems.
89
- - Do not make outputs optional to silence validation failures.
90
- - If the workflow returns a different shape, fix the workflow/capability contract.
91
-
92
- ## Calling Actions
93
-
94
- For typed one-shot actions:
95
-
96
- ```ts
97
- const output = await howone.ai.summarizeDocument.run({
98
- document_url,
99
- summary_length: 'short',
100
- })
101
-
102
- setSummary(output.summary)
103
- ```
104
-
105
- When `outputSchema` exists, `.run()` returns the validated `finalResult` payload directly.
106
-
107
- Do not read:
108
-
109
- ```ts
110
- result.finalResult.summary
111
- result.data.summary
112
- result.raw.finalResult
113
- ```
114
-
115
- Those are execution-envelope paths, not the typed SDK action contract.
116
-
117
- ## Streaming And Events
118
-
119
- Use `.stream()` when UI needs live output or cancellation:
120
-
121
- ```ts
122
- const session = howone.ai.generateStory.stream(input, {
123
- onMessageChunk: (line) => appendLog(line),
124
- onProgress: (percent, line) => {
125
- if (line?.startsWith('[DISPLAY]')) setStatus(line.replace('[DISPLAY]', '').trim())
126
- if (percent === 100) setProgress(100)
127
- },
128
- onCreditInsufficient: (event) => showCreditError(event.message),
129
- onRunError: (event) => showExecutionError(event.message),
130
- onError: (error) => setError(error.message),
131
- onComplete: (result) => setRawResult(result),
132
- })
133
-
134
- cancelButton.onclick = () => session.cancel()
135
- const final = await session.result
136
- ```
137
-
138
- Use `.events()` when code wants an async iterable:
139
-
140
- ```ts
141
- for await (const event of howone.ai.generateStory.events(input)) {
142
- if (event.type === 'progress') {
143
- appendLog(String(event.message))
144
- }
145
- if (event.type === 'run_complete') {
146
- setFinalResult(event.message)
147
- }
148
- if (event.type === 'credit_insufficient') {
149
- showCreditError(String(event.message))
150
- }
151
- if (event.type === 'run_error') {
152
- showExecutionError(String(event.message))
153
- }
154
- }
155
- ```
156
-
157
- ## UI State
158
-
159
- The SDK returns data and exposes callbacks. The app owns all visible UI.
160
-
161
- Recommended states:
162
-
163
- ```ts
164
- type AiUiState<T> =
165
- | { status: 'idle' }
166
- | { status: 'running'; progress?: number }
167
- | { status: 'succeeded'; output: T }
168
- | { status: 'failed'; message: string }
169
- | { status: 'cancelled' }
170
- ```
53
+ | string | `z.string()` |
54
+ | string URI | `z.string().url()` |
55
+ | number/integer | `z.number()` / `z.number().int()` |
56
+ | boolean | `z.boolean()` |
57
+ | array | `z.array(item)` |
58
+ | object | `z.object({ ... })` |
59
+ | enum | `z.enum([...])` |
60
+ | nullable | `z.nullable(...)` |
61
+ | not required | `.optional()` |
171
62
 
172
- Do not add or import SDK toast APIs. Do not show SDK-owned overlays.
63
+ Do not make required fields optional and do not use `.passthrough()` to conceal a workflow result
64
+ shape mismatch. Typed `.run()` returns the validated final result directly.
173
65
 
174
- ## Persistence Handoff
175
-
176
- If AI output should survive refresh, use entity persistence after the action returns.
177
-
178
- For history-style products, prefer `runAiActionAndPersist()`:
66
+ ## Result handoff
179
67
 
180
68
  ```ts
181
- const result = await runAiActionAndPersist({
182
- entity: howone.entities.Generation,
183
- input: { prompt },
184
- createPending: (input) => ({
185
- prompt: input.prompt,
186
- status: 'pending',
187
- requestedAt: new Date().toISOString(),
188
- }),
189
- run: (input) => howone.ai.generateImage.run(input),
190
- mapCompleted: ({ output }) => ({
191
- status: 'completed',
192
- resultUrl: output.generated_image_url,
193
- completedAt: new Date().toISOString(),
194
- }),
195
- mapFailed: ({ error }) => ({
196
- status: 'failed',
197
- errorMessage: error instanceof Error ? error.message : 'Generation failed',
198
- }),
199
- })
200
- ```
201
-
202
- For simple save-after-success:
203
-
204
- ```ts
205
- const output = await howone.ai.summarizeDocument.run(input)
69
+ const output = await howone.ai.summarizeDocument.run({ documentUrl })
206
70
  await howone.entities.DocumentSummary.create({
207
- documentUrl: input.document_url,
71
+ documentUrl,
208
72
  summary: output.summary,
209
73
  status: 'completed',
210
74
  })
211
75
  ```
212
76
 
213
- Do not ask the workflow to write records. Do not pass owner fields for authenticated own entities.
214
-
215
- ## Workflow Edit Handoff
216
-
217
- When changing external workflow behavior later:
218
-
219
- 1. If schema changes, update AI capability contract first and sync manifest.
220
- 2. Submit update through `external-ai-capability` with `updates: [{ capabilityName, updatePrompt }]`.
221
- 3. Re-read `.howone/ai/manifest.json`; update may rotate `workflowId` to a fresh config UUID.
222
- 4. Regenerate SDK bindings whenever the manifest `workflowId`, input schema, or output schema changed.
223
- 5. Behavior-only updates still require checking the manifest before deciding SDK is unchanged.
77
+ For history, use pending-first `runAiActionAndPersist`. The workflow does not perform database CRUD;
78
+ the app persists through generated entity bindings.
224
79
 
225
- The SDK does not use old `workflowConfigID` status values. It binds to the current manifest
226
- `workflowId`, which is the EAX config id used in execution URLs.
80
+ For streaming, use `onMessageChunk`, await `session.result`, and catch terminal workflow errors.
81
+ For events, a terminal error event is yielded before the iterator rejects.
227
82
 
228
- ## Handoff Checklist
83
+ ## Final checklist
229
84
 
230
- - `.howone/ai/manifest.json` exists and is current.
231
- - Each action has `workflowId`.
232
- - Zod input/output schemas match manifest required fields.
233
- - `defineAiAction` uses action name + exact workflow UUID.
234
- - UI uses returned typed output, not raw execution envelope.
235
- - Streaming session is cancellable when UI exposes cancel.
236
- - Persistence goes through `howone.entities.*`.
237
- - Visible status/error UI is app-owned.
85
+ - [ ] Current AI manifest was read after the last sync.
86
+ - [ ] Action IDs and UUIDs match exactly.
87
+ - [ ] Zod requiredness/enums match the manifest.
88
+ - [ ] No `aiUrl`, `aiBaseUrl`, or raw EAX URL was added.
89
+ - [ ] App uses `HowOneProvider client={howone}`.
90
+ - [ ] Verifier, typecheck, and build pass.
@@ -86,7 +86,7 @@ Use callbacks instead:
86
86
 
87
87
  ```tsx
88
88
  <HowOneProvider
89
- auth="required"
89
+ client={howone}
90
90
  brand="visible"
91
91
  onAuthRedirect={({ mode, returnUrl }) => {
92
92
  setAuthUi({ redirecting: true, mode, returnUrl })
@@ -102,8 +102,8 @@ Use callbacks instead:
102
102
  Hide the logo only when explicitly requested:
103
103
 
104
104
  ```tsx
105
- <HowOneProvider brand="hidden" />
106
- <HowOneProvider showBrandButton={false} />
105
+ <HowOneProvider client={howone} brand="hidden" />
106
+ <HowOneProvider client={howone} showBrandButton={false} />
107
107
  ```
108
108
 
109
109
  ## UI Feedback Rule
@@ -132,7 +132,7 @@ For streaming workflows, use callbacks/events:
132
132
  const session = howone.ai.generateImage.stream(
133
133
  { prompt },
134
134
  {
135
- onStreamContent: (delta) => appendLog(delta),
135
+ onMessageChunk: (delta) => appendLog(delta),
136
136
  onProgress: (progress) => setProgress(progress),
137
137
  onError: (error) => setStatus({ type: 'error', message: error.message }),
138
138
  onComplete: (result) => setResult(result.finalResult),
@@ -1,52 +1,40 @@
1
1
  # Workflow Execute SSE
2
2
 
3
- Use this reference for `@howone/sdk` AI action streaming and raw workflow execution calls.
3
+ Use this reference when an app needs live workflow progress, cancellation, or raw event iteration.
4
+ For ordinary AI actions, start with `07-ai-action-calls.md` and use the typed action methods.
4
5
 
5
- ## Endpoint Contract
6
+ ## Environment and endpoint
6
7
 
7
- The SDK uses the current workflow execute SSE endpoint:
8
+ The SDK chooses the EAX base from `createClient({ env })`:
8
9
 
9
- | Method | Path |
10
+ | `env` | EAX base |
10
11
  |---|---|
11
- | `POST` | `/workflow/execute/{project_short_id}/{config_id}` |
12
+ | `local` | `https://eax-backend-orchestrator-dev.fly.dev` |
13
+ | `dev` | `https://eax-backend-orchestrator-dev.fly.dev` |
14
+ | `prod` | `https://eax-backend-orchestrator-prod.fly.dev` |
12
15
 
13
- The request body is:
16
+ Do not construct this URL in app code or override it with `aiUrl`. A workflow action calls:
14
17
 
15
- ```json
16
- {
17
- "inputs": {},
18
- "priority": "normal"
19
- }
18
+ ```text
19
+ POST {eaxBase}/workflow/execute/{projectId}/{workflowId}
20
20
  ```
21
21
 
22
- `priority` is optional. All requests require `Authorization: Bearer <JWT>`.
23
-
24
- The successful HTTP response includes:
25
-
26
- | Header | Meaning |
27
- |---|---|
28
- | `Content-Type: text/event-stream` | Streaming response |
29
- | `X-Run-Id` | Execution task id for status/cancel follow-up |
30
-
31
- Do not use or generate code for old endpoints such as
32
- `/workflow/{appId}/{workflowId}/execute_sse`.
22
+ The request body is `{ inputs: {}, priority?: 'low' | 'normal' | 'high' }` and carries the current
23
+ client token as `Authorization: Bearer ...`.
33
24
 
34
- ## Wire Format
25
+ ## Wire protocol
35
26
 
36
- Frames contain only a `data:` line and a blank line:
27
+ Frames are JSON `data:` lines separated by a blank line. The JSON envelope owns the ID and event
28
+ name; there are no separate legacy SSE `id:` or `event:` lines:
37
29
 
38
30
  ```text
39
- data: {"id":"evt_8ae6d6e8a4ea","event":"run_start","message":"execution with sse is started"}
31
+ data: {"id":"evt_1","event":"run_start","message":"execution with sse is started"}
40
32
 
41
- data: {"id":"evt_241ff985450b","event":"progress","message":"[DISPLAY] Executing node: Extract"}
33
+ data: {"id":"evt_2","event":"progress","message":"[DISPLAY] Executing node: Extract"}
42
34
 
43
- data: {"id":"evt_f3a1b2c3d4e5","event":"run_complete","message":{"video_url":"https://..."}}
35
+ data: {"id":"evt_3","event":"run_complete","message":{"imageUrl":"https://..."}}
44
36
  ```
45
37
 
46
- There are no separate SSE `event:` or `id:` lines. The JSON envelope owns those fields.
47
-
48
- ## Envelope
49
-
50
38
  ```ts
51
39
  type WorkflowSseEnvelope = {
52
40
  id: string
@@ -55,51 +43,96 @@ type WorkflowSseEnvelope = {
55
43
  }
56
44
  ```
57
45
 
58
- Event meanings:
59
-
60
- | Event | Message | Meaning |
61
- |---|---|---|
62
- | `run_start` | string | Stream opened and execution started. |
63
- | `progress` | string | One worker log line. `[DISPLAY]` lines are intended for UI. |
64
- | `run_complete` | object | Final workflow-specific output. SDK maps this to `AiResult.finalResult`. |
65
- | `run_error` | string | Workflow failed. |
66
- | `credit_insufficient` | string | Billing/credit block. |
67
-
68
46
  The terminal event is exactly one of `run_complete`, `run_error`, or `credit_insufficient`.
69
- There is no `stream_end` event.
70
-
71
- ## SDK Mapping
72
-
73
- `howone.ai.run(configId, inputs)` and typed action `.run()` call the endpoint above.
47
+ There is no `stream_end`, `node_start`, `tool_call_end`, or `ai_message_chunk` event in this
48
+ workflow endpoint.
74
49
 
75
- `AiResult` maps the stream as:
50
+ ## SDK mapping
76
51
 
77
52
  ```ts
78
53
  type AiResult = {
79
54
  success: boolean
80
- runId?: string
55
+ runId?: string // X-Run-Id response header
81
56
  outcome: 'success' | 'credit_insufficient' | 'run_error' | null
82
57
  finalResult: Record<string, unknown> | null
83
58
  progressLogs: string[]
59
+ stateData: Record<string, unknown> // deprecated; always empty for this protocol
60
+ nodeExecutions: unknown[] // deprecated; always empty
61
+ toolExecutions: unknown[] // deprecated; always empty
62
+ totalDuration: number
84
63
  errors: string[]
85
64
  events: AiEvent[]
86
65
  }
87
66
  ```
88
67
 
89
- For typed actions with an `outputSchema`, `.run()` returns the validated `run_complete.message`
90
- object directly.
68
+ `run_complete.message` becomes `finalResult`. `progress.message` lines append to `progressLogs`.
69
+ `run_error` and `credit_insufficient` append errors and cause `run()`/`stream.result` to reject with
70
+ `WorkflowExecutionError`; the `events()` iterator yields the terminal event before rejecting.
71
+
72
+ ## UI pattern
73
+
74
+ ```tsx
75
+ function WorkflowProgress({ input }: { input: GenerateInput }) {
76
+ const [logs, setLogs] = useState<string[]>([])
77
+ const [error, setError] = useState<string | null>(null)
78
+
79
+ async function run() {
80
+ const controller = new AbortController()
81
+ try {
82
+ const result = await howone.ai.generate.run(input, {
83
+ signal: controller.signal,
84
+ onMessageChunk: (line) => setLogs((current) => [...current, line]),
85
+ })
86
+ console.log(result)
87
+ } catch (value) {
88
+ setError(value instanceof Error ? value.message : String(value))
89
+ }
90
+ }
91
+
92
+ return <button onClick={() => void run()}>Run workflow</button>
93
+ }
94
+ ```
95
+
96
+ For a cancel button, keep the controller/session in a ref and call `abort()` or `session.cancel()`.
97
+ Treat abort as a user action in UI; do not report it as an EAX `run_error`.
91
98
 
92
- ## Callback Rules
99
+ ## Callback rules
93
100
 
94
- - `onRunStart(event)` receives the `run_start` envelope.
95
- - `onMessageChunk(text, event)` receives each `progress.message` line.
96
- - `onProgress(0, message)` receives each progress line; `onProgress(100)` fires on success.
97
- - `onRunComplete(event, result)` receives `event.message` as the final object.
98
- - `onRunError(event)` receives `event.message` as the error string.
99
- - `onCreditInsufficient(event)` receives `event.message` as the credit error string.
100
- - `onEvent(event, 'workflow')` fires for every parsed envelope.
101
+ - `onEvent(event, 'workflow')` fires for each parsed envelope.
102
+ - `onRunStart` fires for `run_start`.
103
+ - `onMessageChunk(text, event)` fires for each non-empty progress line.
104
+ - `onProgress(0, message)` is a compatibility callback for progress lines; `onProgress(100)` fires
105
+ on successful completion.
106
+ - `onRunComplete` receives the final object.
107
+ - `onRunError` and `onCreditInsufficient` receive terminal messages.
108
+ - `onComplete` receives the assembled result before a terminal failure is rejected.
109
+ - `onError` receives transport and terminal errors.
101
110
 
102
- Do not write UI code that expects `event.payload.result`, `event.payload.details.reason`,
103
- `node_start`, `tool_call_end`, `state_update`, or `ai_message_chunk`. Those belong to an old
104
- multi-channel protocol and are not part of the current workflow execute SSE contract.
111
+ Callbacks are not a substitute for awaiting `run()` or `session.result`; always handle the promise.
112
+ The SDK retains at most 1,000 events and 500 progress lines by default while still invoking callbacks
113
+ for every event. Configure the bounds for a diagnostic screen rather than retaining unbounded logs.
105
114
 
115
+ ## Raw event iteration
116
+
117
+ ```ts
118
+ try {
119
+ for await (const event of howone.ai.generate.events(input)) {
120
+ renderEvent(event)
121
+ }
122
+ } catch (error) {
123
+ renderFailure(error)
124
+ }
125
+ ```
126
+
127
+ On a terminal failure, consumers see the `run_error` or `credit_insufficient` event before the
128
+ iterator rejects. Breaking the loop cancels the underlying reader.
129
+
130
+ ## Common mistakes
131
+
132
+ | Mistake | Fix |
133
+ |---|---|
134
+ | Calling the old `/workflow/{appId}/{id}/execute_sse` path | Use the typed SDK action. |
135
+ | Adding `/api` to EAX URL | EAX base is not the REST base; app code should not build it. |
136
+ | Using `onStreamChunk`/`onTextChunk` | Use `onMessageChunk`. |
137
+ | Treating `run_error` as a successful result | Catch `WorkflowExecutionError` and inspect `outcome`. |
138
+ | Parsing `event.payload.result` | Read `event.message`; `run_complete` message is the final object. |
@@ -189,7 +189,7 @@ Schema:
189
189
  "create": "scoped",
190
190
  "update": "none",
191
191
  "delete": "none",
192
- "requiredScopes": ["created_by_user_id"],
192
+ "requiredScopes": ["submissionKey"],
193
193
  "allowedFilters": [],
194
194
  "allowedSorts": [],
195
195
  "defaultLimit": 1,
@@ -203,7 +203,7 @@ Frontend:
203
203
 
204
204
  ```ts
205
205
  await howone.public.entities.Feedback.create({
206
- created_by_user_id: projectUserId,
206
+ submissionKey,
207
207
  message,
208
208
  rating,
209
209
  })
@@ -211,7 +211,8 @@ await howone.public.entities.Feedback.create({
211
211
 
212
212
  Rules:
213
213
 
214
- - Public create needs a clear `created_by_user_id` source when backend requires ownership mapping.
214
+ - Public create must use the declared public scope/business fields. Never pass ownership/system
215
+ fields such as `created_by_user_id`; the SDK rejects them and the backend derives ownership/scope.
215
216
  - Do not expose public read unless needed.
216
217
  - Add anti-abuse UX/server constraints outside the dynamic schema when needed.
217
218
  - Never persist UI-only fields from form components.
@@ -13,6 +13,24 @@ HowOne builds generated full-stack AI apps. Platform contracts are separate from
13
13
 
14
14
  Load only the track needed for the user's request. Do not read SDK files while designing backend or AI contracts unless the task has reached manifest-to-code implementation.
15
15
 
16
+ ## Version Evidence (Mastra-style)
17
+
18
+ Before writing app SDK code, establish this evidence chain:
19
+
20
+ ```text
21
+ synced project manifests
22
+ → app package.json SDK range
23
+ → installed @howone/sdk version
24
+ → installed dist/*.d.ts and runtime exports
25
+ → this Skill's matching recipe
26
+ → remote docs only as supplemental context
27
+ ```
28
+
29
+ Read `references/version-evidence.md` and run
30
+ `node .howone/skills/howone/scripts/verify-project.mjs` from the app root. If the requested and
31
+ installed SDK versions disagree, install dependencies before coding. Never solve a version mismatch
32
+ with `any`, guessed signatures, or copied latest documentation.
33
+
16
34
  ## Trigger Preconditions
17
35
 
18
36
  Use this skill before work when any condition is true:
@@ -100,7 +118,8 @@ AI contract:
100
118
 
101
119
  ```text
102
120
  get_current_ai_capabilities -> apply_capability_patch -> sync_ai_artifacts -> external-ai-capability
103
- -> wait for terminal result -> sync_ai_artifacts -> read .howone/ai/manifest.json
121
+ -> continue independent app implementation (never end merely to wait)
122
+ -> terminal result -> sync_ai_artifacts -> read .howone/ai/manifest.json
104
123
  ```
105
124
 
106
125
  No contract dry-run step. Normal generation applies one well-formed patch directly. For destructive,
@@ -110,10 +129,14 @@ approved patch.
110
129
  SDK/code:
111
130
 
112
131
  ```text
113
- read synced manifests -> choose auth posture -> update src/lib/sdk.ts -> install HowOneProvider
114
- -> implement UI calls using src/lib/sdk.ts imports -> validate auth and runtime wiring
132
+ read version evidence -> read synced manifests -> choose auth posture -> update src/lib/sdk.ts
133
+ -> install HowOneProvider (client={howone}) -> implement UI calls using generated bindings
134
+ -> run project verifier -> typecheck/build
115
135
  ```
116
136
 
137
+ For fragile operations, prefer the deterministic verifier and the operation recipes over retyping
138
+ transport details in an app. Run the verifier again after manifest or dependency changes.
139
+
117
140
  ## App Runtime Activation Gate
118
141
 
119
142
  Classify runtime activation before editing the application shell:
@@ -125,13 +148,13 @@ Classify runtime activation before editing the application shell:
125
148
  one module-level client in `src/lib/sdk.ts`, import it before Provider initialization, and wrap the
126
149
  application root in `HowOneProvider`.
127
150
  - HowOne AI, uploads, and `howone.entities.*` authenticated/private access require a real token path.
128
- Default to hosted auth with `HowOneProvider auth="required"` unless the product explicitly requires
129
- a custom or external login experience.
151
+ Use the default hosted client and pass the composed singleton to
152
+ `<HowOneProvider client={howone}>`; its required guard is inherited from `client.auth.guard`.
130
153
  - A custom HowOne login requires `createClient({ auth: 'custom', loginPath })`, a real OTP/OAuth flow
131
- that writes the returned token through `howone.auth.setToken()`, and `HowOneProvider auth="none"`
132
- with app-owned route guards. A login-looking screen without token acquisition is not completion.
154
+ that writes the returned token through `howone.auth.setToken()`, and a Provider receiving that
155
+ client. A login-looking screen without token acquisition is not completion.
133
156
  - An app that uses only manifest-approved `howone.public.entities.*` access may use
134
- `HowOneProvider auth="optional"`; do not force login for a deliberately public experience.
157
+ `auth: 'none'`; the Provider inherits a non-required guard and must not force login.
135
158
 
136
159
  Do not delete the scaffold Provider while replacing `App.tsx` or `main.tsx` when the HowOne runtime
137
160
  is active. Before completion, verify the Provider still wraps the rendered app, the SDK module is
@@ -144,6 +167,7 @@ data, local-only persistence, or unauthenticated fallback behavior.
144
167
  - Backend fields/access/indexes: `{appRoot}/.howone/database/manifest.json` after sync.
145
168
  - AI names/workflow IDs/schemas: `{appRoot}/.howone/ai/manifest.json` after sync.
146
169
  - App runtime entry: `{appRoot}/src/lib/sdk.ts`.
170
+ - Installed SDK typings/runtime: `{appRoot}/node_modules/@howone/sdk/dist/*` after package install.
147
171
  - Do not handwrite `.howone/` metadata.
148
172
  - Do not infer contract identifiers from prompts, memory, or dependency source.
149
173
 
@@ -196,6 +220,14 @@ Do not read unrelated capability references.
196
220
  | `04-app-sdk/11-entity-data-access-patterns.md` | App entity access calls from synced manifest |
197
221
  | `04-app-sdk/12-query-dsl-and-responses.md` | App query/filter/sort/pagination calls |
198
222
 
223
+ ### References and scripts
224
+
225
+ | Resource | Use |
226
+ |---|---|
227
+ | `references/version-evidence.md` | Verify installed SDK version and declaration/runtime evidence before coding |
228
+ | `references/common-errors.md` | Diagnose stale signatures, routing, auth, query, upload, and AI failures |
229
+ | `scripts/verify-project.mjs` | Deterministically check project wiring and manifest workflow IDs |
230
+
199
231
  ## Hard Rules
200
232
 
201
233
  - Backend and AI design references must not include SDK implementation work.
@@ -206,6 +238,14 @@ Do not read unrelated capability references.
206
238
  - An active HowOne app runtime must include `src/lib/sdk.ts`, `HowOneProvider`, and the auth posture
207
239
  required by its AI/entity/upload access. Contract creation without usable authenticated app wiring
208
240
  is incomplete.
241
+ - New app code must pass the composed singleton to `<HowOneProvider client={howone}>`; the Provider
242
+ derives its default route guard from `client.auth.guard`.
243
+ - EAX workflow URLs are owned by `env`: `local/dev` use
244
+ `https://eax-backend-orchestrator-dev.fly.dev`, and `prod` uses
245
+ `https://eax-backend-orchestrator-prod.fly.dev`. Do not add `aiUrl`, `aiBaseUrl`, or a raw EAX URL.
246
+ - `howone.raw.*` resolves to response data, not an AxiosResponse. Its REST base already includes
247
+ `/api`; app-owned paths must not repeat that prefix.
248
+ - Use `@howone/sdk/devtools` for `ElementSelectorProvider`; keep core free of React imports.
209
249
  - AI workflows must not perform database CRUD; persistence is app code through entities.
210
250
  - Detailed capability references constrain workflow design; they do not replace synced manifests or
211
251
  define direct app SDK calls.