JevHistoryPairwiseProtocol.java

原始文件

1package com.android.commands.monkey.ape.jev;
2
3import java.nio.charset.StandardCharsets;
4import java.util.*;
5import org.json.*;
6
7/** Frozen history-pairwise-offline-v1-20261031; only code aggregates into Probe eligibility. */
8final class JevHistoryPairwiseProtocol {
9    static final java.nio.charset.Charset UTF8=StandardCharsets.UTF_8;
10    static final String VERSION="history-pairwise-offline-v1-20261031";
11    static final String POLICY="exploratory-top3-v1";
12    /** P1 view marker; the frozen SYSTEM prompt text remains unchanged. */
13    static final String EVIDENCE_VIEW_VERSION="candidate-closure-v1";
14    static final String PROJECTED_SCHEMA=VERSION+":"+EVIDENCE_VIEW_VERSION;
15    static final String INCREMENTAL_VIEW_VERSION="incremental-candidate-closure-v2";
16    static final int MAX_HISTORY=3;
17    static final String RULES="Judge whether two concrete GUI actions represent the same application-function operation, using only the supplied GUI evidence and page context.\nSAME_OPERATION: the same application function, differing only in parameter, data object, or entry point.\nDIFFERENT_OPERATION: different application functions.\nINSUFFICIENT_EVIDENCE: the visible evidence is insufficient to judge reliably.\nThe same handler, class, resource ID, menu container, or switch is not sufficient evidence of operation equivalence. Judge application functions, not implementation form.";
18    /** Exact suffix and insertion used by the archived StartLux P1 comparison. */
19    static final String P1_DECISION_STANDARD="\n\nDecision standard:\n"
20        +"SAME_OPERATION means the candidate and the executed history are the same GUI operation pattern already represented; differences limited to data instances, entered values, or equivalent entry points are SAME.\n"
21        +"DIFFERENT_OPERATION means the candidate is a distinct command, feature entry, or interaction branch that the executed history does not represent and is reasonably worth one additional concrete-action attempt. Do not require proof of new method coverage, a new state, or successful completion.\n"
22        +"INSUFFICIENT_EVIDENCE means the supplied GUI and execution history do not support a reliable relation.\n"
23        +"Treat a visible menu-command label as evidence of the command. Do not equate a menu command with ordinary text entered into an EditText. Different Checklist rows with the same edit behavior are normally SAME. For a long-pressed EditText, do not infer a different block operation from the block name alone; it may be ordinary text selection. Similar class, resource ID, outer container, or ModelAction does not establish equivalence. Compare operation behavior, not wording alone.";
24    static final String PROMOTE="PROMOTE",SAME="SAME_OPERATION",DIFFERENT="DIFFERENT_OPERATION",INSUFFICIENT="INSUFFICIENT_EVIDENCE";
25    static final class Request {
26        final String requestId,bodyJson,inputSignature,inputFingerprint,sourceInputSignature,evidenceViewVersion;
27        final List<String> candidateIds;final int requestBytes,questionCount;
28        final JSONObject plan;
29        Request(String id,Prepared prepared,JSONObject payload)throws JSONException {
30            requestId=id;bodyJson=prepared.bodyJson;plan=new JSONObject(payload.toString());
31            inputSignature=prepared.inputSignature;inputFingerprint=prepared.inputFingerprint;requestBytes=prepared.requestBytes;
32            sourceInputSignature=payload.optString("source_input_signature",inputSignature);
33            evidenceViewVersion=payload.optString("evidence_view_version","");
34            questionCount=prepared.questionCount;candidateIds=new ArrayList<String>(prepared.candidateIds);
35        }
36    }
37    static final class Decision {
38        final String candidateId,representativeModelActionId;
39        final double confidence;final boolean resolved;
40        JSONObject audit=new JSONObject();
41        Decision(String id,String choice,double score){this(id,choice,score,!"UNRESOLVED".equals(choice));}
42        Decision(String id,String choice,double score,boolean certain){candidateId=id;representativeModelActionId=choice;confidence=score;resolved=certain;}
43        boolean promote(){return resolved && PROMOTE.equals(representativeModelActionId);}
44        JSONObject audit(JSONObject target)throws JSONException {
45            for(Iterator<?> it=audit.keys();it.hasNext();){String key=(String)it.next();target.put(key,audit.get(key));}
46            return target;
47        }
48    }
49    static final class Result {
50        final Map<String,Decision> decisions;
51        final Map<String,JSONObject> pairAnswers;
52        Result(Map<String,Decision> out,Map<String,JSONObject> pairs){decisions=out;pairAnswers=pairs;}
53    }
54    static final class BudgetExceeded extends JSONException {
55        final int requestBytes,stateBytes,questionsBytes,candidateCount,pairCount,limitBytes;
56        BudgetExceeded(int requestBytes,int candidateCount,int pairCount,int stateBytes,int questionsBytes,int limit)throws JSONException {
57            super("SEMANTIC_REQUEST_BUDGET_EXCEEDED");
58            this.requestBytes=requestBytes;this.candidateCount=candidateCount;this.pairCount=pairCount;this.limitBytes=limit;
59            this.stateBytes=stateBytes;this.questionsBytes=questionsBytes;
60        }
61    }
62    static final class Prepared {
63        final JSONObject body;
64        final String bodyJson,inputSignature,inputFingerprint;
65        final int requestBytes,questionCount;
66        final List<String> candidateIds;
67        Prepared(JSONObject body,JSONObject payload,String bodyJson,String fingerprint,int bytes,int count)throws JSONException {
68            this.body=body;this.bodyJson=bodyJson;inputSignature=payload.getString("input_signature");inputFingerprint=fingerprint;
69            requestBytes=bytes;questionCount=count;
70            candidateIds=new ArrayList<String>();JSONArray ids=payload.getJSONArray("candidate_ids");
71            for(int i=0;i<ids.length();i++)candidateIds.add(ids.getString(i));
72        }
73        Request request(String id,JSONObject payload)throws JSONException{return new Request(id,this,payload);}
74    }
75    static Request request(String id,String model,JSONObject payload,int maxBytes)throws JSONException {
76        Prepared prepared=prepare(model,payload,maxBytes);
77        return prepared.request(id,payload);
78    }
79    static Prepared prepare(String model,JSONObject payload,int maxBytes)throws JSONException {
80        String view=payload.optString("evidence_view_version","");
81        if(!view.isEmpty() && (!(EVIDENCE_VIEW_VERSION.equals(view)||INCREMENTAL_VIEW_VERSION.equals(view))
82                || !(VERSION+":"+view).equals(payload.getJSONObject("state").optString("evidence_schema"))
83                || payload.optString("source_input_signature").isEmpty()))throw new JSONException("UNKNOWN_EVIDENCE_VIEW");
84        if(view.isEmpty() && (PROJECTED_SCHEMA.equals(payload.getJSONObject("state").optString("evidence_schema"))
85                || (VERSION+":"+INCREMENTAL_VIEW_VERSION).equals(payload.getJSONObject("state").optString("evidence_schema"))))
86            throw new JSONException("MISSING_EVIDENCE_VIEW");
87        long questionStart=JevReviewProfile.start();
88        JSONObject questions=new JSONObject(),criteria=new JSONObject()
89            .put(SAME,"Same application-function operation; only parameter, data object, or entry point differs.")
90            .put(DIFFERENT,"Different application-function operations.")
91            .put(INSUFFICIENT,"The current visible GUI evidence is insufficient to determine the operation relation reliably.");
92        JSONArray pairs=payload.getJSONArray("pairs");
93        Set<String> bindings=new HashSet<String>();
94        for(int i=0;i<pairs.length();i++) {
95            JevDeadline.check();JSONObject pair=pairs.getJSONObject(i);String c=pair.getString("candidate"),h=pair.getString("history");
96            String q=pair.getString("id");
97            if(questions.has(q) || !bindings.add(c+"|"+h)
98                    || !payload.getJSONObject("state").getJSONObject("candidates").has(c)
99                    || !payload.getJSONObject("state").getJSONObject("executed_actions").has(h))
100                throw new JSONException("invalid or duplicate pair binding");
101            questions.put(q,new JSONObject().put("type","choice").put("criteria",criteria)
102                .put("instructions",RULES+"\n\nIs candidate at `candidates."+c+"` the same application-function operation as executed action at `executed_actions."+h+"`? Interpret each action using its own source_page_id in pages."+P1_DECISION_STANDARD));
103        }
104        JevReviewProfile.end("questions",questionStart);
105        if(questions.length()==0)throw new JSONException("NO_RECOVERABLE_PAIRS");
106        JSONObject body=new JSONObject().put("model",model).put("state",payload.getJSONObject("state")).put("questions",questions);
107        long serializeStart=JevReviewProfile.start();
108        String bodyJson=body.toString();
109        JevReviewProfile.end("wire_serialize",serializeStart);
110        long sizeStart=JevReviewProfile.start();
111        int bytes=bodyJson.getBytes(UTF8).length;
112        JevReviewProfile.end("wire_size",sizeStart);
113        JevReviewProfile.add("serialized_wire_bytes",0,bytes);
114        int limit=Math.min(maxBytes,32768);
115        if(bytes>limit)throw new BudgetExceeded(bytes,payload.getJSONArray("candidate_ids").length(),questions.length(),
116            body.getJSONObject("state").toString().getBytes(UTF8).length,questions.toString().getBytes(UTF8).length,limit);
117        return new Prepared(body,payload,bodyJson,JevEvidence.digest(bodyJson),bytes,questions.length());
118    }
119    static Result fromProvider(String raw,Request request)throws JSONException {
120        JSONObject answers=new JSONObject(raw).getJSONObject("answers"),questions=new JSONObject(request.bodyJson).getJSONObject("questions");
121        if(answers.length()!=questions.length())throw new JSONException("incomplete pair answers");
122        for(Iterator<?> it=answers.keys();it.hasNext();)if(!questions.has((String)it.next()))throw new JSONException("unknown pair answer");
123        JSONArray pairs=request.plan.getJSONArray("pairs");
124        Map<String,List<JSONObject>> byCandidate=new LinkedHashMap<String,List<JSONObject>>();
125        Map<String,JSONObject> pairAnswers=new LinkedHashMap<String,JSONObject>();
126        for(int i=0;i<pairs.length();i++) {
127            JevDeadline.check();JSONObject pair=pairs.getJSONObject(i),answer=answers.getJSONObject(pair.getString("id"));
128            String choice=answer.getString("choice");
129            if(!SAME.equals(choice)&&!DIFFERENT.equals(choice)&&!INSUFFICIENT.equals(choice))throw new JSONException("invalid pair choice");
130            double confidence=answer.getDouble("confidence");
131            if(!Double.isFinite(confidence)||confidence<0||confidence>1)throw new JSONException("invalid pair confidence");
132            if(answer.has("probabilities")) {
133                JSONObject probabilities=answer.getJSONObject("probabilities");
134                if(probabilities.length()!=3)throw new JSONException("invalid pair probabilities");
135                for(String option:new String[]{SAME,DIFFERENT,INSUFFICIENT}) {
136                    double value=probabilities.getDouble(option);
137                    if(!Double.isFinite(value)||value<0||value>1)throw new JSONException("invalid pair probability");
138                }
139            }
140            String c=pair.getString("candidate");List<JSONObject> values=byCandidate.get(c);
141            if(values==null){values=new ArrayList<JSONObject>();byCandidate.put(c,values);}values.add(answer);
142            pairAnswers.put(pairFingerprint(request,pair),new JSONObject(answer.toString()));
143        }
144        Map<String,Decision> decisions=new LinkedHashMap<String,Decision>();
145        JSONObject coverage=request.plan.getJSONObject("coverage");
146        for(String c:request.candidateIds) {
147            JSONObject expected=coverage.getJSONObject(c);List<JSONObject> values=byCandidate.get(c);
148            double same=0;int different=0,rawSame=0,rawDifferent=0,insufficient=0,low=0,trustedSame=0;
149            if(values!=null)for(JSONObject value:values) {
150                double confidence=value.getDouble("confidence");String choice=value.getString("choice");
151                if(SAME.equals(choice))rawSame++;
152                if(DIFFERENT.equals(choice))rawDifferent++;
153                if(INSUFFICIENT.equals(choice))insufficient++;
154                if(confidence<=0.5)low++;
155                if(confidence>0.5 && SAME.equals(choice)){same=Math.max(same,confidence);trustedSame++;}
156                if(confidence>0.5 && DIFFERENT.equals(choice))different++;
157            }
158            int count=expected.getInt("selected_history"),comparisons=values==null?0:values.size();
159            if(count<0 || count>MAX_HISTORY || comparisons!=count)throw new JSONException("invalid selected history coverage");
160            boolean full=expected.getBoolean("complete") && count==expected.getInt("legal_history_count");
161            Decision decision;
162            if(same>0)decision=new Decision(c,"REPRESENTED",same);
163            else if(different>0)decision=new Decision(c,PROMOTE,same==0?maxTrustedDifferent(values):0);
164            else decision=new Decision(c,"UNRESOLVED",comparisons==1?values.get(0).getDouble("confidence"):0,false);
165            decision.audit=new JSONObject().put("aggregation_policy",POLICY)
166                .put("total_history",expected.getInt("legal_history_count")).put("history_complete",expected.optBoolean("history_collection_complete",expected.getBoolean("complete")))
167                .put("unknown_mapping_count",expected.optInt("unknown_mapping_count"))
168                .put("recoverable_history_count",expected.getInt("recoverable_history_count"))
169                .put("selected_history",count).put("comparison_count",comparisons)
170                .put("same_count",rawSame).put("different_count",rawDifferent).put("insufficient_count",insufficient)
171                .put("trusted_same_count",trustedSame).put("trusted_different_count",different).put("low_confidence_count",low)
172                .put("full_history_compared",full).put("exploratory",decision.promote() && !full);
173            decisions.put(c,decision);
174        }
175        return new Result(decisions,pairAnswers);
176    }
177    private static double maxTrustedDifferent(List<JSONObject> values)throws JSONException {
178        double max=0;for(JSONObject value:values)if(DIFFERENT.equals(value.getString("choice")) && value.getDouble("confidence")>0.5)max=Math.max(max,value.getDouble("confidence"));return max;
179    }
180    /** Stable identity for one Pair; excludes unrelated candidates in the same request. */
181    static String pairFingerprint(Request request,JSONObject pair)throws JSONException {
182        JSONObject wire=new JSONObject(request.bodyJson);
183        JSONObject state=wire.getJSONObject("state"),candidate=state.getJSONObject("candidates").getJSONObject(pair.getString("candidate"));
184        JSONObject history=state.getJSONObject("executed_actions").getJSONObject(pair.getString("history"));
185        JSONArray pages=new JSONArray();
186        for(JSONObject action:new JSONObject[]{candidate,history}) {
187            String page=action.optString("source_page_id","");
188            if(!page.isEmpty() && state.getJSONObject("pages").has(page))pages.put(state.getJSONObject("pages").get(page));
189        }
190        JSONObject candidateKey=new JSONObject(candidate.toString());candidateKey.remove("source_page_id");
191        JSONObject historyKey=new JSONObject(history.toString());historyKey.remove("source_page_id");
192        JSONObject value=new JSONObject().put("protocol",VERSION).put("model",wire.getString("model"))
193            .put("contract",request.plan.optString("review_contract","")).put("provider_endpoint",request.plan.optString("provider_endpoint","")).put("state_id",request.plan.optString("state_id","")).put("model_version",request.plan.optLong("model_version",-1)).put("evidence_schema",state.optString("evidence_schema","")).put("pair_role","candidate-vs-history")
194            .put("criteria",new JSONObject().put(SAME,"Same application-function operation; only parameter, data object, or entry point differs.").put(DIFFERENT,"Different application-function operations.").put(INSUFFICIENT,"The current visible GUI evidence is insufficient to determine the operation relation reliably."))
195            .put("rules",RULES+P1_DECISION_STANDARD).put("candidate",candidateKey).put("history",historyKey)
196            .put("history_provenance",request.plan.getJSONObject("history_provenance").optJSONObject(pair.getString("history")))
197            .put("pages",pages);
198        return JevEvidence.digest(JevSharedEvidence.canonical(value));
199    }
200    /** Attribute ranking only; equal signatures are NOT asserted to be semantic equivalents. */
201    static List<String> selectHistory(JSONObject candidate,Map<String,JSONObject> history)throws JSONException {
202        final Map<String,Set<String>> features=new LinkedHashMap<String,Set<String>>();
203        for(String id:history.keySet()){JevDeadline.check();features.put(id,selectionFeatures(history.get(id)));}
204        return selectHistory(candidate,history.keySet(),features);
205    }
206    static List<String> selectHistory(JSONObject candidate,Collection<String> historyIds,Map<String,Set<String>> features)throws JSONException {
207        return selectHistoryFeatures(selectionFeatures(candidate),historyIds,features);
208    }
209    static List<String> selectHistoryFeatures(Set<String> wanted,Collection<String> historyIds,Map<String,Set<String>> features)throws JSONException {
210        final Map<String,Integer> scores=new HashMap<String,Integer>();
211        List<String> ranked=new ArrayList<String>(historyIds);
212        for(String id:ranked){JevDeadline.check();Set<String> f=features.get(id);
213            if(f==null)throw new JSONException("missing cached history selection features");
214            int score=0;for(String value:f)if(wanted.contains(value))score+=value.startsWith("label:")?100:value.startsWith("word:")?10:value.startsWith("resource_id:")?8:2;
215            scores.put(id,score);}
216        // Stable sort preserves the Registry's chronological / identity tie order.
217        Collections.sort(ranked,new Comparator<String>(){public int compare(String a,String b){return Integer.compare(scores.get(b),scores.get(a));}});
218        List<String> selected=new ArrayList<String>();Set<Set<String>> seen=new HashSet<Set<String>>();
219        while(!ranked.isEmpty() && selected.size()<MAX_HISTORY){
220            JevDeadline.check();String next=ranked.get(0);
221            if(!selected.isEmpty())for(String id:ranked)if(!seen.contains(features.get(id))){next=id;break;}
222            selected.add(next);seen.add(features.get(next));ranked.remove(next);
223        }
224        return selected;
225    }
226    static Set<String> selectionFeatures(JSONObject control)throws JSONException {
227        Set<String> out=new TreeSet<String>();if(control==null)return out;
228        addFeatures(control,out);JSONArray subtree=control.optJSONArray("target_subtree");
229        if(subtree!=null)for(int i=0;i<subtree.length();i++)addFeatures(subtree.getJSONObject(i),out);
230        return out;
231    }
232    private static void addFeatures(JSONObject node,Set<String> out)throws JSONException {
233        for(String field:new String[]{"text","content_desc","resource_id","class","action_type"}) {
234            addSelectionFeature(field,node.optString(field,""),out);
235        }
236    }
237    static void addSelectionFeature(String field,String raw,Set<String> out) {
238        String value=raw.trim().toLowerCase(Locale.ROOT);if(value.isEmpty())return;
239        if(field.equals("text")||field.equals("content_desc")){
240            out.add("label:"+value);for(String word:value.split("[^\\p{L}\\p{N}]+"))if(!word.isEmpty())out.add("word:"+word);
241        }else out.add(field+":"+value);
242    }
243    private JevHistoryPairwiseProtocol(){}
244}