diff --git a/README.md b/README.md index 9bd0e5b..e762cdc 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,9 @@ Vercel Blob. Build 73 also installs an offline app shell and caches timestamped market snapshots. During an outage, cycles continue locally; cached entries are allowed for 90 minutes, older snapshots become mark-only, and all cached data expires after 24 hours. +Offline cycles can apply calibration already earned from live observations, but +the evidence ledger is read-only: cached prices cannot grade pending signals, +expire horizons, or create new observations. Build 121 targets five-minute slots with a serialized, self-chained GitHub Actions runtime. It continues from the previous agent snapshot and runs the next due paper cycle diff --git a/index.html b/index.html index e314f08..5bc42b8 100644 --- a/index.html +++ b/index.html @@ -2572,7 +2572,8 @@ function pendingSignalMarketIds(ledger,knownIds=new Set(),now=Date.now()){ } return ids; } -function updateSignalLedger(st,markets,suggestions){ +function updateSignalLedger(st,markets,suggestions,{liveEvidence=true}={}){ + if(!liveEvidence)return st.signal_ledger&&typeof st.signal_ledger==="object"?st.signal_ledger:defaultSignalLedger(); const ledger=st.signal_ledger&&typeof st.signal_ledger==="object"?st.signal_ledger:defaultSignalLedger(); ledger.pending=Array.isArray(ledger.pending)?ledger.pending:[];ledger.outcomes=Array.isArray(ledger.outcomes)?ledger.outcomes:[]; const now=Date.now(),marketMap=Object.fromEntries((markets||[]).map(m=>[String(m.id),m])),stillPending=[]; @@ -4530,10 +4531,8 @@ async function runDailyCycle(){ ?(freshById[String(id)]||((analysisIds.has(String(id)))?cachedById[String(id)]:null)) :(cachedById[String(id)]||null); if(runMode==="live")saveMarketCache(analysisMarkets,sugs,priceMap); - if(runMode==="live"){ - const supplemental=dueSignalIds.map(id=>freshById[String(id)]).filter(Boolean); - updateSignalLedger(st,[...analysisMarkets,...supplemental],sugs); - } + const supplemental=runMode==="live"?dueSignalIds.map(id=>freshById[String(id)]).filter(Boolean):[]; + updateSignalLedger(st,[...analysisMarkets,...supplemental],sugs,{liveEvidence:runMode==="live"}); const marketLearning=buildSignalCalibration(st.signal_ledger); const marketLearningByAgent=Object.fromEntries(AGENTS.map(agent=>[agent.id,buildSignalCalibration(st.signal_ledger,agent.id)])); if(runMode==="live"){ @@ -6843,6 +6842,11 @@ function runEngineSelfTest(){ const ledgerState={signal_ledger:{pending:[{key:"ledger-test",market_id:"ledger-test",observed_at:hoursAgo(25),side:"YES",entry_price:0.40, signal_type:"trend",quality:"confirmed",category:"Politics"}],outcomes:[]}}; updateSignalLedger(ledgerState,[market({id:"ledger-test",yes_price:0.50,no_price:0.50})],[]); + const offlineEvidenceState={signal_ledger:{pending:[{key:"offline-ledger",market_id:"offline-ledger",observed_at:hoursAgo(25),side:"YES",entry_price:0.40, + signal_type:"trend",quality:"confirmed",category:"Politics"}],outcomes:[],expired_ungraded:2}}; + const offlineEvidenceBefore=JSON.stringify(offlineEvidenceState.signal_ledger); + updateSignalLedger(offlineEvidenceState,[market({id:"offline-ledger",yes_price:0.90,no_price:0.10})],[{market_id:"offline-new",side:"YES",entry_price:0.42, + signal_type:"trend",quality:"confirmed",signal_confidence:0.99,jump_risk:false,category:"Politics"}],{liveEvidence:false}); const compactedLedgerState={signal_ledger:compactPublicSignalLedger({pending:[{key:"ledger-compacted",market_id:"ledger-compacted", event_key:"compacted-event",observed_at:hoursAgo(6.5),graded_horizons:[],side:"YES",entry_price:0.40,signal_type:"trend", quality:"confirmed",category:"Politics",days_to_resolution:30,trade_ready_at_observation:true,fees_enabled:false, @@ -7461,6 +7465,7 @@ function runEngineSelfTest(){ &&compactedScopedState.signal_ledger.outcomes[0].eligible_agent_ids[0]==="momentum" &&compactedScopedSuggestion.promoted_for_agents[0]==="momentum", offlineCacheRebuildsAgentPromotion:offlineScopedMomentum.trade_ready&&!offlineScopedValue.trade_ready, + offlineEvidenceCapabilityIsReadOnly:JSON.stringify(offlineEvidenceState.signal_ledger)===offlineEvidenceBefore, losingCohortDemotesOnlyEligibleAgent:losingValueState.state==="demoted"&&!losingValueState.allowed &&unrelatedMomentumProfile.samples===0&&unrelatedMomentumProfile.current_samples===0, legacyUnscopedEvidenceRemainsReadable:buildSignalCalibration(legacyUnscopedLedger,"momentum").samples===1 diff --git a/scripts/test-autonomous-runtime.mjs b/scripts/test-autonomous-runtime.mjs index 2056a9f..77a32cd 100644 --- a/scripts/test-autonomous-runtime.mjs +++ b/scripts/test-autonomous-runtime.mjs @@ -42,6 +42,8 @@ assert.match(index, /const DIRECTIONAL_PROBATION_MIN_EVENTS = 12;/); assert.match(index, /sixHourEvidenceStartsBoundedProbation:/); assert.match(index, /probationMetadataSurvivesOfflineCompaction:/); assert.match(index, /probationExitsAtExecutableSixHourPrice:/); +assert.match(index, /updateSignalLedger\(st,\[\.\.\.analysisMarkets,\.\.\.supplemental\],sugs,\{liveEvidence:runMode==="live"\}\)/); +assert.match(index, /offlineEvidenceCapabilityIsReadOnly:/); assert.match(index, /claimedProbationEvents/); assert.match(api, /adaptive_probation: s\.adaptive_probation, probation_exit_hours: s\.probation_exit_hours/); assert.match(index, /function buildRuntimeFallbackMarketCache\(stored,state\)/);