Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
89ce465e64 |
6 changed files with 129 additions and 0 deletions
12
skills/guanghu-feishu-glp/PROFILE.hldp
Normal file
12
skills/guanghu-feishu-glp/PROFILE.hldp
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
HLDP://guanghu/feishu/glp-readonly-profile/v1 · 2026-10-19
|
||||
├── meta: {"id": "FEISHU-GLP-READONLY-PROFILE-001", "version": "1.0.0", "source_class": "ENGINEERING_PROFILE_NOT_NEW_WORLD_STANDARD"}
|
||||
├── trigger: {"zh": "冰朔指定由京东承载运行,飞书晓通过技能工具递送协议消息;本次完成通信契约与接收器。", "en": "Bingshuo selects JD execution and protocol-message transport from Feishu Xiao; this delivery implements the contract and receiver."}
|
||||
├── emergence: {"zh": "采用GLP/1.0标准信封;REQUEST调用零点原核只读ask,SYNC只查询公开规则发行版本。HTTP一次返回独立ACK、RESPONSE与可选RECEIPT,不是实时流式ACK。", "en": "Use GLP/1.0 envelopes. REQUEST asks Zero Core read-only; SYNC queries public release metadata only. One HTTP response contains distinct ACK, RESPONSE and optional RECEIPT, not a streamed immediate acknowledgement."}
|
||||
├── lock: {"zh": "只问不喂;不调用say、不改脑、不直接执行命令。飞书个人身份与记忆仍属于晓,零点原核回答不冒充晓本人。", "en": "Read-only questions never feed, edit brains or execute commands. Xiao retains its identity and memory; Zero Core replies do not impersonate Xiao."}
|
||||
├── why: {"zh": "复用已有常在线京东及零点问答,不需要飞书AI付费托管。技能说明与真实HTTP客户端一并交付,飞书适配器仍待接线。", "en": "Reuse existing always-on JD and Zero Core without a paid Feishu AI runtime. Deliver real HTTP client code with the skill; the Feishu adapter remains pending."}
|
||||
├── rejected: {"zh": "协议格式、checksum、sender字段均不授予执行权或证明真人。身份范围来自独立传输凭据;不把问答写成本人直接原话或学习事件。", "en": "Protocol syntax, checksums and sender fields do not confer authority or prove a human. Independent transport credentials constrain sender scope; questions are not direct-human food or learning events."}
|
||||
├── sources: {"zh": "冰朔本任务直接原话和已核通信协议来源。", "en": "Current owner language and verified communication standards.", "method_sha256": "6b6fb0efd7816dfc28ccf4acd0ae3d8aee04ac4400c6253d650e6b7d1a069789", "authorization_sha256": "4ccd2207a0ee63978b7e1ac8e488679038b9fbfd12c8d1066ca7e75f7dad1415", "protocol_pins": [{"id": "GLS-0300", "sha256": "9d02bb01a7f2ff7b9f7a4dbefe356cd823e71095aa1dcf0e9cc3c98cd3417be2"}, {"id": "GLS-0302", "sha256": "1e8b06ca9bcde879b0295226b9d55ed782db75266eab6a8f9bbdb3a6dedb655d"}, {"id": "GLS-0305", "sha256": "6b4ef3a9c79f9178e918bfb3109aea144f13ee7d94fe839f7ebd55b8d92fec4c"}, {"id": "GLS-0306", "sha256": "8469e8284a749315755d8f3992580c1612338ab041d7b542d321de9fdeaf99b0"}]}
|
||||
├── interface: {"method": "POST", "path": "/v1/glp", "message_types": ["REQUEST", "SYNC"], "sender_scope": "AUTHENTICATED_CLIENT_PERSONAS", "receiver": "ICE-CH-ZC001", "auth": "BEARER_OVER_TLS_OR_LOOPBACK", "canonical_json": "OBJECT_KEYS_SORTED_RECURSIVELY_ARRAY_ORDER_PRESERVED_UTF8", "checksum": "SHA256_CANONICAL_MESSAGE_WITH_EMPTY_INTEGRITY_CHECKSUM_AND_SIGNATURE", "signature": "EMPTY_TRANSPORT_AUTH_PROFILE_NOT_ASYMMETRIC_SIGNATURE", "max_body_bytes": 24000, "max_question_bytes": 12000, "max_ttl_seconds": 300, "rate_per_client_per_minute": 12, "global_parallel_limit": 2, "replay_scope": "PROCESS_LIFETIME_UNTIL_MESSAGE_EXPIRY", "replay_cache_capacity": 256, "replay_restart_guarantee": false, "private_question_logs": "NONE_IN_RECEIVER", "sync_adopts_rules": false, "public_routing_installed": false}
|
||||
├── errors: {"unauthenticated": 401, "invalid_envelope": 400, "message_id_rebound": 409, "body_too_large": 413, "rate_or_parallel_limit": 429, "upstream_withheld": 502, "cache_full_or_receiver_failure": 503}
|
||||
├── relations: {"mother": "TCS", "communication": "GLP", "history": "HLDP", "standards": "GLS"}
|
||||
└── writeback: {"zh": "交付代码、技能包、测试与京东隔离验证回执;正式常驻与飞书工具配置在后续环节验收。", "en": "Deliver source, skill, tests and an isolated JD verification receipt. Persistent production operation and Feishu tool configuration are later acceptance stages."}
|
||||
21
skills/guanghu-feishu-glp/SKILL.md
Normal file
21
skills/guanghu-feishu-glp/SKILL.md
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
---
|
||||
name: guanghu-feishu-glp
|
||||
description: 晓在飞书向京东零点原核递送只读问答或查询世界规则版本时使用;包含GLP信封、HTTP客户端与服务器接收器。普通文档编辑或现实执行任务不适用。
|
||||
---
|
||||
|
||||
先读同目录 PROFILE.hldp 和飞书办公室的原话瞄点。该目录是可单独复制的技能包,包含 contract.mjs、client.mjs、receiver.mjs 和测试;服务器部署来源是已核字节包。
|
||||
|
||||
从本技能目录运行:
|
||||
|
||||
node client.mjs build 问题.txt 本任务原生会话号 本任务工单号 > request.json
|
||||
node client.mjs send request.json https://本人配置的服务/v1/glp
|
||||
|
||||
发送需在私有环境提供 GUANGHU_GLP_TOKEN,不能写入技能、仓库、文档或日志。服务地址必须由用户的部署配置明确给出;本包没有可自动使用的公网端点。当前只做京东隔离验证,飞书收发适配器尚未接通。飞书端若无执行本脚本的工具,需要后续桥接器提供等价HTTP调用;阅读技能本身不会产生工具能力。
|
||||
|
||||
REQUEST 表示只读问题;build 命令第四个参数 SYNC 表示只查询公开规则发行版本。SYNC返回可核验版本与哈希,不自动采纳规则,更不是完整人格脑同步。GLP请求只送入外部问题入口,不冒充冰朔本人语言,不写零点或晓的脑。
|
||||
|
||||
ACK-RECEIVED是收到;RESPONSE承载结果;ACK-EXECUTED是已完成本次只读查询。失败返回ACK-FAILED,不能把收到写成执行成功。一次HTTP返回这些独立阶段,不提供立即流式ACK。网络重试复用同一份 request.json 和 message_id;相同编号改内容会拒绝。过期后重新生成请求;进程重启不保证去重延续,禁止拿本包执行不可重复的现实动作。
|
||||
|
||||
checksum只证明字节一致,不是身份签名;发送主体范围由独立传输凭据约束。本工程剖面使用空signature。需要人类身份、学习、写入或现实动作时走相应系统的既有入口,不能扩展本问答接口。
|
||||
|
||||
本地验证:node --test receiver.test.mjs。服务器由操作者设置 GUANGHU_GLP_CONFIG 指向既有私有客户端哈希配置,并以 GUANGHU_GLP_PORT 指定空闲端口运行 node receiver.mjs;接收器始终只绑定127.0.0.1。默认后端固定为本机3931零点ask和3922公开规则manifest,不接受消息指定目标URL、文件路径或命令。
|
||||
7
skills/guanghu-feishu-glp/client.mjs
Normal file
7
skills/guanghu-feishu-glp/client.mjs
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import fs from 'node:fs';import {request} from './contract.mjs';
|
||||
const [mode,...a]=process.argv.slice(2);
|
||||
try{
|
||||
if(mode==='build'){if(a.length<3||a.length>4)throw Error('QUESTION_FILE_SESSION_TASK_REQUIRED');console.log(JSON.stringify(request(fs.readFileSync(a[0],'utf8'),{session:a[1],task:a[2],type:a[3]||'REQUEST'}),null,2));}
|
||||
else if(mode==='send'){if(a.length!==2||!process.env.GUANGHU_GLP_TOKEN)throw Error('MESSAGE_URL_AND_PRIVATE_TOKEN_REQUIRED');const u=new URL(a[1]);if(u.username||u.password||u.search||u.hash||u.pathname!=='/v1/glp'||!(u.protocol==='https:'||(u.protocol==='http:'&&['127.0.0.1','[::1]'].includes(u.hostname))))throw Error('GLP_ENDPOINT_INVALID');const r=await fetch(u,{method:'POST',redirect:'error',headers:{'content-type':'application/json',authorization:'Bearer '+process.env.GUANGHU_GLP_TOKEN},body:fs.readFileSync(a[0]),signal:AbortSignal.timeout(65000)});console.log(JSON.stringify(await r.json(),null,2));if(!r.ok)process.exitCode=2;}
|
||||
else throw Error('COMMAND_NOT_SUPPORTED');
|
||||
}catch(e){console.log(JSON.stringify({error:/^[A-Z_]+$/.test(e.message)?e.message:'GLP_TRANSPORT_FAILED'}));process.exitCode=2;}
|
||||
29
skills/guanghu-feishu-glp/contract.mjs
Normal file
29
skills/guanghu-feishu-glp/contract.mjs
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
import crypto from 'node:crypto';
|
||||
export const ZERO='GLW-2.0-BRAIN-BINGSHUO-MASTER-0001', CHANNEL='ICE-CH-ZC001';
|
||||
export const canonical=x=>Array.isArray(x)?'['+x.map(canonical).join(',')+']':x&&typeof x==='object'?'{'+Object.keys(x).sort().map(k=>JSON.stringify(k)+':'+canonical(x[k])).join(',')+'}':JSON.stringify(x);
|
||||
export const hash=x=>crypto.createHash('sha256').update(typeof x==='string'?x:canonical(x)).digest('hex');
|
||||
export function seal(m){const c=structuredClone(m);c.integrity={checksum:'',signature:''};c.integrity.checksum=hash(c);return {glp_message:c};}
|
||||
export function request(question,{type='REQUEST',id=crypto.randomUUID(),sender='ICE-P-X1016',session,task,now=Date.now()}={}){
|
||||
return seal({protocol:'GLP/1.0',message_id:id,message_type:type,created_at:new Date(now).toISOString(),sender:{object_id:sender,object_type:'persona',world_path:'glw://GLW-2.0-0001/persona/'+sender},receiver:{object_id:CHANNEL,object_type:'channel',routing_mode:'direct'},context:{conversation_id:session,parent_message_id:'',relation_id:'',task_id:task,hldp_anchor:''},payload:{language:'zh-CN',content_type:'text',content:question,attachments:[]},control:{priority:'normal',ack_required:true,receipt_required:true,expires_at:new Date(now+300000).toISOString(),retry_policy:'safe'},integrity:{}});
|
||||
}
|
||||
const need=(v,c)=>{if(!v)throw Error(c);};
|
||||
function keys(o,list){need(o&&typeof o==='object'&&!Array.isArray(o)&&Object.keys(o).length===list.length&&list.every(k=>Object.hasOwn(o,k)),'GLP_FIELDS_INVALID');}
|
||||
export function validate(input,client,now=Date.now()){
|
||||
keys(input,['glp_message']);const m=input.glp_message;
|
||||
keys(m,['protocol','message_id','message_type','created_at','sender','receiver','context','payload','control','integrity']);
|
||||
need(m.protocol==='GLP/1.0'&&['REQUEST','SYNC'].includes(m.message_type),'GLP_TYPE_UNSUPPORTED');
|
||||
need(typeof m.message_id==='string'&&/^[-A-Za-z0-9._:]{1,160}$/.test(m.message_id),'GLP_ID_INVALID');
|
||||
keys(m.sender,['object_id','object_type','world_path']);need(m.sender.object_type==='persona'&&client.personas?.includes(m.sender.object_id),'GLP_SENDER_OUTSIDE_SCOPE');
|
||||
need(m.sender.world_path==='glw://GLW-2.0-0001/persona/'+m.sender.object_id,'GLP_WORLD_INVALID');
|
||||
keys(m.receiver,['object_id','object_type','routing_mode']);need(m.receiver.object_id===CHANNEL&&m.receiver.object_type==='channel'&&m.receiver.routing_mode==='direct','GLP_TARGET_INVALID');
|
||||
keys(m.context,['conversation_id','parent_message_id','relation_id','task_id','hldp_anchor']);
|
||||
need(Object.values(m.context).every(x=>typeof x==='string'&&x.length<=300)&&m.context.conversation_id.trim()&&m.context.task_id.trim(),'GLP_CONTEXT_REQUIRED');
|
||||
keys(m.payload,['language','content_type','content','attachments']);need(m.payload.language==='zh-CN'&&m.payload.content_type==='text'&&typeof m.payload.content==='string'&&m.payload.content.trim()&&Buffer.byteLength(m.payload.content)<=12000&&Array.isArray(m.payload.attachments)&&m.payload.attachments.length===0,'GLP_PAYLOAD_INVALID');
|
||||
keys(m.control,['priority','ack_required','receipt_required','expires_at','retry_policy']);
|
||||
const created=Date.parse(m.created_at),expires=Date.parse(m.control.expires_at);
|
||||
need(Number.isFinite(created)&&Number.isFinite(expires)&&created<=now+30000&&expires>now&&expires>created&&expires-created<=300000,'GLP_EXPIRED_OR_TIME_INVALID');
|
||||
need(['normal','low'].includes(m.control.priority)&&m.control.ack_required===true&&typeof m.control.receipt_required==='boolean'&&['none','safe'].includes(m.control.retry_policy),'GLP_CONTROL_UNSUPPORTED');
|
||||
keys(m.integrity,['checksum','signature']);need(m.integrity.signature===''&&/^[a-f0-9]{64}$/.test(m.integrity.checksum),'GLP_INTEGRITY_INVALID');
|
||||
const clean=structuredClone(m);clean.integrity.checksum='';need(hash(clean)===m.integrity.checksum,'GLP_CHECKSUM_MISMATCH');return m;
|
||||
}
|
||||
export function reply(m,type,content){return seal({protocol:'GLP/1.0',message_id:crypto.randomUUID(),message_type:type,created_at:new Date().toISOString(),sender:{object_id:ZERO,object_type:'system',world_path:'glw://GLW-2.0-0001/channel/'+CHANNEL},receiver:{object_id:m.sender.object_id,object_type:m.sender.object_type,routing_mode:'direct'},context:{...m.context,parent_message_id:m.message_id},payload:{language:'zh-CN',content_type:'state',content,attachments:[]},control:{priority:'normal',ack_required:false,receipt_required:false,expires_at:m.control.expires_at,retry_policy:'none'},integrity:{}}).glp_message;}
|
||||
38
skills/guanghu-feishu-glp/receiver.mjs
Normal file
38
skills/guanghu-feishu-glp/receiver.mjs
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
import http from 'node:http';import fs from 'node:fs';import crypto from 'node:crypto';import {pathToFileURL} from 'node:url';
|
||||
import {ZERO,hash,validate,reply} from './contract.mjs';
|
||||
const need=(v,c)=>{if(!v)throw Error(c);};
|
||||
export function authenticate(raw,config,now=Date.now()){
|
||||
if(typeof raw!=='string'||!raw.startsWith('Bearer '))return null;const digest=hash(raw.slice(7));
|
||||
return config.clients?.find(c=>/^[a-f0-9]{64}$/.test(c.key_sha256||'')&&crypto.timingSafeEqual(Buffer.from(digest,'hex'),Buffer.from(c.key_sha256,'hex'))&&Date.parse(c.expires_at)>now)||null;
|
||||
}
|
||||
async function json(url,options={}){const r=await fetch(url,{...options,redirect:'error',signal:AbortSignal.timeout(55000)});need(r.ok,'ZERO_UPSTREAM_UNAVAILABLE');return r.json();}
|
||||
export async function zeroQuestion(text,{get=json}={}){
|
||||
const before=await get('http://127.0.0.1:3931/status');need(before.brain_id===ZERO&&before.integrity==='BRAIN_VERIFIED'&&before.node_role==='REPLICA_READ_ONLY'&&before.brain_write_allowed===false,'ZERO_IDENTITY_OR_ROLE_INVALID');
|
||||
const r=await get('http://127.0.0.1:3931/ask',{method:'POST',headers:{'content-type':'application/json'},body:JSON.stringify({text})});
|
||||
need(r.schema==='guanghu.zeropoint-channel-ask-receipt/v1'&&r.no_state_change===true&&r.brain_turn?.speaker?.brain_id===ZERO&&r.brain_turn.brain_revision===before.revision&&r.brain_turn.no_state_change===true&&r.brain_turn.input_sha256===hash(text)&&r.brain_turn.reply_sha256===hash(r.spoken)&&r.brain_turn.reply===r.spoken&&r.brain_turn?.outcome==='BRAIN_REPLY_ACCEPTED'&&typeof r.spoken==='string'&&r.spoken.trim(),'ZERO_REPLY_WITHHELD');
|
||||
const after=await get('http://127.0.0.1:3931/status');need(after.brain_id===before.brain_id&&after.integrity==='BRAIN_VERIFIED','ZERO_IDENTITY_CHANGED');
|
||||
need(after.node_role==='REPLICA_READ_ONLY'&&after.brain_write_allowed===false&&after.revision===before.revision,'ZERO_SOURCE_CHANGED');
|
||||
return {brain_id:ZERO,revision:before.revision,answer:r.spoken,no_state_change:true};
|
||||
}
|
||||
export function createReceiver({config,ask=zeroQuestion,rules=async()=>{const r=await json('http://127.0.0.1:3922/v1/manifest');need(typeof r.version==='string'&&/^[a-f0-9]{64}$/.test(r.bundle_sha256),'RULE_RELEASE_INVALID');return {version:r.version,bundle_sha256:r.bundle_sha256,bundle_url:r.bundle_url,automatic_adoption:false};},now=Date.now,capacity=256}={}){
|
||||
const messages=new Map(),buckets=new Map();let active=0;
|
||||
const send=(res,status,data)=>{res.writeHead(status,{'content-type':'application/json; charset=utf-8','cache-control':'no-store','x-content-type-options':'nosniff'});res.end(JSON.stringify(data));};
|
||||
const server=http.createServer(async(req,res)=>{try{
|
||||
if(req.method==='GET'&&req.url==='/health')return send(res,200,{state:'GLP_RECEIVER_READY',profile:'FEISHU_ZERO_READONLY_1',production_feishu_connected:false,no_execution_authority:true});
|
||||
if(req.method!=='POST'||req.url!=='/v1/glp')return send(res,404,{error:'ROUTE_NOT_REGISTERED'});
|
||||
const c=authenticate(req.headers.authorization,config);if(!c)return send(res,401,{error:'GLP_AUTH_REQUIRED'});
|
||||
if(!String(req.headers['content-type']||'').startsWith('application/json'))return send(res,415,{error:'JSON_REQUIRED'});
|
||||
const chunks=[];let size=0;for await(const b of req){size+=b.length;if(size>24000)return send(res,413,{error:'GLP_BODY_TOO_LARGE'});chunks.push(b);}
|
||||
let input,m;try{input=JSON.parse(Buffer.concat(chunks));m=validate(input,c,now());}catch(e){return send(res,400,{error:/^[A-Z_]+$/.test(e.message)?e.message:'GLP_JSON_INVALID'});}
|
||||
const k=c.id+':'+m.message_id,digest=hash(input),old=messages.get(k);if(old){if(old.digest!==digest)return send(res,409,{error:'GLP_MESSAGE_ID_REBOUND'});const r=await old.promise;return send(res,r.status,r.value);}
|
||||
for(const [id,v] of messages)if(v.expires<=now())messages.delete(id);
|
||||
const bucket=buckets.get(c.id)||{start:now(),count:0};if(now()-bucket.start>=60000){bucket.start=now();bucket.count=0;}if(bucket.count>=12||active>=2)return send(res,429,{error:'GLP_CAPACITY_LIMIT'});if(messages.size>=capacity)return send(res,503,{error:'GLP_REPLAY_CACHE_FULL'});bucket.count++;buckets.set(c.id,bucket);active++;
|
||||
const ack=reply(m,'ACK',{status:'ACK-RECEIVED'});const promise=(async()=>{try{
|
||||
const result=m.message_type==='REQUEST'?await ask(m.payload.content):await rules();
|
||||
const response=reply(m,'RESPONSE',result),receipt=reply(m,'RECEIPT',{status:'ACK-EXECUTED',operation:m.message_type==='REQUEST'?'READ_ONLY_ZERO_QUESTION':'RULE_VERSION_QUERY',no_neural_state_write:true,no_execution_authority:true});
|
||||
return {status:200,value:{schema:'guanghu.glp-readonly-exchange/v1',request_id:m.message_id,ack,response,...(m.control.receipt_required?{receipt}:{}),no_execution_authority:true}};
|
||||
}catch{return {status:502,value:{error:'GLP_UPSTREAM_UNAVAILABLE',request_id:m.message_id,ack:reply(m,'ACK',{status:'ACK-RECEIVED'}),receipt:reply(m,'RECEIPT',{status:'ACK-FAILED',reply_withheld:true}),no_execution_authority:true}};}finally{active--;}})();
|
||||
messages.set(k,{digest,promise,expires:Date.parse(m.control.expires_at)});const r=await promise;send(res,r.status,r.value);
|
||||
}catch{if(!res.headersSent)send(res,503,{error:'GLP_RECEIVER_UNAVAILABLE'});else res.destroy();}});server.requestTimeout=65000;server.headersTimeout=10000;return server;
|
||||
}
|
||||
if(process.argv[1]&&import.meta.url===pathToFileURL(process.argv[1]).href){const file=process.env.GUANGHU_GLP_CONFIG;need(file,'GLP_CONFIG_REQUIRED');createReceiver({config:JSON.parse(fs.readFileSync(file))}).listen(Number(process.env.GUANGHU_GLP_PORT||3926),'127.0.0.1');}
|
||||
22
skills/guanghu-feishu-glp/receiver.test.mjs
Normal file
22
skills/guanghu-feishu-glp/receiver.test.mjs
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import test from 'node:test';import assert from 'node:assert/strict';import {request,seal,hash,ZERO,validate} from './contract.mjs';import {createReceiver,zeroQuestion} from './receiver.mjs';
|
||||
const token='fixture-not-real-secret',client={id:'fixture',key_sha256:hash(token),expires_at:'2099-01-01T00:00:00Z',personas:['ICE-P-X1016']};
|
||||
const native=(answer,revision=5,text='q')=>({schema:'guanghu.zeropoint-channel-ask-receipt/v1',no_state_change:true,spoken:answer,brain_turn:{speaker:{brain_id:ZERO},brain_revision:revision,no_state_change:true,input_sha256:hash(text),reply_sha256:hash(answer),reply:answer,outcome:'BRAIN_REPLY_ACCEPTED'}});
|
||||
const make=()=>request('请说明这次只读问答的边界。',{session:'fixture-session',task:'fixture-task'});
|
||||
async function fixture(t,extra={}){let calls=0;const server=createReceiver({config:{clients:[client]},ask:async()=>{calls++;return {brain_id:ZERO,answer:'只读测试回答',no_state_change:true};},rules:async()=>({version:'fixture',automatic_adoption:false}),...extra});await new Promise(r=>server.listen(0,'127.0.0.1',r));t.after(()=>{server.closeAllConnections();server.close();});return {calls:()=>calls,url:'http://127.0.0.1:'+server.address().port,post:async(b=make(),auth=token)=>fetch('http://127.0.0.1:'+server.address().port+'/v1/glp',{method:'POST',headers:{'content-type':'application/json',...(auth?{authorization:'Bearer '+auth}:{})},body:JSON.stringify(b)})};}
|
||||
test('anonymous requests and wrong routes never invoke a brain',async t=>{const f=await fixture(t);assert.equal((await f.post(make(),null)).status,401);assert.equal((await fetch(f.url+'/say',{method:'POST'})).status,404);assert.equal(f.calls(),0);});
|
||||
test('valid GLP returns received, response and executed as separate stages',async t=>{const f=await fixture(t),m=make(),r=await f.post(m),j=await r.json();assert.equal(r.status,200);assert.equal(r.headers.get('cache-control'),'no-store');assert.equal(j.ack.message_type,'ACK');assert.equal(j.response.message_type,'RESPONSE');assert.equal(j.receipt.payload.content.status,'ACK-EXECUTED');assert.equal(j.response.context.parent_message_id,m.glp_message.message_id);assert.equal(j.no_execution_authority,true);});
|
||||
test('checksum, foreign persona, target, expiry and missing task are rejected',async t=>{const f=await fixture(t);for(const alter of [m=>m.payload.content+='变更',m=>m.sender.object_id='ICE-P-OTHER',m=>m.receiver.object_id='other',m=>m.control.expires_at='2000-01-01',m=>m.context.task_id='']){const m=make().glp_message;alter(m);const b=seal(m);if(m.payload.content.endsWith('变更'))b.glp_message.integrity.checksum='0'.repeat(64);assert.equal((await f.post(b)).status,400);}assert.equal(f.calls(),0);});
|
||||
test('extra execution fields and unsupported message types are rejected',async t=>{const f=await fixture(t);for(const alter of [m=>m.payload.command='rm',m=>m.message_type='BROADCAST',m=>m.integrity.signature='self-signed',m=>m.payload.attachments.push({url:'http://internal'})]){const m=make().glp_message;alter(m);const b=seal(m);if(m.integrity.signature)b.glp_message.integrity.signature='self-signed';assert.equal((await f.post(b)).status,400);}assert.equal(f.calls(),0);});
|
||||
test('exact replay returns one result; same id with new content conflicts',async t=>{const f=await fixture(t),m=make();const a=await(await f.post(m)).json(),b=await(await f.post(m)).json();assert.deepEqual(a,b);const changed=structuredClone(m.glp_message);changed.payload.content='另一问题';assert.equal((await f.post(seal(changed))).status,409);assert.equal(f.calls(),1);});
|
||||
test('concurrent retries share the same pending question',async t=>{let release,calls=0;const blocked=new Promise(r=>release=r),f=await fixture(t,{ask:async()=>{calls++;await blocked;return {answer:'ok'};}}),m=make();const a=f.post(m),b=f.post(m);await new Promise(r=>setTimeout(r,30));release();const [x,y]=await Promise.all([a,b]);assert.equal(x.status,200);assert.equal(y.status,200);assert.equal(calls,1);});
|
||||
test('SYNC queries release metadata without calling a brain or adopting rules',async t=>{const f=await fixture(t),j=await(await f.post(request('当前发行版本',{type:'SYNC',session:'s',task:'t'}))).json();assert.equal(j.response.payload.content.version,'fixture');assert.equal(j.response.payload.content.automatic_adoption,false);assert.equal(f.calls(),0);});
|
||||
test('upstream failure is withheld and cached with failed receipt',async t=>{let calls=0;const f=await fixture(t,{ask:async()=>{calls++;throw Error('secret /private/path');}}),m=make();for(let i=0;i<2;i++){const r=await f.post(m),j=await r.json();assert.equal(r.status,502);assert.equal(j.receipt.payload.content.status,'ACK-FAILED');assert.equal(JSON.stringify(j).includes('secret'),false);}assert.equal(calls,1);});
|
||||
test('a full replay cache refuses new requests instead of evicting live ids',async t=>{const f=await fixture(t,{capacity:1});assert.equal((await f.post()).status,200);assert.equal((await f.post()).status,503);assert.equal(f.calls(),1);});
|
||||
test('oversized messages do not reach a brain',async t=>{const f=await fixture(t);assert.equal((await f.post({padding:'x'.repeat(25000)})).status,413);const m=make();assert.throws(()=>validate(m,{personas:[]}),/SENDER/);});
|
||||
test('native Zero adapter checks own identity and read-only reply',async()=>{const status={brain_id:ZERO,revision:5,integrity:'BRAIN_VERIFIED',node_role:'REPLICA_READ_ONLY',brain_write_allowed:false};let urls=[];const get=async(u,o)=>{urls.push(u);if(u.endsWith('/status'))return status;assert.equal(JSON.parse(o.body).text,'q');return native('已核验回答');};assert.equal((await zeroQuestion('q',{get})).revision,5);assert.equal(urls.filter(x=>x.endsWith('/ask')).length,1);assert.equal(urls.some(x=>x.endsWith('/say')),false);});
|
||||
test('native adapter withholds wrong identity, rejected reply and changed source',async()=>{const status={brain_id:ZERO,revision:5,integrity:'BRAIN_VERIFIED',node_role:'REPLICA_READ_ONLY',brain_write_allowed:false};await assert.rejects(zeroQuestion('q',{get:async()=>({...status,brain_id:'other'})}),/IDENTITY/);await assert.rejects(zeroQuestion('q',{get:async u=>u.endsWith('/status')?status:{no_state_change:false}}),/WITHHELD/);let reads=0;await assert.rejects(zeroQuestion('q',{get:async u=>u.endsWith('/status')?{...status,revision:5+reads++}:native('x')}),/SOURCE_CHANGED/);});
|
||||
|
||||
test('expired credentials cannot enter the receiver',async t=>{const f=await fixture(t,{config:{clients:[{...client,expires_at:'2000-01-01T00:00:00Z'}]}});assert.equal((await f.post()).status,401);assert.equal(f.calls(),0);});
|
||||
test('new requests are rate limited while exact retries stay cached',async t=>{const f=await fixture(t);for(let i=0;i<12;i++)assert.equal((await f.post()).status,200);assert.equal((await f.post()).status,429);assert.equal(f.calls(),12);});
|
||||
|
||||
test('native reply speaker, input digest and reply digest must match',async()=>{const status={brain_id:ZERO,revision:5,integrity:'BRAIN_VERIFIED',node_role:'REPLICA_READ_ONLY',brain_write_allowed:false};for(const mutate of [r=>r.brain_turn.speaker.brain_id='foreign',r=>r.brain_turn.input_sha256='0'.repeat(64),r=>r.spoken='changed',r=>r.brain_turn.brain_revision=4]){const r=native('answer');mutate(r);await assert.rejects(zeroQuestion('q',{get:async u=>u.endsWith('/status')?status:r}),/WITHHELD/);}});
|
||||
Loading…
Reference in a new issue