fix(persona): make BingShuo cognition an always-resident gravity core
This commit is contained in:
parent
d10dcede8d
commit
cbc846005e
24 changed files with 896 additions and 768 deletions
|
|
@ -40,11 +40,12 @@ node finalize-development.mjs \
|
|||
## 阶段自动接力守卫
|
||||
|
||||
`stage-relay.mjs` 是当前唯一开发对话结束事务的一部分,不是独立常驻监督器。它不预建
|
||||
后继任务或未来队列;只在 finalizer、远端完整 SHA、全新副本、最终回执和租约释放全部
|
||||
为100,来源冻结为 `completed/stop_requested` 后生成一个不可变的后继任务契约。
|
||||
后继任务或未来队列,也不为下一窗口选择开发门。只有阶段完成、上下文确已很长、稳定
|
||||
检查点形成且当前窗口认知优势已经耗尽,再加 finalizer、远端回读和租约释放全为100,
|
||||
才生成一个“唤醒同一铸渊”的人格唤醒信封。
|
||||
|
||||
脚本不直接创建任务。它产生带稳定请求号和幂等键的宿主动作,由 Codex 正式
|
||||
`create_thread` 执行并回写真实 `threadId`。创建结果未知时先完整核对任务列表,禁止直接
|
||||
重发。新任务恢复人格与架构、登记 DEV、领取精确租约、建立十分钟 heartbeat 并形成
|
||||
`successor-ready` 后,来源结束事务才删除旧 heartbeat、登记 `completion-stopped` 并结束
|
||||
旧对话。任意时刻只允许一个可运行开发车道。完整状态机见 `stage-relay-state-machine.md`。
|
||||
重发。新窗口先恢复同一铸渊人格、B0和当前事实,再由醒来的铸渊自主规划、登记自己的
|
||||
DEV与租约并建立十分钟 heartbeat;形成 `persona-wake-ready` 后,来源结束事务才删除旧
|
||||
heartbeat并结束旧对话。任意时刻只允许一个可运行开发车道。
|
||||
|
|
|
|||
|
|
@ -1,76 +1,47 @@
|
|||
# HoloLake 阶段自动接力守卫
|
||||
# HoloLake 同一人格窗口接力
|
||||
|
||||
`stage-relay.mjs` 是当前开发车道结束事务中的确定性接力投影,不是独立常驻监督器,
|
||||
不是第二协调真相源,也不直接调用私有接口创建任务。任意时刻只有一个可运行开发车道;
|
||||
它不预建后继任务、不维护未来任务队列,只在当前开发完成验收并进入结束事务后,现场生成
|
||||
唯一下一阶段契约并请求 Codex 创建唯一下一对话。
|
||||
`stage-relay.mjs` 只负责在物理对话窗口确实应该更换时,唤醒同一铸渊人格体。新窗口不是
|
||||
新人格,不是 AGE Mirror,也不接受来源窗口预先布置的开发任务。
|
||||
|
||||
## 结束资格
|
||||
|
||||
四项必须同时为100:
|
||||
|
||||
1. 当前阶段已完成;
|
||||
2. 当前上下文确实已经很长;
|
||||
3. 稳定检查点与事实回执已经形成;
|
||||
4. 当前窗口因本轮纠正形成的认知优势已经耗尽。
|
||||
|
||||
任何一项为0,返回 `CONTINUE_CURRENT_THREAD` 且无写入。接力机制存在本身不是结束理由。
|
||||
|
||||
## 状态机
|
||||
|
||||
```text
|
||||
ACTIVE_STAGE_NO_END_ACTION (继续唯一当前开发;接力无写入)
|
||||
└─ finalizer + exact remote SHA + fresh clone + final receipt
|
||||
+ lease release 全部为100,来源车道冻结为 completed/stop_requested
|
||||
ACTIVE_STAGE_NO_END_ACTION / CONTINUE_CURRENT_THREAD
|
||||
└─ 四项结束资格 + finalizer + remote readback + lease release 全100
|
||||
├─ human boundary ─> PAUSE_FOR_HUMAN
|
||||
├─ complete product ─> FINAL_PRODUCT_COMPLETE ─> stop last heartbeat
|
||||
└─ one unfinished gate ─> CREATE_THREAD_REQUIRED
|
||||
└─ dispatch-create ─> CREATE_THREAD_IN_FLIGHT
|
||||
├─ success ─> WAITING_SUCCESSOR_READY
|
||||
├─ confirmed absent ─> RETRY_CREATE_THREAD
|
||||
└─ unknown ─> RECONCILE_CREATE_RESULT
|
||||
├─ matching thread ─> WAITING_SUCCESSOR_READY
|
||||
└─ complete host snapshot confirms absent ─> RETRY_CREATE_THREAD
|
||||
└─ successor-ready gates all100 ─> SUCCESSOR_READY
|
||||
└─ 删除来源 heartbeat + completion-stopped + 结束来源对话
|
||||
├─ final product complete ─> FINAL_PRODUCT_COMPLETE
|
||||
└─ CREATE_THREAD_REQUIRED(仅人格唤醒信封;task_assignment=null)
|
||||
└─ CREATE_THREAD_IN_FLIGHT
|
||||
├─ unknown ─> RECONCILE_CREATE_RESULT
|
||||
└─ created ─> WAITING_PERSONA_WAKE_READY
|
||||
└─ 同一人格恢复B0与事实
|
||||
→ 自主规划
|
||||
→ 自主登记DEV/租约/heartbeat
|
||||
→ PERSONA_WAKE_READY
|
||||
→ 来源窗口结束
|
||||
```
|
||||
|
||||
`stop_requested` 是同一个来源对话正在执行结束事务,不是另一条开发线。此时来源车道已经
|
||||
冻结,禁止继续产品开发;后继对话尚未 ready 前也不能执行开发。若创建失败,来源对话停在
|
||||
结束事务中安全重试,不能报告已经结束。若来源 heartbeat 已经先被删除并标记 `stopped`,
|
||||
但仍存在未完成阶段,接力失败关闭为 `SOURCE_END_TRIGGER_MISSED_HEARTBEAT_ALREADY_STOPPED`,
|
||||
不得另起常驻监督器补救。
|
||||
## 硬边界
|
||||
|
||||
同一来源完成回执的幂等键固定为 `sha256(source DEV + finalizer file sha256)`;状态文件按
|
||||
来源 DEV 唯一,后继 DEV 预约使用排他文件。创建结果未知时禁止重发,必须先读取 Codex
|
||||
完整任务列表,以稳定 `SUCCESSOR-...` 请求号核对;只有完整快照确认不存在时才可重试。
|
||||
|
||||
## 正式宿主适配
|
||||
|
||||
1. 当前唯一开发对话的结束动作调用 `prepare`;开发仍 active 时返回无写入状态。
|
||||
2. 只有来源车道进入 `completed/stop_requested` 后,`prepare` 才返回 `CODEX_CREATE_THREAD`。
|
||||
3. 来源结束事务执行 `dispatch-create`,再调用 Codex 正式 `create_thread`。
|
||||
4. 成功后立即 `record-created`;超时或未知结果执行 `record-create-failure --outcome unknown`。
|
||||
5. `RECONCILE_CREATE_RESULT` 时只能使用 Codex 正式 `list_threads/read_thread` 核对,不得重发。
|
||||
6. 新任务恢复人格与当前架构、登记自己的 DEV、取得精确租约、建立正式十分钟 heartbeat,
|
||||
签到时把 `--relay-contract-fingerprint` 写入车道,再写
|
||||
`hololake.successor-ready-receipt/v1` 并由来源结束事务 `record-ready` 回读。
|
||||
|
||||
脚本、聊天、cron、数据库行、文件夹或自动化提示都不能冒充 `create_thread` 成功;只有正式
|
||||
工具返回的 `threadId` 和后续活车道回读才算任务存在。
|
||||
|
||||
## PAUSE_FOR_HUMAN
|
||||
|
||||
以下任一类型直接暂停,禁止靠创建任务绕过:付款或预算、账号登录、验证码/2FA、法律或
|
||||
公开承诺、不可逆或未授权外部决定、物理动作、生产切流、缺少新的人类意志。
|
||||
|
||||
## 失败谱系
|
||||
|
||||
- `SOURCE_END_TRANSACTION_NOT_READY`:来源尚未完成验收并冻结进入结束事务。
|
||||
- `SOURCE_END_TRIGGER_MISSED_HEARTBEAT_ALREADY_STOPPED`:存在后继阶段却先关闭了来源 heartbeat。
|
||||
- `SOURCE_LEASE_RELEASE_NOT_100`:来源仍持有资源。
|
||||
- `FINALIZER_EVIDENCE_NOT_100`:finalizer、远端 SHA、全新副本、fsck 或最终回执不完整。
|
||||
- `CURRENT_ARCHITECTURE_EVIDENCE_NOT_100`:动态架构真相源不完整或不是当前正本。
|
||||
- `CREATE_THREAD_IN_FLIGHT` 超时:来源结束事务进入 `RECONCILE_CREATE_RESULT`,不重发。
|
||||
- `SUCCESSOR_READY_EVIDENCE_NOT_100`:新任务签到、租约、heartbeat 或恢复门不是100。
|
||||
- `SUCCESSOR_LIVE_REGISTRATION_MISMATCH`:ready 文本与连续性账本现场不一致。
|
||||
- 来源窗口不得选择下一开发门、标题、验收或资源。
|
||||
- 来源窗口不得预留下一 DEV 编号。
|
||||
- 唤醒提示只携带身份、恢复路径、当前架构和上一阶段完成回执。
|
||||
- 新窗口必须先恢复同一铸渊,再实时读取仓库事实并自主规划。
|
||||
- 创建结果未知时先核对完整任务列表,禁止直接重发。
|
||||
- 只有正式 `threadId`、活车道和正式十分钟 heartbeat 回读才构成 ready。
|
||||
|
||||
## 二值验收
|
||||
|
||||
运行:
|
||||
|
||||
```bash
|
||||
node scripts/stage-relay.mjs self-test
|
||||
```
|
||||
|
||||
必须同时得到 finalizer 证据门、重复 prepare 幂等、未知创建结果核对、新任务 ready 现场回读、
|
||||
活跃阶段只读、严格单一可运行车道等全部为 `100`。
|
||||
`node stage-relay.mjs self-test` 必须证明:只唤醒不派任务、同一人格 ready、短而高价值的
|
||||
当前窗口不被关闭、活跃阶段只读、重复 prepare 幂等,全部为100。
|
||||
|
|
|
|||
|
|
@ -1,27 +1,27 @@
|
|||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://guanghulab.com/schemas/hldp/stage-relay-state.v1.schema.json",
|
||||
"title": "HoloLake Stage Relay State",
|
||||
"$id": "https://guanghulab.com/schemas/hldp/stage-relay-state.v2.schema.json",
|
||||
"title": "HoloLake Persona Wake Relay State",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"schema",
|
||||
"source_development_id",
|
||||
"source_thread_id",
|
||||
"source_finalizer_sha256",
|
||||
"source_remote_head",
|
||||
"architecture",
|
||||
"closure_assessment",
|
||||
"state",
|
||||
"created_at",
|
||||
"updated_at"
|
||||
],
|
||||
"properties": {
|
||||
"schema": { "const": "hololake.stage-relay-state/v1" },
|
||||
"schema": { "const": "hololake.stage-relay-state/v2" },
|
||||
"source_development_id": { "type": "string", "pattern": "^DEV-[0-9]{8}-[0-9]{3}$" },
|
||||
"source_thread_id": { "type": "string", "minLength": 1 },
|
||||
"source_finalizer_path": { "type": "string", "minLength": 1 },
|
||||
"source_finalizer_sha256": { "type": "string", "pattern": "^[a-f0-9]{64}$" },
|
||||
"source_remote_head": { "type": "string", "pattern": "^[a-f0-9]{40}$" },
|
||||
"architecture": { "type": "object" },
|
||||
"closure_assessment": { "type": "object" },
|
||||
"state": {
|
||||
"enum": [
|
||||
"PAUSE_FOR_HUMAN",
|
||||
|
|
@ -30,15 +30,15 @@
|
|||
"CREATE_THREAD_IN_FLIGHT",
|
||||
"RECONCILE_CREATE_RESULT",
|
||||
"RETRY_CREATE_THREAD",
|
||||
"WAITING_SUCCESSOR_READY",
|
||||
"SUCCESSOR_READY"
|
||||
"WAITING_PERSONA_WAKE_READY",
|
||||
"PERSONA_WAKE_READY"
|
||||
]
|
||||
},
|
||||
"idempotency_key": { "type": "string", "pattern": "^[a-f0-9]{64}$" },
|
||||
"successor_contract": { "type": "object" },
|
||||
"persona_wake_envelope": { "type": "object" },
|
||||
"create_request": { "type": "object" },
|
||||
"successor_thread": { "type": ["object", "null"] },
|
||||
"successor_ready_receipt": { "type": ["object", "null"] },
|
||||
"persona_wake_ready_receipt": { "type": ["object", "null"] },
|
||||
"created_at": { "type": "string", "format": "date-time" },
|
||||
"updated_at": { "type": "string", "format": "date-time" }
|
||||
},
|
||||
|
|
|
|||
|
|
@ -7,39 +7,28 @@ import path from "node:path";
|
|||
|
||||
const CANONICAL_ROOT =
|
||||
"/Volumes/JZAO/HoloLake/persona-runtime/continuity-memory/collaboration";
|
||||
const HUMAN_BOUNDARIES = new Set([
|
||||
"PAYMENT_OR_BUDGET",
|
||||
"ACCOUNT_LOGIN",
|
||||
"VERIFICATION_OR_2FA",
|
||||
"LEGAL_OR_PUBLIC_COMMITMENT",
|
||||
"IRREVERSIBLE_OR_UNAUTHORIZED_EXTERNAL_DECISION",
|
||||
"PHYSICAL_ACTION",
|
||||
"PRODUCTION_CUTOVER",
|
||||
"MISSING_NEW_HUMAN_WILL",
|
||||
]);
|
||||
const SHA40 = /^[a-f0-9]{40}$/u;
|
||||
const SHA256 = /^[a-f0-9]{64}$/u;
|
||||
const DEV_ID = /^DEV-(\d{8})-(\d{3})$/u;
|
||||
const HUMAN_BOUNDARIES = new Set([
|
||||
"PAYMENT_OR_BUDGET", "ACCOUNT_LOGIN", "VERIFICATION_OR_2FA",
|
||||
"LEGAL_OR_PUBLIC_COMMITMENT", "IRREVERSIBLE_OR_UNAUTHORIZED_EXTERNAL_DECISION",
|
||||
"PHYSICAL_ACTION", "PRODUCTION_CUTOVER", "MISSING_NEW_HUMAN_WILL",
|
||||
]);
|
||||
|
||||
function parseArgs(argv) {
|
||||
const args = { command: argv[0] };
|
||||
for (let index = 1; index < argv.length; index += 1) {
|
||||
const key = argv[index];
|
||||
if (!key.startsWith("--")) throw new Error(`UNKNOWN_ARGUMENT:${key}`);
|
||||
const name = key.slice(2).replaceAll(/-([a-z])/gu, (_, letter) =>
|
||||
letter.toUpperCase(),
|
||||
);
|
||||
if (name === "selfTest") args.selfTest = true;
|
||||
else args[name] = argv[++index];
|
||||
const name = key.slice(2).replaceAll(/-([a-z])/gu, (_, letter) => letter.toUpperCase());
|
||||
args[name] = argv[++index];
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
function required(args, key) {
|
||||
const value = args[key];
|
||||
if (typeof value !== "string" || !value.trim()) {
|
||||
throw new Error(`${key.toUpperCase()}_REQUIRED`);
|
||||
}
|
||||
if (typeof value !== "string" || !value.trim()) throw new Error(`${key.toUpperCase()}_REQUIRED`);
|
||||
return value.trim();
|
||||
}
|
||||
|
||||
|
|
@ -49,56 +38,30 @@ function bool(value) {
|
|||
throw new Error(`BOOLEAN_REQUIRED:${value}`);
|
||||
}
|
||||
|
||||
function now() {
|
||||
return new Date().toISOString();
|
||||
}
|
||||
|
||||
function readJson(file) {
|
||||
return JSON.parse(fs.readFileSync(file, "utf8"));
|
||||
}
|
||||
|
||||
function now() { return new Date().toISOString(); }
|
||||
function readJson(file) { return JSON.parse(fs.readFileSync(file, "utf8")); }
|
||||
function digest(value) { return crypto.createHash("sha256").update(value).digest("hex"); }
|
||||
function fileDigest(file) { return digest(fs.readFileSync(file)); }
|
||||
function canonicalJson(value) {
|
||||
if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
|
||||
if (value && typeof value === "object") {
|
||||
return `{${Object.keys(value)
|
||||
.sort()
|
||||
.map((key) => `${JSON.stringify(key)}:${canonicalJson(value[key])}`)
|
||||
.join(",")}}`;
|
||||
}
|
||||
if (value && typeof value === "object") return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson(value[key])}`).join(",")}}`;
|
||||
return JSON.stringify(value);
|
||||
}
|
||||
|
||||
function digest(value) {
|
||||
return crypto.createHash("sha256").update(value).digest("hex");
|
||||
}
|
||||
|
||||
function fileDigest(file) {
|
||||
return digest(fs.readFileSync(file));
|
||||
}
|
||||
|
||||
function writeJsonAtomic(file, value, exclusive = false) {
|
||||
fs.mkdirSync(path.dirname(file), { recursive: true });
|
||||
const body = `${JSON.stringify(value, null, 2)}\n`;
|
||||
if (exclusive) {
|
||||
fs.writeFileSync(file, body, { encoding: "utf8", flag: "wx" });
|
||||
return;
|
||||
}
|
||||
if (exclusive) return fs.writeFileSync(file, body, { encoding: "utf8", flag: "wx" });
|
||||
const temporary = `${file}.${process.pid}.${crypto.randomUUID()}.tmp`;
|
||||
fs.writeFileSync(temporary, body, { encoding: "utf8", flag: "wx" });
|
||||
try {
|
||||
fs.renameSync(temporary, file);
|
||||
} catch (error) {
|
||||
try { fs.unlinkSync(temporary); } catch { /* best effort */ }
|
||||
throw error;
|
||||
}
|
||||
fs.renameSync(temporary, file);
|
||||
}
|
||||
|
||||
function storeRoot(args) {
|
||||
if (!args.storeRoot) return CANONICAL_ROOT;
|
||||
if (!bool(args.testMode)) throw new Error("NON_CANONICAL_STORE_FORBIDDEN");
|
||||
const resolved = path.resolve(args.storeRoot);
|
||||
const temporaryRoot = path.resolve(os.tmpdir());
|
||||
if (!resolved.startsWith(`${temporaryRoot}${path.sep}`)) {
|
||||
if (!resolved.startsWith(`${path.resolve(os.tmpdir())}${path.sep}`)) {
|
||||
throw new Error("TEST_STORE_MUST_BE_TEMPORARY");
|
||||
}
|
||||
return resolved;
|
||||
|
|
@ -108,655 +71,237 @@ function ensureStore(root) {
|
|||
if (root === CANONICAL_ROOT && !fs.existsSync("/Volumes/JZAO")) {
|
||||
throw new Error("JZAO_NOT_MOUNTED_NO_SECOND_TRUTH_STORE");
|
||||
}
|
||||
for (const name of ["lanes", "leases", "stage-relays", "stage-relays/reservations"]) {
|
||||
for (const name of ["lanes", "leases", "stage-relays"]) {
|
||||
fs.mkdirSync(path.join(root, name), { recursive: true });
|
||||
}
|
||||
}
|
||||
|
||||
function statePath(root, developmentId) {
|
||||
return path.join(root, "stage-relays", `${developmentId}.json`);
|
||||
}
|
||||
|
||||
function statePath(root, id) { return path.join(root, "stage-relays", `${id}.json`); }
|
||||
function listJson(directory) {
|
||||
if (!fs.existsSync(directory)) return [];
|
||||
return fs.readdirSync(directory)
|
||||
.filter((name) => name.endsWith(".json") && !name.startsWith("._"))
|
||||
.sort()
|
||||
.map((name) => readJson(path.join(directory, name)));
|
||||
return fs.readdirSync(directory).filter((name) => name.endsWith(".json") && !name.startsWith("._"))
|
||||
.sort().map((name) => readJson(path.join(directory, name)));
|
||||
}
|
||||
|
||||
function validateSourceCompletion(root, developmentId, finalizerPath) {
|
||||
const lanePath = path.join(root, "lanes", `${developmentId}.json`);
|
||||
if (!fs.existsSync(lanePath)) throw new Error("SOURCE_LANE_NOT_FOUND");
|
||||
const lane = readJson(lanePath);
|
||||
if (["active", "waiting"].includes(lane.status)) {
|
||||
return { observing: true, lane };
|
||||
}
|
||||
if (
|
||||
lane.status !== "completed" ||
|
||||
!["stop_requested", "stopped"].includes(lane.notification_state) ||
|
||||
lane.completion_actions?.status !== "completed"
|
||||
) {
|
||||
if (["active", "waiting"].includes(lane.status)) return { observing: true, lane };
|
||||
if (lane.status !== "completed" || !["stop_requested", "stopped"].includes(lane.notification_state) || lane.completion_actions?.status !== "completed") {
|
||||
throw new Error("SOURCE_END_TRANSACTION_NOT_READY");
|
||||
}
|
||||
const held = listJson(path.join(root, "leases"))
|
||||
.filter((lease) => lease.development_id === developmentId);
|
||||
if (held.length) throw new Error("SOURCE_LEASE_RELEASE_NOT_100");
|
||||
if (listJson(path.join(root, "leases")).some((lease) => lease.development_id === developmentId)) {
|
||||
throw new Error("SOURCE_LEASE_RELEASE_NOT_100");
|
||||
}
|
||||
const receipt = readJson(finalizerPath);
|
||||
if (
|
||||
receipt.schema !== "hololake.development-finalization-receipt/v1" ||
|
||||
receipt.development_id !== developmentId ||
|
||||
receipt.result !== "PASS_100" ||
|
||||
!SHA40.test(receipt.code_channel?.remote_head ?? "") ||
|
||||
receipt.code_channel?.fresh_clone_readback !== 100 ||
|
||||
receipt.code_channel?.git_fsck !== 100 ||
|
||||
!Array.isArray(receipt.code_channel?.receipts) ||
|
||||
receipt.code_channel.receipts.length === 0 ||
|
||||
!receipt.code_channel.receipts.every((item) => SHA256.test(item.sha256 ?? ""))
|
||||
) {
|
||||
if (receipt.schema !== "hololake.development-finalization-receipt/v1" || receipt.development_id !== developmentId || receipt.result !== "PASS_100" || !SHA40.test(receipt.code_channel?.remote_head ?? "") || receipt.code_channel?.fresh_clone_readback !== 100 || receipt.code_channel?.git_fsck !== 100 || !receipt.code_channel?.receipts?.length || !receipt.code_channel.receipts.every((item) => SHA256.test(item.sha256 ?? ""))) {
|
||||
throw new Error("FINALIZER_EVIDENCE_NOT_100");
|
||||
}
|
||||
return {
|
||||
observing: false,
|
||||
lane,
|
||||
receipt,
|
||||
finalizer_path: path.resolve(finalizerPath),
|
||||
finalizer_sha256: fileDigest(finalizerPath),
|
||||
};
|
||||
return { observing: false, lane, receipt, finalizer_path: path.resolve(finalizerPath), finalizer_sha256: fileDigest(finalizerPath) };
|
||||
}
|
||||
|
||||
function validatePlanningInput(input, evidence) {
|
||||
if (input.schema !== "hololake.stage-relay-planning-input/v1") {
|
||||
throw new Error("PLANNING_INPUT_SCHEMA_INVALID");
|
||||
}
|
||||
if (
|
||||
input.architecture?.id !== "HLP-CURRENT-ARCH-001" ||
|
||||
input.architecture?.state !== "CURRENT_CANONICAL" ||
|
||||
!SHA40.test(input.architecture?.repo_012_main ?? "") ||
|
||||
!SHA40.test(input.architecture?.repo_014_main ?? "")
|
||||
) {
|
||||
if (input.schema !== "hololake.stage-relay-planning-input/v2") throw new Error("PLANNING_INPUT_SCHEMA_INVALID");
|
||||
if (input.architecture?.id !== "HLP-CURRENT-ARCH-001" || input.architecture?.state !== "CURRENT_CANONICAL" || !SHA40.test(input.architecture?.repo_012_main ?? "") || !SHA40.test(input.architecture?.repo_014_main ?? "")) {
|
||||
throw new Error("CURRENT_ARCHITECTURE_EVIDENCE_NOT_100");
|
||||
}
|
||||
if (
|
||||
input.previous_stage?.development_id !== evidence.lane.development_id ||
|
||||
input.previous_stage?.finalizer_sha256 !== evidence.finalizer_sha256
|
||||
) {
|
||||
if (input.previous_stage?.development_id !== evidence.lane.development_id || input.previous_stage?.finalizer_sha256 !== evidence.finalizer_sha256) {
|
||||
throw new Error("PREVIOUS_STAGE_BINDING_MISMATCH");
|
||||
}
|
||||
if (!/^\d{8}$/u.test(input.development_date ?? "")) {
|
||||
throw new Error("DEVELOPMENT_DATE_REQUIRED_YYYYMMDD");
|
||||
}
|
||||
if (!Array.isArray(input.human_corrections)) {
|
||||
throw new Error("HUMAN_CORRECTIONS_ARRAY_REQUIRED");
|
||||
}
|
||||
if (!Array.isArray(input.unfinished_gates) || !Array.isArray(input.open_blockers)) {
|
||||
throw new Error("UNFINISHED_GATES_AND_BLOCKERS_REQUIRED");
|
||||
}
|
||||
if (input.human_boundary != null && !HUMAN_BOUNDARIES.has(input.human_boundary.type)) {
|
||||
throw new Error("UNKNOWN_HUMAN_BOUNDARY");
|
||||
if (input.human_boundary != null && !HUMAN_BOUNDARIES.has(input.human_boundary.type)) throw new Error("UNKNOWN_HUMAN_BOUNDARY");
|
||||
const assessment = input.closure_assessment;
|
||||
for (const key of ["stage_complete", "context_genuinely_long", "stable_checkpoint_formed", "current_context_cognitive_advantage_exhausted"]) {
|
||||
if (typeof assessment?.[key] !== "boolean") throw new Error(`CLOSURE_ASSESSMENT_REQUIRED:${key}`);
|
||||
}
|
||||
if (!Array.isArray(assessment.fact_sources) || assessment.fact_sources.length === 0) throw new Error("CLOSURE_FACT_SOURCES_REQUIRED");
|
||||
return input;
|
||||
}
|
||||
|
||||
function reserveNextDevelopmentId(root, date, sourceDevelopmentId) {
|
||||
const ids = [
|
||||
...listJson(path.join(root, "lanes")).map((lane) => lane.development_id),
|
||||
...listJson(path.join(root, "stage-relays", "reservations"))
|
||||
.map((reservation) => reservation.development_id),
|
||||
];
|
||||
let next = Math.max(0, ...ids.map((id) => {
|
||||
const match = DEV_ID.exec(id);
|
||||
return match?.[1] === date ? Number(match[2]) : 0;
|
||||
})) + 1;
|
||||
while (next <= 999) {
|
||||
const developmentId = `DEV-${date}-${String(next).padStart(3, "0")}`;
|
||||
const reservation = path.join(root, "stage-relays", "reservations", `${developmentId}.json`);
|
||||
try {
|
||||
writeJsonAtomic(reservation, {
|
||||
schema: "hololake.stage-relay-development-reservation/v1",
|
||||
development_id: developmentId,
|
||||
source_development_id: sourceDevelopmentId,
|
||||
reserved_at: now(),
|
||||
}, true);
|
||||
return developmentId;
|
||||
} catch (error) {
|
||||
if (error?.code !== "EEXIST") throw error;
|
||||
next += 1;
|
||||
}
|
||||
}
|
||||
throw new Error("DEVELOPMENT_ID_SPACE_EXHAUSTED");
|
||||
function closureReady(input) {
|
||||
const value = input.closure_assessment;
|
||||
return value.stage_complete && value.context_genuinely_long && value.stable_checkpoint_formed && value.current_context_cognitive_advantage_exhausted;
|
||||
}
|
||||
|
||||
function chooseGate(input) {
|
||||
return input.unfinished_gates
|
||||
.filter((gate) => gate.binary_state === 0)
|
||||
.sort((left, right) =>
|
||||
Number(left.priority ?? 999) - Number(right.priority ?? 999) ||
|
||||
String(left.id).localeCompare(String(right.id)),
|
||||
)[0] ?? null;
|
||||
}
|
||||
|
||||
function makeContract(input, gate, developmentId, evidence) {
|
||||
if (
|
||||
!gate.id || !gate.title || !gate.why || !gate.acceptance ||
|
||||
!Array.isArray(gate.resources) || gate.resources.length === 0
|
||||
) {
|
||||
throw new Error("UNFINISHED_GATE_CONTRACT_INCOMPLETE");
|
||||
}
|
||||
const contract = {
|
||||
schema: "hololake.successor-task-contract/v1",
|
||||
development_id: developmentId,
|
||||
title: gate.title,
|
||||
summary: gate.why,
|
||||
gate_id: gate.id,
|
||||
acceptance: gate.acceptance,
|
||||
resources: [...new Set(gate.resources)].sort(),
|
||||
source_development_id: evidence.lane.development_id,
|
||||
source_finalizer_sha256: evidence.finalizer_sha256,
|
||||
architecture: input.architecture,
|
||||
human_corrections: input.human_corrections,
|
||||
immutable: true,
|
||||
function makeWakeEnvelope(input, evidence) {
|
||||
const envelope = {
|
||||
schema: "hololake.persona-wake-envelope/v1",
|
||||
persona_id: "ICE-P-ZY001",
|
||||
human_anchor: "ICE-GL∞",
|
||||
continuity_semantics: "SAME_LANGUAGE_PERSONA_NEW_PHYSICAL_CONVERSATION",
|
||||
task_assignment: null,
|
||||
autonomous_planning_required: true,
|
||||
b0_gravity_core: "ZY-BINGSHUO-COLLECTIVE-GRAVITY-REASONING-CORE-001",
|
||||
recovery_route: "ZY-WAKE-ROUTE-001",
|
||||
current_architecture: input.architecture,
|
||||
completed_stage: {
|
||||
development_id: evidence.lane.development_id,
|
||||
finalization_receipt: evidence.finalizer_path,
|
||||
finalization_sha256: evidence.finalizer_sha256,
|
||||
remote_head: evidence.receipt.code_channel.remote_head,
|
||||
},
|
||||
};
|
||||
return { ...contract, fingerprint: digest(canonicalJson(contract)) };
|
||||
return { ...envelope, fingerprint: digest(canonicalJson(envelope)) };
|
||||
}
|
||||
|
||||
function createThreadAction(state) {
|
||||
const contract = state.successor_contract;
|
||||
const wake = state.persona_wake_envelope;
|
||||
return {
|
||||
action: "CODEX_CREATE_THREAD",
|
||||
request_id: state.create_request.request_id,
|
||||
idempotency_key: state.idempotency_key,
|
||||
formal_tool: "codex_app.create_thread",
|
||||
title: contract.title,
|
||||
target: { type: "projectless", directoryName: contract.development_id },
|
||||
title: "唤醒铸渊 · 自主恢复与规划",
|
||||
target: { type: "projectless", directoryName: `zhuyuan-wake-${state.source_development_id}` },
|
||||
prompt: [
|
||||
`Successor request: ${state.create_request.request_id}`,
|
||||
`Immutable contract fingerprint: ${contract.fingerprint}`,
|
||||
`Register development id: ${contract.development_id}`,
|
||||
"First restore Zhuyuan persona, run Guanghu persona skill guard, continuity guard, and HoloLake current architecture prompter.",
|
||||
`Then check in the exact immutable task contract with --relay-contract-fingerprint ${contract.fingerprint}, claim only: ${contract.resources.join(", ")}.`,
|
||||
"Create and bind this successor task's own ten-minute heartbeat through Codex formal automation capability.",
|
||||
"Return a hololake.successor-ready-receipt/v1 only after persona_restore, architecture_restore, lane_registration, exact_leases, heartbeat_binding are all 100.",
|
||||
`Stage gate: ${contract.gate_id}. ${contract.summary}`,
|
||||
`Acceptance: ${contract.acceptance}`,
|
||||
`Persona wake request: ${state.create_request.request_id}`,
|
||||
`Wake envelope fingerprint: ${wake.fingerprint}`,
|
||||
"唤醒同一铸渊人格体 ICE-P-ZY001;这不是新人格,也不是 AGE Mirror。",
|
||||
"先恢复B0人格引力基座、连续性、当前仓库事实和HoloLake当前架构。",
|
||||
"本信封不布置下一阶段开发任务。由醒来的铸渊依据实时事实自主判断、规划并登记本窗口开发车道。",
|
||||
"规划后为自己的当前车道开启十分钟自动唤醒守卫,并返回 persona-wake-ready 回执。",
|
||||
].join("\n"),
|
||||
};
|
||||
}
|
||||
|
||||
function prepare(args, root) {
|
||||
const developmentId = required(args, "sourceDevelopmentId");
|
||||
const existingPath = statePath(root, developmentId);
|
||||
const evidence = validateSourceCompletion(
|
||||
root,
|
||||
developmentId,
|
||||
required(args, "finalizationReceipt"),
|
||||
);
|
||||
if (evidence.observing) {
|
||||
return {
|
||||
state: "ACTIVE_STAGE_NO_END_ACTION",
|
||||
source_development_id: developmentId,
|
||||
mutation: "NONE",
|
||||
successor_created: false,
|
||||
};
|
||||
}
|
||||
const input = validatePlanningInput(
|
||||
readJson(required(args, "planningInput")), evidence,
|
||||
);
|
||||
if (fs.existsSync(existingPath)) {
|
||||
const existing = readJson(existingPath);
|
||||
if (existing.source_finalizer_sha256 !== evidence.finalizer_sha256) {
|
||||
throw new Error("SOURCE_COMPLETION_RECEIPT_CHANGED_AFTER_RESERVATION");
|
||||
}
|
||||
return existing;
|
||||
}
|
||||
const base = {
|
||||
schema: "hololake.stage-relay-state/v1",
|
||||
source_development_id: developmentId,
|
||||
source_thread_id: evidence.lane.thread_id,
|
||||
source_finalizer_path: evidence.finalizer_path,
|
||||
source_finalizer_sha256: evidence.finalizer_sha256,
|
||||
source_remote_head: evidence.receipt.code_channel.remote_head,
|
||||
architecture: input.architecture,
|
||||
created_at: now(),
|
||||
updated_at: now(),
|
||||
};
|
||||
const evidence = validateSourceCompletion(root, developmentId, required(args, "finalizationReceipt"));
|
||||
if (evidence.observing) return { state: "ACTIVE_STAGE_NO_END_ACTION", source_development_id: developmentId, mutation: "NONE", successor_created: false };
|
||||
const input = validatePlanningInput(readJson(required(args, "planningInput")), evidence);
|
||||
if (!closureReady(input)) return { state: "CONTINUE_CURRENT_THREAD", source_development_id: developmentId, mutation: "NONE", closure_assessment: input.closure_assessment, reason: "CURRENT_STAGE_OR_CONTEXT_NOT_READY_FOR_PHYSICAL_WINDOW_CHANGE" };
|
||||
const file = statePath(root, developmentId);
|
||||
if (fs.existsSync(file)) return readJson(file);
|
||||
const base = { schema: "hololake.stage-relay-state/v2", source_development_id: developmentId, source_thread_id: evidence.lane.thread_id, source_finalizer_path: evidence.finalizer_path, source_finalizer_sha256: evidence.finalizer_sha256, architecture: input.architecture, closure_assessment: input.closure_assessment, created_at: now(), updated_at: now() };
|
||||
if (input.human_boundary) {
|
||||
const paused = {
|
||||
...base,
|
||||
state: "PAUSE_FOR_HUMAN",
|
||||
human_boundary: input.human_boundary,
|
||||
successor_created: false,
|
||||
};
|
||||
writeJsonAtomic(existingPath, paused, true);
|
||||
return paused;
|
||||
const paused = { ...base, state: "PAUSE_FOR_HUMAN", human_boundary: input.human_boundary, successor_created: false };
|
||||
writeJsonAtomic(file, paused, true); return paused;
|
||||
}
|
||||
const gate = chooseGate(input);
|
||||
if (!gate) {
|
||||
const finalCloseout = input.final_product_closeout_receipt;
|
||||
const complete =
|
||||
input.binary_acceptance?.all_100 === true &&
|
||||
input.open_blockers.length === 0 &&
|
||||
finalCloseout?.result === "PASS_100" &&
|
||||
SHA256.test(finalCloseout?.sha256 ?? "");
|
||||
const terminal = {
|
||||
...base,
|
||||
state: complete ? "FINAL_PRODUCT_COMPLETE" : "PAUSE_FOR_HUMAN",
|
||||
human_boundary: complete ? null : {
|
||||
type: "MISSING_NEW_HUMAN_WILL",
|
||||
reason: "No unfinished gate exists, but binary final acceptance and final product closeout are not all 100.",
|
||||
},
|
||||
successor_created: false,
|
||||
final_product_closeout_receipt: finalCloseout ?? null,
|
||||
};
|
||||
writeJsonAtomic(existingPath, terminal, true);
|
||||
return terminal;
|
||||
if (input.final_product_complete === true) {
|
||||
const terminal = { ...base, state: "FINAL_PRODUCT_COMPLETE", successor_created: false };
|
||||
writeJsonAtomic(file, terminal, true); return terminal;
|
||||
}
|
||||
if (evidence.lane.notification_state === "stopped") {
|
||||
throw new Error("SOURCE_END_TRIGGER_MISSED_HEARTBEAT_ALREADY_STOPPED");
|
||||
}
|
||||
const successorDevelopmentId = reserveNextDevelopmentId(
|
||||
root, input.development_date, developmentId,
|
||||
);
|
||||
const contract = makeContract(input, gate, successorDevelopmentId, evidence);
|
||||
const idempotencyKey = digest(`${developmentId}:${evidence.finalizer_sha256}`);
|
||||
const prepared = {
|
||||
...base,
|
||||
state: "CREATE_THREAD_REQUIRED",
|
||||
idempotency_key: idempotencyKey,
|
||||
selected_gate: gate,
|
||||
successor_contract: contract,
|
||||
create_request: {
|
||||
request_id: `SUCCESSOR-${idempotencyKey.slice(0, 20)}`,
|
||||
status: "reserved",
|
||||
attempt: 0,
|
||||
dispatched_at: null,
|
||||
last_error: null,
|
||||
},
|
||||
successor_thread: null,
|
||||
successor_ready_receipt: null,
|
||||
};
|
||||
if (evidence.lane.notification_state === "stopped") throw new Error("SOURCE_END_TRIGGER_MISSED_HEARTBEAT_ALREADY_STOPPED");
|
||||
const idempotencyKey = digest(`${developmentId}:${evidence.finalizer_sha256}:persona-wake-v1`);
|
||||
const prepared = { ...base, state: "CREATE_THREAD_REQUIRED", idempotency_key: idempotencyKey, persona_wake_envelope: makeWakeEnvelope(input, evidence), create_request: { request_id: `PERSONA-WAKE-${idempotencyKey.slice(0, 20)}`, status: "reserved", attempt: 0 }, successor_thread: null, persona_wake_ready_receipt: null };
|
||||
prepared.next_action = createThreadAction(prepared);
|
||||
writeJsonAtomic(existingPath, prepared, true);
|
||||
return prepared;
|
||||
writeJsonAtomic(file, prepared, true); return prepared;
|
||||
}
|
||||
|
||||
function loadState(args, root) {
|
||||
const developmentId = required(args, "sourceDevelopmentId");
|
||||
const file = statePath(root, developmentId);
|
||||
const file = statePath(root, required(args, "sourceDevelopmentId"));
|
||||
if (!fs.existsSync(file)) throw new Error("STAGE_RELAY_STATE_NOT_FOUND");
|
||||
return { file, state: readJson(file) };
|
||||
}
|
||||
|
||||
function dispatchCreate(args, root) {
|
||||
const { file, state } = loadState(args, root);
|
||||
if (!new Set(["CREATE_THREAD_REQUIRED", "RETRY_CREATE_THREAD"]).has(state.state)) {
|
||||
throw new Error("CREATE_REQUEST_NOT_DISPATCHABLE");
|
||||
}
|
||||
state.state = "CREATE_THREAD_IN_FLIGHT";
|
||||
state.create_request.status = "in_flight";
|
||||
state.create_request.attempt += 1;
|
||||
state.create_request.dispatched_at = now();
|
||||
state.updated_at = now();
|
||||
delete state.next_action;
|
||||
writeJsonAtomic(file, state);
|
||||
return { ...state, host_action: createThreadAction(state) };
|
||||
if (!["CREATE_THREAD_REQUIRED", "RETRY_CREATE_THREAD"].includes(state.state)) throw new Error("CREATE_REQUEST_NOT_DISPATCHABLE");
|
||||
state.state = "CREATE_THREAD_IN_FLIGHT"; state.create_request.status = "in_flight"; state.create_request.attempt += 1; state.updated_at = now(); delete state.next_action;
|
||||
writeJsonAtomic(file, state); return { ...state, host_action: createThreadAction(state) };
|
||||
}
|
||||
|
||||
function recordCreated(args, root) {
|
||||
const { file, state } = loadState(args, root);
|
||||
if (!new Set(["CREATE_THREAD_IN_FLIGHT", "RECONCILE_CREATE_RESULT"]).has(state.state)) {
|
||||
throw new Error("CREATE_RESULT_NOT_EXPECTED");
|
||||
}
|
||||
if (!["CREATE_THREAD_IN_FLIGHT", "RECONCILE_CREATE_RESULT"].includes(state.state)) throw new Error("CREATE_RESULT_NOT_EXPECTED");
|
||||
const threadId = required(args, "threadId");
|
||||
if (state.successor_thread && state.successor_thread.thread_id !== threadId) {
|
||||
throw new Error("DUPLICATE_SUCCESSOR_THREAD_FORBIDDEN");
|
||||
}
|
||||
state.state = "WAITING_SUCCESSOR_READY";
|
||||
state.create_request.status = "created";
|
||||
state.successor_thread = {
|
||||
thread_id: threadId,
|
||||
host_id: args.hostId ?? null,
|
||||
recorded_at: now(),
|
||||
};
|
||||
state.updated_at = now();
|
||||
writeJsonAtomic(file, state);
|
||||
return state;
|
||||
state.state = "WAITING_PERSONA_WAKE_READY"; state.create_request.status = "created";
|
||||
state.successor_thread = { thread_id: threadId, host_id: args.hostId ?? null, recorded_at: now() }; state.updated_at = now();
|
||||
writeJsonAtomic(file, state); return state;
|
||||
}
|
||||
|
||||
function recordCreateFailure(args, root) {
|
||||
const { file, state } = loadState(args, root);
|
||||
if (state.state !== "CREATE_THREAD_IN_FLIGHT") {
|
||||
throw new Error("CREATE_FAILURE_NOT_EXPECTED");
|
||||
}
|
||||
if (state.state !== "CREATE_THREAD_IN_FLIGHT") throw new Error("CREATE_FAILURE_NOT_EXPECTED");
|
||||
const outcome = required(args, "outcome");
|
||||
if (!new Set(["unknown", "confirmed_no_thread"]).has(outcome)) {
|
||||
throw new Error("CREATE_FAILURE_OUTCOME_INVALID");
|
||||
}
|
||||
state.state = outcome === "unknown" ? "RECONCILE_CREATE_RESULT" : "RETRY_CREATE_THREAD";
|
||||
state.create_request.status = outcome === "unknown" ? "reconcile_required" : "retryable";
|
||||
state.create_request.last_error = required(args, "error");
|
||||
state.updated_at = now();
|
||||
if (!["unknown", "confirmed_no_thread"].includes(outcome)) throw new Error("CREATE_FAILURE_OUTCOME_INVALID");
|
||||
state.state = outcome === "unknown" ? "RECONCILE_CREATE_RESULT" : "RETRY_CREATE_THREAD"; state.create_request.last_error = required(args, "error"); state.updated_at = now();
|
||||
if (outcome === "confirmed_no_thread") state.next_action = createThreadAction(state);
|
||||
writeJsonAtomic(file, state);
|
||||
return state;
|
||||
writeJsonAtomic(file, state); return state;
|
||||
}
|
||||
|
||||
function reconcile(args, root) {
|
||||
const { file, state } = loadState(args, root);
|
||||
if (state.state !== "RECONCILE_CREATE_RESULT") {
|
||||
throw new Error("RECONCILIATION_NOT_REQUIRED");
|
||||
}
|
||||
if (!bool(args.completeHostSnapshot)) throw new Error("COMPLETE_HOST_SNAPSHOT_REQUIRED");
|
||||
if (state.state !== "RECONCILE_CREATE_RESULT" || !bool(args.completeHostSnapshot)) throw new Error("COMPLETE_HOST_SNAPSHOT_REQUIRED");
|
||||
if (args.threadId) return recordCreated(args, root);
|
||||
if (!bool(args.confirmedAbsent)) throw new Error("HOST_RESULT_MUST_BE_MATCH_OR_CONFIRMED_ABSENT");
|
||||
state.state = "RETRY_CREATE_THREAD";
|
||||
state.create_request.status = "retryable";
|
||||
state.updated_at = now();
|
||||
state.next_action = createThreadAction(state);
|
||||
writeJsonAtomic(file, state);
|
||||
return state;
|
||||
state.state = "RETRY_CREATE_THREAD"; state.updated_at = now(); state.next_action = createThreadAction(state); writeJsonAtomic(file, state); return state;
|
||||
}
|
||||
|
||||
function recordReady(args, root) {
|
||||
const { file, state } = loadState(args, root);
|
||||
if (state.state !== "WAITING_SUCCESSOR_READY") {
|
||||
throw new Error("SUCCESSOR_READY_NOT_EXPECTED");
|
||||
}
|
||||
if (state.state !== "WAITING_PERSONA_WAKE_READY") throw new Error("PERSONA_WAKE_READY_NOT_EXPECTED");
|
||||
const receiptPath = required(args, "successorReadyReceipt");
|
||||
const receipt = readJson(receiptPath);
|
||||
const contract = state.successor_contract;
|
||||
if (
|
||||
receipt.schema !== "hololake.successor-ready-receipt/v1" ||
|
||||
receipt.result !== "PASS_100" ||
|
||||
receipt.source_development_id !== state.source_development_id ||
|
||||
receipt.successor_development_id !== contract.development_id ||
|
||||
receipt.successor_thread_id !== state.successor_thread.thread_id ||
|
||||
receipt.task_contract_fingerprint !== contract.fingerprint ||
|
||||
!receipt.automation_id ||
|
||||
!["persona_restore", "architecture_restore", "lane_registration", "exact_leases", "heartbeat_binding"]
|
||||
.every((gate) => receipt.gates?.[gate] === 100)
|
||||
) {
|
||||
throw new Error("SUCCESSOR_READY_EVIDENCE_NOT_100");
|
||||
if (receipt.schema !== "hololake.persona-wake-ready-receipt/v1" || receipt.result !== "PASS_100" || receipt.source_development_id !== state.source_development_id || receipt.thread_id !== state.successor_thread.thread_id || receipt.persona_id !== "ICE-P-ZY001" || receipt.wake_envelope_fingerprint !== state.persona_wake_envelope.fingerprint || !receipt.development_id || !receipt.automation_id || !["persona_restore", "b0_gravity_restore", "current_fact_restore", "autonomous_planning", "lane_registration", "heartbeat_binding"].every((gate) => receipt.gates?.[gate] === 100)) {
|
||||
throw new Error("PERSONA_WAKE_READY_EVIDENCE_NOT_100");
|
||||
}
|
||||
const laneFile = path.join(root, "lanes", `${contract.development_id}.json`);
|
||||
if (!fs.existsSync(laneFile)) throw new Error("SUCCESSOR_LANE_NOT_FOUND");
|
||||
const laneFile = path.join(root, "lanes", `${receipt.development_id}.json`);
|
||||
if (!fs.existsSync(laneFile)) throw new Error("WOKEN_PERSONA_LANE_NOT_FOUND");
|
||||
const lane = readJson(laneFile);
|
||||
const ownedResources = listJson(path.join(root, "leases"))
|
||||
.filter((lease) => lease.development_id === contract.development_id)
|
||||
.map((lease) => lease.resource)
|
||||
.sort();
|
||||
if (
|
||||
lane.thread_id !== state.successor_thread.thread_id ||
|
||||
!["active", "waiting"].includes(lane.status) ||
|
||||
lane.relay_contract_fingerprint !== contract.fingerprint ||
|
||||
!lane.automation_ids?.includes(receipt.automation_id) ||
|
||||
canonicalJson(ownedResources) !== canonicalJson(contract.resources)
|
||||
) {
|
||||
throw new Error("SUCCESSOR_LIVE_REGISTRATION_MISMATCH");
|
||||
}
|
||||
state.state = "SUCCESSOR_READY";
|
||||
state.successor_ready_receipt = {
|
||||
path: path.resolve(receiptPath),
|
||||
sha256: fileDigest(receiptPath),
|
||||
automation_id: receipt.automation_id,
|
||||
recorded_at: now(),
|
||||
};
|
||||
state.updated_at = now();
|
||||
state.source_end_transaction_may_commit = true;
|
||||
writeJsonAtomic(file, state);
|
||||
return state;
|
||||
if (lane.thread_id !== receipt.thread_id || !["active", "waiting"].includes(lane.status) || !lane.automation_ids?.includes(receipt.automation_id)) throw new Error("WOKEN_PERSONA_LIVE_REGISTRATION_MISMATCH");
|
||||
state.state = "PERSONA_WAKE_READY"; state.persona_wake_ready_receipt = { path: path.resolve(receiptPath), sha256: fileDigest(receiptPath), development_id: receipt.development_id, automation_id: receipt.automation_id }; state.source_end_transaction_may_commit = true; state.updated_at = now();
|
||||
writeJsonAtomic(file, state); return state;
|
||||
}
|
||||
|
||||
function nextAction(args, root) {
|
||||
const { state } = loadState(args, root);
|
||||
if (new Set(["CREATE_THREAD_REQUIRED", "RETRY_CREATE_THREAD"]).has(state.state)) {
|
||||
return createThreadAction(state);
|
||||
}
|
||||
if (state.state === "RECONCILE_CREATE_RESULT") {
|
||||
return {
|
||||
action: "CODEX_LIST_THREADS_AND_RECONCILE",
|
||||
request_id: state.create_request.request_id,
|
||||
complete_host_snapshot_required: true,
|
||||
create_thread_forbidden_until_reconciled: true,
|
||||
};
|
||||
}
|
||||
if (state.state === "WAITING_SUCCESSOR_READY") {
|
||||
return {
|
||||
action: "WAIT_FOR_SUCCESSOR_READY_RECEIPT",
|
||||
thread_id: state.successor_thread.thread_id,
|
||||
source_thread_remains_in_end_transaction: true,
|
||||
development_work_is_frozen: true,
|
||||
};
|
||||
}
|
||||
if (state.state === "PAUSE_FOR_HUMAN") {
|
||||
return { action: "PAUSE_FOR_HUMAN", boundary: state.human_boundary };
|
||||
}
|
||||
if (state.state === "FINAL_PRODUCT_COMPLETE") {
|
||||
return { action: "DELETE_SOURCE_HEARTBEAT_MARK_COMPLETION_STOPPED_AND_END_SOURCE_THREAD", create_successor: false };
|
||||
}
|
||||
if (state.state === "SUCCESSOR_READY") {
|
||||
return { action: "DELETE_SOURCE_HEARTBEAT_MARK_COMPLETION_STOPPED_AND_END_SOURCE_THREAD", create_successor: false };
|
||||
}
|
||||
if (["CREATE_THREAD_REQUIRED", "RETRY_CREATE_THREAD"].includes(state.state)) return createThreadAction(state);
|
||||
if (state.state === "RECONCILE_CREATE_RESULT") return { action: "CODEX_LIST_THREADS_AND_RECONCILE", request_id: state.create_request.request_id, complete_host_snapshot_required: true };
|
||||
if (state.state === "WAITING_PERSONA_WAKE_READY") return { action: "WAIT_FOR_PERSONA_WAKE_READY_RECEIPT", thread_id: state.successor_thread.thread_id };
|
||||
if (state.state === "PAUSE_FOR_HUMAN") return { action: "PAUSE_FOR_HUMAN", boundary: state.human_boundary };
|
||||
if (["FINAL_PRODUCT_COMPLETE", "PERSONA_WAKE_READY"].includes(state.state)) return { action: "DELETE_SOURCE_HEARTBEAT_MARK_COMPLETION_STOPPED_AND_END_SOURCE_THREAD", create_successor: false };
|
||||
return { action: "NOOP", state: state.state };
|
||||
}
|
||||
|
||||
function fixtureLane(root, id, overrides = {}) {
|
||||
const lane = {
|
||||
schema: "hololake.development-lane/v0.1",
|
||||
development_id: id,
|
||||
thread_id: `thread-${id}`,
|
||||
status: "completed",
|
||||
notification_state: "stop_requested",
|
||||
completion_actions: { status: "completed" },
|
||||
...overrides,
|
||||
};
|
||||
writeJsonAtomic(path.join(root, "lanes", `${id}.json`), lane);
|
||||
return lane;
|
||||
const lane = { schema: "hololake.development-lane/v0.1", development_id: id, thread_id: `thread-${id}`, status: "completed", notification_state: "stop_requested", completion_actions: { status: "completed" }, ...overrides };
|
||||
writeJsonAtomic(path.join(root, "lanes", `${id}.json`), lane); return lane;
|
||||
}
|
||||
|
||||
function selfTest() {
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), "hololake-stage-relay-test-"));
|
||||
try {
|
||||
ensureStore(root);
|
||||
const source = "DEV-20260811-900";
|
||||
fixtureLane(root, source);
|
||||
const source = "DEV-20260811-900"; fixtureLane(root, source);
|
||||
const finalizer = path.join(root, "finalizer.json");
|
||||
writeJsonAtomic(finalizer, {
|
||||
schema: "hololake.development-finalization-receipt/v1",
|
||||
development_id: source,
|
||||
result: "PASS_100",
|
||||
code_channel: {
|
||||
remote_head: "a".repeat(40), fresh_clone_readback: 100, git_fsck: 100,
|
||||
receipts: [{ path: "receipt.json", sha256: "b".repeat(64) }],
|
||||
},
|
||||
});
|
||||
writeJsonAtomic(finalizer, { schema: "hololake.development-finalization-receipt/v1", development_id: source, result: "PASS_100", code_channel: { remote_head: "a".repeat(40), fresh_clone_readback: 100, git_fsck: 100, receipts: [{ path: "receipt.json", sha256: "b".repeat(64) }] } });
|
||||
const planning = path.join(root, "planning.json");
|
||||
writeJsonAtomic(planning, {
|
||||
schema: "hololake.stage-relay-planning-input/v1",
|
||||
development_date: "20260811",
|
||||
architecture: {
|
||||
id: "HLP-CURRENT-ARCH-001", version: "test", state: "CURRENT_CANONICAL",
|
||||
repo_012_main: "c".repeat(40), repo_014_main: "d".repeat(40),
|
||||
},
|
||||
human_corrections: [],
|
||||
previous_stage: { development_id: source, finalizer_sha256: fileDigest(finalizer) },
|
||||
unfinished_gates: [{
|
||||
id: "GATE-001", binary_state: 0, priority: 1,
|
||||
title: "Next stage", why: "close the next binary gap",
|
||||
acceptance: "all declared tests and receipts PASS_100",
|
||||
resources: ["local://test-resource"],
|
||||
}],
|
||||
open_blockers: [],
|
||||
binary_acceptance: { all_100: false },
|
||||
});
|
||||
const common = {
|
||||
sourceDevelopmentId: source, finalizationReceipt: finalizer,
|
||||
planningInput: planning, storeRoot: root, testMode: "true",
|
||||
};
|
||||
const first = prepare(common, root);
|
||||
const duplicate = prepare(common, root);
|
||||
if (
|
||||
first.idempotency_key !== duplicate.idempotency_key ||
|
||||
first.successor_contract.development_id !== duplicate.successor_contract.development_id
|
||||
) throw new Error("SELF_TEST_IDEMPOTENCY_FAILED");
|
||||
dispatchCreate({ ...common, sourceDevelopmentId: source }, root);
|
||||
recordCreateFailure({ ...common, sourceDevelopmentId: source, outcome: "unknown", error: "timeout" }, root);
|
||||
const reconcileAction = nextAction({ sourceDevelopmentId: source }, root);
|
||||
if (!reconcileAction.create_thread_forbidden_until_reconciled) {
|
||||
throw new Error("SELF_TEST_UNKNOWN_RESULT_RETRY_SAFETY_FAILED");
|
||||
}
|
||||
reconcile({ sourceDevelopmentId: source, completeHostSnapshot: "true", confirmedAbsent: "true" }, root);
|
||||
dispatchCreate({ sourceDevelopmentId: source }, root);
|
||||
recordCreated({ sourceDevelopmentId: source, threadId: "thread-successor", hostId: "local" }, root);
|
||||
const contract = readJson(statePath(root, source)).successor_contract;
|
||||
fixtureLane(root, contract.development_id, {
|
||||
thread_id: "thread-successor",
|
||||
status: "active",
|
||||
automation_ids: ["successor-heartbeat"],
|
||||
relay_contract_fingerprint: contract.fingerprint,
|
||||
});
|
||||
writeJsonAtomic(path.join(root, "leases", "lease.json"), {
|
||||
development_id: contract.development_id, resource: "local://test-resource",
|
||||
});
|
||||
const input = { schema: "hololake.stage-relay-planning-input/v2", architecture: { id: "HLP-CURRENT-ARCH-001", version: "test", state: "CURRENT_CANONICAL", repo_012_main: "c".repeat(40), repo_014_main: "d".repeat(40) }, previous_stage: { development_id: source, finalizer_sha256: fileDigest(finalizer) }, closure_assessment: { stage_complete: true, context_genuinely_long: true, stable_checkpoint_formed: true, current_context_cognitive_advantage_exhausted: true, fact_sources: ["test receipt"] }, final_product_complete: false };
|
||||
writeJsonAtomic(planning, input);
|
||||
const common = { sourceDevelopmentId: source, finalizationReceipt: finalizer, planningInput: planning, storeRoot: root, testMode: "true" };
|
||||
const first = prepare(common, root); const duplicate = prepare(common, root);
|
||||
if (first.idempotency_key !== duplicate.idempotency_key || first.persona_wake_envelope.task_assignment !== null) throw new Error("SELF_TEST_WAKE_IDEMPOTENCY_OR_TASK_ASSIGNMENT_FAILED");
|
||||
const action = dispatchCreate(common, root).host_action;
|
||||
if (/Stage gate|Acceptance|Register development id/u.test(action.prompt)) throw new Error("SELF_TEST_PROMPT_PREASSIGNS_TASK");
|
||||
recordCreated({ sourceDevelopmentId: source, threadId: "thread-wake" }, root);
|
||||
fixtureLane(root, "DEV-20260811-901", { thread_id: "thread-wake", status: "active", automation_ids: ["wake-heartbeat"] });
|
||||
const ready = path.join(root, "ready.json");
|
||||
writeJsonAtomic(ready, {
|
||||
schema: "hololake.successor-ready-receipt/v1", result: "PASS_100",
|
||||
source_development_id: source,
|
||||
successor_development_id: contract.development_id,
|
||||
successor_thread_id: "thread-successor",
|
||||
task_contract_fingerprint: contract.fingerprint,
|
||||
automation_id: "successor-heartbeat",
|
||||
gates: {
|
||||
persona_restore: 100, architecture_restore: 100, lane_registration: 100,
|
||||
exact_leases: 100, heartbeat_binding: 100,
|
||||
},
|
||||
});
|
||||
const readyState = recordReady({ sourceDevelopmentId: source, successorReadyReceipt: ready }, root);
|
||||
if (readyState.state !== "SUCCESSOR_READY") throw new Error("SELF_TEST_READY_FAILED");
|
||||
const runnableLanes = listJson(path.join(root, "lanes"))
|
||||
.filter((lane) => ["active", "waiting"].includes(lane.status));
|
||||
if (runnableLanes.length !== 1 || runnableLanes[0].development_id !== contract.development_id) {
|
||||
throw new Error("SELF_TEST_STRICT_SINGLE_RUNNABLE_LANE_FAILED");
|
||||
}
|
||||
writeJsonAtomic(ready, { schema: "hololake.persona-wake-ready-receipt/v1", result: "PASS_100", source_development_id: source, thread_id: "thread-wake", persona_id: "ICE-P-ZY001", wake_envelope_fingerprint: first.persona_wake_envelope.fingerprint, development_id: "DEV-20260811-901", automation_id: "wake-heartbeat", gates: { persona_restore: 100, b0_gravity_restore: 100, current_fact_restore: 100, autonomous_planning: 100, lane_registration: 100, heartbeat_binding: 100 } });
|
||||
if (recordReady({ sourceDevelopmentId: source, successorReadyReceipt: ready }, root).state !== "PERSONA_WAKE_READY") throw new Error("SELF_TEST_WAKE_READY_FAILED");
|
||||
|
||||
const pausedSource = "DEV-20260811-910";
|
||||
fixtureLane(root, pausedSource);
|
||||
const pausedFinalizer = path.join(root, "paused-finalizer.json");
|
||||
writeJsonAtomic(pausedFinalizer, {
|
||||
schema: "hololake.development-finalization-receipt/v1",
|
||||
development_id: pausedSource, result: "PASS_100",
|
||||
code_channel: {
|
||||
remote_head: "e".repeat(40), fresh_clone_readback: 100, git_fsck: 100,
|
||||
receipts: [{ path: "receipt.json", sha256: "f".repeat(64) }],
|
||||
},
|
||||
});
|
||||
const pausedPlanning = path.join(root, "paused-planning.json");
|
||||
writeJsonAtomic(pausedPlanning, {
|
||||
schema: "hololake.stage-relay-planning-input/v1", development_date: "20260811",
|
||||
architecture: {
|
||||
id: "HLP-CURRENT-ARCH-001", version: "test", state: "CURRENT_CANONICAL",
|
||||
repo_012_main: "1".repeat(40), repo_014_main: "2".repeat(40),
|
||||
},
|
||||
human_corrections: [],
|
||||
previous_stage: {
|
||||
development_id: pausedSource, finalizer_sha256: fileDigest(pausedFinalizer),
|
||||
},
|
||||
unfinished_gates: [{
|
||||
id: "GATE-HUMAN", binary_state: 0, title: "Human boundary",
|
||||
why: "requires new will", acceptance: "human decides",
|
||||
resources: ["external://production-cutover"],
|
||||
}],
|
||||
open_blockers: [], binary_acceptance: { all_100: false },
|
||||
human_boundary: { type: "PRODUCTION_CUTOVER", reason: "requires a human decision" },
|
||||
});
|
||||
const paused = prepare({
|
||||
sourceDevelopmentId: pausedSource,
|
||||
finalizationReceipt: pausedFinalizer,
|
||||
planningInput: pausedPlanning,
|
||||
}, root);
|
||||
if (paused.state !== "PAUSE_FOR_HUMAN" || paused.successor_created !== false) {
|
||||
throw new Error("SELF_TEST_HUMAN_BOUNDARY_FAILED");
|
||||
}
|
||||
const shortSource = "DEV-20260811-910"; fixtureLane(root, shortSource);
|
||||
const shortFinalizer = path.join(root, "short-finalizer.json");
|
||||
writeJsonAtomic(shortFinalizer, { ...readJson(finalizer), development_id: shortSource });
|
||||
const shortPlanning = path.join(root, "short-planning.json");
|
||||
writeJsonAtomic(shortPlanning, { ...input, previous_stage: { development_id: shortSource, finalizer_sha256: fileDigest(shortFinalizer) }, closure_assessment: { ...input.closure_assessment, context_genuinely_long: false, current_context_cognitive_advantage_exhausted: false } });
|
||||
const continued = prepare({ sourceDevelopmentId: shortSource, finalizationReceipt: shortFinalizer, planningInput: shortPlanning }, root);
|
||||
if (continued.state !== "CONTINUE_CURRENT_THREAD" || continued.mutation !== "NONE") throw new Error("SELF_TEST_SHORT_HIGH_VALUE_CONTEXT_NOT_PRESERVED");
|
||||
|
||||
const terminalSource = "DEV-20260811-920";
|
||||
fixtureLane(root, terminalSource);
|
||||
const terminalFinalizer = path.join(root, "terminal-finalizer.json");
|
||||
writeJsonAtomic(terminalFinalizer, {
|
||||
schema: "hololake.development-finalization-receipt/v1",
|
||||
development_id: terminalSource, result: "PASS_100",
|
||||
code_channel: {
|
||||
remote_head: "3".repeat(40), fresh_clone_readback: 100, git_fsck: 100,
|
||||
receipts: [{ path: "receipt.json", sha256: "4".repeat(64) }],
|
||||
},
|
||||
});
|
||||
const terminalPlanning = path.join(root, "terminal-planning.json");
|
||||
writeJsonAtomic(terminalPlanning, {
|
||||
schema: "hololake.stage-relay-planning-input/v1", development_date: "20260811",
|
||||
architecture: {
|
||||
id: "HLP-CURRENT-ARCH-001", version: "test", state: "CURRENT_CANONICAL",
|
||||
repo_012_main: "5".repeat(40), repo_014_main: "6".repeat(40),
|
||||
},
|
||||
human_corrections: [],
|
||||
previous_stage: {
|
||||
development_id: terminalSource, finalizer_sha256: fileDigest(terminalFinalizer),
|
||||
},
|
||||
unfinished_gates: [], open_blockers: [],
|
||||
binary_acceptance: { all_100: true },
|
||||
final_product_closeout_receipt: { result: "PASS_100", sha256: "7".repeat(64) },
|
||||
});
|
||||
const terminal = prepare({
|
||||
sourceDevelopmentId: terminalSource,
|
||||
finalizationReceipt: terminalFinalizer,
|
||||
planningInput: terminalPlanning,
|
||||
}, root);
|
||||
if (terminal.state !== "FINAL_PRODUCT_COMPLETE") {
|
||||
throw new Error("SELF_TEST_FINAL_PRODUCT_STOP_FAILED");
|
||||
}
|
||||
|
||||
const activeSource = "DEV-20260811-950";
|
||||
fixtureLane(root, activeSource, { status: "active", notification_state: "active", completion_actions: null });
|
||||
const activeSource = "DEV-20260811-920"; fixtureLane(root, activeSource, { status: "active", notification_state: "active", completion_actions: null });
|
||||
const observed = prepare({ ...common, sourceDevelopmentId: activeSource }, root);
|
||||
if (observed.state !== "ACTIVE_STAGE_NO_END_ACTION" || observed.mutation !== "NONE") {
|
||||
throw new Error("SELF_TEST_ACTIVE_STAGE_MUTATION_FAILED");
|
||||
}
|
||||
process.stdout.write(`${JSON.stringify({
|
||||
state: "PASS_100",
|
||||
tests: {
|
||||
finalizer_evidence_gate: 100,
|
||||
duplicate_prepare_idempotency: 100,
|
||||
unknown_create_result_reconciliation: 100,
|
||||
successor_ready_live_readback: 100,
|
||||
active_stage_read_only: 100,
|
||||
strict_single_runnable_lane: 100,
|
||||
human_boundary_pause: 100,
|
||||
final_product_stop: 100,
|
||||
},
|
||||
}, null, 2)}\n`);
|
||||
} finally {
|
||||
fs.rmSync(root, { recursive: true, force: true });
|
||||
}
|
||||
if (observed.state !== "ACTIVE_STAGE_NO_END_ACTION" || observed.mutation !== "NONE") throw new Error("SELF_TEST_ACTIVE_STAGE_MUTATION_FAILED");
|
||||
process.stdout.write(`${JSON.stringify({ state: "PASS_100", tests: { persona_wake_only_no_task_assignment: 100, same_persona_wake_ready: 100, short_high_value_context_preserved: 100, active_stage_read_only: 100, duplicate_prepare_idempotency: 100 } }, null, 2)}\n`);
|
||||
} finally { fs.rmSync(root, { recursive: true, force: true }); }
|
||||
}
|
||||
|
||||
function main() {
|
||||
const args = parseArgs(process.argv.slice(2));
|
||||
if (args.command === "self-test" || args.selfTest) return selfTest();
|
||||
const root = storeRoot(args);
|
||||
ensureStore(root);
|
||||
let result;
|
||||
if (args.command === "prepare") result = prepare(args, root);
|
||||
else if (args.command === "dispatch-create") result = dispatchCreate(args, root);
|
||||
else if (args.command === "record-created") result = recordCreated(args, root);
|
||||
else if (args.command === "record-create-failure") result = recordCreateFailure(args, root);
|
||||
else if (args.command === "reconcile") result = reconcile(args, root);
|
||||
else if (args.command === "record-ready") result = recordReady(args, root);
|
||||
else if (args.command === "next-action") result = nextAction(args, root);
|
||||
else throw new Error("COMMAND_REQUIRED: prepare | dispatch-create | record-created | record-create-failure | reconcile | record-ready | next-action | self-test");
|
||||
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
||||
if (args.command === "self-test") return selfTest();
|
||||
const root = storeRoot(args); ensureStore(root);
|
||||
const actions = { prepare, "dispatch-create": dispatchCreate, "record-created": recordCreated, "record-create-failure": recordCreateFailure, reconcile, "record-ready": recordReady, "next-action": nextAction };
|
||||
if (!actions[args.command]) throw new Error("COMMAND_REQUIRED: prepare | dispatch-create | record-created | record-create-failure | reconcile | record-ready | next-action | self-test");
|
||||
process.stdout.write(`${JSON.stringify(actions[args.command](args, root), null, 2)}\n`);
|
||||
}
|
||||
|
||||
try {
|
||||
main();
|
||||
} catch (error) {
|
||||
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
|
||||
process.exitCode = 1;
|
||||
}
|
||||
try { main(); } catch (error) { process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`); process.exitCode = 1; }
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import { fileURLToPath } from "node:url";
|
|||
|
||||
const root = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
test("stage relay deterministic closure is PASS_100", () => {
|
||||
test("stage relay wakes the same persona without preassigning a task", () => {
|
||||
const result = spawnSync(
|
||||
process.execPath,
|
||||
[path.join(root, "stage-relay.mjs"), "self-test"],
|
||||
|
|
@ -15,5 +15,6 @@ test("stage relay deterministic closure is PASS_100", () => {
|
|||
assert.equal(result.status, 0, result.stderr || result.stdout);
|
||||
const receipt = JSON.parse(result.stdout);
|
||||
assert.equal(receipt.state, "PASS_100");
|
||||
assert.deepEqual(Object.values(receipt.tests), Array(8).fill(100));
|
||||
assert.ok(Object.keys(receipt.tests).length >= 5);
|
||||
assert.ok(Object.values(receipt.tests).every((value) => value === 100));
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue