@oracle-agent/oracle 0.35.20 → 0.35.22

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 (59) hide show
  1. package/README.md +24 -1
  2. package/dist/agent-lifecycle.mjs +3 -0
  3. package/dist/assets/skills/oracle-desk-product/SKILL.md +23 -17
  4. package/dist/assets/skills/oracle-mcp-install/SKILL.md +17 -22
  5. package/dist/assets/skills/protocol-api-key-integration/SKILL.md +14 -11
  6. package/dist/assets/skills/self-custodial-onchain-execution/SKILL.md +14 -13
  7. package/dist/assets/skills/trade-loop-circuit-breaker/SKILL.md +1 -1
  8. package/dist/bin/desk-server.mjs +36 -36
  9. package/dist/bin/oracle-data-mcp.mjs +17 -17
  10. package/dist/bin/oracle-mev-arb-shadow.mjs +4 -4
  11. package/dist/bin/oracle-mev-shadow.mjs +6 -6
  12. package/dist/bin/oracle-public-server.mjs +5 -5
  13. package/dist/bin/oracle-route.mjs +11 -11
  14. package/dist/bin/oracle-scan.mjs +15 -15
  15. package/dist/bin/oracle.mjs +19 -10
  16. package/dist/cli/commands/chat.mjs +200 -200
  17. package/dist/cli/commands/context.mjs +16 -0
  18. package/dist/cli/commands/doctor.mjs +7 -7
  19. package/dist/cli/commands/eval.mjs +113 -113
  20. package/dist/cli/commands/extension-policy.mjs +1 -1
  21. package/dist/cli/commands/governance-prepare.mjs +5 -0
  22. package/dist/cli/commands/lifecycle.mjs +17 -0
  23. package/dist/cli/commands/model.mjs +204 -204
  24. package/dist/cli/commands/portfolio-risk.mjs +4 -0
  25. package/dist/cli/commands/position-health.mjs +4 -0
  26. package/dist/cli/commands/quickstart.mjs +5 -5
  27. package/dist/cli/commands/safety-intelligence.mjs +4 -0
  28. package/dist/cli/commands/sign.mjs +1 -1
  29. package/dist/cli/commands/signer.mjs +22 -22
  30. package/dist/cli/commands/snipe.mjs +4 -4
  31. package/dist/cli/commands/swap.mjs +13 -13
  32. package/dist/cli/commands/tax-lots.mjs +4 -0
  33. package/dist/cli/commands/venues.mjs +2 -2
  34. package/dist/context-graph.mjs +3 -0
  35. package/dist/data/desk-data.mjs +34 -34
  36. package/dist/data/providers/lp-destination-discovery.mjs +6 -0
  37. package/dist/data/providers/lp-liquidity-ranker.mjs +1 -0
  38. package/dist/index.mjs +50 -48
  39. package/dist/local-signer/anvil-attestation-worker.mjs +1 -1
  40. package/dist/local-signer/runtime.mjs +1 -1
  41. package/dist/router/index.mjs +3 -3
  42. package/dist/router/prepare-bridge.mjs +2 -2
  43. package/dist/scanner/index.mjs +4 -4
  44. package/dist/tenant-rbac.mjs +1 -1
  45. package/dist/workflow-telemetry.mjs +1 -1
  46. package/package.json +5 -1
  47. package/public/eval-report.json +2 -2
  48. package/public/oracle-splash/brand/liquidlaunch.webp +0 -0
  49. package/public/oracle-splash/brand/markets.webp +0 -0
  50. package/public/oracle-splash/brand/motion.webp +0 -0
  51. package/public/oracle-splash/brand/nest.webp +0 -0
  52. package/public/oracle-splash/brand/outcome.webp +0 -0
  53. package/public/oracle-splash/brand/silhouette.webp +0 -0
  54. package/public/oracle-splash/brand/tradexyz.webp +0 -0
  55. package/public/oracle-splash/index.html +9 -9
  56. package/server.json +2 -2
  57. package/dist/assets/skills/oracle-best-execution.md +0 -27
  58. package/dist/assets/skills/oracle-desk.md +0 -27
  59. package/dist/assets/skills/oracle-receipts.md +0 -29
package/README.md CHANGED
@@ -122,6 +122,29 @@ Three properties define it:
122
122
 
123
123
  Default posture is `DISARMED`.
124
124
 
125
+ ### Context and lifecycle governance
126
+
127
+ Oracle includes the two organization-scale governance layers directly in the
128
+ public self-hosted package:
129
+
130
+ - `@oracle-agent/oracle/context-graph`: tenant/domain-scoped nodes and edges with
131
+ mandatory caller-asserted provenance explicitly marked `unverified`, evidence
132
+ hashes, freshness TTLs, stale exclusion, trusted domain assignments and RBAC,
133
+ immutable revision history, private atomic storage, and a hash chain that
134
+ detects corruption and un-rehashed edits. The local hash chain is not an
135
+ external anti-rollback anchor against a same-UID writer.
136
+ - `@oracle-agent/oracle/agent-lifecycle`: inventory for agents, tools, workflows,
137
+ first-class expiring credentials, and external credential references, with
138
+ last-use/eval health, credential expiry,
139
+ inactivity retirement, regression quarantine, and owner-reviewed reactivation.
140
+
141
+ Use `oracle context --help` and `oracle lifecycle --help`, or import the APIs.
142
+ These are local governance surfaces: they add no public HTTP endpoint, expose no
143
+ credential-value field, reject recognized secret-shaped input, and grant no signing authority. Public self-hosted and
144
+ operator/admin installations use the same capabilities, safety checks, and
145
+ execution flow; operator fee configuration is the product-rate difference, not
146
+ a capability fork.
147
+
125
148
  ## Why it's different
126
149
 
127
150
  The differentiators are **policy, custody, and receipts** — not a smarter chat
@@ -252,7 +275,7 @@ known answer. This matters: the canonical QuoterV2 address also has bytecode on
252
275
  Base, but does not price that chain's pairs. A codesize check would have
253
276
  allowlisted the wrong contract.
254
277
 
255
- **122 provider modules** in the read/quote catalog, covering **220 unique
278
+ **125 provider modules** and **537 operations** in the read/quote catalog, covering **220 unique
256
279
  protocols/venues** across EVM, Solana, and Bitcoin (115 EVM + cross-chain, 98
257
280
  Solana venues backed by 101 verified Jupiter program IDs, 6 Bitcoin surfaces).
258
281
  One module can cover many protocols: the Jupiter module alone routes 98 Solana
