@wowok/skills 1.3.1 → 1.3.2
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/package.json +1 -1
- package/wowok-auditor/SKILL.md +4 -1
- package/wowok-guard/SKILL.md +24 -0
- package/wowok-machine/SKILL.md +18 -0
- package/wowok-onboard/SKILL.md +52 -0
- package/wowok-order/SKILL.md +80 -0
- package/wowok-provider/SKILL.md +54 -8
- package/wowok-safety/SKILL.md +12 -0
- package/wowok-scenario/SKILL.md +143 -2
- package/wowok-tools/SKILL.md +124 -13
package/package.json
CHANGED
package/wowok-auditor/SKILL.md
CHANGED
|
@@ -108,7 +108,10 @@ This Skill keeps the **audit flow**, the **4 audit dimensions** (Guard completen
|
|
|
108
108
|
| Permission configured | Permission object exists with correct indices for every Forward | FAIL: no Permission |
|
|
109
109
|
| Allocators configured | `order_allocators` non-empty and each Allocator audited | FAIL: no Allocators |
|
|
110
110
|
| User confirmation | User has explicitly confirmed publish intent | FAIL: no confirmation |
|
|
111
|
-
| Compensation fund | `compensation_fund` funded (recommended for trust) | WARN: empty fund |
|
|
111
|
+
| Compensation fund present | `compensation_fund` funded (recommended for trust) | WARN: empty fund |
|
|
112
|
+
| Compensation fund invariant | If `compensation_fund > 0` then `arbitrations` MUST be non-empty (per service.move:494-496 `assert!(vector::length(&self.arbitrations) > 0, E_ARBITRATION_NOT_SET_WITH_COMPENSATION_FUND)`) | FAIL: funded but no Arbitration bound |
|
|
113
|
+
| order_allocators immutability | `order_allocators` set BEFORE publish (service.move:503 `assert!(!self.bPublished)`) — cannot be modified post-publish | FAIL: attempt to modify after publish |
|
|
114
|
+
| machine immutability | `machine` bound BEFORE publish (service.move:633 `assert!(!self.bPublished)`) — cannot be modified post-publish | FAIL: attempt to modify after publish |
|
|
112
115
|
| Backup export | `machineNode2file` + `guard2file` backups persisted | WARN: no backup |
|
|
113
116
|
|
|
114
117
|
---
|
package/wowok-guard/SKILL.md
CHANGED
|
@@ -160,6 +160,30 @@ External Repository? → query + table entry declaring Repository address
|
|
|
160
160
|
Entity reputation? → query + table entry declaring ENTITY_REGISTRAR_ADDRESS(0xaab) / ENTITY_LINKER_ADDRESS(0xaaa)
|
|
161
161
|
```
|
|
162
162
|
|
|
163
|
+
### Circular Dependency Handling (Service ↔ Guard)
|
|
164
|
+
|
|
165
|
+
When a Guard needs to query a Service that doesn't exist yet (classic chicken-and-egg: `buy_guard` validates Service fields, but Service creation needs `buy_guard` address), use **LocalMark NAME** (not address) in the Guard's query table. LocalMark is resolved by the SDK at publish time, deferring the address binding until after both objects exist.
|
|
166
|
+
|
|
167
|
+
**Workflow** (4-step safe pattern):
|
|
168
|
+
1. **Create Service (no publish)** — Service exists as draft with a LocalMark name
|
|
169
|
+
2. **Create Guard** — In the Guard table, reference the Service via `name_or_address: "<service_local_mark_name>"` (NOT a hardcoded address)
|
|
170
|
+
3. **Update Service** — Set `service.buy_guard = <guard_address>` (now resolvable since Guard is created)
|
|
171
|
+
4. **Publish Service** — SDK resolves LocalMark → on-chain address; both objects now bound
|
|
172
|
+
|
|
173
|
+
> **Why LocalMark**: A Guard that hardcodes a Service address fails creation if the Service doesn't exist yet. LocalMark defers resolution, allowing the Guard to be created first (or in either order). This pattern also applies to Allocator guards that query Service fields.
|
|
174
|
+
|
|
175
|
+
### Triple Verification Template (withdraw / refund Guards)
|
|
176
|
+
|
|
177
|
+
Withdraw and refund guards protect fund flow — the highest-risk operations. They should verify **three independent conditions** (AND-ed):
|
|
178
|
+
|
|
179
|
+
| # | Verification | What to Check | Typical Query |
|
|
180
|
+
|---|---------------|---------------|---------------|
|
|
181
|
+
| (a) | Order status | Order is in expected state (not cancelled / disputed) | `order.service` + `order.balance` |
|
|
182
|
+
| (b) | Progress node completion | Progress.current == expected node (e.g. "completion" / "refund") | `progress.current` (via witness 100 from Order) |
|
|
183
|
+
| (c) | Permission / role | Caller is authorized (merchant / arbiter / customer) | `permission.owner` / `permission.admin has` (Level 2 identity-set) |
|
|
184
|
+
|
|
185
|
+
> **Why all three**: (a) alone is insufficient — Order may be in correct state but Progress not advanced. (b) alone is insufficient — Progress may be at correct node but Order cancelled. (c) alone is insufficient — caller may be authorized but state wrong. AND-combine all three at the Guard root.
|
|
186
|
+
|
|
163
187
|
### Design Before Building
|
|
164
188
|
|
|
165
189
|
**Design thoroughly before calling the create operation** — there is no edit phase after creation (see The Immutability Contract above).
|
package/wowok-machine/SKILL.md
CHANGED
|
@@ -220,14 +220,32 @@ All stem from the same root: **every on-chain object has a publish/create freeze
|
|
|
220
220
|
- Never publish before all Guards are created, tested, and bound.
|
|
221
221
|
- `clear` is irreversible — export via `machineNode2file` first.
|
|
222
222
|
|
|
223
|
+
### R-M1-11 Anti-Pattern: Refund Terminal Nodes (CRITICAL)
|
|
224
|
+
|
|
225
|
+
**Forbidden node names**: `deposit_refunded`, `deposit_deducted`, `refunded`, `disputed` (when used as a refund terminal), or any node implying the Machine itself performs fund movement.
|
|
226
|
+
|
|
227
|
+
**Why forbidden**: Machines are pure state machines — they have **no fund movement primitive**. A node named `deposit_refunded` suggests the Machine itself refunds the deposit, but in reality it just routes; the actual refund MUST be triggered by an **Allocator** watching `progress.current == <trigger_node>`. Without a bound Allocator, funds lock permanently in the Order escrow.
|
|
228
|
+
|
|
229
|
+
**Correct pattern**:
|
|
230
|
+
| Wrong | Correct (R-M1-11 compliant) |
|
|
231
|
+
|-------|------------------------------|
|
|
232
|
+
| `deposit_refunded` (terminal) | `return_approved` (routing) → Allocator fires refund |
|
|
233
|
+
| `deposit_deducted` (terminal) | `damage_confirmed` (routing) → Allocator fires deduction |
|
|
234
|
+
| `refunded` (terminal) | `refund_routing` (routing) → Allocator fires |
|
|
235
|
+
| N/A (dispute) | `arbiter_rule` (routing) → Arbitration off-Machine (no Allocator) |
|
|
236
|
+
|
|
237
|
+
See [wowok-scenario](../wowok-scenario/SKILL.md) "Rental Mode Template (R-M1-11 Compliant)" for a complete 10-node rental topology.
|
|
238
|
+
|
|
223
239
|
### Pre-Publish Validation Checklist
|
|
224
240
|
|
|
225
241
|
Before `publish: true`, verify:
|
|
226
242
|
|
|
227
243
|
- [ ] **Entry point exists**: at least one Pair with `prev_node: ""` — workflow cannot start otherwise
|
|
244
|
+
- [ ] **⚠ Entry node has ≥1 Forward** (CRITICAL): entry node (`prev_node: ""`) MUST have at least one forward — Progress starts at `current=""` and follows the entry node's forwards to advance. Empty entry forwards = Progress permanently stuck at `current=""`. Schema-enforced (P0-2). Example: `{name:"Ordered", pairs:[{prev_node:"", forwards:[{next_node:"Ordered", namedOperator:"", weight:1}]}]}`
|
|
228
245
|
- [ ] **Every node has outgoing Forwards** (except terminals): no dead-end nodes
|
|
229
246
|
- [ ] **Every node has incoming Pair** (except entry): no orphaned nodes
|
|
230
247
|
- [ ] **All thresholds independently achievable**: no dead branches (competing Pair always wins first)
|
|
248
|
+
- [ ] **⚠ R-M1-11 Compliance** (CRITICAL for deposit/refund scenarios): NO terminal nodes named `deposit_refunded`, `deposit_deducted`, `refunded`, or any name implying Machine-internal refund/deduction. Refund/deduction MUST flow through an **Allocator** triggered by a routing node (e.g., `return_approved`, `damage_confirmed`, `arbiter_rule`). Violating this causes funds to lock in the Machine with no Allocator path. See [wowok-scenario](../wowok-scenario/SKILL.md) "Rental Mode Template (R-M1-11 Compliant)".
|
|
231
249
|
- [ ] All Guards exist on-chain and tested (use `gen_passport`)
|
|
232
250
|
- [ ] `namedOperator` vs `permissionIndex` correct per Forward
|
|
233
251
|
- [ ] Every Forward has at least one of `namedOperator` or `permissionIndex`
|
package/wowok-onboard/SKILL.md
CHANGED
|
@@ -92,3 +92,55 @@ The onboarding flow is backed by the MCP SQLite-based project pipeline. Each ste
|
|
|
92
92
|
| R8 | Allocation | `onchain_operations.allocation` CREATE + `service.order_allocators` | Fund split (mode defaults from MCP) |
|
|
93
93
|
| R9 | Test order | `onchain_operations.order` CREATE + `progress` advance + `allocation.alloc_by_guard` | Full flow dry run |
|
|
94
94
|
| R10 | Publish | `onchain_operations.machine` publish + `service` publish | Pre-publish audit must PASS |
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## Industry Selection Guide
|
|
99
|
+
|
|
100
|
+
When the user describes their business (R2), match to one of the supported industry modes. The MCP layer auto-fills scenario defaults when `project_industry` is passed to `create_project`.
|
|
101
|
+
|
|
102
|
+
| Industry | Mode | One-line Description |
|
|
103
|
+
|----------|------|----------------------|
|
|
104
|
+
| `general` | `general` | Free-form / hybrid — no presets, full manual control |
|
|
105
|
+
| `retail` | `general` (retail profile) | Physical goods sales with stock + WIP product listings |
|
|
106
|
+
| `service` | `general` (service profile) | Intangible services (consulting, design) — milestone delivery |
|
|
107
|
+
| `rental` | `rental` | Equipment / vehicle / property rental with deposit escrow + return inspection (R-M1-11 compliant — uses `return_approved`/`damage_confirmed`/`arbiter_rule` routing nodes, NO `deposit_refunded`/`deposit_deducted`/`refunded` terminal nodes; see [wowok-scenario](../wowok-scenario/SKILL.md) "Rental Mode Template") |
|
|
108
|
+
| `freelance` | `freelance` | Design / dev / consulting — milestone allocation + acceptance gate |
|
|
109
|
+
| `education` | `education` | Courses / training / tutoring — periodic release per session attendance |
|
|
110
|
+
| `travel` | `travel` | Custom tours / multi-segment trips — multi-tier allocation per segment |
|
|
111
|
+
| `subscription` | `subscription` | SaaS / content membership / periodic service — periodic charge + cancel guard |
|
|
112
|
+
|
|
113
|
+
> If unsure which fits, default to `general`. Users can switch modes mid-onboarding (see wowok-scenario "Escape Hatch"). For mode composition (e.g., freelance + subscription retainer), see [wowok-scenario](../wowok-scenario/SKILL.md).
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## Deployment Checklist
|
|
118
|
+
|
|
119
|
+
Before declaring onboarding complete, verify ALL items. Each is a hard gate — a missing item blocks successful order flow.
|
|
120
|
+
|
|
121
|
+
| # | Item | How to Verify |
|
|
122
|
+
|---|------|---------------|
|
|
123
|
+
| 1 | Permission created | `query_toolkit` (onchain_objects, type=permission) returns object |
|
|
124
|
+
| 2 | Machine published | `query_toolkit` (onchain_objects, type=machine) → `bPublished: true` |
|
|
125
|
+
| 2b | **R-M1-11 compliance** (rental / deposit / refund scenarios only) | `machineNode2file` export → grep node names; MUST NOT contain `deposit_refunded`/`deposit_deducted`/`refunded`; MUST contain routing nodes (`return_approved`/`damage_confirmed`/`arbiter_rule`) with bound Allocators (item 4) |
|
|
126
|
+
| 3 | Guards created | `query_toolkit` (onchain_objects, type=guard) returns all expected guards |
|
|
127
|
+
| 4 | Allocators configured | Each Allocator `sharing[].sharing` Rate entries sum to **10000** (Rate mode); or `Amount` mode values set. For rental/refund scenarios, verify Allocators' `trigger_node` references the routing nodes (e.g., `return_approved`) — NOT missing (else R-M1-11 refund path is broken) |
|
|
128
|
+
| 5 | Service created with all bindings | `query_toolkit` (onchain_objects, type=service) → machine, order_allocators, buy_guard all non-empty |
|
|
129
|
+
| 6 | Service published | `query_toolkit` (onchain_objects, type=service) → `bPublished: true` |
|
|
130
|
+
| 7 | Test order placed | R9 test order created + Progress advanced + Allocator triggered successfully |
|
|
131
|
+
|
|
132
|
+
> If any item fails, do NOT proceed to handoff. Fix the underlying issue, then re-verify. Use `project_operation.evaluate_project` (risk) to auto-detect missing bindings.
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## Common Errors
|
|
137
|
+
|
|
138
|
+
| Error | Cause | Fix |
|
|
139
|
+
|-------|-------|-----|
|
|
140
|
+
| `dynamicFieldNotFound` | SDK cannot resolve a dynamic field reference | Set `env.account` (account not configured) — pass account in the tool call wrapper |
|
|
141
|
+
| `Circular dependency` (Guard ↔ Service creation) | Guard needs Service address; Service needs Guard address | Use **LocalMark NAME** (not address) in Guard query table — see [wowok-guard](../wowok-guard/SKILL.md) "Circular Dependency Handling" |
|
|
142
|
+
| `order.balance invalid` | Used wrong field for order amount | Use `order.amount` (not `order.balance` — `balance` is residual escrow, `amount` is original payment) |
|
|
143
|
+
| Allocator `rate sum != 10000` | Rate-mode Allocator sharing percentages don't sum to 100% | Ensure all `sharing[].sharing` values in Rate mode sum to exactly **10000** basis points (e.g., 80% = 8000) |
|
|
144
|
+
| `IMPACK_GUARD_NOT_FOUND` (gen_passport) | Repository query with `quote_guard = Some(addr)` | `impack_list` is empty during verify phase — only `quote_guard = None` passes; see [wowok-guard](../wowok-guard/SKILL.md) |
|
|
145
|
+
| `Permission denied` (Progress advance, abort code 5) | Wrong operation path for forward's `namedOperator` | Empty `namedOperator` → use `order.progress`; non-empty → use `progress.operate`; `permissionIndex` → use `progress.operate` |
|
|
146
|
+
| Allocator never fires (refund stuck) — R-M1-11 violation | Machine has a node like `deposit_refunded` instead of routing node `return_approved`; or Allocator's `trigger_node` is missing/mispelled | Rename node to `return_approved` / `damage_confirmed`; bind Allocator to that node; see [wowok-scenario](../wowok-scenario/SKILL.md) "Rental Mode Template (R-M1-11 Compliant)" |
|
package/wowok-order/SKILL.md
CHANGED
|
@@ -75,6 +75,8 @@ Use `wip_file` → `op: "verify"`, `wipFilePath: "<wip_url>"`, `hash_equal: "<wi
|
|
|
75
75
|
|
|
76
76
|
**Step 1**: `query_toolkit` → `onchain_objects` for `<machine_id>`. Fail if `bPublished === false` or `bPaused === true`.
|
|
77
77
|
|
|
78
|
+
**Step 1b (entry-node forward check)**: In the exported Machine JSON, verify the entry node (`prev_node: ""`) has ≥1 forward. If empty, Progress will be permanently stuck at `current=""` — flag as 🔴 BLOCKER. (New Machines are schema-blocked from this, but legacy Machines may still have it.)
|
|
79
|
+
|
|
78
80
|
**Step 2**: `machineNode2file` → export the complete Machine JSON. Contains all nodes and forwards — parse locally, never node-by-node. Machine structure: see [wowok-machine](../wowok-machine/SKILL.md).
|
|
79
81
|
|
|
80
82
|
**Step 3: Classify every forward**:
|
|
@@ -252,6 +254,15 @@ Present all three dimensions. Never just the operation name.
|
|
|
252
254
|
- `namedOperator === ""` + Guard → passport required, no bypass
|
|
253
255
|
- `namedOperator !== ""` → not user-operable
|
|
254
256
|
|
|
257
|
+
### Progress.current="" Diagnostic (stuck at initial state)
|
|
258
|
+
|
|
259
|
+
If `query_toolkit` returns a Progress with `current: ""`:
|
|
260
|
+
|
|
261
|
+
1. **Root cause**: Machine entry node (`prev_node: ""`) has empty `forwards[]` — Progress cannot advance.
|
|
262
|
+
2. **Query Machine**: `query_toolkit` → `onchain_objects` for `progress.machine` → inspect `node.pairs` for `prev_node: ""`.
|
|
263
|
+
3. **If forwards empty**: Machine must be republished with ≥1 forward on the entry node (nodes are immutable after publish — clone + add forward + new Machine + rebind to Service).
|
|
264
|
+
4. **MCP auto-diagnostic**: `query_toolkit` now attaches `_diagnostic` with cause + 5-step fix guide when `current=""` is detected.
|
|
265
|
+
|
|
255
266
|
---
|
|
256
267
|
|
|
257
268
|
## Phase 5: Arbitration
|
|
@@ -268,6 +279,75 @@ Flow: `arbitration.dispute` → WTS evidence → Messenger → `order.arb_confir
|
|
|
268
279
|
|
|
269
280
|
Builder-only operations: `order.transfer_to` (ownership), `order.receive` (withdraw — agents can execute, only builder receives).
|
|
270
281
|
|
|
282
|
+
### How to Withdraw Funds from Order (`order.receive`)
|
|
283
|
+
|
|
284
|
+
After Allocation distributes funds to Order (as `CoinWrapper` objects), the builder (Order owner) MUST call `order.receive` to unwrap and withdraw the funds. Without this step, funds stay locked in the Order as `CoinWrapper` objects.
|
|
285
|
+
|
|
286
|
+
> **P0-01 / P3-04 fix**: The `receive` field now uses `ReceivedObjectsOrRecentlySchema` (consistent with `owner_receive` on all other objects). Do NOT wrap in `{result: ...}` — pass directly.
|
|
287
|
+
|
|
288
|
+
#### Step 1: Query received objects (optional but recommended)
|
|
289
|
+
|
|
290
|
+
Before calling `order.receive`, query what the Order has received to verify there are funds to withdraw:
|
|
291
|
+
|
|
292
|
+
```json
|
|
293
|
+
{
|
|
294
|
+
"tool": "query_toolkit",
|
|
295
|
+
"query_type": "onchain_received",
|
|
296
|
+
"name_or_address": "<order_name_or_address>",
|
|
297
|
+
"type": "CoinWrapper"
|
|
298
|
+
}
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
**Expected response**: A `ReceivedBalance` object with `token_type`, `balance`, and `received[]` array of `CoinWrapper` objects. If empty, there is nothing to withdraw yet.
|
|
302
|
+
|
|
303
|
+
#### Step 2: Call `order.receive` with `"recently"` (simplest form)
|
|
304
|
+
|
|
305
|
+
Use the string `"recently"` to auto-receive all recently received `CoinWrapper` objects:
|
|
306
|
+
|
|
307
|
+
```json
|
|
308
|
+
{
|
|
309
|
+
"tool": "onchain_operations",
|
|
310
|
+
"data": {
|
|
311
|
+
"operation_type": "order",
|
|
312
|
+
"data": {
|
|
313
|
+
"object": "<order_name_or_address>",
|
|
314
|
+
"receive": "recently"
|
|
315
|
+
}
|
|
316
|
+
},
|
|
317
|
+
"env": {
|
|
318
|
+
"account": "<builder_account_name>",
|
|
319
|
+
"network": "testnet",
|
|
320
|
+
"confirmed": true
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
**Expected result**:
|
|
326
|
+
- Builder account receives the underlying token (e.g., WOW) — `CoinWrapper` is auto-unwrapped
|
|
327
|
+
- Transaction digest returned
|
|
328
|
+
- Order's received `CoinWrapper` objects are consumed
|
|
329
|
+
|
|
330
|
+
#### Step 2 (alternatives)
|
|
331
|
+
|
|
332
|
+
For precise control, pass an explicit array of `{id, type}` objects, or pass the `ReceivedBalance` object from Step 1 query directly as `receive`. Both forms accept the same `env` block as the `"recently"` form above.
|
|
333
|
+
|
|
334
|
+
#### Common Pitfalls
|
|
335
|
+
|
|
336
|
+
- ❌ **Do NOT wrap in `{result: ...}`** — the `receive` field accepts `ReceivedObjectsOrRecently` directly, NOT `QueryReceivedResult` (`{result: [...]}`)
|
|
337
|
+
- ❌ **Do NOT call `order.receive` if no funds received yet** — query first via Step 1
|
|
338
|
+
- ✅ Only the **builder** (Order owner) can call `receive` — agents cannot withdraw funds (they can execute the call, but only builder receives)
|
|
339
|
+
- ✅ `CoinWrapper` is auto-unwrapped to the underlying token (e.g., WOW)
|
|
340
|
+
- ✅ The `receive` field is consistent with `owner_receive` on all other objects (arbitration/contact/demand/machine/permission/repository/reward/service/treasury)
|
|
341
|
+
|
|
342
|
+
#### When to Call `order.receive`
|
|
343
|
+
|
|
344
|
+
| Trigger | Why | Action |
|
|
345
|
+
|---------|-----|--------|
|
|
346
|
+
| Allocation fires (e.g., refund Allocator triggers on `return_approved`) | Order receives `CoinWrapper` with refund amount | Call `order.receive` to withdraw to builder account |
|
|
347
|
+
| Arbitration awards compensation | Order receives `CoinWrapper` with award amount | Call `order.receive` to withdraw to builder account |
|
|
348
|
+
| Multi-stage allocation (partial refund + partial deduction) | Order receives multiple `CoinWrapper` objects | Call `order.receive` with `"recently"` to receive all at once |
|
|
349
|
+
| Order closed with no allocation | No `CoinWrapper` received | Do NOT call `order.receive` (nothing to withdraw) |
|
|
350
|
+
|
|
271
351
|
---
|
|
272
352
|
|
|
273
353
|
## Phase 3: Customer Intelligence (MCP-Handled)
|
package/wowok-provider/SKILL.md
CHANGED
|
@@ -175,6 +175,42 @@ STEP 5: Post-Publish (MODIFY Service — mutable after publish)
|
|
|
175
175
|
| Guard | Reuse if logic matches | After creation |
|
|
176
176
|
| Service | — | After publish: machine, order_allocators frozen |
|
|
177
177
|
|
|
178
|
+
### Service Step-by-Step Update (Two-Phase Deployment)
|
|
179
|
+
|
|
180
|
+
Deploy a Service in two phases to handle Guard↔Service circular dependencies via LocalMark names. Both phases call `onchain_operations` (operation_type: "service"); they differ by `publish` flag and binding completeness.
|
|
181
|
+
|
|
182
|
+
**Phase 1 — CREATE (no publish)**: Build the full object graph with LocalMark name references so addresses can resolve later.
|
|
183
|
+
|
|
184
|
+
```
|
|
185
|
+
onchain_operations.service {
|
|
186
|
+
name: "<service_local_mark>",
|
|
187
|
+
type_parameter, permission,
|
|
188
|
+
machine: "<machine_local_mark>", # reference by name
|
|
189
|
+
order_allocators: [{ guard: "<guard_local_mark>", sharing: [...] }, ...],
|
|
190
|
+
arbitrations: { list: ["<arb_local_mark>", ...] },
|
|
191
|
+
buy_guard: "<buy_guard_local_mark>", # reference by name — defers resolution
|
|
192
|
+
publish: false # CRITICAL: do not publish yet
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
**Phase 2 — PUBLISH**: After all referenced objects (Machine, Guards, Allocators) are created/published, re-call to publish.
|
|
197
|
+
|
|
198
|
+
```
|
|
199
|
+
onchain_operations.service {
|
|
200
|
+
object: "<service_local_mark>", # target the existing draft by name
|
|
201
|
+
publish: true
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
> The SDK resolves all LocalMark names → on-chain addresses at publish time. Phase 1 + Phase 2 together replace the "create draft → mutate → publish" sequence in STEP 1-4 of the Service Build Lifecycle when circular dependencies exist.
|
|
206
|
+
|
|
207
|
+
**Post-publish mutability** (SDK-LOCKED vs mutable):
|
|
208
|
+
|
|
209
|
+
| Field | After Publish |
|
|
210
|
+
|-------|---------------|
|
|
211
|
+
| `buy_guard`, `sales`, `description`, `repositories` (add), `rewards` (add) | **Mutable** |
|
|
212
|
+
| `machine`, `order_allocators`, `arbitrations` | **SDK-LOCKED** (immutable — fork required to change) |
|
|
213
|
+
|
|
178
214
|
---
|
|
179
215
|
|
|
180
216
|
## Key Concepts
|
|
@@ -186,14 +222,15 @@ STEP 5: Post-Publish (MODIFY Service — mutable after publish)
|
|
|
186
222
|
```
|
|
187
223
|
Service (merchant storefront)
|
|
188
224
|
├── permission → Permission (required, mutable after publish)
|
|
189
|
-
├── machine → Machine (required, IMMUTABLE after publish)
|
|
190
|
-
├── order_allocators →
|
|
225
|
+
├── machine → Machine (required, IMMUTABLE after publish per service.move:633 assert!(!self.bPublished))
|
|
226
|
+
├── order_allocators → Allocators inline struct (optional, IMMUTABLE after publish per service.move:503; each Order creates an independent Allocation at runtime)
|
|
191
227
|
├── arbitrations → Arbitration[] (optional, mutable after publish, max 20)
|
|
192
|
-
├── compensation_fund →
|
|
193
|
-
├──
|
|
228
|
+
├── compensation_fund → Balance<T> value (optional, mutable after publish; NOT a Treasury address — Treasury is an independent object)
|
|
229
|
+
├── repositories → Repository[] (optional, mutable after publish; consensus repository refs)
|
|
230
|
+
├── sales → ServiceSale[] (optional, mutable after publish; inline product listings with name/price/stock/wip — NOT a Repository ref)
|
|
194
231
|
├── rewards → Reward[] (optional, mutable after publish)
|
|
195
232
|
├── um → Contact (optional, mutable after publish; customer service)
|
|
196
|
-
├── customer_required →
|
|
233
|
+
├── customer_required → string[] (optional, mutable after publish; personal info mark names, not a direct Personal ref)
|
|
197
234
|
└── buy_guard → Guard (optional, mutable after publish; gates order placement)
|
|
198
235
|
|
|
199
236
|
Order (per purchase, runtime-created)
|
|
@@ -201,11 +238,20 @@ Order (per purchase, runtime-created)
|
|
|
201
238
|
├── service → Service snapshot (immutable after creation)
|
|
202
239
|
├── machine → Machine (immutable after creation)
|
|
203
240
|
├── progress → Progress (immutable after binding)
|
|
204
|
-
├──
|
|
205
|
-
└── allocation →
|
|
241
|
+
├── dispute → Arb[] (optional; Arb addresses pushed on dispute per order.move:93, immutable once set — NOT an Arbitration ref)
|
|
242
|
+
└── allocation → Allocation (optional, created at runtime; triggered via Progress.forward)
|
|
206
243
|
|
|
207
244
|
Cross-object references:
|
|
208
|
-
- Guard is referenced by 9 object types
|
|
245
|
+
- Guard is referenced by 9 object types via diverse nested paths (see wowok-guard SKILL for full schema):
|
|
246
|
+
- Service.buy_guard (top-level Option<address>)
|
|
247
|
+
- Machine.forward.guard (per-node dynamic table; SDK does not expose — requires query_table)
|
|
248
|
+
- Allocation.allocators[].guard (array element — graph-builder edge fieldName: allocator_guard)
|
|
249
|
+
- Arbitration.voting_guard[].guard (array element) + Arbitration.usage_guard (top-level Option)
|
|
250
|
+
- Reward.guards[].guard (array element — graph-builder edge fieldName: guard)
|
|
251
|
+
- Repository.policies[].write_guard[].guard (deeply nested) + Repository.policies[].quote_guard
|
|
252
|
+
- Treasury.external_deposit_guard[].guard + Treasury.external_withdraw_guard[].guard (dual arrays)
|
|
253
|
+
- Demand.guards[].guard (array element — graph-builder edge fieldName: guard)
|
|
254
|
+
- Passport.info[].guard (verification snapshot, read-only)
|
|
209
255
|
- Machine is referenced by 4 object types (Service.machine, Order.machine, Progress.machine, Order snapshot)
|
|
210
256
|
- Permission is the central hub — 11 objects hold BuiltinPermissionIndex
|
|
211
257
|
```
|
package/wowok-safety/SKILL.md
CHANGED
|
@@ -87,6 +87,17 @@ Present a preview table (Operation, Object, Network, Account) with a warning des
|
|
|
87
87
|
|
|
88
88
|
Before publishing a Service or Machine: (1) Export and review — use `guard2file` to export Guard definitions, `machineNode2file` to export Machine nodes; (2) Verify logic — confirm Guards and Machine nodes match user intent; (3) Warn about immutability — once published, many fields become locked. Present a publish confirmation warning listing what becomes immutable, then ask: "This action cannot be easily undone. Proceed?"
|
|
89
89
|
|
|
90
|
+
#### 2.3.1 R-M1-11 Pre-Publish Hard Gate (Refund/Deduction Topology)
|
|
91
|
+
|
|
92
|
+
For any Machine touching deposits, refunds, escrow, or deductions (rental, freelance milestone, dispute-aware e-commerce), the AI MUST verify R-M1-11 compliance before allowing publish:
|
|
93
|
+
|
|
94
|
+
- **Hard blocker**: any node named `deposit_refunded`, `deposit_deducted`, `refunded`, or any name implying the Machine itself performs fund movement.
|
|
95
|
+
- **Required pattern**: refund/deduction flows through an **Allocator** triggered by a routing node (e.g., `return_approved`, `damage_confirmed`). The Allocator watches `progress.current == <routing_node>` and executes fund movement.
|
|
96
|
+
- **Dispute path**: `arbiter_rule` (routing) → Arbitration off-Machine — no Allocator; arbitration::dispute handles fund distribution.
|
|
97
|
+
- **Verification**: review the `machineNode2file` export and grep node names against the forbidden list. If any match, BLOCK publish and alert the user.
|
|
98
|
+
|
|
99
|
+
**Why critical**: A refund terminal node without a bound Allocator causes funds to lock permanently in the Order escrow — there is no primitive to move funds out. This was the root cause of P0-01 (Turo deployment: `deposit_refunded` node locked 0.25 WOW customer refunds). See [wowok-machine](../wowok-machine/SKILL.md) "R-M1-11 Anti-Pattern" and [wowok-scenario](../wowok-scenario/SKILL.md) "Rental Mode Template (R-M1-11 Compliant)".
|
|
100
|
+
|
|
90
101
|
---
|
|
91
102
|
|
|
92
103
|
## 3. Common Mistakes to Avoid
|
|
@@ -95,6 +106,7 @@ Before publishing a Service or Machine: (1) Export and review — use `guard2fil
|
|
|
95
106
|
|---------|---------------|------------|
|
|
96
107
|
| **Forgetting no_cache** | Cache lag in dependency chain | Set `env.no_cache: true` on all operations when building multiple objects |
|
|
97
108
|
| **Missing permission indices** | Machine forwards reference non-existent indices | Verify Permission object has required indices before creating Machine |
|
|
109
|
+
| **R-M1-11 violation** (refund terminal node) | Designing Machine with `deposit_refunded`/`deposit_deducted`/`refunded` nodes implying the Machine refunds — funds lock in escrow with no Allocator path (root cause of P0-01) | Use routing nodes (`return_approved`/`damage_confirmed`/`arbiter_rule`) + bound Allocators; grep `machineNode2file` output for forbidden names BEFORE publish |
|
|
98
110
|
|
|
99
111
|
---
|
|
100
112
|
|
package/wowok-scenario/SKILL.md
CHANGED
|
@@ -116,6 +116,8 @@ When two modes specify different Permission indexes for the same role, user deci
|
|
|
116
116
|
| `freelance` | 7 nodes (ordered→...→completed→{wonder\|no_wonder}) | 5 (buy/deliver/accept/withdraw/rating_window) | 2 (100% provider at completed / 0 at wonder-no_wonder) | Customer never accepts delivery |
|
|
117
117
|
| `rental` | 10 nodes (reserved→...→completed→{wonder\|no_wonder}) | 5 (deposit/return/inspect/damage/rating_window) | 3 (rent at completed / refund-to-order via Allocator / damage deduct) | Owner claims damage without pre-rental WIP |
|
|
118
118
|
|
|
119
|
+
> **Freelance entry-node forward (CRITICAL)**: the entry node (`prev_node: ""`, typically "Ordered") MUST have ≥1 forward (e.g. `{next_node:"Ordered", namedOperator:"", weight:1}`). Without it, Progress is permanently stuck at `current=""`. The 5-Guard default covers buy/deliver/accept/withdraw/rating_window; `penalty_guard` is OPTIONAL (add only if penalty deduction is needed — most freelance flows omit it).
|
|
120
|
+
|
|
119
121
|
> **Dispute Independence**: `refunded`/`disputed`/`arb` MUST NOT appear as Machine nodes (R-M1-11 critical). Refunds flow through Allocator (100%→OrderHolder) or Arbitration (dispute → ruling → arb_withdraw), both off-Machine. Wonder/no_wonder are post-completion reputation terminals only.
|
|
120
122
|
|
|
121
123
|
### Freelance Audit Checklist (pre-publish BLOCKERS)
|
|
@@ -144,11 +146,121 @@ When two modes specify different Permission indexes for the same role, user deci
|
|
|
144
146
|
|
|
145
147
|
### Rental Failure Playbooks
|
|
146
148
|
|
|
147
|
-
- Renter never returns: timeout forward to `damage_confirmed`, `
|
|
149
|
+
- Renter never returns: timeout forward to `damage_confirmed`, `damage_deduct` Allocator fires (deduct deposit to host)
|
|
148
150
|
- No pre-rental WIP: impossible post-publish — audit checklist blocks this at publish time
|
|
149
|
-
- Owner refuses inspect: timeout forward auto-passes `inspect_guard`, `refund_guard` fires
|
|
151
|
+
- Owner refuses inspect: timeout forward auto-passes `inspect_guard`, `refund_guard` fires on `return_approved`, deposit returns to customer
|
|
150
152
|
- Double-spend dispute: Machine topology ensures mutually exclusive forwards (first-Pair-wins), `escalate_arbiter` routes to Arbitration
|
|
151
153
|
|
|
154
|
+
### Rental Mode Template (R-M1-11 Compliant)
|
|
155
|
+
|
|
156
|
+
> **P3-03 fix**: The original Turo deployment used `deposit_refunded`/`deposit_deducted` as Machine nodes, which violates R-M1-11. This template replaces them with R-M1-11-compliant node names (`return_approved` / `damage_confirmed`). Deposit refund/deduction flows through Allocator triggered by these nodes, NOT through "refund terminal" nodes.
|
|
157
|
+
|
|
158
|
+
#### Corrected 10-Node Machine Topology
|
|
159
|
+
|
|
160
|
+
```
|
|
161
|
+
reserved → paid_deposit → in_use → returned → inspected ─┬─→ return_approved → completed
|
|
162
|
+
├─→ damage_confirmed → completed
|
|
163
|
+
└─→ arbiter_rule → completed
|
|
164
|
+
|
|
165
|
+
completed → wonder | no_wonder (rating terminals, optional)
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
**Node inventory (10 nodes)**:
|
|
169
|
+
|
|
170
|
+
| # | Node | Operator | Business meaning | Entry forward |
|
|
171
|
+
|---|------|----------|------------------|---------------|
|
|
172
|
+
| 1 | `reserved` | system | Renter selected item, pending payment | Order creation (auto) |
|
|
173
|
+
| 2 | `paid_deposit` | customer | Paid rent + deposit | `pay_deposit_and_rent` (Customer, `namedOperator:""`) |
|
|
174
|
+
| 3 | `in_use` | host | Item handed over, renter using | `pickup` (Host, `permissionIndex:1000`) |
|
|
175
|
+
| 4 | `returned` | customer | Renter returned item | `trigger_return` (Customer, `namedOperator:""`) |
|
|
176
|
+
| 5 | `inspected` | host | Host inspected condition | `inspect_item` (Host, `permissionIndex:1000`) |
|
|
177
|
+
| 6 | `return_approved` | host | No damage — refund deposit | `approve_return` (Host, `permissionIndex:1000`) — **path 1** |
|
|
178
|
+
| 7 | `damage_confirmed` | host | Damage confirmed — deduct deposit | `claim_damage` (Host, `permissionIndex:1000`) — **path 2** |
|
|
179
|
+
| 8 | `arbiter_rule` | system | Escalated to arbitration | `escalate_arbiter` (Customer, `namedOperator:""`) — **path 3** |
|
|
180
|
+
| 9 | `completed` | host | Trip finalized | `finalize` (Host, `permissionIndex:1000`) |
|
|
181
|
+
| 10 | `wonder` / `no_wonder` | customer | Rating terminal (one of two) | `rate_good` / `rate_bad` (Customer, `namedOperator:""`) |
|
|
182
|
+
|
|
183
|
+
**R-M1-11 compliance**: NO `deposit_refunded`, `deposit_deducted`, or `refunded` nodes. Deposit refund/deduction flows through Allocator triggered by `return_approved` / `damage_confirmed` nodes. `arbiter_rule` is allowed (it routes to Arbitration, not a refund terminal).
|
|
184
|
+
|
|
185
|
+
#### 3 Mutually Exclusive Forwards from `inspected`
|
|
186
|
+
|
|
187
|
+
| Forward | Next node | Operator | Trigger condition | Allocator fired |
|
|
188
|
+
|---------|-----------|----------|-------------------|----------------|
|
|
189
|
+
| `approve_return` | `return_approved` | Host (perm 1000) | No damage WIP diff | Allocator 1 (refund to customer) |
|
|
190
|
+
| `claim_damage` | `damage_confirmed` | Host (perm 1000) | Damage WIP diff > 0 | Allocator 2 (deduct to host) |
|
|
191
|
+
| `escalate_arbiter` | `arbiter_rule` | Customer (`namedOperator:""`) | Dispute | Arbitration (off-Machine) |
|
|
192
|
+
|
|
193
|
+
Move contract guarantees first-Forward-wins: Progress can only advance from `inspected` to ONE of the three next nodes.
|
|
194
|
+
|
|
195
|
+
#### 3 Allocator Templates (Amount mode recommended)
|
|
196
|
+
|
|
197
|
+
> **Why Amount mode**: Rate mode requires sum == 10000 (hard constraint). Amount mode is clearer for fixed rent/deposit amounts. See [Allocation Mode Documentation](../../wiki/market/行业/租赁-Turo/06-Allocation模式说明-待审核.md) for details.
|
|
198
|
+
|
|
199
|
+
**Allocator 0 — Rent payment (triggers on `completed`)**:
|
|
200
|
+
```json
|
|
201
|
+
{
|
|
202
|
+
"guard": "rent_completed_guard",
|
|
203
|
+
"sharing": [
|
|
204
|
+
{ "who": { "Entity": { "name_or_address": "<host>" } }, "sharing": "750000000", "mode": "Amount" }
|
|
205
|
+
]
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
- Fires when `progress.current == "completed"` (via `rent_completed_guard`)
|
|
209
|
+
- Pays 0.75 WOW rent to host
|
|
210
|
+
|
|
211
|
+
**Allocator 1 — Deposit refund (triggers on `return_approved`)**:
|
|
212
|
+
```json
|
|
213
|
+
{
|
|
214
|
+
"guard": "refund_guard",
|
|
215
|
+
"sharing": [
|
|
216
|
+
{ "who": { "GuardIdentifier": 0 }, "sharing": "250000000", "mode": "Amount" }
|
|
217
|
+
]
|
|
218
|
+
}
|
|
219
|
+
```
|
|
220
|
+
- Fires when `progress.current == "return_approved"` (via `refund_guard`)
|
|
221
|
+
- Refunds 0.25 WOW deposit to customer (Order owner, via `GuardIdentifier:0`)
|
|
222
|
+
|
|
223
|
+
**Allocator 2 — Damage deduction (triggers on `damage_confirmed`)**:
|
|
224
|
+
```json
|
|
225
|
+
{
|
|
226
|
+
"guard": "damage_guard",
|
|
227
|
+
"sharing": [
|
|
228
|
+
{ "who": { "Entity": { "name_or_address": "<host>" } }, "sharing": "250000000", "mode": "Amount" }
|
|
229
|
+
]
|
|
230
|
+
}
|
|
231
|
+
```
|
|
232
|
+
- Fires when `progress.current == "damage_confirmed"` (via `damage_guard`)
|
|
233
|
+
- Deducts 0.25 WOW deposit to host
|
|
234
|
+
|
|
235
|
+
#### 5 Guard Templates
|
|
236
|
+
|
|
237
|
+
| Guard | Type | Trigger condition | Purpose |
|
|
238
|
+
|-------|------|-------------------|---------|
|
|
239
|
+
| `deposit_guard` | Permission | `progress.current == "paid_deposit"` + signer is Customer | Verify customer paid rent + deposit |
|
|
240
|
+
| `return_guard` | Permission | `progress.current == "returned"` + signer is Host | Verify item returned |
|
|
241
|
+
| `inspect_guard` | Permission | `progress.current == "inspected"` + signer is Host | Verify inspection done |
|
|
242
|
+
| `damage_guard` | WIP | Pre + post WIP hash diff > 0 | Verify damage evidence |
|
|
243
|
+
| `rating_window_guard` | Permission | `progress.current == "completed"` + within N days | Rating window timer |
|
|
244
|
+
|
|
245
|
+
#### Permission Index Design
|
|
246
|
+
|
|
247
|
+
| Role | Permission Index | Operations |
|
|
248
|
+
|------|------------------|------------|
|
|
249
|
+
| Host | 1000 | pickup / inspect / approve_return / claim_damage / finalize |
|
|
250
|
+
| Arbiter | 1500 | arbitration ruling (via `voting_guard`, NOT Permission index) |
|
|
251
|
+
| Customer | (no index) | pay_deposit / trigger_return / escalate_arbiter (via `namedOperator:""`) |
|
|
252
|
+
|
|
253
|
+
**Customer authorization**: uses `namedOperator: ""` (empty string = OrderHolder). Set automatically by `service::buy` when Order is created. No Permission index needed.
|
|
254
|
+
|
|
255
|
+
#### Migration Guide (from R-M1-11 violating deployment)
|
|
256
|
+
|
|
257
|
+
If you have an existing rental Machine with `deposit_refunded`/`deposit_deducted` nodes:
|
|
258
|
+
|
|
259
|
+
1. **Clone the Machine** (nodes are immutable after publish)
|
|
260
|
+
2. **Rename nodes**: `deposit_refunded` → `return_approved`; `deposit_deducted` → (remove, merge into `damage_confirmed`)
|
|
261
|
+
3. **Rebind Allocators**: Allocator 1 trigger node `deposit_refunded` → `return_approved`; Allocator 2 trigger node `deposit_deducted` → `damage_confirmed`
|
|
262
|
+
4. **Publish new Machine** and rebind to Service (requires Service to be in draft state; if already published, use `service.machine_rebind` with setting_lock_duration wait)
|
|
263
|
+
|
|
152
264
|
---
|
|
153
265
|
|
|
154
266
|
## Education Mode (Phase 2 — Outline)
|
|
@@ -177,6 +289,35 @@ When two modes specify different Permission indexes for the same role, user deci
|
|
|
177
289
|
- **Key trait**: multi-tier Allocation is WoWok's unique advantage over traditional travel platforms
|
|
178
290
|
- **GTM angle**: targets "paid in full then service shrinks" pain point
|
|
179
291
|
|
|
292
|
+
### Travel Mode Preset (Reference)
|
|
293
|
+
|
|
294
|
+
> Concise preset for quick deployment. For full template, see Phase 2 expansion.
|
|
295
|
+
|
|
296
|
+
**10-Node Machine Workflow**:
|
|
297
|
+
```
|
|
298
|
+
inquiry → booking → payment → confirmation → preparation → departure → in-progress → completion → review → refund
|
|
299
|
+
```
|
|
300
|
+
- `refund` is a routing node (Allocator-triggered), NOT a refund terminal — R-M1-11 compliant
|
|
301
|
+
- Rating terminals (`wonder` / `no_wonder`) optional after `review`
|
|
302
|
+
|
|
303
|
+
**4 Guards**:
|
|
304
|
+
|
|
305
|
+
| Guard | Type | Trigger | Purpose |
|
|
306
|
+
|-------|------|---------|---------|
|
|
307
|
+
| `buy_guard` | Permission | Order creation | Validate customer eligibility + segment WIP |
|
|
308
|
+
| `withdraw_guard` | Permission | `progress.current == "completion"` | Verify trip completed before host payout |
|
|
309
|
+
| `refund_guard` | Permission | `progress.current == "refund"` | Verify trip interruption / agency approval |
|
|
310
|
+
| `dispute_guard` | Permission | Arb escalation | Route to Arbitration (off-Machine) |
|
|
311
|
+
|
|
312
|
+
**2 Allocators** (Rate mode, sum = 10000 each):
|
|
313
|
+
|
|
314
|
+
| Allocator | Trigger Node | sharing[0] | sharing[1] |
|
|
315
|
+
|-----------|--------------|------------|------------|
|
|
316
|
+
| Withdraw | `completion` | Agency 80% (Entity) | Hotel/Guide/Driver 20% (Entity) |
|
|
317
|
+
| Refund | `refund` | Customer 100% (GuardIdentifier:0 = Order owner) | — |
|
|
318
|
+
|
|
319
|
+
**Required flags**: `require_compensation_fund = true` (protects customer prepayment if agency defaults)
|
|
320
|
+
|
|
180
321
|
---
|
|
181
322
|
|
|
182
323
|
## Subscription Mode (Phase 3 — Outline)
|
package/wowok-tools/SKILL.md
CHANGED
|
@@ -99,7 +99,8 @@ When a Guard queries a related object (e.g., Progress from an Order), `convert_w
|
|
|
99
99
|
|--------|------------|----------|
|
|
100
100
|
| Guard | After creation | Create new, update all refs |
|
|
101
101
|
| Machine (nodes) | After publish | Create new Machine, rebind Service |
|
|
102
|
-
| Service `machine`/`order_allocators` | After publish | Create new Service |
|
|
102
|
+
| Service `machine`/`order_allocators`/`arbitrations` | After publish (SDK-locked) | Create new Service |
|
|
103
|
+
| Service `buy_guard` | **NOT locked** — mutable after publish | Can modify directly |
|
|
103
104
|
| Passport | After generation | Regenerate with `gen_passport` |
|
|
104
105
|
| Payment | After transfer | Irreversible — no protocol refund |
|
|
105
106
|
|
|
@@ -150,34 +151,47 @@ Step 3: MODIFY reward { object: "reward_v1", guard_add: [...] } // bind guard
|
|
|
150
151
|
|
|
151
152
|
| `operation_type` | Key Constraints (not in schema) |
|
|
152
153
|
|-----------------|----------------------------------|
|
|
153
|
-
| `service` | `machine` must be **published**. Allocators: array order = priority (first-Guard-wins). Publish locks `machine`/`order_allocators
|
|
154
|
+
| `service` | `machine` must be **published**. Allocators: array order = priority (first-Guard-wins). **Publish locks 3 fields (SDK-level)**: `machine`/`order_allocators`/`arbitrations` — MUST be set BEFORE `publish:true`. **buy_guard is MUTABLE after publish** (no SDK/Move lock). `setting_locked_time_add` is extendable. TIME-LOCKED (need pause + setting_lock_duration): `rewards` remove/clear. REMAIN MUTABLE: `sales`/`discount`/`description`/`location`/`pause`/`repositories`/`rewards`(add)/`compensation_fund_add`/`customer_required`/`um`/`buy_guard`. To change locked fields after publish, clone a new Service. |
|
|
154
155
|
| `machine` | Nodes immutable after publish. Forward needs ≥1 of `namedOperator`/`permissionIndex` (both empty = SDK error). `""` = entry node. → [wowok-machine](../wowok-machine/SKILL.md) |
|
|
155
|
-
| `progress` |
|
|
156
|
+
| `progress` | CANONICAL form: `operate: {operation: {next_node_name, forward}, op: "next"\|"hold"\|"unhold"\|"adminUnhold", message?}`. LEGACY `hold: boolean` is auto-converted (`hold:true`→`op:'hold'`, `hold:false`→`op:'next'`). SDK auto-fetches Machine when resolving `object_address`. **⚠ Routing rule**: Use `progress.operate` ONLY for forwards with non-empty `namedOperator` or `permissionIndex`-only. For forwards with `namedOperator=""` (OrderHolder), use `order.progress` instead — direct `progress::next` aborts with Permission denied (code 5). |
|
|
156
157
|
| `arbitration` | MAX 20 propositions, 520 voters. Verdict (2→3) **irreversible** — only customer can `order.arb_objection`. Non-Finished withdrawal = 30-day wait. → [wowok-arbitrator](../wowok-arbitrator/SKILL.md) |
|
|
157
158
|
| `guard` | `root.type:"node"` (inline) or `"file"` (JSON/MD). MAX 4 `rely`. `rep:false` Guards excluded from others' `rely`. System addresses `0xaab`/`0xaaa` need table entries. → [wowok-guard](../wowok-guard/SKILL.md) |
|
|
158
159
|
| `gen_passport` | MAX 20 Guards/call (AND-ed). Omit `info` to auto-fetch. Passport = frozen immutable credential. |
|
|
159
160
|
| `order` | Agents can operate but **cannot withdraw** — only builder. `order.progress`+Guard requires Passport. **⚠ Routing rule**: `order.progress` works ONLY for forwards with `namedOperator=""` (OrderHolder) — uses `order.has_op_permission`. For non-empty `namedOperator` or `permissionIndex`-only forwards, use `progress.operate` on the Progress object directly. Arb via `order.arb_confirm`/`arb_objection` (not `arbitration` directly). `arb_claim_compensation` once-only. → [wowok-order](../wowok-order/SKILL.md) |
|
|
160
|
-
| `payment` | `type_parameter` required
|
|
161
|
+
| `payment` | TWO modes: (1) CREATE: `type_parameter` required, `revenue[]` + `info`. **Irreversible** — no refund. (2) RECEIVE: `{object: '<coinwrapper_id_or_name>', receive: true, type_parameter: '0x2::wow::WOW'}` — unwraps a CoinWrapper (created by Allocation's `alloc_by_guard`) to the caller's wallet via `payment::unwrap_to_myself`. **CoinWrapper DOES arrive** in recipient's wallet via `transfer::public_transfer` (as an owned object), but it is NOT spendable coins — recipient must call `payment receive` to unwrap it into actual coins. Find received CoinWrappers via `query_toolkit` with `query_type='onchain_received'`. |
|
|
161
162
|
| `personal` | **Permanently public** — warn users before writing sensitive data. |
|
|
162
163
|
| `demand` | Guard-gated: `guards` filter presenters. Separate from Service. |
|
|
163
164
|
| `treasury` | Guardable deposits/withdrawals. Each entry creates Payment record for audit. |
|
|
164
165
|
| `repository` | Composite key: `name + entity`. Guard validates writer + content. |
|
|
165
166
|
| `reward` | `guard_add`: `Fixed` (equal) or `GuardU64Identifier` (dynamic). `guard_expiration_time` freezes Guard list; `null` removes. |
|
|
166
|
-
| `allocation` |
|
|
167
|
+
| `allocation` | **Manual trigger** — `alloc_by_guard` does NOT auto-execute on Progress advance. After advancing Progress, call `allocation` with `{object: '<alloc_name>', alloc_by_guard: '<guard_name>'}` to release funds. Order: Amount → Rate → Surplus, first-Guard-wins per mode. Each trigger releases currently-available balance based on Guard validation. Also: `received_coins` unwraps CoinWrappers into the Allocation's pending balance for re-allocation. |
|
|
167
168
|
| `contact` | Bridge: Service `um` ↔ Messenger `ims[]`. IM mutations need permission index 453; no events (poll `ims[]`). |
|
|
168
169
|
| `permission` | 0–999 reserved; custom ≥1000. SDK rejects <1000. Reusable across objects. |
|
|
169
170
|
| `proof` | Immutable (freeze_object). `proof_type=1` reserved for WTS; >100 for custom. Large data → Repository + `about_address`, not inline. |
|
|
170
171
|
| `gen_proof` | Convenience wrapper: creates Proof without `namedNew`. Same immutability rules. Use `proof` with `namedNew` when naming is needed. |
|
|
171
172
|
|
|
173
|
+
### Customer Operation Routing (decision tree)
|
|
174
|
+
|
|
175
|
+
```
|
|
176
|
+
forward.namedOperator?
|
|
177
|
+
├─ "" (empty = OrderHolder) → order.progress (NOT progress.operate)
|
|
178
|
+
│ └─ progress::next aborts: "Permission denied" (code 5)
|
|
179
|
+
│ └─ Needs Passport if forward has guard
|
|
180
|
+
└─ "<non-empty>" or permissionIndex-only → progress.operate
|
|
181
|
+
└─ Provider or named operator executes
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
> **Invariant**: `namedOperator === ""` ⟹ MUST use `order.progress`. No exceptions.
|
|
185
|
+
|
|
172
186
|
**WIP hash anti-bait**: Capture `sale.wip_hash` when browsing; pass in `buy.items[].wip_hash`. Two-layer: SDK verifies file hash off-chain, Move asserts on-chain. Merchant swap = order fails.
|
|
173
187
|
|
|
174
188
|
### Other Tools (compact)
|
|
175
189
|
|
|
176
190
|
| Tool | Key Constraints |
|
|
177
191
|
|------|----------------|
|
|
178
|
-
| `query_toolkit` | `token_list` cached (first query populates). `account_balance`: `balance=true` for totals, `coin={cursor,limit}` for paginated. `onchain_objects` batches 50/req. `local_names` resolves accounts + marks. |
|
|
192
|
+
| `query_toolkit` | `token_list` cached (first query populates). `account_balance`: `balance=true` for totals, `coin={cursor,limit}` for paginated. `onchain_objects` batches 50/req. `local_names` resolves accounts + marks. **To list all local accounts**: use `query_type='account_list'` (account_operation itself has no list action). **To find received CoinWrappers** (for payment receive): use `query_type='onchain_received'`. |
|
|
179
193
|
| `onchain_table_data` | 12 types. Global (no `parent`): `entity_registrar`, `entity_linker`. `onchain_table_item_generic` = universal fallback. |
|
|
180
|
-
| `account_operation` | `faucet` testnet/localnet only. Mainnet funding: `transfer` from existing account (1 WOW = 10^9 base units). `gen` with `messenger: true` enables Messenger. **Naming convention**: `<role>-<number>` (e.g. `shop-001`, `user-001`, `arb-001`) for easy filtering. `gen.replaceExistName:true` is DISCOURAGED — suspends old account; FORBIDDEN on default account (name=''). Private keys never leave device. |
|
|
194
|
+
| `account_operation` | `faucet` testnet/localnet only. Mainnet funding: `transfer` from existing account (1 WOW = 10^9 base units). `gen` with `messenger: true` enables Messenger. **Naming convention**: `<role>-<number>` (e.g. `shop-001`, `user-001`, `arb-001`) for easy filtering. `gen.replaceExistName:true` is DISCOURAGED — suspends old account; FORBIDDEN on default account (name=''). Private keys never leave device. **No `list` action** — to enumerate all local accounts, use `query_toolkit` with `query_type='account_list'`. |
|
|
181
195
|
| `local_mark_operation` | Max 50 tags/entry (64 chars). `replaceExistName:true` steals names — prefer `_v1`/`_v2`. |
|
|
182
196
|
| `local_info_operation` | Max 50 contents/entry, 300 chars each. |
|
|
183
197
|
| `messenger_operation` | Stranger: 1 msg before reply (~480 chars). Guard block → rejection includes guard list; sender needs Passport. WTS: `generate` needs continuous sequences. → [wowok-messenger](../wowok-messenger/SKILL.md) |
|
|
@@ -186,7 +200,43 @@ Step 3: MODIFY reward { object: "reward_v1", guard_add: [...] } // bind guard
|
|
|
186
200
|
| `machineNode2file` | Read-only; exports complete topology. |
|
|
187
201
|
| `onchain_events` | 6 event types; cursor `{eventSeq, txDigest}`. |
|
|
188
202
|
| `wowok_buildin_info` | 5 info types. Guard instructions filter by `name`/`return_type`/`param_count`. **Never use Value type 19**. |
|
|
189
|
-
| `schema_query` | `list` returns empty if schemas not generated → `npm run generate:schemas`. |
|
|
203
|
+
| `schema_query` | `list` returns empty if schemas not generated → `npm run generate:schemas`. Actions: `list`, `get` (full schema), `get_field` (field-path query e.g. `field_path='data.node'`), `get_output` (output schema), `search`, `list_operations`, `get_guard_templates` (Guard creation templates + best practices + common errors). Use `output_file='.trae/tmp/schema.json'` to write large schemas to workspace temp files (accessible via Read tool). |
|
|
204
|
+
| `project_operation` | Sub-tool for project lifecycle: `create_project`, `add_object`, `build_graph`, `evaluate_project`, `pre_evaluate_check`, `verify_deployment`, `create_version`, `get_project_detail`. See §"Project Operation Extended Actions" below for the two pre/post-publish verification actions. |
|
|
205
|
+
|
|
206
|
+
### Project Operation Extended Actions
|
|
207
|
+
|
|
208
|
+
Two `project_operation` actions act as **pre-flight and post-flight verification** around `evaluate_project` / deployment. They never mutate on-chain state — they only query and report.
|
|
209
|
+
|
|
210
|
+
#### `pre_evaluate_check` (Pre-evaluation Readiness)
|
|
211
|
+
|
|
212
|
+
**Purpose**: Verify input data completeness BEFORE calling `evaluate_project`, so callers can fix missing/stale data without paying the cost of a full evaluation.
|
|
213
|
+
|
|
214
|
+
**Call pattern**: `wowok({ tool: "project_operation", data: { action: "pre_evaluate_check", project_id: "..." } })`
|
|
215
|
+
|
|
216
|
+
**Returns**:
|
|
217
|
+
- `ready: boolean` — true if `evaluate_project` will produce meaningful results
|
|
218
|
+
- `missing[]` — hard blockers (e.g., PE-01 empty project, PE-06 producer without Service)
|
|
219
|
+
- `warnings[]` — soft issues (PE-02 no graph edges, PE-03 uncached objects, PE-04 stale cache >24h, PE-05 dangling edges, PE-07 all drafts)
|
|
220
|
+
- `counts` — quick metrics: `objects`, `edges`, `uncached`, `stale`, `dangling_edges`, `drafts`
|
|
221
|
+
- `summary` — one-line human-readable status
|
|
222
|
+
|
|
223
|
+
**When to use**: Always call BEFORE `evaluate_project`. If `ready=false`, fix the listed `missing` issues first. If `warnings` exist, decide whether to refresh data (`refresh_objects`, `build_graph`) or proceed with caveats.
|
|
224
|
+
|
|
225
|
+
#### `verify_deployment` (Post-deployment Drift Detection)
|
|
226
|
+
|
|
227
|
+
**Purpose**: After deploying objects on-chain, verify the on-chain state matches what SQLite expects. Catches drift caused by manual edits, version bumps, or third-party modifications after initial deployment.
|
|
228
|
+
|
|
229
|
+
**Call pattern**: `wowok({ tool: "project_operation", data: { action: "verify_deployment", project_id: "..." } })`
|
|
230
|
+
|
|
231
|
+
**Returns per-object verification status**:
|
|
232
|
+
- `matched` — on-chain state matches SQLite
|
|
233
|
+
- `mismatched` — on-chain state differs (fields listed in `differences`)
|
|
234
|
+
- `missing` — object no longer exists on-chain
|
|
235
|
+
- `unreachable` — query failed (network error or rate limit)
|
|
236
|
+
|
|
237
|
+
**Also checks critical bindings for Service objects**: `machine`, `permission`, `buy_guard`, `order_allocators` are all set to non-null on-chain if they were recorded as bound in SQLite.
|
|
238
|
+
|
|
239
|
+
**When to use**: After every publish operation (Service publish, Machine publish). Periodically for production monitoring. When debugging "evaluation says bound but on-chain says unbound" discrepancies.
|
|
190
240
|
|
|
191
241
|
---
|
|
192
242
|
|
|
@@ -209,20 +259,20 @@ Step 3: MODIFY reward { object: "reward_v1", guard_add: [...] } // bind guard
|
|
|
209
259
|
|
|
210
260
|
| Aspect | Treasury | Allocation |
|
|
211
261
|
|--------|----------|------------|
|
|
212
|
-
| Purpose | Team fund management (deposit/withdraw with audit trail) | Order fund distribution (
|
|
213
|
-
| Trigger | Manual deposit/withdraw (Guard-gated) |
|
|
262
|
+
| Purpose | Team fund management (deposit/withdraw with audit trail) | Order fund distribution (manual `alloc_by_guard` trigger after Progress advance) |
|
|
263
|
+
| Trigger | Manual deposit/withdraw (Guard-gated) | Manual `alloc_by_guard` after Progress advance (NOT automatic) |
|
|
214
264
|
| Guard | External guard on withdrawals | Allocation guard on distribution rules |
|
|
215
265
|
| Use when | Holding pooled funds, compensation funds, team wallets | Splitting order payments among recipients |
|
|
216
266
|
|
|
217
|
-
Compensation fund
|
|
267
|
+
**Compensation fund ≠ Treasury**: `Service.compensation_fund` is `Balance<T>` stored inline on the Service object (per service.move:179), NOT a Treasury address. Treasury is an independent object. Funds are added via `compensation_fund_add` (joins Coin<T> into the balance) and withdrawn via `compensation_fund_receive` after pause + lock duration. Each Treasury entry creates a Payment record for audit; withdrawal requires Guard verification.
|
|
218
268
|
|
|
219
269
|
### Reward (Incentive Pools)
|
|
220
270
|
|
|
221
|
-
Guard-gated claim pools: `
|
|
271
|
+
Guard-gated claim pools: each entry in `Reward.guards[].guard` (array — see wowok-guard SKILL) verifies eligibility before payout. `guard_add` modes: `Fixed` (equal split among claimants) or `GuardU64Identifier` (dynamic amount from Guard table index). `guard_expiration_time` freezes the Guard list (set `null` to remove freeze). Use cases: customer loyalty rewards, referral bonuses, airdrop campaigns, attendance rewards. Query claim history via `query_toolkit` → `onchain_table_item_reward_record`.
|
|
222
272
|
|
|
223
273
|
### Demand (Customer-Posted Requests)
|
|
224
274
|
|
|
225
|
-
Demand is the **inverse** of Service: customer posts a request + optional reward pool, providers submit offers. Guard-gated: `guards`
|
|
275
|
+
Demand is the **inverse** of Service: customer posts a request + optional reward pool, providers submit offers. Guard-gated: each entry in `Demand.guards[].guard` (array — see wowok-guard SKILL) filters which providers can present. The `service_identifier` field on each ServiceGuard differentiates filtering roles (e.g., recommend vs. eligibility). Separate `operation_type: "demand"` — NOT `service`. Use when: customer needs competitive bids (custom work, bulk procurement, reverse-auction marketplace). Pair with Reward to incentivize providers.
|
|
226
276
|
|
|
227
277
|
### Repository (On-Chain Database)
|
|
228
278
|
|
|
@@ -276,3 +326,64 @@ Discover? → tool: "schema_query" / "wowok_buildin_info" / "onchain_events"
|
|
|
276
326
|
| Arbitration called directly | Customer path: `order.arb_confirm` / `order.arb_objection`. Order is the interface |
|
|
277
327
|
|
|
278
328
|
---
|
|
329
|
+
|
|
330
|
+
## Cross-Network Name Management (testnet → mainnet)
|
|
331
|
+
|
|
332
|
+
> Object addresses differ between testnet and mainnet. WoWok uses **local names** to bridge this gap — the same name resolves to different addresses on different networks.
|
|
333
|
+
|
|
334
|
+
### How Name Resolution Works
|
|
335
|
+
|
|
336
|
+
- **Local names** are stored per-network in the local SQLite database (`LocalMark`).
|
|
337
|
+
- When you create an object with `namedNew.name = "my_service"`, the name→address mapping is stored for the **current network** (specified in `env.network`).
|
|
338
|
+
- When you reference `"my_service"` in a subsequent call, the SDK resolves it to the address **for the current network**.
|
|
339
|
+
- Names are **NOT shared across networks** — testnet and mainnet have separate name registries.
|
|
340
|
+
|
|
341
|
+
### Testnet → Mainnet Migration Workflow
|
|
342
|
+
|
|
343
|
+
1. **Develop and test on testnet**: Create all objects with `replaceExistName: true` and consistent names (e.g., `myshop_permission`, `myshop_machine`, `myshop_service`).
|
|
344
|
+
2. **Record the JSON call sequence**: Save all `onchain_operations` calls (with their `data` and `env` fields) that worked on testnet.
|
|
345
|
+
3. **Switch `env.network` to `mainnet`**: Change `env.network` from `"testnet"` to `"mainnet"` in all calls. Also switch `env.account` to a mainnet-funded account.
|
|
346
|
+
4. **Re-run the same call sequence on mainnet**: With `replaceExistName: true` on all object creations, the same names will be re-registered to new mainnet addresses. All cross-references (e.g., `permission: "myshop_permission"` in Machine) will resolve to the new mainnet addresses automatically.
|
|
347
|
+
5. **Fund the mainnet account**: Use `account_operation` with `transfer` from a funded account (1 WOW = 10^9 base units).
|
|
348
|
+
|
|
349
|
+
### Key Rules
|
|
350
|
+
|
|
351
|
+
| Rule | Detail |
|
|
352
|
+
|------|--------|
|
|
353
|
+
| `replaceExistName: true` | Re-creates the name→address mapping for the current network. Use on all object creations when re-deploying. |
|
|
354
|
+
| Name consistency | Use the SAME names across testnet and mainnet. The SDK resolves names per-network, so `"my_service"` on testnet ≠ `"my_service"` on mainnet (different addresses). |
|
|
355
|
+
| `env.network` | The ONLY field that changes between testnet and mainnet runs. All `data` fields (names, references, configs) stay the same. |
|
|
356
|
+
| Object references | Always use names (strings), NOT hardcoded addresses (0x...). Hardcoded addresses break across networks. |
|
|
357
|
+
| Account funding | `faucet` works only on testnet/localnet. Mainnet requires `transfer` from an existing funded account. |
|
|
358
|
+
|
|
359
|
+
### Example: Testnet → Mainnet
|
|
360
|
+
|
|
361
|
+
```json
|
|
362
|
+
// testnet call (works)
|
|
363
|
+
{
|
|
364
|
+
"tool": "onchain_operations",
|
|
365
|
+
"data": {
|
|
366
|
+
"operation_type": "service",
|
|
367
|
+
"data": { "object": { "name": "my_service", "permission": "my_permission", ... } },
|
|
368
|
+
"env": { "account": "test_account", "network": "testnet", "confirmed": true }
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// mainnet call (same data, only env changes)
|
|
373
|
+
{
|
|
374
|
+
"tool": "onchain_operations",
|
|
375
|
+
"data": {
|
|
376
|
+
"operation_type": "service",
|
|
377
|
+
"data": { "object": { "name": "my_service", "permission": "my_permission", ... } },
|
|
378
|
+
"env": { "account": "main_account", "network": "mainnet", "confirmed": true }
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
### Common Mistakes
|
|
384
|
+
|
|
385
|
+
- **Hardcoding addresses**: `"machine": "0xabc123..."` breaks on mainnet. Use `"machine": "my_machine"` instead.
|
|
386
|
+
- **Forgetting `replaceExistName`**: Without it, re-deploying to a new network creates a name conflict if the name already exists locally.
|
|
387
|
+
- **Mixing networks in one session**: All calls in a deployment sequence should use the same `env.network`. Mixing testnet and mainnet calls causes name resolution failures.
|
|
388
|
+
|
|
389
|
+
---
|