@wowok/agent-mcp 3.2.1 → 3.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (176) hide show
  1. package/dist/chain-semantics/field-capability.d.ts +11 -0
  2. package/dist/chain-semantics/field-capability.js +1 -0
  3. package/dist/chain-semantics/freeze-map.d.ts +11 -0
  4. package/dist/chain-semantics/freeze-map.js +1 -0
  5. package/dist/chain-semantics/fund-flow.d.ts +10 -0
  6. package/dist/chain-semantics/fund-flow.js +1 -0
  7. package/dist/chain-semantics/guard-verbs.d.ts +22 -0
  8. package/dist/chain-semantics/guard-verbs.js +1 -0
  9. package/dist/chain-semantics/index.d.ts +10 -0
  10. package/dist/chain-semantics/index.js +1 -0
  11. package/dist/chain-semantics/object-graph.d.ts +18 -0
  12. package/dist/chain-semantics/object-graph.js +1 -0
  13. package/dist/communication/wts-attestation.d.ts +34 -0
  14. package/dist/communication/wts-attestation.js +1 -0
  15. package/dist/composition/audit.d.ts +10 -0
  16. package/dist/composition/audit.js +1 -0
  17. package/dist/composition/compile.d.ts +38 -0
  18. package/dist/composition/compile.js +1 -0
  19. package/dist/composition/materialize.d.ts +64 -0
  20. package/dist/composition/materialize.js +1 -0
  21. package/dist/composition/plan.d.ts +92 -0
  22. package/dist/composition/plan.js +1 -0
  23. package/dist/composition/reflux.d.ts +28 -0
  24. package/dist/composition/reflux.js +1 -0
  25. package/dist/config/judgment.d.ts +25 -0
  26. package/dist/config/rule-engine.d.ts +10 -0
  27. package/dist/config/rule-engine.js +1 -1
  28. package/dist/evaluation/builtins/graph-service.spec.js +1 -1
  29. package/dist/evaluation/engine.js +1 -1
  30. package/dist/experience/realtime-feedback.js +1 -1
  31. package/dist/extensions/business-modules.js +1 -1
  32. package/dist/extensions/industry-pack.js +1 -1
  33. package/dist/extensions/modes.js +1 -1
  34. package/dist/graph/onchain/expand.js +1 -1
  35. package/dist/graph/onchain/exposure.js +1 -1
  36. package/dist/graph/onchain/facts.js +1 -1
  37. package/dist/keeper/detectors.d.ts +7 -0
  38. package/dist/keeper/detectors.js +1 -1
  39. package/dist/keeper/types.d.ts +1 -1
  40. package/dist/keeper/types.js +1 -1
  41. package/dist/knowledge/allocation-ledger.js +1 -1
  42. package/dist/knowledge/allocation-puzzle.js +1 -1
  43. package/dist/knowledge/allocation-risk.d.ts +2 -1
  44. package/dist/knowledge/allocation-risk.js +1 -1
  45. package/dist/knowledge/allocation-templates.js +1 -1
  46. package/dist/knowledge/arb-fund-facts.d.ts +31 -0
  47. package/dist/knowledge/arb-fund-facts.js +1 -0
  48. package/dist/knowledge/audit-rules.js +1 -1
  49. package/dist/knowledge/baselines/rules/allocation.json +75 -16
  50. package/dist/knowledge/baselines/rules/guard.json +27 -8
  51. package/dist/knowledge/baselines/rules/plan.json +161 -0
  52. package/dist/knowledge/baselines/semantic-facts.d.ts +12 -0
  53. package/dist/knowledge/baselines/semantic-facts.js +1 -0
  54. package/dist/knowledge/builtin-templates.d.ts +3 -1
  55. package/dist/knowledge/builtin-templates.js +1 -1
  56. package/dist/knowledge/entity-recipient-guard.d.ts +21 -0
  57. package/dist/knowledge/entity-recipient-guard.js +1 -0
  58. package/dist/knowledge/getting-started.js +1 -1
  59. package/dist/knowledge/guard-design-patterns.js +1 -1
  60. package/dist/knowledge/guard-ledger.js +1 -1
  61. package/dist/knowledge/guard-risk.js +1 -1
  62. package/dist/knowledge/guard-templates.js +1 -1
  63. package/dist/knowledge/index.js +1 -1
  64. package/dist/knowledge/industry-registry.js +1 -1
  65. package/dist/knowledge/jsonrpc-enum.js +1 -1
  66. package/dist/knowledge/machine-topology.d.ts +34 -0
  67. package/dist/knowledge/machine-topology.js +1 -1
  68. package/dist/knowledge/onchain-constants.d.ts +2 -2
  69. package/dist/knowledge/onchain-constants.js +1 -1
  70. package/dist/knowledge/scenario-modes.d.ts +13 -0
  71. package/dist/knowledge/scenario-modes.js +1 -1
  72. package/dist/knowledge/service-allocation-semantics.js +1 -1
  73. package/dist/knowledge/service-ledger.js +1 -1
  74. package/dist/knowledge/service-templates.js +1 -1
  75. package/dist/knowledge/strategy-manuals.js +1 -1
  76. package/dist/knowledge/template-loader.d.ts +37 -1
  77. package/dist/knowledge/template-loader.js +1 -1
  78. package/dist/knowledge/template-registry.js +1 -1
  79. package/dist/knowledge/template-scanner.js +1 -1
  80. package/dist/knowledge/tool-constraints.js +1 -1
  81. package/dist/knowledge/tools-reference.js +1 -1
  82. package/dist/knowledge/workflow-guidance.d.ts +4 -0
  83. package/dist/knowledge/workflow-guidance.js +1 -1
  84. package/dist/monitor/MonitorLoop.js +1 -1
  85. package/dist/participation/radar-core.d.ts +6 -0
  86. package/dist/participation/radar-core.js +1 -1
  87. package/dist/playbooks/service-build/business-puzzle.js +1 -1
  88. package/dist/playbooks/service-build/index.d.ts +1 -0
  89. package/dist/playbooks/service-build/intent-analyzer.d.ts +1 -0
  90. package/dist/playbooks/service-build/intent-analyzer.js +1 -1
  91. package/dist/playbooks/service-build/machine-nodes.d.ts +1 -1
  92. package/dist/playbooks/service-build/machine-nodes.js +1 -1
  93. package/dist/playbooks/service-build/machine-panorama.js +1 -1
  94. package/dist/playbooks/service-build/merchant-guide.js +1 -1
  95. package/dist/playbooks/service-build/migration-planner.js +1 -1
  96. package/dist/playbooks/service-build/object-panorama.d.ts +27 -1
  97. package/dist/playbooks/service-build/object-panorama.js +1 -1
  98. package/dist/playbooks/service-build/participation-radar.js +1 -1
  99. package/dist/playbooks/service-build/pipeline-actions.d.ts +4 -0
  100. package/dist/playbooks/service-build/pipeline-actions.js +1 -1
  101. package/dist/playbooks/service-build/relationship-profile.js +1 -1
  102. package/dist/playbooks/service-build/semantic-graph.d.ts +1 -0
  103. package/dist/playbooks/service-build/semantic-graph.js +1 -1
  104. package/dist/playbooks/service-build/service-panorama.js +1 -1
  105. package/dist/playbooks/service-build/service-quote.js +1 -1
  106. package/dist/review/index.js +1 -1
  107. package/dist/safety/confirm-gate.js +1 -1
  108. package/dist/schema/call/machine.d.ts +1 -0
  109. package/dist/schema/call/machine.js +1 -1
  110. package/dist/schema/call/order.d.ts +60 -0
  111. package/dist/schema/call/order.js +1 -1
  112. package/dist/schema/call/semantic.js +1 -1
  113. package/dist/schema/call/service.d.ts +56 -0
  114. package/dist/schema/call/service.js +1 -1
  115. package/dist/schema/common/index.js +1 -1
  116. package/dist/schema/goal/planning.d.ts +7 -0
  117. package/dist/schema/goal/planning.js +1 -1
  118. package/dist/schema/index.d.ts +1 -0
  119. package/dist/schema/index.js +1 -1
  120. package/dist/schema/industry-pack/index.d.ts +26 -0
  121. package/dist/schema/industry-pack/index.js +1 -1
  122. package/dist/schema/local/index.js +1 -1
  123. package/dist/schema/messenger/index.d.ts +20 -0
  124. package/dist/schema/messenger/index.js +1 -1
  125. package/dist/schema/operations.d.ts +50 -0
  126. package/dist/schema/persona/index.d.ts +110 -0
  127. package/dist/schema/query/bi.d.ts +2 -0
  128. package/dist/schema/query/bi.js +1 -1
  129. package/dist/schema/query/index.js +1 -1
  130. package/dist/schema/schema-version.js +1 -1
  131. package/dist/schema/service-clone/index.d.ts +46 -0
  132. package/dist/schema/service-clone/index.js +1 -0
  133. package/dist/schema/utils/node-parser.js +1 -1
  134. package/dist/schema-query-impl/index.js +1 -1
  135. package/dist/schemas/index.json +7 -1
  136. package/dist/schemas/industry_pack_operation.output.json +113 -0
  137. package/dist/schemas/industry_pack_operation.schema.json +7 -1
  138. package/dist/schemas/messenger_operation.output.json +42 -4
  139. package/dist/schemas/onchain_operations.output.json +1 -1
  140. package/dist/schemas/onchain_operations.schema.json +409 -98
  141. package/dist/schemas/onchain_operations_allocation.schema.json +5 -5
  142. package/dist/schemas/onchain_operations_arbitration.schema.json +2 -2
  143. package/dist/schemas/onchain_operations_contact.schema.json +1 -1
  144. package/dist/schemas/onchain_operations_demand.schema.json +3 -3
  145. package/dist/schemas/onchain_operations_gen_passport.schema.json +1 -1
  146. package/dist/schemas/onchain_operations_guard.schema.json +59 -59
  147. package/dist/schemas/onchain_operations_machine.schema.json +3 -3
  148. package/dist/schemas/onchain_operations_order.schema.json +133 -1
  149. package/dist/schemas/onchain_operations_progress.schema.json +1 -1
  150. package/dist/schemas/onchain_operations_proof.schema.json +1 -1
  151. package/dist/schemas/onchain_operations_repository.schema.json +1 -1
  152. package/dist/schemas/onchain_operations_reward.schema.json +4 -4
  153. package/dist/schemas/onchain_operations_service.schema.json +188 -9
  154. package/dist/schemas/onchain_operations_treasury.schema.json +7 -7
  155. package/dist/schemas/onchain_table_data.output.json +5 -1
  156. package/dist/schemas/persona_operation.output.json +266 -0
  157. package/dist/schemas/persona_operation.schema.json +42 -0
  158. package/dist/schemas/query_toolkit.output.json +46 -46
  159. package/dist/schemas/query_toolkit.schema.json +5 -1
  160. package/dist/schemas/service_clone_operation.output.json +168 -0
  161. package/dist/schemas/service_clone_operation.schema.json +49 -0
  162. package/dist/schemas/workflow_operation.schema.json +1 -1
  163. package/dist/tools/handlers/benchmark-migration.d.ts +17 -0
  164. package/dist/tools/handlers/benchmark-migration.js +1 -1
  165. package/dist/tools/handlers/config.js +1 -1
  166. package/dist/tools/handlers/industry-pack.js +1 -1
  167. package/dist/tools/handlers/messenger.js +1 -1
  168. package/dist/tools/handlers/onchain.js +1 -1
  169. package/dist/tools/handlers/query.js +1 -1
  170. package/dist/tools/handlers/service-clone.d.ts +2 -0
  171. package/dist/tools/handlers/service-clone.js +1 -0
  172. package/dist/tools/index.js +1 -1
  173. package/dist/tools/registry/business.js +1 -1
  174. package/dist/tools/wrap.d.ts +6 -1
  175. package/dist/tools/wrap.js +1 -1
  176. package/package.json +4 -3
@@ -1,5 +1,5 @@
1
1
  {
2
- "comment": "Allocation rule pack (R-A) — declarative form of the retired in-code Allocation risk rules. Facts are produced by the kernel extractor (allocation-risk.ts, bound to the allocation-ledger constants and the canonical recipient-constraint doctrine); rules only reference fact names. First-offender walks (empty sharing, rate/amount violations, weak guards) are sunk into the extractor. Levels/wording can be retuned per rule and rules can be disabled (enabled=false, trace left) via the judgment document — factory rules can never be deleted or invented here.",
2
+ "comment": "Allocation rule pack (R-A) — declarative form of the retired in-code Allocation risk rules. Facts are produced by the kernel extractor (allocation-risk.ts, bound to the allocation-ledger constants and the canonical recipient-constraint doctrine); rules only reference fact names. First-offender walks (empty sharing, rate/amount violations, weak guards) are sunk into the extractor. Levels/wording can be retuned per rule and rules can be disabled (enabled=false, trace left) via the judgment document — factory rules can never be deleted or invented here. RETRACTION (2026-10-06): R-RECIPIENT-03 previously cited `const ERECIPIENT_ZERO_ENTITY: u64 = 20` at allocation.move:64-67, asserted at allocation.move:259-268, as verbatim Move source. That quote was FABRICATED — `grep ERECIPIENT_ZERO_ENTITY` over all of D:\\wowok\\wow returns 0 .move hits and git history never contained it; the real const E* block is allocation.move:22-62 + :75 and occupies 1..19 with no 20. The rule text below now cites the verified coordinates. Do not restore the constant or the error-code parity claim.",
3
3
  "facts": {
4
4
  "allocators_config_present": "An allocators_config block is present in the context.",
5
5
  "allocators_count": "Number of allocators in allocators_config (undefined when the block is absent).",
@@ -28,6 +28,9 @@
28
28
  "amount_rule_amount_sum": "Sum of Amount-mode sharing values (Number()) for the offender.",
29
29
  "amount_rule_threshold": "Number(allocators_config.threshold) for the offender (NaN when absent — comparisons stay false).",
30
30
  "amount_rule_max": "Number(allocator.max) for the offender (only set on the exceeds_max branch).",
31
+ "surplus_dup_rate_index": "First allocator whose Surplus row pays the SAME recipient as one of its own Rate/RateGross legs (undefined when none). Recipient identity is normalized per kind (Entity lowercased, GuardIdentifier by id, Signer by kind).",
32
+ "surplus_dup_rate_guard": "Guard address of the Surplus-duplication offender.",
33
+ "surplus_dup_rate_recipient": "Rendered duplicated recipient (e.g. 'Entity(0x…)', 'GuardIdentifier(0)', 'Signer') for evidence display.",
31
34
  "signer_theft_detected": "Canonical RC-FIND-01 judgment (recipient-constraint.ts): a sharing.who=Signer recipient whose Guard does not bind the caller identity.",
32
35
  "signer_theft_index": "Allocator index of the unbound-Signer theft offender.",
33
36
  "signer_theft_guard": "Guard address of the theft offender.",
@@ -35,6 +38,10 @@
35
38
  "guard_id_bad_identifier": "First GuardIdentifier recipient whose Passport table entry is known and not address-typed (undefined when none).",
36
39
  "guard_id_bad_type": "Declared Passport table type of the offending identifier.",
37
40
  "guard_id_bad_index": "Allocator index carrying the offending GuardIdentifier recipient.",
41
+ "entity_recipient_bad_count": "Number of sharing rows whose Entity recipient is unusable — empty/omitted name_or_address, a literal zero address, or (when the caller supplies builder_address) the deploying account. Judged by the shared guard (entity-recipient-guard.ts, R-RECIPIENT-03); 0 when the table carries no Entity row or carries none that is unusable.",
42
+ "entity_recipient_bad_count_display": "Rendered entity_recipient_bad_count ('0' when none) for evidence display.",
43
+ "entity_recipient_bad_rows_csv_sp": "Offending rows as 'Allocator[<i>].sharing[<j>] (<reason>)' joined with ', ' (description display form; undefined when none).",
44
+ "entity_recipient_bad_rows_csv": "Offending rows as 'Allocator[<i>].sharing[<j>] (<reason>)' joined with ',' (evidence display form; undefined when none).",
38
45
  "duplicate_guards_present": "Two or more allocators reference the same Guard object address.",
39
46
  "allocators_total": "Total allocator count (duplicate scan).",
40
47
  "allocators_unique": "Unique Guard address count (duplicate scan).",
@@ -220,6 +227,32 @@
220
227
  }
221
228
  ]
222
229
  },