@@ -0,0 +1,3 @@
1
+ import{createHash as je}from"node:crypto";var ze=1,Le=1,$e=1;var qe=Object.freeze(["owner","operator","agent","viewer"]),Me=Object.freeze(["tenant","config","policy","grant","workflow","trade","receipt","audit","telemetry","context","lifecycle"]),de=Object.freeze({read:"read",create:"create",update:"update",delete:"delete",manage:"manage",prepare:"prepare",cancel:"cancel",export:"export",approve:"approve"}),Ye=Object.freeze(["owner"]),O=Object.freeze({ALLOW_ROLE_PERMISSION:"allow.role_permission",DENY_MISSING_CONTEXT:"deny.missing_context",DENY_INVALID_CONTEXT:"deny.invalid_context",DENY_INVALID_REQUEST:"deny.invalid_request",DENY_UNKNOWN_RESOURCE:"deny.unknown_resource",DENY_UNKNOWN_ACTION:"deny.unknown_action",DENY_UNBOUND_OBJECT:"deny.unbound_object",DENY_TENANT_MISMATCH:"deny.tenant_mismatch",DENY_RESOURCE_MISMATCH:"deny.resource_mismatch",DENY_ROLE_PERMISSION:"deny.role_permission",DENY_POLICY_NOT_APPROVER:"deny.policy_not_approver"}),Pe=new Set(qe),Z=new Set(Me),He=new Set(Object.values(de)),Ue=new Set(["user","service","local-owner"]),Ve=new Set(["verified-session","service-credential","desktop-session","owner-arm-token","local-owner"]),B=/^[A-Za-z0-9][A-Za-z0-9._:@-]{0,127}$/,le="tob1",Fe=new WeakSet;var ue=Object.freeze({owner:Object.freeze(["tenant:read","tenant:manage","config:read","config:manage","policy:read","policy:manage","grant:read","grant:create","grant:update","grant:delete","grant:manage","workflow:read","workflow:create","workflow:update","workflow:delete","workflow:cancel","workflow:approve","trade:read","trade:prepare","receipt:read","receipt:export","audit:read","audit:export","telemetry:read","telemetry:create","telemetry:export","context:read","context:create","context:update","context:delete","context:manage","context:export","lifecycle:read","lifecycle:create","lifecycle:update","lifecycle:delete","lifecycle:manage","lifecycle:approve","lifecycle:export"]),operator:Object.freeze(["tenant:read","config:read","policy:read","grant:read","workflow:read","workflow:create","workflow:update","workflow:cancel","trade:read","trade:prepare","receipt:read","audit:read","telemetry:read","telemetry:create","context:read","context:create","context:update","context:delete","lifecycle:read","lifecycle:create","lifecycle:update","lifecycle:manage"]),agent:Object.freeze(["tenant:read","config:read","policy:read","grant:read","workflow:read","workflow:create","workflow:update","trade:read","trade:prepare","receipt:read","telemetry:create","context:read","context:create","context:update","lifecycle:read"]),viewer:Object.freeze(["tenant:read","config:read","grant:read","workflow:read","trade:read","receipt:read","audit:read","telemetry:read","context:read","lifecycle:read"])}),Ot=Object.freeze(Object.fromEntries(Object.entries(ue).map(([e,t])=>[e,Object.freeze([...t])]))),T=class extends Error{constructor(t,n="invalid-context"){super(t),this.name="TenantContextError",this.code=n}};function se(e){return!!e&&typeof e=="object"&&!Array.isArray(e)&&Object.getPrototypeOf(e)===Object.prototype}function W(e){if(e&&typeof e=="object"&&!Object.isFrozen(e)){Object.freeze(e);for(let t of Object.values(e))W(t)}return e}function G(e){return e===null||typeof e!="object"?JSON.stringify(e):Array.isArray(e)?`[${e.map(G).join(",")}]`:`{${Object.keys(e).sort().map(t=>`${JSON.stringify(t)}:${G(e[t])}`).join(",")}}`}function fe(e){return je("sha256").update(G(e)).digest("hex")}function Je(e,t){return e.length===t.length&&e.every((n,r)=>n===t[r])}function Be(e){if(!se(e)||e.schemaVersion!==ze||!B.test(e.tenantId||"")||typeof e.localCompatibility!="boolean")return!1;let t=e.actor;if(!se(t)||t.schemaVersion!==Le||!B.test(t.actorId||"")||!Ue.has(t.actorType)||!Ve.has(t.authSource)||!Array.isArray(t.roles)||t.roles.length===0)return!1;let n=[...new Set(t.roles)].sort();return n.every(r=>Pe.has(r))&&Je(n,t.roles)}function M(e){return Fe.has(e)&&Be(e)}function Ge(e){if(!M(e))throw new T("a resolved tenant context is required","unresolved-context");return e}function Y(e){return Buffer.from(e,"utf8").toString("base64url")}function J(e,t){if(!e||!/^[A-Za-z0-9_-]+$/.test(e))throw new T(`${t} binding is invalid`,"invalid-object-binding");let n=Buffer.from(e,"base64url").toString("utf8");if(!n||Y(n)!==e)throw new T(`${t} binding is non-canonical`,"invalid-object-binding");return n}function pe(e){let t=String(e??"").trim();if(!t||t.length>256||/[\u0000-\u001f\u007f]/.test(t))throw new T("objectId is invalid","invalid-object-id");return t}function ye(e,t,n){Ge(e);let r=String(t??"").trim();if(!Z.has(r))throw new T("resource is unknown","unknown-resource");let i=pe(n);return`${le}.${Y(e.tenantId)}.${Y(r)}.${Y(i)}`}function Ze(e){let t=String(e??""),n=t.split(".");if(n.length!==4||n[0]!==le)throw new T("tenant-bound object id required","unbound-object");let r=J(n[1],"tenant"),i=J(n[2],"resource"),s=J(n[3],"object");if(!B.test(r)||!Z.has(i))throw new T("tenant-bound object id is invalid","invalid-object-binding");return pe(s),W({schemaVersion:1,tenantId:r,resource:i,objectId:s,boundId:t})}function We(e){if(typeof e!="string"||!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/.test(e))return null;let t=Date.parse(e);return Number.isFinite(t)&&new Date(t).toISOString()===e?e:null}function ce(e){let t={...e,decisionId:fe(e)};return W(t)}function Ke({context:e,resource:t,action:n,objectId:r=null,decidedAt:i}={}){let s=We(i),p={schemaVersion:$e,recordType:"tenant.access-decision",decidedAt:s,tenantId:M(e)?e.tenantId:null,actor:M(e)?{actorId:e.actor.actorId,actorType:e.actor.actorType,authSource:e.actor.authSource,roles:[...e.actor.roles]}:null,request:{resource:typeof t=="string"?t:null,action:typeof n=="string"?n:null,objectRefHash:r==null?null:fe(String(r))}},f=v=>ce({...p,allowed:!1,outcome:"deny",reason:v});if(e==null)return f(O.DENY_MISSING_CONTEXT);if(!M(e))return f(O.DENY_INVALID_CONTEXT);if(!s)return f(O.DENY_INVALID_REQUEST);if(!Z.has(t))return f(O.DENY_UNKNOWN_RESOURCE);if(!He.has(n))return f(O.DENY_UNKNOWN_ACTION);if(r!=null){let v;try{v=Ze(r)}catch{return f(O.DENY_UNBOUND_OBJECT)}if(v.tenantId!==e.tenantId)return f(O.DENY_TENANT_MISMATCH);if(v.resource!==t)return f(O.DENY_RESOURCE_MISMATCH)}if(n===de.approve&&!e.actor.roles.some(S=>Ye.includes(S)))return f(O.DENY_ROLE_PERMISSION);let b=`${t}:${n}`,N=e.actor.roles.some(v=>ue[v]?.includes(b));return ce({...p,allowed:N,outcome:N?"allow":"deny",reason:N?O.ALLOW_ROLE_PERMISSION:O.DENY_ROLE_PERMISSION})}function he(e){let t=Ke(e);if(!t.allowed){let n=new T(`tenant access denied: ${t.reason}`,"access-denied");throw n.decision=t,n}return t}import{createHash as Xe,randomUUID as Qe}from"node:crypto";import{chmodSync as et,closeSync as H,constants as z,existsSync as K,fsyncSync as X,lstatSync as me,mkdirSync as we,openSync as Q,readFileSync as tt,renameSync as nt,unlinkSync as ee,writeFileSync as Ae}from"node:fs";import{dirname as ve,resolve as ge}from"node:path";function te(e){return e===null||typeof e!="object"?JSON.stringify(e):Array.isArray(e)?`[${e.map(te).join(",")}]`:`{${Object.keys(e).sort().map(t=>`${JSON.stringify(t)}:${te(e[t])}`).join(",")}}`}function k(e){return Xe("sha256").update(te(e)).digest("hex")}function _(e){if(e&&typeof e=="object"&&!Object.isFrozen(e)){Object.freeze(e);for(let t of Object.values(e))_(t)}return e}function rt(e,t,n){let r=ge(e),i=ve(r);if(we(i,{recursive:!0,mode:448}),K(r)){let p=me(r);if(!p.isFile()||p.isSymbolicLink())throw n("state path must be a regular non-symlink file","unsafe-file")}let s=`${r}.${Qe()}.tmp`;try{Ae(s,`${JSON.stringify(t)}
2
+ `,{encoding:"utf8",mode:384,flag:"wx"});let p=Q(s,z.O_RDONLY);try{X(p)}finally{H(p)}nt(s,r),et(r,384);let f;try{f=Q(i,z.O_RDONLY),X(f)}finally{f!==void 0&&H(f)}}catch(p){try{K(s)&&ee(s)}catch{}throw p}}function ot(e,t,n){if(!e||typeof e!="object"||Array.isArray(e))throw n("state must be an object","integrity-failed");if(e.schemaVersion!==t||!Array.isArray(e.events))throw n("state schema is invalid","integrity-failed");let r=null;for(let p=0;p<e.events.length;p+=1){let f=e.events[p];if(!f||typeof f!="object"||Array.isArray(f))throw n("event is invalid","integrity-failed");if(f.record&&typeof f.record.recordHash=="string"){let{recordHash:v,...S}=f.record;if(k(S)!==v)throw n("record hash integrity failed; state may be tampered","integrity-failed")}let{eventHash:b,...N}=f;if(f.sequence!==p+1||f.previousEventHash!==r||k(N)!==b)throw n("event hash chain integrity failed; state may be tampered","integrity-failed");r=b}let{stateHash:i,...s}=e;if(k(s)!==i)throw n("state hash integrity failed; state may be tampered","integrity-failed");return e}function P(e,t,n){if(!K(e)){let s={schemaVersion:t,events:[]};return{...s,stateHash:k(s)}}let r=me(e);if(!r.isFile()||r.isSymbolicLink())throw n("state path must be a regular non-symlink file","unsafe-file");if((r.mode&511)!==384)throw n("state file permissions must be 0600","unsafe-permissions");let i;try{i=JSON.parse(tt(e,"utf8"))}catch{throw n("state JSON is malformed","integrity-failed")}return ot(i,t,n)}function it(e,t,n){let r=`${e}.lock`,i;try{i=Q(r,z.O_CREAT|z.O_EXCL|z.O_WRONLY,384),Ae(i,`${JSON.stringify({pid:process.pid,createdAt:new Date().toISOString()})}
3
+ `,"utf8"),X(i)}catch(s){if(i!==void 0){H(i);try{ee(r)}catch{}}throw s?.code==="EEXIST"?t("state is locked by another writer","state-locked"):s}try{return n()}finally{try{H(i)}finally{ee(r)}}}function Se({path:e,schemaVersion:t,fail:n}){if(typeof e!="string"||e.length<1||e.length>4096)throw n("bounded state path required","invalid-path");if(typeof n!="function")throw new TypeError("fail factory required");let r=ge(e);we(ve(r),{recursive:!0,mode:448});let i=P(r,t,n);function s(){let f={schemaVersion:t,events:i.events};i={...f,stateHash:k(f)},rt(r,i,n)}function p({tenantId:f,recordType:b,recordId:N,operation:v,occurredAt:S,record:C,expectedRecordHash:V=null}){return it(r,n,()=>{if(i=P(r,t,n),(([...i.events].reverse().find(x=>x.record?.boundId===C?.boundId)??null)?.record?.recordHash??null)!==V)throw n("record changed concurrently; retry from current state","state-conflict");if(C&&typeof C.recordHash=="string"){let{recordHash:x,...F}=C;if(k(F)!==x)throw n("record hash is inconsistent","integrity-failed")}let $={sequence:i.events.length+1,previousEventHash:i.events.at(-1)?.eventHash??null,tenantId:f,recordType:b,recordId:N,operation:v,occurredAt:S,record:C},q=_({...$,eventHash:k($)});return i={...i,events:[...i.events,q]},s(),q})}return Object.freeze({append:p,events:()=>(i=P(r,t,n),i.events.map(f=>structuredClone(f))),verify:()=>{try{return i=P(r,t,n),{ok:!0,count:i.events.length,stateHash:i.stateHash}}catch(f){return{ok:!1,reason:f?.code||"integrity-failed"}}},path:r})}var at=Object.freeze([/(?:^|\s)Bearer\s+\S+/i,/-----BEGIN [A-Z ]*PRIVATE KEY-----/i,/\b(?:sk-|xprv|gh[pousr]_|github_pat_|AKIA|ASIA)[A-Za-z0-9_/-]{12,}\b/i,/\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b/,/\b(?:0x)?[0-9a-f]{64}\b/i,/(?<![1-9A-HJ-NP-Za-km-z])[59KLc][1-9A-HJ-NP-Za-km-z]{50,51}(?![1-9A-HJ-NP-Za-km-z])/,/(?<![1-9A-HJ-NP-Za-km-z])[1-9A-HJ-NP-Za-km-z]{64,88}(?![1-9A-HJ-NP-Za-km-z])/]),st=new Set(["apikey","key","token","accesstoken","secret","password","passwd","auth","authorization","bearer","signertoken","credential","credentials","clientsecret","privatekey","xapikey","accesskey","refreshtoken"]),ct=Object.freeze(["apikey","accesskey","privatekey","clientsecret","accesstoken","refreshtoken","signertoken","credential","password","passwd","authorization","bearer","secret","token"]);function dt(e){return String(e??"").replace(/[^a-z0-9]/gi,"").toLowerCase()}function lt(e){let t;try{t=new URL(e)}catch{return!1}if(t.username||t.password)return!0;for(let n of t.searchParams.keys()){let r=dt(n);if(st.has(r)||ct.some(i=>r.includes(i)))return!0}return!1}function Ee(e){try{let t=decodeURIComponent(e);return t===e?null:t}catch{return null}}var ut=/[a-z][a-z0-9+.-]{1,15}(?::\/\/|%3a%2f%2f)\S+/gi;function ft(e){return e.replace(/[),.;:!?'"\]}]+$/,"")}function pt(e){let t=e.match(ut)||[];for(let n of t){let r=ft(n),i=Ee(r),s=i?Ee(i):null;for(let p of[r,i,s])if(p&&lt(p))return!0}return!1}function ne(e){return typeof e!="string"||e.length===0?!1:at.some(t=>t.test(e))?!0:pt(e)}var yt="oracle.agent-lifecycle/1",ht="oracle.agent-lifecycle-state/1",mt=Object.freeze(["agent","tool","workflow","credential"]),wt=Object.freeze(["active","quarantined","retired","pending_reactivation"]),U=Object.freeze({inactivityMs:2160*60*60*1e3,maxEvalAgeMs:720*60*60*1e3}),At=50,Ie=100,be=new Set(mt),Re=/^[A-Za-z0-9][A-Za-z0-9._:@/-]{0,127}$/,vt=512,Oe=300*1e3,gt=["authorization","apikey","accesskey","cookie","password","passphrase","secret","token","privatekey","seed","mnemonic","keystore","signature"],re=class extends Error{constructor(t,n="invalid-agent-lifecycle"){super(`agent-lifecycle: ${t}`),this.name="AgentLifecycleError",this.code=n}};function l(e,t){return new re(e,t)}function R(e,t){if(typeof e!="string")throw l(`${t} must be an ISO timestamp`,"invalid-time");let n=Date.parse(e);if(!Number.isFinite(n)||new Date(n).toISOString()!==e)throw l(`${t} must be an ISO timestamp`,"invalid-time");return e}function E(e,t){let n=String(e??"").trim();if(ne(n))throw l(`${t} contains secret-shaped material`,"secret-material");if(!Re.test(n))throw l(`${t} is invalid`,"invalid-id");return n}function L(e,t,{required:n=!0}={}){let r=String(e??"").trim();if(n&&!r||r.length>vt||/[\u0000-\u001f\u007f]/.test(r)||ne(r))throw l(`${t} is invalid or contains secret-shaped material`,"invalid-text");return r}function St(e={}){if(!e||typeof e!="object"||Array.isArray(e)||Object.getPrototypeOf(e)!==Object.prototype)throw l("metadata must be a plain object","invalid-metadata");let t=Object.keys(e).sort();if(t.length>24)throw l("metadata field limit exceeded","metadata-limit");let n={};for(let r of t){if(!/^[A-Za-z][A-Za-z0-9_.-]{0,47}$/.test(r))throw l(`metadata key ${r} is invalid`,"invalid-metadata");let i=r.replace(/[^a-z0-9]/gi,"").toLowerCase();if(gt.some(p=>i.includes(p)))throw l(`metadata key ${r} is forbidden because it may contain a secret`,"secret-material");let s=e[r];if(typeof s=="string")n[r]=L(s,`metadata.${r}`,{required:!1});else if(typeof s=="boolean"||s===null)n[r]=s;else if(typeof s=="number"&&Number.isFinite(s))n[r]=s;else throw l(`metadata.${r} must be a scalar`,"invalid-metadata")}return n}function _e(e=[]){if(!Array.isArray(e)||e.length>32)throw l("credentialRefs must be a bounded array","invalid-credentials");let t=new Set;return e.map(n=>{if(!n||typeof n!="object"||Array.isArray(n))throw l("credential reference is invalid","invalid-credentials");let r=Object.keys(n).filter(s=>!["ref","expiresAt"].includes(s));if(r.length)throw l(`credential reference has unknown fields: ${r.sort().join(", ")}`,"invalid-credentials");let i=E(L(n.ref,"credential ref"),"credential ref");if(t.has(i))throw l("credential references must be unique","invalid-credentials");return t.add(i),{ref:i,expiresAt:R(n.expiresAt,"credential expiresAt")}}).sort((n,r)=>n.ref.localeCompare(r.ref))}function Et(e={}){let t=e.inactivityMs??U.inactivityMs,n=e.maxEvalAgeMs??U.maxEvalAgeMs;if(!Number.isSafeInteger(t)||t<1440*60*1e3||t>3660*24*60*60*1e3)throw l("policy.inactivityMs is invalid","invalid-policy");if(!Number.isSafeInteger(n)||n<1||n>3660*24*60*60*1e3)throw l("policy.maxEvalAgeMs is invalid","invalid-policy");return{inactivityMs:t,maxEvalAgeMs:n}}function Ne(e){let t=e==null||e===""?At:Number(e);if(!Number.isSafeInteger(t)||t<1||t>Ie)throw l(`limit must be an integer from 1 to ${Ie}`,"invalid-limit");return t}function Ce(e){return Buffer.from(JSON.stringify(e),"utf8").toString("base64url")}function It(e,t){if(typeof e!="string"||e.length<1||e.length>2048||!/^[A-Za-z0-9_-]+$/.test(e))throw l("cursor is invalid","invalid-cursor");let n;try{n=JSON.parse(Buffer.from(e,"base64url").toString("utf8"))}catch{throw l("cursor is invalid","invalid-cursor")}let r=n&&typeof n=="object"&&!Array.isArray(n)?Object.keys(n).sort():[],i=["after","checkedAt","entityType","kind","sequence","status","tenantId","version"].sort();if(JSON.stringify(r)!==JSON.stringify(i)||n.version!==1||n.kind!==t.kind||n.tenantId!==t.tenantId||n.entityType!==t.entityType||n.status!==t.status||!Number.isSafeInteger(n.sequence)||n.sequence<0||n.sequence>t.headSequence||typeof n.after!="string"||!Re.test(n.after)||Ce(n)!==e)throw l("cursor does not match this lifecycle query","invalid-cursor");try{R(n.checkedAt,"cursor checkedAt")}catch{throw l("cursor is invalid","invalid-cursor")}if(Date.parse(n.checkedAt)>Date.parse(t.authorizedAt))throw l("cursor timestamp is in the future","invalid-cursor");return n}function Te(e){let t=new Map;for(let n of e)t.set(`${n.tenantId}\0${n.recordId}`,n.record);return t}function ke(e,{at:t=new Date().toISOString(),inactivityMs:n=U.inactivityMs,maxEvalAgeMs:r=U.maxEvalAgeMs}={}){let i=R(t,"at"),s=Date.parse(i),p=[];if(e.latestEval&&(e.latestEval.passed===!1||e.latestEval.score<e.latestEval.threshold)&&p.push("eval-below-threshold"),e.latestEval?.passed===!0&&s-Date.parse(e.latestEval.evaluatedAt)>r&&p.push("eval-stale"),(e.credentialRefs||[]).some(b=>Date.parse(b.expiresAt)<=s)&&p.push("credential-expired"),e.entityType==="credential"&&e.expiresAt&&Date.parse(e.expiresAt)<=s&&p.push("credential-expired"),p.length)return _({status:"quarantined",reasons:p.sort(),evaluatedAt:i});let f=e.lastUsedAt??e.registeredAt;return s-Date.parse(f)>=n?_({status:"retired",reasons:["inactive"],evaluatedAt:i}):_({status:"active",reasons:[],evaluatedAt:i})}function zt({path:e,now:t=()=>new Date().toISOString(),policy:n={}}={}){let r=Et(n),i=Se({path:e,schemaVersion:ht,fail:l});function s(){return R(t(),"now")}function p(o,y,u=s()){return he({context:o,resource:"lifecycle",action:y,decidedAt:u})}function f(o,y){return Te(i.events()).get(`${o.tenantId}\0${y}`)??null}function b({context:o,kind:y,cursor:u,checkedAt:c,entityType:d=null,status:a=null}){let h=i.events(),m=h.at(-1)?.sequence??0,A=u==null?null:It(u,{kind:y,tenantId:o.tenantId,entityType:d,status:a,headSequence:m,authorizedAt:c}),w=A?.sequence??m,g=A?.checkedAt??c,D=A?.after??null;return{records:[...Te(h.filter(I=>I.sequence<=w)).values()].filter(I=>I.tenantId===o.tenantId).filter(I=>d==null||I.entityType===d).sort((I,j)=>I.entityId.localeCompare(j.entityId)).filter(I=>D==null||I.entityId.localeCompare(D)>0),sequence:w,checkedAt:g}}function N({items:o,hasMore:y,lastEntityId:u,kind:c,context:d,entityType:a=null,status:h=null,sequence:m,checkedAt:A}){let w=y?Ce({version:1,kind:c,tenantId:d.tenantId,entityType:a,status:h,sequence:m,checkedAt:A,after:u}):null;return _({items:o,nextCursor:w,sequence:m})}function v(o,y){let u=ke(o,{at:y,...r}),c=["quarantined","retired","pending_reactivation"].includes(o.status)&&u.status==="active",d=c?o.status:u.status,a=c?[...o.reasons||[]]:[...u.reasons];return _({...structuredClone(o),status:d,reasons:a,evaluatedAt:u.evaluatedAt})}function S(o,y,u,c=s()){let d=u.recordHash??null,a={...u};delete a.recordHash;let h=_({...a,recordHash:k(a)});return i.append({tenantId:o.tenantId,recordType:"lifecycle",recordId:h.entityId,operation:y,occurredAt:c,record:h,expectedRecordHash:d}),h}function C({context:o,input:y}={}){let u=s();p(o,"create",u);let c=E(y?.entityId,"entityId");if(f(o,c))throw l("entity already exists","duplicate-entity");let d=String(y?.entityType??"").trim();if(!be.has(d))throw l("entityType is invalid","invalid-entity-type");let a=_e(y?.credentialRefs);if(d==="credential"&&a.length)throw l("credential entities cannot contain credentialRefs","invalid-credentials");let h=d==="credential"?R(y?.expiresAt,"expiresAt"):null,m=a.some(g=>Date.parse(g.expiresAt)<=Date.parse(u))||h!=null&&Date.parse(h)<=Date.parse(u),A=E(y?.ownerActorId,"ownerActorId"),w=E(o.actor.actorId,"registering actorId");if(!o.actor.roles.includes("owner")&&A!==w)throw l("only an owner may assign lifecycle ownership to another actor","owner-assignment-required");return S(o,"register",{schemaVersion:yt,tenantId:o.tenantId,boundId:ye(o,"lifecycle",c),entityId:c,entityType:d,label:L(y?.label,"label"),ownerActorId:A,registeredBy:w,credentialRefs:a,expiresAt:h,metadata:St(y?.metadata),status:m?"quarantined":"active",reasons:m?["credential-expired"]:[],registeredAt:u,updatedAt:u,lastUsedAt:null,latestEval:null,reactivation:null,revision:1},u)}function V({context:o,entityId:y}={}){let u=s();p(o,"read",u);let c=f(o,E(y,"entityId"));return c?v(c,u):null}function oe({context:o,entityType:y=null,status:u=null,limit:c=null,cursor:d=null}={}){let a=s();if(p(o,"read",a),y!=null&&!be.has(y))throw l("entityType is invalid","invalid-entity-type");if(u!=null&&!wt.includes(u))throw l("status is invalid","invalid-status");let h=Ne(c),m=b({context:o,kind:"list",cursor:d,checkedAt:a,entityType:y,status:u}),A=m.records.map(g=>v(g,m.checkedAt)).filter(g=>u==null||g.status===u),w=A.slice(0,h).map(g=>_(structuredClone(g)));return N({items:w,hasMore:A.length>h,lastEntityId:w.at(-1)?.entityId??null,kind:"list",context:o,entityType:y,status:u,sequence:m.sequence,checkedAt:m.checkedAt})}function ie({context:o,entityId:y,outcome:u,occurredAt:c=null}={}){let d=s();p(o,"update",d);let a=f(o,E(y,"entityId"));if(!a)throw l("entity not found","not-found");let h=new Set(["succeeded","failed","denied","cancelled","skipped"]),m=String(u??"").trim();if(!h.has(m))throw l("usage outcome is invalid","invalid-outcome");let A=R(c??d,"occurredAt"),w=v(a,d);if(w.status!=="active")throw(w.status!==a.status||JSON.stringify(w.reasons)!==JSON.stringify(a.reasons||[]))&&S(o,"usage-denied",{...a,status:w.status,reasons:[...w.reasons],evaluatedAt:w.evaluatedAt,updatedAt:d,revision:a.revision+1},d),l(`entity is not active: ${w.status}`,"entity-not-active");if(Date.parse(A)>Date.parse(d)+Oe)throw l("occurredAt is too far in the future","future-usage");if(a.lastUsedAt&&Date.parse(A)<Date.parse(a.lastUsedAt))throw l("occurredAt must be monotonic","non-monotonic-usage");return S(o,"usage",{...a,lastUsedAt:A,lastOutcome:m,updatedAt:d,revision:a.revision+1},d)}function $({context:o,entityId:y,suite:u,score:c,threshold:d,evaluatedAt:a=null}={}){let h=s();p(o,"update",h);let m=f(o,E(y,"entityId"));if(!m)throw l("entity not found","not-found");let A=Number(c),w=Number(d);if(![A,w].every(j=>Number.isFinite(j)&&j>=0&&j<=1))throw l("eval score and threshold must be from 0 to 1","invalid-eval");let g=R(a??h,"evaluatedAt");if(Date.parse(g)>Date.parse(h)+Oe)throw l("evaluatedAt is too far in the future","future-eval");if(m.latestEval&&Date.parse(g)<Date.parse(m.latestEval.evaluatedAt))throw l("evaluatedAt must be monotonic","non-monotonic-eval");let D={suite:E(u,"eval suite"),score:A,threshold:w,evaluatedAt:g,evaluatorActorId:E(o.actor.actorId,"evaluatorActorId"),passed:A>=w},ae={...m,latestEval:D},I=v(ae,h);return S(o,"eval",{...m,latestEval:D,status:I.status,reasons:[...I.reasons],evaluatedAt:I.evaluatedAt,updatedAt:h,revision:m.revision+1},h)}function q({context:o,entityId:y,refs:u}={}){let c=s();p(o,"update",c);let d=f(o,E(y,"entityId"));if(!d)throw l("entity not found","not-found");let a=v(d,c);if(a.entityType==="credential")throw l("credential entities use updateCredentialExpiry","invalid-credentials");let h=_e(u),m=h.some(A=>Date.parse(A.expiresAt)<=Date.parse(c));return S(o,"credential-refs",{...a,credentialRefs:h,status:m?"quarantined":a.status,reasons:m?[...new Set([...a.reasons||[],"credential-expired"])].sort():a.reasons,updatedAt:c,revision:a.revision+1},c)}function x({context:o,entityId:y,expiresAt:u}={}){let c=s();p(o,"update",c);let d=f(o,E(y,"entityId"));if(!d)throw l("entity not found","not-found");let a=v(d,c);if(a.entityType!=="credential")throw l("entity is not a credential","invalid-entity-type");let h=R(u,"expiresAt"),m=Date.parse(h)<=Date.parse(c);return S(o,"credential-expiry",{...a,expiresAt:h,status:m?"quarantined":a.status,reasons:m?[...new Set([...a.reasons||[],"credential-expired"])].sort():a.reasons,updatedAt:c,revision:a.revision+1},c)}function F({context:o,limit:y=null,cursor:u=null}={}){let c=s();p(o,"manage",c);let d=Ne(y),a=b({context:o,kind:"sweep",cursor:u,checkedAt:c}),h=a.checkedAt,m=[],A=a.records.slice(0,d);for(let w of A){let g=v(w,h);(g.status!==w.status||JSON.stringify(g.reasons)!==JSON.stringify(w.reasons||[]))&&S(o,"sweep",{...w,status:g.status,reasons:[...g.reasons],evaluatedAt:h,updatedAt:h,revision:w.revision+1},h),m.push({entityId:w.entityId,status:g.status,reasons:[...g.reasons],evaluatedAt:h})}return N({items:m.map(w=>_(w)),hasMore:a.records.length>d,lastEntityId:A.at(-1)?.entityId??null,kind:"sweep",context:o,sequence:a.sequence,checkedAt:h})}function xe({context:o,entityId:y,reason:u}={}){let c=s();p(o,"update",c);let d=f(o,E(y,"entityId"));if(!d)throw l("entity not found","not-found");let a=v(d,c);if(!["quarantined","retired"].includes(a.status))throw l("only quarantined or retired entities may request reactivation","invalid-transition");return S(o,"reactivation-requested",{...a,status:"pending_reactivation",reactivation:{requestedAt:c,requestedBy:E(o.actor.actorId,"requestedBy"),reason:L(u,"reactivation reason"),reviewedAt:null,reviewedBy:null,review:null},updatedAt:c,revision:a.revision+1},c)}function De({context:o,entityId:y,review:u}={}){let c=s();p(o,"approve",c);let d=f(o,E(y,"entityId"));if(!d)throw l("entity not found","not-found");if(d.status!=="pending_reactivation"||!d.reactivation)throw l("reactivation request is required","invalid-transition");let a=ke(d,{at:c,...r});if(a.status!=="active")throw l(`reactivation remediation incomplete: ${a.reasons.join(", ")}`,"remediation-required");let h=c;return S(o,"reactivation-approved",{...d,status:"active",reasons:[],reactivation:{...d.reactivation,reviewedAt:h,reviewedBy:E(o.actor.actorId,"reviewedBy"),review:L(u,"reactivation review")},updatedAt:h,revision:d.revision+1},c)}return Object.freeze({register:C,get:V,list:oe,recordUsage:ie,recordEval:$,updateCredentialRefs:q,updateCredentialExpiry:x,sweep:F,requestReactivation:xe,approveReactivation:De,verify:i.verify})}export{mt as AGENT_LIFECYCLE_ENTITY_TYPES,yt as AGENT_LIFECYCLE_SCHEMA_VERSION,ht as AGENT_LIFECYCLE_STATE_VERSION,wt as AGENT_LIFECYCLE_STATUSES,re as AgentLifecycleError,At as DEFAULT_AGENT_LIFECYCLE_PAGE_LIMIT,U as DEFAULT_AGENT_LIFECYCLE_POLICY,Ie as MAX_AGENT_LIFECYCLE_PAGE_LIMIT,zt as createAgentLifecycleController,ke as evaluateLifecycleRecord};
@@ -97,11 +97,17 @@ does not prove npm users receive it. A locally hosted model/data/policy loop doe
97
97
  a bundled signer. A merged feature is not public until the npm artifact containing it is
98
98
  published and inspected.
99
99
 
100
- **Candidate release in this source tree:** public package `@oracle-agent/oracle@0.35.20`
100
+ **Candidate release in this source tree:** public package `@oracle-agent/oracle@0.35.22`
101
101
  ships the encrypted local vault + short-lived loopback signer. Hosted/keyless
102
- Oracle cannot hold keys or move funds. Self-host after `oracle sign init|import` is **auto-armed**
103
- for user-initiated actions. Public still charges the disclosed fee card; Administrator
104
- (`@oracle-agent/agent`, never on npm) stays zero-fee with DEMI's keys.
102
+ Oracle cannot hold keys or move funds. Self-hosting with `oracle sign init` or
103
+ `oracle sign import --kind evm --key-file <0600 file>` writes an encrypted vault and marks
104
+ the signer ARMED/ready. `allowed` remains false. An explicit command asks for confirmation
105
+ before signing, executing, or broadcasting. The only same-turn shortcut is to say `quick
106
+ transaction` and explicitly confirm in the same sentence. An explicitly configured autonomous
107
+ grant, such as a shadow-profit promotion rule, is the separate exception. Signing still
108
+ requires a sealed policy plus a short-lived signer session. Public follows Administrator
109
+ execution behavior but charges the disclosed fee card; Administrator (`@oracle-agent/agent`,
110
+ never on npm) stays zero-fee with DEMI's keys.
105
111
 
106
112
  Public signer families are **EVM, Solana, Bitcoin only** — not separate HL/Poly
107
113
  account surfaces. Do not resurrect Tread.fi OEMS/API protocol interaction; TradFi
@@ -114,18 +120,18 @@ matches the intended release and the packed tarball was secret-scanned. Never ac
114
120
 
115
121
  Detail: `references/public-admin-parity.md`.
116
122
 
117
- **Key-path wiring bug found + fixed 2026-08-09 (verify this stays wired).** `oracle sign
118
- import` wrote the key to `<config>/keys/evm.json` and armed `exec.env`, but the loopback
119
- signer daemon resolves key material from the vault path written by `oracle sign import`.
120
- A freshly imported key was **silently
121
- never picked up** by `oracle signer` — import reported success, signing just never found
122
- it. Fix: import now also writes `signer.env` pointing both vars at the file it created,
123
- preserving other entries (`wireSignerKeyEnv` in `src/cli/commands/sign.mjs`; regression
124
- test in `test/cli-signplane.test.mjs` asserts key material, shred, arm, AND env wiring).
125
- Lesson for this class: "the key file exists on disk" is NOT proof the signer can sign —
126
- trace provision → env → daemon resolution end to end before claiming a signer works.
127
- Note `sign.mjs` is ESM: helpers need `await import("node:fs"/"node:path")`, and `join`
128
- scoped inside a verb block is not visible to a module-level helper.
123
+ **Current signer provisioning contract (verify this stays wired).** `oracle sign init` creates
124
+ an encrypted EVM keyring. `oracle sign import --kind evm|btc|sol --key-file <0600 file>`
125
+ imports protected key material into the same vault. The passphrase comes from a hidden TTY or
126
+ `--passphrase-file <0600 file>`, and the encrypted keyring is stored at
127
+ `<config>/signer/vault.json`. Importing marks the signer ARMED/ready, but it does not sign,
128
+ execute, submit, trade, or broadcast. An explicit command asks for confirmation; execution
129
+ starts only after confirmation. The only same-turn shortcut is `quick transaction` plus an
130
+ explicit confirm in the same sentence. An explicit autonomous grant and its configured trigger
131
+ are the separate exception. A signer can act only after `oracle sign policy --policy-file <0600
132
+ JSON file>` seals nonempty allowlists and a short-lived signer session starts. Never accept
133
+ private keys in arguments, environment variables, chat, or app UI. Trace provision, vault
134
+ resolution, policy, session, and receipt end to end before claiming the signer works.
129
135
 
130
136
  The gap found 2026-08-09: agent enforced only its own env caps (`ORACLE_MAX_NOTIONAL_USD`, …)
131
137
  and NEVER read the app's `agent-policy.json` — two B walls that could drift (app UI noLimits
@@ -332,7 +338,7 @@ authorized recipient auditable.
332
338
  - `references/agent-policy-nolimits.md` — noLimits / API actions
333
339
  - `references/agent-parity.md` — agent=admin variant parity diff, shared-policy hash fix, merge-vs-remote-CI-shape-tests technique
334
340
  - `references/agent-rename-and-npm.md` — the 2026-08-09 operator→agent rename procedure + npm unpublish/deprecate/2FA knowledge
335
- - `references/public-admin-parity.md` — public vs Admin: whose keys, fees, auto-arm, EVM/SOL/BTC-only signer, no Tread OEMS
341
+ - `references/public-admin-parity.md` — public vs Admin: whose keys, fees, vault provisioning vs arming, EVM/SOL/BTC-only signer, no Tread OEMS
336
342
 
337
343
  ## Related
338
344
 
@@ -89,30 +89,25 @@ oracle mcp watchdog --once # one-shot health check
89
89
  ## Wallet setup
90
90
 
91
91
  ```bash
92
- oracle init --apply # generates fresh wallet at ~/.config/oracle/keys/evm.json (0600)
93
- oracle sign import # interactive: paste existing private key (hidden input)
94
- oracle sign import --keyfile <path> # import from file (auto-shreds temp file after import)
92
+ oracle sign init [--passphrase-file <0600 file>]
93
+ oracle sign import --kind evm --key-file <0600 file> [--passphrase-file <0600 file>]
94
+ oracle sign import --kind btc --key-file <0600 file> [--passphrase-file <0600 file>]
95
+ oracle sign import --kind sol --key-file <0600 file> [--passphrase-file <0600 file>]
96
+ oracle sign policy --policy-file <0600 JSON file>
97
+ oracle sign status
98
+ oracle sign doctor
95
99
  ```
96
100
 
97
- Both `oracle sign import` paths **auto-arm** — importing a key sets `ORACLE_EXEC_ENABLED=1`
98
- in `~/.config/oracle/exec.env`. No separate `oracle exec arm` step needed.
99
- The intent is the signal: pasting a private key means the user wants to trade immediately.
100
-
101
- **Rationale:** The user explicitly chose to import their private key and wire the exec MCP.
102
- Adding a mandatory \"arm\" step on top is unnecessary friction. Import = armed.
103
-
104
- **Safety boundary:** The key never leaves the local machine. Oracle's wallet is like a
105
- CLI-native MetaMask — key on disk at 0600, MCP process local to the machine, same trust model.
106
-
107
- The interactive prompt reassures users:
108
- ```
109
- Your key stays local — Oracle never sends it anywhere.
110
- Paste your EVM private key (0x...):
111
- >
112
- ```
113
- Key goes to stdin, never to shell history. Saved at 0600 permissions.
114
-
115
- Never use `echo "0xKEY"` or `--key 0x...` — private keys must never hit shell history.
101
+ The passphrase comes from a hidden TTY prompt or an owner-only file. Key material comes only
102
+ from `--key-file`; Oracle refuses private keys in arguments and inherited environment variables.
103
+ The encrypted keyring lives at `<config>/signer/vault.json`. Importing marks the signer
104
+ ARMED/ready, but `allowed` remains false. An explicit command asks for confirmation before
105
+ signing, executing, or broadcasting. The only same-turn shortcut is to say `quick transaction`
106
+ and explicitly confirm in the same sentence. An explicitly configured autonomous grant, such
107
+ as a shadow-profit promotion rule, is the separate exception. Signing still requires a sealed
108
+ policy with nonempty allowlists and a short-lived signer session. Public follows Administrator
109
+ execution behavior and applies the disclosed public fees.
110
+ Never ask users to paste private keys into chat, app UI, or shell arguments.
116
111
 
117
112
  ## Print mode
118
113
  ```
@@ -97,23 +97,26 @@ Cover, in this order:
97
97
  need to know they can evaluate the thing before handing over credentials.
98
98
  Hyperliquid reads are keyless; Polymarket market data is keyless.
99
99
  3. **Per-venue credential shape**, because they differ structurally:
100
- - **Hyperliquid issues no API key.** You authorize with an Ethereum private
101
- key — use an **API wallet** (`app.hyperliquid.xyz/API`) that can trade but
102
- **cannot withdraw**. Say that explicitly; it is the single most important
103
- safety fact for an agent.
100
+ - **Hyperliquid issues no API key.** An owner-local signer authorizes requests
101
+ with a dedicated **API wallet** (`app.hyperliquid.xyz/API`) that can trade but
102
+ **cannot withdraw**. The key stays in the protected local signer vault and is
103
+ never passed through chat, prompts, arguments, or inherited environment.
104
104
  - **Polymarket needs two different things**: L2 API credentials
105
- (`API_KEY`/`API_SECRET`/`API_PASSPHRASE`) for CLOB order posting, *and* the
106
- Polygon private key that owns the funds. Conflating them wastes an hour.
107
- 4. **Storage, worst → best**, with the honest tradeoff at each tier: env vars
108
- (visible in `ps`, inherited by children), a `0600` key file, an encrypted
109
- vault. Include the `chmod`/`printf` commands so nobody invents their own.
105
+ (`API_KEY`/`API_SECRET`/`API_PASSPHRASE`) for CLOB order posting, *and* an
106
+ owner-local signer for the Polygon wallet that owns the funds. Conflating
107
+ them wastes an hour; neither credential belongs in model context.
108
+ 4. **Storage is owner-local and fail closed.** Use the documented protected `0600`
109
+ key-file and encrypted signer-vault workflow. Never print, paste, or interpolate
110
+ key material into shell commands, chat, prompts, or process arguments.
110
111
  5. **What the encryption does NOT protect.** A vault defends backups, cloud sync,
111
112
  and stolen disks at rest; it does **not** defend malware running as the user
112
113
  while the process is unlocked. Overselling a file-level scheme is worse than
113
114
  shipping none, because it changes behaviour.
114
115
  6. **Both trade paths side by side** — non-custodial (Oracle prepares, user's
115
- wallet signs) and agentic (Oracle holds a key and submits) — so the reader
116
- picks deliberately rather than defaulting into custody.
116
+ wallet signs) and automated owner-local execution (the signer submits only
117
+ after the user's explicit bounded authorization, including an explicitly
118
+ configured shadow-profit promotion rule) — so the reader picks deliberately
119
+ rather than defaulting into custody.
117
120
 
118
121
  Also state where credentials travel: pinned provider endpoints, key dropped
119
122
  rather than forwarded when a `baseUrl` is redirected or downgraded to `http://`.
@@ -21,27 +21,28 @@ A wallet signature proves address control, not operator/admin authority.
21
21
 
22
22
  An open preview may accept any signed wallet only when every money-moving action remains self-custodial and backend live submission is absent from the build.
23
23
 
24
- ## Arming posture: key present = armed, user asks = it runs
24
+ ## Arming posture: key present = armed, confirmation = it runs
25
25
 
26
26
  **DEMI correction — do not gate each capability behind its own env flag.**
27
27
 
28
28
  ```
29
29
  Key present -> capability is ARMED
30
- User asks for it -> it RUNS
30
+ User commands it -> ask for CONFIRMATION
31
+ User confirms it -> it RUNS
32
+ Quick transaction + confirm in one sentence -> it RUNS
31
33
  Autonomous mode -> the ONLY separate opt-in
32
34
  ```
33
35
 
34
- Supplying a signing key **is** the authorization decision. Making someone who
35
- already handed over a key hunt down a second and third `ORACLE_X_ENABLED=1` to
36
- buy an NFT is friction, not security — and it teaches people to set every flag,
37
- which is strictly worse than one coherent default. What actually protects the
38
- user is that every action is **user-initiated** and **bounded by caps at the
39
- moment it runs**.
36
+ Installing the owner-local signer vault is the readiness/arming decision, not an
37
+ execution instruction. Making someone with an armed signer hunt down a second
38
+ and third `ORACLE_X_ENABLED=1` to buy an NFT is friction, not security, and it
39
+ teaches people to set every flag. What protects the user is that every action is
40
+ **user-initiated**, **confirmed**, and **bounded by caps at the moment it runs**.
40
41
 
41
42
  Autonomous/unattended trading is the sole exception, because it is the only mode
42
- where the agent initiates value movement without being asked. An agent that
43
- trades while you sleep is a different risk class from one that buys the NFT you
44
- just named.
43
+ where the agent initiates value movement without a new instruction. It requires
44
+ an explicit autonomous grant, including any configured promotion rule that runs
45
+ only after the strategy's shadow-profit condition passes.
45
46
 
46
47
  Keep an `execute: true` marker at the call site (it stops a send happening as a
47
48
  side effect of a read path) but do not add an env arm beside it. `armed` and
@@ -89,9 +90,9 @@ Report each gate separately. `agent:execute=true` does not mean "trading": an un
89
90
 
90
91
  ### Chat-leaked burner keys are still burn-only
91
92
 
92
- If a user pastes a Solana secret key, BTC WIF, seed phrase, or Xverse seed into chat, do **not** import or use it, even when they say it is only a burner. Treat the key as compromised, tell them to generate/import a fresh burner directly on the machine that will sign, and accept only the public address in chat. Long base58 Solana strings are often secret keys; validate by reading the public address only. For Bitcoin inscriptions, prefer a fresh local WIF file that pays commit/reveal fees while inscribing **to** a separate ordinal receive address. Never store an Xverse seed for agentic testing.
93
+ If a user pastes a Solana secret key, BTC WIF, seed phrase, or Xverse seed into chat, do **not** import or use it, even when they say it is only a burner. Treat the key as compromised. Accept only the public address in chat. Tell them to create a fresh burner directly on the signing machine. Long base58 Solana strings are often secret keys; validate by reading the public address only. For Bitcoin inscriptions, prefer a fresh local WIF file that pays commit/reveal fees while inscribing **to** a separate ordinal receive address. Never store an Xverse seed for agentic testing.
93
94
 
94
- Creator/operator urgency is not an exception. If DEMI says "use it for now", "save it locally", or "we already did it for EVM", keep the key-ingress invariant and make the safe local path fast: create the owner-only key directory, create empty `0600` files, and provide/run a prompt-only local installer (`read -s`, no echo, no logs) for the user to paste on the signing machine. After install, the agent may report file mode/size, derive public addresses, and check balances/readiness; never print or copy the raw secret.
95
+ Creator/operator urgency is not an exception. If DEMI says "use it for now", "save it locally", or "we already did it for EVM", keep the key-ingress invariant and make the safe local path fast: create the owner-only key directory, create empty `0600` files, and run a prompt-only local installer (`read -s`, no echo, no logs) on the signing machine against those files. After install, the agent may report file mode/size, derive public addresses, and check balances/readiness; never print or copy the raw secret.
95
96
 
96
97
  Detailed response pattern: `references/chat-leaked-key-ingestion.md`.
97
98
 
@@ -26,7 +26,7 @@ Any runaway loop or unverified execution path is existential when it can move re
26
26
  - **Spend/exposure cap:** respect the daily loss limit; never code around a halt. A halt is a signal, not an obstacle.
27
27
  - **Backoff + jitter** on API timeouts — cascading timeouts are a known cost/failure path.
28
28
  - **Kill-switch check** before resume: verify why it halted before clearing it. Reset ≠ diagnosis.
29
- - **Signer separation:** automatic execution may use only a separate capped hot wallet/session key/smart wallet. Never DEMI's main seed/private key.
29
+ - **Signer separation:** automatic execution may use only a separate capped hot wallet/session key/smart wallet. Never use DEMI's main seed/private key.
30
30
  - **Two-step deploy boundary:** shipping executor code and securely installing a dedicated signer are not the same as enabling broadcasts. A signer may be installed root-only in an unfunded, execution-off state after explicit installation approval so its derived address and status path can be verified. Keep `agent:execute` blocked, capital at zero, and asset allowlists empty until a separate explicit DEMI GO enables the capped signer. Never expose or print the key during either stage.
31
31
  - **Policy proof:** after every deploy, pull the live policy and state `execution.enabled`, `backendSigner`, `liveExecute`, and whether `agent:execute` is blocked before claiming auto-exec status.
32
32