@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.
- package/README.md +24 -1
- package/dist/agent-lifecycle.mjs +3 -0
- package/dist/assets/skills/oracle-desk-product/SKILL.md +23 -17
- package/dist/assets/skills/oracle-mcp-install/SKILL.md +17 -22
- package/dist/assets/skills/protocol-api-key-integration/SKILL.md +14 -11
- package/dist/assets/skills/self-custodial-onchain-execution/SKILL.md +14 -13
- package/dist/assets/skills/trade-loop-circuit-breaker/SKILL.md +1 -1
- package/dist/bin/desk-server.mjs +36 -36
- package/dist/bin/oracle-data-mcp.mjs +17 -17
- package/dist/bin/oracle-mev-arb-shadow.mjs +4 -4
- package/dist/bin/oracle-mev-shadow.mjs +6 -6
- package/dist/bin/oracle-public-server.mjs +5 -5
- package/dist/bin/oracle-route.mjs +11 -11
- package/dist/bin/oracle-scan.mjs +15 -15
- package/dist/bin/oracle.mjs +19 -10
- package/dist/cli/commands/chat.mjs +200 -200
- package/dist/cli/commands/context.mjs +16 -0
- package/dist/cli/commands/doctor.mjs +7 -7
- package/dist/cli/commands/eval.mjs +113 -113
- package/dist/cli/commands/extension-policy.mjs +1 -1
- package/dist/cli/commands/governance-prepare.mjs +5 -0
- package/dist/cli/commands/lifecycle.mjs +17 -0
- package/dist/cli/commands/model.mjs +204 -204
- package/dist/cli/commands/portfolio-risk.mjs +4 -0
- package/dist/cli/commands/position-health.mjs +4 -0
- package/dist/cli/commands/quickstart.mjs +5 -5
- package/dist/cli/commands/safety-intelligence.mjs +4 -0
- package/dist/cli/commands/sign.mjs +1 -1
- package/dist/cli/commands/signer.mjs +22 -22
- package/dist/cli/commands/snipe.mjs +4 -4
- package/dist/cli/commands/swap.mjs +13 -13
- package/dist/cli/commands/tax-lots.mjs +4 -0
- package/dist/cli/commands/venues.mjs +2 -2
- package/dist/context-graph.mjs +3 -0
- package/dist/data/desk-data.mjs +34 -34
- package/dist/data/providers/lp-destination-discovery.mjs +6 -0
- package/dist/data/providers/lp-liquidity-ranker.mjs +1 -0
- package/dist/index.mjs +50 -48
- package/dist/local-signer/anvil-attestation-worker.mjs +1 -1
- package/dist/local-signer/runtime.mjs +1 -1
- package/dist/router/index.mjs +3 -3
- package/dist/router/prepare-bridge.mjs +2 -2
- package/dist/scanner/index.mjs +4 -4
- package/dist/tenant-rbac.mjs +1 -1
- package/dist/workflow-telemetry.mjs +1 -1
- package/package.json +5 -1
- package/public/eval-report.json +2 -2
- package/public/oracle-splash/brand/liquidlaunch.webp +0 -0
- package/public/oracle-splash/brand/markets.webp +0 -0
- package/public/oracle-splash/brand/motion.webp +0 -0
- package/public/oracle-splash/brand/nest.webp +0 -0
- package/public/oracle-splash/brand/outcome.webp +0 -0
- package/public/oracle-splash/brand/silhouette.webp +0 -0
- package/public/oracle-splash/brand/tradexyz.webp +0 -0
- package/public/oracle-splash/index.html +9 -9
- package/server.json +2 -2
- package/dist/assets/skills/oracle-best-execution.md +0 -27
- package/dist/assets/skills/oracle-desk.md +0 -27
- 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
|
-
**
|
|
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&<(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.
|
|
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-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
**
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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,
|
|
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 --
|
|
93
|
-
oracle sign import
|
|
94
|
-
oracle sign 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
|
-
|
|
98
|
-
|
|
99
|
-
The
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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.**
|
|
101
|
-
|
|
102
|
-
**cannot withdraw**.
|
|
103
|
-
|
|
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*
|
|
106
|
-
Polygon
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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
|
|
116
|
-
|
|
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,
|
|
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
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
buy an NFT is friction, not security
|
|
37
|
-
|
|
38
|
-
|
|
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
|
|
43
|
-
|
|
44
|
-
|
|
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
|
|
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
|
|
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
|
|