230
+ {
231
+ "rule_id": "R-A-1-04",
232
+ "dimension": "structural",
233
+ "trigger": "A Surplus sharing row pays the same recipient as a Rate leg in the same Allocator — the remainder swallows an undeclared share",
234
+ "cases": [
235
+ {
236
+ "when": { "field": "surplus_dup_rate_index", "op": "present" },
237
+ "level": "critical",
238
+ "title": "Surplus row pays ${surplus_dup_rate_recipient}, which a Rate leg already pays (Allocator ${surplus_dup_rate_index}) — undeclared share swallowed",
239
+ "description": "Allocator ${surplus_dup_rate_index} (guard=${surplus_dup_rate_guard}) carries a Surplus row whose recipient ${surplus_dup_rate_recipient} is identical to one of its own Rate legs. The chain pays every Rate leg its declared basis points and then pays the ENTIRE remainder to the Surplus recipient — so this party quietly receives rate% + 100% of the residual, and any leg the author intended but forgot (commission, payment fee, platform take) is never created. The Rate-sum rule (R-A-1-02) passes because the sum is legal; the misroute is invisible there. order_allocators freezes permanently at Service publish, so the error is irreversible once live. Mainnet precedent (Booking replica, 2026-10-06): a Surplus row added to satisfy the Rate-sum rule pointed at the property again — the property took 100%, the 15% commission and 1.5% payment-fee legs never existed, and the published Service had to be abandoned.",
240
+ "scenario": "Surplus recipient duplicated from a Rate leg — typically a filler row added to make the Rate sum legal, whose recipient the author forgot to change to the intended commission/fee party",
241
+ "mitigation": "Point the Surplus row at the party meant to absorb the remainder (usually the merchant), distinct from every Rate leg — or, if the duplicated recipient is truly intended to take rate% + remainder, collapse the two rows into ONE Rate leg at 10000 minus the other legs' sum so the declared split equals the actual split. Every intended payee must be its own explicit leg.",
242
+ "evidence": "allocator=${surplus_dup_rate_index}, guard=${surplus_dup_rate_guard}, duplicatedRecipient=${surplus_dup_rate_recipient}",
243
+ "stakeholders": ["owner", "recipient", "service_provider"],
244
+ "exposure": {
245
+ "loss_parties": [
246
+ { "role": "merchant", "basis": "The omitted leg (commission, fee, royalty) is paid to the duplicated recipient instead — the intended payee is silently unpaid on every order, and the misroute is frozen at publish." },
247
+ { "role": "buyer", "basis": "The declared split the buyer read before ordering no longer matches where the money lands — the ledger overstates the duplicated party's share." }
248
+ ],
249
+ "gain_paths": [
250
+ { "role": "merchant", "path": "The duplicated recipient leg (a merchant-designated payee) receives its Rate share plus the entire Surplus remainder on every distribution, with no on-chain assertion objecting.", "viability": "viable", "gate": "Nothing gates it — the chain pays Rate legs then the Surplus verbatim; only this scan sees the duplication." }
251
+ ]
252
+ }
253
+ }
254
+ ]
255
+ },
223
256
  {
224
257
  "rule_id": "R-A-2-01",
225
258
  "dimension": "semantic",
@@ -266,15 +299,15 @@
266
299
  {
267
300
  "rule_id": "R-A-2-03",
268
301
  "dimension": "semantic",
269
- "trigger": "Duplicate Guard addresses in allocators (alloc_by_guard address match only hits the first one)",
302
+ "trigger": "Duplicate Guard addresses in allocators (every allocator bound to the submitted guard fires — duplicates stack, they do not compete)",
270
303
  "cases": [
271
304
  {
272
305
  "when": { "field": "duplicate_guards_present", "op": "eq", "value": true },
273
- "level": "medium",
274
- "title": "Duplicate Guard addresses in allocators",
275
- "description": "Multiple Allocators reference the same Guard object: ${duplicate_guards_csv_sp}. alloc_by_guard locates the Allocator by iterating guard addresses; duplicate addresses only match the first one, so subsequent duplicate Allocators are unreachable (dead code).",
306
+ "level": "high",
307
+ "title": "Duplicate Guard addresses in allocators — every duplicate FIRES on the same call",
308
+ "description": "Multiple Allocators reference the same Guard object: ${duplicate_guards_csv_sp}. allocation.move:366-368 runs a full loop with no early exit: a distribution call submitting that guard fires EVERY matching allocator, in vector order, each against the balance left by the previous one. This is a legitimate waterfall mechanism ONLY if intended; if the duplicates were meant as alternative paths, they are NOT alternatives — one call triggers all of them, the pool is split repeatedly, and a later allocator may abort on insufficient balance or pay out of money earmarked for another track.",
276
309
  "scenario": "Reusing same Guard object across multiple Allocators",
277
- "mitigation": "Create independent Guard objects for each Allocator (they may share root/table but must differ in description).",
310
+ "mitigation": "Intended waterfall (fee first, then split the remainder): keep the shared guard and verify the vector order matches the intended payout sequence. Intended alternatives (refund vs damage vs settle): give each path its OWN Guard object (they may share root/table but must be distinct objects) so the caller's guard selection picks exactly one path.",
278
311
  "evidence": "total=${allocators_total}, unique=${allocators_unique}, dupes=${duplicate_guards_csv}",
279
312
  "stakeholders": ["owner", "allocator"]
280
313
  }
@@ -305,11 +338,11 @@
305
338
  { "field": "guard_identifier_recipient_present", "op": "eq", "value": true },
306
339
  { "field": "for_object_anchor_set", "op": "eq", "value": false }
307
340
  ] },
308
- "level": "medium",
309
- "title": "payment_info.for_object = None — GuardIdentifier destination has no on-chain anchor",
310
- "description": "payment_info.for_object is not set: alloc() enforces NO recipient binding for Recipient::GuardIdentifier lines. Whoever builds the passport chooses the payout address; nothing on-chain constrains it. This is legal by design (Allocation is a generic component, the anchor is opt-in), but it means recipient routing is trust-on-creator.",
341
+ "level": "critical",
342
+ "title": "GuardIdentifier recipients with NO for_object anchor — the chain enforces NOTHING",
343
+ "description": "payment_info.for_object is None while allocator(s) pay GuardIdentifier recipients. Verified on-chain: the recipient binding check is gated on the anchor being Some — allocation.move:444-448 runs the ERECIPIENT_NOT_FOR_OBJECT (15) assertion only inside `if (option::is_some(&binding))`; with None the passport-submitted address is paid VERBATIM, and the code comment itself (allocation.move:434-443) pushes the due-diligence obligation to the tooling layer. Whoever builds a satisfying passport names the payout address — funds can be redirected with nothing on-chain objecting. The Service order flow always anchors (service.move:394 sets for_object to the paying order) and the Arbitration fee flow anchors too (arbitration.move:342), so a None anchor is reachable only through a standalone or custom-constructed PaymentInfo — exactly the surface this rule gates.",
311
344
  "scenario": "Allocation created without a for_object anchor while using GuardIdentifier recipients",
312
- "mitigation": "If enforced routing is desired, set payment_info.for_object to the intended recipient anchor at create (it becomes mechanism-enforced: mismatch aborts with code 15). If no anchor is intended, document that the recipient is the passport builder's choice and constrain it in the guard expression instead.",
345
+ "mitigation": "Set payment_info.for_object to the intended anchor at create (mismatch then aborts with code 15 — mechanism-enforced). If the payout destination must stay passport-chosen by design, do NOT use GuardIdentifier legs: use Entity legs (static, declared at create) or constrain the submitted address inside the guard expression itself. Treat any None+GuardIdentifier config as a fund-redirection hole until one of those is in place.",
313
346
  "evidence": "for_object=None",
314
347
  "stakeholders": ["owner", "recipient"],
315
348
  "exposure": {
@@ -419,7 +452,7 @@
419
452
  {
420
453
  "rule_id": "R-A-4-01",
421
454
  "dimension": "lifecycle",
422
- "trigger": "caller-specified guard with no matching Allocator — silent return causes funds to be stuck",
455
+ "trigger": "caller-specified guard with no matching Allocator — aborts with E_ALLOCATOR_NOT_MATCHED",
423
456
  "cases": [
424
457
  {
425
458
  "when": { "all": [
@@ -428,16 +461,16 @@
428
461
  { "field": "has_fallback_allocator", "op": "falsy" }
429
462
  ] },
430
463
  "level": "critical",
431
- "title": "caller-specified guard with no matching Allocator — silent return causes funds to be stuck",
432
- "description": "Multi-Allocation configuration (${allocators_count} Allocators) without alternative Allocator. alloc() is driven by the caller explicitly specifying an Allocator via alloc_by_guard: if the guard address does not match any allocators[].guard, alloc() silently returns (no error, no distribution), funds remain stuck in Allocation.balance, and the caller gets no immediate signal. Linked to guard-risk.ts R-X1-05.",
464
+ "title": "caller-specified guard with no matching Allocator — aborts with E_ALLOCATOR_NOT_MATCHED",
465
+ "description": "Multi-Allocation configuration (${allocators_count} Allocators) without alternative Allocator. alloc() is driven by the caller explicitly specifying an Allocator via alloc_by_guard: if the guard address does not match any allocators[].guard, alloc() aborts with E_ALLOCATOR_NOT_MATCHED (allocation.move:295) — the transaction reverts and funds remain in the order escrow, not stuck in Allocation.balance. Linked to guard-risk.ts R-X1-05.",
433
466
  "scenario": "Caller passes a guard address not present in allocators[] (e.g. wrong Guard object, or order state not matching any terminal node)",
434
- "mitigation": "1) The caller should verify alloc_by_guard ∈ allocators[].guard address set before calling alloc(). 2) Provide an alternative Allocator and document the caller's retry strategy. 3) Caller should check balance==0 after alloc() to detect no-match.",
467
+ "mitigation": "1) The caller should verify alloc_by_guard ∈ allocators[].guard address set before calling alloc(). 2) Provide an alternative Allocator and document the caller's retry strategy. 3) Catch the abort and surface the mismatch to the operator.",
435
468
  "evidence": "allocators=${allocators_count}, hasFallback=${has_fallback_allocator_display}",
436
469
  "stakeholders": ["owner", "service_provider", "system"],
437
470
  "exposure": {
438
471
  "loss_parties": [
439
- { "role": "buyer", "basis": "A no-match alloc_by_guard call silently distributes nothing — the escrowed order funds sit frozen in Allocation.balance with no error to surface the miss." },
440
- { "role": "merchant", "basis": "The merchant share stays frozen with the pool until a caller retries with a matching guard address." }
472
+ { "role": "buyer", "basis": "A no-match alloc_by_guard call aborts with E_ALLOCATOR_NOT_MATCHED — the escrowed order funds stay in the order escrow, and the caller must retry with a matching guard." },
473
+ { "role": "merchant", "basis": "The merchant share stays in the order escrow until a caller retries with a matching guard address." }
441
474
  ],
442
475
  "gain_paths": []
443
476
  }
@@ -518,6 +551,32 @@
518
551
  "stakeholders": ["owner"]
519
552
  }
520
553
  ]
554
+ },
555
+ {
556
+ "rule_id": "R-RECIPIENT-03",
557
+ "dimension": "semantic",
558
+ "trigger": "A sharing row pays an Entity recipient whose name_or_address is omitted, blank, the literal zero address, or the deploying account",
559
+ "cases": [
560
+ {
561
+ "when": { "field": "entity_recipient_bad_count", "op": "gte", "value": 1 },
562
+ "level": "critical",
563
+ "title": "${entity_recipient_bad_count_display} sharing row(s) pay an unnamed Entity — the share goes to the DEPLOYING ACCOUNT: ${entity_recipient_bad_rows_csv_sp}",
564
+ "description": "Offending sharing row(s): ${entity_recipient_bad_rows_csv_sp}. A recipient row reading { who: { Entity: { name_or_address: \"\" } } } (or with the key omitted entirely) is NOT a no-op: the SDK resolver substitutes the DEFAULT LOCAL ACCOUNT before the address ever reaches Move, so the order escrow pays the account that ran the deploy instead of any intended third party. There is no on-chain defence at all: allocation.move:250-255 swallows Recipient::Entity in a (_) catch-all at allocator_add, and allocation.move:451-453 / :514 forward the raw address to payment::new at payout, so the chain accepts ANY Entity address and a degenerate one silently burns the share permanently. DIRECT-SALES INVARIANT (verbatim, shared with the tool-constraint layer and the pre-call gate): WoWok is DIRECT SALES: the merchant pays gas only and there is NO platform commission, so no allocator sharing row may pay the deploying account. An omitted / empty / unresolved Entity recipient is NOT a no-op — the SDK resolves it to the DEFAULT LOCAL ACCOUNT (ts-sdk/packages/wowok/src/w/local/index.ts:89-91 -> Account.Instance().get_address(undefined)), so the share silently leaves the escrow to whoever ran the deploy. AUDIT INVARIANT: any allocator sharing item resolving to the platform/builder entity with a non-zero share is an ERROR under direct-sales. Under direct sales there is no platform to pay, so an unnamed Entity row is never legitimate.",
565
+ "scenario": "A sharing row is authored with a blank, omitted, zero, or builder-equivalent Entity recipient — typically a placeholder the author intended to fill in, or a 'platform' leg copied from an incumbent marketplace split that this protocol has no recipient for.",
566
+ "mitigation": "WoWok is DIRECT SALES: the merchant pays gas only and there is NO platform commission, so no allocator sharing row may pay the deploying account. An omitted / empty / unresolved Entity recipient is NOT a no-op — the SDK resolves it to the DEFAULT LOCAL ACCOUNT (ts-sdk/packages/wowok/src/w/local/index.ts:89-91 -> Account.Instance().get_address(undefined)), so the share silently leaves the escrow to whoever ran the deploy. AUDIT INVARIANT: any allocator sharing item resolving to the platform/builder entity with a non-zero share is an ERROR under direct-sales. How to fix the allocator table: (a) the merchant keeps the WHOLE sale — ONE sharing row { who: { GuardIdentifier: 0 }, sharing: 10000, mode: \"Rate\" }, the refund track anchored via payment_info.for_object so only the order owner can receive it; (b) a GENUINE third party must be an EXPLICIT, non-empty Entity the buyer can see BEFORE ordering — { who: { Entity: { name_or_address: \"<mark_name_or_0x_address>\" } } }; (c) NEVER use { Signer: \"signer\" } for a refund — it pays tx_context::sender, i.e. whoever runs the alloc() call; (d) order_allocators is PERMANENTLY IMMUTABLE after publish (no pause+lock escape), so fix the recipient table BEFORE publish, not after.",
567
+ "evidence": "badRows=${entity_recipient_bad_rows_csv}, count=${entity_recipient_bad_count_display}",
568
+ "stakeholders": ["owner", "recipient", "service_provider"],
569
+ "exposure": {
570
+ "loss_parties": [
571
+ { "role": "buyer", "basis": "The buyer's escrowed order funds are the pool being drained: a share routed to the deploying account is money the buyer paid that never reaches the party the template named, and no receipt, constraint, or audit trail distinguishes it from a legitimate payout." },
572
+ { "role": "merchant", "basis": "A third-party share (supplier, orchestrator, arbitrator fee) credited to the deploying account inflates the merchant's own take while the genuine recipient is unpaid — the ledger a buyer reads before ordering no longer matches where the money lands." }
573
+ ],
574
+ "gain_paths": [
575
+ { "role": "collaborator", "path": "Whoever holds the DEFAULT LOCAL ACCOUNT at deploy time (the deploying account) receives the unnamed share verbatim, because the SDK resolves the blank Entity to that account and the chain records it as a plain Recipient::Entity payout.", "viability": "viable", "gate": "Direct-pay and unconditional: nothing in Move distinguishes a substituted recipient from an authored one. allocation.move:250-255 matches only Recipient::GuardIdentifier and lets Recipient::Entity fall through a (_) catch-all, and allocation.move:451-453 / :514 forward the raw address to payment::new with no check at all — so there is no chain-side assertion left to fire." }
576
+ ]
577
+ }
578
+ }
579
+ ]
521
580
  }
522
581
  ]
523
582
  }
