@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wowok/skills",
3
- "version": "1.3.1",
3
+ "version": "1.3.2",
4
4
  "description": "WoWok AI Skills for Claude and other AI assistants - Helping AI use WoWok MCP tools correctly",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -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
  ---
@@ -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).
@@ -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`
@@ -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)" |
@@ -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)
@@ -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 → Allocation[] (optional, mutable after publish)
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 → Treasury (optional, mutable after publish)
193
- ├── sales → Repository (optional, mutable after publish; products with WIP files)
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 → Personal (optional, mutable after publish; customer data schema)
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
- ├── arbitrationArbitration (optional, immutable once set)
205
- └── allocation → Fund distribution engine (triggered via Progress.forward)
241
+ ├── disputeArb[] (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 (Service.buy_guard, Machine.forward.guard, Allocation.allocation_guard, Arbitration.voting_guard, Reward.claim_guard, Repository.write_guard, Treasury.external_guard, Demand.recommend_guard, Passport.guard)
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
  ```
@@ -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
 
@@ -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`, `deposit_deduct` Allocator fires
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, deposit returns
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)
@@ -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`; `sales`/`discount`/`description` stay mutable. |
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` | Two-phase: `hold:true` (lock) `hold:false` (submit). `adminUnhold:true` force-releases. 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
+ | `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. **Irreversible** — no refund. |
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` | Auto-executes on Progress advance. Order: Amount → Rate → Surplus, first-Guard-wins per mode. |
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 (auto-trigger on Progress advance) |
213
- | Trigger | Manual deposit/withdraw (Guard-gated) | Automatic when Progress reaches configured node |
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 = Treasury bound to Service. Each Treasury entry creates a Payment record for audit. Withdrawal requires Guard verification.
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: `claim_guard` 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`.
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` filter which providers can present. `recommend_guard` filters presenter submissions. 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.
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
+ ---