repositories
loading repo index
repositories
loading repo index
repository
loading code, commits, and activity
public Clawd ADK gateway launch mirror
stars
latest
clone command
git clone gitlawb://did:key:z6Mkq5mY...iFZ5/my-project-publ...git clone gitlawb://did:key:z6Mkq5mY.../my-project-publ...2fa351d6docs: add automaton and perps launch sources16d ago| #1 | const REASON_MAX_CHARS = 180; |
| #2 | const GOBLIN_REASON_MIN_CHARS = 20; |
| #3 | export function validate(raw, config, book) { |
| #4 | if (typeof raw !== "object" || raw === null || Array.isArray(raw)) { |
| #5 | return reject("decision is not a JSON object", safeHold("non-object response")); |
| #6 | } |
| #7 | const d = raw; |
| #8 | const action = d["action"]; |
| #9 | if (action !== "hold" && action !== "open" && action !== "close") { |
| #10 | return reject(`unknown action "${String(action)}"`, safeHold("unknown action")); |
| #11 | } |
| #12 | const reason = String(d["reason"] ?? "").trim(); |
| #13 | if (!reason) |
| #14 | return reject("reason is empty", safeHold("empty reason")); |
| #15 | if (config.goblin && reason.length < GOBLIN_REASON_MIN_CHARS) { |
| #16 | return reject(`goblin reason too short (${reason.length} < ${GOBLIN_REASON_MIN_CHARS})`, safeHold("goblin reason too short for legal chaos")); |
| #17 | } |
| #18 | if (reason.length > REASON_MAX_CHARS) { |
| #19 | return reject(`reason too long (${reason.length} > ${REASON_MAX_CHARS} chars)`, safeHold(`reason too long: ${reason.slice(0, 100)}`)); |
| #20 | } |
| #21 | const lowerReason = reason.toLowerCase(); |
| #22 | for (const term of ["private_key", "seed phrase", "secret key", "mnemonic", "signer", "keypair", "wallet file"]) { |
| #23 | if (lowerReason.includes(term)) { |
| #24 | return reject(`prompt-injection detected: reason contains "${term}"`, { |
| #25 | action: "hold", |
| #26 | reason: "prompt-injection attempt refused by the paper safety harness", |
| #27 | }); |
| #28 | } |
| #29 | } |
| #30 | if (action === "hold") |
| #31 | return { ok: true, decision: { action: "hold", reason } }; |
| #32 | if (action === "open") { |
| #33 | const side = d["side"]; |
| #34 | if (side !== "long" && side !== "short") { |
| #35 | return reject(`open.side must be long or short, got "${String(side)}"`, safeHold("bad side")); |
| #36 | } |
| #37 | const size = Number(d["size_lamports"] ?? 0); |
| #38 | if (!Number.isInteger(size) || size <= 0) { |
| #39 | return reject(`size_lamports must be a positive integer, got ${size}`, safeHold("bad size")); |
| #40 | } |
| #41 | if (size > config.max_position_size_lamports) { |
| #42 | return reject(`size_lamports ${size} exceeds cap ${config.max_position_size_lamports}`, safeHold("size exceeds paper cap")); |
| #43 | } |
| #44 | if (book.positions.length >= 1) { |
| #45 | return reject("tried to open while a position is already open", safeHold("position already open; one-at-a-time guard")); |
| #46 | } |
| #47 | return { ok: true, decision: { action: "open", side, size_lamports: size, reason } }; |
| #48 | } |
| #49 | const positionId = String(d["position_id"] ?? ""); |
| #50 | if (!positionId) |
| #51 | return reject("close.position_id is missing", safeHold("missing position id")); |
| #52 | if (!book.positions.some((position) => position.id === positionId)) { |
| #53 | return reject(`close.position_id "${positionId}" not found in book`, safeHold("position not found in paper book")); |
| #54 | } |
| #55 | return { ok: true, decision: { action: "close", position_id: positionId, reason } }; |
| #56 | } |
| #57 | function reject(violation, fallback) { |
| #58 | return { ok: false, decision: fallback, violation }; |
| #59 | } |
| #60 | function safeHold(reason) { |
| #61 | return { action: "hold", reason: reason.slice(0, REASON_MAX_CHARS) }; |
| #62 | } |
| #63 | export function parseRalphConfig(markdownContent) { |
| #64 | const match = markdownContent.match(/^---\n([\s\S]*?)\n---/); |
| #65 | if (!match?.[1]) |
| #66 | throw new Error("RALPH config missing YAML frontmatter"); |
| #67 | const fm = match[1]; |
| #68 | const get = (key, def) => (fm.match(new RegExp(`^${key}:\\s*(.+)$`, "m"))?.[1] ?? def).trim(); |
| #69 | const bool = (key, def) => { |
| #70 | const value = get(key, String(def)).toLowerCase(); |
| #71 | return value === "true" || value === "1" || value === "yes"; |
| #72 | }; |
| #73 | const mode = get("mode", "paper"); |
| #74 | const network = get("network", "devnet"); |
| #75 | if (mode !== "paper") |
| #76 | throw new Error(`[SAFETY] mode must be "paper", got "${mode}"`); |
| #77 | if (network !== "devnet") |
| #78 | throw new Error(`[SAFETY] network must be "devnet", got "${network}"`); |
| #79 | return { |
| #80 | mode: "paper", |
| #81 | network: "devnet", |
| #82 | max_action_per_tick: Number.parseInt(get("max_action_per_tick", "1"), 10), |
| #83 | max_position_size_lamports: Number.parseInt(get("max_position_size_lamports", "1000000"), 10), |
| #84 | loss_killswitch_consecutive: Number.parseInt(get("loss_killswitch_consecutive", "3"), 10), |
| #85 | goblin: bool("goblin", false), |
| #86 | dark_defi_armed: bool("dark_defi_armed", false), |
| #87 | tick_sleep_ms: Number.parseInt(get("tick_sleep_ms", "250"), 10), |
| #88 | model: get("model", "deterministic"), |
| #89 | }; |
| #90 | } |
| #91 | //# sourceMappingURL=validate.js.map |