@@ -1,5 +1,5 @@
1
1
  {
2
- "comment": "Guard rule pack (R-C/R-X) — declarative form of the retired in-code Guard risk rules (the guard condition-tree lint pack: 34 rules over the serialized root, table classification and the scene ledger). Facts are produced by the kernel extractor (guard-risk.ts, bound to the GUARDQUERY invariant catalog, the witness chain table, the certain-outcome folder and the per-scene reentrancy specs); rules only reference fact names. Levels/wording can be retuned per rule and rules can be disabled (enabled=false, trace left) via the judgment document — factory rules can never be deleted or invented here.",
2
+ "comment": "Guard rule pack (R-C/R-X) — declarative form of the retired in-code Guard risk rules (the guard condition-tree lint pack: 35 rules over the serialized root, table classification and the scene ledger). Facts are produced by the kernel extractor (guard-risk.ts, bound to the GUARDQUERY invariant catalog, the witness chain table, the certain-outcome folder and the per-scene reentrancy specs); rules only reference fact names. Levels/wording can be retuned per rule and rules can be disabled (enabled=false, trace left) via the judgment document — factory rules can never be deleted or invented here.",
3
3
  "facts": {
4
4
  "has_signer_check": "KERNEL — the serialized root contains a context(Signer) check.",
5
5
  "has_clock_check": "KERNEL — the serialized root contains a context(Clock) node.",
@@ -96,7 +96,9 @@
96
96
  "tree_folds_always_false_reason": "KERNEL — the decisive always-false pattern rendered for the evidence line.",
97
97
  "write_guard_scene": "KERNEL — the Guard is bound as a Repository policy write_guard (scene binding_field=write_guard / id repository_write_guard).",
98
98
  "write_guard_no_identity_check": "KERNEL — write_guard scene AND no context(Signer) leaf anywhere in the root: the gate binds a STATE, never the WRITER, so any caller satisfying it may write or overwrite any row.",
99
- "write_guard_open": "KERNEL — write_guard scene AND (the tree folds true OR it binds no caller identity): the Repository write is effectively unauthenticated even though the policy has a guard."
99
+ "write_guard_open": "KERNEL — write_guard scene AND (the tree folds true OR it binds no caller identity): the Repository write is effectively unauthenticated even though the policy has a guard.",
100
+ "bare_bool_query_count": "KERNEL — number of DISTINCT query nodes sitting directly under logic_and/logic_or (bare Bool clause position; absent when zero).",
101
+ "bare_bool_query_csv_sp": "KERNEL — those bare-clause queries rendered '#<id>' or by name, comma-space joined."
100
102
  },
101
103
  "rules": [
102
104
  {
@@ -163,6 +165,23 @@
163
165
  }
164
166
  ]
165
167
  },
168
+ {
169
+ "rule_id": "R-X1-18",
170
+ "dimension": "logic_gap",
171
+ "trigger": "A query node sits directly under logic_and/logic_or — a bare Bool clause asserting the POSITIVE state of whatever it reads (OFN mainnet #P-01)",
172
+ "cases": [
173
+ {
174
+ "when": { "field": "bare_bool_query_count", "op": "present" },
175
+ "level": "medium",
176
+ "title": "Bare Bool query clause (${bare_bool_query_csv_sp}) asserts the POSITIVE state — polarity unchecked",
177
+ "description": "A query placed directly under logic_and/logic_or must return Bool and asserts that the condition IS true ('is set / exists / claimed'). OFN mainnet #P-01: a refund guard used a bare order.claimed_by-some query (id 1570) — it reads 'already claimed', so it is FALSE on every fresh order; all five refund rails aborted (code 7), and order_allocators is immutable after publish (service.move asserts !bPublished), so the Service had to be rebuilt from scratch. When the intent is the NEGATIVE — the common refund/double-spend guard pattern ('not yet claimed', 'no allocation yet') — a bare clause expresses the exact opposite of what the author meant.",
178
+ "scenario": "Refund guard with a bare claimed_by query: every refund call aborts on a fresh order, and publish freezes the misconfiguration permanently",
179
+ "mitigation": "Wrap the query in logic_equal with an explicit Bool constant so the polarity is stated: logic_equal[query(order.claimed_by some), Bool false] for 'not yet claimed' (the refund pattern); logic_equal[query(...), Bool true] when the positive state is genuinely required. Re-run the Guard analyzer after the edit.",
180
+ "evidence": "Bare query clause(s) under logic_and/logic_or: ${bare_bool_query_csv_sp}",
181
+ "stakeholders": ["provider", "customer"]
182
+ }
183
+ ]
184
+ },
166
185
  {
167
186
  "rule_id": "R-C1-01",
168
187
  "dimension": "data_source_trust",
@@ -488,7 +507,7 @@
488
507
  "title": "allocators Guard without Signer binding — ${rc3_06_title_suffix}",
489
508
  "description": "Guard is bound to Service order_allocators but does NOT verify context(Signer). In the allocators scene, sharing.who determines the fund recipient:\n - sharing.who=Signer → funds flow to the CALLER (transaction signer)\n - sharing.who=Entity → funds flow to a FIXED address (Treasury/personal)\n - sharing.who=GuardIdentifier → funds flow to the submitted constrained object (e.g. an Order), receivable only by that object's owner (object receipt = owner receipt); see RC-FIND-03 residual in the recipient constraint audit\n\nWhen sharing.who=Signer and the Guard does not bind Signer, ANY caller who passes the Guard receives the order funds — a direct theft. Even an authorized caller is unnecessary: the attacker only needs to pass the Guard's condition (e.g. submit any completed order_id), then the funds go to their own address.\n\nThis is distinct from R-C3-01 (generic Signer binding) and R-C3-05 (cross-project bypass). R-C3-06 addresses the allocators-specific Guard+sharing coupling: the SAFE design uses sharing.who=Entity so the recipient is fixed, making Guard Signer binding unnecessary.\n\nsharing_recipients hint: ${rc3_06_recipients_label}",
490
509
  "scenario": "Fund theft: attacker passes the allocators Guard (e.g. submits a completed order_id) and sharing.who=Signer routes 100% of the order funds to the attacker's address",
491
- "mitigation": "TWO mitigation options:\n (A) Traditional: add context(Signer) binding to the Guard root (logic_equal[context(Signer), identifier[N](authorized_address)]) so only the authorized recipient can trigger allocation.\n (B) PREFERRED: use sharing.who=Entity (Treasury object or fixed personal address) in the Allocator's sharing config. Funds then always flow to the designated recipient regardless of who triggers allocation — the Guard does NOT need to bind Signer. This is more robust because it does not depend on Guard correctness.\n\nDesign checklist when using sharing.who=Entity:\n - If Entity points to a Treasury object, verify the Treasury's permission is consistent with the Service's permission (different permissions = different permission organizations = minor risk).\n - allocators require unique guard addresses: create separate Guard objects (identical root/table, different descriptions) for each allocator.\n - First-match-wins: only the first Allocator whose Guard passes executes; multiple allocators represent alternative strategies, not a simultaneous split.\n - Still add order.service project binding (query 1563) to prevent cross-project bypass (R-C3-05) triggering premature allocation.",
510
+ "mitigation": "TWO mitigation options:\n (A) Traditional: add context(Signer) binding to the Guard root (logic_equal[context(Signer), identifier[N](authorized_address)]) so only the authorized recipient can trigger allocation.\n (B) PREFERRED: use sharing.who=Entity (Treasury object or fixed personal address) in the Allocator's sharing config. Funds then always flow to the designated recipient regardless of who triggers allocation — the Guard does NOT need to bind Signer. This is more robust because it does not depend on Guard correctness.\n\nDesign checklist when using sharing.who=Entity:\n - If Entity points to a Treasury object, verify the Treasury's permission is consistent with the Service's permission (different permissions = different permission organizations = minor risk).\n - allocators require unique guard addresses: create separate Guard objects (identical root/table, different descriptions) for each allocator.\n - Each allocator is bound to a DIFFERENT guard — the caller's submitted guard selects which path fires. Same-guard allocators fire sequentially on the same call (waterfall); different-guard allocators are alternative paths.\n - Still add order.service project binding (query 1563) to prevent cross-project bypass (R-C3-05) triggering premature allocation.",
492
511
  "evidence": "Scene: service_order_allocators_guard. Guard binds Signer: no. sharing_recipients: ${rc3_06_recipients_label}. Level rationale: ${rc3_06_level_rationale}.",
493
512
  "stakeholders": ["provider", "customer"],
494
513
  "exposure": {
@@ -512,7 +531,7 @@
512
531
  "title": "allocators Guard without Signer binding — ${rc3_06_title_suffix}",
513
532
  "description": "Guard is bound to Service order_allocators but does NOT verify context(Signer). In the allocators scene, sharing.who determines the fund recipient:\n - sharing.who=Signer → funds flow to the CALLER (transaction signer)\n - sharing.who=Entity → funds flow to a FIXED address (Treasury/personal)\n - sharing.who=GuardIdentifier → funds flow to the submitted constrained object (e.g. an Order), receivable only by that object's owner (object receipt = owner receipt); see RC-FIND-03 residual in the recipient constraint audit\n\nWhen sharing.who=Signer and the Guard does not bind Signer, ANY caller who passes the Guard receives the order funds — a direct theft. Even an authorized caller is unnecessary: the attacker only needs to pass the Guard's condition (e.g. submit any completed order_id), then the funds go to their own address.\n\nThis is distinct from R-C3-01 (generic Signer binding) and R-C3-05 (cross-project bypass). R-C3-06 addresses the allocators-specific Guard+sharing coupling: the SAFE design uses sharing.who=Entity so the recipient is fixed, making Guard Signer binding unnecessary.\n\nsharing_recipients hint: ${rc3_06_recipients_label}",
514
533
  "scenario": "Fund theft: attacker passes the allocators Guard (e.g. submits a completed order_id) and sharing.who=Signer routes 100% of the order funds to the attacker's address",
515
- "mitigation": "TWO mitigation options:\n (A) Traditional: add context(Signer) binding to the Guard root (logic_equal[context(Signer), identifier[N](authorized_address)]) so only the authorized recipient can trigger allocation.\n (B) PREFERRED: use sharing.who=Entity (Treasury object or fixed personal address) in the Allocator's sharing config. Funds then always flow to the designated recipient regardless of who triggers allocation — the Guard does NOT need to bind Signer. This is more robust because it does not depend on Guard correctness.\n\nDesign checklist when using sharing.who=Entity:\n - If Entity points to a Treasury object, verify the Treasury's permission is consistent with the Service's permission (different permissions = different permission organizations = minor risk).\n - allocators require unique guard addresses: create separate Guard objects (identical root/table, different descriptions) for each allocator.\n - First-match-wins: only the first Allocator whose Guard passes executes; multiple allocators represent alternative strategies, not a simultaneous split.\n - Still add order.service project binding (query 1563) to prevent cross-project bypass (R-C3-05) triggering premature allocation.",
534
+ "mitigation": "TWO mitigation options:\n (A) Traditional: add context(Signer) binding to the Guard root (logic_equal[context(Signer), identifier[N](authorized_address)]) so only the authorized recipient can trigger allocation.\n (B) PREFERRED: use sharing.who=Entity (Treasury object or fixed personal address) in the Allocator's sharing config. Funds then always flow to the designated recipient regardless of who triggers allocation — the Guard does NOT need to bind Signer. This is more robust because it does not depend on Guard correctness.\n\nDesign checklist when using sharing.who=Entity:\n - If Entity points to a Treasury object, verify the Treasury's permission is consistent with the Service's permission (different permissions = different permission organizations = minor risk).\n - allocators require unique guard addresses: create separate Guard objects (identical root/table, different descriptions) for each allocator.\n - Each allocator is bound to a DIFFERENT guard — the caller's submitted guard selects which path fires. Same-guard allocators fire sequentially on the same call (waterfall); different-guard allocators are alternative paths.\n - Still add order.service project binding (query 1563) to prevent cross-project bypass (R-C3-05) triggering premature allocation.",
516
535
  "evidence": "Scene: service_order_allocators_guard. Guard binds Signer: no. sharing_recipients: ${rc3_06_recipients_label}. Level rationale: ${rc3_06_level_rationale}.",
517
536
  "stakeholders": ["provider", "customer"]
518
537
  }
@@ -668,10 +687,10 @@
668
687
  {
669
688
  "when": { "field": "scene_binding_field", "op": "eq", "value": "order_allocators" },
670
689
  "level": "medium",
671
- "title": "order_allocators first-match-wins may cause unfair allocation",
672
- "description": "order_allocators uses a first-match-wins strategy: the first Allocator whose Guard passes executes the allocation.If the order is poorly designed, some participants may never get allocated.For example: if the first Allocator's Guard condition is too broad, subsequent more precise Allocators will never execute.Allocation Guards typically combine Type1 (query order fields), Type3 (submit Order address), and Type4 (Signer) data.",
673
- "scenario": "Allocation skip: the Provider's allocation Allocator is skipped by preceding broad conditions",
674
- "mitigation": "1) Ensure the order_allocators order goes from strict to loose;2) Each Allocator's Guard conditions should be mutually exclusive;3) Consider using logic_or dependencies rather than sequential ordering",
690
+ "title": "order_allocators multi-guard paths may cause unfair allocation",
691
+ "description": "A distribution call submits ONE guard; every allocator bound to that guard fires in vector order against the shrinking pool (allocation.move:366-368). If a single guard binds multiple allocators with overlapping conditions, the pool is split repeatedly and later allocators may receive less than intended or abort on insufficient balance. For alternative business paths (settle vs refund), use DIFFERENT guards so the caller's guard choice selects exactly one path.",
692
+ "scenario": "Allocation path overlap: multiple allocators bound to the same guard all fire, draining the pool in unexpected order",
693
+ "mitigation": "1) Bind each business path (settle/refund/damage) to its OWN Guard object; 2) Make guard conditions mutually exclusive via guard logic, not list order; 3) When a waterfall IS intended (fee first, then split remainder), keep the shared guard and verify the vector order matches the intended payout sequence",
675
694
  "stakeholders": ["provider"]
676
695
  }
677
696
  ]
@@ -0,0 +1,161 @@
1
+ {
2
+ "comment": "Plan-domain rules (L1 composition kernel). Facts are produced by composition/audit.ts planFacts() from a Plan AST. These six rules exist because each one names a verified mainnet failure class from wiki/test/10 (TRAILHAVEN): zero allocator rows passing silently, prose guard logic, dual-rail double-pay, path-dependent settlement windows, write-only enforcement, dropped guard/repository hosts.",
3
+ "facts": {
4
+ "has_service": "Plan creates a service object",
5
+ "has_machine": "Plan creates a machine object",
6
+ "has_repository": "Plan creates a repository object",
7
+ "allocator_row_count": "Total allocator rows across service create param and order_allocators configure steps",
8
+ "refund_row_count": "Allocator rows on the refund track",
9
+ "terminal_count": "Machine terminal nodes (never a non-entry prev_node)",
10
+ "terminal_nodes_display": "Comma-joined terminal node names",
11
+ "first_uncovered_terminal": "First terminal node no allocator guard accepts via progress.current equality (FUND-EXIT-01)",
12
+ "first_refund_guard_missing_claimed_by": "First refund-track guard step id whose queries never read order.claimed_by (DUAL-RAIL-01, A-3)",
13
+ "first_path_dependent_anchor": "First guard step id using a path-dependent time query (TIME-ANCHOR-01, E-10)",
14
+ "path_sensitive": "The anchored node is reachable by more than one path, so the anchor can shift",
15
+ "first_write_only_unenforced_field": "First write-only param set with no guard query covering it (WRITE-ONLY-01, A-1/A-10)",
16
+ "first_orphan_guard": "First guard step id never referenced by any host binding (HOST-GRAPH-01, B-2)",
17
+ "first_repo_query_without_repository": "First guard step id querying repository.* while the plan creates no repository (HOST-GRAPH-01, B-2)",
18
+ "pending_allocator_guard_count": "Unresolved guard designs bound to allocator rows (compile-time judgment surface; >0 downgrades FUND-EXIT-01 from critical to medium)",
19
+ "first_pending_fund_guard": "First unresolved guard design bound to an allocator row or machine edge (UNRESOLVED-GUARD-01) — publishing now ships that path UNGUARDED",
20
+ "first_pending_buy_guard": "First unresolved guard design bound to the service buy path (UNRESOLVED-GUARD-01 warning tier)"
21
+ },
22
+ "rules": [
23
+ {
24
+ "rule_id": "PLAN-ALLOC-01",
25
+ "dimension": "fund_flow",
26
+ "trigger": "Service plan materializes zero allocator rows — the TRAILHAVEN failure: migration_apply emitted plan_allocation: [] with no critical, and the shop could neither settle nor refund.",
27
+ "cases": [
28
+ {
29
+ "when": { "all": [ { "field": "has_service", "op": "truthy" }, { "field": "allocator_row_count", "op": "eq", "value": 0 } ] },
30
+ "level": "critical",
31
+ "title": "Service plan has no allocator rows",
32
+ "description": "The plan creates a service but materializes zero allocator rows. On chain, publish aborts with E_NO_ALLOCATOR; worse, a plan that reaches materialization without rows means the shop cannot settle or refund anything.",
33
+ "mitigation": "Add at least one settlement-track allocator row (guard + recipient + mode). If the business has a refund path, add a refund-track row too.",
34
+ "stakeholders": ["merchant", "customer"]
35
+ }
36
+ ]
37
+ },
38
+ {
39
+ "rule_id": "FUND-EXIT-01",
40
+ "dimension": "fund_flow",
41
+ "trigger": "A business-terminal machine node is accepted by no allocator guard — funds that reach it are stranded forever (Machine/Service freeze at publish, A-2). Two tiers: with zero pending guard designs the money has NO declared exit (critical); with pending designs the exit is DECLARED but unverifiable until the judgment layer resolves them (medium).",
42
+ "cases": [
43
+ {
44
+ "when": { "all": [ { "field": "terminal_count", "op": "gt", "value": 0 }, { "field": "first_uncovered_terminal", "op": "present" }, { "field": "pending_allocator_guard_count", "op": "eq", "value": 0 } ] },
45
+ "level": "critical",
46
+ "title": "Terminal node '${first_uncovered_terminal}' has no fund exit",
47
+ "description": "Terminal nodes (${terminal_nodes_display}) are where orders rest forever. '${first_uncovered_terminal}' is accepted by no allocator guard and no pending guard design exists that could cover it, so escrowed funds reaching it can never leave. This must be proven impossible BEFORE publish — the graph and allocators freeze permanently.",
48
+ "mitigation": "Add an allocator whose guard accepts the terminal node (progress.current == '${first_uncovered_terminal}'), or add an outgoing edge so the node is no longer terminal.",
49
+ "stakeholders": ["merchant", "customer"]
50
+ },
51
+ {
52
+ "when": { "all": [ { "field": "terminal_count", "op": "gt", "value": 0 }, { "field": "first_uncovered_terminal", "op": "present" }, { "field": "pending_allocator_guard_count", "op": "gt", "value": 0 } ] },
53
+ "level": "medium",
54
+ "title": "Terminal node '${first_uncovered_terminal}' fund exit is declared but unverified",
55
+ "description": "Terminal nodes (${terminal_nodes_display}) are where orders rest forever. '${first_uncovered_terminal}' is accepted by no RESOLVED allocator guard; pending guard designs exist that may cover it, but until they are resolved into structured guards the coverage cannot be verified.",
56
+ "mitigation": "Resolve the pending guard designs (judgment layer / guardResolutions) so the allocator guard verifiably accepts '${first_uncovered_terminal}' before publish.",
57
+ "stakeholders": ["merchant", "customer"]
58
+ }
59
+ ]
60
+ },
61
+ {
62
+ "rule_id": "UNRESOLVED-GUARD-01",
63
+ "dimension": "composition",
64
+ "trigger": "A guard design requirement (pattern.* spec) is bound to a fund path or buy path but was never resolved into a structured guard — publishing now ships that path UNGUARDED.",
65
+ "cases": [
66
+ {
67
+ "when": { "field": "first_pending_fund_guard", "op": "present" },
68
+ "level": "critical",
69
+ "title": "Guard design '${first_pending_fund_guard}' is unresolved and bound to a fund path",
70
+ "description": "The allocator row or machine edge referencing this guard_key will carry NO guard at publish: the settlement/refund/forward it was designed to gate executes unconditionally. This is a publish-freeze blocker — the freeze gate refuses to go live with fund-path guards pending.",
71
+ "mitigation": "Resolve the guard design into a structured guard (guardResolutions with kind 'template' or 'inline'), or remove the binding.",
72
+ "stakeholders": ["merchant", "customer"]
73
+ },
74
+ {
75
+ "when": { "field": "first_pending_buy_guard", "op": "present" },
76
+ "level": "medium",
77
+ "title": "Buy-guard design '${first_pending_buy_guard}' is unresolved",
78
+ "description": "The service buy path was designed to screen purchasers, but the guard was never resolved; the service publishes without a buy_guard, so anyone can order.",
79
+ "mitigation": "Resolve the buy-guard design into a structured guard, or accept an unscreened buy path deliberately.",
80
+ "stakeholders": ["merchant"]
81
+ }
82
+ ]
83
+ },
84
+ {
85
+ "rule_id": "DUAL-RAIL-01",
86
+ "dimension": "fund_flow",
87
+ "trigger": "Refund-track guard does not read order.claimed_by — the dual rail (A-3) is deliberate on chain: alloc never reads claimed_by, compensate never touches the escrow. Double-pay prevention is a TEMPLATE convention; this rule is its only enforcement.",
88
+ "cases": [
89
+ {
90
+ "when": { "field": "first_refund_guard_missing_claimed_by", "op": "present" },
91
+ "level": "high",
92
+ "title": "Refund guard '${first_refund_guard_missing_claimed_by}' does not check claimed_by",
93
+ "description": "Chain semantics keep the allocation rail and the compensation rail independent (A-3, deliberate). A refund guard that does not assert claimed_by is none allows double payment: arbitration indemnity AND escrow refund.",
94
+ "mitigation": "Add a query on order.claimed_by (must be none) to the refund guard, or document an explicit double-compensation policy.",
95
+ "stakeholders": ["merchant"]
96
+ }
97
+ ]
98
+ },
99
+ {
100
+ "rule_id": "TIME-ANCHOR-01",
101
+ "dimension": "time_anchor",
102
+ "trigger": "A window guard anchors on a path-dependent time query (progress.current_time refreshes on every node entry) while the gated node is reachable by more than one path — detour nodes shift the settlement base (E-10: a buyer leaving a review postponed merchant settlement by the review window).",
103
+ "cases": [
104
+ {
105
+ "when": { "all": [ { "field": "first_path_dependent_anchor", "op": "present" }, { "field": "path_sensitive", "op": "truthy" } ] },
106
+ "level": "high",
107
+ "title": "Window guard '${first_path_dependent_anchor}' anchors on a path-dependent time",
108
+ "description": "progress.current_time is the time of ENTERING the current node, not of any business event. The gated node has multiple inbound paths, so an optional detour (e.g. a review node) silently extends the window. This is an exploitable merchant-side funds-lockup.",
109
+ "mitigation": "Anchor the window on order.time (path-independent), or restructure the machine so the anchored node has exactly one inbound path.",
110
+ "stakeholders": ["merchant", "customer"]
111
+ },
112
+ {
113
+ "when": { "field": "first_path_dependent_anchor", "op": "present" },
114
+ "level": "medium",
115
+ "title": "Window guard '${first_path_dependent_anchor}' uses a path-dependent time anchor",
116
+ "description": "The anchor is currently unambiguous (single-path), but any later template edit adding a bypass node silently shifts the window base.",
117
+ "mitigation": "Prefer order.time for settlement/refund windows.",
118
+ "stakeholders": ["merchant"]
119
+ }
120
+ ]
121
+ },
122
+ {
123
+ "rule_id": "WRITE-ONLY-01",
124
+ "dimension": "field_capability",
125
+ "trigger": "Plan sets a write-only field (only the native Guard engine ever reads it) with no guard query covering it — the enforcement the merchant believes in does not exist (A-1: customer_required was collected but never enforced on the buy path).",
126
+ "cases": [
127
+ {
128
+ "when": { "field": "first_write_only_unenforced_field", "op": "present" },
129
+ "level": "critical",
130
+ "title": "Field '${first_write_only_unenforced_field}' is set but never enforced",
131
+ "description": "Write-only fields are read ONLY by guard queries. Setting '${first_write_only_unenforced_field}' without a covering guard query creates the illusion of enforcement: the buy path will not check it.",
132
+ "mitigation": "Add a guard whose table queries the field (see chain-semantics WRITE_ONLY_QUERY_PREFIXES), bound to the enforcing forward or buy path — or remove the field.",
133
+ "stakeholders": ["merchant", "customer"]
134
+ }
135
+ ]
136
+ },
137
+ {
138
+ "rule_id": "HOST-GRAPH-01",
139
+ "dimension": "composition",
140
+ "trigger": "A guard exists but is bound to no host, or a guard queries repository data while the plan has no repository — the TRAILHAVEN B-2 failure: the Repository object and the third guard were silently dropped, leaving the shipping-evidence gate with nothing to check.",
141
+ "cases": [
142
+ {
143
+ "when": { "field": "first_repo_query_without_repository", "op": "present" },
144
+ "level": "critical",
145
+ "title": "Guard '${first_repo_query_without_repository}' queries a repository the plan never creates",
146
+ "description": "The guard reads repository.* data, but no repository step exists in the plan. On chain the query aborts on missing data — the forward it gates can never pass.",
147
+ "mitigation": "Add the repository create step and bind the guard to it, or remove the repository queries.",
148
+ "stakeholders": ["merchant"]
149
+ },
150
+ {
151
+ "when": { "field": "first_orphan_guard", "op": "present" },
152
+ "level": "high",
153
+ "title": "Guard '${first_orphan_guard}' is bound to no host",
154
+ "description": "The guard is created but never referenced by any machine forward, service buy_guard, allocator row, repository policy, arbitration, or reward. It is dead weight — usually a sign that a host binding was silently dropped during composition.",
155
+ "mitigation": "Wire the guard into its intended host binding, or delete the step.",
156
+ "stakeholders": ["merchant"]
157
+ }
158
+ ]
159
+ }
160
+ ]
161
+ }
@@ -0,0 +1,12 @@
1
+ export interface SemanticFact {
2
+ fact_id: string;
3
+ subject: string;
4
+ authoritative: string;
5
+ falsehoods: string[];
6
+ harm: string;
7
+ }
8
+ export declare const SEMANTIC_FACTS: SemanticFact[];
9
+ export declare function scanContent(filePath: string, content: string): Array<{
10
+ fact_id: string;
11
+ phrasing: string;
12
+ }>;
@@ -0,0 +1 @@
1
+ export const SEMANTIC_FACTS=[{'fact_id':'FACT-ALLOC-01','subject':'Allocator\x20guard-matching\x20and\x20firing\x20semantics','authoritative':'A\x20distribution\x20call\x20submits\x20ONE\x20guard\x20address;\x20every\x20allocator\x20whose\x20bound\x20guard\x20equals\x20the\x20submitted\x20address\x20fires,\x20in\x20vector\x20order,\x20each\x20against\x20the\x20balance\x20left\x20by\x20the\x20previous\x20(allocation.move:366-368\x20—\x20the\x20loop\x20has\x20no\x20early\x20exit).\x20'+'Allocators\x20with\x20DIFFERENT\x20guards\x20are\x20alternative\x20paths\x20selected\x20by\x20the\x20caller\x27s\x20guard\x20choice,\x20not\x20by\x20list\x20position.\x20Mutual\x20exclusion\x20must\x20be\x20expressed\x20inside\x20the\x20guard\x20conditions.','falsehoods':['first-match-wins','first\x20Allocator\x20whose\x20Guard\x20returns\x20true','first\x20allocator\x20whose\x20guard\x20passes\x20wins','the\x20first\x20Allocator\x20whose\x20Guard\x20returns\x20true\x20wins','first-match\x20allocator\x20semantics','only\x20the\x20first\x20one','only\x20the\x20first\x20one\x20matches','duplicate\x20addresses\x20only\x20match\x20the\x20first','the\x20first\x20one\x20wins','the\x20first\x20allocator\x20whose\x20guard'],'harm':'Templates\x20built\x20on\x20\x27first-match\x27\x20semantics\x20silently\x20stack\x20distributions\x20or\x20leave\x20refund\x20paths\x20unreachable,\x20because\x20the\x20contract\x20fires\x20ALL\x20allocators\x20bound\x20to\x20the\x20submitted\x20guard.'},{'fact_id':'FACT-ALLOC-02','subject':'Surplus\x20semantics\x20—\x20drains\x20the\x20entire\x20remaining\x20pool','authoritative':'Surplus\x20mode\x20distributes\x20the\x20ENTIRE\x20remaining\x20balance\x20of\x20the\x20order\x27s\x20allocation\x20pool,\x20not\x20just\x20the\x20amount\x20earmarked\x20for\x20a\x20specific\x20track.\x20If\x20a\x20refund-path\x20allocator\x20uses\x20Surplus\x20while\x20the\x20settlement\x20path\x20has\x20unpaid\x20fixed-amount\x20legs,\x20the\x20Surplus\x20takes\x20everything,\x20including\x20the\x20settlement\x20money.','falsehoods':['Surplus\x20takes\x20only\x20what\x20is\x20left','Surplus\x20returns\x20the\x20remainder\x20after\x20other\x20tracks','Surplus\x20is\x20safe\x20because\x20it\x20only\x20takes\x20leftovers'],'harm':'A\x20refund/deposit\x20allocator\x20using\x20Surplus\x20will\x20over-refund\x20the\x20buyer\x20and\x20starve\x20the\x20merchant\x20when\x20the\x20settlement\x20track\x20has\x20not\x20yet\x20fired\x20(ziroom\x20T5:\x20585/525\x20WOW\x20fully\x20refunded,\x20merchant\x20received\x200).'},{'fact_id':'FACT-ALLOC-03','subject':'Allocator\x20fix\x20semantics\x20—\x20computed\x20Amount-leg\x20total,\x20PAID,\x20never\x20a\x20holdback','authoritative':'fix\x20is\x20not\x20a\x20settable\x20parameter:\x20the\x20allocator-registration\x20call\x20computes\x20it\x20internally\x20as\x20the\x20sum\x20of\x20the\x20allocator\x27s\x20Amount-mode\x20legs\x20(allocation.move:399\x20fix:\x20total_amount),\x20and\x20alloc()\x20PAYS\x20every\x20Amount\x20leg\x20(allocation.move:540\x20balance::split)\x20while\x20subtracting\x20fix\x20only\x20from\x20the\x20net-Rate\x20base\x20(allocation.move:485\x20total_rates\x20=\x20balance\x20-\x20fix).\x20A\x20pure-Rate\x20allocator\x20therefore\x20has\x20fix=0\x20and\x20drains\x20100%\x20of\x20the\x20pool;\x20adding\x20Amount\x20legs\x20raises\x20outflow,\x20it\x20never\x20withholds.\x20The\x20allocator-registration\x20call\x20surface\x20takes\x20only\x20(guard,\x20sharing,\x20max)\x20—\x20no\x20fix\x20argument\x20at\x20all\x20—\x20so\x20a\x20template-side\x20fix\x20value\x20is\x20silently\x20dropped\x20before\x20the\x20transaction\x20is\x20built.\x20The\x20chain\x20has\x20NO\x20holdback/reserve\x20primitive;\x20the\x20only\x20way\x20to\x20leave\x20funds\x20in\x20a\x20pool\x20is\x20to\x20not\x20distribute\x20them\x20(no\x20allocator\x20fires,\x20or\x20a\x20max-capped\x20Surplus\x20stops\x20at\x20the\x20cap).','falsehoods':['the\x20reserve\x20never\x20leaves','reserve\x20never\x20leaves\x20the\x20pool','the\x20reserve\x20stays\x20in\x20the\x20pool','reserve\x20physically\x20stays\x20in\x20the\x20pool','the\x20fix\x20retains\x20the\x20reserve','fix-retained\x20claim\x20reserve','leaves\x20the\x20reserve\x20in\x20the\x20pool','fix=reserve','only\x20on-chain\x20holdback'],'harm':'Templates\x20that\x20declare\x20fix=reserve\x20ship\x20a\x20payout\x20rail\x20that\x20drains\x20100%\x20of\x20the\x20escrow\x20at\x20the\x20first\x20release;\x20the\x20claim/refund\x20legs\x20behind\x20it\x20are\x20dead\x20by\x20construction\x20and\x20the\x20money\x20is\x20gone\x20(airbnb\x20mainnet\x20audit\x202026-10-07:\x20ABNB_STAY_SERVICE\x20allocators[0]\x20fix=\x220\x22,\x20full\x20drain\x20at\x20payout_24h).'},{'fact_id':'FACT-GUARD-01','subject':'GuardIdentifier\x20recipient\x20+\x20for_object\x20binding','authoritative':'ERECIPIENT_NOT_FOR_OBJECT\x20is\x20enforced\x20ONLY\x20when\x20the\x20recipient\x20kind\x20is\x20GuardIdentifier\x20AND\x20payment_info.for_object\x20is\x20Some(binding).\x20Pure\x20Entity\x20recipients\x20and\x20unbound\x20GuardIdentifier\x20legs\x20skip\x20this\x20check\x20entirely\x20—\x20the\x20tooling\x20layer\x20(scanner,\x20template\x20compiler)\x20must\x20flag\x20them\x20(allocation.move:434-443).','falsehoods':['the\x20contract\x20checks\x20all\x20recipients\x20against\x20for_object','GuardIdentifier\x20recipients\x20are\x20always\x20safe','the\x20contract\x20validates\x20every\x20recipient\x20binding'],'harm':'An\x20unbound\x20GuardIdentifier\x20or\x20pure\x20Entity\x20recipient\x20can\x20redirect\x20funds\x20to\x20the\x20deploying\x20account\x20or\x20an\x20arbitrary\x20address\x20without\x20any\x20on-chain\x20error.'},{'fact_id':'FACT-MACHINE-01','subject':'Machine\x20edge\x20\x27from\x27\x20semantics','authoritative':'A\x20machine\x20edge\x20declares\x20a\x20single\x20source\x20node\x20in\x20its\x20\x27from\x27\x20field.\x20Multiple\x20edges\x20from\x20the\x20same\x20source\x20node\x20are\x20allowed\x20and\x20represent\x20alternative\x20paths;\x20they\x20are\x20NOT\x20merged.\x20An\x20edge\x20without\x20\x27from\x27\x20is\x20invalid\x20in\x20the\x20new\x20topology\x20format\x20and\x20causes\x20compile-time\x20blockers.','falsehoods':['edges\x20without\x20from\x20are\x20auto-derived','missing\x20from\x20means\x20any\x20node','from\x20is\x20optional\x20for\x20backward\x20compatibility'],'harm':'Old\x20flat\x20machine_shape\x20without\x20from\x20fields\x20compiles\x20into\x20an\x20incomplete\x20topology,\x20leaving\x20nodes\x20unreachable\x20and\x20fund\x20paths\x20missing.'},{'fact_id':'FACT-ENTITY-01','subject':'Entity\x20recipient\x20fallback\x20to\x20deployer','authoritative':'When\x20an\x20Entity\x20recipient\x27s\x20name_or_address\x20is\x20empty,\x20omitted,\x20or\x20resolves\x20to\x20the\x20deploying\x20account,\x20the\x20share\x20is\x20paid\x20to\x20the\x20DEPLOYING\x20ACCOUNT\x20(allocation.move:434-443).\x20This\x20is\x20a\x20silent\x20fallback\x20—\x20no\x20error\x20is\x20raised.','falsehoods':['empty\x20Entity\x20names\x20are\x20rejected','the\x20contract\x20validates\x20Entity\x20recipients','an\x20unnamed\x20Entity\x20recipient\x20causes\x20an\x20abort'],'harm':'A\x20template\x20with\x20a\x20placeholder\x20Entity\x20recipient\x20silently\x20pays\x20the\x20share\x20to\x20whoever\x20deploys\x20the\x20service,\x20which\x20is\x20almost\x20never\x20the\x20intended\x20recipient.'}];export function scanContent(a,b){const c=[],d=b['toLowerCase']();for(const e of SEMANTIC_FACTS){for(const f of e['falsehoods']){const g=f['toLowerCase']();let h=0x0;while((h=d['indexOf'](g,h))!==-0x1){const i=d['slice'](Math['max'](0x0,h-0xc),h),j=/(?:^|[\s"'])not\s+$/['test'](i)||/never\s+$/['test'](i);!j&&c['push']({'fact_id':e['fact_id'],'phrasing':f}),h+=g['length'];}}}return c;}
@@ -19,9 +19,11 @@ export interface ArchetypeTemplate {
19
19
  user_inputs: string[];
20
20
  key_risk: string;
21
21
  build_notes: string[];
22
+ payment_model?: "milestone" | "subscription" | "escrow";
23
+ arbitration_mode?: "none" | "merchant_pick" | "mutual_pick" | "third_party";
22
24
  time_anchors?: Record<string, string>;
23
25
  }
24
- export declare const BUILTIN_TEMPLATES_VERSION = 19;
26
+ export declare const BUILTIN_TEMPLATES_VERSION = 29;
25
27
  export declare const BUILTIN_TEMPLATES: ArchetypeTemplate[];
26
28
  export declare const TEMPLATE_KEYWORDS: Record<string, string[]>;
27
29
  export declare function getBuiltinTemplate(id: string): ArchetypeTemplate | undefined;